什么是作用域?
作用域是一套规则,用于确定在何处以及如何查询变量(标识符)。
作用域链
什么是作用域链
当一个函数或代码块嵌套在另一个函数或代码块中,就形成了作用域链。这么说可能不够直观,参考《你不知道的JavaScript》的插图如下
整个建筑表示嵌套的作用域链,第一层表示当前作用域,顶层表示全局作用域。
如何在作用域链中查找变量
要准确查找作用域中的变量首先必须明白的三点:
- 函数声明与变量声明都会提升,但是函数声明会首先被提升,然后才是变量声明提升。
- 作用域查找都是从内部作用域开始 ,然后往上级作用域 查找,直到遇到第一个匹配的标识符为止 ,如果没有遇到标识符则会一直查找到最外层的全局作用域。
- 无论函数在哪里被调用,以及如何被调用,函数的词法作用域都只是由函数声明所处的位置决定。而不是函数调用是的位置所决定。
当然也需要掌握JavaScript提升的相关知识点JavaScript之var变量声明提升及函数声明提升,下面通过示例剖析。
示例剖析
示例1
javascript
var x = 10
fn()
function foo() {
console.log(x)
}
function fn(){
var x = 30
foo()
}
// 等价于👇🏻的代码
/*
function foo() {
console.log(x)
}
function fn(){
var x = 30
foo()
}
var x;
x = 10;
fn()
*/
控制台打印的值为:10
代码剖析:
首先声明的函数fn
、foo
会提升到头部即fn
、foo
函数声明前置 。查找函数foo
内部的标识符x
,函数foo
内部的作用域并没有匹配到该标识符,往其上层作用域(全局作用域)匹配到一个对应的标识符即是全局变量 var x = 10
,则执行fn()
输出在控制台的结果值为10
。
示例2
javascript
var a = 1
function fn1(){
// var a = 2;语句声明并赋值的变量a会提升到函数fn1内部的顶层
function fn2(){
console.log(a)
}
function fn3(){
var a = 4
fn2()
}
// 被fn1执行过后,a会被赋值为2
var a = 2
return fn3
}
var fn = fn1()
fn() //输出多少
控制台打印的值为:2
大致划分出四个作用域:
1️⃣ 全局作用域包含 变量`a`、函数`fn1`、函数`fn`
2️⃣ 函数`fn1`所创造的的作用域,包含`fn2`、`fn3`、变量`a`
3️⃣ 函数fn3内部作用域,包含一个变量`a`
代码剖析:
执行fn
函数输出的值,即是执行函数fn2
内部语句console.log(a)
在控制台打印的值。函数fn2
变量a
对应的作用域,首先从函数fn2()
内部作用域找是否能匹配的标识符。当前fn2
内部无法匹配,则往上层作用域(fn1()
所创建的作用域)查找变量a
。fn1
内部var a = 2;
变量a
提升到函数fn1
作用域的顶部 ,则fn2
内部变量a
匹配的正是fn1
作用域声明的变量a
。在var fn = fn1()
函数fn1
被调用了,return fn3
前面的语句a = 2
给变量a
赋值。最终console.log(a)
首先打印变量a
,且被赋值为2,所以控制台打印的值为2。
示例3
javascript
var a = 1
function fn1(){
function fn3(){
var a = 4
fn2()
}
var a = 2
return fn3
}
function fn2(){
console.log(a)
}
var fn = fn1()
fn() //输出多少
控制台打印的值为:1
代码剖析:
查找作用域中的变量要记住记住上面三条规则,其中一条就是"函数运行在它们被定义的作用域,而非被执行的作用域"。函数 _fn2_
在函数 _fn3_
被调用,但是函数 _fn2_
是被声明在全局作用域中的。从函数 _fn2()_
内部查找匹配变量 _a_
_,内部没有声明的该变量,则_往上层查找即是全局变量 a (a = 1)
。 如果注释掉上例中全局作用域中的var a = 1
,则fn()
控制台会报错"Uncaught ReferenceError: a is not defined"
示例4
javascript
var a = 1
function fn1(){
function fn3(){
function fn2(){
console.log(a)
}
fn2()
// fn2() 被调用之后,才会给a赋值为4
var a = 4 // 该语句放置在fn2()前面,则打印的结果值是4
}
var a = 2
return fn3
}
var fn = fn1()
fn() //输出多少
控制台打印的值为:undefined
代码剖析:
返回fn1
内部声明的fn3
函数,执行fn
函数则会执行函数fn3
。fn2
函数体内部变量a
匹配的作用域为它的上级作用域fn3
函数所创建的作用域。
javascript
function fn3(){
function fn2(){
console.log(a)
}
fn2()
var a = 4
}
fn2()
的的输出结果是undefined
,这是由于在fn3()
所创建的作用域变量a
提升到函数内顶部,但是变量a赋值 是在调用 fn2()
之后,所以console.log(a)
中的a
,并没有被赋值,既输出结果是undefined
。 如果把 var a = 4
语句放在调用fn2()
之前,则输出的结果是4