摘要:函數(shù)式編程的定義函數(shù)是一段可以通過其名稱被調(diào)用的代碼。純函數(shù)大多數(shù)函數(shù)式編程的好處來自于編寫純函數(shù),純函數(shù)是對(duì)給定的輸入返回相同的輸出的函數(shù),并且純函數(shù)不應(yīng)依賴任何外部變量,也不應(yīng)改變?nèi)魏瓮獠孔兞俊?/p>
一個(gè)持續(xù)更新的github筆記,鏈接地址:Front-End-Basics,可以watch,也可以star。
此篇文章的地址:JavaScript函數(shù)式編程入門經(jīng)典
正文開始
什么是函數(shù)式編程?為何它重要? 數(shù)學(xué)中的函數(shù)f(x) = y // 一個(gè)函數(shù)f,以x為參數(shù),并返回輸出y
關(guān)鍵點(diǎn):
函數(shù)必須總是接受一個(gè)參數(shù)
函數(shù)必須總是返回一個(gè)值
函數(shù)應(yīng)該依據(jù)接收到的參數(shù)(例如x)而不是外部環(huán)境運(yùn)行
對(duì)于一個(gè)給定的x,只會(huì)輸出唯一的一個(gè)y
函數(shù)式編程技術(shù)主要基于數(shù)學(xué)函數(shù)和它的思想,所以要理解函數(shù)式編程,先了解數(shù)學(xué)函數(shù)是有必要的。
函數(shù)式編程的定義函數(shù)是一段可以通過其名稱被調(diào)用的代碼。它可以接受參數(shù),并返回值。
與面向?qū)ο缶幊蹋∣bject-oriented programming)和過程式編程(Procedural programming)一樣,函數(shù)式編程(Functional programming)也是一種編程范式。我們能夠以此創(chuàng)建僅依賴輸入就可以完成自身邏輯的函數(shù)。這保證了當(dāng)函數(shù)被多次調(diào)用時(shí)仍然返回相同的結(jié)果(引用透明性)。函數(shù)不會(huì)改變?nèi)魏瓮獠凯h(huán)境的變量,這將產(chǎn)生可緩存的,可測試的代碼庫。
函數(shù)式編程具有以下特征 1、引用透明性所有的函數(shù)對(duì)于相同的輸入都將返回相同的值,函數(shù)的這一屬性被稱為引用透明性(Referential Transparency)
// 引用透明的例子,函數(shù)identity無論輸入什么,都會(huì)原封不動(dòng)的返回 var identity = (i) => {return i}
把一個(gè)引用透明的函數(shù)用于其他函數(shù)調(diào)用之間。
sum(4,5) + identity(1)
根據(jù)引用透明的定義,我們可以把上面的語句換成:
sum(4,5) + 1
該過程被稱為替換模型(Substitution Model),因?yàn)楹瘮?shù)的邏輯不依賴其他全局變量,你可以直接替換函數(shù)的結(jié)果,這與它的值是一樣的。所以,這使得并發(fā)代碼和緩存成為可能。
并發(fā)代碼: 并發(fā)運(yùn)行的時(shí)候,如果依賴了全局?jǐn)?shù)據(jù),要保證數(shù)據(jù)一致,必須同步,而且必要時(shí)需要鎖機(jī)制。遵循引用透明的函數(shù)只依賴參數(shù)的輸入,所以可以自由的運(yùn)行。
緩存: 由于函數(shù)會(huì)為給定的輸入返回相同的值,實(shí)際上我們就能緩存它了。比如實(shí)現(xiàn)一個(gè)計(jì)算給定數(shù)值的階乘的函數(shù),我們就可以把每次階乘的結(jié)果緩存下來,下一次直接用,就不用計(jì)算了。比如第一次輸入5,結(jié)果是120,第二次輸入5,我們知道結(jié)果必然是120,所以就可以返回已緩存的值,而不必再計(jì)算一次。
2、聲明式和抽象函數(shù)式編程主張聲明式編程和編寫抽象的代碼。
// 有一個(gè)數(shù)組,要遍歷它并把它打印到控制臺(tái) /*命令式*/ var array = [1,2,3] for(var i = 0; i < array.length; i++) console(array[i]) // 打印 1,2,3 // 命令式編程中,我們精確的告訴程序應(yīng)該“如何”做:獲取數(shù)組的長度,通過數(shù)組的長度循環(huán)數(shù)組,在每一次循環(huán)中用索引獲取每一個(gè)數(shù)組元素,然后打印出來。 // 但是我們的任務(wù)只是打印出數(shù)組的元素。并不是要告訴編譯器要如何實(shí)現(xiàn)一個(gè)遍歷。 /*聲明式*/ var array = [1,2,3] array.forEach((element) => console.log(element)) // 打印 1,2,3 // 我們使用了一個(gè)處理“如何”做的抽象函數(shù),然后我們就能只關(guān)心做“什么”了
大多數(shù)函數(shù)式編程的好處來自于編寫純函數(shù),純函數(shù)是對(duì)給定的輸入返回相同的輸出的函數(shù),并且純函數(shù)不應(yīng)依賴任何外部變量,也不應(yīng)改變?nèi)魏瓮獠孔兞俊?/p> 純函數(shù)的好處
純函數(shù)產(chǎn)生容易測試的代碼
純函數(shù)容易寫出合理的代碼
純函數(shù)容易寫出并發(fā)代碼
純函數(shù)總是允許我們并發(fā)的執(zhí)行代碼。因?yàn)榧兒瘮?shù)不會(huì)改變它的環(huán)境,這意味著我們根本不需要擔(dān)心同步問題。
純函數(shù)的輸出結(jié)果可緩存
既然純函數(shù)總是為給定的輸入返回相同的輸出,那么我們就能夠緩存函數(shù)的輸出。
高階函數(shù) 數(shù)據(jù)和數(shù)據(jù)類型程序作用于數(shù)據(jù),數(shù)據(jù)對(duì)于程序的執(zhí)行很重要。每種編程語言都有數(shù)據(jù)類型。這些數(shù)據(jù)類型能夠存儲(chǔ)數(shù)據(jù)并允許程序作用其中。
JavaScript中函數(shù)是一等公民(First Class Citizens)當(dāng)一門語言允許函數(shù)作為任何其他數(shù)據(jù)類型使用時(shí),函數(shù)被稱為一等公民。也就是說函數(shù)可被賦值給變量,作為參數(shù)傳遞,也可被其他函數(shù)返回。
函數(shù)作為JavaScript的一種數(shù)據(jù)類型,由于函數(shù)是類似String的數(shù)據(jù)類型,所以我們能把函數(shù)存入一個(gè)變量,能夠作為函數(shù)的參數(shù)進(jìn)行傳遞。所以JavaScript中函數(shù)是一等公民。
高階函數(shù)的定義接受另一個(gè)函數(shù)作為其參數(shù)的函數(shù)稱為高階函數(shù)(Higher-Order-Function),或者說高階函數(shù)是接受函數(shù)作為參數(shù)并且/或者返回函數(shù)作為輸出的函數(shù)。
抽象和高階函數(shù)一般而言,高階函數(shù)通常用于抽象通用的問題,換句話說,高階函數(shù)就是定義抽象。
抽象 : 在軟件工程和計(jì)算機(jī)科學(xué)中,抽象是一種管理計(jì)算機(jī)系統(tǒng)復(fù)雜性的技術(shù)。 通過建立一個(gè)人與系統(tǒng)進(jìn)行交互的復(fù)雜程度,把更復(fù)雜的細(xì)節(jié)抑制在當(dāng)前水平之下。簡言之,抽象讓我們專注于預(yù)定的目標(biāo)而無須關(guān)心底層的系統(tǒng)概念。
例如:你在編寫一個(gè)涉及數(shù)值操作的代碼,你不會(huì)對(duì)底層硬件的數(shù)字表現(xiàn)方式到底是16位還是32位整數(shù)有很深的了解,包括這些細(xì)節(jié)在哪里屏蔽。因?yàn)樗鼈儽怀橄蟪鰜砹耍涣粝铝撕唵蔚臄?shù)字給我們使用。
// 用forEach抽象出遍歷數(shù)組的操作 const forEach = (array,fn) => { let i; for(i=0;i閉包和高階函數(shù)console.log(data))
什么是閉包?簡言之,閉包就是一個(gè)內(nèi)部函數(shù)。什么是內(nèi)部函數(shù)?就是在另一個(gè)函數(shù)內(nèi)部的函數(shù)。
閉包的強(qiáng)大之處在于它對(duì)作用域鏈(或作用域?qū)蛹?jí))的訪問。從技術(shù)上講,閉包有3個(gè)可訪問的作用域。
(1) 在它自身聲明之內(nèi)聲明的變量
(2) 對(duì)全局變量的訪問
(3) 對(duì)外部函數(shù)變量的訪問(關(guān)鍵點(diǎn))
實(shí)例一:假設(shè)你再遍歷一個(gè)來自服務(wù)器的數(shù)組,并發(fā)現(xiàn)數(shù)據(jù)錯(cuò)了。你想調(diào)試一下,看看數(shù)組里面究竟包含了什么。不要用命令式的方法,要用函數(shù)式的方法來實(shí)現(xiàn)。這里就需要一個(gè) tap 函數(shù)。
const tap = (value) => { return (fn) => { typeof fn === "function" && fn(value) console.log(value) } } // 沒有調(diào)試之前 forEach(array, data => { console.log(data + data) }) // 在 forEach 中使用 tap 調(diào)試 forEach(array, data => { tap(data)(() => { console.log(data + data) }) })
完成一個(gè)簡單的reduce函數(shù)
const reduce = (array,fn,initialValue) => { let accumulator; if(initialValue != undefined) accumulator = initialValue else accumulator = array[0] if(initialValue === undefined) for(let i = 1; i < array.length; i++) accumulator = fn(accumulator, array[i]) else for(let value of array) accumulator = fn(accumulator,value) return accumulator } console.log(reduce([1,2,3], (accumulator,value) => accumulator + value)) // 打印出6柯里化與偏應(yīng)用 一些概念 一元函數(shù)
只接受一個(gè)參數(shù)的函數(shù)稱為一元(unary)函數(shù)。
二元函數(shù)只接受兩個(gè)參數(shù)的函數(shù)稱為二元(binary)函數(shù)。
變參函數(shù)變參函數(shù)是接受可變數(shù)量的函數(shù)。
柯里化柯里化是把一個(gè)多參數(shù)函數(shù)轉(zhuǎn)換為一個(gè)嵌套的一元函數(shù)的過程。
例如
// 一個(gè)多參數(shù)函數(shù) const add = (x,y) => x + y; add(2,3) // 一個(gè)嵌套的一元函數(shù) const addCurried = x => y => x + y; addCurried(2)(3) // 然后我們寫一個(gè)高階函數(shù),把 add 轉(zhuǎn)換成 addCurried 的形式。 const curry = (binaryFn) => { return function (firstArg) { return function (secondArg) { return binaryFn(firstArg,secondArg) } } } let autoCurriedAdd = carry(add) autoCurriedAdd(2)(3)
上面只是簡單實(shí)現(xiàn)了一個(gè)二元函數(shù)的柯里化,下面我們要實(shí)現(xiàn)一個(gè)更多參數(shù)的函數(shù)的柯里化。
const curry = (fn) => { if (typeof fn !== "function") { throw Error("No function provided") } return function curriedFn (...args) { // 判斷當(dāng)前接受的參數(shù)是不是小于進(jìn)行柯里化的函數(shù)的參數(shù)個(gè)數(shù) if(args.length < fn.length) { // 如果小于的話就返回一個(gè)函數(shù)再去接收剩下的參數(shù) return function (...argsOther) { return curriedFn.apply(null, args.concat(argsOther)) } }else { return fn.apply(null,args) } } } const multiply = (x,y,z) => x * y * z; console.log(curry(multiply)(2)(3)(4))
柯里化的應(yīng)用實(shí)例:從數(shù)組中找出含有數(shù)字的元素
let match = curry(function (expr,str) { return str.match(expr) }) let hasNumber = match(/[0-9]+/) let initFilter = curry(function (fn,array) { return array.filter(fn) }) let findNumberInArray = initFilter(hasNumber) console.log(findNumberInArray(["aaa", "bb2", "33c", "ffffd", ])) // 打印 [ "bb2", "33c" ]偏應(yīng)用
我們上面設(shè)計(jì)的柯里化函數(shù)總是在最后接受一個(gè)數(shù)組,這使得它能接受的參數(shù)列表只能是從最左到最右。
但是有時(shí)候,我們不能按照從左到右的這樣嚴(yán)格傳入?yún)?shù),或者只是想部分地應(yīng)用函數(shù)參數(shù)。這里我們就需要用到偏應(yīng)用這個(gè)概念,它允許開發(fā)者部分地應(yīng)用函數(shù)參數(shù)。
const partial = function (fn, ...partialArgs) { return function (...fullArguments) { let args = partialArgs let arg = 0; for(let i = 0; i < args.length && arg < fullArguments.length; i++) { if(args[i] === undefined) { args[i] = fullArguments[arg++] } } return fn.apply(null,args) } }
偏應(yīng)用的示例:
// 打印某個(gè)格式化的JSON let prettyPrintJson = partial(JSON.stringify,undefined,null,2) console.log(prettyPrintJson({name:"fangxu",gender:"male"})) // 打印出 { "name": "fangxu", "gender": "male" }組合與管道 Unix的理念
每個(gè)程序只做好一件事情,為了完成一項(xiàng)新的任務(wù),重新構(gòu)建要好于在復(fù)雜的舊程序中添加新“屬性”。
每個(gè)程序的輸出應(yīng)該是另一個(gè)尚未可知的程序的輸入。
每一個(gè)基礎(chǔ)函數(shù)都需要接受一個(gè)參數(shù)并返回?cái)?shù)據(jù)。
組合(compose)const compose = (...fns) => { return (value) => reduce(fns.reverse(),(acc,fn) => fn(acc), value) }
compose 組合的函數(shù),是按照傳入的順序從右到左調(diào)用的。所以傳入的 fns 要先 reverse 一下,然后我們用到了reduce ,reduce 的累加器初始值是 value ,然后會(huì)調(diào)用 (acc,fn) => fn(acc), 依次從 fns 數(shù)組中取出 fn ,將累加器的當(dāng)前值傳入 fn ,即把上一個(gè)函數(shù)的返回值傳遞到下一個(gè)函數(shù)的參數(shù)中。
組合的實(shí)例:
let splitIntoSpace = (str) => str.split(" ") let count = (array) => array.length const countWords = composeN(count, splitIntoSpace) console.log(countWords("make smaller or less in amount")) // 打印 6管道/序列
compose 函數(shù)的數(shù)據(jù)流是從右往左的,最右側(cè)的先執(zhí)行。當(dāng)然,我們還可以讓最左側(cè)的函數(shù)先執(zhí)行,最右側(cè)的函數(shù)最后執(zhí)行。這種從左至右處理數(shù)據(jù)流的過程稱為管道(pipeline)或序列(sequence)。
// 跟compose的區(qū)別,只是沒有調(diào)用fns.reverse() const pipe = (...fns) => (value) => reduce(fns,(acc,fn) => fn(acc),value)函子 什么是函子(Functor)?
定義:函子是一個(gè)普通對(duì)象(在其它語言中,可能是一個(gè)類),它實(shí)現(xiàn)了map函數(shù),在遍歷每個(gè)對(duì)象值的時(shí)候生成一個(gè)新對(duì)象。
實(shí)現(xiàn)一個(gè)函子1、簡言之,函子是一個(gè)持有值的容器。而且函子是一個(gè)普通對(duì)象。我們就可以創(chuàng)建一個(gè)容器(也就是對(duì)象),讓它能夠持有任何傳給它的值。
const Container = function (value) { this.value = value } let testValue = new Container(1) // => Container {value:1}
我們給 Container 增加一個(gè)靜態(tài)方法,它可以為我們?cè)趧?chuàng)建新的 Containers 時(shí)省略 new 關(guān)鍵字。
Container.of = function (value) { return new Container(value) } // 現(xiàn)在我們就可以這樣來創(chuàng)建 Container.of(1) // => Container {value:1}
2、函子需要實(shí)現(xiàn) map 方法,具體的實(shí)現(xiàn)是,map 函數(shù)從 Container 中取出值,傳入的函數(shù)把取出的值作為參數(shù)調(diào)用,并將結(jié)果放回 Container。
為什么需要 map 函數(shù),我們上面實(shí)現(xiàn)的 Container 僅僅是持有了傳給它的值。但是持有值的行為幾乎沒有任何應(yīng)用場景,而 map 函數(shù)發(fā)揮的作用就是,允許我們使用當(dāng)前 Container 持有的值調(diào)用任何函數(shù)。
Container.prototype.map = function (fn) { return Container.of(fn(this.value)) } // 然后我們實(shí)現(xiàn)一個(gè)數(shù)字的 double 操作 let double = (x) => x + x; Container.of(3).map(double) // => Container {value: 6}
3、map返回了一傳入函數(shù)的執(zhí)行結(jié)果為值的 Container 實(shí)例,所以我們可以鏈?zhǔn)讲僮鳌?/p>
Container.of(3).map(double).map(double).map(double) // => Container {value: 24}
通過以上的實(shí)現(xiàn),我們可以發(fā)現(xiàn),函子就是一個(gè)實(shí)現(xiàn)了map契約的對(duì)象。函子是一個(gè)尋求契約的概念,該契約很簡單,就是實(shí)現(xiàn) map 。根據(jù)實(shí)現(xiàn) map 函數(shù)的方式不同,會(huì)產(chǎn)生不同類型的函子,如 MayBe 、 Either
函子可以用來做什么?之前我們用tap函數(shù)來函數(shù)式的解決代碼報(bào)錯(cuò)的調(diào)試問題,如何更加函數(shù)式的處理代碼中的問題,那就需要用到下面我們說的MayBe函子
MayBe 函子讓我們先寫一個(gè)upperCase函數(shù)來假設(shè)一種場景
let value = "string"; function upperCase(value) { // 為了避免報(bào)錯(cuò),我們得寫這么一個(gè)判斷 if(value != null || value != undefined) return value.toUpperCase() } upperCase(value) // => STRING
如上面所示,我們代碼中經(jīng)常需要判斷一些null和undefined的情況。下面我們來看一下MayBe函子的實(shí)現(xiàn)。
// MayBe 跟上面的 Container 很相似 export const MayBe = function (value) { this.value = value } MayBe.of = function (value) { return new MayBe(value) } // 多了一個(gè)isNothing MayBe.prototype.isNoting = function () { return this.value === null || this.value === undefined; } // 函子必定有 map,但是 map 的實(shí)現(xiàn)方式可能不同 MayBe.prototype.map = function(fn) { return this.isNoting()?MayBe.of(null):MayBe.of(fn(this.value)) } // MayBe應(yīng)用 let value = "string"; MayBe.of(value).map(upperCase) // => MayBe { value: "STRING" } let nullValue = null MayBe.of(nullValue).map(upperCase) // 不會(huì)報(bào)錯(cuò) MayBe { value: null }Either 函子
MayBe.of("tony") .map(() => undefined) .map((x)f => "Mr. " + x)
上面的代碼結(jié)果是 MyaBe {value: null},這只是一個(gè)簡單的例子,我們可以想一下,如果代碼比較復(fù)雜,我們是不知道到底是哪一個(gè)分支在檢查 undefined 和 null 值時(shí)執(zhí)行失敗了。這時(shí)候我們就需要 Either 函子了,它能解決分支拓展問題。
const Nothing = function (value) { this.value = value; } Nothing.of = function (value) { return new Nothing(value) } Nothing.prototype.map = function (fn) { return this; } const Some = function (value) { this.value = value; } Some.of = function (value) { return new Some(value) } Some.prototype.map = function (fn) { return Some.of(fn(this.value)); } const Either = { Some, Nothing }Pointed 函子
函子只是一個(gè)實(shí)現(xiàn)了 map 契約的接口。Pointed 函子也是一個(gè)函子的子集,它具有實(shí)現(xiàn)了 of 契約的接口。 我們?cè)?MayBe 和 Either 中也實(shí)現(xiàn)了 of 方法,用來在創(chuàng)建 Container 時(shí)不使用 new 關(guān)鍵字。所以 MayBe 和 Either 都可稱為 Pointed 函子。
ES6 增加了 Array.of, 這使得數(shù)組成為了一個(gè) Pointed 函子。Monad 函子
MayBe 函子很可能會(huì)出現(xiàn)嵌套,如果出現(xiàn)嵌套后,我們想要繼續(xù)操作真正的value是有困難的。必須深入到 MayBe 內(nèi)部進(jìn)行操作。
let joinExample = MayBe.of(MayBe.of(5)); // => MayBe { value: MayBe { value: 5 } } // 這個(gè)時(shí)候我們想讓5加上4,需要深入 MayBe 函子內(nèi)部 joinExample.map((insideMayBe) => { return insideMayBe.map((value) => value + 4) }) // => MayBe { value: MayBe { value: 9 } }
我們這時(shí)就可以實(shí)現(xiàn)一個(gè) join 方法來解決這個(gè)問題。
// 如果通過 isNothing 的檢查,就返回自身的 value MayBe.prototype.join = function () { return this.isNoting()? MayBe.of(null) : this.value }
let joinExample2 = MayBe.of(MayBe.of(5)); // => MayBe { value: MayBe { value: 5 } } // 這個(gè)時(shí)候我們想讓5加上4就很簡單了。 joinExample2.join().map((value) => value + 4) // => MayBe { value: 9 }
再延伸一下,我們擴(kuò)展一個(gè) chain 方法。
MayBe.prototype.chain = function (fn) { return this.map(fn).join() }
調(diào)用 chain 后就能把嵌套的 MayBe 展開了。
let joinExample3 = MayBe.of(MayBe.of(5)); // => MayBe { value: MayBe { value: 5 } } joinExample3.chain((insideMayBe) => { return insideMayBe.map((value) => value + 4) }) // => MayBe { value: 9 }
Monad 其實(shí)就是一個(gè)含有 chain 方法的函子。只有of 和 map 的 MayBe 是一個(gè)函子,含有 chain 的函子是一個(gè) Monad。
總結(jié) JavaScript是函數(shù)式編程語言嗎?函數(shù)式編程主張函數(shù)必須接受至少一個(gè)參數(shù)并返回一個(gè)值,但是JavaScript允許我們創(chuàng)建一個(gè)不接受參數(shù)并且實(shí)際上什么也不返回的函數(shù)。所以JavaScript不是一種純函數(shù)語言,更像是一種多范式的語言,不過它非常適合函數(shù)式編程范式。
補(bǔ)充 1、純函數(shù)是數(shù)學(xué)函數(shù)function generateGetNumber() { let numberKeeper = {} return function (number) { return numberKeeper.hasOwnProperty(number) ? number : numberKeeper[number] = number + number } } const getNumber = generateGetNumber() getNumber(1) getNumber(2) …… getNumber(9) getNumber(10) // 此時(shí)numberKeeper為: { 1: 2 2: 4 3: 6 4: 8 5: 10 6: 12 7: 14 8: 16 9: 18 10: 20 }
現(xiàn)在我們規(guī)定,getNumber只接受1-10范圍的參數(shù),那么返回值肯定是 numberKeeper 中的某一個(gè) value 。據(jù)此我們分析一下 getNumber ,該函數(shù)接受一個(gè)輸入并為給定的范圍(此處范圍是10)映射輸出。輸入具有強(qiáng)制的、相應(yīng)的輸出,并且也不存在映射兩個(gè)輸出的輸入。
下面我來再看一下數(shù)學(xué)函數(shù)的定義(維基百科)
在數(shù)學(xué)中,函數(shù)是一種輸入集合和可允許的輸出集合之間的關(guān)系,具有如下屬性:每個(gè)輸入都精確地關(guān)聯(lián)一個(gè)輸出。函數(shù)的輸入稱為參數(shù),輸出稱為值。對(duì)于一個(gè)給定的函數(shù),所有被允許的輸入集合稱為該函數(shù)的定義域,而被允許的輸出集合稱為值域。
根據(jù)我們對(duì)于 getNumber 的分析,對(duì)照數(shù)學(xué)函數(shù)的定義,會(huì)發(fā)現(xiàn)完全一致。我們上面的getNumber函數(shù)的定義域是1-10,值域是2,4,6,……18,20
2、實(shí)例文中所有的概念對(duì)應(yīng)的實(shí)例可以在 https://github.com/qiqihaobenben/learning-functional 獲取,可以打開對(duì)應(yīng)的注釋來實(shí)際執(zhí)行一下。
3、薦書《JavaScript ES6 函數(shù)式編程入門經(jīng)典》,強(qiáng)烈建議想入門函數(shù)式編程的同學(xué)看一下,書有點(diǎn)老,可以略過工具介紹之類的,關(guān)鍵看其內(nèi)在的思想,最重要的是,這本書很薄,差不多跟一本漫畫書類似。
4、推薦文章(非引用文章)漫談 JS 函數(shù)式編程(一)
從一道坑人的面試題說函數(shù)式編程
函數(shù)式編程入門教程
函數(shù)式編程的一點(diǎn)實(shí)戰(zhàn)
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/104113.html
摘要:本文與大家分享一些編程語言的入門書籍,其中不乏經(jīng)典。全書貫穿的主體是如何思考設(shè)計(jì)開發(fā)的方法,而具體的編程語言,只是提供一個(gè)具體場景方便介紹的媒介。入門入門容易理解而且讀起來幽默風(fēng)趣,對(duì)于編程初學(xué)者和語言新手而言是理想的書籍。 本文與大家分享一些Python編程語言的入門書籍,其中不乏經(jīng)典。我在這里分享的,大部分是這些書的英文版,如果有中文版的我也加上了。有關(guān)書籍的介紹,大部分截取自是官...
摘要:書籍如下面向?qū)ο缶幊讨改希L(fēng)格輕松易懂,比較適合初學(xué)者,原型那塊兒講得透徹,種繼承方式呢。還有另一件事情是,比如發(fā)現(xiàn)自己某個(gè)知識(shí)點(diǎn)不太清楚,可以單獨(dú)去百度。 作者:小不了鏈接:https://zhuanlan.zhihu.com/p/...來源:知乎著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。 鑒于時(shí)不時(shí),有同學(xué)私信問我(老姚,下同)怎么學(xué)前端的問題。這里統(tǒng)一回...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:從最開始的到封裝后的都在試圖解決異步編程過程中的問題。為了讓編程更美好,我們就需要引入來降低異步編程的復(fù)雜性。異步編程入門的全稱是前端經(jīng)典面試題從輸入到頁面加載發(fā)生了什么這是一篇開發(fā)的科普類文章,涉及到優(yōu)化等多個(gè)方面。 TypeScript 入門教程 從 JavaScript 程序員的角度總結(jié)思考,循序漸進(jìn)的理解 TypeScript。 網(wǎng)絡(luò)基礎(chǔ)知識(shí)之 HTTP 協(xié)議 詳細(xì)介紹 HTT...
摘要:前言月份開始出沒社區(qū),現(xiàn)在差不多月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議那么今天我就把看過的一些學(xué)習(xí)資源主要是博客,博文推薦分享給大家。 1.前言 6月份開始出沒社區(qū),現(xiàn)在差不多9月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了!一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議!那么今天我就...
閱讀 3754·2021-09-22 15:49
閱讀 3310·2021-09-08 09:35
閱讀 1426·2019-08-30 15:55
閱讀 2328·2019-08-30 15:44
閱讀 719·2019-08-29 16:59
閱讀 1605·2019-08-29 16:16
閱讀 485·2019-08-28 18:06
閱讀 898·2019-08-27 10:55