閉包
閉包就是能夠?qū)⒑瘮?shù)內(nèi)部的變量與外部進(jìn)行通信的一座橋梁怔鳖。
function f() {
var n = 100
console.log(n) // 100
}
console.log(n) // ReferenceError: n is not defined
f()
我們定義了一個(gè)函數(shù),函數(shù)在下面f()被調(diào)用,函數(shù)就會(huì)從上往下執(zhí)行读第,執(zhí)行完之后函數(shù)內(nèi)部的聲明的變量都會(huì)被清空掉,函數(shù)內(nèi)部的變量是對(duì)外界不開放的
- 假如我們想要在另外一個(gè)函數(shù)里使用這個(gè)變量n我們有哪些方法哪拥刻?
function f() {
var n = 100
console.log(n) //100
function increment () {
n = n + 1;
console.log(n) //101
}
increment()
}
f()
我們?cè)趂()函數(shù)里定義了一個(gè)increment()函數(shù),里面使用了變量n,這次并沒有報(bào)錯(cuò)父泳。這就是js里鏈?zhǔn)阶饔糜虬愫撸M(jìn)行冒泡似的查找變量聲明。
上面的例子惠窄,我們看到了一個(gè)函數(shù)體內(nèi)定義了另外一個(gè)函數(shù)蒸眠。我們知道js中函數(shù)是可以作為變量傳遞的,那么我們修改一下上面的例子
function f(increment) {
var n = 100
console.log(n) // 100
increment(n) //調(diào)用
}
f(function(n) {
console.log(n + 1) // 101
})
發(fā)生了什么杆融,我們將increment函數(shù)作為變量傳給了f函數(shù)楞卡,大家看完這個(gè)例子是不是感覺有點(diǎn)熟悉的感覺。我們加入一些打印看下執(zhí)行順序
function f(increment) {
console.log("f函數(shù)開始")
var n = 100
console.log(n) // 100
increment(n) //調(diào)用
console.log("f函數(shù)結(jié)束")
}
f(function(n) {
console.log(n + 1) // 101
console.log("increment函數(shù)")
})
//打印順序
// f函數(shù)開始
// 100
// 101
// increment函數(shù)
// f函數(shù)結(jié)束
我們看到在f函數(shù)里是從上下到下一次執(zhí)行的,其實(shí)上面的例子和下面的事等價(jià)的
function f() {
var increment = function(n) {
console.log(n + 1)
}
var n = 100;
console.log(n) // 100
increment(n) // 101
}
f()
好了蒋腮,說了這么多閉包到底可以干啥類淘捡。就如我們上面所說的,可以捕獲函數(shù)里的變量池摧,比如我們函數(shù)體內(nèi)有一個(gè)異步操作焦除,我們想在這個(gè)函數(shù)執(zhí)行完這些異步操作之后
就可以把這些數(shù)據(jù)傳出去供別人使用了。
function getData(successCallBack) {
console.log("getData函數(shù)開始執(zhí)行")
setTimeout(function() {
console.log("執(zhí)行回調(diào)")
successCallBack("這些是數(shù)據(jù)")
}, 3000);
console.log("getData函數(shù)體結(jié)束")
}
getData((data) => {
console.log(data)
})
//執(zhí)行結(jié)果如下
// getData函數(shù)開始執(zhí)行
// getData函數(shù)體結(jié)束
// 執(zhí)行回調(diào)
// 這些是數(shù)據(jù)
閉包會(huì)造成內(nèi)存泄漏作彤,下面我們玩?zhèn)€游戲1,1,2,3,5,8,13,21,34...求第n位是多少膘魄!一個(gè)經(jīng)典的算法題
- 使用遞歸
var count = 0
function fib (num) {
console.log(count ++)
if (num <= 0) {
return 0
}
if (num === 1) {
return 1
}
return fib(num - 2) + fib(num - 1)
}
console.log(fib(7)) //8
fib(6) = fib(5) + fib(4), fib(5) = fib(4) + fib(3) ...... f(2) = f(1) + f(0),一直到最后我們才找到f(0) = 0, f(1) = 1,那么此時(shí)函數(shù)f(2)才會(huì)被釋放掉,然后f(3)回后f(3)才會(huì)被釋放掉竭讳。创葡。。,
所以如果num數(shù)夠大的話那么內(nèi)存里存儲(chǔ)的這些函數(shù)也會(huì)增多绢慢。大家可以測(cè)試一下我測(cè)的當(dāng)num = 45時(shí)就會(huì)執(zhí)行大概18s,這個(gè)數(shù)字會(huì)根據(jù)有所不同灿渴,根據(jù)電腦性能,反正是很耗時(shí)的呐芥。
var count = 0
function fib (index , left, right) { //使用right作為變量逻杖,存儲(chǔ)最后這個(gè)值
console.log(count ++)
if (index === 1) {
return right
}
return fib(index - 1, right, left + right)
}
console.log(fib(7,0,1)) // 8
- 如何不在函數(shù)里寫控制語句,完成一個(gè)循環(huán)
function circle(index) {
if (index === 0) {
return
}
console.log(index)
return circle(index - 1)
}
circle(5) //5 4 3 2 1
- 閉包是如何保存上下文的
function print() {
console.log("print開始執(zhí)行")
for (var index = 0; index < 5; index++) {
console.log("for begin")
setTimeout(function() {
console.log("callback begin")
console.log(index) // 5,5,5,5,5
}, 1000);
}
console.log("print執(zhí)行結(jié)束")
}
print()
//執(zhí)行順序
// print開始執(zhí)行
// for begin
// for begin
// for begin
// for begin
// for begin
// print執(zhí)行結(jié)束
// callback begin
// 5
// callback begin
// 5
// callback begin
// 5
// callback begin
// 5
// callback begin
// 5
打印了五個(gè)五為什么思瘟?在for語句里setTimeout是個(gè)異步的函數(shù)荸百,在for執(zhí)行完之后index已經(jīng)是五了,然后在打印滨攻,當(dāng)然都是5了
- 上面的例子相當(dāng)于下面的
function print() {
console.log("print開始執(zhí)行")
for (var index = 0; index < 5; index++) {
console.log("for begin")
var callBack = function() {
console.log("callback begin")
console.log(index) // 5,5,5,5,5
}
setTimeout(callBack, 1000);
}
console.log("print執(zhí)行結(jié)束")
}
print()
- 使用閉包留index的值
function print() {
console.log("print開始執(zhí)行")
for (var index = 0; index < 5; index++) {
console.log("for begin")
var callBack = function(index) {
console.log("callback begin")
console.log(index) // 5,5,5,5,5
}
setTimeout(callBack(index), 1000); //callBack保留index的值
}
console.log("print執(zhí)行結(jié)束")
}
print()
//打印順序
// print開始執(zhí)行
// for begin
// callback begin
// 0
// for begin
// callback begin
// 1
// for begin
// callback begin
// 2
// for begin
// callback begin
// 3
// for begin
// callback begin
// 4
// print執(zhí)行結(jié)束
- 最終就變成了我們經(jīng)常寫的下面這部分
function print(callBack) {
console.log("print開始執(zhí)行")
for (var index = 0; index < 5; index++) {
console.log("for begin")
setTimeout(callBack(index), 1000);
}
console.log("print執(zhí)行結(jié)束")
}
var callBack = (index) => {
console.log("callback begin")
console.log(index)
}
print(callBack)
//執(zhí)行順序
// print開始執(zhí)行
// for begin
// callback begin
// 0
// for begin
// callback begin
// 1
// for begin
// callback begin
// 2
// for begin
// callback begin
// 3
// for begin
// callback begin
// 4
// print執(zhí)行結(jié)束
- 在一個(gè)我們經(jīng)常見的就是函數(shù)作為返回值進(jìn)行返回
function increment(x) {
return function (y) {
return x + y
}
}
var incre10 = increment(10) //相當(dāng)于 function(y) {return 10 + y}
console.log(incre10(8)) // 18
//increment相當(dāng)于一個(gè)構(gòu)造器可以創(chuàng)建函數(shù)
- 考慮一下例子
var object = {
name: 'Bill',
getName: function() {
return function() {
return this.name
}
}
}
console.log(object.getName()()) //undefined
我們看到getName的上下文是object 那么getName的中使用this的話應(yīng)該是指向object
object.getName()()此時(shí)的上下文是global的因?yàn)槲以趎ode環(huán)境下執(zhí)行的如果在網(wǎng)頁里應(yīng)該是window
global.name = 'Jason'
var object = {
name: 'Bill',
getName: function() {
return function() {
return this.name
}
}
}
console.log(object.getName()()) //Jason
- 還有一種方法
var object = {
name: 'Bill',
getName: function() {
var that = this //getName的上下文環(huán)境是object
return function() {
return that.name
}
}
}
console.log(object.getName()()) //Jason
所以使用閉包的時(shí)候要注意上下文環(huán)境問題