1. 閉包
閉包就是一個函數,捕獲作用域內的外部綁定。
-
函數對象可以通過作用域鏈相互關聯起來,函數體內部的變量都可以保存在函數作用域內,這種特性在計算機科學文獻中稱爲“閉包” ----《JavaScript權威指南》
-
閉包是指有權訪問另一個函數作用域中的變量的函數。 ---- 《Javascript高級程序設計》
-
當函數可以記住並訪問所在的詞法作用域時,就產生了閉包,即使函數是在當前詞法作用 域之外執行。— 《你不知道的JavaScript(上)》
2. 常見閉包兩種形式
2.1 函數作爲返回值
function connectTest() {
var a = 'OK'
function childFun(childParam) {
return "haha~~~" + a + childParam
}
return childFun
}
var report = connectTest()
console.log(report(''))
函數childFun()的詞法作用域能夠connectTest()的內部作用域。然後將進行傳遞。
在函數connectTest執行後,其返回值(函數childFun)賦值給report 對象。
當打印執行report()的時候,實際是通過不同的標識符引用調用了內部函數childFun,
在代碼最下方延時一下還會打印麼?
setTimeout(() => {
console.log(report('!!!'))
}, 3000)
js中有垃圾回收器,函數等執行完之後會釋放不再使用的內存空間。當執行完connectTest(),按道理會被銷燬,但是由於函數childFun所在,使得該作用域能夠一直存活,以供childFun之後任何時間進行引用。。但每一個新的閉包都會捕獲不一樣的值!
2.2 函數作爲參數傳遞
function connectTest() {
var a = 'OK'
function childFun(childParam) {
return "haha~~~" + a + childParam
}
return childFun
}
var report = connectTest()
function test(fn) {
var a = 'test'
fn('')
console.log(fn('!!!test')) // haha~~~OK!!!test
}
test(report)
3. 閉包優缺點
優點:
- 避免全局變量的污染【阻止父作用域被銷燬,沒有被垃圾回收器釋放內存】
- 變量儘量私有化
缺點:
- 使用不佳內存泄露 ,降低程序的性能【駐留在內存中,增大內存使用率】
4. 常見閉包的場景
4.1 回調函數
在定時器、事件監聽器、Ajax 請求、跨窗口通信、Web Workers 或者任何其他的異步(或者同步)任務中,只要使用了回調函數,實際上就是在使用閉包!
4.2 模塊化
爲了最大限度減少暴露捕獲變量的風險,JS經常用下面這種模式
var dateYear = (function () {
var year = 2019;
return {
lastYear: function (n) {
return year -= n;
},
futureYear: function (n) {
return year += n;
}
}
})()
var num = 3;
console.log(dateYear.year) // undefined
console.log('2019的前面' + num + '年是:' + dateYear.lastYear(num))
// console.log('2019的後面' + num + '年是:' + dateYear.futureYear(num))
此時year是私有變量,只有這兩個函數可以調用,其他任何手段都是無法進行訪問,閉包模式提過強大的訪問保護。
4.3 循環中的問題
for (var i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, 1000);
};
輸出的結果是// 5 5 5 5 5.
解釋一下爲什麼5個5,延遲函數的會調會在循環結束時才執行,此時循環已經在5了。
如何需要修改
for (var i = 0; i < 5; i++) {
(function () {
var j = i;
setTimeout(function timer() {
console.log(j);
}, j * 1000);
})();
// 修改完善一下
// (function (j) {
// setTimeout(function timer() {
// console.log(j);
// }, j * 1000);
// })(i);
}
此時打印// 0 1 2 3 4
方案二,當然將var換成let,就不存在這回事兒了,【所以說讓let代替var】
for (let i = 0; i < 5; i++) {
setTimeout(() => {
console.log(i)
}, 1000);
};
到此差不多對閉包已經認識了,可以在自己開發的項目中看看有那些閉包~~~
5 遞歸
5.1 遞歸的定義
遞歸通常涉及函數調用自身。
遞歸常見的兩種方式
a. 自身直接調用自身的方法或函數
function searchSelf(someProperty) {
searchSelf(someProperty)
}
b. 間接調用自身的函數
function searchSelf(someProperty) {
myself(someProperty)
}
function myself(someProperty) {
searchSelf(someProperty)
}
每個遞歸函數都必須要有邊界條件,即一個停止的條件,以防止無限遞歸(同時瀏覽器
會拋出棧溢出錯誤 stack overflow error)
遞歸併不比普通版本更快,反而更慢。但遞歸容易理解,而且它所需的代面量少。
ES6中使用了尾掉用優化,讓遞歸不會更慢。
(相關尾掉用的參考)