闭包

预计阅读时间: 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。

这种一般就是内部引用外部的变量,但如何在外部 引用 / 修改 内部的变量呢?

外部修改内部值

  1. 暴露一个修改接口
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
  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。

img

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 区别:

  1. __proto__是每个对象(包括普通对象和函数对象)都有的一个属性,指向创建该对象的构造函数的原型对象。
  2. prototype是构造函数独有属性,每个函数(作为构造函数)都有一个 prototype属性。
  3. __proto__是对象的内部属性,prototype是构造函数的属性。

原型链的相关方法:

  1. Object.getPrototypeOf( ) :查找一个对象的原型对象。
  2. instanceof 操作符:判断一个对象是否是一个构造函数的实例。
  3. isPrototypeOf( ):判断一个对象是否是另外一个对象的原型对象。
  4. hasOwnProperty( ):判断一个属性是定义在对象本身还是从原型对象上继承得到的,如果是本身返回 true,如果是继承得到的返回 false。