前言
在JavaScript中有一个作用域(Scope)的特性,作用域可以将代码层次分层,按照特定的规则隔离和充分利用变量。以便子作用域可以访问父作用域,从而形成作用域链,本文将以人话的方式来聊聊作用域和作用域链。
本文内容概要如下:
作用域(Scope)
1. 什么是作用域
作用域可以理解为对变量(包括函数、对象)的生命周期和可访问性,制定的一套规则。我们举一个简单的例子来说明:
var globalVariable = '全局变量'
function foo() {
var localVariable = "局部变量";
}
foo();
console.log(globalVariable); // 全局变量
console.log(localVariable); // Uncaught ReferenceError: localVariable is not defined
从上边例子可知,localVariable是在函数作用域内定义,在foo() 执行完后,这个变量就被销毁,在全局作用域内是访问不到的。
在ES6之前,没有 块级作用域这个概念,只有全局作用域、函数作用域和动态作用域。 ES6之后,我们可以通过 关键字let和const来创建 块级作用域。
2.全局作用域
直接在script标签内的JS代码,都是全局作用域。有以下几种情况可以拥有全局作用域:
-
最外层函数 和在最外层函数外面定义的变量拥有全局作用域
var globalVariable = '全局变量' function foo() { var localVariable = "局部变量"; function innerFoo() { console.log("innerFoo") } console.log(localVariable); } console.log(globalVariable); // 全局变量 console.log(localVariable); // Uncaught ReferenceError: localVariable is not defined foo(); // 局部变量 innerFoo(); // Uncaught ReferenceError: innerFoo is not defined // window console.log(window.globalVariable); // 全局变量 window.foo(); // 局部变量由最后两句代码可知,在全局作用域内,创建的变量都会变为window对象的属性,创建的变量都会变为window的方法保存
-
在函数或代码块{}内未定义的变量也拥有全局作用域
function foo() {
variable = "我仍然是全局变量";
var localVar = '局部变量'
}
foo() // 要先执行这个函数
console.log(variable); // 我仍然是全局变量
console.log(localVar); // Uncaught ReferenceError: localVar is not defined
3.函数作用域
在函数内部定义的变量,拥有局部作用域。调用函数时创建函数作用域,函数执行完毕,作用域销毁。在局部作用域内是封闭的,从外层作用域内无法访问函数内部的作用域。
function foo(){
var localVar = "函数内部作用域"
}
foo()
console.log(localVar); // Uncaught ReferenceError: localVar is not defined
4.块状作用域
ES6之后,使用let和const自变量,可以产生块状作用域,块状作用域可谓好处多多,自己去度娘一波
if(true) {
let blockVar = "块状作用域"
var variable = "HopeSun"
}
console.log(variable) // HopeSun
console.log(blockVar) // Uncaught ReferenceError: blockVar is not defined
可见,在块状作用域之外的地方是访问不了变量的
5.动态作用域
在执行阶段才能确定的作用域,叫动态作用域
var variable = 'HopeSun'
function foo() {
console.log(this.variable)
}
foo(); // HopeSun
foo.bind({variable: "variable"})() // variable
上述代码中bind把作用域的范围指向了{variable: "variable"},因此this指向的是当前作用域对象
词法作用域和动态作用域
作用域是在代码编写的时候决定,还是在在吗执行过程中决定?
var variable = 'HopeSun'
console.log(variable)
function foo() {
console.log(variable)
}
如上例中,在写代码时我们就知道 variable 是全局作用域,如果在函数内部定义的变量就是函数作用域,用专业术语就是:词法作用域。通俗讲就是:变量的定义域是定义时决定而不是在运行时决定,因此词法作用域取决于源码,通过静态分析可以确定,因此词法作用域也称为静态作用域。相反,只有执行阶段才能决定的变量,就是动态作用域。
作用域链
1.自由变量
首先认识一下自由变量
var a = 100
function foo() {
var b = 200
console.log(a) // a 是自由变量
console.log(b)
}
foo()
在上边代码中,当访问a 变量时,在当前作用域内并没有定义(对比变量 b)。当前作用域没有定义的变量,这称为 自由变量。自由变量的值如何得到,向父级作用域寻找(不严谨)。
2. 什么是作用域链
对于自由变量,当获取值时,会向父级作用域寻找,若父级作用域也没有,就会一层一层向上寻找,知道找到全局作用域,若无,就放弃。这用一层一层的关系,就是** 作用域链**。
var a = 100
function foo() {
var b = 200
function bar(){
console.log(a) // 自由变量
}
bar()
}
foo()
如上代码,当要获取a值时,寻找顺序:bar->foo->全局作用域
3. 自由变量的取值
关于自由变量的值,上文提到要到父作用域中取,其实有时候这种解释会产生歧义。
var x = 10
function foo() {
console.log(x)
}
function show(fun) {
var x = 20;
(function(){
fun(); // 10
})()
}
show(foo);
在foo函数中,取自由变量x的值时,要到哪个作用域中取?——要到创建foo函数的那个作用域中取,无论foo函数将在哪里调用。
因此,在自由变量获取值时,要到创建这个函数的作用域中取值,这里强调的是“创建”,而不是“调用”