Giter VIP home page Giter VIP logo

Comments (256)

jawil avatar jawil commented on May 1, 2024 527

未进入执行阶段之前,变量对象(VO)中的属性都不能访问!但是进入执行阶段之后,变量对象(VO)转变为了活动对象(AO),里面的属性都能被访问了,然后开始进行执行阶段的操作。

它们其实都是同一个对象,只是处于执行上下文的不同生命周期。@jDragonV

from blog.

alexzhao8326 avatar alexzhao8326 commented on May 1, 2024 197

@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.

jawil avatar jawil commented on May 1, 2024 166

一个执行上下文的生命周期可以分为两个阶段。

  1. 创建阶段

在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向。

  1. 代码执行阶段

创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

都没有错,博主讲的主要是针对变量对象,而变量对象的创建是在EC(执行上下文)的创建阶段,所以侧重点主要是EC的生命周期的第一个阶段,我觉得再执行var foo = 1这句话有点不妥,应该是给foo赋值,应该是执行foo=1这个操作,因为在EC创建阶段var已经被扫描了一遍。

@alexzhao8326

from blog.

zuoyi615 avatar zuoyi615 commented on May 1, 2024 87
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.

MrGoodBye avatar MrGoodBye commented on May 1, 2024 74
console.log(foo);

function foo(){
    console.log("foo");
}

var foo = 1;
console.log(foo);

var foo = 1;

function foo(){
    console.log("foo");
}

另外,以上两处代码得出的结论一样,说明:

同一作用域下,函数提升比变量提升得更靠前.

大家知道的微微一笑就好了:)

from blog.

MrGoodBye avatar MrGoodBye commented on May 1, 2024 58

思考题第二题:

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.

oakland avatar oakland commented on May 1, 2024 53

@jawil ,你说的有一点误差,AO 实际上是包含了 VO 的。因为除了 VO 之外,AO 还包含函数的 parameters,以及 arguments 这个特殊对象。也就是说 AO 的确是在进入到执行阶段的时候被激活,但是激活的除了 VO 之外,还包括函数执行时传入的参数和 arguments 这个特殊对象。
AO = VO + function parameters + arguments
@jDragonV

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 42

引用《JavaScript权威指南》回答你哈:调用函数时,会为其创建一个Arguments对象,并自动初始化局部变量arguments,指代该Arguments对象。所有作为参数传入的值都会成为Arguments对象的数组元素。

from blog.

jawil avatar jawil commented on May 1, 2024 27

这段代码,我默认是全局环境下执行。
执行上下文的生命周期可以分为两个阶段(也就是这段代码从开始到结束经历的过程)。

  • 创建阶段
    在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向

  • 代码执行阶段
    创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

这里我们先重点了解执行上下文中变量对象的创建。

变量对象的创建,依次经历了以下几个过程。

  1. 建立arguments对象。检查当前上下文中的参数,建立该对象下的属性与属性值(全局环境下没有这步)。

  2. 检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

  3. 检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为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.

izhangzw avatar izhangzw commented on May 1, 2024 25

Arguments对象是什么 - -。

from blog.

xumengzi avatar xumengzi commented on May 1, 2024 24

根据你们的讨论,关于这一段代码的实现,

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.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 13

@wedaren 进入执行上下文时,初始化的规则如下,从上到下就是一种顺序:

default

from blog.

keyiran avatar keyiran commented on May 1, 2024 13

楼主,关于词法解析阶段的初始化顺序,你的总结和我之前的了解有很大出入啊,第一阶段是分析形参没疑问,但是是第二阶段你总结的是分析函数声明,我的笔记第二阶段是分析var声明的变量,最后一个阶段才是函数声明。笔记如下:

词法分析:

  1. 分析形参:接收形参到挂载到AO,接收实参赋值,如果没有传实参,值为undefined
  2. 分析变量声明(var):如果AO上已存在,不做修改(var 声明的变量和参数本质上都是私有变量),如果AO上不存在,挂载到AO,值undefined。
  3. 分析函数声明:挂载函数名到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.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 12

@zuoyi615 感谢写下自己的分析过程,如果这段代码是在全局环境下执行的,变量对象应该用 VO 表示,此时也没有 arguments 属性

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 8

@DarkYeahs 感谢回答哈~ o( ̄▽ ̄)d @veedrin 我也来说一下,第一个例子是因为,只有通过 var 声明的变量才会有提升, a 没有提升,执行 console.log(a) 的时候也没有挂载到全局变量上,所以会报错。

第二个和第三个例子本质是一样,值得注意的是,词法作用域规定的是查找变量的区域有哪些,并不规定具体的内容有哪些,以第二个例子为例的话,词法作用域规定了 foo 函数查找变量的时候,先从 foo 函数中查找,查找不到,就去到全局变量中查找,但是全局变量中到底有哪些变量,这是要根据函数什么时候执行才能确定的

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 7

@youzaiyouzai666 我觉得这个更像是函数声明的问题。当函数声明出现在代码块中,javascript 引擎不会将其作为函数声明处理,而是处理成函数表达式。

所以,这段代码相当于:

var foo;
foo();
if(true){
  foo = function(){
    console.log('1111');
  }
}else{
    foo = function(){
      console.log('22222');
  }
}

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 6

@jawil 哈哈,十分感谢回答~~~ @alexzhao8326 这道题应该是因为没有分成两个阶段来讲,所以让你觉得分析得不是很完整吧。我在写的时候,觉得毕竟是思考题,讲清楚问题的关键点即可,所以也没有给出完整的分析。如果你看完前面的内容,相信你一定能明白结果为什么会是这样,对于你修改后的例子,相信你也能解释的了。当然了,学习时严谨的态度还是要有的,感谢指出,o( ̄▽ ̄)d

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 6

@Kevin-Loeng 我们先整理下变量对象的分析顺序:

文章中的:

  1. 先分析形参
  2. 分析函数,如果存在,则替换
  3. 分析变量,如果存在,则不修改

@keyiran 的方式:

  1. 分析形参
  2. 分析变量,如果存在,不做修改
  3. 分析函数,如果存在,则替换

其实想一想,两者实现的最终结果是一样的……

以你举得这个例子为例:

function fun(a){
   var a;
   console.log(a);
   a = 2;
}

fun(1);  // 打印1,而不是undefined

按照我的方式:

  1. 分析形参,值为1
  2. 分析函数,不存在,则不替换
  3. 分析变量,已经存在,不修改

所以最终打印的值为 1。

按照@keyiran 的方式:

  1. 分析形参,值为 1
  2. 分析变量,存在,不做修改
  3. 分析函数,不存在

最终分析的值也是 1。

如果真要论个优先级的话,函数声明最高,因为它能覆盖变量声明和形参,其次是形参,因为变量声明不能覆盖形参,最低的就是变量声明了。

我个人觉得无论是文章中的还是 @keyiran 的,都是一种对规律的总结,真正的实现方式到底是怎样的,估计只有实现语言的人知道了。

而且你想想,有没有可能在底层实现的时候,是顺序扫描代码的,如果先有变量,后遇到同名的函数,就覆盖,如果现有函数,后遇到同名的变量,就不覆盖呢?

from blog.

izhangzw avatar izhangzw commented on May 1, 2024 4

VO 和 AO 到底是什么关系。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024 2

@LuDongWang 哈哈,你这是为我知错能改的精神点赞吗?

from blog.

kkkisme avatar kkkisme commented on May 1, 2024 2

个人觉得变量提升就是个伪概念,完全可以摒弃这一说法。用执行上下文的变量对象来理解这些要容易的多。

from blog.

Ghohankawk avatar Ghohankawk commented on May 1, 2024 2

确实,一边看楼上各位的精彩讨论,一边再看分析,在没看评论之前,我也就立马想到了,比如调整变量a和函数a的顺序

console.log(a);//function(){}
function a() {};
console.log(a);//function(){}
var a = 1;
console.log(a);//1

到底会产生什么样的结果?
实际上,@keyiran ,这位大大,我觉得给出了最好的解释,他反驳了一个问题就是,前面很多人强调的,函数声明会是最高的优先级,同名变量不会覆盖?我也觉得这个地方,确实有点说不通,
答案,就是,他说的,执行顺序和词法分析决定了这个谁覆盖谁的问题。
当然,很多看不懂的人还继续问为啥的话,我觉得,应该是没看懂,楼主写的很关键的地方就是

执行环境准备+执行阶段,这2个概念,

其实,我之前看过很多,有这个概念,一直没理解,今天看了这么多同学的讨论,对这个地方,确实有点理解啦

from blog.

lawpachi avatar lawpachi commented on May 1, 2024 2

博主请教一下:变量对象和作用域有什么区别。
我看你不知道的JS中对作用域的解释为:定义如何在某些位置存储变量,以及如何在稍后找到这些变量。我们称这组规则为作用域。
我看汤姆大叔博客对变量对象的解释:如果变量与执行上下文相关,那变量自己应该知道它的数据存储在哪里,并且知道如何访问。这种机制称为变量对象。
感觉都是规定数据存储和访问的机制,不同点是什么。我的理解二作用域是一个抽象的概念,变量对象是对抽象概念实现的一个实实在在的对象?不知道理解是否正确。

from blog.

LbHongYu avatar LbHongYu commented on May 1, 2024 2

你好博主,关于第一题昨天想了许久没有想通。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.

ckclark avatar ckclark commented on May 1, 2024 1

是w3school 不是W3C school

from blog.

freeser avatar freeser commented on May 1, 2024 1

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.

coconilu avatar coconilu commented on May 1, 2024 1

针对第二个问题,我有个不同的见解。

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.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@jawil 非常感谢回答,一语中的。

from blog.

alexzhao8326 avatar alexzhao8326 commented on May 1, 2024

是的,显然你的说法更严谨,也符合分析的过程! 学习了@jawil

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@zuoyi615 o( ̄▽ ̄)d

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@oakland 非常感谢补充~~~ 这一点我也没有注意到~ o( ̄▽ ̄)d

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@ckclark 哎呀呀,我犯了一个严重的错误,非常感谢指出~o( ̄▽ ̄)d

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@JarvenIV 是的,o( ̄▽ ̄)d

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@MrGoodBye 在《JavaScript深入之执行上下文栈》中,我以前写错了一点,现在已经修正了,其实是在函数执行的时候,才创建执行上下文,这个可能将你误导了,我很抱歉。

因为第二个例子的代码写在了全局中,所以函数声明和变量声明都是在全局对象中,在代码执行阶段,执行 console.log 时,会创建 console.log 函数的执行上下文,然后读取全局变量中的 foo ,然后因为覆盖规则的原因,打印函数

from blog.

NikolasWang avatar NikolasWang commented on May 1, 2024

给楼主点赞

from blog.

huanqundong avatar huanqundong commented on May 1, 2024

非常感谢,感觉学会了很多

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@huanqundong 看完这篇,还有十一篇,未来还有更多篇,希望你能保持学习的热情,不断成长~

from blog.

youzaiyouzai666 avatar youzaiyouzai666 commented on May 1, 2024
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.

deot avatar deot commented on May 1, 2024

受益;
思考题中理解变量提升有帮助:

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.

xiehongguang avatar xiehongguang commented on May 1, 2024

在思考题1的第一段中,我在金丝雀中执行的结果与分析不同。
返回结果是:1,而非报错
function foo() {
console.log(a);
a = 1;
}

foo(); // ???
1
undefined

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@alvis888 没有在金丝雀测验过哈~ 应该是解析不同吧

from blog.

YanLIU0822 avatar YanLIU0822 commented on May 1, 2024

var foo = 1;
console.log(foo);
function foo(){
console.log("foo");
};
这次打印结果就是“1”;
这个打印结果为什么为1?
函数不是在初始阶段会提升到作用域顶部吗? @jawil

from blog.

YanLIU0822 avatar YanLIU0822 commented on May 1, 2024

@jawil 谢谢,讲的听清楚的,但是我还有个问题,想问问,关于变量提升,是不是可以把变量函数提升也放在这里解释呀?

在进入执行上下文的时候,会有两个阶段。
第一阶段就是创建阶段,创建变量对象(暂时只考虑变量对象,除此之外还有作用域链,this):
变量对象包括三部分:
arguments(如果是函数的话);

函数声明,key : value,key为有function声明的函数名(所以对于函数表达式并不是这个范围,而是作为变量处理),value为指向函数的引用;( 这是不是跟函数提升有关,这里会提升整个函数体? ),如果已存在变量名,也会覆盖,(这就是对应着同一个上下文中,同名函数会被后者覆盖?);

变量声明,key: value, key 为有var声明的变量名,value也undefined,如果变量名存在的话,比如说跟上一步的函数名同名,则会跳过,保持key: value不变;(这个是不是跟变量提升只是提升变量声明,而不提升初始化值有关呀?)

在创建阶段已经完成了变量和函数提升,变量只会提升声明不包括初始化值,函数会提升整个函数体,而且如果出现同名,会被最近的函数体覆盖。除此以外,其他按照原始顺序不变?

然后到执行阶段,就是按照创建阶段的顺序执行吧。

是这样理解的么😄?

from blog.

jawil avatar jawil commented on May 1, 2024

你所说的变量函数应该是函数表达式,就是类似var a=function(){},这种你就看做一个跟var a=1来看待。这当然也存在变量提升。

同名函数会被后者覆盖,如果是函数跟变量同名则会跳过,等到变量赋值时候,变量也会覆盖函数原来的值。其他粗略看了一眼,好像没什么毛病,这种概念性的东西多结合实践体会一下,博主已经写得很清楚了。@YanLIU0822

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@freeser 嗯嗯,正是如此~

from blog.

ry928330 avatar ry928330 commented on May 1, 2024

@alexzhao8326 我觉得你举的例子也是符合作者所说的情况的。
即:var a =1; console.log(a);function foo(){console.log("foo");}; 在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。
然而此时,只是将一个function类型的a,赋值为了1,这js这种也不是什么不允许的。就像你声明了一个对象,你把它赋值为了一个数组一样。所以打印出来,a的值还是1.

from blog.

thisisandy avatar thisisandy commented on May 1, 2024

@alvis888 我的canary 是报错,没有输出1

from blog.

thisisandy avatar thisisandy commented on May 1, 2024

@oakland 我在其他的文章里有看到一种说法,认为在函数作用域内,AO===VO的,认为函数初始化之后,将arguments放置在AO中即是VO https://stackoverflow.com/questions/6337344/activation-and-variable-object-in-javascript

from blog.

JJL-SH avatar JJL-SH commented on May 1, 2024

我突然发现看完正文,再把评论看一遍。再回头看一遍正文啥都明白了

from blog.

bluefantasy728 avatar bluefantasy728 commented on May 1, 2024

@mqyqingfeng 你好,有幸拜读了你的文章,很是受益。
文中有个地方想请教一下

1. 函数的所有形参 (如果是函数上下文)
- 由名称和对应值组成的一个变量对象的属性被创建
- 没有实参,属性值设为 undefined

  • 这个“所有形参”是不是就是你所说的arguments对象?我看到你后面的例子中调用foo(1)的时候,在进入执行上下文后,AO的a属性是1,这个a是形参吧,它在函数执行的第一阶段(就是所谓的进入执行上下文阶段)的时候就已经赋值成1了么(我原来理解的是第一阶段是赋值成undefined的)?还是说在代码执行阶段再赋值成1的?
  • 后面那句“没有实参,属性值为undefined”,没看明白什么意思,你是说,如果没有实参,arguments属性就赋值为undefined么?

from blog.

bluefantasy728 avatar bluefantasy728 commented on May 1, 2024

我又想到一个问题,既然评论里有提到VO和AO是一个东西,只是在执行上下文的生命周期中会转换角色而已,那就是说在第一步进入执行上下文的时候那时还不是AO,而是VO咯?这算挑刺吗?哈哈

from blog.

think2011 avatar think2011 commented on May 1, 2024

@bluefantasy728
我理解也是的,VO和AO是同一个,未进入执行阶段VO的属性不能访问,进入执行之后,VO变成AO,就能访问了。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@keyiran 这两种分析规则都能解释通这两个例子, 我也觉得这些都是主观的结论。这些结论可以帮助大家分析具体的例子,采用哪一种都可以,毕竟 Hoisting 只是一种思考执行上下文(特别是创建和执行阶段)在JavaScript中如何工作的一种方式,真到具体执行的时候,就涉及到 JavaScript 引擎的扫描规则,而这个规则究竟是什么样的呢?我并没有查过具体的资料,不过我的猜想是:顺序扫描,如果先有变量,后遇到同名的函数,就覆盖,如果现有函数,后遇到同名的变量,就不覆盖,接着执行~

from blog.

Designer-Jelly avatar Designer-Jelly commented on May 1, 2024

看了通篇 反复看了评论看懂了 谢谢楼上各位大大
我的理解是 楼主的思考题和评论里的就是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.

rylan0119 avatar rylan0119 commented on May 1, 2024

在函数上下文中,arguments与函数形参是什么关系?ES6与之前的实现似乎是不同的

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@liruilong119 在 ES5 的非严格模式下,arguments 与形参有绑定关系,具体可以参考 #14,ES6 下一些情况比如有默认值的时候,会默认采用严格模式,取消这种绑定关系

from blog.

mengLLLL avatar mengLLLL commented on May 1, 2024

为什么实参的值可以取到,而b就是undefinde?

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

from blog.

rylan0119 avatar rylan0119 commented on May 1, 2024

@mengLLLL应该是由于活动对象AO在进入上下文时由Arguments对象初始化,能够获得函数的实参了,所以此时有值,而b是函数内部声明的参数,由于变量声明提升,被AO获得,但此时上下文还没执行,没有完成赋值操作所以b的值为undefined

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@liruilong119 感谢回答,正是如此,@mengLLLL 这个时候的 AO 指的是进入执行上下文时的 AO ,只是完成了变量提升和函数提升,但是并没有进入执行阶段~

from blog.

dengnan123 avatar dengnan123 commented on May 1, 2024

看来我好好看看博主的 git了 基础太差了我

from blog.

 avatar commented on May 1, 2024

@mqyqingfeng

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.

DarkYeahs avatar DarkYeahs commented on May 1, 2024

@veedrin

  1. 第一个a = 1是一个未声明的变量进行赋值的操作,它不会被提升,所以在执行console.log(a)的时候在搜索的时候搜索不到的,然后在执行a = 1的时候才会在全局的VO中进行注册,这个时候a才能被搜索到
  2. 第二个首先在全局的VO中注册了foo函数,然后执行a = 1的时候再在全局中注册了a这个变量,接着再执行foo(),所以打印出来的就是1
  3. 第三个跟第一个是同样的解释

from blog.

Zefeng666 avatar Zefeng666 commented on May 1, 2024

let a = 1;
console.log(window.a);//undefined
博主能解释一下let和var底层的区别吗?不胜感激~

from blog.

 avatar commented on May 1, 2024

@mqyqingfeng @DarkYeahs

原来未声明的变量不会提升,多谢!

issues留言不会提醒么?说好的同性交友社区呢!

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@Zefeng666 let 的这种特性,即 let 会在全局作用域下创建一个新的绑定,但该绑定不会添加为全局对象的属性,应该是规范中就规定成这样的,跟它的不提升特性并没有什么关系。

至于 let 和 var 底层的区别,我能想到的内容在 ECMAScript 6 入门 都有……

from blog.

RayJune avatar RayJune commented on May 1, 2024

@mqyqingfeng

想请教一下博主关于变量提升的问题:

console.log(a);  // undefined
var a = 1;

因为变量提升,打印 a 不会报错,但是只会打印出 undefined,只声明没有赋值。

按照执行上下文的理解,代码会分成两个阶段进行处理:分析和执行,关于分析中变量声明的部分,

由名称和对应值(undefined)组成一个变量对象的属性被创建

这里是不是改为 只创建名称,不赋值 更合适呢?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@RayJune 这就要看怎么理解 undefined 这个值了,我也不能保证我说的是正确的,在我看来,undefined 是一个原始值,是一个占用着固定内存大小的内容,当只声明变量的时候,其实相当于将这个原始值赋值给变量,所以才会表述为 由名称和对应值(undefined)组成一个变量对象的属性被创建

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@joker09 确实,变量提升是一个在规范中找不到的术语。变量提升被认为是思考执行上下文(特别是创建和执行阶段)在 JavaScript 中如何工作的一种方式。不过如果直接用变量对象去解释,估计给新手会增加不少的理解成本~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@Ghohankawk 哈哈,感谢分享自己的想法,正是大家的交流才让一个知识点越聊越明了~

from blog.

zhiyinghou avatar zhiyinghou commented on May 1, 2024
function bar(a){
	console.log(a);
	function a() {};
}

执行 bar(2)
结果是 function a() {}
是不是说明 如果形参名称跟已经声明函数相同,则形参也不会干扰声明函数这类属性

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@houzhiying977 可以呀,其实在规则里也有讲到:

default

如果变量对象已经存在相同名称的属性,则完全替换这个属性,这个所谓的“相同名称的属性”就包括形参和变量声明~

from blog.

ShumRain avatar ShumRain commented on May 1, 2024

所以所有的变量都是存在变量对象上的对吗?而原始类型存在栈上的说法是错误的?变量对象又是存在哪的?- -

from blog.

jiangjiu avatar jiangjiu commented on May 1, 2024

需要注意的是,在严格模式下,this默认是指向undefined,并非window对象。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@ShumRain 这个不冲突吧……变量对象你可以理解为一个对象,对象以及对象中的值存在哪里就还是存在哪里呀~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@jiangjiu 确实如此,感谢补充哈~

from blog.

wnbupt avatar wnbupt commented on May 1, 2024
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.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@wnbupt 感谢补充哈~ 确实是这样的~

from blog.

Kevin-Loeng avatar Kevin-Loeng commented on May 1, 2024

@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.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@lawpachi 嗯嗯,我的想法跟你一致呀~

from blog.

lawpachi avatar lawpachi commented on May 1, 2024

还有一个问题,执行代码会产生执行上下文,执行上下文分为1.进入执行上下文2. 代码执行。按照分析博主的分析,变量提升处于1.进入执行上下文阶段。
但是JS代码执行会先进行编译(词法分析,生成AST,生成计算机识别代码),然后进行代码执行。编译过程的词法分析就会变量提升。所以又怎么会在执行代码时候才产生的变量提升呢?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@lawpachi 没有说到执行代码的时候产生变量提升呀,不过执行代码的时候,会根据代码修改变量对象~

from blog.

lawpachi avatar lawpachi commented on May 1, 2024

麻烦博主指教:进入执行上下文会产生AO变量对象,按照@jawil 变量对象创建的几个过程的说法 ,

变量对象的创建,依次经历了以下几个过程。

建立arguments对象。

检查当前上下文的函数声明,也就是使用function关键字声明的函数。在变量对象中以函数名建立一个属性,属性值为指向该函数所在内存地址的引用。如果函数名的属性已经存在,那么该属性将会被新的引用所覆盖。

检查当前上下文中的变量声明,每找到一个变量声明,就在变量对象中以变量名建立一个属性,属性值为undefined。如果该变量名的属性已经存在,为了防止同名的函数被修改为undefined,则会直接跳过,原属性值不会被修改。(上面的例子就属于这种情况,foo函数名与变量foo同名)

所以按照如上所述变量对象的创建会产生变量提升。

但是根据你不知道的JS中写到,变量提升存在代码的编译过程。还没到产生执行上下文阶段。

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@lawpachi 文章中其实没有区分编译阶段与进入执行上下文阶段的,文章中认为两者是一样的:

default

分析其实就是指编译的过程,也是产生执行执行上下文的过程

from blog.

xxyj avatar xxyj commented on May 1, 2024

箭头函数中已经没有了arguments属性... 博主能分析下为什么把这个干掉了呢?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@xxyj 可能是因为 arguments 设计的不够好吧……正常情况下,我们使用 arguments 还需要转成数组形式,ES6 可能认为这是个没有必要的操作,所以就干掉了 arguments, 然后提供了一个更好用的方案,就是利用拓展运算符:

(...arguments) => console.log(arguments);

你还可以写成这样:

(first, ...arguments) => console.log(first, arguments);

使用这种方式,完全可以替代掉 arguments,所以就去掉了吧……

不过这是我猜的啦,在规范中我并没有找到去掉的原因~

from blog.

MagicHacker avatar MagicHacker commented on May 1, 2024

@mqyqingfeng 你说全局对象是由Object构造函数实例化的一个对象,但是window.proto === Object.prototype为false啊

from blog.

ciomedyu avatar ciomedyu commented on May 1, 2024
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.

Tan90Qian avatar Tan90Qian commented on May 1, 2024

@mqyqingfeng

function a(b){
	console.log(b); // ƒ b(){}
	function b(){}
	console.log(b); // ƒ b(){}
}
a(function c(){})

对于这个现象,我更倾向于js对于函数的执行采取的流程为进入函数的执行上下文,创建函数的AO -> 形参/变量的声明 -> 实参赋值给AO中对应的属性 -> 函数的声明 -> 执行函数体内其余,而且我有个看法,是否是这样的:js的编译阶段,只会对全局环境下的变量和函数进行声明及提前,而不会去访问被声明的函数体内部?否则似乎无法解释函数体内部的函数声明会覆盖实参传给形参的值这一现象。

from blog.

wnbupt avatar wnbupt commented on May 1, 2024
function foo3(a) {
    var a = 10
    function a() {}
    console.log(a)
}
foo3(20) // 10

请问博主为什么会输出10呢?不是function声明会被提升,覆盖arguments中的a和变量声明,应该打印出[Function: a]吧?多谢解答~~
@mqyqingfeng

from blog.

mygaochunming avatar mygaochunming commented on May 1, 2024

@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.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@MagicHacker 嗯,这样说确实不严谨,准确的说是继承了 Object
default

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@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.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@mygaochunming 感谢回答哈~ 确实如此 ヾ(^▽^ヾ)

from blog.

wnbupt avatar wnbupt commented on May 1, 2024

@mygaochunming 多谢多谢~我明白了,其实就是因为函数声明会优先变量声明,已经被提前了,之后的a = 10就会覆盖函数,所以打印出来就是10。

from blog.

Tan90Qian avatar Tan90Qian commented on May 1, 2024

@mqyqingfeng

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.

lucky3mvp avatar lucky3mvp commented on May 1, 2024

谢谢博主,看了之后收益颇多。
想请教一个问题,关于es6 let const没有变量提升,是不是就不能按照“进入执行环境”那三步来分析VO了?再结合此前讲的静态作用域,怎么感觉像是变成了动态作用域了?关于let应该怎么理解呢?

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@lucky3mvp 是的哈~ let 和 const 没有变量提升,所以用的是其他的规则,比如说暂时性死区,要说怎么解释 let 的话,嗯……我个人认为是:过程依然分为进入执行上下文和代码执行两个阶段,在进入执行上下文阶段,对于 let 声明的变量进行"检查",比如是否重复声明,暂时性死区等等,然后代码执行阶段赋值,那么问题来了,是在进入上下文阶段创建的呢还是代码执行阶段创建的呢?一时间我也有些疑惑啦……

至于变成了动态作用域,嗯……这个意思不是很理解哈~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@Tan90Qian

为什么“函数体内部的函数声明提前”能够将尚未执行的函数的实参值给顶替掉?

嗯……因为规则就是这么设计的……那为什么这么设计呢?我就不清楚了……

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@coconilu hoisting 毕竟只是一个帮助大家理解的概念,我也不能百分百确定就是这样,只要能用它解释更多的场景就没有什么问题~

from blog.

mqyqingfeng avatar mqyqingfeng commented on May 1, 2024

@LbHongYu 是的~

from blog.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.