目錄
點贊再看,養成好習慣,總結不易,老鐵多多支持~
引入(閉包和塊作用域)
<!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.onclick = function () {
alert('第'+(i+1)+'個'); // 永遠是第btns.length個,因爲for循環已結束
}
}*/
// 方法一,利用對象屬性
/*
for (var i = 0,length=btns.length; i < length; i++) {
var btn = btns[i]
//將btn所對應的下標保存在btn上
btn.index = i
btn.onclick = function () {
alert('第'+(this.index+1)+'個')
}
}*/
// 方法二,利用閉包
// for (var i = 0, length = btns.length; i < length; i++) {
// // (function (j) {
// // var btn = btns[j]
// // btn.onclick = function () {
// // alert('第' + (j + 1) + '個')
// // }
// // })(i);
// // 等同於上面一段
// (function () {
// var j = i; // 因爲當前作用域是沒有i的,i在作用域之外,這樣onclick就持有了該作用域的引用,這個引用就叫做閉包
// var btn = btns[j];
// btn.onclick = function () {
// alert('第' + (j + 1) + '個');
// }
// })();
// }
// 或者利用ES6的let聲明和塊作用域結合起來
// for循環頭部的let聲明會在每次循環迭代的過程中被聲明
for (let i = 0, length = btns.length; i < length; i++) {
let btn = btns[i];
btn.onclick = function () {
alert('第' + (i + 1) + '個'); // 持有外部作用域的引用,形成閉包
}
}
// 塊作用域和閉包聯手便可以天下無敵
</script>
</body>
</html>
運行效果:
理解閉包:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>01_理解閉包</title>
</head>
<body>
<!--
1. 如何產生閉包?
* 當一個嵌套的內部(子)函數引用了嵌套的外部(父)函數的變量(函數)時, 就產生了閉包
2. 閉包到底是什麼?
* 使用chrome調試查看
* 理解一: 閉包是嵌套的內部函數(絕大部分人)
* 理解二: 包含被引用變量(函數)的對象(極少數人)
* 注意: 閉包存在於嵌套的內部函數中
3. 產生閉包的條件?
* 函數嵌套
* 內部函數引用了外部函數的數據(變量/函數)
-->
<script type="text/javascript">
function fn1() {
var a = 2;
var b = 'abc';
function fn2() { //執行函數定義就會產生閉包(不用調用內部函數)
console.log(a);
}
// fn2();
}
fn1();
function fun1() {
var a = 3;
var fun2 = function () {
console.log(a);
}
}
fun1();
</script>
</body>
</html>
常見的閉包:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>02_常見的閉包</title>
</head>
<body>
<!--
1. 將函數作爲另一個函數的返回值
2. 將函數作爲實參傳遞給另一個函數調用
-->
<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
// 2. 將函數作爲實參傳遞給另一個函數調用
function showDelay(msg, time) {
setTimeout(function () {
alert(msg)
}, time)
}
showDelay('atguigu', 2000)
</script>
</body>
</html>
閉包的作用:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>03_閉包的作用</title>
</head>
<body>
<!--
1. 使用函數內部的變量在函數執行完後, 仍然存活在內存中(延長了局部變量的生命週期)
2. 讓函數外部可以操作(讀寫)到函數內部的數據(變量/函數)
問題:
1. 函數執行完後, 函數內部聲明的局部變量是否還存在? 一般是不存在, 存在於閉中的變量纔可能存在
2. 在函數外部能直接訪問函數內部的局部變量嗎? 不能, 但我們可以通過閉包讓外部操作它
-->
<script type="text/javascript">
function fn1() {
var a = 2
function fn2() {
a++
console.log(a)
// return a
}
function fn3() {
a--
console.log(a)
}
return fn3
}
var f = fn1()
f() // 1
f() // 0
</script>
</body>
</html>
閉包的生命週期:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>04_閉包的生命週期</title>
</head>
<body>
<!--
1. 產生: 在嵌套內部函數定義執行完時就產生了(不是在調用)
2. 死亡: 在嵌套的內部函數成爲垃圾對象時
-->
<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 //閉包死亡(包含閉包的函數對象成爲垃圾對象)
</script>
</body>
</html>
利用閉包的實際例子(返回價格的區間元素)
let lessons = [
{
title: "媒體查詢響應式佈局",
click: 89,
price: 12
},
{
title: "FLEX 彈性盒模型",
click: 45,
price: 120
},
{
title: "GRID 柵格系統",
click: 19,
price: 67
},
{
title: "盒子模型詳解",
click: 29,
price: 300
}
];
function between(a, b) {
return function(v) {
return v.price >= a && v.price <= b;
}
}
console.table(lessons.filter(between(10, 100)));
執行結果
移動動畫的抖動和加速(閉包應用,動畫演示)
<!DOCTYPE html>
<html lang="en">
<body>
<style>
button {
position: absolute;
}
</style>
<button>按鈕</button>
</body>
<script>
let btns = document.querySelectorAll("button");
btns.forEach(function (item) {
let bind = false;
item.addEventListener("click", function () {
// if (!bind) {
let left = 1;
bind = setInterval(function () {
item.style.left = left++ + "px";
}, 100);
// }
});
});
</script>
</html>
運行結果
這居然?鬼畜了???
就是因爲你的left寫在了click回調函數裏面。因爲每點擊一次就會創建一塊function空間,裏面left變量去定時改變style,每改變一次style.left就會導致一次迴流從而再渲染一次。每次點擊left初始值爲1,上一次的已經爲+了很多次,上上次的已經爲+了非常多次。渲染的時候你就會看到一會1px一會很多px的鬼畜情況,也就是動畫抖動。
那麼可以把left變量提到click上面一行就解決了吧?
......
let bind = false;
let left = 1;
item.addEventListener("click", function () {
// if (!bind) {
bind = setInterval(function () {
item.style.left = left++ + "px";
}, 100);
// }
});
......
運行結果
這???居然加速了 ,越來越快了!!!因爲每點擊一次就會有一個定時器100ms輪詢改變left變量,這個left變量對於click回調函數來說是的共有的一塊作用域。所以越來越多的定時器不斷的left++,你就看到了加速現象。
正確做法如下:
<!DOCTYPE html>
<html lang="en">
<body>
<style>
button {
position: absolute;
}
</style>
<button>按鈕</button>
</body>
<script>
let btns = document.querySelectorAll("button");
btns.forEach(function (item) {
let bind = false;
item.addEventListener("click", function () {
if (!bind) {
let left = 1;
bind = setInterval(function () {
item.style.left = left++ + "px";
}, 100);
}
});
});
</script>
</html>
現象就正常了,沒有抖動也沒有加速。
根據閉包進行傳入字段排序(通用排序)
<script>
let lessons = [
{
title: "媒體查詢響應式佈局",
click: 89,
price: 12
},
{
title: "FLEX 彈性盒模型",
click: 45,
price: 120
},
{
title: "GRID 柵格系統",
click: 19,
price: 67
},
{
title: "盒子模型詳解",
click: 29,
price: 300
}
];
function order(field, type = 'asc') { // 默認asc升序
return (a, b) => {
if (type == "asc") return a[field] > b[field] ? 1 : -1;
return a[field] > b[field] ? -1 : 1;
}
}
console.table(lessons.sort(order("price"))); // order("price", "desc")可以降序
</script>
閉包內存泄漏的解決方法
閉包特性中上級作用域會爲函數保存數據,從而造成的如下所示的內存泄漏問題
<!DOCTYPE html>
<html lang="en">
<body>
<div desc="zaixianxuexi">在線學習</div>
<div desc="kaiyuanchanpin">開源產品</div>
</body>
<script>
let divs = document.querySelectorAll("div");
divs.forEach(function (item) {
item.addEventListener("click", function () {
console.log(item.getAttribute("desc"));
});
});
</script>
</html>
下面通過清除不需要的數據解決內存泄漏問題
let divs = document.querySelectorAll("div");
divs.forEach(function(item) {
let desc = item.getAttribute("desc");
item.addEventListener("click", function() {
console.log(desc);
});
item = null;
});
再給一個例子加深印象
<script type="text/javascript">
function fn1() {
var arr = new Array[100000]
function fn2() {
console.log(arr.length)
}
return fn2
}
var f = fn1()
f()
f = null //讓內部函數成爲垃圾對象-->回收閉包
</script>
閉包導致的this遺留問題(教你判斷this指向口訣)
let hd = {
user: "後盾人",
get: function() {
console.log(this); // 這裏的this是hd對象
return function() { // 這裏的this是window對象
return this.user;
};
}
};
var a = hd.get(); // 執行get(),返回裏面的function
// this 總是指向調用該函數的對象,即函數在搜索this時只會搜索到當前活動對象。
// 下面是函數因爲是在全局環境下調用的,所以this指向window
console.log(a()); // this.user爲undefined,因爲this指向window
this 總是指向調用該函數的對象,即函數在搜索this時只會搜索到當前活動對象。所以這裏get()裏面的function的this是Window。
順帶總結一下this記憶方法
如果你定義的是對象,對象裏面定義了函數,這個函數叫做方法,方法裏面的this是指向當前對象,如果方法裏面還有函數2,那麼函數2的this指向Window,理由就是上面這個例子。
如果你定義的函數,而你執行的時候是new 函數,那麼認爲你創建了對象,this判斷同上。
如果你定義的函數,執行的時候直接調用,比如function a(){...},調用爲a(),那麼a裏面的this指向Window
上面犀利的總結一般人我不告訴他。
閉包的其他應用 : 定義JS模塊
閉包的自定義js模塊:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>05_閉包的應用_自定義JS模塊</title>
</head>
<body>
<!--
閉包的應用2 : 定義JS模塊
* 具有特定功能的js文件
* 將所有的數據和功能都封裝在一個函數內部(私有的)
* 只向外暴露一個包信n個方法的對象或函數
* 模塊的使用者, 只需要通過模塊暴露的對象調用方法來實現對應的功能
-->
<script type="text/javascript" src="myModule.js"></script>
<script type="text/javascript">
var module = myModule()
module.doSomething()
module.doOtherthing()
</script>
</body>
</html>
myModule.js
function myModule() {
//私有數據
var msg = 'My atguigu'
//操作數據的函數
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>
<!--
閉包的應用2 : 定義JS模塊
* 具有特定功能的js文件
* 將所有的數據和功能都封裝在一個函數內部(私有的)
* 只向外暴露一個包信n個方法的對象或函數
* 模塊的使用者, 只需要通過模塊暴露的對象調用方法來實現對應的功能
-->
<script type="text/javascript" src="myModule2.js"></script>
<script type="text/javascript">
myModule2.doSomething()
myModule2.doOtherthing()
</script>
</body>
</html>
myModule2.js
(function () {
//私有數據
var msg = 'My atguigu'
//操作數據的函數
function doSomething() {
console.log('doSomething() '+msg.toUpperCase())
}
function doOtherthing () {
console.log('doOtherthing() '+msg.toLowerCase())
}
//向外暴露對象(給外部使用的方法)
window.myModule2 = {
doSomething: doSomething,
doOtherthing: doOtherthing
}
})()
這個應用還有新玩法,見這裏: js除了立即執行函數,你還可以這麼玩 (預計閱讀 1 min)
關注、留言,我們一起學習。
===============Talk is cheap, show me the code================