作用域与作用域链

什么是作用域和作用域链?

作用域(scope):简单来说作用域就是变量能够生效的一个范围,一旦超过了这个范围变量就无法访问

作用域链(Scope Chain):访问变量的一个查找过程,首先会在当前的作用域进行查找,查找不到就会返回到父级作用域来查找,若是依旧没有找到,就会依次逐级向上查找,直到全局作用域,变量访问的原则遵循就近原则

作用域的类型:

  1. 全局作用域
  2. 函数作用域
  3. 块级作用域

函数作用域

foo函数内声明了一个名字叫Jack ,在函数内访问打印,没有问题,但是在函数外无法访问到,name变量的生效范围只有函数的{ } 内这么大一块的范围

1
2
3
4
5
6
function foo() {
let name ='jack'
console.log(name); //jack
}
foo()
console.log(name);//报错

块级作用域

块级作用域可通过ES6新增命令letconst声明,所声明的变量在指定块的作用域外无法被访问。块级作用域在如下情况被创建:

  1. 在一个函数内部
  2. 在一个代码块(由一对花括号包裹)内部
1
2
3
4
for(let i = 0;i<5;i++) {
console.log(i);//0,1,2,3,4
}
console.log(i);//Uncaught ReferenceError: i is not defined

全局作用域

声明在<script>标签和js文件最外层的就是全局作用域,使用var声明的变量也是属于全局作用域,无论在哪里都可以被访问到。

这里在全局声明了一个变量 a 赋值为了10,在全局和函数内都可以被访问到

1
2
3
4
5
6
7
let a = 10
console.log(a);//10

function foo() {
console.log(a);//10
}
foo()

所有window对象的属性拥有全局作用域

一般情况下,window对象的内置属性都拥有全局作用域,例如window.namewindow.locationwindow.top等等。

全局作用域有个弊端:如果我们写了很多行 JS 代码,变量定义都没有用函数包括,那么它们就全部都在全局作用域中。这样就会 污染全局命名空间, 容易引起命名冲突。

值得注意的是若是变量没有声明直接进行赋值也属于全局变量,不推荐

1
2
3
4
5
6
function foo() {
age = 20
console.log(age);//20
}
foo()
console.log(age);//20

window对象动态添加的属性默认也是属于全局的,同样不推荐

作用域链

1
2
3
4
5
6
7
8
9
10
11
12
let foo = 'foo';
function fo() {
let one = 'bac';
// 打印 'one'
console.log(one);
// 打印 'foo'
console.log(foo);
number = 42;
console.log(number); // 打印 42
}
fo();

当函数fo()被调用,Javascript引擎首先在当前作用域下寻找变量bac,然后寻找foo变量但发现在当前作用域下找不到,然后继续在外部作用域寻找找到了它(这里是在全局作用域找到的)。

然后将42赋值给变量numberJavascript引擎会在当前作用域以及外部作用域下一步步寻找number变量(没找到)。

如果是在非严格模式下,引擎会创建一个number的全局变量并把42赋值给它。但如果是严格模式下就会报错了。

结论:当使用一个变量的时候,Javascript引擎会循着作用域链一层一层往上找该变量,直到找到该变量为止。

小结

作用域:

  • 作用域是指变量、函数和对象的可访问范围。
  • JavaScript中的作用域分为全局作用域和局部作用域。
  • 全局作用域中定义的变量可以在整个程序中访问,称为全局变量。
  • 局部作用域由函数创建,在函数内部定义的变量只能在函数内部访问,称为局部变量或函数作用域变量。

作用域链:

  • 作用域链是一种变量查找机制,用于确定在特定作用域中访问变量时的查找顺序。
  • 当访问一个变量时,JavaScript引擎首先在当前作用域中查找该变量,如果找到了则直接使用。
  • 如果在当前作用域中没有找到该变量,JavaScript引擎将会沿着作用域链向上一级作用域继续查找,直到找到该变量或者抵达全局作用域。
  • 作用域链的尽头是全局作用域,如果在所有的嵌套作用域中都找不到该变量,JavaScript引擎将抛出 ReferenceError