參考
- 學(xué)習(xí)Javascript閉包(Closure)
- JavaScript 高級程序設(shè)計(第三版)P178
定義函數(shù)
定義函數(shù)有兩種方式:一是函數(shù)聲明踱侣,而是函數(shù)表達(dá)式购公。
函數(shù)聲明重要的特征是织阅,函數(shù)聲明提升——在執(zhí)行代碼之前會先讀取函數(shù)聲明,這就意味著可以把函數(shù)聲明放在調(diào)用此函數(shù)的語句后面雕擂。
sayHi()
function sayHi() {
console.log('hi')
}
// hi
然而踊东,函數(shù)表達(dá)式?jīng)]有這種特性,比如說:
sayHi()
const sayHi = function() {
console.log('hi')
}
//error: Uncaught TypeError: sayHi is not a function
閉包
定義
閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù) ——JavaScript 高級程序設(shè)計(第三版)
看了紅寶書的閉包胞四,和很多關(guān)于閉包的文章恬汁。覺得還是紅寶書的描述規(guī)范一點,怎么理解這句話呢辜伟?簡單來說就是函數(shù)能訪問別的函數(shù)的變量氓侧,這個函數(shù)就是閉包。
把閉包簡單理解成"定義在一個函數(shù)內(nèi)部的函數(shù)"导狡≡枷铮—— 學(xué)習(xí)Javascript閉包(Closure)——(阮一峰)
閉包用途——讀取函數(shù)內(nèi)部的變量
function A() {
var value = 999
function B() {
console.log('value', value)
}
B()
}
A() //999
函數(shù) B 在函數(shù) A中定義,那么函數(shù) B 可以訪問 A 中的變量(還可以訪問全局變量)旱捧。函數(shù) B 就是閉包独郎,很簡單吧。再看:
function A() {
var value = 999
}
function B() {
console.log('value', value)
}
B()
//報錯: Uncaught ReferenceError: value is not defined
此時,函數(shù) B 不能訪問函數(shù) A 中的 value囚聚,B就不是閉包靖榕。就是這樣子
經(jīng)常會看到用兩個括號的方式使用閉包,例如:
(function(value) {
function B() {
console.log(value)
}
B()
})(999)
// 999
以前一直不明白這是什么意思顽铸,其實仔細(xì)看下茁计。就是一個立即執(zhí)行的匿名函數(shù),匿名函數(shù)中的函數(shù) B 能訪問匿名函數(shù)的變量(參數(shù))谓松,函數(shù) B 就是閉包星压。
閉包用途—— 讓變量的值始終保持在內(nèi)存中
function f1() {
var name = 'MyName'
function sayHello() {
alert('hello ' + name)
}
sayHello()
}
f1() // hello MyName
這段代碼,當(dāng) f1執(zhí)行后鬼譬,變量 name就會回收娜膘。再看下面:
function f1() {
var name = 'MyName'
function sayHello() {
alert('hello ' + name)
}
return sayHello
}
var result = f1()
result() // hello MyName
函數(shù) f1 返回了函數(shù) sayHello,并保存在全局變量 result 中,這里 result 就是 sayHello 函數(shù)閉包优质。無論調(diào)用多少次 result 都能訪問 f1函數(shù)內(nèi)的變量 name竣贪,這就意味著name 一直在內(nèi)存中,并沒有在f1執(zhí)行后被回收巩螃。
經(jīng)典面試題
要求每隔一秒打印 1演怎、2、3避乏、4爷耀、5,很容易寫出這樣的代碼來:
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i)
}, 1000 * i)
}
結(jié)果是每隔一秒打印了一個6出來拍皮。由于setTimeout是異步的歹叮,首先會把循環(huán)執(zhí)行完。等到打印時铆帽,變量 i 已經(jīng)是6了咆耿,于是打印了5個6。
解決辦法:閉包
for (var i = 1; i <= 5; i++) {
(function(j) {
setTimeout(function timer() {
console.log(j)
}, j * 1000)
})(i)
}
這里為什么就不是打印6呢锄贼?timer就是閉包票灰,time 訪問的是 匿名函數(shù)的參數(shù) j 。相當(dāng)于循環(huán)里面調(diào)用了5次匿名函數(shù)宅荤,每次傳給匿名函數(shù)的參數(shù)分別是1、2浸策、3冯键、4、5庸汗”谷罚可以理解為每次調(diào)用匿名函數(shù),都給其參數(shù) j 賦值 i。所以最終可以正確打印1改化、2掩蛤、3、4陈肛、5揍鸟。
解決這個問題還要其他的辦法。
方法一:setTimeout 第三個參數(shù)
for (var i = 1; i <= 5; i++) {
setTimeout(function(j) {
console.log(j)
}, i * 1000, i)
}
方法二: 用 let 聲明變量
for (let i = 1; i <= 5; i++) {
setTimeout(function() {
console.log(i)
}, i * 1000)
}