一、前言
Javascript是解释性语言,Javascript分为两个阶段:解释阶段和执行阶段
解释阶段:
词法分析
语法分析
作用域规则确定
执行阶段:
创建执行上下文
执行函数代码
垃圾回收
二、作用域
1.1 什么是作用域
作用域是js引擎根据标识符查找变量和函数的一套规则,它规定变量和函数的可使用范围。
1.2 作用域类型
-
全局作用域:在代码中任何地方都能访问到的变量拥有全局作用域。
(1) 最外层函数和在最外层函数外面定义的变量var outVariable = "我是最外层变量"; //最外层变量 function outFun() { //最外层函数 var inVariable = "内层变量"; function innerFun() { //内层函数 console.log(inVariable); } innerFun(); } console.log(outVariable); //我是最外层变量 outFun(); //内层变量 console.log(inVariable); //inVariable is not defined innerFun(); //innerFun is not defined
(2) 所有末定义直接赋值的变量
function outFun2() { variable = "未定义直接赋值的变量"; var inVariable2 = "内层变量2"; } outFun2();//要先执行这个函数,否则根本不知道里面是啥 console.log(variable); //未定义直接赋值的变量 console.log(inVariable2); //inVariable2 is not defined
(3) 所有window对象的属性拥有全局作用域
-
函数作用域:只在函数内部可以访问到的变量
var a = 10; // 全局 function func() { var b = 20;// 函数 } console.log(a); // 10 console.log(b); // error, b in not defined
二、作用域链
首先,我们要明确一点,作用域和作用域链是不同的。作用域是一套规则,作用域链是在代码执行过程中,会动态变化的一条索引路径。
2.1 谈谈[[scope]]
每个 javascript 函数都是一个对象,对象中有些属性我们可以访问,但有些不可以,这些属性仅供 javascript 引擎存取,[[scope]]就是其中一个。创建一个函数的时候,这个函数中的[[scope]]属性中就会包含所有上级变量对象在其中,这些变量对象以链式连接。你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
2.2 回顾执行上下文生命周期
- 创建阶段(创建执行上下文)
(1) 生成变量对象
创建arguments对象
检查函数声明
检查变量声明
(2) 建立作用域链
(3) 确定this指向 - 执行阶段(执行上下文中的代码)
(1) 变量赋值,函数引用,变量对象变为活跃对象
(2) 执行其他代码 - 执行完毕后出栈,等待垃圾回收机制回收
2.3 谈谈执行上下文
在函数调用的时候,会创建一个函数执行上下文,这个上下文定义了函数执行时的环境,函数每次调用时都会创建一个函数执行上下文,函数执行完毕后执行上下文被销毁。执行上下文会建立作用域链。
2.4 [[scope]]和执行上下文中的作用域链
[[scope]]是在函数定义时创建的,执行上下文是在函数调用时创建的,函数执行结束便会销毁。
2.5 什么是作用域链
这里我们用变量对象的观点来解释作用域链:
当查找变量的时候,会先从当前上下文的变量对象中查找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中查找,一直找到全局上下文的变量对象,也就是全局对象。这样由多个执行上下文的变量对象构成的链就叫做作用域链。
2.6 作用域链的建立和变化
举个例子:
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
执行过程如下:
1.checkscope 函数被创建,保存作用域链到 内部属性[[scope]]
checkscope.[[scope]] = [
globalContext.VO
];
2.执行 checkscope 函数,创建 checkscope 函数执行上下文,checkscope 函数执行上下文被压入执行上下文栈
ECStack = [
checkscopeContext,
globalContext
];
3.checkscope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[scope]]属性创建作用域链
checkscopeContext = {
Scope: checkscope.[[scope]],
}
4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: checkscope.[[scope]],
}
5.第三步:将活动对象压入 checkscope 作用域链顶端
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: undefined
},
Scope: [AO, [[Scope]]]
}
6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值
checkscopeContext = {
AO: {
arguments: {
length: 0
},
scope2: 'local scope'
},
Scope: [AO, [[Scope]]]
}
7.查找到 scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出
ECStack = [
globalContext
];
三、用词法环境解释作用域链
占位符