先来看一段代码:
showname();
console.log(myname);
var myname="Claire";
function showname(){
console.log("I am Claire")
};
这段代码的输出结果为
I am Claire
undefined
那么,在学习JS的时候知道JS是按照顺序执行的,在执行第一句以及第二句的时候,函数showname(),和变量myname都没有被定义,按照之前的说法的话,那么这段代码在执行的过程会被报错,但是能成功运行并且输出相应结果,这里就引入了变量提升的概念
变量提升是指在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。
什么是声明和赋值???
比如var name="Claire"
就可以拆分成两部分
var name=undefined //声明过程
name="Claire" //赋值过程
从字面上面来看,变量提升就是在物理层面将函数的声明部分和变量的声明部分放到了代码的前面,但不是这样的,而是在编译的时候将它存放到内存里面,,编译完成之后,再对他进行执行
我们用这个过程来解释最初的那段代码,并将它分为两个过程:
变量提升过程:
var myname=undefined;
function showname(){
console.log("I am Claire")
};
执行过程:
showname();
console.log(myname);
myname="Claire";
经过编译后,会生成两部分内容:执行上下文(Execution context)和可执行代码。在执行上下文中存在一个变量环境的对象(Viriable Environment),该对象中保存了变量提升的内容,比如上面代码中的变量myname 和函数 showname,都保存在该对象中。(关于执行上下文下面会讲解)
然后一步一步分析代码
第 1 行和第 2 行,由于这两行代码不是声明操作,所以 JavaScript 引擎不会做任何处 理;
第 3 行,由于这行是经过 var 声明的,因此 JavaScript 引擎将在环境对象中创建一个名 为 myname 的对象,并使用 undefined 对其初始化;
第 4 行,JavaScript 引擎发现了一个通过 function 定义的函数,所以它将函数定义存储 到堆 (HEAP)中,并在环境对象中创建一个 showname 的属性,然后将该属性值指向 堆中函数的位置
1 VariableEnvironment:
2 myname -> undefined,
3 showName ->function : {console.log(myname)
然后到了执行阶段
:
当执行到 showname 函数时,JavaScript 引擎便开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以 JavaScript 引擎便开始执行该函数,并输出函数结果。
接下来打印“myname”信息,JavaScript 引擎继续在变量环境对象中查找该对象,由于变量环境存在 myname 变量,并且其值为
undefined,所以这时候就输出undefined。 接下来执行第 3 行,把“Claire”赋给 myname
变量,赋值后变量环境中的myname 属性值改变为“Claire”
再来看个例子:
foo(); // foo2
var foo = function() {
console.log('foo1');
}
foo(); // foo1,foo重新赋值
function foo() {
console.log('foo2');
}
foo(); // foo1
我们用刚才那个方式来解释一下这一段代码:
首先在编译的过程中会产生这样一个结果:
遇到了第一个 foo 函数,会将该函数体存放到变量环境中。接
下来是第二个 foo 函数,继续存放至变量环境中,但是变量环境中已经存在一个foo 函数了,此时,第二个 foo 函数会将第一个 foo 函数覆
盖掉。这样变量环境中就只存在第二个 showName 函数了
紧接着执行,当执行到 foo 函数时,JavaScript 引擎便开始在变量环境对象中查找该函数,由于变量环境对象中存在该函数的引用,所以 JavaScript 引擎便开始执行该函数,并输出函数结果。也就是foo2,然后再执行到变量foo的时候,被同样赋值了一个函数,就相当于两个函数名为foo的函数,此时后面的就会覆盖掉之前的,也就是foo2会被覆盖掉,所以之后的调用都是结果都会打印foo1。
在上面将变量提升的时候,说到了执行上下文
执行上下文分为三种:
全局执行上下文
:只有一个,浏览器中的全局对象就是 window 对象,this 指向这个全局对象。当 JavaScript 执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生存周期内,全局执行上下文只有一份。
函数执行上下文
:存在无数个,只有在函数被调用的时候才会被创建,每次调用函数都会创建一个新的执行上下文。
Eval 函数执行上下文
:指的是运行在 eval 函数中的代码,很少用而且不建议使用。
执行栈,也叫调用栈,具有 LIFO(后进先出)结构,用于存储在代码执行期间创建的所有执行上下文。
首次运行JS代码时,会创建一个全局执行上下文并Push到当前的执行栈中。每当发生函数调用,引擎都会为该函数创建一个新的函数执行上下文并Push到当前执行栈的栈顶。
根据执行栈LIFO规则,当栈顶函数运行完成后,其对应的函数执行上下文将会从执行栈中Pop出,上下文控制权将移到当前执行栈的下一个执行上下文。
var a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
// Inside first function
// Inside second function
// Again inside first function
// Inside Global Execution Context
执行上下文分两个阶段创建:1)创建阶段; 2)执行阶段
创建阶段
1、确定 this 的值,也被称为 This Binding。
2、LexicalEnvironment(词法环境) 组件被创建。
3、VariableEnvironment(变量环境) 组件被创建。
ExecutionContext = {
ThisBinding = <this value>, // 确定this
LexicalEnvironment = { ... }, // 词法环境
VariableEnvironment = { ... }, // 变量环境
}
词法环境(Lexical Environment)
词法环境有两个组成部分
1、环境记录:存储变量和函数声明的实际位置
2、对外部环境的引用:可以访问其外部词法环境
词法环境有两种类型
1、全局环境:是一个没有外部环境的词法环境,其外部环境引用为 null。拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。
2、函数环境:用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。
变量环境
变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。
在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储**函数声明和变量( let 和 const )绑定,而后者仅用于存储变量( var )**绑定。
参考链接:https://muyiy.cn/blog/1/1.1.html#%E5%88%9B%E5%BB%BA%E9%98%B6%E6%AE%B5