深入之闭包

2019-07-13  本文已影响0人  明里人
定义

闭包是指那些能够访问自由变量的函数

什么是自由变量?

自由变量是指在函数中使用,但这个变量既不是函数参数,也不是函数局部变量

由此,闭包有两部分组成

闭包 = 函数 + 函数能够访问的自由变量

举个例子:

var a = 1;
function foo(){
  console.log(a);  // 1
}
foo();

foo 函数可以访问到变量a,变量a既不是foo函数的参数,也不是foo函数的局部变量,所以a是自由变量,形成了闭包。
《JavaScript权威指南》 中就讲到:从技术角度将,所有的JavaScript函数都是闭包。
ECMAScript中,闭包指的是:

1、从理论角度:所有的函数,在它们创建的时候将上层上下文的数据都保存起来,即使是全局变量也是如此,函数访问全局变量就相等于在访问自由变量,这时候使用全局(最外层)的作用域。
2、从实践角度:
i、即使创建它的上下文已经销毁,它仍然存在(如:父函数执行返回内部函数)
ii、在代码中引用了自有变量

分析
let scope = "global scope";
function checkscope(){
    let scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
let foo = checkscope();
console.log(foo());  // local scope

由于作用域链,foo 函数依然可以读取到 checkscope.AO(AO表示checkscope函数作用域) 的值,即使 checkscope 执行被销毁了,JavaScript 依然会让 checkscope.AO 活在内存中,foo 函数依然可以通过作用域链找到变量 scope,从而实现了闭包这个概念。

面试必考题
let data = [];
for(var i = 0; i < 3; i ++){
    data[i] = function(){
        console.log(i);
    }
}
data[0]();
data[1]();
data[2]();

答案都是 3。

先明确一下这两个概念:全局上下文:globalContext,GO表示全局作用域。AO表示函数局部作用域。

在执行到 data[0] 函数之前,此时全局上下文 GO 为:

globalContext = {
  GO: {
    data: [ ... ],
    i: 3
  }
}

当执行 data[0] 函数的时候,data [0] 函数的作用域链为:

data[0]Context = {
  Scope: [AO, globalContext.GO]
}

data[0] 的 AO 并没有 i 值,所以会从 globalContext.GO中查找,此时全局 i 为 3,所以打印结果为3.

所以改成闭包将每次循环中的 i 的值,存储到 data数组中每个对应方法中。

let data = [];
for(var i = 0; i < 3; i ++){
    data[i] = (function(i){
        return function(){
            console.log(i);
        }
    })(i)
}
data[0]();  // 0
data[1]();  // 1
data[2]();  // 2

通过使用立即执行函数将 for 循环中全局 GO 的 此时的 i 值存储在在立即执行函数中参数,并在立即执行函数中返回一个函数,此时这个函数根据作用域链就可以访问立即执行函数 AO 中的 i 值。

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了变化:

data[0]Context = {
  Scope: [AO, 匿名函数Context.AO globalContext.GO]
}

匿名函数执行上下文AO:

匿名函数Context = {
  AO: {
    i: 0
  }
}

闭包的应用:

可通过立即执行函数实现属性私有化

闭包的防范:

闭包会导致多个执行函数公用一个共有变量,如果不是特殊需要,应尽量防止这种情况发生。

上一篇 下一篇

猜你喜欢

热点阅读