一天不装逼,就难受。本来还想装一下,但是打开 ecma-262 自己就绝望了;没有猴年马月不可能看一遍。只好自己举栗子来说明一下JavaScript中的原型,作用域……
栗子(一)
1 | var ILoveU = 'are you kidding?'; |
这里不会用什么奇技淫巧,注重基本概念的理解。我不会定义一个这个的函数 function shit(shit){var shit; return shit;}
。这ecma-262发展太快了。不断有新的特性加入,还有概念上的改名。先有个简单的理解,以后再慢慢细化。
我们让栗子(一)跑起来,用Chrome 【版本 67.0.3396.99(正式版本) (64 位)】吧,我觉得灰常好用(我搞不懂,IE11在win7上跑,反正很难用,我也不想截图了)。
先粗略的过一遍,直观的感受一下。
- 我们第一个要看的就是 Call Stack
在 Call Stack 中,最底下的(anonymous)就是大伙说的 global Execution Context,我们的源代码就在这里面运行。其中定义一个变量,一个函数,然后到了 19行,调用了helloWorld,就产生了新的 helloWord-EC;里面定义了一个”2B”变量,一个函数,再到14行调用了worldCup2018,产生了worldCup2018-EC。
这种Stack数据结构哪里都有它的身影,什么push,pop……
瞅了一眼ecma-262,哎,真是复杂……你只要记住,调用函数就会产生一个新的数据结构EC就得了(至于第一个global-EC怎么产生的?还有没有其他方法产生?我们后面会有所涉及)。
爱因斯坦:「如果你无法向6岁小孩解释它,那代表你自己也不明白。」
对这句话,我是深表怀疑。老爱真的说过?鸡汤喝多了,不好。该下功夫的地方还是自己出力,我还想一个月让小孩长到20岁那么大呢。谈不上什么对错,看山是山,看山不是山,看山还是山,不多说了。
- 假设,我们让代码停在第一行(怎么停?踩刹车啊!自己去改改)。我有句话不知当讲不当讲?这个点,其实挺重要的。此时已经进入了global-EC。但是在进入之前,V8(或者其他引擎)会创建好 global object(到底怎么弄的?我也不知道,或许我说的就是错的,但是 global object 得有)。这是什么东东?
简单说,global object上面挂了很多东西,比如Infinity,NaN,parseInt,Number,Object,Map,Promise……
。记住,是在执行代码之前创建的global object。管你有没有代码,都有这东西。浏览器执行你的代码,产生global-EC,上面说了这是一个重要的数据结构,要存很多值。情况没有我们想的那么简单,但是复杂的情况我们不考虑,呵呵(不管你我理解对错,你我的代码都能运行,或许还很完美。你没养过猪,但是会吃红烧肉啊!)。
3. fuck ecma-262。我们还是来说global-EC,我们只看Realm部分。> Before it is evaluated, all ECMAScript code must be associated with a realm. Conceptually, a realm consists of a set of intrinsic objects, an ECMAScript global environment, all of the ECMAScript code that is loaded within the scope of that global environment, and other associated state and resources.把 global object挂上去, global Environment Record也挂上去。
我他妈还以为自己在实现ecma-262呢!
- 看着我们的代码,我们还是来说global-EC,(我不知道EC这个数据结构各个部分该叫什么名字,网上有叫VariableObject/ActiveObject,ScopeChain,thisValue的)。我也不打算起什么名字了。放个图,看吧。
此时Scope里面就一个Global,Global上挂了global object,还有各种乱七八糟的属性;注意ILoveU: undefined
和 helloWorld: f helloWorld(argc, argv)
。此时我们的代码还在第一行。这也就是进入EC的时候,初始化阶段;然后才是执行代码。(明白这两个阶段,你就能明白什么时候一个名字绑定了你所期望的值。)
- 初始化阶段,把var,FD都存入当前环境,也就是Scope的Global中。var的变量
ILoveU
,名字存进去,值是undefined。FDhelloWorld
名字也存进去,值就是一个函数对象。(FD、FE、NFE、FS自便查找)(注意:Scope,GLobal是当前我用的调试器中的。不必在意这些名字。只需记住有个数据结构存储环境即可。就是你一眼看过全局代码,有些名字需要存起来。)this
被设置为 Global,此时就是window。
Scope,这个词,我不知道为什么很多地方都叫作用域。我还是觉得环境更准确。任何程序的运行,都必须有外界提供一个初始环境。硬件给操作系统提供,操作系统给各种游戏、浏览器提供,浏览器给JS代码提供。不管是外环境还是内环境,都是事物发展所必须的。你说个人名“王麻子”,你的小伙伴就知道你在说“隔壁老王”,而不是说公元前666年的老王;小伙伴们都默认了一个小环境;如果没有环境,什么都说不清。当然,如果你隔壁两边都住的“王麻子”,就需要提供更多的信息,才能区分你说的左隔壁还是右隔壁老王。
还有一点很重要,我们点击看到helloWorld
,能看到[[Scopes]]。这个是干啥用的?先不管干啥用的,它的值,记录了定义时环境。helloWord
哪里定义的,global-EC中啊,所以它的[[Scopes]]值就是global-EC的Scope,这里是含有一个元素的数组的形式,也就是[Global]。
- 执行阶段,好的,那我就一步一步执行即可。第一行代码,执行完毕。
ILoveU: "are you kidding?"
;然后就到了17行helloWorld(1, "holy shit!");
,那么我们继续执行。欧,我们进入helloWord-EC了。
我们不扯乱七八糟的名称了。直接撸代码。
- 那我们就来看看helloWord-EC。
初始化阶段,这个是函数调用产生的EC,比global-EC多了参数,但还是要构建环境。在局部环境Local里存
argc: 1, argv: "holy shit!", BBC: undefined, worldCup2018: f worldCup2018(), this: undefined
,实际应该还有arguments对象,不过Chrome浏览器显示的数据应该都是优化过的。(后面会稍稍提及。)helloWord
产生了Local,再加helloWord
的[[Scopes]]共同构成了此时的Scope。注意,我用了"use strict";
,就是不想过多涉及this
,我觉得 this is not important, at least for now, it’s irrelevant. 如果没有strict,this会被设置为window。worldCup2018
的[[Scopes]]就是helloWord-EC的Scope,也就是[Closure (helloWorld), Global]。执行阶段,走一步瞧瞧,
BBC: "2B"
,我们又来到了14行worldCup2018();
,没啥可说的,继续走起。
- 那我们就来看看worldCup2018-EC。
初始化阶段,初始化局部环境Local,外加它自己(
worldCup2018
)的[[Scopes]],构成此时的Scope。你或许很纳闷,Closure (helloWorld)
里面只有一个BBC: "2B"
,其实这里应该是最少显示了,显示多余的没卵用。worldCup2018有一句return BBC
,仅仅用到了Closure (helloWorld)
中的BBC
,如果用到了 helloWorld的argc,这里也会显示;如果连BBC
也没有用到,Chrome这里就不会有Closure (helloWorld)
,直接优化掉了。因为此时,Closure (helloWorld)
对worldCup2018来说就是多余的,但是挂在那里也是可以的。(比如你在worldCup2018里面再定义一个函数并且此函数用到了BBC,再调用一下此函数,那么Closure (helloWorld)
对worldCup2018就不能优化掉。)执行阶段,没什么可说的。
我们该回去了。没必要再定义66层函数了。三层,我都感觉多了。我脑子不够用。
注意看 Call Stack。
Look,我们退出了worldCup2018-EC。时间不早了,继续走。
退出了helloWord-EC。
退出了global-EC(global-EC应该是程序关闭了才退出。调试器在这里插了一脚,这里应该与调试有关)。
回家吃饭吧。
本来打算几句话讲讲prototype,scope。到现在却感觉Call Stack,尤其EC都说的很乱。改天补充吧。