JS代码的执行顺序有时与代码先后顺序有所差异,抛开异步代码,即使是同步代码,它的执行也与你的预期不一致,比如:
console.log(f1) // f1() {console.log('echo');}
function f1() {
console.log('听风是风');
};
f1(); //echo
function f1() {
console.log('echo');
};
f1(); //echo
按照代码书写顺序,应该先输出 听风是风,再输出 echo才对,很遗憾,两次输出均为 echo;如果我们将上述代码中的函数声明改为函数表达式,结果又不太一样:
console.log(f1) // undefined
var f1 = function () {
console.log('听风是风');
};
f1(); //听风是风
var f1 = function() {
console.log('echo');
};
f1(); //echo
这说明代码在执行前一定发生了某些微妙的变化,JS引擎究竟做了什么呢?这就不得不提JS执行上下文了。
JS执行上下文
JS代码在执行前,JS引擎会做一番准备工作,这份工作其实就是创建对应的执行上下文,js执行上下文有一套规则,规范变量存储、提升、this指向
- 分类: 全局执行上下文、函数执行上下文
- 作用:对数据进行预处理
- 执行上下文环境的个数:n+1
- n:函数调用的次数,函数每调用一次,执行一次上下文环境
- 1:全局上下文环境
function wrap(){
console.log("函数执行上下文环境")
}
wrap();
wrap();
wrap();
wrap();
wrap();
console.log("全局执行上下文环境")
//6个执行上下文环境
- 什么时候创建执行上下文环境?
- 全局执行上下文:全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文:函数调用时
全局执行上下文
全局执行上下文只有一个,在客户端中一般由浏览器创建,也就是我们熟知的window对象,我们能通过this直接访问到它。
console.log(this) // Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
全局对象window上预定义了大量的方法和属性,我们在全局环境的任意处都能直接访问这些属性方法,同时window对象还是var声明的全局变量的载体。我们通过var创建的全局对象,都可以通过window直接访问。
var a = 1;
var b = 2;
console.log(window)
函数执行上下文
函数执行上下文可存在无数个,每当一个函数被调用时都会创建一个函数上下文;需要注意的是,同一个函数被多次调用,都会创建一个新的上下文。
说到这你是否会想,上下文种类不同,而且创建的数量还这么多,它们之间的关系是怎么样的,又是谁来管理这些上下文呢,这就不得不说说执行上下文栈了
规则
全局执行上下文
1.var定义的全局变量==>undefined, 添加为window的属性
2.function声明的全局函数==>赋值(fun), 添加为window的方法
3.提升
4.this==>赋值(window)
函数执行上下文
1.形参变量==>赋值(实参)
2.arguments==>赋值(实参列表)
3.处理 var定义的局部变量
4.处理 function声明的函数
5.提升
6.this==>赋值(调用函数的对象)
提升
- 变量提升:通过var定义(声明)的变量, 在定义语句之前就可以访问到,值为undefined
- 函数提升:通过function声明的函数, 在之前就可以直接调用,值为 函数定义(对象)
- 提升规则:
- 函数的提升是整体的提升
- 变量的提升是声明的提升
- 函数的提升优于变量的提升
- 提升是指提升到本层作用域的最顶层
- 变量的提升不会理会if else 这种条件暗示,会跳出流程,进行提示
- 永远不要再流程控制语句的块内部定义函数
执行上下文栈
执行上下文栈(下文简称执行栈)也叫调用栈,执行栈用于存储代码执行期间创建的所有上下文,具有LIFO(Last In First Out后进先出,也就是先进后出)的特性。
JS代码首次运行,都会先创建一个全局执行上下文并压入到执行栈中,之后每当有函数被调用,都会创建一个新的函数执行上下文并压入栈内;由于执行栈LIFO的特性,所以可以理解为,JS代码执行完毕前在执行栈底部永远有个全局执行上下文。
理解压栈
- 当全局代码开始执行前,先创建全局执行上下文环境
- 当全局执行上下文环境创建好了以后将上下文中的所有内容放入栈内存
- 最先放入的在最下边(global)
- 其他执行的函数的执行上下文依次放入(放入的顺序是代码的执行顺序)
- 后进先出
作用域和执行上下文的关系
- 一个作用域在一个时刻可以拥有多个执行上下文,但处于活动状态的只有一个
- 上下文环境从属于所在的作用域
- 全局上下文环境==>全局作用域
- 函数上下文环境==>对应的函数使用域
作用域和执行上下文的区别
- 作用域是在代码编译时确定的
- 全局执行上下文环境是在全局作用域确定之后, js代码马上执行之前创建
- 函数执行上下文环境在函数体代码执行前创建
- 作用域是静态的, 只要函数定义好了就一直存在, 且不会再变化
- 上下文环境是动态的, 调用函数时创建, 函数调用结束时上下文环境就会被释放