Javascript入门:闭包也不过就是这么回事儿嘛

闭包


1. 什么是闭包?

作为一名前端无知小白,我猜MDN的中文版一定是机器翻译的,因为有时翻到上面的中文怎么看都不像是人话,比如闭包的定义:

Closures (闭包)是使用被作用域封闭的变量,函数,闭包等执行的一个函数的作用域。通常我们用和其相应的函数来指代这些作用域。(可以访问独立数据的函数)

能看得懂这个定义才真有鬼了。

好的,我们还是用蹩脚的英文来看看吧:

“A closure is the combination of a function and the lexical environment within which that function was declared.“

闭包是函数以及在函数声明下的词法环境的结合。

函数?变量?词法环境?

似乎感觉懂了一点点?

翻了下JS权威指南,里面说”Javascript函数对象的内部状态不仅包含函数的代码逻辑,还必须引用当前的作用域链。函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为闭包“。

所以我们从变量的作用域开始谈起。

2. 变量作用域

先来说几个概念:

全局变量:在所有作用域都可访问的变量,在函数外定义的变量就是全局变量

局部变量:在函数中使用关键字声明的变量,它的作用域只在声明该变量的函数内,在函数外面是访问不到该变量的。

词法作用域:词法作用域也叫静态作用域,也就是说函数的作用域在函数定义的时候就决定了,而不是调用的时候决定。JavaScript采用静态作用域,变量的作用域完全由写代码期间函数声明的位置来定义的。

话不多说,上代码:

代码1:

var func = function(){

     var a = 'closure'

       console.log(a);         // closure

}

func();

console.log(a); // Uncaught ReferenceError: a is not defined

局部变量a只能在函数内部使用,函数调用结束时,该变量就会被垃圾回收机制回收而销毁

代码2:

var value = 1;

function foo() {

    console.log(value);

}

​function bar() {

    var value = 2;

    foo();

}

bar(); 

foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。

3. 变量的生命周期

我们知道的事情有:

1. 一个局部变量当定义该变量的函数调用结束时,该变量就会被垃圾回收机制回收而销毁。再次调用该函数时又会重新定义了一个新变量。

2. 按照代码书写时的样子,内部函数可以访问函数外面的变量。

那么,如果在函数内部声明一个内部函数,并将内部函数作为值返回,调用外部函数之后,内部函数保持对外部函数词法作用域的引用,这样会发生什么呢?由于内部函数作为值返回了出去,所以外层函数执行完毕,其词法作用域中的变量也不会被销毁。

这就是闭包!

老规矩,上代码:

function foo() {

    var a = 2;

    function bar() {

    console.log( a );

}

   return bar;

}

   var baz = foo();

 baz(); // 2 —— 朋友,这就是闭包的效果。

这段代码非常清晰地展示了闭包,函数 bar() 的词法作用域能够访问 foo() 的内部作用域。然后我们将 bar() 函数本身当作一个值类型进行传递。在 foo() 执行后,其返回值赋值给变量 baz 并调用 baz(),也就是调用了内部函数bar()。bar()在自己定义的词法作用域以外的地方执行。由于bar()保持着队foo()词法作用域的引用,所以foo()在执行完后内部作用域也不会被程序销毁。

循环和闭包

for循环是常见的说明闭包的例子,也是我这样的小白非常容易错的地方。

for (var i = 0; i < 5; i++) {

setTimeout(function () {

console.log(i)

}, 0)

}

我们可能会简单的以为控制台会打印出‘ 0 1 2 3 4 ’,可事实却打印出了5个‘ 5 ’,这又是为什么呢?我们发现,setTimeout 函数时异步的,等到函数执行时,for循环已经结束了,此时的 i 的值为 5,所以 function() { console.log(i) } 去找变量 i,只能拿到 5。

所以,改进一下:

for (var i = 0; i < 5; i++) {

!function (i) {

setTimeout(function () {

console.log(i)

}, 0)}(i)

我们套用了一个立即执行函数,当i=0, 此时 function(i){} 此匿名函数中的 i 的值为 0,等到 setTimeout 执行时顺着外层去找 i,这时就能拿到 0。如此循环,就能拿到想要的 0 1 2 3 4。

作为小白,可能还是太明白什么是立即执行函数,下篇我们会详细介绍。这里我们先换个例子。

function constfunc(v){return function(){  return v;  };} // 这个函数返回一个总是返回v的函数

var funcs = []; // 创建一个空的数组

for(var i=0; i<10;i++){funcs[i] = constfunc(i)}

for(i=0;i

这样就可以打印出0~9了。

不知道你明白了没有,反正我是明白啦~

今天给自己加个鸡蛋!

你可能感兴趣的:(Javascript入门:闭包也不过就是这么回事儿嘛)