什么是执行上下文(
Execution Context)和作用域链(Scope Chain)?
执行上下文(Execution Context)
1:执行上下文是JS代码执行时所需要的环境。它包含了变量、函数声明、this 的值等信息。可以把它想象成一个盒子,这个盒子里面装着当前代码执行所需要的所有东西。
2:JS中执行执行上下文分为三种
全局执行上下文:不在函数内部的代码处于全局环境,一个程序只有一个全局执行上下文,全局环境会生成全局对象,在浏览器中是
window,此时this指向该全局对象;在 Node.js 环境中全局对象是global。函数执行上下文:每当一个函数被调用时, 都会为这个函数创建一个新的函数执行上下文,并被推入执行栈中(
入栈),当函数执行完成出栈(出栈),当前函数执行上下文会销毁(闭包除外)Eval函数执行上下文(
不推荐):eval函数参数指定的代码会创建eval执行上下文压栈,不使用,有安全和性能问题。3:首先在了解执行上下文的时候,我们需要先了解执行栈。
执行栈(Execution Stack)
- 栈:栈(Stack)是一种数据结构,它遵循后进先出的原则。
- 执行栈:也称调用栈,用于跟踪JS代码的执行顺序。用来管理执行上下文。当JS代码开始执行时,它记录了函数的调用和返回顺序,JS引擎始终执行的是栈顶的上下文。
- 函数调用时,JS引擎就会为该函数创建一个新的函数执行上下文并
push(入栈)到当前执行栈的栈顶当栈顶函数运行结束,其对应的函数执行上下文就会从执行栈中pop(出栈)出去
执行上下文的创建
执行上下文在创建时,做了下面两件事:创建词法环境(
LexicalEnvironment),创建变量环境(VariableEnvironment)
网上对 this 绑定时间说法不一,查看 ECMAScript 原文可知:大概意思就是执行上下文是通过词法环境来确定
this的绑定。
词法环境
词法环境是 ECMAScript 的一种规范类型。它依据代码的词法结构,像 “字典” 一样记录标识符(变量名、函数名等)与具体变量或函数的关联,帮助JS引擎在执行代码时找到标识符对应的实际内容。
词法环境内部组成:
- 环境记录(
EnvironmentRecord):用于存储变量和函数声明。在不同的词法环境中,环境记录的内容有所不同。 - 外部引用(
outer):提供了访问父词法环境的能力。当在一个词法环境中无法找到某个标识符时,就会通过外部引用去父词法环境中查找。
词法环境类型区分:词法环境对应执行上下文,分为全局环境(GlobalEnvironment)和函数环境(FunctionEnvironment)。
let c = 3;
function gn() {
let d = 4;
function inner() {
let e = 5;
}
inner();
}
gn();
//全局词法环境
GlobalEnvironment: {
//词法环境
LexicalEnvironment: {
//环境记录
EnvironmentRecord: {
type: 'object',
c: 3,
gn: <function>
},
outer: <null>,
this: <globalObject>
}
}
//gn函数词法环境
FunctionEnvironmentForGn: {
LexicalEnvironment: {
EnvironmentRecord: {
//表示这是一个声明性的环境记录
//通常用于函数环境中存储局部变量和参数等。
type: 'declarative',
arguments: {length: 0},
d: <uninitialized>,
},
//表示函数gn的词法环境的外部引用是全局词法环境
outer: <GlobalEnvironment>,
this: <globalObject>
}
}
//inner函数词法环境
FunctionEnvironmentForInner: {
LexicalEnvironment: {
EnvironmentRecord: {
type: 'declarative',
arguments: {length: 0},
e: <uninitialized>,
},
//表示内部函数inner的词法环境的外部引用是函数gn的词法环境
outer: <FunctionEnvironmentForGn>,
this: <globalObject>
}
}
变量环境
变量环境本质是词法环境,只存var声明的变量,将它们初始化时赋值为undefined(实现变量提升)。
var c = 3;
let d = 4;
//全局词法环境
GlobalEnvironment: {
// 变量环境
VariableEnvironment: {
EnvironmentRecord: {
type: 'object',
c: <undefined>,
},
//全局词法环境,没有外部引用
outer: <null>,
this: <globalObject>
},
// 词法环境
LexicalEnvironment: {
EnvironmentRecord: {
type: 'object',
d: <uninitialized>,
},
outer: <null>,
this: <globalObject>
}
}
作用域
在JS中,作用域和执行上下文是两个不同但又紧密相关的概念
JavaScript 属于解释型语言,JavaScript 的执行分为:解释 和 执行 两个阶段
解释阶段:
1、
词法分析:把源代码拆成符号,像 “var a = 1;”,能识别出 “var” 是关键字、“a” 是变量名、“=” 是运算符、“1” 是字面量。2、
语法分析:基于词法分析,把符号组成抽象语法树,检查语法结构,如函数括号是否匹配、语句有无分号。3、
作用域规则确定:确定变量和函数的作用域。函数定义的时候,它的作用域就确定了,它能访问哪些变量和函数也确定了,作用域在编写代码时就已经确定好,执行的时候不变。执行阶段:
1、
创建执行上下文:函数被调用的时候,就会给这个函数创建一个执行上下文。这里面有函数执行需要的各种信息,像变量环境、词法环境、this值这些。2、
执行函数代码:按代码从上到下的顺序执行函数里的语句。在这个过程中,会根据之前定好的作用域规则来找变量和函数,然后做计算、赋值之类的操作。3、
垃圾回收:函数执行完了,那些没用的内存空间就会被回收,把资源释放出来。
作用域与执行上下文的区别
确定时间不同:
1、作用域在解释阶段确定,而执行上下文在执行阶段创建。
2、这意味着函数的作用域在函数定义的时候就已经固定了,无论在哪里调用这个函数,它的作用域都不会改变。例如:
function outer() {
var outerVar = 'I am outer';
function inner() {
console.log(outerVar);
}
return inner;
}
var newFunction = outer();
//仍然可以访问outerVar,因为inner的作用域在定义时就包含了outer的作用域
newFunction();
this 的值在执行上下文中的确定
一般函数:
this的值是在函数执行前,创建函数执行上下文时被赋值的。this的值取决于函数的调用方式,跟调用该方法的对象有关。谁调用函数,this就指向谁。箭头函数:箭头函数中的
this始终指向该函数定义时所在作用域指向的对象。由于作用域不变,所以箭头函数的this指向在定义时就固定了,不会改变。
var obj = {
method: function() {
console.log(this);
}
};
obj.method(); //this指向 obj
var anotherFunction = obj.method;
anotherFunction(); //this指向全局对象(在浏览器中是window)
var obj = {
normalFunction: function() {
console.log(this);
},
arrowFunction: () => {
console.log(this);
}
};
obj.normalFunction(); //this指向obj
obj.arrowFunction(); //this指向obj定义时所在的作用域指向的对象,通常是全局对象(在浏览器中是window)
作用域链(Scope Chain)
1:执行上下文中包含一个额外的属性,该属性指向创建该执行上下文的函数本身
2:每个函数在创建的时候,会有一个隐藏属性scope,该函数指向创建他的执行上下文
代码中全局作用域有变量 a 和函数 func,func的函数作用域有变量 b。fn 能打印全局变量 a,a 对于 fn 的函数作用域是自由变量,这是借助作用域链实现的从父级作用域取值。
var a = 1;
function func() {
var b = 2;
console.log(a, b); // 1, 2
}
func();
再看一个例子,下面的代码最终打印的结果是 1,因为对于 func1 来说,他的父级作用域是全局作用域,而不是 func2
var a = 1;
function func1() {
console.log(a);
}
function func2() {
var a = 2;
func1();
}
func2();