在讲闭包之前,我们先了解一下什么是自由变量。

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

示例代码:

let a = 1;

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

foo();

上述代码中,对于函数 foo 来说,a 既不是函数参数也不是函数的局部变量的变量,因此 a 属于自由变量。

什么是闭包

在 ECMAScript 中,闭包(Closure)是指能够访问自由变量的函数。

按照以上的概念,我们可以说所有函数都是闭包,因为它们都是在创建的时候就保存了上层上下文的作用域链,观察如下代码:

let a = 1;

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

foo(); // > 1

(function () {

  let a = 2;
  foo(); // > 1

})();

ECMAScript 使用的是词法作用域(Lexical scoping,又称静态作用域),即在函数创建时,就保存上层上下文的作用域链,上述代码中,foo 函数创建时,其所使用的变量 a 是已经在上下文中静态保存好的,因此在执行 foo() 时 a 的值为 1。

而任何函数,在其创建时保存的上层上下文的作用域中都有全局的自由变量 global(在浏览器中,global 为 window),因此说所有函数都是闭包。

实践中的闭包

上面说的是理论上的闭包,但在实践中,闭包不仅仅只是能够访问自由变量的函数,闭包是指引用了自由变量的,并且被引用的自由变量将和这个函数一同存在的函数,在创建该函数的上下文已经销毁时,该函数仍然存在。

示例代码:

function foo(){
    let a = 1;

    return function(){
	console.log(a)
    }
}
foo()

上述代码中,foo 函数执行后返回了一个匿名函数,该函数引用了自由变量 a,而在 foo() 执行完毕后,创建该函数的环境已经销毁,但该函数并没有被销毁,因此 foo() 的返回值就是一个闭包。

闭包会使引用的自由变量不能被清除,这就使得闭包比其他函数占用内存更多,但这也是闭包的强大之处,以下是一个使用闭包的例子:

let foo = function() {

  let a = 1;

  return {
    add:function(){
  	return ++a;
    },
    sub:function(){
  	return --a;
    }
  }
}

let f = foo();
f.add(); // 2
f.sub(); // 1

再来看一个面试中经常遇到的题目:

let data = [];

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

data[0](); // 3
data[1](); // 3
data[2](); // 3

这三个函数创建时均使用的是已经在上下文中静态保存好的变量 i,而在 for 循环结束时,变量 i 的值为 3,当 data[0]() 执行时,其所引用的自由变量 i 的值为 3,因此输出 3。

我们的目标是输出 0、1、2,上面的例子显然无法实现这个需求,利用闭包可以很轻松地解决这个问题:

let data = [];

for (let i = 0; i < 3; i++) {
  data[i] = (function(x) {
    return function () {
      alert(i);
    };
  })(i); // 传入"i"值
}

data[0](); // 0
data[1](); // 1
data[2](); // 2

以上就是关于 js 自由变量与闭包全部内容,希望对大家有帮助。

「点点赞赏,手留余香」

3

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » js中自由变量与闭包

2 评论

  1. let data = [];

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

    data[0](); // 3
    data[1](); // 3
    data[2](); // 3

    这段代码执行后,实际上就是如所想的输出 0 1 2,因为判断语句使用 let,属于块级作用域,判断语句里面改成 var i = 0; i < 3; i++,才会全部输出3

    1. 非常感谢指出错误,已修改,感谢大佬指点。

发表回复