因为自己的网站还没弄好能被百度引擎搜索到,所以转载到SF上

原文链接:https://kiznaiver1998.cn/2019...

前情提要

之前裸面北森实习遭遇惨败...闭包本来是自己仔仔细细看过几次的内容也没答好,经过自己仔细的感悟,然后看各大佬的资料,终于又有了一些新的体会(看了之前说的闭包...感觉自己真是一个睿智...🐷)

什么是闭包

之前我一直听标准答案是:

函数记住并访问其所在的词法作用域,叫做闭包现象,而此时函数对作用域的引用叫做闭包。

什么是词法作用域

  • JavaScript 采用词法作用域 -> 函数的作用域在函数定义的时候就决定了。
  • 当然,与之相对的还有动态作用域 -> 函数的作用域是在函数调用的时候决定了。

下面有个经典的面试题用于理解 JavaScript 词法作用域的特性:

var value = 1;

function foo() {
    console.log(value);
}

function bar() {
    var value = 2;
    foo();
}

bar();
//结果为1
//因为词法作用域,函数的作用域在函数定义就决定了,所以foo的value只能在foo定义的作用域向上查找,找到全局变量value = 1    

利用这个特性,我们在 foo 外再加一个函数。因为词法作用域的关系,foo 的 value 从 foo 定义的作用域向上查找,就能找到 fn 的 value 为 3.

var value = 1;
function fn(){
  var value = 3;
  function foo() {
    console.log(value);
  }
  foo();
}
function bar() {
    var value = 2;
    fn();
}    
bar();//3

通过 chrome 调试,我们可以看到运行过程大致为这样

//bar执行上下文入栈
value声明为undefined
value赋值为2
//fn执行上下文入栈
value声明为undefined(这里因为词法作用域的关系,在fn的作用域是没有value的)
value赋值为3
//可以看到结果,fn是一个闭包,产生的引用是对value: 3的引用
Closure(fn) value: 3

所以之前我看到《实战 ES2015》上说的闭包的原理是利用高阶函数的特性(函数里面可以声明函数),产生穿越作用域的引用(就是说 foo 函数能向上查找)

什么是执行上下文

虽说上面已经说了,闭包的产生是因为词法作用域,但是如果了解了执行上下文,才能感受到“记住”的特性。

var value = 1;
function fn(){
  var value = 3;
  function foo() {
    console.log(value);
  }
  return foo;
}
function bar() {
    var value = 2;
    return fn();
}    
var result = bar();
result();//3

对于这个例子,这个函数的执行上下文栈会经过以下过程

ECStack.push(globalContext)//全局global入栈
ECStack.push(barContext)//bar被执行入栈
ECStack.push(fnContext)//fn入栈
ECStack.pop(fnContext)//fn出栈
ECStack.pop(outContext)//bar出栈
ECStack.push(result)//result入栈
ECStack.pop(result)//result出栈
ECStack.pop(globalContext)//global出栈
/* 以上过程都可以使用调试通过Scope看到 */

可以看到 result 执行上下文入栈的时候,fn 已经出栈了,但是 result 仍然能够“记住” fn 的 value 值。所以,通过闭包,我们可以在其他的执行上下文中,访问到函数的内部变量。

鞭尸环节

来源:https://www.cnblogs.com/kizna...

之前一直觉得这个闭包没啥好说的,实际...当时根本看闭包的视角不一样

for (var i = 1 ; i <= 5 ; ++i) {
    (function(i){
        setTimeout( function timer(){
            console.log(i);
        } , i*1000);
    })(i);
}

​ 从现在自己的视角来看这里呢,因为 timer 在匿名的立即执行函数中定义,所以对于词法作用域来说,timer 能够向上访问到匿名函数的 i。而我们知道 setTimeout 作为任务分发器,它的第一个参数(也是回调函数)会进入任务队列,而当它执行的时候,即使匿名函数的执行上下文已经出栈,但它依然能够访问 i,从而产生了闭包。

闭包的作用

  • 函数柯里化
  • 延长变量生命周期
  • 创建模块(实现面向对象的公有方法抛出私有变量的设计)

Kiznaiver
2 声望0 粉丝