1.閉包的概念
????在對作用域寓搬,作用域鏈的概念進行討論時我們知道杂彭,一般情況下定義在函數(shù)內部的變量在函數(shù)外部是不可訪問的滥嘴。但某些時候有又確實有這樣的需求木蹬,這時就會用到閉包。閉包若皱,就是能夠讀取其他函數(shù)內部變量的函數(shù)镊叁。這就是閉包的概念。通過閉包我們可以在一個函數(shù)內部訪問另一個函數(shù)內部的變量走触。
2.閉包的形式
下面介紹閉包的形式晦譬,也就是訪問函數(shù)內部變量的常見手段。
1 函數(shù)返回值為函數(shù)
function foo (){
let name = 'xiaom'
return function (){
return name
}
}
const bar = foo()
console.log(bar())
// 全局變量bar獲取到了局部作用域foo的內部變量,這是最常見的形式
2 內部函數(shù)賦給外部變量
let num;
function foo() {
const _num = 18;
function bar() {
return _num;
}
num = bar;
}
foo();
console.log(num()); // 18
3 通過立即執(zhí)行函數(shù)行成獨立作用域互广,保存變量(es6之后使用let敛腌,const替代)。
下面是一個經典例子惫皱。
for (var i = 1; i <= 5; i++) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
}
// 上述代碼的預期輸出結果是每隔一秒按順序輸出12345像樊。
// 根據(jù)前面討論過的事件循環(huán)機制,定時器任務會在同步任務執(zhí)行完畢后再執(zhí)行旅敷。因此此時的i已經變成6 會直接輸出5個6生棍。
// 解決這一問題的關鍵就是每次循環(huán)形成一個獨立作用域,這樣定時器中的操作執(zhí)行時會訪問對應作用域的變量媳谁。
for (var i = 1; i <= 5; i++) {
// 包一層立即執(zhí)行函數(shù) 并傳入i 由于內部操作用到了i 因此會形成閉包
(function (i) {
setTimeout(function timer() {
console.log(i);
}, i * 1000);
})(i)
閉包函數(shù)的其他形式大多是以上形式的變體涂滴。
3.閉包的優(yōu)缺點
通過上述例子可以總結出閉包的幾大優(yōu)點
- 1.外部可以訪問函數(shù)內部變量。
- 2.讓函數(shù)內部變量一直保留在內存中韩脑。
function fn1(){
let count = 0;
function fn2 (){
count++
return count
}
return fn2
}
let result1 = fn1()
console.log(result1()) // 1
console.log(result1()) // 2
//通常來講氢妈,函數(shù)執(zhí)行完畢后粹污,函數(shù)連同它內部的變量會被一同銷毀段多。
//由于函數(shù)fn內部變量count被外部引用,因此fn執(zhí)行完畢后壮吩,其內部變量count不會被銷毀进苍。因此過度使用閉包會造成內存消耗加缘。
- 3.形成獨立作用域。
????顯然觉啊,通過上述第二點也能看出拣宏,由于閉包會使函數(shù)內部變量一直保存在內存中,造成內存消耗杠人,因此過度使用會造成頁面性能問題勋乾。解決方法是及時刪除不使用的局部變量。
4.閉包的應用—柯里化函數(shù)
下面介紹閉包的一個典型應用:柯里化函數(shù)嗡善。介紹柯里化之前需要先了解高階函數(shù)的概念辑莫。
高階函數(shù),是對其他函數(shù)進行操作的函數(shù)罩引,可以將它們作為參數(shù)或返回它們各吨。
通俗的講,滿足以下條件之一的函數(shù)就是高階函數(shù):
- 接受參數(shù)為函數(shù)
- 返回值為函數(shù)
下面是一個簡單例子袁铐,利用高階函數(shù)為傳入的函數(shù)綁定this指向揭蜒。他同時滿足上述兩個條件。
function foo() {
console.log(this.name);
}
const obj = {
name: "xiaom",
};
const obj1 = {
name: "xiaoh"
}
function bindThis(fn, obj) {
return fn.bind(obj);
}
bindThis(foo, obj)(); //xiaom
bindThis(foo, obj1)(); //xiaoh
柯里化就是一種特殊的高階函數(shù)剔桨,下面介紹柯里化函數(shù)的概念屉更。
定義:柯里化(Currying)是把接受多個參數(shù)的函數(shù)變換成接受一個單一參數(shù)(最初函數(shù)的第一個參數(shù))的函數(shù),并且返回接受余下的參數(shù)且返回結果的新函數(shù)的技術领炫。
直接看定義非撑伎澹晦澀難懂,我們把上述例子稍加改造帝洪。
function foo() {
console.log(this.name);
}
const obj = {
name: "xiaom",
};
const obj1 = {
name: "xiaoh"
}
function curryingFn(fn) {
return (obj) => {
return fn.bind(obj)
}
}
const newFoo = curryingFn(foo)
newFoo(obj)() // xiaom
newFoo(obj1)() // xiaoh
????上述操作就是把原先接受兩個參數(shù)的函數(shù)變成先接受一個參數(shù)似舵,再返回一個函數(shù)去處理剩余的參數(shù)〈邢浚可以看到砚哗,柯里化函數(shù)的形式恰好符合閉包函數(shù)的第一種形式。而柯里化函數(shù)的優(yōu)勢就是參數(shù)復用砰奕。試想蛛芥,就上述例子而言,當我們需要多次改變fn的指向時就無需每次都傳入fn军援,只需傳入需要綁定的對象即可仅淑。
下面再介紹幾個利用柯里化函數(shù)進行參數(shù)復用的典型例子。
- 正則判斷
如下胸哥,封裝一個正則判斷的函數(shù)涯竟,傳入正則表達式和目標字符串,返回判斷結果。沒有問題庐船。
function check(targetString, reg) {
return reg.test(targetString);
}
check(/^1[34578]\d{9}$/, '14900000088');
check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, 'test@163.com');
當業(yè)務需求只是判斷手機號或判斷郵箱時银酬,仍需要每次都傳入相應的正則這就很低效,因此可以使用柯里化函數(shù)再次進行封裝筐钟。
function curring(reg) {
return (str) => {
return reg.test(str);
};
}
var checkPhone = curring(/^1[34578]\d{9}$/);
var checkEmail = curring(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);
console.log(checkPhone("183888888")); // false
console.log(checkPhone("17654239819")); // true
console.log(checkEmail("exy@163.com")); // true
- 判斷html標簽
????我們知道Vue中的自定義組件在模板中是可以用html標簽的形式書寫的揩瞪。那么vue碰到一個標簽,如何知道他是html標簽還是自定義組件呢篓冲?常規(guī)的思路是李破,html標簽類型就那幾十種。將其存入數(shù)組中壹将,每碰到一個標簽就判斷其是否在該數(shù)組中即可喷屋。但這種方式缺點也很明顯,每次都要循環(huán)數(shù)組瞭恰,非常消耗性能屯曹。我們可以將數(shù)組結構轉為字典。
let set = {};
tags.forEach( key => set[ key ] = true )
進一步優(yōu)化惊畏,將該標簽集合以參數(shù)的形式傳入恶耽。這樣就封裝了一個通用函數(shù),它可以判斷某個元素是否在指定集合中颜启。
let tags = "div,p,a,img,ul,li".split(",");
function makeMap(keys) {
let set = {};
tags.forEach((key) => (set[key] = true));
return function (tagName) {
return !!set[tagName.toLowerCase()];
};
}
let isHTMLTag = makeMap(tags);
console.log(isHTMLTag('Menu')) // false
console.log(isHTMLTag('div')) // true
- 自定義封裝bind方法
相較于call和apply,bind是永久性的改變this指向偷俭,相當于復用了使用call/apply時傳入的目標對象,因此可以使用柯里化函數(shù)封裝缰盏。
function foo(action) {
console.log(this.name + action);
}
const obj1 = {
name: "小明",
};
foo.__proto__.myBind = function (obj) {
const fun = this;
return function (args) {
fun.call(obj, args);
};
};
const newFoo = foo.myBind(obj1);
newFoo("跑步"); // 小明跑步
????柯里化函數(shù)的優(yōu)勢遠不止這些涌萤。我們重點要理解柯里化函數(shù)的設計思想及其應用場景。在實際業(yè)務中遇到一些固定的操作口猜,需要復用的數(shù)據(jù)负溪,或為函數(shù)擴展功能時,就可以考慮使用柯里化函數(shù)济炎〈眨柯里化的更多優(yōu)勢還需再實際編碼中進行體會。
參考鏈接:http://www.reibang.com/p/5e1899fe7d6b