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);
在这里输入代码,可通过选择【代码语言】突出显示
正在回答
同学你好,可以这样理解。另外,关于闭包,有不同的说法。但是大家所熟知的,是之前老师给你讲解的那种。在学术上,有的概念确实不同人有不同理解,这个倒没有什么影响,你可以保留自己的理解哦。
祝学习愉快~
同学你好,问题解答如下:
1.理解的没错,每个函数都会形成自己的作用域链的。
2.作用域链是函数的一个内部属性,老师也给你打印过哦,console.dir就是打印函数内部的方法和属性,再看一下,如下圈出的这些都是函数的属性,而其中的[[Scopes]]属性,它里面保存的就是作用域链。
3.作用域简单的来说就是函数或者变量可访问范围,例如一个函数fn中定义一个变量a,这个变量a只能在函数fn中可以访问,这个范围就可以理解为作用域。
4.作用域链本身就是作用域组成的,例如老师拿一串糖葫芦,跟同学说这就是糖葫芦。然后同学说,老师我看见的是山楂啊。说它是山楂还是糖葫芦都没有对错,就看你对待事物的理解了。我们理解一个抽象的概念时,不能太具象化了。你把它具象化了,看到的实物就是山楂。你把它抽象化了,那么它就是糖葫芦。即使你把它吃的只剩一个山楂了,我也可以叫它糖葫芦。
作用域和作用域链就是这么个关系。下面打印的是f3函数的[[Scopes]]属性,它里面保存的是这个函数的所能够访问的作用域。这些它能够访问到的作用域,不就形成了一个作用域链么。所以f3函数中使用某一个变量,就是顺着这个作用域链去查找。
学习计算机,对于一些专业术语,理解要灵活一点。这些专业性的文档,原版都是英文的。所以翻译的过程中,同一个概念,在理解与描述上可能不太一样。但是万变不离其宗,不管大家如何去描述这个概念,它核心的作用大家都知道是怎么回事就行,不是非得找一个统一的话术去给它下个定义。就像前面老师说的作用域,非要具象化,那么它就是你所看到的一个保存着变量、函数等等的对象集合。但是具象化了,你根本不知道作用域是干嘛的,因为设计作用域的初衷肯定不是为了让它保存变量或者函数等,所以按照老师自己的理解,把它说成变量或者函数的可访问范围更通俗。当你自己学习了这个概念之后,也可以按照自己的理解去描述它,本身没有什么对与错,不用太纠结这些哦。
另外,因为这个问题下一直回复,看起来很乱,不便于同学后期的复习与整理。如果还有其他问题,可以重新创建一个问题提问,灰灰老师会继续为你解答。
祝学习愉快~
同学你好,参考如下理解:
1.在JavaScript中,函数也是对象。函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供JavaScript引擎访问的内部属性。其中一个内部属性是[[Scope]],该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为作用域链。
2.老师还要啰嗦一下作用域链的形成,不然很难理解。
参考如下例子:
当有函数嵌套时,会把局部作用域推入到[[Scope]]的首部(0的位置)。如果函数中还嵌套着子函数,那么这个子函数又会推入到作用域链的最前端(即0的位置),而外层函数作用域的位置就变成了1,全局作用域位置为2。子函数中如果还嵌套着孙子函数,孙子函数作用域也会推入到作用域链的最前端,位置层层递进......而作用域链的终点就是全局作用域(Global)。
我们看一下输出结果:
以上圈出的就是f3的作用域链。访问变量时,查找[[Scope]]这个对象的顺序是按照升序,例如查找变量a,先在当前作用域f3中查找,f3中没有定义此变量。那么就会在原型链中按照顺序查找,即先在第一个对象中(位置0的函数f2)中查找,没有找到变量a。就继续作用域链中的下一个对象(位置1的函数f1)中查找,找到了这个变量就会打印。假如查找一个变量,找到作用域链的最低端(全局 Global)还没有查找到此变量,则标记为变量未定义,程序就会报错。
综上,简单的理解就是,查找一个变量,如果在当前作用域中没有查到,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
3.感谢同学对老师的肯定,如果回答有帮助到你,可以采纳一下哦,老师也需要鼓励呢~QAQ
祝学习愉快~
同学你好,问题解答如下:
1.一般没有人说闭包的创建过程是什么,按照老师的理解,闭包只是函数使用的一种形式,同学所说的只是代码的一个执行流程。不过每一个人的理解是不一样的,没有什么对与错,对于同学描述的闭包创建过程,这样理解也是可以的哦。
2.“闭包是一个对象,是函数的属性”这个说法不对。之前老师说过,闭包的结构是函数嵌套着函数,闭包就是能够读取函数内部变量的函数。
常见的闭包是之前欧欧老师举得例子,即如下例子:
你可以理解为闭包是定义在一个函数内部的子函数(例如如下函数b是闭包),并不是函数的属性。
3.浏览器中开发者工具,是指的按F12查看Sources吗?如下图所示,如果是,那么说的是对的。如果不是,请截图说明一下。
4. 按照老师的理解,调用栈就像浏览器中的javascript解释器,是追踪函数执行流的一种机制。当执行环境中调用了多个函数时,通过这种机制,我们能够追踪到哪个函数正在执行,执行的函数体中又调用了哪个函数。在本案例中,replaceThing 函数内部没有调用其他函数了。它调用之后,会被推入到调用栈里面,当函数调用完毕之后,就会被弹出调用栈。
假如说replaceThing 函数里面调用了另一个函数xx(),那么执行过程是:
(1)先执行replaceThing (),形成一个栈帧。执行replaceThing 的时候,里面调用了xx(),又形成另一个栈帧压在旧栈帧之上(即xx的这个栈帧在栈顶,replaceThing栈帧在栈底)。
(2)当xx执行完毕之后,就会推出(弹出)调用栈,继续执行replaceThing ,当replaceThing 执行完毕,replaceThing 也被调用栈推出了。
同学说的全局变量引用函数内部的变量,是说的内存中,变量指向某一个内存地址,和调用栈没有关系,这里不要混淆哦。
5. 同学问的内容都很深了,面试一般都不会问这些哦,不需要过多深究,了解一下就行。另外,不管是百度百科,博客或者其他论坛,说法都不太明确,也不一定准确,容易造成误解。例如百度百科,一是百度百科说的专业术语过多,不够通俗,理解起来容易产生偏差。二是,一个名词应用的场景比较多。在不同的学术上,同一个名词所表示的概念会不太一样,甚至完全是两个意思。你查到的名词,可能与我们前端方面所说的概念不是一回事。
祝学习愉快~
同学你好,问题解答如下:
1.闭包是让外部可以访问函数内部的变量,使内部变量不能被销毁。它只是函数使用的一种形式,所以闭包没有什么创建过程可言,同学说的是代码执行的过程。不同代码执行过程不一样,同学提问中的例子,详细的执行过程,老师在上次已经讲解,参考如下:
2.有一部分公司是有笔试这个流程的,但是问的原理不会很偏。笔试只是一个加分项,并不是面试成功的关键。做完笔试,会有技术官进行面试,重要的还是面试哦。闭包,作用域,dom,bom,promise...这些都是课程中讲过的。老师只是针对同学提问中的例子,是非常偏的。虽然它写在了面试专栏中,但估计没有几个公司会考察这个例子。另外,老师也拿着这个例子与前端前辈进行了一些交流和讨论,也说这个例子没有实际的意义。而且再往深入里面挖掘它,就超出了老师们的知识范畴了。
3.外包的意思就是别的公司没有开发团队,然后承包给外包公司去做。外包公司业务多,锻炼机会更多。其实,在外包公司更能锻炼与提升自身的能力。骗子公司在每一个行业都有存在的,如果使用任何理由收取你费用的,那肯定是骗子公司了。另外,同学面试时,可以在网上查一查要面试的公司,如果是正规的外包公司是不用担心的。
4.前端好就业只是相对其他行业来说,人才需求量大,机会多。但是能不能抓住机会,还是要看自己,不是说你学了就能找到,要提升自己的核心能力。能力强了,机会才能来找你哦。慕课网的学分企业是不看的,但是课程中的项目是可以在面试的时候展示的。
祝学习愉快~
同学你好,问题解答如下:
1.代码执行之后,自然会从调用栈中移除的,但跟内存是两码事哦。
2.因为转行的人,对计算机没有系统的学习,知识体系不够完善。那么公司在招聘的时候,肯定会优先考虑招聘计算机专业的。既然同学做了转行的打算,就应该接受这个现状。我们不能改变它,就在别的地方下功夫,这一点同学想的是对的,但是努力却用错了方向。
找工作的关键在于自己的核心竞争力,核心竞争力高比别人高你才能胜出。不同阶段,核心竞争力是不一样的。像同学现在还没有工作经验,那么和你竞争初级开发岗位的,大部分都是没有经验的。那么对于没有工作经验的,公司关注的核心是,把你招到公司能不能干活,干的好不好。如果给你一个项目,你能快速上手,而且完成的很出色,没有哪一个公司放着会干活的不要,去招一个只会理论知识不会干活的。
像同学看的专栏,理论知识是比较深的,且不实用,面试官不会在招聘初级开发人员时考察这些。而是看你都做过什么项目,这直接关系到你到了公司能不能上手干活。所以你要有几个自己拿的出手的项目,这些项目不是说非得是工作中的项目。自己平时,可以在网上找一找有没有好的网站,可以模仿着自己做一个小网站。
等到你以后有了工作经验,再去往深层次的理论知识上提升。因为到后面竞争高级开发岗位时,都是有很多项目经验的人了,大家都会做项目,而且干的好。所以面试官就不会再去考察这些大家都会的东西,而是找一些比较偏比较深的知识,来考察大家知识面的广度了。
如果想要得到面试机会,可以在简历上下一点功夫,把自己会的都写出来。在薪资上,可以适当放低一点,这也算是一个竞争优势。第一份工作,薪水不要看太重,能在工作中,实打实的做几个项目给自己镀金才是最有价值的。老师只能是给你一点指导建议,最重要的还得自己努力,加油哦~
祝学习愉快~
同学你好,老师看了一下这篇文章,作者描述的比较乱,部分内容说的是有问题的。之前的老师说的也有一些问题,老师再重新给你讲解一下:
(1)这里是有闭包的,参考如下:
replaceThing函数内部创建的对象(即如下图所示的对象)被全局变量theThing使用。因为全局变量在整个程序运行过程中都是不会销毁的(除非关闭页面),所以theThing使用到的值也不会被销毁。
可以把someMethod理解为闭包,一是满足函数嵌套函数的结构(someMethod函数所在的作用域是replaceThing函数,如下图所示),二是someMethod函数不能被销毁,它所在的作用域replaceThing函数也不能被销毁,replaceThing函数中的局部变量自然也就不会被销毁了,形成了一个闭包。
(2)还有一个问题是链式引用,这才是导致内存泄漏的重要原因。即下一个作用域使用到上一个作用域中创建的对象。关键点在于var originalThing = theThing,具体参考如下:
第一次调用replaceThing函数,我们先称之为作用域1。因为theThing默认值为null,originalThing赋值为null。接着后面重新给theThing赋值为一个对象,注意这个对象是作用域1中的。
第二次调用replaceThing函数,我们先称之为作用域2。theThing现在是一个对象,originalThing赋值为一个对象。因为这个对象是在作用域1中的创建的,作用域2在使用这个对象时,作用域1肯定不能被释放了。接着后面又重新给theThing赋值为一个对象,注意这个新对象是作用域2中创建的。可想而知,当第三次调用函数时,作用域3也会使用到作用域2所创建的对象。定时器无限的执行,一直调用replaceThing,所以下一个作用域里面就会使用上一个作用域里面创建的对象,形成了链式引用。
(3)unused函数没有被任何地方使用,所以把它当成一个用var定义的普通变量就行。
(4)这个闭包没有什么意义,因为这只是别人写代码导致的一个bug。而且这个闭包非常的特殊,算是一个特例吧,老师在之前也没有遇到过这种形式的闭包,所以在看文章的时候,也是思考了好久才知道作者想要表达的意思。同学现在刚入门,看这种知识比较深入的文章还不太适合,对自己学习上的提升帮助不大。不管是去面试还是工作,它都没有意义,只能是作为一个知识的扩展了解一下。建议同学先把学习重点放在课程内容本身,把基础打好,把课程讲解的大案例都做一做,这才是对你以后面试和工作有实质性帮助的哦。
祝学习愉快~
同学你好,理解不正确,内存中始终只有一个theThing和一个replaceThing。具体如下:
虽然定时器每执行一次,就会调用一次replaceThing,但是replaceThing方法内的theThing,都是修改的全局中的theThing,因此始终只有一个theThing(全局的),只有一个它会常驻内存;
定时器每次调用replaceThing完毕,replaceThing所创建出来的作用域、内存空间就会被销毁,所以即使间隔一段时间后,会再次调用replaceThing,但是内存中始终只有一个replaceThing处于执行、执行完毕的过程中。
祝学习愉快!
同学你好,是想问“theThing”会不会标记清除吗?如果是的话,它不会被标记清除,它里面的someMethod也不会被标记清除,具体如下:
定时器每隔1s就执行一次,每执行一次,就会声明一个unused函数,并更改theThing的值,这个过程中,unused是未执行的状态,它里面的变量不会离开它的执行环境(只有unused执行了,它里面的变量才会离开执行环境),而unused中,引用了originalThing,从而间接的引用了theThing,因此theThing不会被标记清除。而theThing最终的值里包含someMethod,因此someMethod也不会被清除。
如果不是这个问题,建议同学在详细描述一下,以便老师更快的为你解答问题。
另外,垃圾回收机制,同学只要大概了解一下即可,不用深入了解。
祝学习愉快!
恭喜解决一个难题,获得1积分~
来为老师/同学的回答评分吧
0 星