持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第11天,点击查看活动详情
JavaScript作用域
作用域(Scope)
作用域的基本定义
JavaScript权威指南p56
-
一个变量的作用域(Scope)是程序源代码中定义这个变量的区域。
-
Scope 就是范围的意思,那么变量的作用域就是变量的“范围”,字面意思说就是变量能够在哪些“范围”来起作用(或者被访问)。
-
作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
这里使用书上的例子
var scope = "global"; // 声明一个全局变量
function checkScope(){
var scope = 'local'; // 声明一个同名的局部变量
return scope // 返回局部变量的值,而不是全局变量的值
}
console.log(checkScope()); // local
复制代码
为了解读上面的代码就得抛出全局作用域和局部作用域的概念了。
全局作用域和局部作用域
函数外部定义的scope变量的作用域就是全局作用域,在JavaScript代码中的任何地方都是有定义的。函数内部声明的scope变量的作用域就是局部作用域,在函数内部才有定义。
这里checkScope函数返回scope首先在函数作用域查找scope变量,如果没有在去全局作用域去查找。自然先找到局部作用域(函数作用域)下的scope变量。
静态作用域与动态作用域
JavaScript采用的是静态作用域。
静态作用域(词法作用域)和动态作用域的核心区别就是,函数作用域是在什么时候确定的。
静态作用域是在函数定义的时候就确定了,由字面意思就是,一旦函数定义了,函数的作用域就确定无法改变了。动态作用域是函数调用的时候才确定。由字面意思来说就是函数调用位置不同,函数作用域也就不同。
var value = 1;
function foo() {
console.log(value);
}
function bar() {
var value = 2;
foo();
}
bar(); // 1
// 结果是 ???
复制代码
假设JavaScript采用静态作用域,让我们分析下执行过程:
执行 foo 函数,先从 foo 函数内部查找是否有局部变量 value,如果没有,就根据书写的位置,查找上面一层的代码,也就是 value 等于 1,所以结果会打印 1。
假设JavaScript采用动态作用域,让我们分析下执行过程:
执行 foo 函数,依然是从 foo 函数内部查找是否有局部变量 value。如果没有,就从调用函数的作用域,也就是 bar 函数内部查找 value 变量,所以结果会打印 2。
前面我们已经说了,JavaScript采用的是静态作用域,所以这个例子的结果是 1。
看下面2个例子
f()函数定义的位置不同最终返回的值也不同
var scope = "global scope";
function checkscope(){
var scope = "local scope";
function f(){
return scope;
}
return f();
}
console.log(checkscope()); // local scope
复制代码
这里函数f()定义在函数checkscope()函数中,当执行函数f()时,返回scope时自然去定义函数的位置找到,先从 f()函数内部查找是否有局部变量 scope,如果没有,就根据书写的位置,查找上面一层的代码,也就是 scope = "local scope"
var scope = "global scope";
function f(){
return scope;
}
function checkscope(){
var scope = "local scope";
return f();
}
console.log(checkscope()); // global scope
复制代码
这里函数f()定义在函数checkscope()函数中,当执行函数f()时,返回scope时自然去定义函数的位置找到,先从 f()函数内部查找是否有局部变量 scope,如果没有,就根据书写的位置,查找上面一层的代码,也就是 scope = "global scope"
这2个例子进一步证明JavaScript使用的是静态作用域。
执行上下文栈
可执行代码
JavaScript有3种可以执行代码:全局代码、函数代码、eval代码。
在执行这些可执行代码的时候会进行“准备工作”,也就是创建执行上下文。
为了方便管理众多的执行上下文,就设计了一个执行上下文的栈。
执行上下文的推入弹出
首先执行全局代码,创建一个全局执行上下文,并推入执行上下文栈中,只有当整个应用程序结束的时候,ECStack 才会被清空,所以程序结束之前, ECStack 最底部永远有个全局执行上下文。
对应执行一个函数时,会创建一个函数执行上下文,并将这个函数执行上下文推入执行上下文栈中,等函数执行完毕之后就会弹出栈外。
变量对象
执行函数时会创建一个执行上下文,那么这个执行上下文就有3个属性(变量对象(Variable object,VO)、作用域链(Scope chain)、this),其中一个就是变量对象。
变量对象概念
变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
全局上下文的变量对象
全局上下文的变量对象是全局对象
全局对象
全局对象是预定义的对象,作为 JavaScript 的全局函数和全局属性的占位符。通过使用全局对象,可以访问所有其他所有预定义的对象、函数和属性。
函数上下文的变量对象
在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。
活动对象和变量对象是一个东西,在进入一个执行上下文时,这个执行上下文对应的变量对象被激活,就改名叫活动对象了,也就可以访问活动对象身上的属性了。
活动对象创建时间
活动对象是在进入函数上下文时刻被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。
执行过程
1.进入执行上下文(变量对象的初始化)
进入执行上下文的时候,会创建一个活动对象,根据三大规则进行初始化。
2.执行代码(变量对象的加工)
当执行代码的时候,会顺序执行代码,根据代码,修改变量对象的值。
变量对象内容
- 函数的所有形参 (如果是函数上下文)
由名称和对应值组成的一个变量对象的属性被创建
没有实参,属性值设为 undefined
- 函数声明
由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建
如果变量对象已经存在相同名称的属性,则完全替换这个属性
- 变量声明
由名称和对应值(undefined)组成一个变量对象的属性被创建;
如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性
作用域链
作用域链是如何创建和变化
函数定义时
函数定义时,会将不完整作用域链保存在函数内置属性[[scope]]内
当函数创建的时候,就会保存所有父变量对象到其中,你可以理解 [[scope]] 就是所有父变量对象的层级链,但是注意:[[scope]] 并不代表完整的作用域链!
执行函数时(函数激活,进入执行上下文那个时刻)
进入执行上下文,创建变量对象,并激活,进AO推进[[scope]]保存的作用域链的前端。
一个函数从定义到执行完毕,涉及执行上下文,变量对象,作用域链等等的过程
var scope = "global scope";
function checkscope(){
var scope2 = 'local scope';
return scope2;
}
checkscope();
复制代码
1.执行全局代码,创建一个全局执行上下文,并推入执行上下文栈中,全局执行上下文有一个属性是全局对象。
2.checkscope函数定义时,创建一个静态作用域,函数的内置属性[[scope]]会保存一个作用域链,里面保存了所有父级变量对象。
3.执行checkscope函数时,会创建一个执行上下文,将其推入执行上下文栈中。
4.不会立刻执行函数,会对执行上下文的属性进行加工和创建
一、将函数内置属性[[scope]]保存的不完整作用域链,作为作用域链的属性值保存到执行上下文身上。
二、创建一个变量对象并激活为活动对象,这里只是对活动对象的初始化。
三、在初始化之后,将活动对象推入作用域链前端。
复制代码
-
正式开始执行checkscope函数,顺序执行代码,根据代码,修改活动对象的值。
-
查找到 scope2 的值,返回后函数执行完毕数之后,将函数执行上下文弹出执行上下文栈,进行销毁。
简单描述上面的过程
-
执行全局代码,创建全局执行上下文,压入执行上下文栈,全局上下文保存了全局对象,
-
定义checkscope函数,确定静态作用域,内置属性[[scope]]被赋值不完整作用域链,
-
执行checkscope函数,创建函数执行上下文,推入执行上下文栈,将[[scope]]保存的作用域链保存到函数执行上下文上,创建初始化活动对象,将活动对象推入作用域链,
-
顺序执行代码,加工活动对象,
-
函数执行完毕,函数执行上下文弹出执行上下文栈,
注意:
-
函数执行上下文,活动对象都是在创建之后先压入对应的执行上下文栈,作用域链内,在进行一系列加工。
-
活动对象不会弹出作用域链,执行上下文会弹出栈。
-
定义函数时会保存不完整作用域链。
-
执行上下文的创建,活动对象的创建,作用域链的创建都是在执行可执行代码时创建的