深刻理解 JavaScript 本身(闭包、原型链、事件循环、Promise)
h5打开以查看
1. 闭包 (Closure)
一句话概括: 闭包是一个函数与其被创建时的词法作用域(lexical scope)的组合。
深入理解:
-
词法作用域(静态作用域):函数的作用域在函数定义的时候就已经决定了,而不是在执行的时候。它取决于代码书写的位置。
-
机制:当一个内部函数引用了其外部函数作用域中的变量时,即使外部函数已经执行完毕并返回,JavaScript 的垃圾回收机制也不会回收外部函数的作用域链。这个内部函数依然持有对外部作用域的引用,这就是闭包。
例子与作用:
javascript
function createCounter() {
let count = 0; // 外部作用域的变量
return function() {
count++; // 内部函数引用了外部变量
return count;
};
}
const myCounter = createCounter();
console.log(myCounter()); // 1
console.log(myCounter()); // 2
// 尽管 createCounter() 已执行完,但 myCounter 仍能访问和修改 count
-
数据私有化:如上例,
count变量对外是完全隐藏的,只能通过myCounter这个函数来操作,实现了类似“私有变量”的功能。 -
回调函数与异步编程:在事件监听、setTimeout、Ajax 请求等场景中,回调函数常常形成闭包,记住了它被定义时的环境。
-
函数工厂:像
createCounter这样的函数可以生成多个独立的计数器,每个都有自己独立的count变量。
常见误区:在循环中使用闭包,如果不正确使用 let (块级作用域) 或立即执行函数(IIFE),会导致所有闭包都引用循环变量的最终值。
2. 原型链 (Prototype Chain)
一句话概括: JavaScript 实现继承的主要机制,每个对象都有一个指向其原型的内部链接,形成一条链,用于属性和方法的查找。
深入理解:
-
__proto__(隐式原型):每个对象都有一个__proto__属性,指向它的构造函数的prototype对象。这是一个访问器属性(现代代码中更推荐使用Object.getPrototypeOf(obj))。 -
prototype(显式原型):只有函数才有prototype属性。当你使用new关键字调用函数时,新创建对象的__proto__就会指向该函数的prototype。 -
查找机制:当你访问一个对象的属性时,JS 引擎会先在对象自身查找。如果没找到,就通过
__proto__去它的原型上找,再没找到就去原型的原型上找,直到找到null(原型链的尽头)为止。这就是原型链。
例子与图示:
javascript
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() { // 方法定义在原型上
console.log(`Hello, I'm ${this.name}`);
};
const alice = new Person('Alice');
alice.sayHello(); // 1. alice 自身没有 sayHello
// 2. 查找 alice.__proto__ (即 Person.prototype)
// 3. 找到了,调用
console.log(alice.__proto__ === Person.prototype); // true
console.log(Person.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__); // null
作用:
-
实现继承:子构造函数的
prototype指向父构造函数的一个实例,从而实现方法和属性的共享与继承。 -
共享方法:将方法定义在
prototype上,而不是构造函数内部。这样所有实例共享同一个方法,极大节省了内存。定义在构造函数内部的属性则是每个实例独有的。
现代语法:ES6 的 class 关键字本质上是原型继承的语法糖,它让继承的写法更清晰、更接近于传统面向对象语言,但底层依然是原型链。
3. 事件循环 (Event Loop)
一句话概括: JavaScript 管理异步回调的机制,它决定了代码的执行顺序,是实现“非阻塞”行为的关键。
深入理解:
JavaScript 是单线程的,意味着它一次只能做一件事。事件循环机制让它能够处理高并发操作而不阻塞。
核心组件:
-
调用栈 (Call Stack):执行同步代码的地方,函数调用会形成一个栈帧。
-
Web APIs (在浏览器中) / C++ APIs (在 Node.js 中):提供异步功能(如
setTimeout,fs.readFile,fetch)。当调用这些 API 时,会将其回调函数和相应任务交给它们处理,调用栈继续执行后续代码。 -
任务队列 (Callback Queue / Task Queue):Web/C++ API 处理完任务后,会将对应的回调函数放入任务队列中等待。
-
微任务队列 (Microtask Queue):一个优先级更高的队列,用于存放
Promise.then/catch/finally的回调和MutationObserver等。 -
事件循环 (Event Loop):它像一个永恒的循环,不断地做一件事:
-
检查调用栈是否为空。
-
如果调用栈为空,它先检查微任务队列,将其中的所有任务全部执行完毕(清空)。
-
然后,从任务队列中取出一个任务(如 setTimeout 回调)放入调用栈执行。
-
重复此过程。
-
例子与顺序:
javascript
console.log('1. Start');
setTimeout(() => console.log('2. Timeout'), 0);
Promise.resolve()
.then(() => console.log('3. Promise'));
console.log('4. End');
// 输出顺序:
// 1. Start
// 4. End
// 3. Promise (微任务优先)
// 2. Timeout (宏任务后)
为什么重要? 在 Node.js 中,正是这个机制使得单线程能够高效处理成千上万的网络请求、文件操作等 I/O 密集型任务,而不会因为等待响应而卡住。
4. Promise
一句话概括: Promise 是一个对象,它代表了一个异步操作的最终完成(或失败) 及其结果值。它是管理异步回调的现代化解决方案。
深入理解:
-
三种状态:
-
pending(进行中):初始状态。
-
fulfilled(已成功):操作成功完成。
-
rejected(已失败):操作失败。
状态一旦改变就不可逆(从 pending 变为 fulfilled 或 rejected)。
-
-
解决回调地狱:Promise 的
.then()和.catch()方法允许我们进行链式调用(Chaining),将纵向增长的嵌套回调变成了横向增长的扁平代码,大大提高了可读性和可维护性。 -
错误冒泡:链中的任何一个环节出错,错误都会一直向后传递,直到被最近的
.catch()捕获。这提供了比回调函数更强大的错误处理能力。 -
与事件循环的关系:Promise 的回调(
.then,.catch,.finally)是微任务,因此它们比setTimeout等宏任务具有更高的优先级。
例子:
javascript
// 模拟一个异步操作
function asyncOperation(data) {
return new Promise((resolve, reject) => {
setTimeout(() => {
if (Math.random() > 0.3) {
resolve(`Success! Data: ${data}`); // 成功,改变状态为 fulfilled
} else {
reject(new Error('Failed!')); // 失败,改变状态为 rejected
}
}, 1000);
});
}
// 使用 Promise 链式调用
asyncOperation('Hello')
.then(result => {
console.log(result);
return asyncOperation('World'); // 返回一个新的 Promise,继续链式调用
})
.then(anotherResult => {
console.log(anotherResult);
})
.catch(error => { // 捕获链中任何位置的错误
console.error('An error occurred:', error);
});
async/await:ES8 的 async/await 是建立在 Promise 之上的语法糖,它让你能用写同步代码的方式去写异步代码,进一步提升了可读性。await 会“暂停” async 函数的执行,等待 Promise 的解决。
总结与联系
-
闭包 让你能在异步回调中记住并访问定义时的上下文。
-
事件循环 是 JavaScript 处理异步的核心机制,它管理着调用栈和任务队列。
-
Promise 是管理异步操作的现代化工具,它利用事件循环的微任务队列提供了更优雅的链式调用和错误处理。
-
原型链 是 JavaScript 对象系统的基础构建模式,与异步特性关系稍远,但决定了语言如何组织代码和实现复用。
更多推荐
所有评论(0)