作用域和作用域链
作用域
作用域是 规定变量与函数可访问范围的一套规则。
最常见的两种作用域,分别是全局作用域和函数作用域。
词法作用域
与词法作用域对立的是动态作用域,所有也可以成为静态作用域。因为我们前端领域没有动态作用域的概率,所以可以统称为作用域。
全局作用域
全局作用域中声明的代码,可以在任何地方被访问。以下三种情形属于全局作用域
- 在全局对象(window)下的属性与方法
- 在最外层声明的变量与方法
- 在非严格模式中,函数作用域中未申明直接赋值的属性和方法
我们应该尽量少将变量或方法定义为全局,因为
- 我们可能无意间修改了全局变量的值,但其他场景不知道
- 容易造成命名冲突
- 在应用程序的执行过程中,全局变量的内存无法被释放
知识体系关联:
css 没有作用域的概念,那么也就意味着,每一个组件的样式都有可能影响别的组件布局,因为我们常常会通过一些方式让当前组件具备方为约束,以达到作用域的效果
1 | .sidebar .container { |
在 vue 中,也专门提供了 css 作用域的语法,以达到类似效果
1 | <style scoped> |
会被编译为
1 | <style> |
有一个细节需要关注一下,在全局声明的变量还存在一些差异。
使用 var 声明的变量,会被挂载到 window 对象下
而使用 const/let 的变量,会被挂载到 script 对象下
函数作用域
函数表达式或函数声明让花括号具备作用域,我们称之为函数作用域。函数作用域中的变量与方法,只能在下层子作用域中被访问,不能被其他不相关的作用域访问。
1 | var arr = [1, 2, 3, 4, 5]; |
这种方式并不会有作用域的限制,i 挂载在全局对象下,循环结束后,i 还能被访问。
模拟块级作用域
在没有块级作用域之前,我们可以使用自执行函数模拟块级作用域
1 | (function () { |
块级作用域
es6 引入了块级作用域。使用let
和 const
声明的变量,能被任何花括号约束
let
1 | { |
1 | for (let i = 1; i <= 5; i++) { |
不能重复声明
1 | let a = 10; |
不能在声明前访问,因为存在暂时性死区
1 | console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization |
const
const 与 let 差不多,唯一不同的是,const 声明的变量的值不能被修改。
如果是基础数据类型,则值不能被修改。
1 | const a = 1; |
如果是引用数据类型,则地址不能被修改。
1 | const a = { n: 1 }; |
let 和 const 的使用原则是,优先使用 const,不能用 const 再用 let。
作用域链
在一个函数内,能够访问哪些变量与函数,都要有一个具体的方式体现出来。因此有了作用域链。
每一个函数都有一个 [[scopes]] 属性,它是由一系列父作用域对象组成的数组。
当一个函数要寻找变量的值是从哪来的,就首先会在当前执行上下文中查找,如果没有找到,就回去作用域链中查找。作用域链存在于函数对象的一个属性 [[scopes]],该属性在代码预解析阶段就已经确定了。
完整作用域链的组成部分
- Global 全局对象
- Script 对象:全局环境下 let/const 声明的变量对象(变量和函数)
- Closure 对象:闭包对象,嵌套函数生成
- Local 对象:可称为活动对象。只有这个对象是在函数执行过程中才会确定,其它在函数声明是就确定了。local 对象会记录当前函数上下文的变量与函数声明,还会额外记录 this