摘要:本文試圖盡可能系統(tǒng)的描述函數(shù)式編程。函數(shù)式編程使用參數(shù)保存狀態(tài),最好的例子就是遞歸??吕锘瘮?shù)有利于指定函數(shù)行為,并將現(xiàn)有函數(shù)組合為新函數(shù)。
JavaScript函數(shù)式編程 摘要
以往經(jīng)??吹健焙瘮?shù)式編程“這一名詞,卻始終沒有花時間去學(xué)習(xí),暑期實習(xí)結(jié)束之后一直忙于邊養(yǎng)老邊減肥,81天成功瘦身30斤+ ,開始回歸正常的學(xué)習(xí)生活。
便在看《JavaScript函數(shù)式編程》這本書,以系統(tǒng)了解函數(shù)式編程的知識。本文試圖盡可能系統(tǒng)的描述JavaScript函數(shù)式編程。當(dāng)然認(rèn)識暫時停留于本書介紹的程度,如有錯誤之處,還請指正。
注:本書采用的函數(shù)式庫Underscore。一下部分代碼運行時,需引入Underscore。
函數(shù)式編程簡介我們用一句話來直白的描述函數(shù)式編程:
函數(shù)式編程通過使用函數(shù)來將值轉(zhuǎn)換成抽象單元,接著用于構(gòu)建軟件系統(tǒng)。
概括的來說,函數(shù)式編程包括以下技術(shù)
確定抽象,并為其構(gòu)建函數(shù)
利用已有的函數(shù)來構(gòu)建更為復(fù)雜的抽象
通過將現(xiàn)有的函數(shù)傳給其他函數(shù)來構(gòu)建更加復(fù)雜的抽象
注:JavaScript并不僅限于函數(shù)式編程語言,以下是另外3種常用的編程方式。
命令式編程: 通過詳細描述行為的編程方式
基于原型的面向?qū)ο缶幊蹋?基于原型對象及其實例的編程方式
元編程:對JavaScript執(zhí)行模型數(shù)據(jù)進行編寫和操作的編程方式
函數(shù)式編程的一些特性 純函數(shù)純函數(shù)堅持以下屬性(堅持純度的標(biāo)準(zhǔn)不僅將有助于使程序更容易測試,也更容易推理。)
其結(jié)果只能從它的參數(shù)的值來計算
不能依賴于能被外部操作改變的數(shù)據(jù)
不能改變外部狀態(tài)
不變性 —— 沒有副作用所謂"副作用"(side effect),指的是函數(shù)內(nèi)部與外部互動(最典型的情況,就是修改全局變量的值),產(chǎn)生運算以外的其他結(jié)果。
函數(shù)式編程強調(diào)沒有"副作用",意味著函數(shù)要保持獨立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變量的值。
上一點已經(jīng)提到,函數(shù)式編程只是返回新的值,不修改系統(tǒng)變量。因此,不修改變量,也是它的一個重要特點。在其他類型的語言中,變量往往用來保存"狀態(tài)"(state)。不修改變量,意味著狀態(tài)不能保存在變量中。
函數(shù)式編程使用參數(shù)保存狀態(tài),最好的例子就是遞歸。下面的代碼是一個將字符串逆序排列的函數(shù),它演示了不同的參數(shù)如何決定了運算所處的"狀態(tài)"。
function reverse(string) { if(string.length == 0) { return string; } else { return reverse(string.substring(1, string.length)) + string.substring(0, 1); } }函數(shù)是一等公民
“一等”這個術(shù)語通常用來描述值。當(dāng)函數(shù)被看作“一等公民”時,那它就可以去任何值可以去的地方,很少有限制。比如數(shù)字在Javascript里就是一等公民,同程
作為一等公民的函數(shù)就會擁有類似數(shù)字的性質(zhì)。
var fortytwo = function(){return 42} // 函數(shù)與數(shù)字一樣可以存儲為變量 var fortytwo = [32, function(){return 42}] // 函數(shù)與數(shù)字一樣可以存儲為數(shù)組的一個元素 var fortytwo = {number: 32, fun: function(){return 42}} // 函數(shù)與數(shù)字一樣可以作為對象的成員變量 32 + (function(){return 42}) () // 函數(shù)與數(shù)字一樣可以在使用時直接創(chuàng)建出來 // 函數(shù)與數(shù)字一樣可以被傳遞給另一個函數(shù) function weirdAdd(n, f){ return n + f()} weirdAdd(32, function(){return 42}) // 函數(shù)與數(shù)字一樣可以被另一個函數(shù)返回 return 32; return function(){return 42}Applicative編程
Applicative編程是特殊函數(shù)式編程的一種形式。Applicative編程的三個典型例子是map,reduce,filter
函數(shù)A作為參數(shù)提供給函數(shù)B。 (即定義一個函數(shù),讓它接收一個函數(shù),然后調(diào)用它)
_.find(["a","b",3,"d"], _.isNumber) // _.find與_.isNumber都是Underscore中的方法 // 自行實現(xiàn)一個Applicative函數(shù) function exam(fun, coll) { return fun(coll); } // 調(diào)用 exam(function(e){ return e.join(",") }, [1,2,3]) // 結(jié)果 ”1,2,3“高階函數(shù)
定義:一個高階函數(shù)應(yīng)該可以執(zhí)行以下至少一項操作。
以一個函數(shù)作為參數(shù)
返回一個函數(shù)作為結(jié)果
以其他函數(shù)為參數(shù)的函數(shù) 關(guān)于傳遞函數(shù)的思考: max,finder,best// max是一個高階函數(shù) var people = [{name: "Fred", age: 65}, {name: "Lucy", age: 36}]; _.max(people, function(p) { return p.age }); //=> {name: "Fred", age: 65}
但是,在某些方面這個函數(shù)是受限的,并不是真正的函數(shù)式。具體來說,對于_.max而言,比較總是需要通過大于運算符(>)來完成。
不過,我們可以創(chuàng)建一個新的函數(shù)finder。它接收兩個函數(shù):一個用來生成可比較的值,而另一個用來比較兩個值并返回當(dāng)中的”最佳“值。
function finder(valueFun, bestFun, coll) { return _.reduce(coll, function(best, current) { var bestValue = valueFun(best); var currentValue = valueFun(current); return (bestValue === bestFun(bestValue, currentValue)) ? best : current; }); }
在任何情況下,我們現(xiàn)在都可以用finder來找到不同類型的”最佳“值:
finder(function(e){return e.age}, Math.max, people) // => {name: ”Fred", age: 65} finder(function(e){return e.name}, function(x, y){ return (x.charAt((0) === "L") ? x : y),people}) // 偏好首字母為L的人 // => {name:"Lucy", age: 36}
縮減一點
函數(shù)finder短小精悍,并且能按照我們預(yù)期來工作,但為了滿足最大程度的靈活性,它重復(fù)了一些邏輯。
// 在 finder函數(shù)中 return (bestValue === bestFun(bestValue, currentValue)) ? best : current; // 在輸入的函數(shù)參數(shù)中 return (x.charAt((0) === "L") ? x : y
你會發(fā)現(xiàn)上述兩者的邏輯是完全相同的。finder的實現(xiàn)可以根據(jù)以下兩個假設(shè)來縮減。
如果第一個參數(shù)比第二個參數(shù)“更好”,比較最佳值的函數(shù)返回為true
比較最佳值的函數(shù)知道如何“分解”它的參數(shù)
在以上假設(shè)的基礎(chǔ)下,我們可以實現(xiàn)一個更簡潔的best函數(shù)。
function best(fun, coll) { return _.reduce(coll, function(x, y) { return fun(x, y) ? x : y; }); } best(function(x,y) { return x > y }, [1,2,3,4,5]); //=> 5關(guān)于傳遞函數(shù)的更多思考:重復(fù),反復(fù)和條件迭代
首先,從一個簡單的函數(shù)repeat開始。它以一個數(shù)字和一個值為參數(shù),將該值進行多次復(fù)制,并放入一個數(shù)組中:
function repeat(times, VALUE) { return _.map(_.range(times), function() { return VALUE; }); } repeat(4, "Major"); //=> ["Major", "Major", "Major", "Major"]
使用函數(shù),而不是值
通過將參數(shù)從值替換為函數(shù),打開了一個充滿可能性的世界。
function repeatedly(times, fun) { return _.map(_.range(times), fun); } repeatedly(3, function() { return Math.floor((Math.random()*10)+1); }); //=> [1, 3, 8]
再次強調(diào),“使用函數(shù),而不是值”
我們常常會知道函數(shù)應(yīng)該被調(diào)用多少次,但有時候也知道什么時候推出并不取決于“次數(shù)”,而是條件!因此我可以定義另一個名為iterateUntil的函數(shù)。
iterateUntil接收2個參數(shù),一個用來執(zhí)行一些動作,另一個用來進行結(jié)果檢查。
function iterateUntil(fun, check, init) { var ret = []; var result = fun(init); while (check(result)) { ret.push(result); result = fun(result); } return ret; };返回其他函數(shù)的函數(shù)
function invoker (NAME, METHOD) { // 接收一個方法,并在任何給定的對象上調(diào)用它 return function(target /* args ... */) { if (!existy(target)) fail("Must provide a target"); var targetMethod = target[NAME]; var args = _.rest(arguments); return doWhen((existy(targetMethod) && METHOD === targetMethod), function() { return targetMethod.apply(target, args); }); }; }; var rev = invoker("reverse", Array.prototype.reverse); _.map([[1,2,3]], rev); //=> [[3,2,1]]
高階函數(shù)捕獲參數(shù)
高階函數(shù)的參數(shù)是用來“配置”返回函數(shù)的行為的。對于makeAdder而言,它的參數(shù)配置了其返回函數(shù)每次添加數(shù)值的大小
function makeAdder(CAPTURED) { return function(free) { return free + CAPTURED; }; } var add10 = makeAdder(10); add10(32); //=> 42
捕獲變量的好處
用閉包來捕獲增加值,并用作后綴。(但這樣并不具有引用透明)
function makeUniqueStringFunction(start) { var COUNTER = start; return function(prefix) { return [prefix, COUNTER++].join(""); } }; var uniqueString = makeUniqueStringFunction(0); uniqueString("dari"); //=> "dari0" uniqueString("dari"); //=> "dari1"由函數(shù)構(gòu)建函數(shù) 函數(shù)式組合的精華
精華:使用現(xiàn)有的零部件來建立新的行為,這些新行為同樣也成為了已有的零部件。
// 接收一個或多個函數(shù),然后不斷嘗試依次調(diào)用這些函數(shù)的方法,直到返回一個非`undefined`的值 function dispatch(/* funs */) { var funs = _.toArray(arguments); var size = funs.length; return function(target /*, args */) { var ret = undefined; var args = _.rest(arguments); for (var funIndex = 0; funIndex < size; funIndex++) { var fun = funs[funIndex]; ret = fun.apply(fun, construct(target, args)); if (existy(ret)) return ret; } return ret; }; } var str = dispatch(invoker("toString", Array.prototype.toString), invoker("toString", String.prototype.toString)); str("a"); //=> "a" str(_.range(10)); //=> "0,1,2,3,4,5,6,7,8,9"
在這里,我們想做的只是返回一個遍歷函數(shù)數(shù)組,并apply給一個目標(biāo)對象的函數(shù),返回第一個存在的值。dispatch滿足了多態(tài)JavaScript
函數(shù)的定義。這樣簡化了委托具體方法的任務(wù)。例如,在underscore的實現(xiàn)中,你經(jīng)常會看到許多不同的函數(shù)重復(fù)這樣的模式。
確保目標(biāo)的存在
檢查是否有原生版本,如果是則使用它
如果沒有,那么做一些實現(xiàn)這些行為的具體任務(wù)。
做特定類型的任務(wù)(如適用)
做特定參數(shù)的任務(wù)(如適用)
做特定個參數(shù)的任務(wù)(如適用)
同樣的模式也體現(xiàn)在Underscore的函數(shù)_.map()的實現(xiàn)中:
_.map = _.collect = function(obj, iteratee, context) { iteratee = cb(iteratee, context); var keys = !isArrayLike(obj) && _.keys(obj), length = (keys || obj).length, results = Array(length); for (var index = 0; index < length; index++) { var currentKey = keys ? keys[index] : index; results[index] = iteratee(obj[currentKey], currentKey, obj); } return results; };
使用dispatch可以簡化一些這方面的代碼,并且更容易擴展。想象一下,你正在寫一個可以為數(shù)組和字符串類型生成字符描述的
函數(shù)。使用dispatch則可以優(yōu)雅的實現(xiàn):
var str = dispatch(invoker("toString", Array.prototype.toString), invoker("toString", String.prototype.toString)); str("a"); //=> "a" str(_.range(10)); //=> "0,1,2,3,4,5,6,7,8,9"柯里化 Curring
柯里化函數(shù)為每一個邏輯參數(shù)返回一個新函數(shù)。
例如:
// 除法 function divide(n,d){ return n/d; } // 手動柯里化 function curryDivide(n) { return function(d) { return n/d; }; }
curryDivide是手動柯里化函數(shù),也就是說,我顯示地返回對應(yīng)參數(shù)數(shù)量的函數(shù)。
自動柯里化參數(shù)
// 接收一個函數(shù),并返回一個只接受一個參數(shù)的函數(shù)。 function curry(fun) { // 柯里化一個參數(shù),雖然似乎沒什么用 return function(arg) { return fun(arg); }; } function curry2(fun) { // 柯里化兩個參數(shù) return function(secondArg) { return function(firstArg) { return fun(firstArg, secondArg); }; }; } function curry3(fun) { // 柯里化三個參數(shù) return function(last) { return function(middle) { return function(first) { return fun(first, middle, last); }; }; }; };
curry2函數(shù)接受一個函數(shù)并將其柯里化成兩個深層參數(shù)的函數(shù)??梢杂盟鼇韺崿F(xiàn)先前定義的除法函數(shù)。
var divide10 = curry2(div)(10) divide10(50) // => 5
柯里化函數(shù)有利于指定JavaScript函數(shù)行為,并將現(xiàn)有函數(shù)“組合”為新函數(shù)。并且使用柯里化比較容易產(chǎn)生流利的函數(shù)式API。
部分應(yīng)用柯里化函數(shù)逐漸返回消耗參數(shù)的函數(shù),直到所有參數(shù)都耗盡。然而,部分應(yīng)用函數(shù)是一個“部分“執(zhí)行,等待接收剩余的參數(shù)立即執(zhí)行的函數(shù)。
// 部分應(yīng)用一個或兩個已知的參數(shù) function partial1(fun, arg1) { return function(/* args */) { var args = construct(arg1, arguments); // construct為拼接數(shù)組,在此代碼略去 return fun.apply(fun, args); }; } function partial2(fun, arg1, arg2) { return function(/* args */) { var args = cat([arg1, arg2], arguments); // cat也為拼接數(shù)組,在此代碼略去 return fun.apply(fun, args); }; } // 部分應(yīng)用任意數(shù)量的參數(shù) function partial(fun /*, pargs */) { var pargs = _.rest(arguments); return function(/* arguments */) { var args = cat(pargs, _.toArray(arguments)); return fun.apply(fun, args); }; }通過組合端至端的拼接函數(shù)
一種理想化的函數(shù)式程序是向函數(shù)流水線的一端輸送的一塊數(shù)據(jù),從另一端輸出一個全新的數(shù)據(jù)塊。
!_.isString(name)
這個流水線由_.isString和!組成
_.isString接收一個對象,并返回一個布爾值
!接收一個布爾值,并返回一個布爾值
// 通過組合多個函數(shù)及其數(shù)據(jù)轉(zhuǎn)換建立新的函數(shù) function isntString(str){ return !_.isString(str) } isntString(1) // => true // 還可以使用Underscore的_.compose函數(shù)實現(xiàn)同樣的功能 // _.compose函數(shù)從右往左執(zhí)行。即最右邊函數(shù)的結(jié)果會被送入其左側(cè)的函數(shù),一個接一個 var isntString = _.compose(function(x) { return !x }, _.isString); isntString([]); //=> true遞歸
理解遞歸對理解函數(shù)式編程來說非常重要,原因有三。
遞歸的解決方案包括使用對一個普通問題子集的單一抽象的使用
遞歸可以隱藏可變狀態(tài)
遞歸是一種實現(xiàn)懶惰和無限大結(jié)構(gòu)的方法
自吸收函數(shù)在編寫自遞歸函數(shù)時,規(guī)則如下
知道什么時候停止
決定怎樣算一個步驟
把問題分解成一個步驟和一個較小的問題
function myLength(ary) { if (_.isEmpty(ary)) // _.isEmpty何時停止 return 0; else // 進行一個步驟 1+ ; return 1 + myLength(_.rest(ary)); // 小一些的問題 _.rest(ary) }
尾遞歸
尾遞歸與一般自遞歸的明顯區(qū)別是,”一個步驟“和”縮小的問題“中的元素都要進行遞歸調(diào)用。
function tcLength(ary, n) { var l = n ? n : 0; if (_.isEmpty(ary)) return l; else return tcLength(_.rest(ary), l + 1); } tcLength(_.range(10)); //=> 10相互關(guān)聯(lián)函數(shù)
兩個或多個函數(shù)相互調(diào)用被稱為相互遞歸。下面看一個例子,用謂詞函數(shù)來檢查偶數(shù)和奇數(shù):
function evenSteven(n) { if (n === 0) return true; else return oddJohn(Math.abs(n) - 1); } function oddJohn(n) { if (n === 0) return false; else return evenSteven(Math.abs(n) - 1); } // 相互遞歸調(diào)用來回反彈彼此之間遞減某個絕對的值,知道一方或另一方達到0 evenSteven(4) // => true oddJohn(11) // =>true對遞歸的改進
盡管遞歸技術(shù)上是可行的,但是因為JavaScript引擎沒有優(yōu)化遞歸調(diào)用,因此,在使用或?qū)戇f歸函數(shù)時,可能會碰到如下錯誤
evenSteven(10000) // 棧溢出
遞歸應(yīng)該被看作一個底層操作,應(yīng)該盡可能地避免(很容易造成棧溢出)。普通的共識是,首先是要函數(shù)組合,僅當(dāng)需要的時才使用遞歸和蹦床。
蹦床(tramponline):使用蹦床展平調(diào)用,而不是深度嵌套的遞歸調(diào)用。
首先,看看如何手動修復(fù)evenOline和oddOline使得遞歸調(diào)用不會溢出。一個辦法是返回一個函數(shù),它包裝調(diào)用,而不是直接直接調(diào)用。
function evenOline(n) { if (n === 0) return true; else return partial1(oddOline, Math.abs(n) - 1); } function oddOline(n) { if (n === 0) return false; else return partial1(evenOline, Math.abs(n) - 1); } oddOline(3)()() // 返回的只是一個函數(shù)調(diào)用 // => function(){return evenOline(Math.abs(n) - 1)} oddOline(3)()()() // 將函數(shù)調(diào)用執(zhí)行 // => true oddOline(10000)()()()... // 10000個()去執(zhí)行返回的函數(shù)調(diào)用 // => true
當(dāng)然,我們不能直接向用戶暴露這個API,可以提高另外一個函數(shù)trampoline,從程序執(zhí)行來進行扁平化處理。
function trampoline(fun /*, args */) { // 不斷調(diào)用函數(shù)的返回值,知道它不是一個函數(shù)為止 var result = fun.apply(fun, _.rest(arguments)); while (_.isFunction(result)) { result = result(); } return result; } trampoline(oddOline, 10000) // false
由于調(diào)用鏈的間接性,使用蹦床增加了相互遞歸函數(shù)的一些開銷。然而滿總比溢出要好。同樣,你可能不希望強迫用戶使用trampoline,只是為了避免堆棧溢出。我們可以進一步隱藏其外觀。
function isEvenSafe(n) { if (n === 0) return true; else return trampoline(partial1(oddOline, Math.abs(n) - 1)); } function isOddSafe(n) { if (n === 0) return false; else return trampoline(partial1(evenOline, Math.abs(n) - 1)); }基于流的編程 鏈接
使用jQuery等庫經(jīng)常會使用鏈接,鏈接可以讓我們的代碼更加簡潔,如下是鏈接的實現(xiàn)示例。
鏈接方法的原理在于。每個鏈接的方法都返回統(tǒng)一的宿主對象引用。
function createPerson() { var firstName = ""; var age = 0; return { setFirstName: function(fn) { firstName = fn; return this; }, setAge: function(a) { age = a; return this; }, toString: function() { return [firstName, lastName, age].join(" "); } }; } createPerson() .setFirstName("Mike") .setAge(108) .toString(); //=> "Mike 108"
惰性鏈
上述鏈接是直接執(zhí)行,然而我們也可以實行惰性鏈,即使其先緩存待執(zhí)行的函數(shù),等到調(diào)用執(zhí)行函數(shù)時一起執(zhí)行。
封裝了一些行為的函數(shù)通常被稱為thunk,存儲在_calls中的thunk期待將作為接受force方法調(diào)用的對象的中間目標(biāo)。
function LazyChain(obj) { this._calls = []; // 用于緩存待執(zhí)行函數(shù)的數(shù)組 thunk this._target = obj; // 目標(biāo)對象 } LazyChain.prototype.invoke = function(methodName /*, args */) { // 將函數(shù)壓入的方法 var args = _.rest(arguments); this._calls.push(function(target) { var meth = target[methodName]; return meth.apply(target, args); }); return this; }; LazyChain.prototype.force = function() { // 強制執(zhí)行this._calls中的函數(shù) return _.reduce(this._calls, function(target, thunk) { return thunk(target); }, this._target); }; // 使用,直到force方法被調(diào)用才將 concat, sort,join執(zhí)行 new LazyChain([2,1,3]) .invoke("concat", [8,5,7,6]) .invoke("sort") .invoke("join"," ") .force(); // => "1 2 3 4 5 6 7 8"管道
鏈接模式有利于給對象的方法調(diào)用創(chuàng)建流程的API,但是對于函數(shù)式API則未必。
方法連接有各種各樣的缺點,包括緊耦合對象的set和get邏輯。主要問題是,函數(shù)鏈經(jīng)常會做調(diào)用之間改變傳遞的共同引用。函數(shù)式API重點在操作值而不是引用。
一下是管道的具體實現(xiàn)
function pipeline(seed /*, args */) { return _.reduce(_.rest(arguments), function(l,r) { return r(l); }, seed); }; pipeline(42, function(n){return -n},function(n){return n+1}) // => -41寫在最后
本文更多的是對《JavaScript函數(shù)式編程》一書的摘要,并透過一段段代碼試圖闡述函數(shù)式編程的思想。
希望以后的工作中能夠吸取函數(shù)式編程的好,并慢慢對其加深理解。從書中獲取知識,最終還是要落于實踐中去的。
同時,希望能夠通過這篇文章幫助不了解函數(shù)式編程的小伙伴建立系統(tǒng)的認(rèn)識。
WilsonLiu"s blog首發(fā)地址:http://blog.wilsonliu.cn
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/91224.html
摘要:函數(shù)式編程,一看這個詞,簡直就是學(xué)院派的典范。所以這期周刊,我們就重點引入的函數(shù)式編程,淺入淺出,一窺函數(shù)式編程的思想,可能讓你對編程語言的理解更加融會貫通一些。但從根本上來說,函數(shù)式編程就是關(guān)于如使用通用的可復(fù)用函數(shù)進行組合編程。 showImg(https://segmentfault.com/img/bVGQuc); 函數(shù)式編程(Functional Programming),一...
摘要:為了盡可能提升互通性,已經(jīng)成為函數(shù)式編程庫遵循的實際標(biāo)準(zhǔn)。與輕量級函數(shù)式編程的概念相反,它以火力全開的姿態(tài)進軍的函數(shù)式編程世界。 原文地址:Functional-Light-JS 原文作者:Kyle Simpson-《You-Dont-Know-JS》作者 關(guān)于譯者:這是一個流淌著滬江血液的純粹工程:認(rèn)真,是 HTML 最堅實的梁柱;分享,是 CSS 里最閃耀的一瞥;總結(jié),...
摘要:所支持的面向?qū)ο缶幊贪ㄔ屠^承。發(fā)明于年的就是首批支持函數(shù)式編程的語言之一,而演算則可以說是孕育了這門語言。即使在今天,這個家族的編程語言應(yīng)用范圍依然很廣。 1. 能說出來兩種對于 JavaScript 工程師很重要的編程范式么? JavaScript 是一門多范式(multi-paradigm)的編程語言,它既支持命令式(imperative)/面向過程(procedural)編程...
摘要:所支持的面向?qū)ο缶幊贪ㄔ屠^承。發(fā)明于年的就是首批支持函數(shù)式編程的語言之一,而演算則可以說是孕育了這門語言。即使在今天,這個家族的編程語言應(yīng)用范圍依然很廣。 1. 能說出來兩種對于 JavaScript 工程師很重要的編程范式么? JavaScript 是一門多范式(multi-paradigm)的編程語言,它既支持命令式(imperative)/面向過程(procedural)編程...
摘要:所支持的面向?qū)ο缶幊贪ㄔ屠^承。發(fā)明于年的就是首批支持函數(shù)式編程的語言之一,而演算則可以說是孕育了這門語言。即使在今天,這個家族的編程語言應(yīng)用范圍依然很廣。 1. 能說出來兩種對于 JavaScript 工程師很重要的編程范式么? JavaScript 是一門多范式(multi-paradigm)的編程語言,它既支持命令式(imperative)/面向過程(procedural)編程...
閱讀 2845·2023-04-25 18:06
閱讀 2648·2021-11-22 09:34
閱讀 1728·2021-11-08 13:16
閱讀 1347·2021-09-24 09:47
閱讀 3078·2019-08-30 15:44
閱讀 2805·2019-08-29 17:24
閱讀 2614·2019-08-23 18:37
閱讀 2469·2019-08-23 16:55