一、作用域(Scope)
1、什么叫作用域
作用域就是一个变量一个函数在什么范围内可用
2、作用域的作用
隔离变量,不同作用域下的同名变量不会冲突
3、词法作用域
词法作用域也叫静态作用域,意思是变量或函数的作用域在变量声明时确定
示例:
function test(fn) {
const a = 1
fn()
}
const a = 2
function fn() {
console.log(a)
}
test(fn) // 2
声明fn时,a变量为2,调用fn时,a变量为1,fn中a的值为2。因为js中采用的是词法作用域。
4、作用域的分类:
类型 | 概念 | 特点 |
---|---|---|
全局作用域 | 声明在最外层的变量或函数 | 全局都可以使用 |
局部作用域 | 也叫函数作用域,在函数内创建的变量 | 只能在函数内使用,函数外是无法访问的(闭包会可能会导致AO被保持,这是一个特殊情况) |
块级作用域 | ES6的let和const创建的变量具有块级作用域 | ①只能在当前 {} 中访问 ②变量不会提示 ③不能反复声明 |
动态作用域 | call/apply/bind改变this指向 | 动态地改变this的指向 |
注意:
暗示全局变量:未定义直接赋值的变量,实质上是为window添加了一个属性,它也是一种定义全局作用域的方式
function fn() {
a = 1
}
fn()
console.log(a)
5、作用域与执行上下文(GO/AO)的区别
js属于解释型语言,js的执行分为:解释和执行两个阶段
- 解释阶段:词法分析、语法分析、作用域规则确定
- 执行阶段:创建执行上下文、执行函数代码、垃圾回收
执行阶段是在解释阶段没有问题的前提下开始,所以执行上下文是在作用域的范围内创建的,它们不是一回事,并且有着以下的区别:
区别 | 作用域 | 执行上下文 |
---|---|---|
1. 确定时机 | 解释阶段:在函数声明时作用域就确定了 | 执行阶段:①GO在全局作用域确定后创建②AO在函数调用时,执行函数体代码前创建 |
2. 是否可变 | js采用词法作用域,也叫静态作用域,一旦确定,不会改变 | 预编译期间,GO/AO随时可变(this的指向是调用时决定的) |
3. 销毁时机 | 不会销毁 | 函数调用完释放AO,页面关闭时释放GO |
6、自由变量
在函数中,需要跨越当前作用域才能访问到的变量,叫做自由变量
var a = 10
function fn() {
console.log(a) // a就是自由变量
}
fn()
二、作用域链([[Scopes]])
1、什么叫作用域链
函数在声明
时,系统生成的一个隐式属性([[Scopes]]),它是一个存储作用域链的容器,里面存放的是GO和AO对象,用户访问不到,由js引擎来访问。
查找变量时,从[[Scopes]]顶端开始向下查找,一直找到GO,还是没找到就报错(Uncaught ReferenceError: a is not defined),这种一层一层的关系,就是作用域链。
2、函数在声明到执行过程中,[[Scopes]]的变化
- 函数在声明时,已经生成了作用域和作用域链,GO被存储在第0位;函数在调用时(前一刻),生成自己的AO排在顶端,将GO挤到第1位
- 函数在声明时,它的作用域链就是上级函数调用时的作用域链;当函数调用时(前一刻),比上级多一个自己的AO,并且永远都是自己的AO排在第0位,旧的AO和GO依次向下排列,查找时从作用域链顶端向下查找
- 函数执行完,对应的AO就被销毁,所以多次调用函数会多次生成AO,AO和AO之间没有关系
示例:
function a() {
function b() {
var b = 2
}
var a = 1
b()
}
var c = 3
a()
console.dir(a)
①当函数a被声明时,系统生成[[Scopes]]
属性,[[Scopes]]
保存该函数的作用域链,该作用域链的第0位存储当前环境下的全局执行期上下文GO,GO里存储全局下的所有对象,其中包含函数a和全局变量c。每个函数的作用域链中至少有一个GO,所以在函数中可以访问全局变量
GO: {
c: undefined,
a: f a(){}
}
②当函数a执行时(前一刻),作用域链的顶端(第0位)存储a函数生成的函数执行期上下文AO,同时第1位存储GO。查找变量时,在函数a的作用域链([[Scopes]]
)中从顶端向下查找。通过打断点,当函数a执行完Local(函数a的AO)就被销毁了,下一次执行函数a时再创建它的AO
AO: {
a: undefined,
b: f b(){}
}
GO: {
c: 3,
a: f a(){}
}
为什么说第0位变成AO,而GO被挤到第1位了,可以看下调用栈
③当函数b被定义时,是在函数a的环境下,所以函数b的作用域链就是函数a在调用时(前一刻)的作用域链。
AO: {
a: undefined,
b: f b(){}
}
GO: {
c: 3,
a: f a(){}
}
④当函数b执行时(前一刻),在作用域链([[Scopes]]
)中第0位存储b的AO,a的AO和GO依次向下排列
bAO: {
b: 2
}
aAO: {
a: 1,
b: f b(){}
}
GO: {
c: 3,
a: f a(){}
}
⑤函数b执行完,bAO释放;函数a执行完,aAO释放