一、执行上下文
1-1 什么是执行上下文
执行上下文就是JavaScript代码被解析和执行时所在环境的抽象概念,JavaScript中运行任何的代码都是在执行上下文中运行
1-2 执行上下文类型
- 全局执行上下文:不在函数中的代码都位于全局执行上下文中,一个程序中只能存在一个全局执行上下文,全局执行上下文会创建一个全局对象(在浏览器中就是window对象),然后将this指针指向这个全局对象
- 函数执行上下文:每次函数被调用时,都会为该函数创建一个新的执行上下文,一个程序中可以存在任意数量的函数执行上下文
- Eval函数执行上下文
1-3 变量对象
- 变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明
- 在全局上下文中,变量对象就是全局对象
- 在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。活动对象和变量对象其实是一个东西,只是变量对象是规范上的或者说是引擎实现上的,不可在 JavaScript 环境中访问,只有到当进入一个执行上下文中,这个执行上下文的变量对象才会被激活,所以才叫 activation object 呐,而只有被激活的变量对象,也就是活动对象上的各种属性才能被访问。活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象
1-4 执行上下文生命周期
执行上下文的生命周期包括三个阶段:创建阶段 -> 执行阶段 -> 回收阶段
创建阶段:
- 函数执行上下文
① 初始化函数的参数 arguments ,提升函数声明和变量声明
② 创建作用域链(作用域链是在创建变量对象之后创建的。因为作用域链是用于解析变量,当被要求解析变量时,JavaScript始终从代码嵌套的最内层开始,如果最内层没有找到变量,就会跳转到上一级的父作用域中查找,直到找到该变量)
③ 确定 this 指向
- 全局执行上下文
在一段JS脚本执行之前,会先解析代码(所以说 JS 是解释执行的脚本语言),解析的时候会先创建一个全局执行上下文环境,然后提升函数声明和变量声明。变量先赋值为undefined,函数则先声明好,这一步做完了,然后开始正式执行程序。
执行阶段:执行变量赋值、代码执行
回收阶段:执行上下文出栈等虚拟机回收执行上下文
二、变量提升
参考《变量提升》
三、确定this指向
概念:this 的值是在函数执行的时候才能确认,定义的时候不能确认。因为this是执行上下文环境的一部分,而执行上下文需要在代码执行之前确定,而不是定义的时候
3-1 四种基本情况
情况一:直接调用
function foo() {
console.log(this)
}
foo() // 非严格模式 window 严格模式 undefined
// 对于直接调用 foo 来说,不管 foo 函数被放在了什么地方,this 一定是 window 或 undefined
情况二:定义在对象中的函数被调用
let obj = {
name:'hzy'
foo:function(){
console.log(this.name)
}、
}
obj.foo() //hzy
// 函数的定义位置不影响其this指向,this指向只和调用函数的对象有关。
let obj = { name:'hzy' }
function foo(){
console.log(this.name)
}
obj.foo = foo
obj.foo() //hzy
情况三:构造函数中的this
function Foo(){
this.name = 'hzy';
}
let f = new Foo()
console.log(f.name) // 构造函数中的this与被实例化的新对象绑定。
情况四:箭头函数
let p = {
a: function () {
var obj = {
b: () => {console.log(this)},
}
obj.b()
}
}
p.a() // {a: ƒ} 函数执行上下文的this值
// 箭头函数不绑定this, 它会捕获其定义时的位置上下文的this值, 作为自己的this值
3-2 this指向案例
// 案例1:setTimeout & setInterval 延时器中的普通回调函数的this,指向window
function Person(){
this.age = 0
setTimeout(function(){
console.log(this)
},3000)
}
const p = new Person() //window
// 案例2:
function Person(){
this.age = 0
setTimeout(()=>{console.log(this)},3000)
// 箭头函数捕获其定义时的位置上下文的this值,这里的上下文是构造函数Person
// 构造函数中的this与被实例化的新对象绑定
}
const p = new Person() //Person{age:0}
四、执行上下文栈
每次调用函数创建一个新的执行上下文,JavaScript引擎创建了执行上下文栈来管理执行上下文,可以把执行上下文栈认为是一个存储函数调用的栈结构,遵循先进后出的原则。
例子一:
上述流程图几个重要关键点
① JavaScript 执行在单线程上,所有的代码都是排队执行
② 一开始浏览器执行全局的代码时,首先创建全局的执行上下文,压入执行栈的顶部
③ 每当执行一个函数就会创建函数的执行上下文,并且把它压入执行栈的顶部;当前函数执行完成后,当前函数的执行上下文出栈,并等待垃圾回收
④ 浏览器的 JS 执行引擎总是访问栈顶的执行上下文
⑤ 全局上下文只有唯一的一个,它在浏览器关闭时出栈
例子二:
var color = "blue";
function changeColor() {
var anotherColor = "red";
function swapColors() {
var tempColor = anotherColor;
anotherColor = color;
color = tempColor;
}
swapColors();
}
changeColor();