成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

你真的懂 JavaScript 閉包與高階函數(shù)嗎?

開發(fā) 前端
如果公民分等級,一等公民什么都可以做,次等公民這不能做那不能做。JavaScript的函數(shù)也是對象,可以有屬性,可以賦值給一個變量,可以放在數(shù)組里作為元素,可以作為其他對象的屬性,什么都可以做,別的對象能做的它能做,別的對象不能做的它也能做。這不就是一等公民的地位嘛。

[[377634]]

本文轉(zhuǎn)載自微信公眾號「前端下午茶」,作者SHERlocked93。轉(zhuǎn)載本文請聯(lián)系前端下午茶公眾號。

「JavaScript 中,函數(shù)是一等公民」,在各種書籍和文章中我們總能看到這句話。

既然有一等,那么當(dāng)然也有次等了。

如果公民分等級,一等公民什么都可以做,次等公民這不能做那不能做。JavaScript的函數(shù)也是對象,可以有屬性,可以賦值給一個變量,可以放在數(shù)組里作為元素,可以作為其他對象的屬性,什么都可以做,別的對象能做的它能做,別的對象不能做的它也能做。這不就是一等公民的地位嘛。

— 程墨Morgan

所以它的含義是:函數(shù)和其他普通對象一樣,其上有屬性也有方法,普通對象能做的,函數(shù)也可以做。

正因為在 JavaScript 中的極大自由,函數(shù)被賦予了卓越的表達力和靈活性,但是也產(chǎn)生了很多讓人抓耳撓腮的問題。本文我們就一起討論一下最常遇見的兩個與函數(shù)密切相關(guān)的概念:閉包和高階函數(shù)。這兩個概念在之后設(shè)計模式的文章中也會經(jīng)常碰見。

注意: 本文屬于基礎(chǔ)篇,如果你已經(jīng)對本文相關(guān)知識點已經(jīng)很了解了,那么可以跳過本文。如果你不夠了解,或者了解的還不完整,那么可以通過本文來復(fù)習(xí)一下 ~

1. 閉包

1.1 什么是閉包

當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。

我們首先來看一個閉包的例子:

  1. function foo() { 
  2.     var a = 2 
  3.      
  4.     function bar() { 
  5.         console.log(a) 
  6.     } 
  7.      
  8.     return bar 
  9.  
  10. var baz = foo() 
  11.  
  12. baz()            // 輸出: 2 

foo 函數(shù)傳遞出了一個函數(shù) bar,傳遞出來的 bar 被賦值給 baz 并調(diào)用,雖然這時 baz 是在 foo 作用域外執(zhí)行的,但 baz 在調(diào)用的時候可以訪問到前面的 bar 函數(shù)所在的 foo 的內(nèi)部作用域。

由于 bar 聲明在 foo 函數(shù)內(nèi)部,bar 擁有涵蓋 foo 內(nèi)部作用域的閉包,使得 foo 的內(nèi)部作用域一直存活不被回收。一般來說,函數(shù)在執(zhí)行完后其整個內(nèi)部作用域都會被銷毀,因為 JavaScript 的 GC(Garbage Collection)垃圾回收機制會自動回收不再使用的內(nèi)存空間。但是閉包會阻止某些 GC,比如本例中 foo() 執(zhí)行完,因為返回的 bar 函數(shù)依然持有其所在作用域的引用,所以其內(nèi)部作用域不會被回收。

注意: 如果不是必須使用閉包,那么盡量避免創(chuàng)建它,因為閉包在處理速度和內(nèi)存消耗方面對性能具有負(fù)面影響。

1.2 利用閉包實現(xiàn)結(jié)果緩存(備忘模式)

備忘模式就是應(yīng)用閉包的特點的一個典型應(yīng)用。比如有個函數(shù):

  1. function add(a) { 
  2.     return a + 1; 

多次運行 add() 時,每次得到的結(jié)果都是重新計算得到的,如果是開銷很大的計算操作的話就比較消耗性能了,這里可以對已經(jīng)計算過的輸入做一個緩存。

所以這里可以利用閉包的特點來實現(xiàn)一個簡單的緩存,在函數(shù)內(nèi)部用一個對象存儲輸入的參數(shù),如果下次再輸入相同的參數(shù),那就比較一下對象的屬性,如果有緩存,就直接把值從這個對象里面取出來。

  1. /* 備忘函數(shù) */ 
  2. function memorize(fn) { 
  3.     var cache = {} 
  4.     return function() { 
  5.         var args = Array.prototype.slice.call(arguments) 
  6.         var key = JSON.stringify(args) 
  7.         return cache[key] || (cache[key] = fn.apply(fn, args)) 
  8.     } 
  9.  
  10. /* 復(fù)雜計算函數(shù) */ 
  11. function add(a) { 
  12.     return a + 1 
  13.  
  14. var adder = memorize(add
  15.  
  16. adder(1)            // 輸出: 2    當(dāng)前: cache: { '[1]': 2 } 
  17. adder(1)            // 輸出: 2    當(dāng)前: cache: { '[1]': 2 } 
  18. adder(2)            // 輸出: 3    當(dāng)前: cache: { '[1]': 2, '[2]': 3 } 

使用 ES6 的方式會更優(yōu)雅一些:

  1. /* 備忘函數(shù) */ 
  2. function memorize(fn) { 
  3.     const cache = {} 
  4.     return function(...args) { 
  5.         const key = JSON.stringify(args) 
  6.         return cache[key] || (cache[key] = fn.apply(fn, args)) 
  7.     } 
  8.  
  9. /* 復(fù)雜計算函數(shù) */ 
  10. function add(a) { 
  11.     return a + 1 
  12.  
  13. const adder = memorize(add
  14.  
  15. adder(1)            // 輸出: 2    當(dāng)前: cache: { '[1]': 2 } 
  16. adder(1)            // 輸出: 2    當(dāng)前: cache: { '[1]': 2 } 
  17. adder(2)            // 輸出: 3    當(dāng)前: cache: { '[1]': 2, '[2]': 3 } 

稍微解釋一下:

備忘函數(shù)中用 JSON.stringify 把傳給 adder 函數(shù)的參數(shù)序列化成字符串,把它當(dāng)做 cache 的索引,將 add 函數(shù)運行的結(jié)果當(dāng)做索引的值傳遞給 cache,這樣 adder 運行的時候如果傳遞的參數(shù)之前傳遞過,那么就返回緩存好的計算結(jié)果,不用再計算了,如果傳遞的參數(shù)沒計算過,則計算并緩存 fn.apply(fn, args),再返回計算的結(jié)果。

當(dāng)然這里的實現(xiàn)如果要實際應(yīng)用的話,還需要繼續(xù)改進一下,比如:

緩存不可以永遠擴張下去,這樣太耗費內(nèi)存資源,我們可以只緩存最新傳入的 n 個;

在瀏覽器中使用的時候,我們可以借助瀏覽器的持久化手段,來進行緩存的持久化,比如 cookie、localStorage 等;

這里的復(fù)雜計算函數(shù)可以是過去的某個狀態(tài),比如對某個目標(biāo)的操作,這樣把過去的狀態(tài)緩存起來,方便地進行狀態(tài)回退。

復(fù)雜計算函數(shù)也可以是一個返回時間比較慢的異步操作,這樣如果把結(jié)果緩存起來,下次就可以直接從本地獲取,而不是重新進行異步請求。

注意: cache 不可以是 Map,因為 Map 的鍵是使用 === 比較的,因此當(dāng)傳入引用類型值作為鍵時,雖然它們看上去是相等的,但實際并不是,比如 [1]!==[1],所以還是被存為不同的鍵。

  1. //  X 錯誤示范 
  2. function memorize(fn) {         
  3.   const cache = new Map() 
  4.   return function(...args) { 
  5.     return cache.get(args) || cache.set(args, fn.apply(fn, args)).get(args) 
  6.   } 
  7.  
  8. function add(a) { 
  9.   return a + 1 
  10.  
  11. const adder = memorize(add
  12.  
  13. adder(1)    // 2    cache: { [ 1 ] => 2 } 
  14. adder(1)    // 2    cache: { [ 1 ] => 2, [ 1 ] => 2 } 
  15. adder(2)    // 3    cache: { [ 1 ] => 2, [ 1 ] => 2, [ 2 ] => 3 } 

2. 高階函數(shù)

高階函數(shù)就是輸入?yún)?shù)里有函數(shù),或者輸出是函數(shù)的函數(shù)。

2.1 函數(shù)作為參數(shù)

如果你用過 setTimeout、setInterval、ajax 請求,那么你已經(jīng)用過高階函數(shù)了,這是我們最常看到的場景:回調(diào)函數(shù),因為它將函數(shù)作為參數(shù)傳遞給另一個函數(shù)。

比如 ajax 請求中,我們通常使用回調(diào)函數(shù)來定義請求成功或者失敗時的操作邏輯:

  1. $.ajax("/request/url"function(result){ 
  2.     console.log("請求成功!"
  3. }) 

在 Array、Object、String 等等基本對象的原型上有很多操作方法,可以接受回調(diào)函數(shù)來方便地進行對象操作。這里舉一個很常用的 Array.prototype.filter() 方法,這個方法返回一個新創(chuàng)建的數(shù)組,包含所有回調(diào)函數(shù)執(zhí)行后返回 true 或真值的數(shù)組元素。

  1. var words = ['spray''limit''elite''exuberant''destruction''present']; 
  2.  
  3. var result = words.filter(function(word) { 
  4.     return word.length > 6 
  5. })       // 輸出: ["exuberant""destruction""present"

回調(diào)函數(shù)還有一個應(yīng)用就是鉤子,如果你用過 Vue 或者 React 等框架,那么你應(yīng)該對鉤子很熟悉了,它的形式是這樣的:

  1. function foo(callback) { 
  2.     // ... 一些操作 
  3.     callback() 

2.2 函數(shù)作為返回值

另一個經(jīng)??吹降母唠A函數(shù)的場景是在一個函數(shù)內(nèi)部輸出另一個函數(shù),比如:

  1. function foo() { 
  2.     return function bar() {} 

主要是利用閉包來保持著作用域:

  1. function add() { 
  2.     var num = 0 
  3.     return function(a) { 
  4.         return num = num + a 
  5.     } 
  6. var adder = add() 
  7.  
  8. adder(1)     // 輸出: 1 
  9. adder(2)     // 輸出: 3 

1. 柯里化

柯里化(Currying),又稱部分求值(Partial Evaluation),是把接受多個參數(shù)的原函數(shù)變換成接受一個單一參數(shù)(原函數(shù)的第一個參數(shù))的函數(shù),并且返回一個新函數(shù),新函數(shù)能夠接受余下的參數(shù),最后返回同原函數(shù)一樣的結(jié)果。

核心思想是把多參數(shù)傳入的函數(shù)拆成單(或部分)參數(shù)函數(shù),內(nèi)部再返回調(diào)用下一個單(或部分)參數(shù)函數(shù),依次處理剩余的參數(shù)。

柯里化有3個常見作用:

  • 參數(shù)復(fù)用
  • 提前返回
  • 延遲計算/運行

先來看看柯里化的通用實現(xiàn):

  1. // ES5 方式 
  2. function currying(fn) { 
  3.     var rest1 = Array.prototype.slice.call(arguments) 
  4.     rest1.shift() 
  5.     return function() { 
  6.         var rest2 = Array.prototype.slice.call(arguments) 
  7.         return fn.apply(null, rest1.concat(rest2)) 
  8.     } 
  9.  
  10. // ES6 方式 
  11. function currying(fn, ...rest1) { 
  12.   return function(...rest2) { 
  13.     return fn.apply(null, rest1.concat(rest2)) 
  14.   } 

用它將一個 sayHello 函數(shù)柯里化試試:

  1. // 接上面 
  2. function sayHello(name, age, fruit) { 
  3.   console.log(console.log(`我叫 ${name},我 ${age} 歲了, 我喜歡吃 ${fruit}`)) 
  4.  
  5. var curryingShowMsg1 = currying(sayHello, '小明'
  6. curryingShowMsg1(22, '蘋果')           // 輸出: 我叫 小明,我 22 歲了, 我喜歡吃 蘋果 
  7.  
  8. var curryingShowMsg2 = currying(sayHello, '小衰', 20) 
  9. curryingShowMsg2('西瓜')               // 輸出: 我叫 小衰,我 20 歲了, 我喜歡吃 西瓜 

更高階的用法參見:JavaScript 函數(shù)式編程技巧 - 柯里化

2. 反柯里化

柯里化是固定部分參數(shù),返回一個接受剩余參數(shù)的函數(shù),也稱為部分計算函數(shù),目的是為了縮小適用范圍,創(chuàng)建一個針對性更強的函數(shù)。核心思想是把多參數(shù)傳入的函數(shù)拆成單參數(shù)(或部分)函數(shù),內(nèi)部再返回調(diào)用下一個單參數(shù)(或部分)函數(shù),依次處理剩余的參數(shù)。

而反柯里化,從字面講,意義和用法跟函數(shù)柯里化相比正好相反,擴大適用范圍,創(chuàng)建一個應(yīng)用范圍更廣的函數(shù)。使本來只有特定對象才適用的方法,擴展到更多的對象。

先來看看反柯里化的通用實現(xiàn)吧~

  1. // ES5 方式 
  2. Function.prototype.unCurrying = function() { 
  3.   var self = this 
  4.   return function() { 
  5.     var rest = Array.prototype.slice.call(arguments) 
  6.     return Function.prototype.call.apply(self, rest) 
  7.   } 
  8.  
  9. // ES6 方式 
  10. Function.prototype.unCurrying = function() { 
  11.   const self = this 
  12.   return function(...rest) { 
  13.     return Function.prototype.call.apply(self, rest) 
  14.   } 

如果你覺得把函數(shù)放在 Function 的原型上不太好,也可以這樣:

  1. // ES5 方式 
  2. function unCurrying(fn) { 
  3.   return function (tar) { 
  4.     var rest = Array.prototype.slice.call(arguments) 
  5.     rest.shift() 
  6.     return fn.apply(tar, rest) 
  7.   } 
  8.  
  9. // ES6 方式 
  10. function unCurrying(fn) { 
  11.   return function(tar, ...argu) { 
  12.     return fn.apply(tar, argu) 
  13.   } 

下面簡單試用一下反柯里化通用實現(xiàn),我們將 Array 上的 push 方法借出來給 arguments 這樣的類數(shù)組增加一個元素:

  1. // 接上面 
  2. var push = unCurrying(Array.prototype.push) 
  3.  
  4. function execPush() { 
  5.   push(arguments, 4) 
  6.   console.log(arguments) 
  7.  
  8. execPush(1, 2, 3)    // 輸出: [1, 2, 3, 4] 

簡單說,函數(shù)柯里化就是對高階函數(shù)的降階處理,縮小適用范圍,創(chuàng)建一個針對性更強的函數(shù)。

  1. function(arg1, arg2)              // => function(arg1)(arg2) 
  2. function(arg1, arg2, arg3)        // => function(arg1)(arg2)(arg3) 
  3. function(arg1, arg2, arg3, arg4)  // => function(arg1)(arg2)(arg3)(arg4) 
  4. function(arg1, arg2, ..., argn)   // => function(arg1)(arg2)…(argn) 

而反柯里化就是反過來,增加適用范圍,讓方法使用場景更大。使用反柯里化, 可以把原生方法借出來,讓任何對象擁有原生對象的方法。

  1. obj.func(arg1, arg2)        // => func(obj, arg1, arg2) 

可以這樣理解柯里化和反柯里化的區(qū)別:

柯里化是在運算前提前傳參,可以傳遞多個參數(shù);

反柯里化是延遲傳參,在運算時把原來已經(jīng)固定的參數(shù)或者 this 上下文等當(dāng)作參數(shù)延遲到未來傳遞。

更高階的用法參見:JavaScript 函數(shù)式編程技巧 - 反柯里化

3. 偏函數(shù)

偏函數(shù)是創(chuàng)建一個調(diào)用另外一個部分(參數(shù)或變量已預(yù)制的函數(shù))的函數(shù),函數(shù)可以根據(jù)傳入的參數(shù)來生成一個真正執(zhí)行的函數(shù)。其本身不包括我們真正需要的邏輯代碼,只是根據(jù)傳入的參數(shù)返回其他的函數(shù),返回的函數(shù)中才有真正的處理邏輯比如:

  1. var isType = function(type) { 
  2.   return function(obj) { 
  3.     return Object.prototype.toString.call(obj) === `[object ${type}]` 
  4.   } 
  5.  
  6. var isString = isType('String'
  7. var isFunction = isType('Function'

這樣就用偏函數(shù)快速創(chuàng)建了一組判斷對象類型的方法~

偏函數(shù)和柯里化的區(qū)別:

  1. 柯里化是把一個接受 n 個參數(shù)的函數(shù),由原本的一次性傳遞所有參數(shù)并執(zhí)行變成了可以分多次接受參數(shù)再執(zhí)行,例如:add = (x, y, z) => x + y + z→curryAdd = x => y => z => x + y + z;
  2. 偏函數(shù)固定了函數(shù)的某個部分,通過傳入的參數(shù)或者方法返回一個新的函數(shù)來接受剩余的參數(shù),數(shù)量可能是一個也可能是多個;

當(dāng)一個柯里化函數(shù)只接受兩次參數(shù)時,比如 curry()(),這時的柯里化函數(shù)和偏函數(shù)概念類似,可以認(rèn)為偏函數(shù)是柯里化函數(shù)的退化版。

原文鏈接:https://mp.weixin.qq.com/s/pv-EYBLpzwuC9Vkkt9gNZw

 

責(zé)任編輯:武曉燕 來源: 前端下午茶
相關(guān)推薦

2019-05-13 14:17:06

抓包Web安全漏洞

2019-10-18 09:50:47

網(wǎng)絡(luò)分層模型網(wǎng)絡(luò)協(xié)議

2020-10-14 15:15:28

JavaScript(

2019-09-15 10:38:28

網(wǎng)絡(luò)分層模型

2023-11-29 08:03:05

2019-11-06 09:52:01

JavaScript單線程非阻塞

2021-08-30 15:41:13

Kafka運維數(shù)據(jù)

2017-11-07 12:35:53

比特幣區(qū)塊鏈虛擬貨幣

2020-03-29 08:27:05

Promise異步編程前端

2022-09-22 14:55:31

前端JavaScripthis

2022-09-26 13:10:17

JavaScriptthis

2021-02-21 16:21:19

JavaScript閉包前端

2021-04-07 19:44:27

JavaStringHashMap

2018-07-17 16:26:17

大數(shù)據(jù)營銷消費者

2016-01-07 11:18:50

用戶畫像

2011-05-25 14:48:33

Javascript閉包

2015-12-22 11:48:50

javascript閉包

2017-05-31 08:45:03

2023-07-11 08:46:38

閉包函數(shù)Rust

2023-01-09 08:00:41

JavaScript閉包
點贊
收藏

51CTO技術(shù)棧公眾號

主站蜘蛛池模板: 国产在线精品一区二区三区 | 在线观看欧美日韩视频 | 91麻豆精品一区二区三区 | 日韩中文字幕 | 欧美激情一区二区三级高清视频 | 91人人爽 | 中国大陆高清aⅴ毛片 | 久久久久99 | 久久久久久久久久影视 | 欧美视频在线观看 | 亚洲 中文 欧美 | 精品国产乱码久久久久久蜜臀 | 日韩波多野结衣 | 久草福利| 色av一区二区三区 | 亚洲成人精品在线 | 日日摸夜夜爽人人添av | 久久高潮| 黄色在线免费观看 | 国产成人综合网 | 台湾a级理论片在线观看 | www精品美女久久久tv | 一区二区三区电影在线观看 | 天天综合网天天综合 | 亚洲一区免费 | 久久久久久99 | 日本高清aⅴ毛片免费 | 成人看片在线观看 | 欧美另类视频在线 | 国产精品亚洲精品 | 亚洲免费网 | 九九国产在线观看 | 亚洲人成在线观看 | 欧美一级免费 | 91视频观看 | 91欧美激情一区二区三区成人 | 超碰男人天堂 | 日韩一级电影免费观看 | 国产精品区二区三区日本 | 亚洲精品久久久久久国产精华液 | 高清久久久 |