JavaScript 执行上下文 变量对象 以及变量、函数声明为什么会提前

  在上一篇博文"谈谈JavaScript的算数运算、二进制浮点数舍入误差及比较、类型转换和变量声明提前问题"中提到,函数、变量声明会提前,却没有解释为什么。本文从执行上下文和变量对象两个角度谈谈为什么函数、变量申明会提前。

  关于执行上下文(Execution Contexts)和变量对象,个人认为网上有几篇比较好的博文:
  1)深入理解JavaScript系列(11):执行上下文(Execution Contexts)深入理解JavaScript系列(12):变量对象(Variable Object),汤姆大叔的博文,更多是ECMA-262标准理解及翻译,词汇、术语比较专业,又比较容易理解;
  2)前端基础进阶(二):执行上下文详细图解前端基础进阶(三):变量对象详解,个人认为写的很好,读者朋友们若想深入了解执行上下文和变量对象,不妨阅读这2篇博文。

1、执行上下文

  每当控制器转到ECMAScript可执行代码的时候,即会进入到一个执行上下文。执行上下文可以理解为当前代码的运行环境,JavaScript运行环境大概包括三种情况:

  • 全局环境:JavaScript代码运行起来会首先进入该环境,例如加载外部的js文件或标签内的代码;
  • 函数环境:当函数被调用执行时,会进入当前函数中执行代码;
  • eval(不建议使用,可忽略)

  因此在JavaScript代码执行过程中,必定会产生多个执行上下文,JavaScript引擎会以栈的方式来处理它们,不妨以ECStack=[]来表示:
  1)首先会压入全局上下文,[globalEC]
  2)函数调用,创建函数上下文,压入ECStack,[globalEC, functionEC]
  3)函数调用完毕,退出函数上下文,从栈顶ECStack弹出,[globalEC]
  4)全局上下文一直都存在,直到页面或浏览器关闭。

2、变量对象

执行上下文有其生命周期:创建阶段和代码执行阶段。

  • 创建阶段:在这个阶段中,执行上下文会分别创建变量对象,建立作用域链,以及确定this的指向,其中变量对象用VO表示的话,应该类似VO = {arguments, function, var 变量}。
  • 代码执行阶段:创建完成之后,就会开始执行代码,这个时候,会完成变量赋值,函数引用,以及执行其他代码。

看个DEMO

function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}
test();

每当代码执行到test()时,就会创建新的函数执行上下文,并压入ECStatck[globalEC, testEC],testEC有2个阶段,伪代码表示如下:

// 创建过程
testEC = {
    VO: {},		// 变量对象
    scopeChain: {},
    明确this指向
}
VO = {
    arguments: {...}, 
    foo: <foo reference>  // 表示foo的地址引用
    a: undefined
}

// 执行阶段
VO ->  AO   // VO被激活,用AO表示,Active Object
AO['a'] = 1
```js

所以demo代码,等同:
```js
function test() {
    function foo() {		//函数声明优先级高于变量
        return 2;
    }
	var a;
    console.log(a);
    console.log(foo());
    a = 1;
}
test();

至此,我们应该明白了为什么变量、函数声明会提前啦,总结一下,2句话:
1)执行上下文有2个阶段,创建阶段(进入上下文)、执行阶段;
2)在创建阶段,会创建变量对象,变量对象包含下列属性:

  • 函数的所有形参(如果我们是在函数执行上下文中),arguments
  • 所有函数声明
  • 所有变量声明

3、函数声明优先级高于变量申明

同样是声明提前,但是函数声明优先级高于变量声明,函数声明先执行,如果该变量名(同名函数已经申明)则变量不会覆盖之前定义,但是后续赋值会覆盖函数声明。
看一个比较经典的demo:

	alert(x); 		// function x() {}
	var x = 10;
	alert(x); 		// 10
	x = 20;
	function x() {};
	alert(x); 		// 20

结合上下文、变量对象,代码等同:

	function x() {};
	alert(x); 		// function x() {}
	x = 10;
	alert(x); 		// 10
	x = 20;
	alert(x); 		// 20

如果不是函数声明,而是函数表达式呢?

	console.log(x); 		// undefined
	var x = 10;
	console.log(x); 		// 10
	x = 20;
	var x = function() {};
	console.log(x); 		// function x() {}

函数表达式,等同与变量定义,根据代码先后顺序执行,上述代码等同:

	var x;
	console.log(x); 		// undefined
	x = 10;
	console.log(x); 		// 10
	x = 20;
	x = function() {};
	console.log(x); 		// function x() {}

猜你喜欢

转载自blog.csdn.net/chuangxin/article/details/84716412
今日推荐