Giter VIP home page Giter VIP logo

Comments (132)

jawil avatar jawil commented on September 21, 2024 250

支持下一啊,虽然对闭包已经看了很多了,每次看一遍都会有一番不同的感受,学习就是一个重复的过程。

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024 119

当执行到data[0]函数的时候,for循环已经执行完了,i是全局变量,此时的值为3,举个例子:

for (var i = 0; i < 3; i++) {}
console.log(i) // 3

from blog.

spicychocolate avatar spicychocolate commented on September 21, 2024 55

循环结束后

data[0] = function(){console.log(i)}
data[1] = function(){console.log(i)}
data[2] = function(){console.log(i)}

执行data[0]()data[1]()data[2]()时,i=3,所以都打印3
这个例子看完N遍后终于知道原理了

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024 27

@xdwxls 词法作用域的问题,具体可以看第二篇《JavaScript深入之词法作用域和动态作用域》,关于这道题,你可以简单理解为函数能够读取到的值跟函数定义的位置有关,跟执行的位置无关

from blog.

tangshuimei avatar tangshuimei commented on September 21, 2024 23

@xdwxls 我觉得可能是虽然fn(),即innerFoo()是在bar里面执行的,但是innerFoo函数的时候他的作用域scope里面分别是[AO,fooContext.AO,globalContext.AO],并没有包括barContext.AO在里面,所以根本就没有声明c这个变量,所以会显示is not define,我就猜猜而已......

from blog.

Muscliy avatar Muscliy commented on September 21, 2024 18

@bighuang624 谢谢,我刚刚用babel 转了下发现其实多了_loop的函数,这个就解释的通了,看来《你不知道的 JavaScript》 这个书很好

"use strict";

var data = [];

var _loop = function _loop(i) {
  data[i] = function () {
    console.log(i);
  };
};

for (var i = 0; i < 3; i++) {
  _loop(i);
}

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

from blog.

fi3ework avatar fi3ework commented on September 21, 2024 15
匿名函数Context = {
    AO: {
        arguments: {
            0: 1,
            length: 1
        },
        i: 0
    }
}

里面的arguments0:1为什么是1呢,按我的理解应该和i的值相同,所以不是0: 0

from blog.

bighuang624 avatar bighuang624 commented on September 21, 2024 13

@Muscliy let 关键字将 for 循环的块隐式地声明为块作用域。而 for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。
这是《你不知道的 JavaScript》中的解释。

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024 10

@frankchou1 看你修改了几次格式,Github 的评论支持 markdown 格式,使用代码块可以用 ```js 和 ``` 包裹

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024 7

@jasonzhangdong 正是如此,data[0] 是一个函数名,data0 表示执行这个函数,当执行函数的时候,循环已经走完了,i 的值为 3:

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

}
console.log(i) // 3

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024 6

@tangshuimei 是的,你可以这样理解,如果要更严谨的话,可以说,执行上下文中的作用域 scope 是由函数的 [[scope]]属性初始化,而函数的[[scope]] 属性保存了函数创建时词法层面上的父级们的 VO 引用,跟函数的执行顺序无关。

from blog.

Muscliy avatar Muscliy commented on September 21, 2024 4
var data = [];

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

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

求教下,要是把var i 改成let 这个原理有事怎么样子的呢?

from blog.

youbooks avatar youbooks commented on September 21, 2024 4

首先感谢你的分享,但是呢看了你写的 js 深入系列这些文章,我是越看越懵比,就拿你这篇文章中例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();

你的解释我是一脸懵比,我直接翻开 javascript权威指南这本书的 183页面,解释非常清楚, 还有我看 阮老师的js标准教程 也是行云流水很流畅,看了你这个系列的 “JavaScript深入之从ECMAScript规范解读this” 也是很懵比,很有读者看了也有跟我一样的感觉,我直接看阮老师的 http://javascript.ruanyifeng.com/oop/this.html **很清晰啊,我不知道是不是作者表达的太乱了

from blog.

xueyida avatar xueyida commented on September 21, 2024 4

感觉子自执行函数理解的话比较抽象,拆开的话,博主的意思更容易理解-_-!

// 自执行函数
for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
        return function(){
            console.log(i);
        }
  })(i);
}``

// 等于上面的自执行函数
for(var i = 0; i<3; i++){
    function test(j){
        return function(){
            console.log(j);
        }
    }

    data[i] = test(i);
}

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024 3

@xdwxls AO 表示活动对象,储存了函数的参数、函数内声明的变量等,在 innnerFoo 中,查找变量 c,就要在 innerFoo 函数的作用域链,也就是 [AO,fooContext.AO,globalContext.AO] 中找到变量 c 的声明,因为没有,所以最终会报错~

from blog.

keyiran avatar keyiran commented on September 21, 2024 2

非常感谢博主!以前对闭包总是雾里看花,终隔一层,提到闭包,有人说函数就是闭包,有人说必须是嵌套,又是引用怎么怎么样,其实现在看来,两者都是,只不过是一种狭义和广义上概念的区别。
另外,通过楼主的分析,渐渐发现,只要理清了变量的查找规则,AO对象词法分析期和执行期的变化,闭包这东西,正是基于这些规则下产生的一种自然而然的现象。

from blog.

LuckyJQ avatar LuckyJQ commented on September 21, 2024 2

看了前面的再来看闭包真是水到渠成的感觉啊!

from blog.

haochi1999 avatar haochi1999 commented on September 21, 2024 2

即时 checkscopeContext被销毁了

但是javascript仍然会让 checkscopeContext.AO 活在内存中

f函数依然可以通过f函数的作用域链找到它....

读完这段话 怎么莫名有点小感动...莫名有点浪漫的感觉...大晚上的我在想啥呢...

继续学习 加油呀自己...

from blog.

anjina avatar anjina commented on September 21, 2024 2

let f;
let o = 10;
function a(o) {
if (!f) {
f = () => {
console.log('console f', o);
}
} else {
console.log('f true', o);
}
o+= 1;
f();
}
a(1); // 2
a(5); // 5,2
这个最后a(5)输出的是还是2却不是6,从原理方面应该怎么解释

当第一次执行a时, f被赋值为箭头函数,此时函数创建 , 会复制所有父对象的变量对象到[[scope]]属性,即[ a.AO, global.VO],然后打印2, 当再次执行 函数a,时形参为5, 会重新创建上下文,并且初始化,但后面f执行为什么打印不是6,就是因为 f [[scope]] 属性引用的上下文是第一次执行函数a时候的上下文, 里面的 o为2 ,不是第二次执行时候的

from blog.

SageWu avatar SageWu commented on September 21, 2024 1

@wangxiaoxaio
每次循环都会产生新的块级作用域,就像初始化形参一样,都有各自的i属性,值分别为012
又由于data这个数组的元素引用着上述作用域里面的函数,故上述作用域在循环结束后不会被销毁。
然后接下来通过函数地址引用去调用这些函数,自然就获取的都是各自作用域中的i属性。

from blog.

zzhihang avatar zzhihang commented on September 21, 2024 1

结合作用域链来理解闭包真的和以前对闭包的理解发生了质的改变!

from blog.

rivens-77 avatar rivens-77 commented on September 21, 2024

请问下学长为什么

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

这里是3??怎么来的 为什么不是0,1,2

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@fi3ework 嗯,这里是笔误,感谢指出,o( ̄▽ ̄)d

from blog.

xdwxls avatar xdwxls commented on September 21, 2024
var fn = null;
function foo() {
    var a = 2;
    function innnerFoo() { 
        console.log(c); 
        console.log(a);
    }
    fn = innnerFoo; 
}

function bar() {
    var c = 100;
    fn(); 
}

foo();
bar();

大神,帮我把这个例子分析下?自己解释感觉说服不了自己,c 为什么会报错,我怎么感觉会读取到bar 执行上下文中变量对象c

from blog.

tangshuimei avatar tangshuimei commented on September 21, 2024

大神,那是不是执行上下文中的作用域scope仅是父级的一个VO记录,不会像跟ECStack那样跟函数执行的次序有关呢?

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@tangshuimei 哈哈,关于这道题的分析,我赞同你的观点~

from blog.

xdwxls avatar xdwxls commented on September 21, 2024

@tangshuimei 你说 innerFoo函数的时候他的作用域scope里面分别是[AO,fooContext.AO,globalContext.AO], 那这个时候AO 具体表示什么呢???有点费解

from blog.

frankchou1 avatar frankchou1 commented on September 21, 2024

我在想,我们在想这个作用域链的时候是不是把for循环的AO给漏了?比如说下面这个例子:

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

这里返回的是1,2,3

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@frankchou1 for 循环不会创建一个执行上下文,所有不会有 AO, i 的值是在全局对象的 AO 中,代码初始的时候为:

globalContext = {
    VO: {
        data: [...],
        i: 0
    }
}

代码执行的时候,不断修改 i 的值

from blog.

alicejxr avatar alicejxr commented on September 21, 2024

看了这么多写闭包的,这个是我看完之后唯一恍然大悟的,之前都是一知半解的。感谢,比心💟

from blog.

dengnan123 avatar dengnan123 commented on September 21, 2024

学习了啊

from blog.

jasonzhangdong avatar jasonzhangdong commented on September 21, 2024
var data = [];

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

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

答案是都是 3,让我们分析一下原因:

当执行到 data[0] 函数之前,此时全局上下文的 VO 为:

globalContext = {
    VO: {
        data: [...],
        i: 3
    }
}

data[0]的时候i不是0吗?为什么是3,整个循环走完了吗?

from blog.

proc07 avatar proc07 commented on September 21, 2024

文中的:“ 即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中 ” 。"活在内存中" 这句话有点描述不完整的感觉。是所有的闭包函数都销毁之后,都会存活在内存中吗?哪什么时候才会从内存中删除呢?

from blog.

imnaifu avatar imnaifu commented on September 21, 2024

第一个例子,换成let,就变成012了

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@jxZhangLi 非常好的问题,要解决这个问题,还要了解一些垃圾回收机制,不过我还没有怎么研究过……关于这个问题,我个人的看法是,并不会被删除,这些变量放在闭包和放在全局作用域,对内存而言是一样的。

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@imnaifu 是的,这是 ES6 的特性,可是为什么换成 let 就会正常打印 012 呢?

from blog.

roadwild avatar roadwild commented on September 21, 2024

@mqyqingfeng @jxZhangLi 第一点是内层函数f引用了自由变量scope;第二点是函数f返回到全局作用域中去了,这点使得f从根出发可到达;所以标记清除法并不会去清除这个scope,个人的一点理解。

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@sinkinlife 我对标记清楚和引用计数这两种方法还没有怎么了解过,感谢补充哈~

from blog.

wnbupt avatar wnbupt commented on September 21, 2024

在闭包这一节还有个很经典的loop

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

输出值为1,2,3,4,5通过看全文我能够理解了。

关于打印i的行为为什么是每隔1s触发一次?

原因是因为setTimeout行为是表示在特定时间后将代码插入到队列中,所以在本段代码中,假设从0开始计时,则一次在第1秒,第2秒...第5秒的时候,将打印任务插入在队列中。若队列空闲,则会执行,因此就会在第1秒,第2秒...第5秒的时候分别打印i的值(说明队列基本空闲,一旦有任务插入,直接就执行了)

感谢大神 @frankchou1

from blog.

frankchou1 avatar frankchou1 commented on September 21, 2024

你可以尝试用声明事件来理解,就是你们平时用的click事件这些(都是异步),你一次性声明了这么多个click,但执行的时候也只有你触发它的时候。对照settimeout来看,它其实也相当于一个声明,真正执行的时候是你设的那个时间。

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@wnbupt 很抱歉回复晚了 @frankchou1 感谢回答哈~

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@wnbupt 这里可以补充一篇事件循环原理 JavaScript 运行机制详解:再谈Event Loop,执行 for 循环的时候,相当于执行多个 setTimeout 函数,相当于往任务队列中添加进了多个任务,这些任务的起点时间基本是一致的,js 会轮询检查是否有任务完成,最终的效果会是相隔 1 s

from blog.

webjscss avatar webjscss commented on September 21, 2024

必包只是词法作用域的一个规则而已。。。大佬们你们觉得的呢😄

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@youbooks 好吧~~~

from blog.

Rainpia avatar Rainpia commented on September 21, 2024

你讲的其他的我都还可以理解,但是这个闭包一直没理解,之后看了 http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
阮叔的博客,感觉比较清晰,建议结合他的方式改进,说不定效果会更好 😄

from blog.

morelearn1990 avatar morelearn1990 commented on September 21, 2024

对于下面这个例子,我们是不是可以理解为,即便 fooContext 被从执行上下文中弹出来销毁,但是 fooContext.AO 被保存到被返回的匿名函数的 [[scope]] 里面,所以还能被访问得到。

function foo(){
  var a = 1;
  function increase(){
    a++
  }
  return function(){
    increase()
    console.log(a)
  }
}

let bar = foo();
bar() //2
bar() //3

from blog.

273539918 avatar 273539918 commented on September 21, 2024

“自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量”。下面这个闭包的j参数,应该不是自由变量(是函数参数),但是闭包是可以访问j的。能解答一下吗?

var funArr = []
for (var i = 0; i < 6; i++) {
    funArr[i] = (function(j) {
        return function() {
            console.log(j);
        }
    })(i)
}

from blog.

hp0912 avatar hp0912 commented on September 21, 2024
function test(){
  console.log(1);
}

为什么我的代码没有语法高亮...

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@Rainpia 感谢建议哈~ 其实本篇我想表达的是闭包的一种实现原理,即通过维护作用链中涉及变量的存活,从而导致能在执行上下文栈销毁后访问到变量的值,这也可以解释我以前一直以来的疑问,就是为什么闭包会导致内存上升的问题

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@morelearn1990 是的~

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@273539918 这个 j 是自由变量,因为

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

其实想表达的意思是:

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

from blog.

mqyqingfeng avatar mqyqingfeng commented on September 21, 2024

@hp0912 现在看你的代码是不是高亮了,你编辑一下你的评论应该就知道了,是没有声明语法的缘故,JavaScript 语法被 ```js 和 ``` 包裹就会高亮

from blog.

YBFJ avatar YBFJ commented on September 21, 2024

您好,想问下那个为什么会突然出现一个匿名函数??这是从哪里冒出来的呀?

from blog.

sunnynudt avatar sunnynudt commented on September 21, 2024

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

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

default

from blog.

sunnynudt avatar sunnynudt commented on September 21, 2024
var data = [];

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

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

bibao2

from blog.

hankanon avatar hankanon commented on September 21, 2024

终于有点豁然开朗的感觉,匿名函数执行会产生一个上下文环境,跟全局变量的环境隔开了,所以就是1,2,3了。

from blog.

sansui-orz avatar sansui-orz commented on September 21, 2024

谢谢大神的分享,但我有个地方有点困惑
文中说到:

当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:

fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
对的,就是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

关于checkscopeContext.AO为什么会活在内存中未被销毁的问题。
我是这样认为的,首先有两个可能:
1、因为f中引用到了checkscope的变量,所以该变量未被回收,以至于checkscopeContext.AO 活在内存中
2、作用域链上的所有作用域的活动对象都不会被销毁
如果是1的话,那么就会有个问题,当checkscopeContext 被销毁时,f还未创建上下文,它是如何知道有函数依赖它的
如果是2的话,好像又和js的垃圾回收机制冲突了。

那么出现checkscopeContext.AO未被回收的原因是因为什么呢?

from blog.

wd2010 avatar wd2010 commented on September 21, 2024

@sansui-orz 我试着回答一下,

1、因为f中引用到了checkscope的变量,所以该变量未被回收,以至于checkscopeContext.AO 活在内存中
...
如果是1的话,那么就会有个问题,当checkscopeContext 被销毁时,f还未创建上下文,它是如何知道有函数依赖它的

在checkscope上下文中,f 通过词法作用域通过作用域链[ checkscopeContext.AO, globalContext.AO]在checkscopeContext.AO中找到了变量scope,之后即使当checkscopeContext 被销毁时checkscopeContext.AO还是存在于fContext的作用域链中的, 至于保存的checkscopeContext.AO中只有scope这一个值还是全部值都包含的话,这个我也不懂了,想请大神指点 @mqyqingfeng

from blog.

sunzhiguang avatar sunzhiguang commented on September 21, 2024

一句话, 闭包就是一个function.

from blog.

lishihong avatar lishihong commented on September 21, 2024

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:

data[0]Context = {
    Scope: [AO, 匿名函数Context.AO globalContext.VO]
}
``` 感觉这个scope 数组里面的顺序是不是有点问题呢,谁能解答下?
感觉是:```js [匿名函数Context.AO,AO, globalContext.VO]``

from blog.

xuhan1995 avatar xuhan1995 commented on September 21, 2024

谢谢大神的分享,但我有个地方有点困惑
文中说到:

当我们了解了具体的执行过程后,我们知道 f 执行上下文维护了一个作用域链:
fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
对的,就是因为这个作用域链,f 函数依然可以读取到 checkscopeContext.AO 的值,说明当 f 函数引用了 checkscopeContext.AO 中的值的时候,即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中,f 函数依然可以通过 f 函数的作用域链找到它,正是因为 JavaScript 做到了这一点,从而实现了闭包这个概念。

关于checkscopeContext.AO为什么会活在内存中未被销毁的问题。
我是这样认为的,首先有两个可能:
1、因为f中引用到了checkscope的变量,所以该变量未被回收,以至于checkscopeContext.AO 活在内存中
2、作用域链上的所有作用域的活动对象都不会被销毁
如果是1的话,那么就会有个问题,当checkscopeContext 被销毁时,f还未创建上下文,它是如何知道有函数依赖它的
如果是2的话,好像又和js的垃圾回收机制冲突了。

那么出现checkscopeContext.AO未被回收的原因是因为什么呢?

1是对的。
确实是当checkscopeContext 被销毁时,f还未创建上下文。
但注意f创建执行上下文(fContext)并不依赖于checkscope的执行上下文(checkscopeContext ),因为他只引用了checkscope的执行上下文(checkscopeContext )中的活动对象(checkscopeContext.AO),所以checkscopeContext.AO没销毁就可以了

from blog.

xuhan1995 avatar xuhan1995 commented on September 21, 2024

当执行 data[0] 函数的时候,data[0] 函数的作用域链发生了改变:
data[0]Context = {
Scope: [AO, 匿名函数Context.AO globalContext.VO]
}

感觉是:```js [匿名函数Context.AO,AO, globalContext.VO]``![](chrome-extension://mdcffelghikdiafnfodjlgllenhlnejl/icons/copy.png)复制到剪切板

没问题。
函数自己的AO在顶级
《JavaScript高级程序设计》P178最下面

作用域链中,外部函数的活动对象始终处于第二位,外部函数的外部函数的活动对象处于第三位,……直至作为作用域链终点的全局执行环境
全局执行环境就是全局执行上下文(global execution context)翻译问题

from blog.

Boboboer avatar Boboboer commented on September 21, 2024

`function checkscope(){
var scope = "local scope";
function f(){
console.log(scope);
scope = 'leeee' //此处的scope是放在了checkscope的AO中,还是放在了全局上下文的VO呢
}
return f;
}

var foo = checkscope();
console.log(foo());
console.log(scope)// scope is not defined`

为什么输出 scope 报错了

from blog.

xuhan1995 avatar xuhan1995 commented on September 21, 2024

`function checkscope(){
var scope = "local scope";
function f(){
console.log(scope);
scope = 'leeee' //此处的scope是放在了checkscope的AO中,还是放在了全局上下文的VO呢
}
return f;
}

var foo = checkscope();
console.log(foo());
console.log(scope)// scope is not defined`


为什么输出 scope 报错了

此处的scope是放在了checkscope的AO中,还是放在了全局上下文的VO呢

scope是checkscope.AO中的。

为什么输出 scope 报错了

根据词法作用域规则scope已经绑定了,不会再声明一个全局变量,见
[(https://github.com//issues/3)]

from blog.

Boboboer avatar Boboboer commented on September 21, 2024

`function checkscope(){
var scope = "local scope";
function f(){
console.log(scope);
scope = 'leeee' //此处的scope是放在了checkscope的AO中,还是放在了全局上下文的VO呢
}
return f;
}

var foo = checkscope();
console.log(foo());
console.log(scope)// scope is not defined`

为什么输出 scope 报错了

此处的scope是放在了checkscope的AO中,还是放在了全局上下文的VO呢

scope是checkscope.AO中的。

为什么输出 scope 报错了

根据词法作用域规则scope已经绑定了,不会再声明一个全局变量,见
[(https://github.com//issues/3)]

谢谢 老哥 你有时间能帮我看看我在 变量对象 那篇最下面提出的两个疑问吗?

from blog.

wangxiaoxaio avatar wangxiaoxaio commented on September 21, 2024

这部分的必刷题部分,把var换为let时,输出会有不同,大佬能解释下吗

from blog.

SageWu avatar SageWu commented on September 21, 2024

@wangxiaoxaio
这是经过转换的代码

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

很自然可以看出闭包现象,这都是因为let加上{}会产生块级作用域,避免变量污染

from blog.

wangxiaoxaio avatar wangxiaoxaio commented on September 21, 2024

@SageWu
你说的这些我也知道,我想问的是使用let声明局部变量后,即使不使用闭包,也可以得到理想的输出,这是为什么?
var data=[]; for(let i=0;i<3;i++) { data[i] = function(){ console.log(i) } } data[0](); //0 data[1](); //1 data[2](); //2

from blog.

a87604476 avatar a87604476 commented on September 21, 2024

请问下,函数执行上下文是在函数执行的时候压入到执行上下文栈后进行初始化的,前提是函数需要执行才能进行上下文的初始化,那么checkscope中的f函数是先返回,但是如果不执行,[[scope]]属性中为什么会有checkscope和全局环境的变量对象呢?

from blog.

a87604476 avatar a87604476 commented on September 21, 2024

请问下,函数执行上下文是在函数执行的时候压入到执行上下文栈后进行初始化的,前提是函数需要执行才能进行上下文的初始化,那么checkscope中的f函数是先返回,但是如果不执行,[[scope]]属性中为什么会有checkscope和全局环境的变量对象呢?

我想解释下我上面提出的疑问,不知道是不是正确:f函数在定义的时候就已经引用了checkscope的词法作用域,在checkscope()执行后生成了checkscopeVO,执行结束返回了f。由于f函数已经引用了checkscope的词法作用域,在checkscope执行后虽然对应的执行上下文对象被释放,但是由于f函数所在的词法作用域导致checkscopeVO不会被回收,在初始化fContext的后会将checkscopeVO设置到f函数的[[scope]]属性中 @mqyqingfeng

from blog.

a87604476 avatar a87604476 commented on September 21, 2024

请问下,函数执行上下文是在函数执行的时候压入到执行上下文栈后进行初始化的,前提是函数需要执行才能进行上下文的初始化,那么checkscope中的f函数是先返回,但是如果不执行,[[scope]]属性中为什么会有checkscope和全局环境的变量对象呢?

我想解释下我上面提出的疑问,不知道是不是正确:f函数在定义的时候就已经引用了checkscope的词法作用域,在checkscope()执行后生成了checkscopeVO,执行结束返回了f。由于f函数已经引用了checkscope的词法作用域,在checkscope执行后虽然对应的执行上下文对象被释放,但是由于f函数所在的词法作用域导致checkscopeVO不会被回收,在初始化fContext的后会将checkscopeVO设置到f函数的[[scope]]属性中 @mqyqingfeng

博主,没事了,我回顾了你发布的执行上下文那篇博客,发现你有解释说,"同时 f 函数被创建,保存作用域链到 f 函数的内部属性[[scope]]",应该在这个步骤的时候f函数的[[scope]]属性中就已经保存了checkscopeVO和globalVO,所以在checkscope执行上下文对象释放后,才会阻止checkscopeVO的释放,翻看了红宝书的P179,也讲述了类似的过程

from blog.

wangxiaoxaio avatar wangxiaoxaio commented on September 21, 2024

@SageWu
ok,谢谢,这样能理解通

from blog.

ye-eric avatar ye-eric commented on September 21, 2024

之前看了很多关于闭包的文章,都是在死记硬背,直到看见这篇,才明白原理,学习到了

from blog.

1120340041 avatar 1120340041 commented on September 21, 2024

let f;
let o = 10;
function a(o) {
if (!f) {
f = () => {
console.log('console f', o);
}
} else {
console.log('f true', o);
}
o+= 1;
f();
}
a(1); // 2
a(5); // 5,2
这个最后a(5)输出的是还是2却不是6,从原理方面应该怎么解释

from blog.

baiyuze avatar baiyuze commented on September 21, 2024

支持下一啊,虽然对闭包已经看了很多了,每次看一遍都会有一番不同的感受,学习就是一个重复的过程。

for循环执行完成后,直接弹出执行栈,所以后面执行到data0时,访问作用域链,i=3,所以打印都是3

from blog.

zonglang avatar zonglang commented on September 21, 2024

@mqyqingfeng 冴羽大大,
我想问一下函数的[[scope]]属性和[[scopes]]属性有什么区别?
scope的作用域链包含所有AO,
scopes的作用域链只包含函数中使用的自由变量,
这二者是完全不同的两个属性,还是有什么关联?

from blog.

cllemon avatar cllemon commented on September 21, 2024

支持下;顺便推荐一篇 Closure - TomXu

from blog.

Been101 avatar Been101 commented on September 21, 2024

1、即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)2
2、在代码中引用了自由变量
按照上面这个说法 下面的函数就不属于闭包了啊

function a() {
   var c = 1;
  function b(){
    console.log(c)
  }
  b()
}
a()

from blog.

luyufa avatar luyufa commented on September 21, 2024

以前对闭包知其然,不知其所以然,看了博主的解答,豁然开朗,膜拜!围观!

from blog.

fesweeper avatar fesweeper commented on September 21, 2024

楼主知道有没有根据语言规范自己用C/C++写一个简单的JS引擎的例子?如果有这样的例子,那么这些概念都能用具体的C/C++代码实现了,概念毕竟看完过段时间就会淡忘。

from blog.

miaomiaoji avatar miaomiaoji commented on September 21, 2024

@jxZhangLi
文中的:“ 即使 checkscopeContext 被销毁了,但是 JavaScript 依然会让 checkscopeContext.AO 活在内存中 ” 。"活在内存中" 这句话有点描述不完整的感觉。是所有的闭包函数都销毁之后,都会存活在内存中吗?哪什么时候才会从内存中删除呢?

回答:从《js高程》中的描述来说(p180),在一个函数内部定义的函数将包含函数(外部函数)的活动对象到它(内部定义的函数)的作用域链中。函数f作为返回值赋值给新的变量,checkscopeContext.AO存在的位置就是返回的函数f的[[scope]]中。所以闭包函数执行完成被销毁之后,checkscopeContext.AO自然也就跟着销毁了

from blog.

pambassador avatar pambassador commented on September 21, 2024

闭包:是外部函数的整个作用域还是只有被引用的变量才保存在内存中呢?

from blog.

zingwu avatar zingwu commented on September 21, 2024

牛牛!学到了

from blog.

sjqjane avatar sjqjane commented on September 21, 2024

作者还会回复吗
有个问题个人感觉是
之所以checkscope上下文已经从调用栈弹出(销毁)了f函数还能访问到其作用域下的scope变量是因为创建checkscope上下文时遇到f函数定义所以f.[[scope]]=[checkscopeContext.AO,globalContext.VO]保存了上级的作用域链,后面又调用f函数初始化f函数上下文的Scope时把前面定义f函数的f.[[scope]] 做了contact啊所以fContext = {
Scope: [AO, checkscopeContext.AO, globalContext.VO],
}
所以举的这个例子
var data = [];

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

data0;
data1;
data2;
data0;开始调用的时候去作用域链里找变量i找的 Scope: [AO, checkscopeContext.AO, globalContext.VO]里的checkscopeContext.AO实际就应该是f函数定义时候的f.[[scope]]=[checkscopeContext.AO,globalContext.VO]里的checkscopeContext.AO,这时候能说for循环走完了么 不能吧 所以为什么是3呢

from blog.

findlay-best-wishes avatar findlay-best-wishes commented on September 21, 2024
匿名函数Context = {
    AO: {
        arguments: {
            0: 1,
            length: 1
        },
        i: 0
    }
}

里面的arguments0:1为什么是1呢,按我的理解应该和i的值相同,所以不是0: 0

为什么匿名函数Context.AO里的 i 值为0,大哥还在吗?实在看不懂了

from blog.

xsfxtsxxr avatar xsfxtsxxr commented on September 21, 2024

看了这篇文章,才发现,通过学习函数执行上下文,突然发现闭包很好理解了,谢谢冴羽大神了。

from blog.

chenyong9528 avatar chenyong9528 commented on September 21, 2024

问一下大佬,如果没有引用自由变量,这个自由变量在内存中还存在吗?只是我们访问不到?

from blog.

iiicon avatar iiicon commented on September 21, 2024

data[1] 和 data[2] 是一样的道理。
所以让我们改成闭包看看:
...
这里是改成了立即执行函数

from blog.

yzStrive avatar yzStrive commented on September 21, 2024

个人理解:所谓的必包是一个函数,创建这个函数的作用域已经销毁,但是这个函数还存在,而且引用了它上级作用域的变量,类似上级作用域的私有变量的getter或者setter

from blog.

xieyezi avatar xieyezi commented on September 21, 2024

结合前面的文章反反复复看了很多遍,终于懂了,大爱作者😘

from blog.

xsfxtsxxr avatar xsfxtsxxr commented on September 21, 2024

例子

var data = [];

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

赋值

因为data[i] = 一个匿名自执行函数。
所以在给data[i]赋值之前就已经执行了函数
因此:

data[0] = function(){console.log(0)}
data[1] = function(){console.log(1)}
data[2] = function(){console.log(2)}

执行

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

不知道这样理解的对不对

from blog.

g1f9 avatar g1f9 commented on September 21, 2024

es2015 的规范使用 Lexical Environment 来解释,大佬可以更新一波

from blog.

zhaoyan11 avatar zhaoyan11 commented on September 21, 2024

“从实践角度:以下函数才算是闭包:
即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)
在代码中引用了自由变量”

老兄,感觉这两句话可以再推敲一下呢?
第一句,“即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)”
你的意思我应该明白了,但是给人一种创建它的上下文销毁可能销毁、可能没销毁的感觉。

第二句,在代码中引用了自由变量,那如果下面两种算不算闭包呢?
情况1:

var x = 1;
function f() {
   return function () {  console.log(x) }
}
var theFn = f()

情况2:
function f() {
   var x = 1;
   return function () {
     return function() { console.log(x) }
  }
}
var theFn = f()()

感觉情况1不属于我们通常理解的闭包,情况2又属于。

所以,能不能改成:

从实践角度:闭包是:
能够访问已经被销毁的执行期上下文中的活动对象的函数。

from blog.

JackDan9 avatar JackDan9 commented on September 21, 2024

Hello, 这样的理解不知道对不对啊?
例子和闭包没有任何关系

var data = [];

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

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


// VS

var data = [];

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

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

只是通过LIFE人为制造一个函数作用域来弥补es5没有块作用域的缺陷而已。

Hello, 楼主!
不好意思!我新提了一个issue#182,也是关于这个问题点的,请楼主帮忙关闭下吧。

from blog.

benyiforever avatar benyiforever commented on September 21, 2024

@mqyqingfeng 楼主上午好,请教一个问题:下面这些没看懂。。。。请问应该先执行哪个?

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope();

JavaScript深入之执行上下文,checkscope和f函数的执行顺序如下:

5.执行 f 函数,创建 f 函数执行上下文,f 函数执行上下文被压入执行上下文栈

    ECStack = [
        fContext,
        checkscopeContext,
        globalContext
    ];

8.f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

ECStack = [
        checkscopeContext,
        globalContext
    ];

9.checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出

ECStack = [
        globalContext
    ];

而在JavaScript深入之闭包文章中执行顺序颠倒了:
7. 执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 执行上下文被压入执行上下文栈
8. checkscope 执行上下文初始化,创建变量对象、作用域链、this等
9. checkscope 函数执行完毕,checkscope 执行上下文从执行上下文栈中弹出
10. 执行 f 函数,创建 f 函数执行上下文,f 执行上下文被压入执行上下文栈
11. f 执行上下文初始化,创建变量对象、作用域链、this等
12. f 函数执行完毕,f 函数上下文从执行上下文栈中弹出

from blog.

MrLeihe avatar MrLeihe commented on September 21, 2024

看了那么多闭包,终于才知道原理,:)

from blog.

MrLeihe avatar MrLeihe commented on September 21, 2024

结合作用域链来理解闭包真的和以前对闭包的理解发生了质的改变!

起飞

from blog.

anjina avatar anjina commented on September 21, 2024

首先感谢你的分享,但是呢看了你写的 js 深入系列这些文章,我是越看越懵比,就拿你这篇文章中例子:

var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}

var foo = checkscope();
foo();

你的解释我是一脸懵比,我直接翻开 javascript权威指南这本书的 183页面,解释非常清楚, 还有我看 阮老师的js标准教程 也是行云流水很流畅,看了你这个系列的 “JavaScript深入之从ECMAScript规范解读this” 也是很懵比,很有读者看了也有跟我一样的感觉,我直接看阮老师的 http://javascript.ruanyifeng.com/oop/this.html **很清晰啊,我不知道是不是作者表达的太乱了

从第一篇看到现在,觉得作者讲的非常好,也很透彻

from blog.

anjina avatar anjina commented on September 21, 2024

“自由变量是指在函数中使用的,但既不是函数参数也不是函数的局部变量的变量”。下面这个闭包的j参数,应该不是自由变量(是函数参数),但是闭包是可以访问j的。能解答一下吗?

var funArr = []
for (var i = 0; i < 6; i++) {
    funArr[i] = (function(j) {
        return function() {
            console.log(j);
        }
    })(i)
}

这个 j 是最内层函数 `return function() {

        console.log(j);
    }`  中return 出来的匿名函数的自由变量

from blog.

anjina avatar anjina commented on September 21, 2024

1、即使创建它的上下文已经销毁,它仍然存在(比如,内部函数从父函数中返回)2
2、在代码中引用了自由变量
按照上面这个说法 下面的函数就不属于闭包了啊

function a() {
   var c = 1;
  function b(){
    console.log(c)
  }
  b()
}
a()

从理论角度看是, 从实践角度看不是

from blog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.