淺談JavaScript閉包與柯里化函數(shù)

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末须尚,一起剝皮案震驚了整個濱河市崖堤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌耐床,老刑警劉巖密幔,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異撩轰,居然都是意外死亡胯甩,警方通過查閱死者的電腦和手機淤年,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜡豹,“玉大人,你說我怎么就攤上這事溉苛【盗” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵愚战,是天一觀的道長娇唯。 經常有香客問我,道長寂玲,這世上最難降的妖魔是什么塔插? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮拓哟,結果婚禮上想许,老公的妹妹穿的比我還像新娘。我一直安慰自己断序,他們只是感情好流纹,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著违诗,像睡著了一般漱凝。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上诸迟,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天茸炒,我揣著相機與錄音,去河邊找鬼阵苇。 笑死壁公,一個胖子當著我的面吹牛,可吹牛的內容都是我干的绅项。 我是一名探鬼主播贮尖,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼趁怔!你這毒婦竟也來了湿硝?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤润努,失蹤者是張志新(化名)和其女友劉穎关斜,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铺浇,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡痢畜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片丁稀。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡吼拥,死狀恐怖,靈堂內的尸體忽然破棺而出线衫,到底是詐尸還是另有隱情凿可,我是刑警寧澤,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布授账,位于F島的核電站枯跑,受9級特大地震影響,放射性物質發(fā)生泄漏白热。R本人自食惡果不足惜敛助,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望屋确。 院中可真熱鬧纳击,春花似錦、人聲如沸攻臀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茵烈。三九已至百匆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呜投,已是汗流浹背加匈。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留仑荐,地道東北人雕拼。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像粘招,于是被迫代替她去往敵國和親啥寇。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容