# 一、作用域
作用域,即变量(变量作用域又称上下文)和函数生效(能被访问)的区域或集合
换句话说,作用域决定了代码区块中变量和其他资源的可见性
举个例子
function myFunction() {
let inVariable = "函数内部变量";
}
myFunction();//要先执行这个函数,否则根本不知道里面是啥
console.log(inVariable); // Uncaught ReferenceError: inVariable is not defined
2
3
4
5
上述例子中,函数myFunction
内部创建一个inVariable
变量,当我们在全局访问这个变量的时候,系统会报错。
这就说明我们在全局是无法获取到(闭包除外)函数内部的变量。
我们一般将作用域分成:
- 全局作用域
- 局部作用域
- 块级作用域
# 1.1、全局作用域
任何不在函数中或是大括号中声明的变量,都是在全局作用域下,全局作用域下声明的变量可以在程序的任意位置访问。
- 在 Web 浏览器中,全局作用域被认为是
window
对象,因此所有全局变量和函数都是作为window
对象的属性和方法创建的。 - 在 Node环境中,全局作用域是
global
对象。 - 所有未定义直接赋值的变量自动声明为全局作用域。
- 全局作用域有很大的弊端,过多的全局作用域变量会污染全局命名空间,容易引起命名冲突。
// 全局变量
var greeting = 'Hello World!';
function greet() {
console.log(greeting);
}
// 打印 'Hello World!'
greet();
2
3
4
5
6
7
# 1.2、函数作用域
函数作用域也叫局部作用域,如果一个变量是在函数内部声明的它就在一个函数作用域下面。
- 函数作用域声明在函数内部的变零,一般只有固定的代码片段可以访问到。
- 作用域是分层的,内层作用域可以访问外层作用域,反之不行。
function greet() {
var greeting = 'Hello World!';
console.log(greeting);
}
// 打印 'Hello World!'
greet();
// 报错: Uncaught ReferenceError: greeting is not defined
console.log(greeting);
2
3
4
5
6
7
8
可见上述代码中在函数内部声明的变量或函数,在函数外部是无法访问的,这说明在函数内部定义的变量或者方法只是函数作用域
# 1.3、块级作用域
ES6引入了let
和const
关键字,和var
关键字不同,在大括号中使用let
和const
声明的变量存在于块级作用域中。在大括号之外不能访问这些变量。
块级作用域在如下情况被创建:
在一个函数内部
在一个代码块(由一对花括号包裹)内部
块级作用域有以下几个特点:
- let和const声明的变量不会有变量提升,也不可以重复声明
- 在循环中比较适合绑定块级作用域,这样就可以把声明的计数器变量限制在循环内部
{
// 块级作用域中的变量
let greeting = 'Hello World!';
var lang = 'English';
console.log(greeting); // Prints 'Hello World!'
}
// 变量 'English'
console.log(lang);
// 报错:Uncaught ReferenceError: greeting is not defined
console.log(greeting);
2
3
4
5
6
7
8
9
10
# 二、词法作用域
词法作用域,又叫静态作用域,变量被创建时就确定好了,而非执行阶段确定的。也就是说我们写好代码时它的作用域就确定了,JavaScript
遵循的就是词法作用域。
var a = 2;
function foo(){
console.log(a)//2
}
function bar(){
var a = 3;
foo();
}
bar()
2
3
4
5
6
7
8
9
上述代码改变成一张图:
由于JavaScript
遵循词法作用域,相同层级的 foo
和 bar
就没有办法访问到彼此块作用域中的变量,所以输出2
# 三、作用域链
当在Javascript
中使用一个变量的时候,首先Javascript
引擎会尝试在当前作用域下去寻找该变量,如果没找到,再到它的上层作用域寻找,以此类推直到找到该变量或是已经到了全局作用域。如果在全局作用域里仍然找不到该变量,它就会在全局范围内隐式声明该变量(非严格模式下)或是直接报错。
作用域链的本质上是一个指向变量对象的指针列表。变量对象是一个包含了执行环境中所有变量和函数的对象。作用域链的前端始终都是当前执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最后一个对象。
下面代码演示下:
var sex = '男';
function person() {
var name = '张三';
function student() {
var age = 18;
console.log(name); // 张三
console.log(sex); // 男
}
student();
console.log(age); // Uncaught ReferenceError: age is not defined
}
person();
2
3
4
5
6
7
8
9
10
11
12
上述代码主要主要做了以下工作:
student
函数内部属于最内层作用域,找不到name
,向上一层作用域person
函数内部找,找到了输出“张三”student
内部输出sex
时找不到,向上一层作用域person
函数找,还找不到继续向上一层找,即全局作用域,找到了输出“男”- 在
person
函数内部输出age
时找不到,向上一层作用域找,即全局作用域,还是找不到则报错