作用域和作用域链

作用域

作用域是 规定变量与函数可访问范围的一套规则

最常见的两种作用域,分别是全局作用域和函数作用域。

词法作用域

与词法作用域对立的是动态作用域,所有也可以成为静态作用域。因为我们前端领域没有动态作用域的概率,所以可以统称为作用域。

全局作用域

全局作用域中声明的代码,可以在任何地方被访问。以下三种情形属于全局作用域

  1. 在全局对象(window)下的属性与方法
  2. 在最外层声明的变量与方法
  3. 在非严格模式中,函数作用域中未申明直接赋值的属性和方法

我们应该尽量少将变量或方法定义为全局,因为

  1. 我们可能无意间修改了全局变量的值,但其他场景不知道
  2. 容易造成命名冲突
  3. 在应用程序的执行过程中,全局变量的内存无法被释放

知识体系关联:

css 没有作用域的概念,那么也就意味着,每一个组件的样式都有可能影响别的组件布局,因为我们常常会通过一些方式让当前组件具备方为约束,以达到作用域的效果

1
2
3
4
.sidebar .container {
}
.sidebar .content {
}

在 vue 中,也专门提供了 css 作用域的语法,以达到类似效果

1
2
3
4
5
<style scoped>
.example{
color: red;
}
</style>

会被编译为

1
2
3
4
5
6
7
8
9
<style>
.example[data-v-f3f3eg9] {
color: red;
}
</style>

<template>
<div class="example" data-v-f3f3eg9>hi</div>
</template>

有一个细节需要关注一下,在全局声明的变量还存在一些差异。
使用 var 声明的变量,会被挂载到 window 对象下
而使用 const/let 的变量,会被挂载到 script 对象下

函数作用域

函数表达式或函数声明让花括号具备作用域,我们称之为函数作用域。函数作用域中的变量与方法,只能在下层子作用域中被访问,不能被其他不相关的作用域访问。

1
2
3
4
5
6
7
var arr = [1, 2, 3, 4, 5];

for (var i = 0; i < arr.length; i++) {
console.log('do something by ', i);
}

console.log(i); // i == 5

这种方式并不会有作用域的限制,i 挂载在全局对象下,循环结束后,i 还能被访问。
pic.1708405619882

模拟块级作用域

在没有块级作用域之前,我们可以使用自执行函数模拟块级作用域

1
2
3
4
5
6
7
(function () {
for (var i = 0; i < arr.length; i++) {
console.log('do something by ', i);
}
})();

console.log(i); // i is not defined

块级作用域

es6 引入了块级作用域。使用letconst声明的变量,能被任何花括号约束

let

1
2
3
4
{
let a = 10;
}
console.log(a); // Uncaught ReferenceError: a is not defined
1
2
3
for (let i = 1; i <= 5; i++) {
console.log(i); // 1 2 3 4 5
}

不能重复声明

1
2
let a = 10;
let a = 20; // Uncaught SyntaxError: Identifier 'a' has already been declared

不能在声明前访问,因为存在暂时性死区

1
2
console.log(a); // Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 10;

const

const 与 let 差不多,唯一不同的是,const 声明的变量的值不能被修改。

如果是基础数据类型,则值不能被修改。

1
2
const a = 1;
a = 2; // Uncaught TypeError: Assignment to constant variable.

如果是引用数据类型,则地址不能被修改。

1
2
3
4
const a = { n: 1 };
// a = {} // 这样不行
a.n = 2; // 这样可以修改
console.log(a);

let 和 const 的使用原则是,优先使用 const,不能用 const 再用 let。

作用域链

在一个函数内,能够访问哪些变量与函数,都要有一个具体的方式体现出来。因此有了作用域链。

每一个函数都有一个 [[scopes]] 属性,它是由一系列父作用域对象组成的数组。

当一个函数要寻找变量的值是从哪来的,就首先会在当前执行上下文中查找,如果没有找到,就回去作用域链中查找。作用域链存在于函数对象的一个属性 [[scopes]],该属性在代码预解析阶段就已经确定了。

完整作用域链的组成部分

  • Global 全局对象
  • Script 对象:全局环境下 let/const 声明的变量对象(变量和函数)
  • Closure 对象:闭包对象,嵌套函数生成
  • Local 对象:可称为活动对象。只有这个对象是在函数执行过程中才会确定,其它在函数声明是就确定了。local 对象会记录当前函数上下文的变量与函数声明,还会额外记录 this
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×