【JavaScript高階】8、函式高階(閉包)
阿新 • • 發佈:2019-01-01
引入
需求: 點選某個按鈕, 提示"點選的是第n個按鈕" 此時傳統方法想要在函式內部使用函式外部變數時,操作十分麻煩,必須將其變為呼叫函式的屬性的屬性值,通過this.屬性使用,因此採用回撥函式內傳入,相當於內部函式應用了巢狀的外部函式的變數,實際上這就是閉包。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>00_引入</title> </head> <body> <button>測試1</button> <button>測試2</button> <button>測試3</button> <!-- 需求: 點選某個按鈕, 提示"點選的是第n個按鈕" --> <script type="text/javascript"> /*var btns = document.getElementsByTagName("button") for (var i = 0,length = btns.length; i < length; i++) { var btn = btns[i] btn.index = i btn.onclick = function () { alert('第'+(this.index+1)+'個按鈕') } }*/ var btns = document.getElementsByTagName("button") for (var i = 0,length = btns.length; i < length; i++) { (function (j) { var btn = btns[j] btn.onclick = function () { alert('第'+(j+1)+'個按鈕') } })(i) } </script> </body> </html>
一、理解閉包
1. 如何產生閉包? * 當一個巢狀的內部(子)函式引用了巢狀的外部(父)函式的變數(函式)時, 就產生了閉包 2. 閉包到底是什麼? * 使用chrome除錯檢視 * 理解一: 閉包是巢狀的內部函式(絕大部分人) * 理解二: 包含被引用變數(函式)的物件(極少數人) * 注意: 閉包存在於巢狀的內部函式中 3. 產生閉包的條件? * 函式巢狀 * 內部函式引用了外部函式的資料(變數/函式)
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>01_理解閉包</title> </head> <body> <script type="text/javascript"> function fn1() { var a = 3 var b = '123' function fn2() { console.log(a) // 3 } fn2() } fn1() </script> </body> </html>
二、常見的閉包
1. 將函式作為另一個函式的返回值 2. 將函式作為實參傳遞給另一個函式呼叫
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>02_常見的閉包</title> </head> <body> <script type="text/javascript"> // 1. 將函式作為另一個函式的返回值 function fn1() { var a = 2 function fn2() { a++ console.log(a) } return fn2 } var f = fn1() f() // 3 f() // 4 f = fn1() //只有每次呼叫外部函式才能產生新的閉包 f() // 3 f() // 4 // 2. 將函式作為實參傳遞給另一個函式呼叫 function showDelay(msg,time) { setTimeout(function () { alert(msg) },time) } showDelay("chenwei",1000) </script> </body> </html>
三、閉包的作用
1. 使用函式內部的變數在函式執行完後, 仍然存活在記憶體中(延長了區域性變數的生命週期) 2. 讓函式外部可以操作(讀寫)到函式內部的資料(變數/函式) 問題: 1. 函式執行完後, 函式內部宣告的區域性變數是否還存在? 一般是不存在, 存在於閉包中的變數才可能存在(執行閉包的函式物件沒有成為垃圾物件) 2. 在函式外部能直接訪問函式內部的區域性變數嗎? 不能,但我們可以通過閉包讓外部操作(讀寫)到函式內部的資料(變數/函式)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>03_閉包的作用</title>
</head>
<body>
<script type="text/javascript">
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
}
function fn3() {
a--
console.log(a)
}
return fn3
}
var f = fn1() // 此時函式內fn2、fn3函式變數都不存在了,此時a還存在是因為f函式引用變數指向閉包
f() // 1
f() // 0
</script>
</body>
</html>
四、閉包的生命週期
1. 產生: 在巢狀內部函式定義執行完時就產生了(不是在呼叫) 2. 死亡: 在巢狀的內部函式成為垃圾物件時
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>04_閉包的生命週期</title>
</head>
<body>
<script type="text/javascript">
function fn1() {
//此時閉包就已經產生了(函式提升, 內部函式物件已經建立了)
var a = 2
function fn2 () {
a++
console.log(a)
}
return fn2
}
var f = fn1()
f() // 3
f() // 4
f = null //閉包死亡(包含閉包的函式物件成為垃圾物件,a此時不存在了)
</script>
</body>
</html>
五、閉包的應用—自定義js模組(方法1)
閉包的應用 : 定義JS模組 * 具有特定功能的js檔案 * 將所有的資料和功能都封裝在一個函式內部(私有的) * 只向外暴露一個包含n個方法的物件或函式 * 模組的使用者, 只需要通過模組暴露的物件呼叫方法來實現對應的功能
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>05_閉包的應用_自定義JS模組</title>
</head>
<body>
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
var module = myModule()
module.doSomething()
module.doOtherthing()
</script>
</body>
</html>
function myModule() {
//私有資料
var msg = "My name is Onedean"
function doSomething() {
console.log('doSomething() ' + msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing() ' + msg.toLowerCase())
}
return {
doSomething:doSomething,
doOtherthing:doOtherthing
}
}
六、閉包的應用—自定義js模組(方法2)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>05_閉包的應用_自定義JS模組2</title>
</head>
<body>
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
myModule2.doSomething()
myModule2.doOtherthing()
</script>
</body>
</html>
(function myModule2(window) { //此處和結尾加window引數也可以不加,加了是為了產品上線時打包壓縮window有可替換的字元
var msg = 'My name is ChenWei'
function doSomething() {
console.log('doSomething() ' + msg.toUpperCase())
}
function doOtherthing() {
console.log('doOtherthing() ' + msg.toLowerCase())
}
window.myModule2 = { // 將myModule2匿名函式暴露為全域性的物件屬性
doSomething:doSomething,
doOtherthing:doOtherthing
}
})(window)
七、閉包的缺點及解決
1. 缺點 * 函式執行完後, 函式內的區域性變數沒有釋放, 佔用記憶體時間會變長 * 容易造成記憶體洩露 2. 解決 * 能不用閉包就不用 * 及時釋放
八、面試題1
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>07_面試題1</title>
</head>
<body>
<script type="text/javascript">
//程式碼片段一
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name; // 沒有引用外部巢狀函式的變數,所以沒有閉包,同時這裡getNameFunc函式外的作用域為全域性window所以此處this為window
};
}
};
alert(object.getNameFunc()()); // The Window //!!!坑:object.getNameFunc()()直接呼叫內部函式,此時的this是window全部物件
//程式碼片段二
var name2 = "The Window";
var object2 = {
name2 : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name2; // 引用了外部巢狀函式的變數that,所以有閉包
};
}
};
alert(object2.getNameFunc()()); // My Object //object.getNameFunc()呼叫的是物件getNameFunc方法的函式,此時的this=呼叫物件object2,內部閉包that=this
</script>
</body>
</html>
九、面試題2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>07_面試題2</title>
</head>
<body>
<script type="text/javascript">
function fun(n,o) {
console.log(o)
return {
fun:function(m){
return fun(m,n)
}
}
}
var a = fun(0) // undefined
a.fun(1) // 0 //產生了新的閉包,但是沒有引用變數接收,新的閉包又釋放了,始終用的是a中的閉包
a.fun(2) // 0
a.fun(3) // 0
var b = fun(0).fun(1).fun(2).fun(3)//undefined 0 1 2
var c = fun(0).fun(1) // undefined 0
c.fun(2) // 1
c.fun(3)// 1
</script>
</body>
</html>