一、作用域、作用域鏈
作用域(scope)
淺顯的理解: 作用域就是變量的可用范圍(scope)
為什么要有作用域: 目的是防止不同范圍的變量之間互相干擾
js中包含2級(jí)作用域:全局作用域 & 函數(shù)作用域。
-
全局作用域
不屬于任何函數(shù)的外部范圍稱為全局作用域,保存在全局的作用域的變量稱為全局變量。
優(yōu)點(diǎn):可反復(fù)使用;缺點(diǎn):會(huì)造成全局污染。
-
函數(shù)作用域
一個(gè)函數(shù)內(nèi)部的范圍稱為函數(shù)作用域,保存在函數(shù)作用域的變量為局部變量。
【注】函數(shù)形參變量也是函數(shù)內(nèi)的局部變量。
優(yōu)點(diǎn):不會(huì)污染;缺點(diǎn):無法反復(fù)使用。
-
作用域的形成
只有函數(shù)的
{}可以形成作用域,其他情況的{}不能形成作用域。比如對(duì)象的大括號(hào)不是作用域,對(duì)象的屬性不是局部變量。【注】es6的塊級(jí)作用域是實(shí)現(xiàn)其實(shí)是匿名函數(shù)自調(diào)用形成的,實(shí)際上也是函數(shù)作用域的原理。
作用域鏈(scopes / scope chain)
js規(guī)定,一個(gè)函數(shù),及能用自己的作用域,又能用外層的作用域變量。所以需要一個(gè)訪問變量的"路線圖"。
每個(gè)函數(shù)在定義時(shí),就已經(jīng)規(guī)劃好了自己專屬的查找變量的”線路圖“,蹭我作用域鏈。
【注】給從未聲明的變量賦值,不會(huì)報(bào)錯(cuò),但會(huì)生成全局變量。(非嚴(yán)格模式)
總結(jié): Js中只有兩種局部變量: 1. 函數(shù)內(nèi)var出來的 2. 函數(shù)的形參變量 看不見var,形參里也沒有, 就不是局部變量。
var a=10;
function fun(){
var a=100;
a++;
console.log(a);
}
fun(); // 101
console.log(a); // 10
// 形參題目---------
var a=10;
function fun(a){
a++;
console.log(a);
}
fun(a); //11
console.log(a); //10
作用域的本質(zhì)
js中的作用域和作用域鏈都是對(duì)象結(jié)構(gòu):

在fun函數(shù)還未調(diào)用時(shí),fun的作用域鏈:

進(jìn)入fun函數(shù)時(shí),作用域鏈增加一個(gè)local作用域

![]() 作用域本質(zhì)
|
![]() 函數(shù)作用域釋放
|
|---|
總結(jié): 函數(shù)作用域 其實(shí)是js引擎在調(diào)用函數(shù)時(shí)才臨時(shí)創(chuàng) 建的一個(gè)作用域?qū)ο?。其中保存函?shù) 的局部變量。而函數(shù)調(diào)用完,函數(shù)作 用域?qū)ο缶歪尫帕恕?/p>
所以,JS中函數(shù)作用域?qū)ο?,還有個(gè)別名 ——”活動(dòng)的對(duì)象(Actived Object)” 簡稱, AO。 所以,局部變量不可重用
二、閉包
閉包c(diǎn)losure:能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
既重用變量又保護(hù)變量不被污染的一種編程方法。
只要希望給一個(gè)函數(shù),保存一個(gè)即可反復(fù)使用,又不會(huì)被 外界污染的專屬局部變量時(shí),就用閉包。
使用閉包3步:
- 用 外層函數(shù)包裹 要保護(hù)的變量和使用變量的內(nèi)層函數(shù)
- 外層函數(shù) 返回 內(nèi)層函數(shù)
- 調(diào)用外層函數(shù),用變量接住返回的內(nèi)層函數(shù)
//第1步: 用外層函數(shù)包裹要保護(hù)的變量和內(nèi)層函數(shù)
function mother(){
var total=1000;
//第2步: 返回內(nèi)層函數(shù)對(duì)象
return function (money){
total-=money
console.log(`花了${money}還剩${total}元`)
}
}
//第3步: 調(diào)用外層函數(shù),用變量接住內(nèi)層函數(shù)對(duì)象
var pay=mother();
//pay接住的就是mother()返回出來的內(nèi)層函數(shù)對(duì)象
pay(100);//應(yīng)該剩900
total=0;//別人程序執(zhí)行了代碼
pay(100);//應(yīng)該剩800

可以看出pay函數(shù)的作用域中有個(gè)閉包來自mother其中保存了total變量。
一句話概括:
閉包如何形成: 外層函數(shù)調(diào)用后, 外層函數(shù)的作用域?qū)ο螅?被返回的內(nèi)層函數(shù)的作用域鏈引用著, 無法釋放, 就形成了閉包對(duì)象
閉包缺點(diǎn):容易造成內(nèi)存泄漏。解決辦法:及時(shí)釋放不用的閉包:將保存內(nèi)存函數(shù)的變量賦值為null。
題目:閉包的應(yīng)用
- 柯里化
// 要求定義函數(shù)add 實(shí)現(xiàn)add(1)(2)(4)得到1+2+4的結(jié)果。
// 函數(shù)柯里化:可以給一個(gè)函數(shù)反復(fù)傳參,傳的參數(shù)還可以累計(jì)到函數(shù)中
var add = function(x1){
var sum = x1;
var fun = function(x2) {
sum+=x2;
return fun;
// 因?yàn)榭吕锘罂梢赃B續(xù)傳參,所以需要return出這個(gè)累加函數(shù),確??梢岳^續(xù)傳參時(shí)繼續(xù)調(diào)用。如果直接返回sum,那么只傳兩個(gè)參數(shù),該過程就結(jié)束了。
}
fun.toString = function() {
// toString方法在隱式轉(zhuǎn)字符串時(shí)調(diào)用,可以返回fun中函數(shù)閉包中的sum變量的值
return sum
}
fun.valueOf = function() {
// valueOf方法在隱式轉(zhuǎn)number時(shí)調(diào)用,可以通過該方法返回sum值
return sum
}
return fun; // 為了可以傳入第二個(gè)參數(shù),add()第一次調(diào)用時(shí),需要返回函數(shù),才能繼續(xù)第二次的傳參調(diào)用
}
alert(add(2)(3)(4)(1))
console.log(0+add(1)(2)(3)(3))
- 二維游戲:2048/消消樂類型
<head>
<style>
.container{
width: 200px;
height: 200px;
border-radius: 6px;
background: rgb(230, 203, 87);
}
.container div{
cursor: pointer;
width: 40px; height: 40px;
background: #fff;
float: left;
margin-left: 8px;
margin-top: 8px;
border-radius: 4px;
text-align: center;
line-height: 40px;
}
</style>
</head>
<body>
<div id="container" class="container"></div>
<script>
// 4*4個(gè)div,每個(gè)表格中都有其坐標(biāo):(0,0) - (3,3),點(diǎn)擊格子彈出該格子的點(diǎn)擊次數(shù),每個(gè)格子互不干擾(閉包)
var div = document.getElementById('container');
// // 方案1:直接給每個(gè)格子添加click事件,用一個(gè)外層函數(shù)自調(diào)用包裹,定義一個(gè)自己的變量n記錄點(diǎn)擊次數(shù),然后return一個(gè)實(shí)際的累加操作函數(shù)
// // 缺點(diǎn):每個(gè)格子的n都是獨(dú)立的,對(duì)于二維游戲來說,格子之間需要計(jì)算,比如2048格子之間的數(shù)值累加,此方法無法實(shí)現(xiàn)
// for(var r =0;r<4;r++) { // 行循環(huán)生成
// for(var c = 0;c<4;c++) { // 列循環(huán)生成
// var cell = document.createElement('div')
// cell.innerText = `${r},${c}`
// div.appendChild(cell);
// // 添加點(diǎn)擊事件
// cell.onclick = (function() {
// var n = 0;
// return function() {
// n++;
// alert(`點(diǎn)擊了${n}次`)
// }
// })()
// }
// }
// // 方案2:使用二維數(shù)組來記錄每個(gè)格子的值,每個(gè)按鈕只保存其行號(hào)和列號(hào)(坐標(biāo)),當(dāng)點(diǎn)擊時(shí),通過按鈕自己保存的行號(hào)和列號(hào)來找到二維數(shù)組中自己對(duì)應(yīng)的值
// // 缺點(diǎn):會(huì)存在全局變量arr,容易被篡改
// var arr = [
// [0,0,0,0],
// [0,0,0,0],
// [0,0,0,0],
// [0,0,0,0]
// ]
// for(var r =0;r<4;r++) { // 行循環(huán)生成
// for(var c = 0;c<4;c++) { // 列循環(huán)生成
// var cell = document.createElement('div')
// cell.innerText = `${r},${c}`
// div.appendChild(cell);
// // 只保存自己的行號(hào)列號(hào)
// cell.onclick = (function(r,c) {
// return function() {
// arr[r][c]++
// alert(`點(diǎn)擊了${arr[r][c]}次`)
// }
// })(r, c)
// }
// }
// 方案3:使用方案2+整個(gè)邏輯的匿名函數(shù)自調(diào)用包裹,使arr也為局部變量不會(huì)被污染
(function(){
var arr = [
[0,0,0,0],
[0,0,0,0],
[0,0,0,0],
[0,0,0,0]
]
for(var r =0;r<4;r++) { // 行循環(huán)生成
for(var c = 0;c<4;c++) { // 列循環(huán)生成
var cell = document.createElement('div')
cell.innerText = `${r},${c}`
div.appendChild(cell);
// 只保存自己的行號(hào)列號(hào)
cell.onclick = (function(r,c) {
return function() {
arr[r][c]++
alert(`點(diǎn)擊了${arr[r][c]}次`)
}
})(r, c)
}
}
})()
</script>
</body>

