常见作用域分为 全局作用域 函数作用域
全局作用域
-
全局对象下的属性和方法
window.name window.location window.top ...
-
最外层声明的变量和方法
const foo = function() {} const str = 'string' const arr = [1,2,3] function bar() {}
-
非严格模式下,函数作用域中未定义,直接赋值的变量和方法
function foo() { bar = 20 // window.bar = 20 }
避免使用全局变量
- 无意修改,其它场景并不知晓
- 极易命名冲突
- 全局变量内存无法释放
tips
全局变量的声明有细微差异
-
var声明,挂载于全局window对象
var a = 100 console.log(window.a) // 100
-
let/const声明,挂载于Script对象下(Script对象无法直接访问,于作用域链中)
const b = 20 console.log(window.b) // undefined
函数作用域
函数声明或函数表达式中,花括号内范围为函数作用域,函数内声明的变量与方法,只能被下层作用域访问
function foo() {
var a = 20;
var b = 30;
}
foo();
function bar() {
return a + b;
}
bar(); // 因为作用域的限制,bar中无法访问到变量a,b,因此执行报错
注意 循环体内没有块级作用域,循环内定义(var)变量,在外部依然可以访问
作用域
作用域的范围信息,在预解析阶段已经确定(未执行前,未产生执行上下文时)
function foo() {
var a = 1;
function bar() {
console.log(a);
console.dir(bar)
}
foo()
块级作用域
利用函数作用域,模拟出块级作用域效果
(
function() {
var a = 1
}
)()
console.log(a) // a is not defind
变量声明
-
var声明,具备变量提升的特性
// 同名变量可以重复声明 var a = 10; var a = 20; // 声明的变量可以任意修改 var b = 30; b = 40; b = function() {} // 花括号不会约束 var 变量的作用范围 { var c = 50 } console.log(c) // 50
console.log(a) // 输出 undefined var a = 20 console.log(a) // 输出 20 ------------------- 相当于 var a = undeinfd console.log(a) a = 20 console.log(a)
-
let声明
- 被任何花括号约束,即存在块级作用域
- 不能重复声明
- 不会提前赋值(无变量提升)(实际存在,但没有赋值undefined)
- 存在暂时性死区,不能声明前访问
-
const声明
- 同上
- 基础数据类型,无法修改
- 引用类型,不能修改引用内存地址
-
优先使用const,不适应则使用let
作用域链
每一个函数都有一个 [[Scopes]] 属性,它是由一系列对象组成的数组。每个对象,都对应某一个父级作用域。它们是从对应的父级函数作用域中,收集到的当前函数作用域内会使用到的变量声明、函数声明、函数参数的集合。
function foo() {
var a = 10
function bar() {
console.log(a)
}
}
预解析首次只回解析最外层函数,检测到函数会被执行时,则对函数进一步进行全量解析,全量解析后再创建该函数的执行上下文,函数内变量/方法在[创建执行上下文]这一步定义,在执行的时候才是进行赋值,创建执行上下文时,会对函数内的函数进行预解析,其余步骤同上
预解析 =》 全量解析 =》 创建执行上下文(定义变量/方法)=》执行(变量/方法赋值)(执行时对函数内的函数进行预解析)
作用域链[[scope]]会进行优化,假如当前函数只使用了父级函数中的一个变量/方法,其作用域链中,该父级函数对应的作用域对象则只有该变量/方法,如果对某一父级函数内变量/方法完全没有使用,作用域链中则会直接去除该作用域 预解析和全量解析都会进行优化
inner.[[Scopes]] = [O(bar), O(foo), O(window)] => inner.[[Scopes]] = [O(bar), O(window)] // O(foo) = {}
假如O(foo)中没有一个对线,那它将会被优化
在当前函数中,要寻找到变量的值是从哪里来的,就首先会从当前执行上下文中查找,如果没有找到,则会去作用域链中查找。这里需要注意的是,作用域链本身就是存在于函数对象中的一个属性 [[Scopes]],因此不是一层一层的往上查找「这里经常理解有误」,该属性是在代码预解析阶段就已经确认好的。
完整作用域链
- Global 全局对象,不会做任何优化,会包含全局对象中的所有属性与方法
- **Script 对象 **在全局环境下,由 let 和 const 声明的变量对象
- **Closure 对象 **我们讨论比较多的闭包对象,嵌套函数生成,仅会保存当前作用域能够访问的变量属性
- **Local 对象 **以上的几种变量对象,都会存在于函数的 [[Scopes]]属性之中,因为他们都能够在函数解析时确认,而 Local 对象则不行,需要在函数的执行过程中才能确定,并且在执行过程中,该对象中的属性是随时会发生变化的,该对象除了会存储当前函数上下文中所有的变量与函数声明,还会额外记录this的指向
- Local当前函数内新声明的变量/方法
- Closure,父级函数作用域内的变量/方法 (因此包括自身?)
- Script const/let定义的变量
- Global 全局window对象