作用域和作用域链
什么是作用域?
作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
具体分类:
名称 | 说明 |
---|---|
window/global Scope | 全局作用域 |
function Scope | 函数作用域 |
Block Scope | 块作用域(ES6) |
eval Scope | eval作用域(已经弃用) |
示例代码:
// 全局作用域
var a = 'andy';
function test() {
// 局部作用域
var b = "Tim";
console.log(b);
}
test(); // 输出 'Tim'
console.log(a); // 输出 'andy'
if (true) {
// 这个 'if' 块语句没有创建一个块级作用域
// name 变量处于全局作用域,因为由var关键字声明
var name = 'Hammad';
// likes 变量处于块级作用域因为由let关键字声明
let likes = 'Coding';
// skills 变量处于块级作用域因为由const关键字声明
const skills = 'JavaScript and PHP';
}
console.log(Hammad); // 输出 'Hammad'
console.log(likes); // Uncaught ReferenceError: likes is not defined
console.log(skills); // Uncaught ReferenceError: skills is not defined
理解前提
- function的AO理解为“独立的仓库(作用域)”
- 原理:利用AO、GO来解决作用域、作用域链相关所产生的一切问题
- 函数也是一种对象类型,一种引用类型,有引用值
- 函数有属性:test.name test.length test.prototype
- 对象的有些属性是我们无法访问的,这些属性就是JS引擎内部固有的隐式属性(内部私有属性)
- AO:函数的执行期上下文
- GO:全局的执行期上下文
原理说明
(1)作用域:[[scope]](隐式属性)
- 1、函数创建时,生成的一个JS内部的隐式属性。
- 2、函数存储作用域链的容器,作用域链中存储AO、GO。
- 3、当函数执行完成以后,旧的AO就销毁,当重新执行时,会重新生成新的AO。所以,AO是一个即时的存储容器。
(2)作用域链
把AO、GO从上到下排列起来,形成链式关系就是作用域链(scope chain)。
执行过程
(1)示例代码
function a() {
function b() {
var b = 2;
}
var a = 1;
b();
}
var c = 3;
a();
(2)图解
当a函数被定义时:
当a函数被执行时(前一刻):
当b函数被定义时:
当b函数被执行时(前一刻):
当b函数被执行结束后:
回归b函数被定义时的状态:
当a函数被执行结束时:
回归a函数被定义时的状态:
(3)总结-作用域的基础过程
- 1、全局执行前一刻生成GO,同时函数声明已经定义
- 2、全局执行(赋值),因为赋值是在执行的时候触发的。比如函数表达式赋值
- 3、永远都是上级执行,下级被定义:全局执行的时候,全局函数已经被定义,全局函数执行的时候,内部的函数被定义
- 4、当函数被定义的时候,已经形成了作用域[[scope]]、作用域链(scope chain),并放入GO
- 5、当函数在执行的那一刻才会生成自己的AO
- 6、函数在被定义的时候拿的是上级的作用域环境
- 7、当函数执行完成以后,旧的AO就销毁,当重新执行时,会重新生成新的AO
(4)结论
- 1、每一个函数在的作用域上都有GO
- 2、函数的AO存在作用域链的最顶端
- 3、每个函数都有一个AO和GO,并且AO在GO的上面
示例代码二
function a () {
function b () {
function c () {
}
c();
}
b();
}
a();
执行过程分析:
作用域与执行上下文
许多开发人员经常混淆作用域和执行上下文的概念,误认为它们是相同的概念,但事实并非如此。
我们知道JavaScript属于解释型语言,JavaScript的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:
解释阶段:
- 词法分析
- 语法分析
- 作用域规则确定
执行阶段:
- 创建执行上下文
- 执行函数代码
- 垃圾回收
JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是this的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。
作用域和执行上下文之间最大的区别是:
执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值。
给大家推荐一个好用的BUG监控工具Fundebug,欢迎免费试用!