You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
想到这,我们是不是应该换个思维去想一下,是不是可以把 const kun = {} 可以当成 new Kun() 来看待。比如人们常说 const kun = {} 就是一个最经典的单例。
下面我先进行,第一种方式的推导:
按照 new Kun() 的过程思考
当执行完 kun = {...} 的时候,如果按照 new 创建对象的方式,来去分析的话,
会经历下面几个过程:
第一:执行构造函数,形成一个私有栈内存(作用域)
第二:将堆内存中的代码复制到栈内存中
第三:各种变量 change
第四:开辟一个堆内存
第五:将 AO (构造函数)中的 this 指向这个堆内存
第六:进行对象的各种属性和方法的初始化,比如增加 a 属性和 say 方法
最后:new 完后,把堆内存地址返回给变量,比如 kun ,然后从这里开始,kun 这个变量就在保留在栈中,并且指向上面的堆内存。
这个时候当你去执行 kun.say() 的时候,就是把 say 函数放到栈顶进行执行,那么问题来了,AO(say) 中的 this 指向什么?按照我写的博客中的说话,是指向调用者,就像全局函数的调用者是 window 对象,这里 say 的调用者是 kun 对象。
问题是这一步究竟怎么理解呢?
如果按照我写的博客去理解的话,你可以理解成当执行完构造函数 Kun 时,会留下一个 AO(Kun) 当然也就是一个节点,AO(Kun) 中的 this 指向了 kun 变量指向的堆内存。所以当 kun.say 执行时,里面的this会向上查找,当找到AO(Kun)的时候,会找到this,也就是变量kun指向的那一块堆内存。
这也借鉴了从根本上解释了,为什么单例模式要比各种 new 的性能要好了,因为单例模式,只有一个 AO(单例构造函数)。一个也就意味着只有一个堆内存。因为只有一个 this 。
引言
针对前面我写的
如何编写高质量的函数 -- 敲山震虎篇
的评论区,小伙伴提出的一些问题在这里做一个统一的回答。
当然,鼓励支持的评论就不说了,谢谢小伙伴的鼓励支持,但是对于一些评论和私聊的小伙伴的疑问,我在这里要统一回复下。
问题一 [书籍推荐]
问题描述
膜拜大佬啊,请问有推荐的书籍学习吗?
问题解决
可以看看我写的这篇文章:
这算是目前我认为最具有前瞻性的前端推荐书籍系列的文章(不吹不黑)。
问题二 [思想活动太多]
问题描述
感觉个人的思想活动不要太多,快速切入主题。
解决答案
看到后,我把前面的很多思想活动都注释掉了,只能在编辑中的注释中看到了。
如图所示:
问题三 [读起来很烧脑]
问题描述
这种难度的写成流程图就好了,现在这样看起来好烧脑。
解决答案
说的对,流程图也画过,但就跟
PPT
一样,没有感觉,画出来感觉很丑,而且很费事。看到网上那些很酷的流程图,我表示莫名的好奇和羡慕,这里问一下有没有这方面有经验的博主或者其他小伙伴,可以推荐一下,怎么快速画流程图。问题四 [敲山震虎什么鬼哦]
问题描述
作者是如何理解和使用
敲山震虎
这个成语的解决答案
嗯哼?作者可是在公司十次叫别人的名字,
5
次都会叫错的人。我怎么可能理解敲山震虎
成语的意识,不存的,唯一能解释通的就是,我在起标题时,突然想到了这个成语,突然觉得很酷,然后就用了。当然了,评论区已经有小伙伴给我取好标题了:
问题五 [只有分析,没有怎么编写]
问题描述
写出高质量的函数,怎么写才是高质量呢,感觉内容有点跑题,中心要表达的不明确。
讲的是执行原理,不是怎么写好。
你说到函数创建,我第一反应是解释器翻译成抽象语法树再弄成机器码。太长了,编写高质量的函数的文章后面怎么又出现了老生常谈的
10
个10
闭包问题。解决答案
确实大部分内容都是在分析函数的执行机制,这点没错,看起来好像跑题了,但这也和本身标题起的有关系,总结一下确实有点跑题。但是,我还想说一些事情。
是想让大家拥有这种能力:
在敲下函数的每一行代码时,或者去阅读每一个别人写的函数时,都能构很自然的体会到其底层的一些你看不到的过程和真相。古人云,只可意会,不可言传。这是内心深处的一种领悟吧。
但是我还是决定在答疑篇,依旧举文章中,最后一个例子来说一下,大家应该怎么想这个事情。
如果你想让
i
都是10
,那你就让result
中的函数的父执行环境引用同一个[scope]
属性,说白了也就是让result[i]
函数拥有相同的父执行环境。如果你想让i
是0
到9
,那你就让result[i]
函数拥有不同的父执行环境。怎么做到不同呢?最简单的方法就是在外层套一个匿名的立即执行函数,使其成为result[i]
的父执行环境。同时这样嵌套以后,你还应该要想到一个事情,那就是你要把 不同的 i 存到AO(匿名函数)
这样你才能保证输出0
到10
。看到这,你就应该想到一个事情,就是为什么使用
let
就不用这样了呢?因为
var
存在变量提升,变量提升是什么?变量提升直白点说,就是把变量的作用域提升了。本来应该在块级作用域中,但是由于原来的JS
不存在块级作用域,那就只能提升到函数作用域了,所以执行子函数时,大家都去上一层作用域找了,这时显而易见都是10
。而
let
使块级作用域变成了可能,从而就不再需要在最外层套一个函数来特意的形成一个函数作用域。让你对这些东西越来越有感觉的时候,你就自然的知道如何去看待一个函数。如何去写出一个高质量的函数,你写的时候你会问自己为什么要按照这种所谓的
best practice
去写。所以我就回答这里吧,后续有啥疑问可以继续提出来,一起讨论也是一件开心的事情。问题六 [ Lexical Environment ]
问题描述
Activation object
这个概念的ES3
里的, 打ES5
开始就没这个说法了, 取而代之的是使用Lexical Environment
来描述作用域的。作者能讲讲这个吗?解决答案
这里我就不讲了吧(头发少掉点...),换汤不换药系列😂。有想深入了解的,可以点击下面连接,传输到
ecma262
对应的issues
。有啥别致的收获也可以加我好友和我分享一下。
问题七 [关于
this
]问题描述
this
指向那个,还是没有看明白this
指向的判定原则,例如对象函数调用的时候,this
指向那个对象本身的时候利用这篇文章的知识要怎么解释呢?谢谢了解决答案
OK
,这个问题问的好,首先你可以看一下下面这篇文章群里小伙伴分享的,我大致看了下,分析的非常透彻,如果你认真看,可以解决你的问题,从规范层面上做了解释。
我感觉我自己怎么在造轮子哈哈哈(另一个我赶紧针扎了一下,造轮子不存在的,必须有点特色)。
那下面我来分析一下吧,用一种意识流来帮你分析一下。
提问者说的是:对象函数调用的时候,
this
是怎么玩的,比如下面代码:输出结果如下:
对应红线表示,对应的输出结果,至于第二种为什么是
undefined
, 按照上面我给的文章链接,解释如下图:你看看上面的代码,使用字面量和使用构造函数的结果都是一样的。
想到这,我们是不是应该换个思维去想一下,是不是可以把
const kun = {}
可以当成new Kun()
来看待。比如人们常说const kun = {}
就是一个最经典的单例。下面我先进行,第一种方式的推导:
当执行完
kun = {...}
的时候,如果按照new
创建对象的方式,来去分析的话,会经历下面几个过程:
第一:执行构造函数,形成一个私有栈内存(作用域)
第二:将堆内存中的代码复制到栈内存中
第三:各种变量
change
第四:开辟一个堆内存
第五:将
AO
(构造函数)中的this
指向这个堆内存第六:进行对象的各种属性和方法的初始化,比如增加
a
属性和say
方法最后:
new
完后,把堆内存地址返回给变量,比如kun
,然后从这里开始,kun
这个变量就在保留在栈中,并且指向上面的堆内存。这个时候当你去执行
kun.say()
的时候,就是把say
函数放到栈顶进行执行,那么问题来了,AO(say)
中的this
指向什么?按照我写的博客中的说话,是指向调用者,就像全局函数的调用者是window
对象,这里say
的调用者是kun
对象。问题是这一步究竟怎么理解呢?
如果按照我写的博客去理解的话,你可以理解成当执行完构造函数
Kun
时,会留下一个AO(Kun)
当然也就是一个节点,AO(Kun)
中的this
指向了 kun 变量指向的堆内存。所以当 kun.say 执行时,里面的this会向上查找,当找到AO(Kun)的时候,会找到this,也就是变量kun指向的那一块堆内存。这也借鉴了从根本上解释了,为什么单例模式要比各种
new
的性能要好了,因为单例模式,只有一个 AO(单例构造函数)。一个也就意味着只有一个堆内存。因为只有一个this
。我觉得我推的应该够清晰了,也可能是胡诌的比较有道理,你再思考思考吧,这个答案先这么回答了😂。
下面进行第二种推的方式:
PS: 所以我就不写面试相关的文章,这类文章太多了,我已经超越不了他们了😂-->问题八 [缺少匿名函数作用域]
问题描述
我有一个问题,在解释第二种为什么输出是
0-9
的时候,为什么作用域链 没有匿名函数的作用域节点。并且还是和之前的那种一样即:AO(result[i]) --> AO(kun) --> VO(G)
,匿名函数应该也是有自己的[scope]
的啊。为什么不是AO(result[i]) --> AO(匿名i) -->AO(kun) --> VO(G) 。
执行
result[i]
函数中return i
时,查找的作用域链都是AO(result[i]) --> AO(kun) --> VO(G)
,输出10
的原因,是result[i]
的作用域中没有i
变量,而去寻找的上一作用域节点AO(kun)
。立即执行函数,给result[i]
增加一层作用域,0-9
函数AO(kun)
中i: 10
。解决答案
这里你们说的对,作用域链要有匿名函数的作用域节点。同时
i
也不是在VO(G)
中的,是在AO(匿名函数)
中。问题九 [C/C++建议]
问题描述
作者码这么多字真心不容易哇,看评论区一片叫好的,说几点觉得不同意的地方或建议
js
层面只有调用栈(数据结构为栈,在C++
层面分配在堆内存中)溢出,Maximum call stack size exceeded
,控制台下只有栈内存溢出。C/C++
的一套东西来解释js
,有些不合适。解决答案
建议很好,这些地方笔者确实没有注意到,比如第二点,我确实不知道这个点,谢谢你的建议,学习了一波,后续我会研究一下这方面的知识的。
问题十 [执行过程分析不正确]
问题描述
没明白第二个输出
0-9
的例子中首先和上面不一样了,在声明函数kun
的时候,就已经执行了10
次匿名函数了 这句话。声明阶段也会运行kun
内部的代码?不是执行阶段的时候?我还以为是ECStack
是[EC(匿名0)
,EC(kun)
,EC(G)
],然后
EC(匿名0)出栈,
EC(匿名1)进栈一直到
9出栈,然后
EC(kun)` 出栈。专门为此注册了账号,希望作者大大给小白解释一下for
循环给数组中每一项放入了一个匿名自调函数,这个匿名自掉函数返回了一个函数,这样数组中的每一项都是一个函数的引用。forEach
的时候调用的其实是匿名函数返回的函数,其实就是闭包。问题解答
这里文章中的写法有点问题,正确的是执行函数
kun
的时候,执行了10
次匿名函数。评论的小伙伴的链接推荐
leto 小伙伴
关于栈帧的文章推荐下面链接,讲解的非常透彻
xbup 小伙伴
可以去看汤姆大叔的文章。系列文章对 函数执行时的 变量对象,活动对象,作用域链等有很详细的讲解。
看截图我知道,又一个美好的故事要发生了。
风之语
深夜
4
点完成的博客,发了出来,发完第二天除了回复评论,文章内容我都没看,后面开始看了下文章内容,确实有一些逻辑错误,谢谢小伙伴的纠正。The text was updated successfully, but these errors were encountered: