关于一个js闭包里的作用域链的问题

直接上代码代码简单

function Foo(){
     var i=0;
     return function(){
         document.write(i++);
     }
}
var f1=Foo(),
f2=Foo();
f1(); 
f1();
f2();


问题,被引入到外部作用域是如果保存的,答案是010,f1和f2如何实现的隔离,如果有图的话就更好了;我觉得大部分网上关于闭包都讲得太抽象,不够具体和直接
i这个变量到底保存在哪个作用域,f2、f1不指向一个函数作用域么

阅读 5.9k
12 个回答

你要的图来了!

1. 刚定义完函数Foo时:

图片描述

2. 所有代码执行完时:

图片描述

3. 说明一下:

问题的关键时:

(1) 函数在执行期间会产生一个执行上下文(EC),作为函数的运行时表示,函数返回时此上下文销毁。所以很函数Foo被调用了两次必然产生两个不同的执行上下文。

(2) 执行上下文中有个叫做词法环境(LE)的东西,用来存放函数执行时产生的局部变量、this等东西。函数Foo里面的局部变量i就放在词法环境LE里面。

(3) 函数返回后,执行上下文销毁了,但是词法环境还是可以存在的,所以你要问i存放在哪里?不错,就在词法环境里面。

(4) 函数里面定义的内部函数总是有一个内部指针(即图中的[[scope]])指向外部词法环境。所以f1和f2可以引用它们的外部词法环境中的各自的i变量,并对它们进行操作。

如果还有什么不明白的,可以留言。

注:为了方便理解,图中某些地方进行了简化,但是本质没有变化。

Foo 每次执行时会产生一个新的scope, f1和f2分别绑定到两个这样的scope, 里面各有一个同名变量i

楼主,这个貌似不太好用图来说明,这里每次调用Foo()函数时,都会执行一次var i=0; 而 f1 和 f2 是分两次调用的,所以会生成两个完全独立的 i 变量,尽管名字相同,但是却是生存在两个不同的作用域中的变量,执行 f1 函数不会影响 f2 函数中的 i 的值,反之执行 f2 同样也不会影响 f1中 i 的值,f1 和 f2 只和各自的 i 变量保持着联系。不知这样会不会好理解一点

ps: 这是另一个类似问题中用户 bachelor 的回复,我觉得解释的还比较明朗:

楼上的各位都在关注闭包,其实题主更应该关注的是这个变量i,因为你每执行这个函数,变量i就重新初始化。
正应为重新执行函数以后,才得到的楼主的答案。

继续回到题主的问题上,如果想让i不被重新执行函数的时候初始化,该怎么办?很简单。

  1. 将变量i变成相对于函数外全局变量,也就是如下这样:

    var i = 0;
    function Foo() {

       return function() {  
          console.log(i++);  
       }  

    }

    var f1 = Foo(),

       f2 = Foo();  

    f1(); // 0
    f1(); // 1
    f2(); // 2

2 不用var声明,不过这样是不推荐的,容易造成变量混乱。

function Foo() {  
    i = 0; 
    return function() {  
       console.log(i++);  
    }  
}  

var f1 = Foo(),  
    f2 = Foo();  
f1(); // 0
f1(); // 1
f2(); // 2

本应该函数执行完后函数内部的变量会被销毁,一切恢复原来的样子。
但由于闭包的原因,i没被销毁,保存在了内存中,谁留的它谁就有权利操作他,
函数每一次被调用的时候会创建一个新的执行环境和对应的作用域链,
第一次的i因为f1,第二次的i因为f2,
所以f1和f2中的i是各自独立的,互不影响,
还是遵守基本法的。

我来讲个故事吧。

//从前,有个贪婪的财主,叫Foo;
function Foo(){
//他掳走了一位相貌俊美的少年,叫小i;
     var i=0;
//小i很纯洁,像一张白纸一样(0);于是乎贪婪的财主把他藏在了一个叫function的地方,准备独自享用(function封装的私有变量,因为var声明的是函数作用域变量);
     return function(){
//没成想财主的家里有个大舌头厨娘,把他的事都抖了出去……于是小i没法在function里藏着了(闭包生成);
         document.write(i++);
//每次出去溜达一圈,都能涨点经验,这样就不是白纸了(++……);
     }
}
//有俩王爷,听说了这件事,都很感兴趣,于是就给Foo暗示,要见见小i的人(调用);
var f1=Foo(),
//财主头疼了:俩瘟神谁也得罪不起啊!算了自认倒霉吧,克隆下给俩人每人准备一个(分别创建两个独立的引用);
f2=Foo();
//第一天零点,王爷f1传召了(他那个)小i;
f1(); 
//鉴于之前表现不错,第二天零点,王爷又找到了(属于他的那个)小i,这时的这个已经有点经验了,王爷表示非常满意。
f1();
//王爷f2坐不住了,于是他也要来了小i,却发现他自己这个小i还是一张白纸……
f2();

大概其就是这个意思吧,私以为javascript的第一个门槛不是闭包什么的劳什子,而是看你能不能准确的理解function;如果能,我觉得javascript就算是入门了吧。

你可以把 f1 = Foo()当作一个变量赋值;

所以 f2 和 f1 中的i 互不影响,因为都在各自新的作用域内。

留意Foo的定义,里面用var定义了一个闭包内的变量i,每执行一次Foo就初始化一个新的i,这就是f1和f2的里的i分别独立的原因。

但对于f1来说,f1每次执行都是指向同一个i,就是f1 =Foo()这一句初始化的i,每次执行都给它加1。

这么跟你说吧:在为f1f2赋值的时候调用Foo()会将i重新设为0。而运行f1f2的时候只是在运行闭包,跟var i = 0;没有关系了。

F1和F2中的i都是放在单独的作用域中,互不影响的人

我自己的理解,仅作参考。
函数定义有三种方式:其中一种就是字面量方式。
var f1 = function(){}
你的Foo()返回的返回值是一个匿名函数,如果从函数定义的角度来说
var f1 = Foo() 等价于 var f1 = function(){document.write(i++)}
var f2 = Foo() 等价于 var f2 = function(){document.write(i++)}

然后根据闭包,作用域链的相关知识。(楼上的朋友已经有回答)
f1(),第一次执行后0。
f1(),第二次执行后1。

f2(),第一次执行后0。

这只是我自己的拙见,仅供参考。

两次执行Foo,虽然都是返回 funtion(){} 但是两次返回的其实不是同一个function 你只要执行一下 f1==f2 就知道了,既然返回的是两个不同的function 那么 他们对应的scope肯定也是不同的。

f1和f2引用的是两个执行环境

推荐问题
宣传栏