- 什么是閉包? 有什么作用
- 閉包的形成
在高級(jí)程序設(shè)計(jì)3中對(duì)于閉包的定義是這樣的
<blockquote>有權(quán)訪問(wèn)另一個(gè)函數(shù)作用域中的變量的函數(shù)。</blockquote>
在Wikipedia中的定于是這樣的
<blockquote>引用了自由變量的函數(shù)域那。</blockquote>
一開(kāi)始渡冻,我對(duì)這樣的抽象概念很模糊霹娄,但是當(dāng)我理解了閉包之后区端,我又認(rèn)為這樣的定義是簡(jiǎn)介而有力的师脂,如果從理解閉包的定義來(lái)說(shuō)物舒,可以認(rèn)為JS中的所有函數(shù)都是閉包,因?yàn)槊總€(gè)函數(shù)都能訪問(wèn)到全局作用域的自由變量
- 閉包的形成
var a = 1
function fn() {
console.log(a)
}
fn()
在這個(gè)例子中替蛉,fn可以訪問(wèn)了全局變量a贯溅,這個(gè)函數(shù)就叫做閉包,當(dāng)然灭返,這只是從理解的角度來(lái)說(shuō)盗迟,實(shí)際上這樣的全局作用域下的閉包是沒(méi)意義的,因?yàn)槿值淖兞繉?duì)象不會(huì)被銷(xiāo)毀熙含。在函數(shù)內(nèi)部的閉包可以在chrome的開(kāi)發(fā)者工具看到
要理解閉包的和閉包的作用罚缕,就要從作用域鏈的形成開(kāi)始,通過(guò)下面的例子來(lái)說(shuō)明
var a =2
function fn() {
var a = 1
function fn2() {
console.log(a)
}
fn2()
}
fn()
因?yàn)閖s是詞法作用域怎静,當(dāng)函數(shù)fn聲明的時(shí)候邮弹,就會(huì)預(yù)先包含全局作用域鏈,然后放在fn.Scope中蚓聘,[[Scope]]是一個(gè)只有語(yǔ)言內(nèi)部才能訪問(wèn)的屬性腌乡,當(dāng)函數(shù)fn被調(diào)用的調(diào)用的時(shí)候,會(huì)創(chuàng)建一個(gè)執(zhí)行環(huán)境夜牡,執(zhí)行環(huán)境里面有兩個(gè)東西与纽,一個(gè)是變量對(duì)象在函數(shù)內(nèi)部又叫活動(dòng)對(duì)象,一個(gè)是作用域鏈塘装,作用域鏈的形成就是復(fù)制fn.Scope里面的變量對(duì)象急迂,然后推入當(dāng)前的活動(dòng)對(duì)象。當(dāng)fn執(zhí)行完的時(shí)候蹦肴,本來(lái)fn執(zhí)行環(huán)境的變量對(duì)象和作用域鏈都會(huì)銷(xiāo)毀僚碎,但是現(xiàn)在只有作用域鏈銷(xiāo)毀了,變量對(duì)象卻沒(méi)有銷(xiāo)毀阴幌,因?yàn)閮?nèi)部的函數(shù)fn2的作用域鏈在引用這fn的變量對(duì)象勺阐。因?yàn)閒n2的作用域鏈里面包含fn的變量對(duì)象,所以可以訪問(wèn)到fn的變量矛双,所以也就形成了閉包渊抽。
- 閉包的缺點(diǎn)
1. 在IE9之前因?yàn)槭褂貌煌睦占瘷C(jī)制會(huì)導(dǎo)致循環(huán)引用會(huì)造成內(nèi)存泄漏
2. 閉包會(huì)攜帶包含函數(shù)的作用域,所以會(huì)比其他函數(shù)占用更多內(nèi)存议忽,過(guò)度使用閉包會(huì)造成內(nèi)存占用過(guò)多
- 閉包的用法
其實(shí)保存變量現(xiàn)場(chǎng)腰吟,封裝私有變量都是對(duì)閉包特性的利用,并不是閉包的定義,不要混淆
1. 保存變量現(xiàn)場(chǎng)毛雇,主要利用了js函數(shù)傳遞參數(shù)的方式是按值傳遞
!function() {
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function () {
alert(i)
}
}
arr[1]()//5
}()
本來(lái)的打算應(yīng)該數(shù)字的每一項(xiàng)都是一個(gè)函數(shù),可以打印出每一項(xiàng)的索引侦镇,但是因?yàn)樗L問(wèn)的i是外部函數(shù)的灵疮,當(dāng)循環(huán)完以后,i已經(jīng)累加到5了壳繁,所以無(wú)論多少輸出當(dāng)然是5震捣,我們可以利用js函數(shù)傳遞是按值傳遞的方式來(lái)改造這個(gè)數(shù)組
!function() {
var arr = []
for (var i = 0; i < 5; i++) {
arr[i] = function (n) {
return function () {
alert(n)
}
}(i)
}
arr[1]()//5
}()
改造后的函數(shù)輸出就是1,主要利用按值傳遞參數(shù)和閉包
2. 封裝私有變量
有時(shí)候我們常常需要隱藏一些數(shù)據(jù)闹炉,而僅僅暴露一些接口供外部使用蒿赢,起到封裝的效果,這時(shí)候就可以利用閉包
var Car = (function () {
var speed = 0
return {
setSpeed: function (s) {
speed = s
},
getSpeed: function () {
return speed
}
}
})()
Car.setSpeed(30)
Car.getSpeed()
這就封裝了一個(gè)汽車(chē)對(duì)象渣触,其中是speed私有的變量羡棵,只能通過(guò)我們提供的接口setSpeed和getSpeed來(lái)訪問(wèn)到
- setTimeout 0 有什么作用
setTimeout表示的是超時(shí)調(diào)用,setTimeout可以接受多個(gè)參數(shù)嗅钻,表示過(guò)延遲多長(zhǎng)時(shí)間再執(zhí)行回調(diào)函數(shù)- 第一個(gè)參數(shù)表示回調(diào)函數(shù)皂冰,可以是一個(gè)字符串或者是一個(gè)函數(shù)名,推薦使用函數(shù)名养篓,因?yàn)樽址畷?huì)導(dǎo)致一定的損耗和安全問(wèn)題
- 第二個(gè)參數(shù)表示延遲的時(shí)間秃流,單位是ms毫秒,因?yàn)閖s是單線程的解釋器柳弄,所以一次只能執(zhí)行一段代碼舶胀,為了控制要執(zhí)行的代碼,就有一個(gè)任務(wù)隊(duì)列碧注,這些任務(wù)就會(huì)按照他們添加到任務(wù)隊(duì)列的順序執(zhí)行嚣伐,延遲的時(shí)間表示再過(guò)多少ms才把當(dāng)前任務(wù)添加到任務(wù)隊(duì)列,如果隊(duì)列前面沒(méi)有任務(wù)应闯,當(dāng)前任務(wù)就會(huì)立即執(zhí)行纤控,如果隊(duì)列前面有任務(wù),就要等到前面的任務(wù)執(zhí)行完之后再執(zhí)行
- 第三個(gè)和后面的參數(shù)表示參數(shù)回調(diào)函數(shù)的參數(shù)
setTimeout(f,0)表示的就是盡快的執(zhí)行函數(shù)f碉纺,他的作用可以調(diào)整時(shí)間的發(fā)生順序船万,例如在開(kāi)發(fā)中,某個(gè)事件發(fā)生在子元素骨田,然后冒泡到父元素耿导,即子元素的回調(diào)函數(shù)會(huì)比父元素的回調(diào)函數(shù)先執(zhí)行,通過(guò)setTimeout(f,0)我們就可以讓子元素的回調(diào)函數(shù)排在隊(duì)列后面态贤,從而讓父元素的回調(diào)函數(shù)先執(zhí)行
參考:
定時(shí)器-阮一峰