闭包
预计阅读时间: 5 分钟
什么是闭包
MDN的解释如下:
闭包(closure)是一个函数以及其捆绑的周边环境状态(lexical environment,词法环境)的引用的组合。换而言之,闭包让开发者可以从内部函数访问外部函数的作用域。在 JavaScript 中,闭包会随着函数的创建而被同时创建。
1function outer() {
2 const a = 0;
3
4 function inner() {
5 console.log('This is inner, a:', a);
6 }
7 return inner;
8}
9const func = outer();
10func(); // This is inner, a: 0
上面的例子中,inner 函数就是引用了外层函数 outer 作用域中的变量 a。
这种一般就是内部引用外部的变量,但如何在外部 引用 / 修改 内部的变量呢?
外部修改内部值
- 暴露一个修改接口
1function outer() {
2 let a = 0;
3
4 function reset(num) {
5 a = num;
6 console.log('This is inner, a:', a);
7 }
8 return reset;
9}
10const func = outer();
11func(1); // This is inner, a: 1
- 通过函数修改对象比如下面这道面试题:
1const ctrl = (() => {
2 const a = {
3 c: 'zzz'
4 }
5 return {
6 get(key) {
7 return a[key]
8 }
9 }
10})()
11
12ctrl.get('b'); // 输出 1
由于 ctrl 他是一个立即执行函数,所以拿到的其实就是 get(key){return a[key]}
这个函数,对于 a 对象来说就是一个闭包。如果要尝试在外面修改 a 对象的属性,那第一步就是要拿到 a 对象,在 js 中如果当前对象没有某个属性,就会往 原型链 上找,一直找到 Object.propotype
,所以可以在直接在 Object
的原型上加一个 b 属性。
1const ctrl = (() => {
2 const a = {
3 c: 'zzz'
4 }
5 return {
6 get(key) {
7 return a[key]
8 }
9 }
10})()
11Object.prototype.b = 1;
12ctrl.get('b'); // 输出 1
这样子就可以在找 a['b']
的时候就会往原型链上找,由于 a 对象是通过字面量表示法。
1const a = {
2 c: 'zzz'
3}
4// 等价于下面
5const a = new Object()
6a.c = 'zzz'
当找 a['b'] 属性值的时候,a 中没有这个字段,所以会找到 Object.prototype
,此时 Object.prototype.b = 1;
就会返回数值 1。
但这样子有一个弊端就是污染到了全局的对象,另外一个就是获取 ctrl 作用域里面的对象,然后在对象上加上 b 属性。
1const ctrl = (() => {
2 const a = {
3 c: 'zzz'
4 }
5 return {
6 get(key) {
7 return a[key]
8 }
9 }
10})()
11Object.defineProperty(Object.prototype, 'getThis', {
12 get() {
13 return this;
14 }
15})
16obj = ctrl.get('getThis'); // {c: 'zzz'}
17obj.b = 1; // {c: 'zzz', b: 1}
18ctrl.get('b'); // 输出 1
原型链
原型:在 JavaScript 中,每个函数对象都会有一个 prototype
属性,这个属性指向函数的原型,也成为 原型对象。
原型上可以存放 属性 或 方法,共享给 实例对象 使用,也可以做 继承。
原型链:对象都会有一个 __proto__
属性指向对象的 原型对象,同时 原型对象 也是对象也有 __proto__
指向原型对象的对象,因此每一个对象的 __proto__
最终会指向到 Object.prototype.__proto__
上面,表示原型链的顶端,而 Object.prototype.__proto__ = null。

1const a = {
2 name: 'a',
3 pos: 1
4}
5const b = {
6 pos: 1
7}
8b.name; // undefined
如果这样子打印 b.name
的话,由于 b 中并没有 name 这个属性,所以会输出 undefined,但要是修改一下 b 的 __proto__
指向呢?
1const a = {
2 name: 'a',
3 pos: 1
4}
5const b = {
6 pos: 1
7}
8b.__proto__ = a;
9b.name; // 'a'
还是上面的步骤,b 中没有 name 属性,所以他会往原型链 __proto__
属性找,这时候就找到了 a,a 中有 name 字段,所以输出的是 a 中 name 的属性值。
__proto__
和 prototype
区别:
__proto__
是每个对象(包括普通对象和函数对象)都有的一个属性,指向创建该对象的构造函数的原型对象。
prototype
是构造函数独有属性,每个函数(作为构造函数)都有一个 prototype
属性。
__proto__
是对象的内部属性,prototype
是构造函数的属性。
原型链的相关方法:
- Object.getPrototypeOf( ) :查找一个对象的原型对象。
- instanceof 操作符:判断一个对象是否是一个构造函数的实例。
- isPrototypeOf( ):判断一个对象是否是另外一个对象的原型对象。
- hasOwnProperty( ):判断一个属性是定义在对象本身还是从原型对象上继承得到的,如果是本身返回 true,如果是继承得到的返回 false。