一句话:执行上下文就是当前JS代码被解析和执行时存在的环境。(ECMAScript中定义的抽象概念)
只有一个不在函数内部的代码都位于全局执行上下文中
创建一个全局对象,其实就是我们的window对象
将this指向这个全局对象
任意数量的函数执行上下文创建阶段 => 执行阶段 => 回收阶段
当函数被调用,但是未执行内部的任何代码之前。
这个阶段会做出以下几件事:
创建变量对象:首先会初始化函数的参数arguments,提升函数声明和变量声明。
创建作用域链:在执行上下文创建阶段,作用域链是在变量对象之后创建的,作用域链本身包含变量对象,作用域链用来解析变量。
确定this指向
我们看下边这段代码:
在变量还没有声明的时候进行打印,会打印出什么呢?
<script>
console.log(a);
var a = 10;
</script>
结果:

我们看到不但没有报错 Uncaught ReferenceError: Cannot access ‘a’ before initialization(变量a未定义),还打印出了undefined。
事实上,在执行代码之前,我们的浏览器会先解析一遍我们的脚本,完成一个初始化的步骤,它遇到var变量时就会先初始化变量为undefined。
这就是变量提升(hoisting),它是指,浏览器在遇到JS执行环境的初始化时,引起的变量提前定义。
如何避免变量提升呢?
使用let,const 关键字声明变量,尽量使用从上图,避免使用var
<script>
console.log(a);//报错:Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 10;
/*--------------------分割线---------------*/
console.log(b);//报错:Uncaught ReferenceError: Cannot access 'a' before initialization
const b = 20;
</script>
声明函数的方法(箭头函数属于第二种声明方式)
我们一起看一段代码:
<script>
console.log(f1); //ƒ f1(){}
function f1(){}
/*--------------分界线-------------*/
console.log(f2);//undefined
var f2 = function(){}
</script>
同样我们看到,在声明函数之前打印该函数,打印出了内容,这是因为函数提升,就是初始化时,引擎把函数声明整个提升到了当前作用域的顶部。在实际打印之前,函数已经被声明。
至于第一个与第二个函数打印结果不同,我们结合第四点,变量提升,第二个函数使用函数表达式的方式声明,将函数赋值给了一个变量,这个变量在提升时被初始化为undefined。
而第一个函数使用函数声明方式声明,打印的是该函数本身。
注意:
<script>
alert(a)//弹出function a(){alert('我是函数');}
var a = "我是变量"
function a(){alert('我是函数');}
</script>
function hoistFunction() {
function foo() {
console.log(1);
}
foo(); // 2
function foo() {
console.log(2);
}
}
hoistFunction();
function hoistFunction() {
foo() // 2
var foo = function () {
console.log(1);
};
foo() // 1
function foo() {
console.log(2);
}
foo(); // 1
}
hoistFunction();
//解释
在JS中,函数是一等公民,函数声明的优先级最高,会被提升到当前作用域的最顶端,所以第一次调用时实际执行了下面的函数声明,第二次调用时,由于前边的函数表达式与之前的函数声明同名,故将其覆盖,以后的调用也将会打印出相同的结果。