0%

JS中的作用域和作用域链

一、作用域(Scope)

作用域表示变量或函数起作用的区域,作用域决定了代码区块中变量和其他资源的可见性(可访问性)。

作用域也指变量的生命周期(一个变量在哪些范围内保持一定值)。

作用域是可访问变量的集合。

作用域最大的用处就是隔离变量。

二、作用域分类

作用域分为全局作用域函数作用域和ES6中的块级作用域

全局作用域:

在代码中任何地方都能访问到的对象拥有全局作用域。

  • 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
  • 所有末定义直接赋值的变量自动声明为拥有全局作用域
  • 所有window对象的属性拥有全局作用域,如window.name、window.location等

全局作用域的弊端:全局变量过多的情况下会污染全局命名空间, 容易引起命名冲突。

函数作用域:

函数作用域是指声明在函数内部的变量,和全局作用域相反,局部作用域一般只在固定的代码片段内可访问到,最常见的如函数内部。

作用域是分层的,内层作用域可以访问外层作用域的变量,反之则不行。

1
2
3
4
5
6
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 200);
};
// 5 5 5 5 5

块级作用域:

使用let和const声明的变量在所在的代码块内(使用花括号{}包裹的区域)有效,即是块级作用域。

块级作用域内的变量不会提升到代码块顶部。let或const 声明的变量不会被提升到当前代码块的顶部,因此需要手动将 let或const 声明放置到顶部,以便让变量在整个代码块内部可用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for(let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 200);
};
// 0 1 2 3 4

for(var i = 0; i < 5; i++) {
const a = i
setTimeout(function() {
console.log(a);
}, 200);
};
// 0 1 2 3 4

三、作用域和执行上下文

JavaScript属于解释型语言。

JavaScript的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:

解释阶段:词法分析,语法分析,确定作用域

执行阶段:创建执行上下文,执行函数代码,垃圾回收

JavaScript解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定。

执行上下文是函数执行之前创建的。执行上下文最明显的就是this的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是: 执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变

一个作用域下可能包含若干个上下文环境。有可能从来没有过上下文环境(函数从来就没有被调用过);有可能有过,现在函数被调用完毕后,上下文环境被销毁了;有可能同时存在一个或多个(闭包)。同一个作用域下,不同的调用会产生不同的执行上下文环境,继而产生不同的变量的值

四、作用域链

执行环境:每个函数都有自己的执行环境。当执行流进入一个函数时(即调用该函数),函数的环境就会被推入一个环境栈中。而在函数执行之后,将其环境弹出栈,把控制权返回给之前的执行环境。

作用域链:当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的第一个变量对象,始终都是当前执行的代码所在环境的变量对象。作用域链中的下一个变量对象来自包含(外部)环境,再下一个变量对象则来自下一个包含环境。这样,一致延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。

即代码不仅仅可以访问当前的作用域的变量,对于嵌套的父级作用域中的变量也可以访问。

作用域链的作用主要用于查找标识符,当作用域需要查询变量的时候会沿着作用域链依次查找,如果找到标识符就会停止搜索,否则将会沿着作用域链依次向后查找,直到作用域链的结尾。

原型链是用于查找引用类型的属性,查找属性会沿着原型链依次进行,如果找到该属性会停止搜索并做相应的操作,否则将会沿着原型链依次查找直到结尾。