摘要:函數是一等公民在談到函數式編程的時候,很多時候會聽到這樣一句話函數是一等公民。變量作用域和閉包變量作用域變量的作用域和閉包作為的基礎,在學習函數式編程中是非常重要的,只有理解了它們,你才能更好的去理解我們后面要講到的高階函數和部分應用等。
函數是一等公民
在談到函數式編程的時候,很多時候會聽到這樣一句話 "函數是一等公民"。那我們如何去理解這句話呢?
"一等" 這個術語通常用來描述值。所以當我們說 "函數是一等公民" 時,也就是說函數擁有值的一切特性,你可以像看待一個值一樣來看待一個函數。舉個例子,數字在 JavaScript 中是一等公民,那么數字擁有的特性,也同樣被函數所擁有。
函數可以像數字一樣被存儲為一個變量
const num = 10; const fun = function() { return 10; }
函數可以像數字一樣作為數組的一個元素
const a = [10, function() { return 20; } ]
函數可以像數字一樣存在于對象的插槽里
const b = { name: "Tony", age: function() { return 20; } }
函數可以像數字一樣在使用時直接創建出來
10 + (function() { return 20; })(); // 30
函數可以像數字一樣被另一個函數返回
const well = function() { return 10; } const good = function() { return function() { return 10; }; }
函數可以像數字一樣被傳遞給另一個函數
const fun = function(value) { return value; } const happy = function(func) { return func(5) * 10; } happy(fun); // 50
最后兩條其實就是 高階函數 的定義,如果你現在不理解也沒有關系,我們在后面的部分會講到它。
變量作用域和閉包 變量作用域變量的作用域和閉包作為 JavaScript 的基礎,在學習函數式編程中是非常重要的,只有理解了它們,你才能更好的去理解我們后面要講到的高階函數和部分應用等。
關于變量的作用域,你需要知道:
全局作用域: JavaScript 中擁有最長生命周期 (一個變量的多長的時間內保持一定的值) 的變量,其變量的生命周期將跨越整個程序。
globalVariable = "This is a global variable!";
詞法作用域: 詞法作用域其實就是指一個變量的可見性,以及它文本表述的模擬值。
a = "outter a"; function good() { a = "middle a"; return function() { a = "inner a"; return "I am " + a; } } good(); // I am inner a
PS:這里的示例代碼僅僅是為了學習,你最好不要這樣去寫,因為它會讓你的代碼變得令人費解。
在上面的例子中,我們分別對 a 變量進行了三次賦值,那么為什么最后我們拿到 a 的值是 "inner a" 而非其他呢?
當我們聲明 a = "outter a" 時,程序會在棧中開辟一個空間去存儲 a,當執行 good()函數時,我們聲明了 a = "middle a",這時候會將棧中 a 的值修改掉,變成 "middle a",最后在執行 return 語句時,我們又聲明了 a = "inner a",這時候會再次修改棧中的 a 的值,變成 "inner a"。因此得到了上面的結果。
在多次給同一變量賦值時,最后得到的值是離使用時最近的一次賦值。通過查找離使用時最近的一次賦值,我們可以快速的得出最后的結果。
動態作用域
提到 JavaScript 的動態作用域,就不得不提到 this 了。this 相關的知識很多,之后有時間再詳細來講講。現在我們先記住 this 所指向的值由調用者確定,如下代碼所示:
function globalThis() { return this; } globalThis.call("APPLE"); //=> "APPLE" globalThis.call("ORANGE"); //=> "ORANGE"
函數作用域
閉包說起閉包,很多人都會覺得有點頭疼,這的確是一個令人費解的概念,不過不要怕,它其實沒有那么難以理解。
閉包的定義閉包是一個函數和聲明該函數的詞法環境的組合
換句話說,閉包就是在使用時被作用域封閉的變量和函數。閉包可以捕獲函數的參數和變量。
舉個例子:
const fun = function() { const a = 10; return function(b) { return a + b; } } const myFunc = fun(); // 此時 myFunc 就變成一個閉包了,這個閉包可以捕獲 fun 函數里的 a 變量,b 參數。
注意閉包是在使用時才會生成的,而非創建時。如上面的例子,如果只創建 fun 函數,而不執行最后一句 fun(),那么 fun 并不能稱之為一個閉包。這里的閉包應該是 fun 運行時所產生的作用域,這個作用域捕獲了fun 里面的變量和參數。
閉包的特點閉包會捕獲一個值(或引用),并多次返回相同的值
每一個新的閉包都會捕獲不一樣的值
再來看一個例子:
const fun = function() { return function() { return 10; } } const myFunc = fun(); // myFunc 不是一個閉包 const fun2 = function(value) { return function() { return value; } } const myFunc2 = fun2("AWESOME"); // myFunc2 是一個閉包 myFunc2(); // AWESOME myFunc2(); // AWESOME 多次執行 myFunc2 閉包,返回的值相同 const myFunc3 = fun2("HAPPY"); // myFunc3 是一個新的閉包 myFunc3(); // HAPPY
這里 myFunc 嚴格意義上并不能叫作一個閉包,因為它并沒有捕獲 fun 任何的變量或者是函數的傳參。而 myFunc2 是一個閉包,因為它捕獲了 fun2 的傳參。
閉包的銷毀閉包延續了變量的生命周期,如果不手動銷毀,閉包里面的變量會一直存在內存中。比如當我們手動將 myFunc = null 時,閉包里面的變量才會被垃圾回收。
實用的閉包說了這么多,你可能會有這樣的疑問,閉包真的有用嗎?閉包一般都會用到什么地方?
*1. 用閉包模擬私有方法, 使公共函數能夠訪問私有函數和變量,實現數據的隱藏和封裝。私有方法有利于限制對代碼的訪問,并且提供了強大的管理命名空間的能力,避免了非核心代碼對公共接口的干擾。
const Counter = () => { let count = 0; const change = (a) => { count = count + a; } return { increase: () => { change(1); }, decrease: () => { change(- 1); }, value: () => { return count; } } } const func1 = new Counter(); func1.value(); // 0 func1.increase(); func1.value(); // 1 func1.decrease(); func1.value(); // 0
*2. 通過一個高階函數,生成不同的閉包,從而得到多個保存不同環境的新函數。
如下面的例子:
const makeAdder = function(x) { return function(y) { return x + y; } } const add5 = makeAdder(5); const add10 = makeAdder(10);
makeAdder 其實是一個函數工廠,用于創建將制定的值和它的參數求和的函數。通過它我們又創建了兩個新函數 add5 和 add10。add5 和 add10 都是閉包。它們共享著相同的函數定義,但是卻保存了不同的環境。在 add5 的環境中,x 為 5,但是在 add10 中,x 則為10。
高階函數 定義滿足以下任意條件之一即可稱之為高階函數:
以一個或者多個函數作為參數
以一個函數作為返回結果
我們常見的 map,find,reduce 都是以函數作為入參的函數,所以它們都是高階函數。
以函數作為參數的函數使用函數作為函數的參數,可以讓我們創建出更靈活的函數。通過將參數從值替換為函數,我們可以得到更多的可能性。因為在調用的時候,我們可以通過傳入不同的函數來完成不同的需求。
正如下面的例子:
const finder = function(val, func) { return val.reduce(function(prev, current) { return func(prev, current); }); } const a = [1, 2, 3, 5, 8]; finder(a, Math.max); // 8 finder(a, Math.min); // 1
在使用 finder 函數時,通過傳入不同的函數,最后得到了完全不同的結果。這也是為什么我們強調 "使用函數,而不是值" 的原因。
以函數作為返回結果的函數以函數作為返回結果的函數,可以構建強大的函數。還記得我們前面提到的閉包嗎? 通過高階函數 makeAdder,我們生成了 add5 和 add10 兩個新的函數。能夠生成閉包的函數,其實都是高階函數。
到這里,第一部分重點內容就講完了。在下一部分中,我們會講到函數式編程中剩下的幾個重要部分:
柯里化和組合
部分應用
遞歸
基于流的編程
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/90738.html
摘要:本文是響應式編程第二章序列的深入研究這篇文章的學習筆記。函數科里化的基本應用,也是函數式編程中運算管道構建的基本方法。四資料參考函數式編程指南 本文是Rxjs 響應式編程-第二章:序列的深入研究這篇文章的學習筆記。示例代碼托管在:http://www.github.com/dashnowords/blogs 更多博文:《大史住在大前端》目錄 showImg(https://segme...
摘要:想學好前端,真的要主動,然后對所有的英文文檔耐心一點。在年月日,國際組織發布了的第六版,該版本正式名稱為,但通常被稱為或者。自此,每年發布一次新標準。但保留了用于依賴注入的構造函數參數類型。必須在構造函數中聲明屬性,而不是在類的代碼體中。 從 TypeScript 到 ES6 到 ES5 在我初學前端的很長一段時間,不愿意碰git,不愿意碰框架,總是嫌麻煩,連ES6也沒有怎么去弄明白...
摘要:本文是響應式編程第一章響應式這篇文章的學習筆記。通過代碼對比可以發現,在響應式編程中,我們不再用對象的概念來對現實世界進行建模,而是使用流的思想對信息進行拆分和聚合。 本文是Rxjs 響應式編程-第一章:響應式這篇文章的學習筆記。示例代碼地址:【示例代碼】 更多文章:【《大史住在大前端》博文集目錄】 showImg(https://segmentfault.com/img/bVbuE...
摘要:本文是響應式編程第四章構建完整的應用程序這篇文章的學習筆記。涉及的運算符每隔指定時間將流中的數據以數組形式推送出去。中提供了一種叫做異步管道的模板語法,可以直接在的微語法中使用可觀測對象示例五一點建議一定要好好讀官方文檔。 本文是【Rxjs 響應式編程-第四章 構建完整的Web應用程序】這篇文章的學習筆記。示例代碼托管在:http://www.github.com/dashnoword...
摘要:函數式編程前端掘金引言面向對象編程一直以來都是中的主導范式。函數式編程是一種強調減少對程序外部狀態產生改變的方式。 JavaScript 函數式編程 - 前端 - 掘金引言 面向對象編程一直以來都是JavaScript中的主導范式。JavaScript作為一門多范式編程語言,然而,近幾年,函數式編程越來越多得受到開發者的青睞。函數式編程是一種強調減少對程序外部狀態產生改變的方式。因此,...
閱讀 1794·2023-04-26 02:14
閱讀 3719·2021-11-23 09:51
閱讀 1381·2021-10-13 09:39
閱讀 3963·2021-09-24 10:36
閱讀 3009·2021-09-22 15:55
閱讀 3511·2019-08-30 12:57
閱讀 2036·2019-08-29 15:30
閱讀 1980·2019-08-29 13:19