在js中,作用域分为全局作用域、函数作用域、块级作用域。
作用域
作用域就是一个变量在代码运行时可被访问的范围。作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
全局作用域
<script>
var a = 1
<script/>
像这种直接定义在window上的变量就具有全局作用域,全局作用域里的变量可以被全局访问,但是这也就造成了一个问题,当定义了全局变量之后,没有销毁的话,容易造成内存泄露。
函数作用域
<script>
var a = 1
function fn(){
let i = 2
console.log(a) // 1
}
<script/>
由function所定义出的函数,其里面的变量具有函数定义域的作用范围,里面的变量只能在该函数中可以被访问,例如window中就无法访问i,这里的i就具有块级作用域,其由let、const定义的变量会具有块级作用域,块级作用域的范围是let所在的大括号{}的范围。
作用域链
作用域链就是由全局作用域与函数作用域所组成的变量查找顺序。需要注意的是,作用域链在js预编译阶段就已经确定。
<script>
var a = 1
function fn(){
let i = 2
console.log(a) // 1
}
<script/>
函数作用域内可以访问到全局作用域的变量a这就是作用域链的原因,接下来我会好好分析一下这个过程
预编译阶段:
1、全局预编译产生GO
2、函数预编译产生AO
3、在自己的作用域将所有变量进行定义并赋值。
所谓作用域链其实就是在‘里面’的作用域可以访问外层作用域的变量(前提是自己没有)。这个‘里面’的意思其实就是包裹的意思,但是需要注意的一点是每个函数作用域是独立的,互为兄弟的函数,不能访问相互的变量,除非在他们内部有变量被定义到了他们的父作用域或者window上(b = 5这种)。
这只是个小例子,但也是作用域链原理最简单的表述,下面给个例子验证一下自己是否懂得了原理。
var a = 1
function f1() {
console.log(a);
}
function f2() {
var a = 2
f1()
}
f2() // 1
根据上面的分析,可以知道f1其实是在window下的函数作用域,尽管在f2中运行,但是其作用域在预编译阶段就已经被确定了。(如果没懂的话,可以跟我上面一样,画个图加深印象和理解)
闭包
了解了作用域链相关知识,不知道大家有没有联系到js中一个非常重要的知识点—闭包。闭包的定义是,函数内部可以引用函数外部的变量。其实,你看看上面那个例子,就可以明白闭包的底层原理其实就是作用域链。
以上例为例,在f1函数内部中没有变量a,去函数外部找,比较特殊的是外面是全局作用域,但是这个完全是符合闭包定义的。
先给一个典型的闭包例子
function f1(){
var a = 1
function f2(){
console.log(a++)
}
return f2
}
const fn = f1()
fn()
fn()
fn()
利用作用域链分析:
显然f2可以访问a变量,且会被存储在fn中不会删除,这就是闭包。