引言
JavaScript是一種強大的,卻被誤解的程式語言。有些人喜歡說它是一個物件導向的程式語言,或者它是一個函數式程式語言。另外一些人喜歡說,它不是一個物件導向的程式語言,或者它不是一個函數式程式語言。還有人認為它兼具物件導向語言和函數式語言的特點,或者,認為它既不是物件導向的也不是函數式的,好吧,讓我們先擱置那些爭論。
讓我們假設我們共有這樣的一個使命:在JavaScript語言所允許的範圍內,盡可能多的使用函數式程式設計的原則來編寫程式。
首先,我們要清理下腦中那些關於函數式程式設計的錯誤觀念。
在JS界被(重度)誤解的函數式程式設計
顯然有相當一批開發者一天到晚的以函數式範式的方式使用JavaScript。我還是要說有更大量的JavaScript開發者,並不真正理解那佯做的真正意義。
我確信,導致這種局面是因為許多用於服務端的web開發語言都源自C語言,而C語言,很顯然不是一種函數式程式語言。
似乎有兩個層級的混亂,第一個層級的混亂我們用下面這個在jQuery中常會用到的例子來說明:
$(".signup").click(function(event){ $("#signupModal").show(); event.preventDefault(); });
嘿,仔細看。我傳遞一個匿名函數作為參數,這在JavaScript世界裡被稱為眾所周知「CallBack」(回呼)函數。
真有人會認為這就是函數式程式設計嗎?根本不是!
這個例子展示了一個函數式語言的關鍵特性:函數作為參數。另一方面,這個3行程式碼的例子也違背了幾乎所有其他的函數式程式設計範式。
第二層級的混亂有點微妙。讀到這裡,一些追求潮流的JS開發者在暗自思考。
好吧,廢話!但是我已經知道了所有關於函數式程式設計的知識與技能。我在我所有的項目上使用Underscore.js。
Underscore.js 是一個廣受歡迎的JavaScript函式庫,到處都在使用。舉個例子,我有一組單詞,我需要得到一個集合,集合裡的每個元素是各個單字的頭兩個字母。用Underscore.js實作這個相當簡單:
var firstTwoLetters = function(words){ return _.map(words,function(word){ return _.first(word,2); }); };
看!看JavaScript巫術。我正在使用這些高級的函數式應用函數,像是_.map 和 _.first。你還有什麼要說的,利蘭(譯註:作者Leland)?
儘管underscore 和 像_.map這樣的函數是非常有價值的函數式範式,但是像這個例子中所採用的組織程式碼的方法看起來…冗長而且對我來說太難於理解。我們真的需要這樣做嗎?
如果開始思考的時候多一點「函數式」的思維,可能我們能夠把上面的例子改成這樣:
// ...一点魔法 var firstTwoLetters = map(first(2));
仔細想想,在1行程式碼中包含了和上面5行程式碼相同的資訊。 words 和word 只是參數/佔位符。這個方法的核心是用一種更明顯的方式來組合map函數,first函數,和常數2。
JavaScript是函數式程式語言嗎?
沒有神奇的公式能夠判定一種語言是不是「函數式」語言。有些語言很明顯就是函數式的,就像其他語言很明顯不是函數式的,但是有大量語言的是模稜兩可的中間派。
於是這裡給出一些常用的、重要的函數式語言的「配料」(JavaScript能實作用粗體標誌)
- 函數是「第一等公民」
- 函數能夠回傳函數
- 詞法上支持閉包
- 函數要「純粹」
- 可靠遞歸
- 沒有變異狀態
這絕對不是一個排它的列表,但是我們至少要逐個討論Javascript中最重要的三個特性,它們支撐我們可以用函數式的方式來編寫程式。
讓我們逐一詳細的了解:
函數是「第一等公民」
這條可能是在所有的配料中最明顯的,並且可能是在許多現代程式語言中最常見到的。
在JavaScript局部變數是透過var關鍵字來定義的。
var foo = "bar";
JavaScript中把函數以局部變數的方式定義是非常容易做到的。
var add = function (a, b) { return a + b; }; var even = function (a) { return a % 2 === 0; };
這些都是事實,變數:變數add和變數even透過被賦值的方式,與函數定義建立引用關係,這種引用關係是在任何時候如果需要是可以被改變的。
// capture the old version of the function var old_even = even; // assign variable `even` to a new, different function even = function (a) { return a & 1 === 0; };
當然,這沒有什麼特別的。但是成為「第一等公民」這個重要的特性使得我們能夠把函數以參數的方式傳遞給另一個函數。舉例:
var binaryCall = function (f, a, b) { return f(a, b); };
这是一个函数,他接受了一个二元函数f,和两个参数a,b,然后调用这个二元函数f,该二元函数f以a、b为输入参数。
add(1,2) === binaryCall(add, 1, 2); // true
这样做看起来有点笨拙,但是当把接下来的函数式编程“配料”合并考虑的时候,牛叉之处就显而易见了…
函数能返回函数(换个说法“高阶函数”)
事情开始变的酷起来。尽管开始比较简单。函数最终以新的函数作为返回值。举个例子:
var applyFirst = function (f, a) { return function (b) { return f(a, b); }; };
这个函数(applyFirst)接受一个二元函数作为其中一个参数,可以把第一个参数(即二元函数)看作是这个applyFirst函数的“部分操作”,然后返回一个一元(一个参数)函数,该一元函数被调用的时候返回外部函数的第一个参数(f)的二元函数f(a, b)。返回两个参数的二元函数。
让我们再谈谈一些函数,例如mult(乘法)函数:
var mult = function(a, b) { return a * b; };
依循mult(乘法)函数的逻辑,我们可以写一个新的函数double(乘方):
var double = applyFirst(mult, 2); double(32); // 64 double(7.5); // 15
这就是偏函数,在FP中经常会用到。(译注:FP全名为 Functional Programming 函数式程序设计 )
我们当然可以像applyFirst那样定义函数:
var curry2 = function (f) { return function (a) { return function (b) { return f(a, b); }; }; };
现在,我想要一个double(乘方)函数,我们换种方式做:
var double = curry2(mult)(2);
这种方式被称作“函数柯里化”。有点类似partial application(偏函数应用),但是更强大一点。
准确的说,函数式编程之所以强大,大部分因于此。简单和易理解的函数成为我们构筑软件的基础构件。当拥有高水平的组织能力、很少重用的逻辑的时候,函数能够被组合和混合在一起用来表达出更复杂的行为。
高阶函数可以得到的乐趣更多。让我们看两个例子:
1.翻转二元函数参数顺序
// flip the argument order of a function var flip = function (f) { return function (a, b) { return f(b, a); }; }; divide(10, 5) === flip(divide)(5, 10); // true
2.创建一个组合了其他函数的函数
// return a function that's the composition of two functions... // compose (f, g)(x) -> f(g(x)) var compose = function (f1, f2) { return function (x) { return f1(f2(x)); }; }; // abs(x) = Sqrt(x^2) var abs = compose(sqrt, square); abs(-2); // 2
这个例子创建了一个实用的函数,我们可以使用它来记录下每次函数调用。
var logWrapper = function (f) { return function (a) { console.log('calling "' + f.name + '" with argument "' + a); return f(a); }; }; var app_init = function(config) { /* ... */ }; if(DEBUG) { // log the init function if in debug mode app_init = logWrapper(app_init); } // logs to the console if in debug mode app_init({ /* ... */ });
词法闭包+作用域
我深信理解如何有效利用闭包和作用域是成为一个伟大JavaScript开发者的关键。
那么…什么是闭包?
简单的说,闭包就是内部函数一直拥有父函数作用域的访问权限,即使父函数已经返回。
可能需要个例子。
var createCounter = function () { var count = 0; return function () { return ++count; }; }; var counter1 = createCounter(); counter1(); // 1 counter1(); // 2 var counter2 = createCounter(); counter2(); // 1 counter1(); // 3
一旦createCounter函数被调用,变量count就被分配一个新的内存区域。然后,返回一个函数,这个函数持有对变量count的引用,并且每次调用的时候执行count加1操作。
注意从createCounter函数的作用域之外,我们是没有办法直接操作count的值。Counter1和Counter2函数可以操作各自的count变量的副本,但是只有在这种非
常具体的方式操作count(自增1)才是被支持的。
在JavaScript,作用域的边界检查只在函数被声明的时候。逐个函数,并且仅仅逐个函数,拥有它们各自的作用域表。(注:在ECMAScript 6中不再是这样,因为let的引入)
一些进一步的例子来证明这论点:
// global scope var scope = "global"; var foo = function(){ // inner scope 1 var scope = "inner"; var myscope = function(){ // inner scope 2 return scope; }; return myscope; }; console.log(foo()()); // "inner" console.log(scope); // "global"
关于作用域还有一些重要的事情需要考虑。例如,我们需要创建一个函数,接受一个数字(0-9),返回该数字相应的英文名称。
简单点,有人会这样写:
// global scope... var names = ['zero','one','two','three','four','five','six','seven','eight','nine']; var digit_name1 = function(n){ return names[n]; };
但是缺点是,names定义在了全局作用域,可能会意外的被修改,这样可能致使digit_name1函数所返回的结果不正确。
那么,这样写:
var digit_name2 = function(n){ var names = ['zero','one','two','three','four','five','six','seven','eight','nine']; return names[n]; };
这次把names数组定义成函数digit_name2局部变量.这个函数远离了意外风险,但是带来了性能损失,由于每次digit_name2被调用的时候,都将重新为names数组定义和分配空间。换个例子如果names是个非常大的数组,或者可能digit_name2函数在一个循环中被调用多次,这时候性能影响将非常明显。
// "An inner function enjoys that context even after the parent functions have returned." var digit_name3 = (function(){ var names = ['zero','one','two','three','four','five','six','seven','eight','nine']; return function(n){ return names[n]; }; })();
这时候我们面临第三个选择。这里我们实现立即调用的函数表达式,仅仅实例化names变量一次,然后返回digit_name3函数,在 IIFE (Immediately-Invoked-Function-Expression 立即执行表达式)的闭包函数持有names变量的引用。
这个方案兼具前两个的优点,回避了缺点。搞定!这是一个常用的模式用来创建一个不可被外部环境修改“private”(私有)状态。

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

Video Face Swap
使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

熱工具

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

SublimeText3 英文版
推薦:為Win版本,支援程式碼提示!

Atom編輯器mac版下載
最受歡迎的的開源編輯器