摘要:检查上下文中的参数,建立该对象下的属性与属性值检查当前上下文的函数声明,也就是使用关键字声明的函数。
function test() { console.log(a); // undefined console.log(foo()); // 2 var a = 1; function foo() { return 2; }; }; test();
不是很准确的草图,大概了解就好。
首先JS解释器(引擎)开始解释代码,构建执行环境栈(Execution Context Stack),并根据执行环境的不同生成不同的执行上下文(Execution Context)
栈底永远是全局上下文(后面说),当遇到test(),确认调用函数,就创建生成test函数自己的上下文,然后将函数执行上下文入栈(push on)到执行环境栈中。
testEC(test Execution Context)将会开始创建变量对象,我们知道,当调用一个函数(激活),一个新的执行上下文就会被创建。而一个执行上下文的生命周期可以分为两个阶段。
创建阶段:
在这个阶段中,执行上下文分别会创建变量对象、建立作用域链,以及确定this的指向。
代码执行阶段:
创建完成后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。
所以testEC将会生成一个变量对象,与之还有作用域链、this(这里先不讨论),注意,这里是变量对象的创建阶段,用VO简示。
变量对象的创建,经历以下过程。
建立arguments对象。检查上下文中的参数,建立该对象下的属性与属性值(key-value)
检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。
所以,于是就有了图中的VO引用的对象的图示,这就是变量提升的真正原因,对于函数声明,它的优先级是大于变量声明的,所以在创建阶段,函数声明的函数名就持有了函数的引用。而变量赋值需要在后面的执行阶段才被赋值。
未进入执行阶段前,变量对象中的属性都不能被访问!但是进入执行阶段之后,变量对象转变为了活动对象(Active Object),里面的属性都能被访问了,然后开始进行执行阶段的操作。
进入执行阶段,这时可称之为活动对象了(Active Object),将进行赋值操作。于是这时的变量就能被自由访问了。
关于arguments对象,是个类数组结构目前我的测试:
函数内设有形参,但是不传实参,遍历arguments对象时没有任何值
函数内不设形参,但是传实参,遍历arguments对象有值对应的索引
function test(a,b,c,d) { for (var i in arguments) { console.log(i); } console.log(arguments.length+"个"); }; test(1,2,3,4); // 0 1 2 3 // "4个" function test2(a,b,c,d) { for (var i in arguments) { document.write(i+"
") } document.write(arguments.length+"个"); }; test2(); // 什么也没有 // 0个
脑子里有这副图后,我们在来看另一个栗子:
function test() { console.log(foo()); console.log(bar); var foo = "Hello"; console.log(foo); var bar = function () { return "world"; }; function foo() { return "hello"; }; }; test(); // "hello" // "undefined" // "Hello"
主要讨论同变量声明与同变量声明与函数声明的情况,这也是变量对象在创建时所做的工作;
检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。
如代码中的foo,在test函数的执行上下文创建变量对象后,创建阶段,foo就是变量对象中的的键名(Key),而键值就是函数的地址指针,因为函数声明的优先级大于变量声明,所以foo此时就持有了函数的引用,而var foo的变量声明就被跳过。
执行阶段,进行赋值操作,foo被重新赋值"Hello",同时变量bar从未赋值(undefined)到持有一个函数的引用,这就是变量对象=>活动对象,执行栈,执行上下文所发生的操作。
关于全局上下文var globla = 10; function test() { return globla++; }; test();
// 以浏览器中为例,全局对象为window // 全局上下文 windowEC = { VO: window, scopeChain: {}, this: window }
以浏览器中为例,全局对象为window。
全局上下文有一个特殊的地方,它的变量对象,就是window对象。而这个特殊,在this指向上也同样适用,this也是指向window。
除此之外,全局上下文的生命周期,与程序的生命周期一致,只要程序运行不结束,比如关掉浏览器窗口,全局上下文就会一直存在。其他所有的上下文环境,都能直接访问全局上下文的属性。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/83386.html
摘要:最先执行完毕的一定是最里面的函数,执行过后弹出调用栈,接着执行上一层函数,直至所有函数执行完,调用栈清空。到这里你应该就会明白,上面函数调用栈,就是生成了一个函数的执行上下文。 showImg(http://upload-images.jianshu.io/upload_images/7803415-36e8e7d048f63524.jpg?imageMogr2/auto-orient...
摘要:最先执行完毕的一定是最里面的函数,执行过后弹出调用栈,接着执行上一层函数,直至所有函数执行完,调用栈清空。到这里你应该就会明白,上面函数调用栈,就是生成了一个函数的执行上下文。 showImg(http://upload-images.jianshu.io/upload_images/7803415-36e8e7d048f63524.jpg?imageMogr2/auto-orient...
摘要:最先执行完毕的一定是最里面的函数,执行过后弹出调用栈,接着执行上一层函数,直至所有函数执行完,调用栈清空。到这里你应该就会明白,上面函数调用栈,就是生成了一个函数的执行上下文。 showImg(http://upload-images.jianshu.io/upload_images/7803415-36e8e7d048f63524.jpg?imageMogr2/auto-orient...
摘要:在中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈。而处于栈顶的是当前正在执行函数的执行上下文,当函数调用完成后,它就会从栈顶被推出理想的情况下,闭包会阻止该操作,闭包后续文章深入详解。 写在开篇 已经不敢自称前端小白,曾经吹过的牛逼总要一点点去实现。 正如前领导说的,自己喝酒吹过的牛皮,跪着都得含着泪去实现。 那么没有年终完美总结,来个新年莽撞开始可好。 进击巨...
摘要:在中,通过栈的存取方式来管理执行上下文,我们可称其为执行栈,或函数调用栈。因为执行中最先进入全局环境,所以处于栈底的永远是全局环境的执行上下文。 一、什么是执行上下文? 执行上下文(Execution Context): 函数执行前进行的准备工作(也称执行上下文环境) JavaScript在执行一个代码段之前,即解析(预处理)阶段,会先进行一些准备工作,例如扫描JS中var定义的变量、...
摘要:作用域链用于表明上下文的执行顺序。当前上下文执行完毕则出栈,执行下一个上下文。 从一个简单的例子出发 先从一个简单的例子出发(先不涉及异步),看看自己是否大致了解浏览器的执行机制: console.log(a); var a=1; function foo(a){ console.log(a); var a=2; console.log(a); } foo(a)...
阅读 1542·2021-11-23 10:01
阅读 2948·2021-11-19 09:40
阅读 3193·2021-10-18 13:24
阅读 3444·2019-08-29 14:20
阅读 2955·2019-08-26 13:39
阅读 1222·2019-08-26 11:56
阅读 2637·2019-08-23 18:03
阅读 346·2019-08-23 15:35