数据类型
1. JS 中有哪些基本数据类型?
答案:
-
基本数据类型(Primitive Types) :
Undefined
:声明未赋值的变量Null
:空值Boolean
:true/false
Number
:整数/浮点数(包括Infinity
,NaN
)String
:字符串BigInt
:大整数(ES2020)Symbol
:唯一值(ES6)
-
引用类型(Reference Type) :
Object
(包含Array
,Function
,Date
等)
解析:
- 基本类型存储在栈内存,按值访问。
- 引用类型存储在堆内存,按引用访问。
typeof null
返回"object"
(历史遗留问题)。
2. 如何判断数据类型?
答案:
typeof variable
:返回类型字符串,无法区分数组/对象。variable instanceof Constructor
:检测原型链。Object.prototype.toString.call(variable)
:最准确方式。
示例:
1
2console.log(Object.prototype.toString.call([]));
3console.log([] instanceof Array);
变量与作用域
3. var、let、const 的区别?
答案:
特性 | var | let | const |
---|---|---|---|
作用域 | 函数级 | 块级 | 块级 |
变量提升 | 是 | 否(TDZ) | 否(TDZ) |
重复声明 | 允许 | 禁止 | 禁止 |
重新赋值 | 允许 | 允许 | 不允许 |
解析:
- TDZ(暂时性死区) :声明前访问会报错。
- const 必须初始化,但对象属性可修改。
闭包
4. 什么是闭包?应用场景?
答案: 闭包是能够访问其他函数内部变量的函数。常见场景:
- 封装私有变量
- 模块化开发
- 函数柯里化
示例:
1
2function createCounter() {
3 let count = 0;
4 return function() {
5 return ++count;
6 };
7}
8const counter = createCounter();
9console.log(counter());
解析: 闭包会导致外部函数的作用域无法释放,可能引发内存泄漏。
原型与继承
5. 描述原型链机制
答案:
- 每个对象都有
__proto__
指向构造函数的prototype
。 - 访问属性时,若对象本身没有,则沿原型链查找。
- 终点是
Object.prototype.__proto__
(null
)。
示例:
1
2function Person(name) { this.name = name; }
3Person.prototype.sayName = function() { console.log(this.name); };
4
5const p = new Person('Alice');
6p.sayName();
异步编程
6. 事件循环(Event Loop)机制
答案:
- JS 是单线程,通过事件循环处理异步。
- 调用栈执行同步任务。
- 异步任务进入任务队列(宏任务:
setTimeout
,微任务:Promise.then
)。 - 每次循环先清空微任务队列,再执行一个宏任务。
示例:
1
2setTimeout(() => console.log('宏任务'), 0);
3Promise.resolve().then(() => console.log('微任务'));
4
ES6+ 新特性
7. 箭头函数与普通函数的区别?
答案:
- 语法更简洁(
() => {}
)。 - 没有自己的
this
,继承外层。 - 不能作为构造函数。
- 没有
arguments
对象,可用剩余参数...args
。
其他重要概念
8. 深拷贝 vs 浅拷贝
答案:
- 浅拷贝:复制一层属性(
Object.assign
, 展开运算符)。 - 深拷贝:递归复制所有层级(
JSON.parse(JSON.stringify(obj))
,手动递归,lodash.cloneDeep
)。
注意: JSON
方法会忽略undefined
和函数。
Promise 实现
9. 手写 Promise 的核心实现
答案:
1
2class MyPromise {
3 constructor(executor) {
4 this.state = 'pending';
5 this.value = undefined;
6 this.onResolvedCallbacks = [];
7 this.onRejectedCallbacks = [];
8
9 const resolve = (value) => {
10 if (this.state !== 'pending') return;
11 this.state = 'fulfilled';
12 this.value = value;
13 this.onResolvedCallbacks.forEach(fn => fn());
14 };
15
16 const reject = (reason) => {
17 if (this.state !== 'pending') return;
18 this.state = 'rejected';
19 this.value = reason;
20 this.onRejectedCallbacks.forEach(fn => fn());
21 };
22
23 try {
24 executor(resolve, reject);
25 } catch (err) {
26 reject(err);
27 }
28 }
29
30 then(onFulfilled, onRejected) {
31 return new MyPromise((resolve, reject) => {
32 const handleCallback = (callback, value) => {
33 try {
34 const result = callback?.(value);
35 result instanceof MyPromise ?
36 result.then(resolve, reject) :
37 resolve(result);
38 } catch (err) {
39 reject(err);
40 }
41 };
42
43 if (this.state === 'fulfilled') {
44 setTimeout(() => handleCallback(onFulfilled, this.value));
45 } else if (this.state === 'rejected') {
46 setTimeout(() => handleCallback(onRejected, this.value));
47 } else {
48 this.onResolvedCallbacks.push(() =>
49 setTimeout(() => handleCallback(onFulfilled, this.value)));
50 this.onRejectedCallbacks.push(() =>
51 setTimeout(() => handleCallback(onRejected, this.value)));
52 }
53 });
54 }
55}
解析:
- 状态不可逆:Promise 状态只能从
pending
变更为fulfilled
或rejected
。 - 链式调用:
.then
返回新 Promise,支持链式调用。 - 异步执行:回调函数通过
setTimeout
模拟微任务队列(实际 Promise 使用微任务)。
防抖与节流
10. 防抖(Debounce)与节流(Throttle)的区别与实现
区别:
防抖 | 节流 | |
---|---|---|
核心 | 事件结束后触发 | 固定时间间隔触发 |
场景 | 搜索框输入联想 | 滚动事件、窗口调整 |
实现 | 每次触发重置定时器 | 通过时间戳或定时器控制频率 |
防抖实现:
1
2function debounce(fn, delay) {
3 let timer = null;
4 return function(...args) {
5 clearTimeout(timer);
6 timer = setTimeout(() => fn.apply(this, args), delay);
7 };
8}
节流实现(时间戳版):
1
2function throttle(fn, interval) {
3 let lastTime = 0;
4 return function(...args) {
5 const now = Date.now();
6 if (now - lastTime >= interval) {
7 fn.apply(this, args);
8 lastTime = now;
9 }
10 };
11}
this 指向
11. 如何确定函数中的 this
?
规则优先级:
- new 绑定:
new Foo()
→this
指向新对象。 - 显式绑定:
call/apply/bind
→ 指向指定对象。 - 隐式绑定:
obj.fn()
→ 指向调用者obj
。 - 默认绑定:全局对象(严格模式下为
undefined
)。 - 箭头函数:继承外层作用域的
this
。
示例:
1
2const obj = {
3 name: 'Alice',
4 sayName: function() { console.log(this.name) },
5 sayNameArrow: () => console.log(this.name)
6};
7obj.sayName();
8obj.sayNameArrow();
模块化
12. CommonJS 与 ES Module 的区别
特性 | CommonJS | ES Module |
---|---|---|
加载方式 | 同步加载(动态导入) | 异步加载(静态解析) |
输出 | module.exports | export /export default |
导入 | require() | import |
执行顺序 | 运行时解析 | 编译时解析(静态) |
适用场景 | Node.js 环境 | 浏览器/现代前端框架 |
CommonJS 示例:
1
2
3module.exports = { add: (a, b) => a + b };
4
5const math = require('./math.js');
6math.add(2, 3);
ES Module 示例:
1
2
3export const add = (a, b) => a + b;
4
5import { add } from './math.js';
6add(2, 3);
POST 与 GET 请求的区别
13. 核心区别
特性 | GET | POST |
---|---|---|
参数位置 | URL 查询字符串(?key=value ) | 请求体(Body) |
安全性 | 参数暴露在 URL 中 | 参数在请求体中,相对安全 |
缓存 | 可被缓存 | 默认不缓存 |
幂等性 | 幂等(多次请求结果相同) | 非幂等(可能修改数据) |
长度限制 | 受浏览器 URL 长度限制 | 无限制 |
使用场景:
- GET:获取数据(如搜索、分页)。
- POST:提交数据(如登录、表单提交)。
重绘(Repaint)与重排(Reflow)的区别
14. 浏览器渲染机制中的关键概念
重绘(Repaint) | 重排(Reflow) | |
---|---|---|
定义 | 元素外观变化(颜色、背景等) | 布局或几何属性变化(尺寸、位置等) |
触发 | color , visibility , background | width , height , margin , display |
性能 | 开销较小 | 开销较大(可能引发整个文档重排) |
优化建议:
- 避免频繁操作 DOM,使用
DocumentFragment
或虚拟 DOM。 - 使用 CSS3 动画(
transform
、opacity
)代替直接修改布局属性。 - 批量修改样式(通过
class
切换而非逐行修改)。
深入核心概念
15. 异步编程的常见实现方式?
答案:
- 回调函数:传统异步方案,但易导致“回调地狱”。
- Promise:链式调用,解决嵌套问题。
- async/await:基于 Promise 的语法糖,以同步方式写异步代码。
- Generator 函数:通过
yield
暂停执行(需配合执行器如co
库)。 - 事件监听:如
EventEmitter
模式。
示例(async/await):
1
2async function fetchData() {
3 try {
4 const res = await fetch('https://api.example.com/data');
5 const data = await res.json();
6 console.log(data);
7 } catch (err) {
8 console.error('请求失败:', err);
9 }
10}
16. 原型链的实际应用场景?
答案:
- 继承机制:通过
prototype
实现对象间属性和方法的共享。 - 内置方法扩展:如为
Array
添加自定义方法。 - 性能优化:共享方法减少内存占用。
示例(方法扩展):
1
2Array.prototype.last = function() {
3 return this[this.length - 1];
4};
5console.log([1, 2, 3].last());
17. 闭包的内存管理问题?
答案:
-
内存泄漏风险:闭包会长期持有外部函数变量的引用,导致无法被垃圾回收。
-
解决方案:
- 及时解除引用:
fn = null
- 避免循环引用
- 使用弱引用(如
WeakMap
)
- 及时解除引用:
JS 延迟加载方式
18. 如何实现 JS 延迟加载?
答案:
方法 | 说明 |
---|---|
defer 属性 | 异步下载,HTML 解析完成后按顺序执行(适用于依赖 DOM 的脚本) |
async 属性 | 异步下载,下载完成后立即执行(适用于独立无依赖的脚本) |
动态脚本插入 | 通过 document.createElement('script') 动态加载 |
setTimeout 延迟 | 延迟执行代码(不推荐,可能阻塞渲染) |
最佳实践:
1
2<script defer src="app.js"></script>
3<script async src="analytics.js"></script>
call、apply、bind 的区别
19. 三者核心区别与使用场景?
答案:
方法 | 传参方式 | 执行时机 | 使用场景 |
---|---|---|---|
call | 参数逐个传递 (a, b ) | 立即执行 | 明确参数数量时 |
apply | 参数数组传递 ([a, b] ) | 立即执行 | 参数数量不确定(如数组处理) |
bind | 参数逐个传递 | 返回绑定后的函数 | 需要延迟执行或作为回调 |
示例:
1
2function greet(message) {
3 console.log(`${message}, ${this.name}`);
4}
5const user = { name: 'Alice' };
6
7greet.call(user, 'Hello');
8greet.apply(user, ['Hi']);
9const boundGreet = greet.bind(user, 'Hey');
10boundGreet();
new 操作符
20. new 操作符的底层实现步骤?
答案:
- 创建空对象:
const obj = {}
- 链接原型:
obj.__proto__ = Constructor.prototype
- 绑定 this:执行构造函数,
this
指向新对象 - 返回对象:若构造函数返回对象则使用该对象,否则返回
obj
手动实现 new
:
1
2function myNew(Constructor, ...args) {
3 const obj = Object.create(Constructor.prototype);
4 const result = Constructor.apply(obj, args);
5 return result instanceof Object ? result : obj;
6}
“use strict” 严格模式
21. 严格模式的主要作用?
答案:
- 消除静默错误:未声明变量赋值会报错(
a = 10
→ReferenceError
) - 禁止删除不可删除属性:
delete Object.prototype
→ 报错 - 函数参数不能重名:
function(a, a) {}
→ 报错 - this 默认不指向全局:未绑定的
this
为undefined
- 禁用
with
语句:避免作用域混乱
示例:
1
2'use strict';
3function test() {
4 console.log(this);
5}
6test();
事件冒泡与事件委托
22. 事件传播机制与委托优化
答案:
- 事件冒泡:事件从目标元素向上传播到根元素(
<div> → <body> → <html>
)。 - 事件捕获:从根元素向下传播到目标元素(与冒泡相反)。
- 事件委托:利用冒泡机制,将事件监听器绑定到父元素,通过
event.target
处理子元素事件。
示例(委托优化列表点击):
1
2document.getElementById('list').addEventListener('click', function(e) {
3 if (e.target.tagName === 'LI') {
4 console.log('点击了:', e.target.textContent);
5 }
6});
优点:
- 减少内存消耗(无需为每个子元素绑定监听器)
- 动态新增元素无需重新绑定
JavaScript 作用域链
23. 作用域链的查找规则?
答案:
- 词法作用域:函数定义时确定作用域链,与调用位置无关。
- 链式结构:当前作用域 → 外层作用域 → … → 全局作用域。
- 变量查找:沿作用域链逐级向上查找,未找到则报
ReferenceError
。
示例:
1
2const globalVar = 'global';
3function outer() {
4 const outerVar = 'outer';
5 function inner() {
6 console.log(outerVar);
7 console.log(globalVar);
8 }
9 inner();
10}
11outer();
堆(Heap)与栈(Stack)的区别
24. 内存管理中的堆与栈
答案:
栈(Stack) | 堆(Heap) | |
---|---|---|
存储内容 | 基本类型值、函数调用上下文(指针) | 引用类型对象(复杂数据结构) |
分配方式 | 系统自动分配/释放(FILO) | 手动分配(开发者申请,GC回收) |
访问速度 | 快(内存连续) | 慢(内存碎片化) |
空间限制 | 固定大小(可能栈溢出) | 灵活(受物理内存限制) |
示例:
1
2let a = 10;
3let b = { name: 'Bob' };
25. 原型链继承的实现方式与优缺点
答案: 实现方式:
-
原型链继承:子类原型指向父类实例
1 2function Parent() { this.name = 'Parent'; } 3Parent.prototype.say = function() { console.log(this.name); }; 4 5function Child() {} 6Child.prototype = new Parent(); 7const child = new Child(); 8child.say();
- 缺点:所有子类实例共享父类引用属性(如数组)。
-
构造函数继承:在子类中调用父类构造函数
1 2function Child() { 3 Parent.call(this); 4}
- 缺点:无法继承父类原型方法。
-
组合继承(原型链 + 构造函数):
1 2function Child() { 3 Parent.call(this); 4} 5Child.prototype = new Parent(); 6Child.prototype.constructor = Child;
- 缺点:父类构造函数被调用两次。
-
ES6 Class 继承:
1 2class Parent { 3 constructor() { this.name = 'Parent'; } 4 say() { console.log(this.name); } 5} 6class Child extends Parent { 7 constructor() { super(); } 8} 9const child = new Child(); 10child.say();
总结:ES6 class
语法糖最简洁,底层基于原型链。
26. 闭包内存管理的实际案例与解决方案
案例:
1
2function createHeavyObject() {
3 const bigData = new Array(1000000).fill('data');
4 return function() { console.log(bigData[0]); };
5}
6const closure = createHeavyObject();
解决方案:
-
手动释放引用:
1 2closure = null;
-
使用 WeakMap(弱引用):
1 2const wm = new WeakMap(); 3function createSafeClosure() { 4 const bigData = new Array(1000000).fill('data'); 5 wm.set(this, bigData); 6 return () => console.log(wm.get(this)?.[0]); 7}
27. 事件循环(Event Loop)的深入解析
执行顺序规则:
- 同步任务:直接进入调用栈执行。
- 微任务(
Promise.then
,MutationObserver
):在每一个宏任务结束后立即执行。 - 宏任务(
setTimeout
,setInterval
, I/O):等待下一个事件循环。
示例:
1
2console.log('1');
3setTimeout(() => console.log('2'), 0);
4Promise.resolve().then(() => console.log('3'));
5console.log('4');
6
28. Promise 链式调用的错误处理
链式调用规则:
- 每个
.then
返回新 Promise。 - 使用
.catch
捕获链中任意位置的错误。
示例:
1
2fetch('url')
3 .then(res => res.json())
4 .then(data => processData(data))
5 .catch(err => console.error('链中任何错误:', err));
async/await 优化:
1
2async function fetchData() {
3 try {
4 const res = await fetch('url');
5 const data = await res.json();
6 return processData(data);
7 } catch (err) {
8 console.error('错误捕获:', err);
9 }
10}
29. 渐进增强(Progressive Enhancement)与优雅降级(Graceful Degradation)
概念 | 核心思想 | 适用场景 |
---|---|---|
渐进增强 | 从基础功能开始,逐步增加高级功能(优先保证基础体验) | 面向未来、兼容旧浏览器 |
优雅降级 | 先实现完整功能,再处理兼容性问题(优先高级浏览器,降级适配低版本) | 快速开发、内部系统 |
示例:
- 渐进增强:先实现
<input type="text">
,再通过 JavaScript 增强为日期选择器。 - 优雅降级:使用 CSS3 动画,低版本浏览器回退为 JavaScript 动画。
30. Web Worker 与 WebSocket 的区别
特性 | Web Worker | WebSocket |
---|---|---|
用途 | 多线程计算,避免阻塞主线程 | 实时双向通信(如聊天室、股票行情) |
通信方式 | 通过 postMessage 传递消息 | 基于 TCP 的全双工通信(ws:// 协议) |
生命周期 | 需手动终止 (worker.terminate() ) | 长连接,持续通信 |
DOM 访问 | 无法直接操作 DOM | 通常由主线程处理数据 |
31. JavaScript 垃圾回收机制
主要算法:
-
标记清除(Mark-Sweep) :
- 从根对象(全局变量、活动函数)出发,标记所有可达对象。
- 清除未标记对象。
-
引用计数(Reference Counting) :
- 记录每个对象的引用次数,次数为0时回收。
- 缺陷:循环引用无法回收(现代浏览器已弃用)。
内存泄漏场景:
- 未清理的定时器:
setInterval
- 未解除的 DOM 引用:
const element = document.getElementById('id')
- 闭包未释放
32. WeakSet 与 WeakMap 的特性
特性 | WeakSet/WeakMap | Set/Map |
---|---|---|
键类型 | 只接受对象作为键 | 任意类型 |
弱引用 | 键是弱引用,不计入垃圾回收机制 | 强引用,阻止垃圾回收 |
可迭代性 | 不可迭代 | 支持 for...of 遍历 |
用例 | 存储 DOM 节点(自动释放) | 通用数据存储 |
示例(WeakMap 缓存):
1
2const cache = new WeakMap();
3function getData(obj) {
4 if (cache.has(obj)) return cache.get(obj);
5 const data = ;
6 cache.set(obj, data);
7 return data;
8}
33. Shadow DOM 的概念与作用
定义:Shadow DOM 是 Web Components 的核心技术之一,用于创建封装的 DOM 子树,与主文档 DOM 隔离。
特点:
- 样式封装:内部样式不影响外部,外部样式默认不影响内部。
- DOM 隔离:通过
shadowRoot
访问内部 DOM。 - 自定义元素:结合
customElements.define
创建可重用组件。
示例:
1
2class MyComponent extends HTMLElement {
3 constructor() {
4 super();
5 const shadow = this.attachShadow({ mode: 'open' });
6 shadow.innerHTML = `
7 <style>p { color: red; }</style>
8 <p>Shadow DOM 内容</p>
9 `;
10 }
11}
12customElements.define('my-component', MyComponent);
34. 递归(Recursion)的应用与注意事项
定义:函数直接或间接调用自身,通过分解问题为更小的同类子问题来求解。
示例(阶乘):
1
2function factorial(n) {
3 if (n <= 1) return 1;
4 return n * factorial(n - 1);
5}
6console.log(factorial(5));
注意事项:
- 终止条件:必须存在明确的递归结束条件。
- 性能问题:深度递归可能导致栈溢出(可用尾递归优化或循环替代)。
35. 纯函数、高阶函数与睡眠函数
纯函数(Pure Function) :
-
相同输入始终返回相同输出。
-
无副作用(不修改外部状态)。
-
示例:
1 2function add(a, b) { return a + b; }
高阶函数(Higher-Order Function) :
-
接收函数作为参数或返回函数。
-
示例:
1 2function map(arr, fn) { return arr.map(fn); }
睡眠函数(Sleep Function) :
-
模拟延迟执行,基于 Promise 实现。
-
示例:
1 2function sleep(ms) { 3 return new Promise(resolve => setTimeout(resolve, ms)); 4} 5async function demo() { 6 await sleep(2000); 7 console.log('2秒后输出'); 8}
36. 实现数组随机排序(Fisher-Yates 算法)
问题:编写 randomSort
函数,实现均匀随机排序。
正确实现(避免 arr.sort(() => Math.random() - 0.5)
的不均匀问题) :
1
2function randomSort(arr) {
3 const result = [...arr];
4 for (let i = result.length - 1; i > 0; i--) {
5 const j = Math.floor(Math.random() * (i + 1));
6 [result[i], result[j]] = [result[j], result[i]];
7 }
8 return result;
9}
10
11
12console.log(randomSort([1, 2, 3, 4, 5]));
解析:
- Fisher-Yates 算法:从后向前遍历,保证每个位置的概率均匀。
- 时间复杂度:O(n),效率优于排序算法。