js内存回收机制,标记清除法,theThing问题

js内存回收机制,标记清除法,theThing问题

# 具体遇到的问题

V8 中,一旦不同的作用域位于同一个父级作用域下,那么它们会共享这个父级作用域。在这段代码里, unused 是一个不会被使用的闭包,但和它共享同一个父级作用域的 someMethod,则是一个 “可抵达”(也就意味着可以被使用)的闭包。unused 引用了 originalThing,这导致和它共享作用域的 someMethod 也间接地引用了 originalThing。
# 相关课程内容截图

# 尝试过的解决思路和结果

# 粘贴全部相关代码,切记添加代码注释(请勿截图)

var theThing = null;

var replaceThing = function () {

  var originalThing = theThing;

  var unused = function () {

    if (originalThing) // 'originalThing'的引用

      console.log("嘿嘿嘿");

  };

  theThing = {

    longStr: new Array(1000000).join('*'),

    someMethod: function () {

      console.log("哈哈哈");

    }

  };

};

setInterval(replaceThing, 1000);

在这里输入代码,可通过选择【代码语言】突出显示

正在回答

登陆购买课程后可参与讨论,去登陆

15回答

同学你好,可以这样理解。另外,关于闭包,有不同的说法。但是大家所熟知的,是之前老师给你讲解的那种。在学术上,有的概念确实不同人有不同理解,这个倒没有什么影响,你可以保留自己的理解哦。

祝学习愉快~

好帮手慕夭夭 2020-10-29 19:20:37

同学你好,作用域链的顶层是当前执行的函数的活动对象,只不过控制台没有输出。老师测试了一下,如果不输出f1中的变量a,那么[[Scopes]]中也不会显示函数f1的。如下:

1.不输出a

http://img1.sycdn.imooc.com//climg/5f9aa42409ef362f04980273.jpg

输出结果:

http://img1.sycdn.imooc.com//climg/5f9aa4310917553c08230149.jpg

2.如果输出a :

http://img1.sycdn.imooc.com//climg/5f9aa44409f1ad0e05630304.jpg

输出结果:

http://img1.sycdn.imooc.com//climg/5f9aa4660997cef109770175.jpg


通过测试结果看, 用过这个对象上的变量,才会显示。至于为啥会这样,就不得而知了,可能是控制台内部的机制

祝学习愉快~


  • 提问者 嗯嗯_ #1
    好了,关于作用域,作用域链,闭包我是这样理解的:作用域链是函数定义时产生的,他的值等于当前执行环境的作用域;作用域是函数执行时产生的,他的值是在自身作用链的基础上,加上一个函数活动对象,函数执行完之后被调用栈弹出,作用域消失,作用域链依旧存在;所以,同一个函数在定义时有作用域链,在执行时有作用域,而且函数执行时的作用域比起定义时的作用域链,永远都多一个活动对象;闭包,一般是指函数嵌套时,引用了自由变量的内侧函数,但是也有另一种说法,闭包就是内层函数[[scope]]中的[[closure]],我认为第二种说法更为准确;至于为什么[[scope]]的顶层对象不是函数活动对象,是因为,真正的作用域链是函数的内部属性,这个属性在内存中存在,只能被js引擎访问,不能被js访问,而控制台输出的[[scope]],他是经过浏览器处理的作用域链,处理过后显示的对象,都是可以被js访问的,所以闭包就是组成函数作用域链的片段,并且这个片段可以被js访问
    2020-10-29 23:46:51
  • 提问者 嗯嗯_ #2
    老师,这样理解可以吗
    2020-10-30 14:05:47
好帮手慕夭夭 2020-10-29 15:02:48

同学你好,问题解答如下:

1.理解的没错,每个函数都会形成自己的作用域链的。

2.作用域链是函数的一个内部属性,老师也给你打印过哦,console.dir就是打印函数内部的方法和属性,再看一下,如下圈出的这些都是函数的属性,而其中的[[Scopes]]属性,它里面保存的就是作用域链。

http://img1.sycdn.imooc.com//climg/5f9a579109f2233805910296.jpg

3.作用域简单的来说就是函数或者变量可访问范围,例如一个函数fn中定义一个变量a,这个变量a只能在函数fn中可以访问,这个范围就可以理解为作用域。

4.作用域链本身就是作用域组成的,例如老师拿一串糖葫芦,跟同学说这就是糖葫芦。然后同学说,老师我看见的是山楂啊。说它是山楂还是糖葫芦都没有对错,就看你对待事物的理解了。我们理解一个抽象的概念时,不能太具象化了。你把它具象化了,看到的实物就是山楂。你把它抽象化了,那么它就是糖葫芦。即使你把它吃的只剩一个山楂了,我也可以叫它糖葫芦。

作用域和作用域链就是这么个关系。下面打印的是f3函数的[[Scopes]]属性,它里面保存的是这个函数的所能够访问的作用域。这些它能够访问到的作用域,不就形成了一个作用域链么。所以f3函数中使用某一个变量,就是顺着这个作用域链去查找。

http://img1.sycdn.imooc.com//climg/5f9a61cc09c01ae200000000.jpg

学习计算机,对于一些专业术语,理解要灵活一点。这些专业性的文档,原版都是英文的。所以翻译的过程中,同一个概念,在理解与描述上可能不太一样。但是万变不离其宗,不管大家如何去描述这个概念,它核心的作用大家都知道是怎么回事就行,不是非得找一个统一的话术去给它下个定义。就像前面老师说的作用域,非要具象化,那么它就是你所看到的一个保存着变量、函数等等的对象集合。但是具象化了,你根本不知道作用域是干嘛的,因为设计作用域的初衷肯定不是为了让它保存变量或者函数等,所以按照老师自己的理解,把它说成变量或者函数的可访问范围更通俗。当你自己学习了这个概念之后,也可以按照自己的理解去描述它,本身没有什么对与错,不用太纠结这些哦。

另外,因为这个问题下一直回复,看起来很乱,不便于同学后期的复习与整理。如果还有其他问题,可以重新创建一个问题提问,灰灰老师会继续为你解答。

祝学习愉快~


  • 提问者 嗯嗯_ #1
    为什么f3函数【scope】作用域链的顶层是闭包,而不是f3函数的自身的活动对象
    2020-10-29 15:58:44
好帮手慕夭夭 2020-10-28 18:24:16

同学你好,参考如下理解:

1.在JavaScript中,函数也是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为作用域链。

2.老师还要啰嗦一下作用域链的形成,不然很难理解。

参考如下例子:

当有函数嵌套时,会把局部作用域推入到[[Scope]]的首部(0的位置)。如果函数中还嵌套着子函数,那么这个子函数又会推入到作用域链的最前端(即0的位置),而外层函数作用域的位置就变成了1,全局作用域位置为2。子函数中如果还嵌套着孙子函数,孙子函数作用域也会推入到作用域链的最前端,位置层层递进......而作用域链的终点就是全局作用域(Global)。

http://img1.sycdn.imooc.com//climg/5f993ae909a7522106970397.jpg


我们看一下输出结果:

http://img1.sycdn.imooc.com//climg/5f993d6009dc199a10600418.jpg

以上圈出的就是f3的作用域链。访问变量时,查找[[Scope]]这个对象的顺序是按照升序,例如查找变量a,先在当前作用域f3中查找,f3中没有定义此变量。那么就会在原型链中按照顺序查找,即先在第一个对象中(位置0的函数f2)中查找,没有找到变量a。就继续作用域链中的下一个对象(位置1的函数f1)中查找,找到了这个变量就会打印。假如查找一个变量,找到作用域链的最低端(全局 Global)还没有查找到此变量,则标记为变量未定义,程序就会报错。

综上,简单的理解就是,查找一个变量,如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

3.感谢同学对老师的肯定,如果回答有帮助到你,可以采纳一下哦,老师也需要鼓励呢~QAQ

祝学习愉快~

  • 提问者 嗯嗯_ #1
    这么说,函数作用域中,一定有作用域链。作用域链描述的是作用域中的对象。最后一个问题,作用域链是不是函数的一个内部属性呢,还是只是对作用域中对象的描述
    2020-10-29 00:18:01
  • 提问者 嗯嗯_ #2
    作用域是什么,我感觉您红色框框出来的是作用域
    2020-10-29 00:48:49
好帮手慕夭夭 2020-10-26 16:11:28

同学你好,问题解答如下:

1.一般没有人说闭包的创建过程是什么,按照老师的理解,闭包只是函数使用的一种形式,同学所说的只是代码的一个执行流程。不过每一个人的理解是不一样的,没有什么对与错,对于同学描述的闭包创建过程,这样理解也是可以的哦。

                                             

2.“闭包是一个对象,是函数的属性”这个说法不对。之前老师说过,闭包的结构是函数嵌套着函数,闭包就是能够读取函数内部变量的函数。

http://img1.sycdn.imooc.com//climg/5f9684f109cf384707600583.jpg

 

常见的闭包是之前欧欧老师举得例子,即如下例子:

你可以理解为闭包是定义在一个函数内部的子函数(例如如下函数b是闭包),并不是函数的属性。

 http://img1.sycdn.imooc.com//climg/5f9684f90925356004540348.jpg


 

3.浏览器中开发者工具,是指的按F12查看Sources吗?如下图所示,如果是,那么说的是对的。如果不是,请截图说明一下。

http://img1.sycdn.imooc.com//climg/5f96850509c43e6904650210.jpg

4. 按照老师的理解,调用栈就像浏览器中的javascript解释器,是追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。在本案例中,replaceThing 函数内部没有调用其他函数了。它调用之后,会被推入到调用栈里面,当函数调用完毕之后,就会被弹出调用栈。

假如说replaceThing 函数里面调用了另一个函数xx(),那么执行过程是:

(1)先执行replaceThing (),形成一个栈帧。执行replaceThing 的时候,里面调用了xx(),又形成另一个栈帧压在旧栈帧之上(即xx的这个栈帧在栈顶,replaceThing栈帧在栈底)。

(2)当xx执行完毕之后,就会推出(弹出)调用栈,继续执行replaceThing ,当replaceThing 执行完毕,replaceThing 也被调用栈推出了。

同学说的全局变量引用函数内部的变量,是说的内存中,变量指向某一个内存地址,和调用栈没有关系,这里不要混淆哦。

5. 同学问的内容都很深了,面试一般都不会问这些哦,不需要过多深究,了解一下就行。另外,不管是百度百科,博客或者其他论坛,说法都不太明确,也不一定准确,容易造成误解。例如百度百科,一是百度百科说的专业术语过多,不够通俗,理解起来容易产生偏差。二是,一个名词应用的场景比较多。在不同的学术上,同一个名词所表示的概念会不太一样,甚至完全是两个意思。你查到的名词,可能与我们前端方面所说的概念不是一回事。

祝学习愉快~



  • 提问者 嗯嗯_ #1
    灰灰老师,感觉你最厉害,什么是作用域链?(不是问作用域链是怎样形成的)
    2020-10-28 11:28:48
好帮手慕夭夭 2020-10-25 19:16:04

同学你好,问题解答如下:

1.闭包是让外部可以访问函数内部的变量,使内部变量不能被销毁。它只是函数使用的一种形式,所以闭包没有什么创建过程可言,同学说的是代码执行的过程。不同代码执行过程不一样,同学提问中的例子,详细的执行过程,老师在上次已经讲解,参考如下:

http://img1.sycdn.imooc.com//climg/5f9556de094aba2c08860344.jpg



2.有一部分公司是有笔试这个流程的,但是问的原理不会很偏。笔试只是一个加分项,并不是面试成功的关键。做完笔试,会有技术官进行面试,重要的还是面试哦。闭包,作用域,dom,bom,promise...这些都是课程中讲过的。老师只是针对同学提问中的例子,是非常偏的。虽然它写在了面试专栏中,但估计没有几个公司会考察这个例子。另外,老师也拿着这个例子与前端前辈进行了一些交流和讨论,也说这个例子没有实际的意义。而且再往深入里面挖掘它,就超出了老师们的知识范畴了。

3.外包的意思就是别的公司没有开发团队,然后承包给外包公司去做。外包公司业务多,锻炼机会更多。其实,在外包公司更能锻炼与提升自身的能力。骗子公司在每一个行业都有存在的,如果使用任何理由收取你费用的,那肯定是骗子公司了。另外,同学面试时,可以在网上查一查要面试的公司,如果是正规的外包公司是不用担心的。

4.前端好就业只是相对其他行业来说,人才需求量大,机会多。但是能不能抓住机会,还是要看自己,不是说你学了就能找到,要提升自己的核心能力。能力强了,机会才能来找你哦。慕课网的学分企业是不看的,但是课程中的项目是可以在面试的时候展示的。


祝学习愉快~


  • 提问者 嗯嗯_ #1
    链式引用理解了。对于闭包有没有创建过程,我是这么理解的,当函数发生嵌套,外层函数没有闭包,内层函数有闭包,那么闭包就有一个从无到有的过程,就是闭包的创建过程。(闭包是一个对象,是函数的属性,这个属性可以从浏览器中开发者工具查看到),括号内的话,对吗?还有一个问题,外层函数执行完后,如果位置在调用栈栈顶就会被调用栈弹出,不管内层函数有没有被全局引用,是吗?(回答一定要清楚)
    2020-10-26 03:50:53
好帮手慕夭夭 2020-10-22 10:40:19

同学你好,问题解答如下:

1.代码执行之后,自然会从调用栈中移除的,但跟内存是两码事哦。

2.因为转行的人,对计算机没有系统的学习,知识体系不够完善。那么公司在招聘的时候,肯定会优先考虑招聘计算机专业的。既然同学做了转行的打算,就应该接受这个现状。我们不能改变它,就在别的地方下功夫,这一点同学想的是对的,但是努力却用错了方向。


找工作的关键在于自己的核心竞争力,核心竞争力高比别人高你才能胜出。不同阶段,核心竞争力是不一样的。像同学现在还没有工作经验,那么和你竞争初级开发岗位的,大部分都是没有经验的。那么对于没有工作经验的,公司关注的核心是,把你招到公司能不能干活,干的好不好。如果给你一个项目,你能快速上手,而且完成的很出色,没有哪一个公司放着会干活的不要,去招一个只会理论知识不会干活的。


像同学看的专栏,理论知识是比较深的,且不实用,面试官不会在招聘初级开发人员时考察这些。而是看你都做过什么项目,这直接关系到你到了公司能不能上手干活。所以你要有几个自己拿的出手的项目,这些项目不是说非得是工作中的项目。自己平时,可以在网上找一找有没有好的网站,可以模仿着自己做一个小网站。


等到你以后有了工作经验,再去往深层次的理论知识上提升。因为到后面竞争高级开发岗位时,都是有很多项目经验的人了,大家都会做项目,而且干的好。所以面试官就不会再去考察这些大家都会的东西,而是找一些比较偏比较深的知识,来考察大家知识面的广度了。


如果想要得到面试机会,可以在简历上下一点功夫,把自己会的都写出来。在薪资上,可以适当放低一点,这也算是一个竞争优势。第一份工作,薪水不要看太重,能在工作中,实打实的做几个项目给自己镀金才是最有价值的。老师只能是给你一点指导建议,最重要的还得自己努力,加油哦~


祝学习愉快~


  • 提问者 嗯嗯_ #1
    我看的专栏就是面试的专栏,不是提升的专栏。有去面试,一上来不看项目,先笔试,再问问题,问题就是闭包,作用域,作用域链,dom,bom,promise,浏览器请求,面向对象,es6,冒泡算法,vue语法,原理,react原理这些东西。我也拿到过offer,去了一家外包公司,上了几天就没去了,公司就不像一家互联网公司,更像传销公司。你们宣传前端好就业,学了才知道根本就不是这样。好就业的都是骗子公司。我准备的项目就是就业班的项目,如果学分拿到最高,是不是能证明我有写代码的能力,别的公司认不认可
    2020-10-22 11:41:52
  • 提问者 嗯嗯_ #2
    你们的专栏里讲的东西和百度百科讲的不一样,不知道谁说的对,你能说一下闭包创建的过程吗,从外部函数定义,创建作用域链说起,一直到全局变量不再引用内部函数,内部函数被回收,你和我说了,我就懂了
    2020-10-22 11:52:19
好帮手慕夭夭 2020-10-21 10:57:38

同学你好,老师看了一下这篇文章,作者描述的比较乱,部分内容说的是有问题的。之前的老师说的也有一些问题,老师再重新给你讲解一下:

(1)这里是有闭包的,参考如下:

replaceThing函数内部创建的对象(即如下图所示的对象)被全局变量theThing使用。因为全局变量在整个程序运行过程中都是不会销毁的(除非关闭页面),所以theThing使用到的值也不会被销毁。

http://img1.sycdn.imooc.com//climg/5f8f9ab409c7a4ca05380241.jpg

可以把someMethod理解为闭包,一是满足函数嵌套函数的结构(someMethod函数所在的作用域是replaceThing函数,如下图所示),二是someMethod函数不能被销毁,它所在的作用域replaceThing函数也不能被销毁,replaceThing函数中的局部变量自然也就不会被销毁了,形成了一个闭包。

http://img1.sycdn.imooc.com//climg/5f8f9e500904d54e05410482.jpg

(2)还有一个问题是链式引用,这才是导致内存泄漏的重要原因。即下一个作用域使用到上一个作用域中创建的对象。关键点在于var originalThing = theThing,具体参考如下:

第一次调用replaceThing函数,我们先称之为作用域1。因为theThing默认值为null,originalThing赋值为null。接着后面重新给theThing赋值为一个对象,注意这个对象是作用域1中的。

第二次调用replaceThing函数,我们先称之为作用域2。theThing现在是一个对象,originalThing赋值为一个对象。因为这个对象是在作用域1中的创建的,作用域2在使用这个对象时,作用域1肯定不能被释放了。接着后面又重新给theThing赋值为一个对象,注意这个新对象是作用域2中创建的。可想而知,当第三次调用函数时,作用域3也会使用到作用域2所创建的对象。定时器无限的执行,一直调用replaceThing,所以下一个作用域里面就会使用上一个作用域里面创建的对象,形成了链式引用。

http://img1.sycdn.imooc.com//climg/5f8f9ca40949841c05680526.jpg

(3)unused函数没有被任何地方使用,所以把它当成一个用var定义的普通变量就行。

(4)这个闭包没有什么意义,因为这只是别人写代码导致的一个bug。而且这个闭包非常的特殊,算是一个特例吧,老师在之前也没有遇到过这种形式的闭包,所以在看文章的时候,也是思考了好久才知道作者想要表达的意思。同学现在刚入门,看这种知识比较深入的文章还不太适合,对自己学习上的提升帮助不大。不管是去面试还是工作,它都没有意义,只能是作为一个知识的扩展了解一下。建议同学先把学习重点放在课程内容本身,把基础打好,把课程讲解的大案例都做一做,这才是对你以后面试和工作有实质性帮助的哦。


祝学习愉快~

  • 提问者 嗯嗯_ #1
    我有一点基础,课程的选择题,自由编程都做了。找到工作的关键是要和别人不一样,你才能胜出,我薄弱的环节是,很难以现在的背景拿到一面(大专学历,转行),您能帮我解决一面的问题吗
    2020-10-21 19:25:11
  • 提问者 嗯嗯_ #2
    replaceThing函数执行完之后,不会被调用栈弹出吗
    2020-10-21 19:26:40
好帮手慕夭夭 2020-10-20 19:38:19

同学你好,问题解答如下:

1. 函数嵌套是闭包最基本的要素,闭包的本质是:函数里的子函数被函数外部一直使用,使子函数无法释放,保持其所在作用域,形成一个封闭的作用域,称之为闭包。

2. 根据上面所讲,“一个全局函数引用了去全局的变量”不会形成闭包。

3. 关于闭包的理解,可以参考"慕欧欧”老师的例子去理解。

4. 有关同学所说的专栏,可能与就业班所讲的不是同一个意思。建议同学把专栏视频链接发给老师,以便老师审核课程内容是否讲错。

祝学习愉快~


  • 提问者 嗯嗯_ #1
    http://www.imooc.com/read/70/article/1616这是专栏的链接,它里面举的例子,他对例子的理解,都是不对的吗?
    2020-10-20 20:21:32
好帮手慕久久 2020-10-20 17:36:25

同学你好,replaceThing不是闭包,整个代码中都没有闭包结构。虽然闭包的基本结构是函数嵌套函数,但是要嵌套的子函数返回出去,让子函数处于全局作用域中,才会形成闭包,比如下面的例子:

http://img1.sycdn.imooc.com//climg/5f8eccb509c00aac04550350.jpg

b不能被释放,b中会访问函数a中的变量i,所以变量i所在的环境也会保留下来,这样就形成了闭包。

祝学习愉快!

  • 提问者 嗯嗯_ #1
    闭包只有函数嵌套这一种吗,一个全局函数引用了去全局的变量,这个函数难道不是闭包吗
    2020-10-20 17:56:03
  • 提问者 嗯嗯_ #2
    你们的专栏里写的,someMethod是一个可抵达的闭包,unused是一个不会被使用的闭包,如果专栏说的是对的,unused是一个闭包,你说就不对。
    2020-10-20 18:13:26
好帮手慕久久 2020-10-20 09:30:24

同学你好,理解不正确,内存中始终只有一个theThing和一个replaceThing。具体如下:

  1. 虽然定时器每执行一次,就会调用一次replaceThing,但是replaceThing方法内的theThing,都是修改的全局中的theThing,因此始终只有一个theThing(全局的),只有一个它会常驻内存;

  2. 定时器每次调用replaceThing完毕,replaceThing所创建出来的作用域、内存空间就会被销毁,所以即使间隔一段时间后,会再次调用replaceThing,但是内存中始终只有一个replaceThing处于执行、执行完毕的过程中。

祝学习愉快!

  • 提问者 嗯嗯_ #1
    replaceThing是闭包吗
    2020-10-20 16:38:31
好帮手慕久久 2020-10-19 18:25:22

同学你好,很抱歉,老师第一次回复的确有些问题,现更正如下:

  1.  someMethod不是闭包,它没有引用任何局部作用域中的变量。

  2. 内存不会泄露。

  3. unused是不可抵达的,因此不管它执不执行都会被标记清除,由于unused是会标记清除的,所以内存不会泄露(同学理解正确)。

  4. 全局中的theThing是不会被标记清除的,因为页面没有关闭,所以它会常驻内存。

祝学习愉快!

  • 提问者 嗯嗯_ #1
    theThing不会清除,是不是意味着内存里有1000个theThing对象
    2020-10-19 20:04:52
  • 提问者 嗯嗯_ #2
    有1000个replaceThing执行上下文
    2020-10-19 20:05:37
嗯嗯_ 提问者 2020-10-19 16:37:46

我想问会不会内存泄露,原因是什么

嗯嗯_ 提问者 2020-10-19 16:32:35

unused变量是不可抵达的,不是吗,不可抵达不管变量的值是什么,有没有执行都会被清除,并不会保留在内存中,那么他的闭包也会被清除,并不会造成内存泄露,这样理解哪里不对了

好帮手慕久久 2020-10-19 11:18:23

同学你好,是想问“theThing”会不会标记清除吗?如果是的话,它不会被标记清除,它里面的someMethod也不会被标记清除,具体如下:

定时器每隔1s就执行一次,每执行一次,就会声明一个unused函数,并更改theThing的值,这个过程中,unused是未执行的状态,它里面的变量不会离开它的执行环境(只有unused执行了,它里面的变量才会离开执行环境),而unused中,引用了originalThing,从而间接的引用了theThing,因此theThing不会被标记清除。而theThing最终的值里包含someMethod,因此someMethod也不会被清除。

如果不是这个问题,建议同学在详细描述一下,以便老师更快的为你解答问题。

另外,垃圾回收机制,同学只要大概了解一下即可,不用深入了解。

祝学习愉快!

  • 提问者 嗯嗯_ #1
    someMethod函数是一个闭包函数吗,如果是,它引用了哪个自由变量
    2020-10-19 17:32:49
问题已解决,确定采纳
还有疑问,暂不采纳

恭喜解决一个难题,获得1积分~

来为老师/同学的回答评分吧

0 星

相似问题

登录后可查看更多问答,登录/注册

请稍等 ...
意见反馈 帮助中心 APP下载
官方微信

在线咨询

领取优惠

免费试听

领取大纲

扫描二维码,添加
你的专属老师