Comments (256)
未进入执行阶段之前,变量对象(VO)中的属性都不能访问!但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。
它们其实都是同一个对象,只是处于执行上下文的不同生命周期。@jDragonV
from blog.
@mqyqingfeng 楼主,有幸拜读你的深入系列,收获颇多,但也存在一些疑问。比如变量对象留给我们的思考题的第二题,按照你的写法:
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1; // 打印函数
但个人觉得这句“这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。”解释得有点欠完整,如果我把代码改写成下面这样:
var foo = 1;
console.log(foo);
function foo(){
console.log("foo");
};
这次打印结果就是“1”;
所以我觉得这么解释比较好:
进入执行上下文时,首先会处理函数声明,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
进入代码执行阶段,先执行console.log(foo),此时foo是函数的应用,再执行var foo = 1;将foo赋值为1,而在我改写的例子里中,先执行var foo = 1;再执行console.log(foo),所以打印1。我觉得加上代码执行阶段会更清晰,哈哈哈
from blog.
一个执行上下文的生命周期可以分为两个阶段。
- 创建阶段
在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向。
- 代码执行阶段
创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。
都没有错,博主讲的主要是针对变量对象,而变量对象的创建是在EC(执行上下文)的创建阶段,所以侧重点主要是EC的生命周期的第一个阶段,我觉得再执行var foo = 1这句话有点不妥,应该是给foo赋值,应该是执行foo=1这个操作,因为在EC创建阶段var已经被扫描了一遍。
from blog.
var foo = 1;
console.log(foo);
function foo(){
console.log("foo");
};
这次打印结果就是“1”;
分解
var foo; // 如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
foo = 1;// 代码执行。PS: 如果没有这行,打印结果是 function foo(){console.log('foo')};
console.log(foo); // 1
function foo(){
console.log("foo");
};
执行上下文的时候:
VO = {
foo: reference to function foo(){}
}
然后再执行了 foo = 1 的操作,修改变量对象的 foo 属性值
AO = {
foo: 1
}
执行代码 console.log(foo) 的结果: 1
from blog.
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
console.log(foo);
var foo = 1;
function foo(){
console.log("foo");
}
另外,以上两处代码得出的结论一样,说明:
同一作用域下,函数提升比变量提升得更靠前.
大家知道的微微一笑就好了:)
from blog.
思考题第二题:
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
解:
JavaScript发现了一段可执行代码(executable code)
,准备创建对应的执行上下文(execution context)
:
在此之前
因为JavaScript的函数提升特性,将代码等量变换为:(1)
function foo(){// 函数提升
console.log("foo");
}
console.log(foo);
var foo = 1;
又因为JavaScript的变量提升特性,将代码等量变换为:(2)
function foo(){// 函数提升
console.log("foo");
}
var foo;// 变量提升
console.log(foo);
foo = 1;
开始创建对应的执行上下文(execution context)
:(3)
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
其中,此处探讨的VO只是被初始化(4)
当javaScript扫描到console.log(foo)
时,执行代码之前,先进入执行上下文(execution context)
,(5)
因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
VO = {
foo: reference to function foo(){},
~foo:undefined// 此处疑问: 此处变量声明的foo是否保存在VO中;以何种形式保存
}
执行代码console.log(foo)
,查找到了VO中的foo,输出结果.(6)
接着执行foo = 1
,执行之后,VO为:(7)
VO = {
foo: 1
}
解答完毕.
第4处跟第5处都不很确定,其他地方也可能有理解不到位.请大家指出.
from blog.
@jawil ,你说的有一点误差,AO 实际上是包含了 VO 的。因为除了 VO 之外,AO 还包含函数的 parameters,以及 arguments 这个特殊对象。也就是说 AO 的确是在进入到执行阶段的时候被激活,但是激活的除了 VO 之外,还包括函数执行时传入的参数和 arguments 这个特殊对象。
AO = VO + function parameters + arguments
@jDragonV
from blog.
引用《JavaScript权威指南》回答你哈:调用函数时,会为其创建一个Arguments对象,并自动初始化局部变量arguments,指代该Arguments对象。所有作为参数传入的值都会成为Arguments对象的数组元素。
from blog.
这段代码,我默认是全局环境下执行。
执行上下文的生命周期可以分为两个阶段(也就是这段代码从开始到结束经历的过程)。
-
创建阶段
在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向 -
代码执行阶段
创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。
这里我们先重点了解执行上下文中变量对象的创建。
变量对象的创建,依次经历了以下几个过程。
-
建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值(全局环境下没有这步)。
-
检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
-
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。(上面的例子就属于这种情况,foo函数名与变量foo同名)
在上面的规则中我们看出,function声明会比var声明优先级更高一点。
我们直接从全局执行上下文开始理解。全局作用域中运行时,全局执行上下文开始创建。为了便于理解,我们用如下的形式来表示。
创建过程
global:EC = {
// 变量对象
VO: {},
scopeChain: {},
this: {}
}
// 因为暂时不详细解释作用域链和this,所以把变量对象专门提出来说明
// VO 为 Variable Object的缩写,即变量对象
VO = {
foo: <foo reference> // 表示foo的地址引用
// foo: undefined
(如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。这个过程已被忽略)
}
未进入执行阶段之前,变量对象中的属性都不能访问!但是进入执行阶段之后,变量对象转变为了活动对象,里面的属性都能被访问了,然后开始进行执行阶段的操作。
// 执行阶段
VO -> AO // Active Object
AO = {
foo: 1(此时函数已被重新赋值)
}
因此,上面你提供的代码,执行顺序就变成了这样:
function foo(){
console.log("foo");
};
foo = 1;
console.log(foo);
所以最后打印的是1.
总结:你自己总结吧 @YanLIU0822
from blog.
Arguments对象是什么 - -。
from blog.
根据你们的讨论,关于这一段代码的实现,
console.log(foo);
var foo = 1;
console.log(foo);
function foo(){};
执行结果是函数和1,我可以这样理解么?
foo() //函数提升
var foo //和函数重名了,被忽略
console.log(foo); //打印函数
foo = 1; //全局变量foo
console.log(foo); //打印1,事实上函数foo已经不存在了,变成了1
望不吝赐教!
from blog.
@wedaren 进入执行上下文时,初始化的规则如下,从上到下就是一种顺序:
from blog.
楼主,关于词法解析阶段的初始化顺序,你的总结和我之前的了解有很大出入啊,第一阶段是分析形参没疑问,但是是第二阶段你总结的是分析函数声明,我的笔记第二阶段是分析var声明的变量,最后一个阶段才是函数声明。笔记如下:
词法分析:
- 分析形参:接收形参到挂载到AO,接收实参赋值,如果没有传实参,值为undefined
- 分析变量声明(var):如果AO上已存在,不做修改(var 声明的变量和参数本质上都是私有变量),如果AO上不存在,挂载到AO,值undefined。
- 分析函数声明:挂载函数名到AO,并赋值函数体, 如果AO上已存在,覆盖。
注:
函数声明,在词法分析阶段,函数名挂载到AO上时,就在这个阶段立马赋值。
var 声明的变量,词法解析阶段,只负责挂载该变量到AO上,值为undefined,赋值在执行阶段进行。
也就是说在执行阶段赋值的,只剩下var声明的变量。
即使是使用var声明的函数表达式这种情况,在AO上值也为undefined。
用这个顺序解释任何场景的题目感觉都挺合理的,特别是对于变量重名的时候,例1:
console.log(a);//function(){}
var a = 1;
function a() {};
console.log(a);//1
词法分析顺序:形参->var声明->函数声明(包括赋值)
词法分析阶段结束时(未进入执行阶段),因为函数声明是最后分析的,所以AO:{a:function(){}}。
然后进入执行阶段,遇到console.log(a),此时AO上a为function(){},所以打印函数,继续向下执行,遇到var a = 1; 因为是执行阶段,只执行 a = 1,所以AO{a:1}。继续向下执行,遇到console.log(a);,打印此时AO上a,也就是1。
例2(上面大家的例子):
console.log(foo);//function(){}
function foo(){
console.log("foo");//没执行
}
var foo = 1;
词法分析顺序:形参->var声明->函数声明(包括赋值)
词法阶段结束后(未进入执行阶段),AO{foo:function(){}}
然后是执行阶段,遇到console.log(foo),此时AO上的foo就是函数体,所以打印函数,然后继续执行,遇到var foo = 1;执行foo = 1;此时AO{foo:1},执行完毕,AO上最终的foo为1。但是你在代码最上面打印foo的时候,var foo = 1;这一句还没执行,AO上的foo还是函数,未被1覆盖。如果在代码的最后再打印一次console.log(foo),那就是1了。
至于你们上面总结的:
1,同一作用域下,函数提升比变量提升得更靠前。
2,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
【这些都是主观的结论,优先级之分只是表象,根本原因还是词法分析顺序和执行顺序一起决定了谁覆盖了谁】
from blog.
@zuoyi615 感谢写下自己的分析过程,如果这段代码是在全局环境下执行的,变量对象应该用 VO 表示,此时也没有 arguments 属性
from blog.
@DarkYeahs 感谢回答哈~ o( ̄▽ ̄)d @veedrin 我也来说一下,第一个例子是因为,只有通过 var 声明的变量才会有提升, a 没有提升,执行 console.log(a) 的时候也没有挂载到全局变量上,所以会报错。
第二个和第三个例子本质是一样,值得注意的是,词法作用域规定的是查找变量的区域有哪些,并不规定具体的内容有哪些,以第二个例子为例的话,词法作用域规定了 foo 函数查找变量的时候,先从 foo 函数中查找,查找不到,就去到全局变量中查找,但是全局变量中到底有哪些变量,这是要根据函数什么时候执行才能确定的
from blog.
@youzaiyouzai666 我觉得这个更像是函数声明的问题。当函数声明出现在代码块中,javascript 引擎不会将其作为函数声明处理,而是处理成函数表达式。
所以,这段代码相当于:
var foo;
foo();
if(true){
foo = function(){
console.log('1111');
}
}else{
foo = function(){
console.log('22222');
}
}
from blog.
@jawil 哈哈,十分感谢回答~~~ @alexzhao8326 这道题应该是因为没有分成两个阶段来讲,所以让你觉得分析得不是很完整吧。我在写的时候,觉得毕竟是思考题,讲清楚问题的关键点即可,所以也没有给出完整的分析。如果你看完前面的内容,相信你一定能明白结果为什么会是这样,对于你修改后的例子,相信你也能解释的了。当然了,学习时严谨的态度还是要有的,感谢指出,o( ̄▽ ̄)d
from blog.
@Kevin-Loeng 我们先整理下变量对象的分析顺序:
文章中的:
- 先分析形参
- 分析函数,如果存在,则替换
- 分析变量,如果存在,则不修改
@keyiran 的方式:
- 分析形参
- 分析变量,如果存在,不做修改
- 分析函数,如果存在,则替换
其实想一想,两者实现的最终结果是一样的……
以你举得这个例子为例:
function fun(a){
var a;
console.log(a);
a = 2;
}
fun(1); // 打印1,而不是undefined
按照我的方式:
- 分析形参,值为1
- 分析函数,不存在,则不替换
- 分析变量,已经存在,不修改
所以最终打印的值为 1。
按照@keyiran 的方式:
- 分析形参,值为 1
- 分析变量,存在,不做修改
- 分析函数,不存在
最终分析的值也是 1。
如果真要论个优先级的话,函数声明最高,因为它能覆盖变量声明和形参,其次是形参,因为变量声明不能覆盖形参,最低的就是变量声明了。
我个人觉得无论是文章中的还是 @keyiran 的,都是一种对规律的总结,真正的实现方式到底是怎样的,估计只有实现语言的人知道了。
而且你想想,有没有可能在底层实现的时候,是顺序扫描代码的,如果先有变量,后遇到同名的函数,就覆盖,如果现有函数,后遇到同名的变量,就不覆盖呢?
from blog.
VO 和 AO 到底是什么关系。
from blog.
@LuDongWang 哈哈,你这是为我知错能改的精神点赞吗?
from blog.
个人觉得变量提升就是个伪概念,完全可以摒弃这一说法。用执行上下文的变量对象来理解这些要容易的多。
from blog.
确实,一边看楼上各位的精彩讨论,一边再看分析,在没看评论之前,我也就立马想到了,比如调整变量a和函数a的顺序
console.log(a);//function(){}
function a() {};
console.log(a);//function(){}
var a = 1;
console.log(a);//1
到底会产生什么样的结果?
实际上,@keyiran ,这位大大,我觉得给出了最好的解释,他反驳了一个问题就是,前面很多人强调的,函数声明会是最高的优先级,同名变量不会覆盖?我也觉得这个地方,确实有点说不通,
答案,就是,他说的,执行顺序和词法分析决定了这个谁覆盖谁的问题。
当然,很多看不懂的人还继续问为啥的话,我觉得,应该是没看懂,楼主写的很关键的地方就是
执行环境准备+执行阶段,这2个概念,
其实,我之前看过很多,有这个概念,一直没理解,今天看了这么多同学的讨论,对这个地方,确实有点理解啦
from blog.
博主请教一下:变量对象和作用域有什么区别。
我看你不知道的JS中对作用域的解释为:定义如何在某些位置存储变量,以及如何在稍后找到这些变量。我们称这组规则为作用域。
我看汤姆大叔博客对变量对象的解释:如果变量与执行上下文相关,那变量自己应该知道它的数据存储在哪里,并且知道如何访问。这种机制称为变量对象。
感觉都是规定数据存储和访问的机制,不同点是什么。我的理解二作用域是一个抽象的概念,变量对象是对抽象概念实现的一个实实在在的对象?不知道理解是否正确。
from blog.
你好博主,关于第一题昨天想了许久没有想通。foo,bar方法中,变量a没有用 var 进行修饰,会被解析为全局变量。在执行阶段,在foo,bar的AO中查找a,没有找到,然后到全局中查找,同是在全局中,为什么执行foo会报错。
今早又看了一遍,有了新的理解:在进入执行上下文阶段,只会将有 `var,function修饰的变量或方法添加到变量对象中。在进行执行阶段前一刻,foo和bar方法的它们的VO中均没有a属性。在执行阶段,执行到 a= 1时,才将a变量添加到全局的变量对象中而不是在进入执行上下文阶段。所以foo方法中会报错,bar方法会打印 1。
请问博主可以这么理解吗?
function foo() {
console.log(a);
a = 1;
}
foo(); // Uncaught ReferenceError: a is not defined
function bar() {
a = 1;
console.log(a);
}
bar(); // 1
from blog.
是w3school 不是W3C school
from blog.
function foo(a) { var a; return a; } function bar(a) { var a = 'bye'; return a; } [foo('hello'), bar('hello')] // ['hello', 'bye']
那这块代码是不是可以用下面这句话解释了:
因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
AO = { arguments: { 0: 'hello', length: 1 }, a: 'hello', // a: undefined }
AO = { arguments: { 0: 'hello', length: 1 }, //a: 'hello', a: 'bye' }
不知道可不可以这样理解@mqyqingfeng
from blog.
针对第二个问题,我有个不同的见解。
console.log(foo);
function foo(){
console.log("foo");
}
var foo = 1;
这里是因为变量提升(hoisting)
进入执行上下文时,伪代码如下:
var foo;// 把所有变量提升
foo = function() {console.log('foo')} ;// 函数声明会被提升(函数表达式并不能被提升)
console.log(foo);
foo = 1;
所以执行代码的时候,会打印出函数
from blog.
@jawil 非常感谢回答,一语中的。
from blog.
是的,显然你的说法更严谨,也符合分析的过程! 学习了@jawil
from blog.
@zuoyi615 o( ̄▽ ̄)d
from blog.
@oakland 非常感谢补充~~~ 这一点我也没有注意到~ o( ̄▽ ̄)d
from blog.
@ckclark 哎呀呀,我犯了一个严重的错误,非常感谢指出~o( ̄▽ ̄)d
from blog.
@JarvenIV 是的,o( ̄▽ ̄)d
from blog.
@MrGoodBye 在《JavaScript深入之执行上下文栈》中,我以前写错了一点,现在已经修正了,其实是在函数执行的时候,才创建执行上下文,这个可能将你误导了,我很抱歉。
因为第二个例子的代码写在了全局中,所以函数声明和变量声明都是在全局对象中,在代码执行阶段,执行 console.log 时,会创建 console.log 函数的执行上下文,然后读取全局变量中的 foo ,然后因为覆盖规则的原因,打印函数
from blog.
给楼主点赞
from blog.
非常感谢,感觉学会了很多
from blog.
@huanqundong 看完这篇,还有十一篇,未来还有更多篇,希望你能保持学习的热情,不断成长~
from blog.
foo();
if(true){
function foo(){
console.log('1111');
}
}else{
function foo(){
console.log('22222');
}
}
chrome59 和ie11都报“VM86:1 Uncaught TypeError: foo is not a function”;
如果用本文中的理论,有点问题,
代码在执行中分为,1进入执行上下文,2执行阶段
具体执行(按照本文的结论推理):
1进入执行上下文------AO={foo:reference to functionfoo(){console.log('22222')},}
2.那么执行阶段会 打印 “22222”;(但实际抛异常)
查阅了一下资料:说不同版本中,结果会不同
from blog.
受益;
思考题中理解变量提升有帮助:
function foo() {
console.log(a);
var a = 1;
}
foo(); // undefined
function foo() {
console.log(a);
a = 1;
}
foo(); // error
这是因为函数中的 "a" 并没有通过 var 关键字声明,所有不会被存放在 AO 中。
代码执行两个阶段:1.进入执行上下文,2. 执行阶段
from blog.
在思考题1的第一段中,我在金丝雀中执行的结果与分析不同。
返回结果是:1,而非报错
function foo() {
console.log(a);
a
= 1;
}
foo(); // ???
1
undefined
from blog.
@alvis888 没有在金丝雀测验过哈~ 应该是解析不同吧
from blog.
var foo = 1;
console.log(foo);
function foo(){
console.log("foo");
};
这次打印结果就是“1”;
这个打印结果为什么为1?
函数不是在初始阶段会提升到作用域顶部吗? @jawil
from blog.
@jawil 谢谢,讲的听清楚的,但是我还有个问题,想问问,关于变量提升,是不是可以把变量函数提升也放在这里解释呀?
在进入执行上下文的时候,会有两个阶段。
第一阶段就是创建阶段,创建变量对象(暂时只考虑变量对象,除此之外还有作用域链,this):
变量对象包括三部分:
arguments(如果是函数的话);
函数声明,key : value,key为有function声明的函数名(所以对于函数表达式并不是这个范围,而是作为变量处理),value为指向函数的引用;( 这是不是跟函数提升有关,这里会提升整个函数体? ),如果已存在变量名,也会覆盖,(这就是对应着同一个上下文中,同名函数会被后者覆盖?);
变量声明,key: value, key 为有var声明的变量名,value也undefined,如果变量名存在的话,比如说跟上一步的函数名同名,则会跳过,保持key: value不变;(这个是不是跟变量提升只是提升变量声明,而不提升初始化值有关呀?)
在创建阶段已经完成了变量和函数提升,变量只会提升声明不包括初始化值,函数会提升整个函数体,而且如果出现同名,会被最近的函数体覆盖。除此以外,其他按照原始顺序不变?
然后到执行阶段,就是按照创建阶段的顺序执行吧。
是这样理解的么😄?
from blog.
你所说的变量函数应该是函数表达式,就是类似var a=function(){},这种你就看做一个跟var a=1来看待。这当然也存在变量提升。
同名函数会被后者覆盖,如果是函数跟变量同名则会跳过,等到变量赋值时候,变量也会覆盖函数原来的值。其他粗略看了一眼,好像没什么毛病,这种概念性的东西多结合实践体会一下,博主已经写得很清楚了。@YanLIU0822
from blog.
@freeser 嗯嗯,正是如此~
from blog.
@alexzhao8326 我觉得你举的例子也是符合作者所说的情况的。
即:var a =1; console.log(a);function foo(){console.log("foo");}; 在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
然而此时,只是将一个function类型的a,赋值为了1,这js这种也不是什么不允许的。就像你声明了一个对象,你把它赋值为了一个数组一样。所以打印出来,a的值还是1.
from blog.
@alvis888 我的canary 是报错,没有输出1
from blog.
@oakland 我在其他的文章里有看到一种说法,认为在函数作用域内,AO===VO的,认为函数初始化之后,将arguments放置在AO中即是VO https://stackoverflow.com/questions/6337344/activation-and-variable-object-in-javascript
from blog.
我突然发现看完正文,再把评论看一遍。再回头看一遍正文啥都明白了
from blog.
@mqyqingfeng 你好,有幸拜读了你的文章,很是受益。
文中有个地方想请教一下
1. 函数的所有形参 (如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为 undefined
- 这个“所有形参”是不是就是你所说的arguments对象?我看到你后面的例子中调用foo(1)的时候,在进入执行上下文后,AO的a属性是1,这个a是形参吧,它在函数执行的第一阶段(就是所谓的进入执行上下文阶段)的时候就已经赋值成1了么(我原来理解的是第一阶段是赋值成undefined的)?还是说在代码执行阶段再赋值成1的?
- 后面那句“没有实参,属性值为undefined”,没看明白什么意思,你是说,如果没有实参,arguments属性就赋值为undefined么?
from blog.
我又想到一个问题,既然评论里有提到VO和AO是一个东西,只是在执行上下文的生命周期中会转换角色而已,那就是说在第一步进入执行上下文的时候那时还不是AO,而是VO咯?这算挑刺吗?哈哈
from blog.
@bluefantasy728
我理解也是的,VO和AO是同一个,未进入执行阶段VO的属性不能访问,进入执行之后,VO变成AO,就能访问了。
from blog.
@keyiran 这两种分析规则都能解释通这两个例子, 我也觉得这些都是主观的结论。这些结论可以帮助大家分析具体的例子,采用哪一种都可以,毕竟 Hoisting 只是一种思考执行上下文(特别是创建和执行阶段)在JavaScript中如何工作的一种方式,真到具体执行的时候,就涉及到 JavaScript 引擎的扫描规则,而这个规则究竟是什么样的呢?我并没有查过具体的资料,不过我的猜想是:顺序扫描,如果先有变量,后遇到同名的函数,就覆盖,如果现有函数,后遇到同名的变量,就不覆盖,接着执行~
from blog.
看了通篇 反复看了评论看懂了 谢谢楼上各位大大
我的理解是 楼主的思考题和评论里的就是foo = 1 在console.log(foo);之前还是之后
楼主的思考题是之后的 那么 foo在ec的时候是function foo(){};和var foo;那么由于函数声明高于变量声明,AO里的foo不是undefined 而是 ,所以打印本身函数。
评论里的是在这个之前,先执行foo=1,AO里的foo已经是1了 ,故执行到console.log(foo);打印1;
请不吝赐教
from blog.
在函数上下文中,arguments与函数形参是什么关系?ES6与之前的实现似乎是不同的
from blog.
@liruilong119 在 ES5 的非严格模式下,arguments 与形参有绑定关系,具体可以参考 #14,ES6 下一些情况比如有默认值的时候,会默认采用严格模式,取消这种绑定关系
from blog.
为什么实参的值可以取到,而b就是undefinde?
AO = {
arguments: {
0: 1,
length: 1
},
a: 1,
b: undefined,
c: reference to function c(){},
d: undefined
}
from blog.
@mengLLLL应该是由于活动对象AO在进入上下文时由Arguments对象初始化,能够获得函数的实参了,所以此时有值,而b是函数内部声明的参数,由于变量声明提升,被AO获得,但此时上下文还没执行,没有完成赋值操作所以b的值为undefined
from blog.
@liruilong119 感谢回答,正是如此,@mengLLLL 这个时候的 AO 指的是进入执行上下文时的 AO ,只是完成了变量提升和函数提升,但是并没有进入执行阶段~
from blog.
看来我好好看看博主的 git了 基础太差了我
from blog.
function foo() {
console.log(a);
a = 1;
}
foo(); // a is not defined
// 这里的a不是全局变量吗?为什么在全局找不到?
function foo() {
console.log(a);
}
a = 1;
foo(); // 1
// js是词法作用域,为什么打印出来是1?
function foo() {
console.log(a);
}
foo(); // a is not defined
a = 1;
// 还是一样,我觉得应该是undefined
求解答,谢谢。
from blog.
@veedrin
- 第一个
a = 1
是一个未声明的变量进行赋值的操作,它不会被提升,所以在执行console.log(a)
的时候在搜索的时候搜索不到的,然后在执行a = 1
的时候才会在全局的VO
中进行注册,这个时候a
才能被搜索到 - 第二个首先在全局的
VO
中注册了foo
函数,然后执行a = 1
的时候再在全局中注册了a
这个变量,接着再执行foo()
,所以打印出来的就是1 - 第三个跟第一个是同样的解释
from blog.
let a = 1;
console.log(window.a);//undefined
博主能解释一下let和var底层的区别吗?不胜感激~
from blog.
原来未声明的变量不会提升,多谢!
issues留言不会提醒么?说好的同性交友社区呢!
from blog.
@Zefeng666 let 的这种特性,即 let 会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性,应该是规范中就规定成这样的,跟它的不提升特性并没有什么关系。
至于 let 和 var 底层的区别,我能想到的内容在 ECMAScript 6 入门 都有……
from blog.
想请教一下博主关于变量提升的问题:
console.log(a); // undefined
var a = 1;
因为变量提升,打印 a 不会报错,但是只会打印出 undefined,只声明没有赋值。
按照执行上下文的理解,代码会分成两个阶段进行处理:分析和执行,关于分析中变量声明的部分,
由名称和对应值(undefined)组成一个变量对象的属性被创建
这里是不是改为 只创建名称,不赋值 更合适呢?
from blog.
@RayJune 这就要看怎么理解 undefined 这个值了,我也不能保证我说的是正确的,在我看来,undefined 是一个原始值,是一个占用着固定内存大小的内容,当只声明变量的时候,其实相当于将这个原始值赋值给变量,所以才会表述为 由名称和对应值(undefined)组成一个变量对象的属性被创建
from blog.
@joker09 确实,变量提升是一个在规范中找不到的术语。变量提升被认为是思考执行上下文(特别是创建和执行阶段)在 JavaScript 中如何工作的一种方式。不过如果直接用变量对象去解释,估计给新手会增加不少的理解成本~
from blog.
@Ghohankawk 哈哈,感谢分享自己的想法,正是大家的交流才让一个知识点越聊越明了~
from blog.
function bar(a){
console.log(a);
function a() {};
}
执行 bar(2)
结果是 function a() {}
是不是说明 如果形参名称跟已经声明函数相同,则形参也不会干扰声明函数这类属性
from blog.
@houzhiying977 可以呀,其实在规则里也有讲到:
如果变量对象已经存在相同名称的属性,则完全替换这个属性,这个所谓的“相同名称的属性”就包括形参和变量声明~
from blog.
所以所有的变量都是存在变量对象上的对吗?而原始类型存在栈上的说法是错误的?变量对象又是存在哪的?- -
from blog.
需要注意的是,在严格模式下,this默认是指向undefined,并非window对象。
from blog.
@ShumRain 这个不冲突吧……变量对象你可以理解为一个对象,对象以及对象中的值存在哪里就还是存在哪里呀~
from blog.
@jiangjiu 确实如此,感谢补充哈~
from blog.
function foo() {
console.log(a);
a = 1;
}
foo(); // Uncaught ReferenceError: a is not defined
在进入执行上下文和执行到console语句时,AO都是:
AO = {
arguments: {
length: 0
}
}
因此会报错,因为AO中不包含a的定义消息。
如果把代码改一改,结果就会发生变化
function foo() {
console.log(a);
var a = 1;
}
foo(); // undefined
原因在于在编译阶段,变量声明会被提升,代码其实会被理解成以下的样子:
function foo() {
var a;
console.log(a);
a = 1;
}
foo(); // undefined
因此,在进入执行上下文时和执行到console语句时:
AO = {
arguments: {
length: 0
},
a: undefined
}
from blog.
@wnbupt 感谢补充哈~ 确实是这样的~
from blog.
@mqyqingfeng , @Ghohankawk ,两位大大你们好。如果真如@keyiran 所说,词法分析的顺序是 1形参、2变量声明、3函数声明,且”如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性“这句话只是主观的论断。那下面的代码怎么解释呢?
function fun(a){
var a;
console.log(a);
a = 2;
}
fun(1); //打印1,而不是undefined
按照@keyiran的说法,第一步分析形参后VO的属性a为1,接下来第二步是分析变量声明,a作为声明的变量,值为undefined,应该会覆盖掉VO已存在的a。但是结果却为1(Chrome浏览器下)。为什么呢?
难道正确的词法分析顺序应该是 1变量声明、2形参、3函数声明?
from blog.
@lawpachi 嗯嗯,我的想法跟你一致呀~
from blog.
还有一个问题,执行代码会产生执行上下文,执行上下文分为1.进入执行上下文2. 代码执行。按照分析博主的分析,变量提升处于1.进入执行上下文阶段。
但是JS代码执行会先进行编译(词法分析,生成AST,生成计算机识别代码),然后进行代码执行。编译过程的词法分析就会变量提升。所以又怎么会在执行代码时候才产生的变量提升呢?
from blog.
@lawpachi 没有说到执行代码的时候产生变量提升呀,不过执行代码的时候,会根据代码修改变量对象~
from blog.
麻烦博主指教:进入执行上下文会产生AO变量对象,按照@jawil 变量对象创建的几个过程的说法 ,
变量对象的创建,依次经历了以下几个过程。
建立arguments对象。
检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。
检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。(上面的例子就属于这种情况,foo函数名与变量foo同名)
所以按照如上所述变量对象的创建会产生变量提升。
但是根据你不知道的JS中写到,变量提升存在代码的编译过程。还没到产生执行上下文阶段。
from blog.
@lawpachi 文章中其实没有区分编译阶段与进入执行上下文阶段的,文章中认为两者是一样的:
分析其实就是指编译的过程,也是产生执行执行上下文的过程
from blog.
箭头函数中已经没有了arguments属性... 博主能分析下为什么把这个干掉了呢?
from blog.
@xxyj 可能是因为 arguments 设计的不够好吧……正常情况下,我们使用 arguments 还需要转成数组形式,ES6 可能认为这是个没有必要的操作,所以就干掉了 arguments, 然后提供了一个更好用的方案,就是利用拓展运算符:
(...arguments) => console.log(arguments);
你还可以写成这样:
(first, ...arguments) => console.log(first, arguments);
使用这种方式,完全可以替代掉 arguments,所以就去掉了吧……
不过这是我猜的啦,在规范中我并没有找到去掉的原因~
from blog.
@mqyqingfeng 你说全局对象是由Object构造函数实例化的一个对象,但是window.proto === Object.prototype为false啊
from blog.
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
}
foo(1);
这里我有一点不懂,大佬。 就是b=3这里 b不是全局变量吗? 为什么执行之后,b还是会覆盖var b = 2 ? 它这个b = 3 不是应该放到全局VO里面去吗?
from blog.
function a(b){
console.log(b); // ƒ b(){}
function b(){}
console.log(b); // ƒ b(){}
}
a(function c(){})
对于这个现象,我更倾向于js对于函数的执行采取的流程为进入函数的执行上下文,创建函数的AO -> 形参/变量的声明 -> 实参赋值给AO中对应的属性 -> 函数的声明 -> 执行函数体内其余
,而且我有个看法,是否是这样的:js的编译阶段,只会对全局环境下的变量和函数进行声明及提前,而不会去访问被声明的函数体内部
?否则似乎无法解释函数体内部的函数声明会覆盖实参传给形参的值
这一现象。
from blog.
function foo3(a) {
var a = 10
function a() {}
console.log(a)
}
foo3(20) // 10
请问博主为什么会输出10呢?不是function声明会被提升,覆盖arguments中的a和变量声明,应该打印出[Function: a]
吧?多谢解答~~
@mqyqingfeng
from blog.
@wnbupt 要分清楚函数的分析过程和执行过程。
分析阶段:
1、形参并赋值: a=20;(这里不确定形参是不是在分析阶段就赋值)
2、函数声明并赋值: a=function a(){};(函数声明的同时会进行赋值操作)
3、变量声明: var a(已经存在变量a,所以不会对AO有所影响)
执行阶段:
1、a=10;(将a的值修改为10,不管你以前是函数还是其他什么值,现在就是10)
2、console.log(a); // 10
from blog.
@MagicHacker 嗯,这样说确实不严谨,准确的说是继承了 Object
from blog.
@ciomedyu 这个……在函数内部声明了 var b = 2; 然后修改 b = 3,打印的结果肯定是 3 呀……
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
console.log(b) // 3
}
foo(1);
这里的问题应该是外层的 b 并没有被修改:
var b = 1;
function foo(a) {
var b = 2;
function c() {}
var d = function() {};
b = 3;
console.log(b) // 3
}
foo(1);
console.log(b) // 1
from blog.
@mygaochunming 感谢回答哈~ 确实如此 ヾ(^▽^ヾ)
from blog.
@mygaochunming 多谢多谢~我明白了,其实就是因为函数声明会优先变量声明,已经被提前了,之后的a = 10
就会覆盖函数,所以打印出来就是10。
from blog.
function foo(b){
console.log('b', b);
/*
b ƒ b(d){
console.log('d1:', d);
function d(){};
console.log('d2', d);
}
*/
function b(d){
console.log('d1:', d); // d1: ƒ d(){}
function d(){};
console.log('d2', d); // d2 ƒ d(){}
}
console.log('b():', b(function c(){})); // b(): undefined
}
foo(function a(){})
上述例子的补充,发现函数体内声明的函数依然如此。JS引擎在预编译阶段究竟做了什么?为什么“函数体内部的函数声明提前
”能够将尚未执行的函数的实参值
给顶替掉?大佬是否对此有所了解
from blog.
谢谢博主,看了之后收益颇多。
想请教一个问题,关于es6 let const没有变量提升,是不是就不能按照“进入执行环境”那三步来分析VO了?再结合此前讲的静态作用域,怎么感觉像是变成了动态作用域了?关于let应该怎么理解呢?
from blog.
@lucky3mvp 是的哈~ let 和 const 没有变量提升,所以用的是其他的规则,比如说暂时性死区,要说怎么解释 let 的话,嗯……我个人认为是:过程依然分为进入执行上下文和代码执行两个阶段,在进入执行上下文阶段,对于 let 声明的变量进行"检查",比如是否重复声明,暂时性死区等等,然后代码执行阶段赋值,那么问题来了,是在进入上下文阶段创建的呢还是代码执行阶段创建的呢?一时间我也有些疑惑啦……
至于变成了动态作用域,嗯……这个意思不是很理解哈~
from blog.
为什么“函数体内部的函数声明提前”能够将尚未执行的函数的实参值给顶替掉?
嗯……因为规则就是这么设计的……那为什么这么设计呢?我就不清楚了……
from blog.
@coconilu hoisting 毕竟只是一个帮助大家理解的概念,我也不能百分百确定就是这样,只要能用它解释更多的场景就没有什么问题~
from blog.
@LbHongYu 是的~
from blog.
Related Issues (20)
- 冴羽答读者问:如何在工作中打造影响力,带动同事?
- 无
- 冴羽答读者问:如何学习更有计划性、提升更稳更快? HOT 4
- 冴羽答读者问:过程比结果重要吗? HOT 1
- 冴羽答读者问:冴羽,你为什么写起了鸡汤? HOT 3
- 聊聊 npm 的语义化版本(Semver) HOT 4
- How to create Backlinks 😤
- 思考题第2题
- 可以理解为原型是prototype,原型链是通过__proto__ 链接起来的吗
- React 之 createElement 源码解读 HOT 8
- React 之元素与组件的区别 HOT 1
- React 之 Refs 的使用和 forwardRef 的源码解读
- React 之 Context 的变迁与背后实现 HOT 1
- 第一段不报错啊,刚试过了,会打印1
- how can i HOT 1
- Hosting of Blog Issue
- 全局对象
- 为啥每次都要创建一个 Child函数来new 子类?现在不都是 const p1 = new Person(); const p2 = new Person()吗
- 文档内容中文件结构的错位 HOT 1
- 原型链继承
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from blog.