点这里,关于变量提升的原理和上下文的创建,递归函数上下文栈具体内容请看进阶篇
执行上下文
-
对于执行上下文(简称“上下文”),我们可以将其理解成变量或函数的代码运行环境,在JS中的执行上下文一共有三种:全局上下文、函数上下文、eval。
-
每一个上下文环境都会有它关联的变量对象,这个变量对象保存了这个上下文环境中的所有的数据,包括:变量和函数。
全局上下文与函数上下文
-
在最外层的上下文就是全局上下文,函数上下文可以通过作用域链去访问保存在全局上下文中的数据。
-
所有上下文会在其所有代码执行完毕后销毁,全局上下文会在关闭网页时销毁
-
全局上下文是唯一的,程序运行立马创建。函数上下文在函数被调用时创建,可以有多个
所有上下文中,最外层的上下文为全局上下文,也就是window对象。通过var定义的全局变量和函数都保存在了window对象身上
var name = "kobe"
function getName() {
return name
}
console.log(window.hasOwnProperty('name'))
console.log(window.name)
console.log(window.hasOwnProperty('getName'))
console.log(window.getName)
打印结果:
大家有没有注意到上面说的是用var定义全局变量会保存到全局上下文window对象,并没有说用let、const定义。先看下面代码:
var num1=1
let num2=2
console.log(window.hasOwnProperty('num1')) //true
console.log(window.hasOwnProperty('num2')) //false
function getNum1(){
console.log(num1) //拿到全局变量num1
}
function getNum2(){
console.log(num2) //拿到全局变量num2
}
getNum1() //1
getNum2() //2
很明显,使用let 和const定义的变量并不会保存到全局上下文当中。但是在函数体内部(函数上下文)同样可以通过作用域链(下面会介绍)正常访问的。
上下文栈
在一个JS程序运行时必然会有多个上下文,那么就会有执行上下文的栈。那么一个程序首次运行时,首先压入栈中的就是全局上下文,随着代码的执行,其他的函数上下文会不断地入栈,出栈。如下图:
整个过程中 一旦函数被调用则会创建对应的函数上下文放并将其压出栈中,随着代码运行完毕函数上下文会被弹出上下文栈并销毁。此时上下文栈的控制权会被返还给之前的上下文(可能是函数上下文也可能是全局上下文)。不难发现全局上下文压入栈底后始终没有出栈,所有的函数上下文不断地进出栈,最后当程序结束(关闭网页)时全局上下文会出栈销毁。
作用域链
代码执行流每进入一个新的上下文,都会创建一个作用域链,用于搜索变量和函数。这个作用域链的范围或大或小,位于作用域链顶端的是代码正在执行的上下文的变量对象。代码在执行时的标识符解析是通过沿作用域链逐级搜索标识符完成的,说起来可能有点绕。看示例:
var a=1
function test2(){
let b=2
//在该函数内通过作用域链完成a和b值的交换
function test1(){
let c=b
//访问函数体外部的变量
b=a
a=c
}
//调用test1函数
test1()
}
//执行函数
test2()
上述代码涉及到三个上下文:全局上下文、两个局部的函数上下文。
全局上下文中包含a变量、test2函数;
test2函数上下文包含b变量、test1函数。可以访问父级上下文window对象身上的全局变量a;
test1函数上下文包含c变量,它可以访问父上下文test2中的b变量和window对象中的a变量。
看图说话:
每一个矩形表示不同的上下文,内部的子级上下文可以访问外部的父级上下文中的任何数据,而反之外部的父级上下文不可以访问内部的子级上下文。他们之间的连接是线性的、有序的。test1函数上下文的作用域链包含了三个变量对象,分别是test1的变量对象、test2的变量对象和window对象。同理test2函数上下文的作用域链包含了两个变量对象,分别是test2的变量对象和window对象。每一个上下文都会从自身的变量对象上搜索数据(代码运行时,自身的变量对象处于作用域链的顶端),搜索不到则会到上一级的变量对象中去搜索,以此类推。
总结
- 执行上下文分为全局上下文、函数上下文、块级上下文。
- 代码执行流没进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数。
- 函数上下文既可以访问它本身的变量,也可以访问包含它的上下文和全局上下文中的变量
- 全局上下文只可以访问自身的变量和函数