前言
我们来看下面这一段代码。
print(); console.log(str); var str = "hello world!"; function print() { console.log(str); }
如果你写过 js,那就知道,这段代码不会报错,会正常输出
undefined undefined
。如果不能理解,不要着急,我们删除
var str = 'hello world'
,再执行:print(); console.log(str); function print() { console.log(str); }
现在就会报错了,
str is not defined
。这是为什么呢?如果不知道不要紧,下边我们就来了解下,要了解 JavaScript 的运行方式,我们就要先了解下变量提升。变量提升(Hosting)
在介绍变量提升之前,我们先通过以下两个例子来讲解 JS 中的声明和赋值。
在 es6 之前,JS 都是通过 var 来声明变量。
var str = "hello word";
这一句等价于
var str; // 变量声明 str = "hello world!"; // 赋值
下面这段代码是一个完整的函数声明,没毛病。
var print = function () { console.log(str); };
这段代码是一个声明变量和赋值的过程,相当于
var print; // 变量声明 print = function () { // 赋值 console.log(str); };
所谓的变量提升,是指 JavaScript 在执行的过程中,JavaScript 引擎把变量提升和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置值为 undefined。
JavaScript 执行流程
从概念的字面上来看,“变量提升”意味着变量和函数的声明会在物理层移动到代码的最前面。但其实是:变量和函数声明在代码里的位置是不会改变的,而且在编译阶段被 JavaScript 引擎放入内存中。JS 代码编译完成之后才会进入执行阶段。
大致流程就是:一段 JavaScript 代码 => 编译阶段 => 执行阶段
下面来看下详细的流程,还是以上边的例子为例
- 变量提升阶段代码
var str = undefined; function print() { console.log(str); }
- 执行代码阶段
print(); console.log(str); str = "hello world";
输入一段 JS 代码,经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码。
执行上下文
是指 JS 执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间的用到变量如 this、变量、对象、函数等。
在执行上下文中存在一个变量环境的对象(Variable Environment),该对象中保存了变量提升的内容,比如上面代码中变量 str 和函数 print 都保存在该对象中。
类似这样:
Variable Environment: str -> undefined, print -> function () { console.log(str) };
接下来逐行分析下代码:
print(); // 1 console.log(str); // 2 var str = "hello world!"; // 3 function print() { // 4 console.log(str); }
编译阶段
- 1 和 2,不是声明操作,JS 引擎不做任何处理
- 3,通过 var 声明变量,所以 JS 引擎在环境对象中创建一个名为 str 的属性,并初始化为 undefined
- 4,这是一个函数声明。JS 引擎发现了一个通过 function 定义的函数,所以 JS 引擎会将函数定义部分存到堆(HEAP)中,并在环境对象中创建一个 print 属性,并将 print 的值指向堆中函数的位置。
这样就生成了变量环境对象,接着 JS 引擎会把声明之外的代码编译成字节码(可执行代码),也就是下面这一段模拟代码:
print(); console.log(str); str = "hello world!";
执行阶段
JS 引擎开始执行“可执行代码”,按照顺序一行一行的执行,过程如下:
- 执行到 print 函数时,JS 引擎变开始在变量环境对象中寻找该函数,由于变量环境对象中 print 存在该函数的引用,所以 JS 引擎便开始执行该函数,在执行过程发现,函数用了 str 变量,所以 JS 引擎继续在变量环境中寻找 str 变量,此时 str 在变量环境对象中的值为 undefined, 所以输出 undefined。
console.log(str)
, JS 引擎继续在变量环境对象中查找 str,此时 str 在变量环境对象中的值为 undefined, 所以输出 undefined.str = 'hello world!';
这是一个赋值操作,把'hello world!'赋值给 str, 赋值结束后变量环境对象变成:Variable Environment: str -> 'hello world!', print -> function () { console.log(str) };
整个流程大致差不多这样。
有个问题,如果代码中存在多个相同的变量和函数时怎么办?
示例代码:
a(); // 1 var a = "hello world"; //2 console.log(a); // 3 function a() { // 4 console.log("inner a function 1"); } var a = "hello, tomorrow"; // 5 console.log(a); // 6 function a() { // 7 console.log("inner a function 2"); } a(); // 8
执行结果是:
/* inner a function 2 hello world hello, tomorrow 报错 a is not a function */
分析下:
编译阶段
- 1,函数调用不做处理
- 2, 有 var 声明变量,在执行环境中变量环境对象上创建 a 变量并赋值为 undefined.
Variable Environent: a -> undefined;
- 3, 函数调用,不做处理
- 4, JS 引擎发现有同名 function 定义的函数 a,把函数定义存储到堆中,并且在变量环境对象中查找是否有 a 属性,有 a,然后把 a 的值指向该函数的在堆中的位置。此时变量环境对象变成(类似这样):
Variable Environent: a -> function () { console.log ('inner a function 1')};
- 5, 有 var 声明变量,在执行环境中变量环境对象上查找是否存在 a 属性,发现存在并 y 有值,不做处理。
- 6, 不做处理
- 7, JS 引擎发现有同过 function 定义的函数 a,把函数定义存储到堆中,并且在变量环境对象中查找是否有 a 属性,有 a,然后把 a 的值指向该函数的在堆中的位置。此时变量环境对象变成(类似这样):
Variable Environent: a -> function () { console.log ('inner a function 2')};
然后执行可执行代码:
a(); // 1 a = "hello world"; //2 console.log(a); // 3 a = "hello, tomorrow"; // 4 console.log(a); // 5 a(); // 6
- 1,JS 引擎变开始在变量环境对象中寻找该函数,由于变量环境对象中 a 存在该函数的引用,所以 JS 引擎便开始执行该函数,输出
inner a function 2
- 2,赋值操作,把'hello world'赋值给 a,此时变量环境对象变更为:
Variable Environent: a -> 'hello world';
- 3,打印 a 的信息。
hello world
- 4, 赋值操作,把'hello, tomorrow'赋值给 a,此时变量环境对象变更为:
Variable Environent: a -> 'hello, tomorrow';
- 5.打印a的信息
hello,tomorrow
- 6,函数调用,但是在变量环境对象中,a 的值为'hello, tomorrow',并没有指向函数的位置,所以报错。
本文作者:前端小毛
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!