看了一些es6的相关,作下笔记总结.
当我们看到一串JavaScript 代码,它是按照顺序执行的吗?
执行上下文
只有理解了执行上线文,才能更好地理解 JavaScript 语言本身,比如变量提升、作用域和闭包等。
从 JavaScript 代码的顺序执行讲起,一步步了解 JavaScript 是怎么运行的。
showName();console.log(myName);
var myName = 'tokgogogo';
function showName() { console.log('函数 showName 被执行');}
JavaScript 是按照顺序执行的。如果按照这个逻辑来理解:
当执行到第一行的时候,由于函数 showName 未定义,所以执行应该报错;
同样执行第二行的时候,由于变量 myName 未定义,所以同样也会报错。
然而实际的结果,先输出了“函数 showName 被执行”,又输出了 undefined 。
根据上面的结果,函数或者变量可以在定义之前使用。那么,如果使用没有定义的变量或者函数,JavaScript 代码还能继续执行吗?
showName();console.log(myName);
function showName() { console.log('函数 showName 被执行');}
再次执行这段代码,JavaScript 引擎会报错:myName is not defined 。
由上述两个执行结果来看,可以得到如下三个结论:
在执行过程中,若使用了未声明的变量,那么 JavaScript 执行会报错。
在一个变量定义之前使用,不会出错,但是该变量的值会为 undefined ,而不是定义的值。
在一个函数定义之前使用,不会出错,且函数能正确执行。
变量提升
var myname = 'tokgogogo';
这段代码可以看成两行代码组成:
var myname; // 声明部分
myname = 'tokgogogo'; // 赋值部分
上面是变量的声明和赋值,函数的声明和赋值:
function foo() { console.log('foo');}
var bar = function() { console.log('bar');}
第一个函数 foo 是一个完整的函数声明,也就是说没有涉及到赋值操作;
第二个函数是先声明变量 bar ,再把函数体赋值给 bar :
所谓的变量提升,是指在 JavaScript 代码执行过程中, JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是 undefined 。
/** * 变量提升部分 */
// 把变量 myName 提升到开头
// 同时给 myName 赋值 undefinedvar
myName = undefined;// 把函数 showName 提升到开头
function showName() { console.log('函数 showName 被执行');}
/** * 可执行部分 */
showName();console.log(myName);
// myName 赋值语句myName = 'tokgogogo';
对原来的代码主要做了两处调整:
第一处是把声明的部分都提升到了代码开头,如变量 myName 和函数 showName ,并给变量设置默认值 undefined ;
第二处是移除原本声明的变量和函数,如
var myName = 'tokgogogo'
的语句,移除了 var 声明,整个移除 showName 的函数声明。
通过这两步,就可以实现变量提升的效果。因此,这就是为什么可以在定义之前使用变量或者函数的原因——函数和变量在执行之前都提升到了代码开头。
JS 代码的执行流程
实际上,变量和函数在声明在代码里的位置是不会变的,而是在编译阶段被 JavaScript 引擎放入内存中。
<u data-slate-mark="true">一段 JavaScript 代码在执行之前需要被 JavaScript 引擎编译,</u><u data-slate-mark="true">编译</u><u data-slate-mark="true">完成后,才会进入</u><u data-slate-mark="true">执行</u><u data-slate-mark="true">阶段。</u>
编译阶段
编译阶段和变量提升的关系。
变量提升部分的代码
var myName = undefined;
function showName() {
console.log('函数 showName 被执行');
}
执行部分的代码
showName();
console.log(myName);
myName = 'tokgogogo';
输入一段代码,经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码。
执行上下文是 JavaScript 执行一段代码时的运行环境,比如调用一个函数,就会进入这个函数的执行上下文,确定该函数在执行期间用到的诸如 this、变量、对象以及函数等。
在执行上下文中存在一个变量环境的对象(Viriable Environment),该对象中保存了变量提升的内容,比如上面代码中的 myName 和函数 showName,都保存在该对象中。
该变量环境对象如下结构:
ViriableEnvironment:
myName -> undefined,
showName -> function : { console.log(myName) }
showName();
console.log(myName);
var myName = 'tokgogogo';
function showName() {
console.log('函数 showName 被执行');
}
对于这段代码:
第一行和第二行,由于这两行代码不是声明操作,所以 JavaScript 引擎不会做任何处理;
第三行,由于这行是经过 var 声明的,因此 JavaScript 引擎将在环境对象中创建一个名为 myName 的属性,并使用 undefined 对其初始化;
第四行,JavaScript 引擎发现了一个通过 function 定义的函数,所以它将函数定义存储到堆(HEAP)中,并在环境对象中创建一个 showName 的属性,然后将该属性值指向堆中函数的位置。
这样就生成了变量环境对象。接下来 JavaScript 引擎会把声明以外的代码编译为字节码。
执行阶段
JavaScript 引擎开始执行“可执行代码”,按照顺序一行一行的执行:
当执行到 showName 函数时,JavaScript 引擎变开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以 JavaScript 引擎便开始执行该函数,并输入“函数 showName 被执行”结果。
接下来打印“myName”信息,JavaScript 引擎继续在变量环境对象中查找该对象,由于变量环境存在 myName 变量,值为 undefined ,所以这时候就输出 undefined 。
接下来执行第三行,把“tokgogogo”赋值给 myName 变量,赋值后变量环境中的 myName 属性值改变为“tokgogogo”,变量环境如下:
ViriableEnvironment:
myName -> "tokgogogo",
showName -> function : { console.log(myName) }
代码中出现相同的变量或者函数怎么办
function showName() {
console.log('showName 1');
}
showName();
function showName() {
console.log('showName 2');
}
showName();
在上面的代码中,先定义了一个 showName 函数,打印 1 ;然后调用 showName ,并定义了一个 showName 函数,打印 2 ;最后接着调用 showName 。
其完整执行流程:
首先是编译阶段。遇到了第一个 showName 函数,会将该函数体存放到变量环境中。接下来是第二个 showName 函数,继续存放至变量环境中,但是变量环境中已经存在一个 showName 函数了。因此,第二个 showName 函数会将第一个 showName 函数覆盖掉。这样变量环境中就只存在第二个 showName 函数了。
接下来是执行阶段。先执行第一个 showName 函数,但由于是从变量环境中查找 showName 函数,而变量环境中只保存了第二个 showName 函数,所以最终调用的是第二个函数,打印的内容是 2 。第二次执行 showName 函数也走同样的流程,所以输出的结果也是 2 。
总结
JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为JavaScript 代码在执行之前需要先编译;
在编译阶段,变量和函数会被存放到变量环境中,变量的默认值被设置为 undefined ;在代码执行阶段,JavaScript 引擎从变量环境中去查找自定义的变量和函数。
如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的。
JavaScript 的执行机制:先编译,再执行。
showName(); // 2
var showName = function() { console.log(1);}
function showName() { console.log(2);}
showName(); // 1
编译阶段:
var showName = undefined;
function showName() { console.log(2);}
执行阶段:
showName();
showName = function() { console.log(1);}
showName();