闭包的相关解释合集(来源于知乎)

来源:互联网 发布:java数组最大长度限制 编辑:程序博客网 时间:2024/05/16 18:08
作者:wyatt-pan
链接:https://www.zhihu.com/question/19554716/answer/12763637
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

JavaScript 中的闭包与其 Scope Chain 特性真是密不可分的.
JavaScript 中的闭包:
例子:
def foo() {
var a = 1;
def bar() {
a = a + 1;
alert(a);
}
return bar;
}

var closure = foo(); // 这个时候返回的是 bar() 这个函数外加其包上的变量 a;
var closure2 = foo(); // 这里同样生成了另外一个闭包(实例)
closure(); // 2
closure2(); // 2 , 绑定了另外一份变量 a
closure(); // 3

对于常规的 foo() 方法来说, 在其内部的变量 a 的存在应该在 foo() 方法执行完毕以后就消失了, 但是 foo() 方法返回了一个新的方法 bar(), 而这个方法却访问到了 foo() 方法的变量 a (JavaScript 通过 Scope Chain 访问到父级属性), 而方法 bar() 的存在延长了变量 a 的存在时间, 类似与将变量 a 关闭在了自己的作用域范围内一样, 只要方法 bar() 没有失效, 那么变量 a 则会一直伴随着方法 bar() 存在, 而变量 a 与方法 bar() 的这样存在形式被称为闭包;

闭包的应用:
在我看来, 虽然时常听到闭包这个概念, 但真正的应用还真不是很多, 也没看到或者想到比较经典的应用. 在 JavaScript 中, 使用得最多的, 恐怕还是将 function 作为 first class 的情况使用, 就好比你可以 var a = new Number(0); 可以将 a 当作函数的参数不断的进行传递, 你也可以 var f = new Function(arg1, arg2...., functionBody) [new Function("x", "y", "return (x + y)/2")] 来定义函数, 然后将 f 作为参数在函数中不断的传递; 但我一般不会让这个 function 产生闭包来进行传递, 而是会传递一个独立的 function 避免写多了自己都忘记是哪个了.

JavaScript 闭包的实现:
如开篇所说, JavaScript 中的闭包实现与 JavaScript 的 Scope Chain 是密不可分的. 首先在 JavaScript 的执行中会一直存在一个 Execute Context Stack (想想 JavaScript 解释器在看到一个 alert(x) 的时候, 如果没有上下文他怎么知道这个 x 是什么?), Execute Context Stack 中最下面一个一定是 GlobalContext, 而在每一个函数的执行开始就会向这个 stack 中压入一个此 Function 的 Execution Context; 而一个 Execution Context 的组成分为三部分:
1. Variable Object: 存储方法内的变量 vars, 方法传入的参数, 函数内定义的函数等等(函数表达式不保存), Variable Object 在任何时候是不可以被直接访问到的, 当然不同的 JS 引擎提供了访问接口就说不定了;
2. Scope Chain: 这个函数执行的时候用以寻找值的 Scope Chain, 这个 Scope Chain 由 Variable Object + All Parent Scopes 组成, Variable Object 会放在这个 Scope Chain 的最前面, 这也是为什么函数内的变量会被最先找到;
3. thisValue, 函数被调用的时候的 this 对象, 存储的就是函数的调用者(caller)的引用;

对于 Variable Object 在不同的情况下会有不同的定义, 例如在全局的时候被称为 Global Object, 而在函数中则被称为 Activation Object 激活对象;

正是由于有了 Execution Context 中的 Scope Chain, JavaScript 才能够使得在方法 bar()

的内部访问到方法 foo() 中的变量 a, 才能够使方法 bar() 将变量 a 关闭在自己的作用范围内不让他随 foo() 方法的执行完毕而销毁;


说个应用场景。
利用闭包可以给对象设置私有属性并利用特权(Privileged)方法访问私有属性。
  var Foo = function(){      var name = 'fooname';      var age = 12;      this.getName = function(){          return name;      };      this.getAge = function(){          return age;      };  };  var foo = new Foo();  foo.name;        //  => undefined  foo.age;         //  => undefined  foo.getName();   //  => 'fooname'  foo.getAge();    //  => 12


作者:甘洪翔
链接:https://www.zhihu.com/question/19554716/answer/24426087
来源:知乎
著作权归作者所有,转载请联系作者获得授权。


作者:gyfnice
链接:https://www.zhihu.com/question/19554716/answer/40356052
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

从前:
有一位公主......
function princess() {
她生活在一个充满奇幻冒险的世界里, 她遇到了她的白马王子, 带着他骑着独角兽开始周游这个世界,与巨龙战斗,巧遇会说话的动物,还有其他一些新奇的事物。
    var adventures = [];    function princeCharming() { /* ... */ } //白马王子    var unicorn = { /* ... */ },          //独角兽        dragons = [ /* ... */ ],         //龙        squirrel = "Hello!";            //松鼠    adventures.push(unicorn, dragons, squirrel, ....);
但是她不得不回到她的王国里,面对那些年老的大臣。
return {
她会经常给那些大臣们分享她作为公主最近在外面充满奇幻的冒险经历。
        story: function() {            return adventures[adventures.length - 1];        }    };}
但是在大臣们的眼里,总是认为她只是个小女孩......
var littleGirl = princess();
....讲的是一些不切实际,充满想象的故事
littleGirl.story();
即便所有大臣们知道他们眼前的小女孩是真的公主,但是他们却不会相信有巨龙或独角兽,因为他们自己从来没有见到过。大臣们只会觉得它们只存在于小女孩的想象之中。

但是我们却知道小女孩述说的是事实.......




作者:倪云建
链接:https://www.zhihu.com/question/19554716/answer/98106832
来源:知乎
著作权归作者所有,转载请联系作者获得授权。

先说结论。闭包,简单说是指:函数对象本身和这个函数关联的作用域链的结合

上面的定义可能听不懂,为了详细说明这个概念,需要先解释清楚JavaScript中作用域相关的概念。

变量作用域

变量作用域是一个变量在定义后在源码中可见的范围。

JavaScript中存在两种变量作用域:

全局作用域:全局变量具有全局作用域,在整个JavaScript代码中都可见

本地作用域:
  • 在函数中定义的变量就是本地变量,这些变量具有本地作用域,他们在其被定义的函数体内部完全可见,函数的参数也算作该函数的本地变量。
  • 本地变量可以覆盖同名的全局变量
  • 函数可以嵌套,且每一个函数都具有自己的本地作用域环境。

例子:

/** * 全局变量,全局可见 */var globalVar = 'global Scope';var anotherGlobalVar = 'another global var'function fn(){     var localVar = 'local Scope';     var anotherGlobalVar = 'global override';     console.log( localVar );     console.log( globalVar );     console.log( anotherGlobalVar );}fn(); // 将输出:   'local Scope'  'globalScope' 'global override'console.log( localVar ); // 报错, localVar 不存在

函数作用域

JavaScript使用函数作用域规则来决定变量的作用域范围。这意味着,一个在函数中被定义的变量,在这个函数体中,以及该函数体中嵌套定义的所有函数内部都可见。另外和同名本地变量覆盖全局变量同理,嵌套函数中的同名本地变量也会覆盖其上层函数中的本地变量。

例子:
function fn(){     var localVar = 'fn var';     function nestedFn(){          var localVar = 'nested fn var';          console.log( localVar );          // ==> 'nested fn var'     }     nestedFn();}fn();

作为对象属性的变量

变量的本质是通过对象来组织到一起的属性集合。

当你定义了一个全局变量,你是在全局对象上定义了一个属性(这个全局对象根据宿主环境不一样而不一样,比如在浏览器里面就是window)

var a = {};console.log( window.a === a ); // true

对于本地变量,你可以同样的将他们视为某个对象的属性,只是在JavaScript中,你并没有方法获取到这个对象。在ES3中,这个对象被称为“调用对象”(Call Object),在ES5中被称为“声明上下文”(declarative enviroment record),以下面这个函数定义为例:
function fn( parameter ){     var localVar = 'local var';}

若我们能找到办法获取到这个“调用对象”的对象引用,即为A,则A将具有如下的属性:
A.parameterA.localVar

作用域链

结合上面的所有信息,在JS的实际执行中,为了准确的定位到一个标示符具体指向的是哪个变量,我们需要了解作用域链的概念。为什么需要这个听起来很复杂的东西?先看看下面的代码:
var neekey = { name: 'Neekey' };function run( name ){     var originName = myName;     var myName = name;     function sayMyCurrentName(){          console.log( myName );     }     function changeMyName( name ){          originName = name;          neekey.name = name;     }     function sayMyOriginName(){          console.log( originName );     }     sayMyCurrentName();     // Neekey     changeMyName( 'Nick' );     sayMyOriginName();      // Nick}run( neekey.name );console.log( neekey.name ); // Nick

这个例子其实不算复杂,但是你在看的时候还是要仔细注意这些个`name`,`myName`,`originName` 都实际指向的是哪个变量。

首先作用域链是什么呢?JavaScript中的每一块代码都会与一个作用域链相关联,这个链是一个对象列表,对于每一个标示符,都依次从这个链的对象中查找具有同样标示符的属性。而作用域链就是由全局变量和不定数量的由函数调用产生的调用对象构成的列表。

看下面的例子:
var x = 1;var y = 2;// 代码执行到此,为了确定x和y,查询其作用域链: [ window ],从 window.x 和 window.y 中取出了值console.log( x + y );function fnA(){     // 函数被调用,产生了fnA的调用对象(即为a),用于查询变量的作用域链为:[ a, window ]     var x = 2;     // a.x 定位到x,a.y不存在,继续从 window.y 中定位到y     console.log( x + y );}fn();

需要注意,在一个函数刚刚被定义时,还没有调用对象存在,只有当这个函数开始被调用执行时,才会有一个调用对象被创建用于储存其中的本地变量。也就是说,相同的一个函数,在每一次调用都会产生一个独立的调用对象。

另外,在函数刚刚被定义时,除了新建了一个函数对象(Function Object)外,还将当前的作用域链和这个函数对象关联在了一起:
var x = 1;var y = 2;// 此时的作用域链: [ window ]// fnA 函数定义后,当前作用域链和fnA 关联了起来,我们可以假设存在类似 fnA.__scope_chain = [ window ] 这样的东西存在function fnA(){     // 函数调用开始,新建了一个调用对象a,和之前关联起来的作用域链结合,生成了属于该函数调用过程的作用域链: [ a, window ]     var x = 2;     console.log( x + y );}fn();

所以当函数嵌套很多的时候,作用域链会很长:
var x = 1;var y = 2;// [ window ]// fnA.__scope_chain = [ window ]function fnA(){     // [ a, window ]     // fB.__scope_chain = [ a, window ]     function fnB(){          // [ b, a, window ]          // fnC.__scope.chain = [ b, a, window ]          function fnC(){                // N次嵌套后               // [ n … b, a, window ]               // fnN.__scope.chain = [ n …. b, a, window ]               function fnN() {                    // [ n+1, n, … b, a, window ]               }          }     }}fn();

词法作用域

词法作用域(lexical scoping)是指,函数在执行时,使用的是它被定义时的作用域,而不是这个函数被调用时的作用域。

有点抽象,我们看下面的例子:
var scope = 'global scope';function checkScope(){     var scope = 'local scope';     return function(){         console.log( scope );     }}var f = checkScope();f();      // local scope

上面的例子中,核心是嵌套定义的这个函数:

return function(){     console.log( scope );}

虽然实际该函数在执行时`fn()`时作用域中的`scope`是 `global scope`,但是实际执行使用的是定义函数时的那个 ‘local scope’,集合上面作用域链的知识,所谓的词法作用域规定了:

函数在执行时使用的作用域链是它在定义时绑定的那个作用域链(准确地说是使用当时绑定的那个作用域链加上新建的调用对象组成的新的作用域链),而不是函数调用时所处上下文的作用域链。

回到闭包上来

上面讲了这么多概念后,回到一开始的答案:

函数对象本身和这个函数关联的作用域链的结合。

也即是:fn + fn.__scope_chain (并不存在__scope_chain,只是为了让这个关联更加形象)

其实,由于作用域链本身的特性,以及函数在定义时就能和作用域链关联起来的自然特性,可以简单说:在JavaScript中,每个函数都是闭包。

理解了闭包本身后,你会发现,闭包的强大之处其实在于:JavaScript中的函数,通过作用域链和词法作用域两者的特性,将该函数定义时的所处的作用域中的相关函数进行了捕获和保存,从而可以在完全不同的上下文中进行引用。

0 0
原创粉丝点击