函數式JavaScript:.apply()、.call() 和arguments對象
我們正在尋求調校JavaScript的方式,使得我們可以做些真正的函數式程式設計。為了做到這一點,詳細理解函數呼叫和函數原型是非常必要的。
函數原型
現在,不管你是已經讀了還是忽略掉上面的連結所對應的文章,我們準備繼續前進!
如果我們點開了我們喜歡的瀏覽器+JavaScript控制台,讓我們來看看Function.prototype物件的屬性:
; html-script: false ] Object.getOwnPropertyNames(Function.prototype) //=> ["length", "name", "arguments", "caller", // "constructor", "bind", "toString", "call", "apply"]
這裡的輸出依賴於你使用的瀏覽器和JavaScript版本。 (我用的是Chrome 33)
我們看到一些我們感興趣的幾個屬性。鑑於這篇文章的目的,我會討論下這幾個:
Function.prototype.length
Function.prototype.call
Function.prototype.apply
第一個是個屬性,另外兩個是方法。除了這三個,我還會願意討論下這個特殊的變數arguments,它和Function.prototype.arguments(已被棄用)稍有不同。
首先,我將定義一個「tester」函數來幫助我們弄清楚發生了什麼事。
; html-script: false ] var tester = function (a, b, c){ console.log({ this: this, a: a, b: b, c: c }); };
這個函數簡單記錄了輸入參數的值,和“上下文變數”,即this的值。
現在,讓我們嘗試一些事情:
; html-script: false ] tester("a"); //=> {this: Window, a: "a", b: (undefined), c: (undefined)} tester("this", "is", "cool"); //=> {this: Window, a: "this", b: "is", c: "cool"}
我們注意到如果我們不輸入第2、3個參數,程式將會顯示它們為undefined(未定義)。此外,我們注意到這個函數預設的「上下文」是全域物件window。
使用Function.prototype.call
一個函數的.call 方法以這樣的方式呼叫這個函數,它把上下文變數this設定為第一個輸入參數的值,然後其他的的參數一個跟一個的也傳進函數。
語法:
; html-script: false ] fn.call(thisArg[, arg1[, arg2[, ...]]])
因此,下面這兩行是等效的:
; html-script: false ] tester("this", "is", "cool"); tester.call(window, "this", "is", "cool");
當然,我們能夠隨需傳入任何參數:
; html-script: false ] tester.call("this?", "is", "even", "cooler"); //=> {this: "this?", a: "is", b: "even", c: "cooler"}
這個方法主要的功能是設定你所調用函數的this變數的值。
使用Function.prototype.apply
函數的.apply方法比.call更實用一些。和.call類似,.apply的呼叫方式也是把上下文變數this設定為輸入參數序列中的第一個參數的值。輸入參數序列的第二個參數也是最後一個,以數組(或類別數組物件)的方式傳入。
語法:
; html-script: false ] fun.apply(thisArg, [argsArray])
因此,下面三行全部等效:
; html-script: false ] tester("this", "is", "cool"); tester.call(window, "this", "is", "cool"); tester.apply(window, ["this", "is", "cool"]);
能夠以數組的方式指定一個參數列表在多數時候非常有用(我們會發現這樣做的好處的)。
例如,Math.max是一個可變參數函數(一個函數可以接受任意數目的參數)。
; html-script: false ] Math.max(1,3,2); //=> 3 Math.max(2,1); //=> 2
這樣,如果我有一個數值數組,並且我需要利用Math.max函數找出其中最大的那個,我怎麼用一行程式碼來做這個事兒呢?
; html-script: false ] var numbers = [3, 8, 7, 3, 1]; Math.max.apply(null, numbers); //=> 8
The .apply method really starts to show it’s importance when coupled with the special arguments variable: The arguments object
.apply方法真正開始顯示出它的重要是當配上特殊參數:Arguments。
每個函數表達式在它的作用域中都有一個特殊的、可使用的局部變數:arguments。為了研究它的屬性,讓我們建立另一個tester函數:
; html-script: false ] var tester = function(a, b, c) { console.log(Object.getOwnPropertyNames(arguments)); };
註:在這種情況下我們必須像上面這樣使用Object.getOwnPropertyNames,因為arguments有一些屬性沒有標記為可以被枚舉的,於是如果僅使用console.log(arguments)這種方式它們將不會被顯示出來。
現在我們按照老辦法,透過呼叫tester函數來測試下:
; html-script: false ] tester("a", "b", "c"); //=> ["0", "1", "2", "length", "callee"] tester.apply(null, ["a"]); //=> ["0", "length", "callee"]
arguments變數的屬性中包含了對應於傳入函數的每個參數的屬性,這些和.length屬性、.callee屬性沒什麼不同。
.callee屬性提供了呼叫目前函數的函數的引用,但是這並不被所有的瀏覽器支援。就目前而言,我們忽略這個屬性。
讓我們重新定義一下我們的tester函數,讓它豐富一點:
; html-script: false ] var tester = function() { console.log({ 'this': this, 'arguments': arguments, 'length': arguments.length }); }; tester.apply(null, ["a", "b", "c"]); //=> { this: null, arguments: { 0: "a", 1: "b", 2: "c" }, length: 3 }
Arguments:是物件還是陣列?
我們看得出,arguments完全不是一個數組,雖然多多少少有點像。在很多情況下,儘管不是,我們還是希望把它當作數組來處理。把arguments轉換成一個數組,這有一個非常不錯的快捷小函數:
; html-script: false ] function toArray(args) { return Array.prototype.slice.call(args); } var example = function(){ console.log(arguments); console.log(toArray(arguments)); }; example("a", "b", "c"); //=> { 0: "a", 1: "b", 2: "c" } //=> ["a", "b", "c"]
這裡我們利用Array.prototype.slice方法把類別數組物件轉換成數組。因為這個,在與.apply同時使用的時候arguments物件最終會極為有用。
一些有用例子
Log Wrapper(日誌包裝器)
建立了logWrapper函數,但是它只是在一元函數下正確工作。
; html-script: false ] // old version var logWrapper = function (f) { return function (a) { console.log('calling "' + f.name + '" with argument "' + a); return f(a); }; };
當然了,我們既有的知識讓我們能夠構建一個可以服務於任何函數的logWrapper函數:
; html-script: false ] // new version var logWrapper = function (f) { return function () { console.log('calling "' + f.name + '"', arguments); return f.apply(this, arguments); }; };
通過調用
; html-script: false ] f.apply(this, arguments);
我们确定这个函数f会在和它之前完全相同的上下文中被调用。于是,如果我们愿意用新的”wrapped”版本替换掉我们的代码中的那些日志记录函数是完全理所当然没有唐突感的。 把原生的prototype方法放到公共函数库中 浏览器有大量超有用的方法我们可以“借用”到我们的代码里。方法常常把this变量作为“data”来处理。在函数式编程,我们没有this变量,但是我们无论如何要使用函数的!
; html-script: false ] var demethodize = function(fn){ return function(){ var args = [].slice.call(arguments, 1); return fn.apply(arguments[0], args); }; };
一些別的例子:
; html-script: false ] // String.prototype var split = demethodize(String.prototype.split); var slice = demethodize(String.prototype.slice); var indexOfStr = demethodize(String.prototype.indexOf); var toLowerCase = demethodize(String.prototype.toLowerCase); // Array.prototype var join = demethodize(Array.prototype.join); var forEach = demethodize(Array.prototype.forEach); var map = demethodize(Array.prototype.map);
當然,許多許多。來看看這些是怎麼執行的:
; html-script: false ] ("abc,def").split(","); //=> ["abc","def"] split("abc,def", ","); //=> ["abc","def"] ["a","b","c"].join(" "); //=> "a b c" join(["a","b","c"], " "); // => "a b c"
題外話:
後面我們會演示,實際上更好的使用demethodize函數的方式是參數翻轉。
在函数式编程情况下,你通常需要把“data”或“input data”参数作为函数的最右边的参数。方法通常会把this变量绑定到“data”参数上。举个例子,String.prototype方法通常操作的是实际的字符串(即”data”)。Array方法也是这样。
为什么这样可能不会马上被理解,但是一旦你使用柯里化或是组合函数来表达更丰富的逻辑的时候情况会这样。这正是我在引言部分说到UnderScore.js所存在的问题,之后在以后的文章中还会详细介绍。几乎每个Underscore.js的函数都会有“data”参数,并且作为最左参数。这最终导致非常难重用,代码也很难阅读或者是分析。:-(
管理参数顺序
; html-script: false ] // shift the parameters of a function by one var ignoreFirstArg = function (f) { return function(){ var args = [].slice.call(arguments,1); return f.apply(this, args); }; }; // reverse the order that a function accepts arguments var reverseArgs = function (f) { return function(){ return f.apply(this, toArray(arguments).reverse()); }; };
组合函数
在函数式编程世界里组合函数到一起是极其重要的。通常的想法是创建小的、可测试的函数来表现一个“单元逻辑”,这些可以组装到一个更大的可以做更复杂工作的“结构”
; html-script: false ] // compose(f1, f2, f3..., fn)(args) == f1(f2(f3(...(fn(args...))))) var compose = function (/* f1, f2, ..., fn */) { var fns = arguments, length = arguments.length; return function () { var i = length; // we need to go in reverse order while ( --i >= 0 ) { arguments = [fns[i].apply(this, arguments)]; } return arguments[0]; }; }; // sequence(f1, f2, f3..., fn)(args...) == fn(...(f3(f2(f1(args...))))) var sequence = function (/* f1, f2, ..., fn */) { var fns = arguments, length = arguments.length; return function () { var i = 0; // we need to go in normal order here while ( i++ < length ) { arguments = [fns[i].apply(this, arguments)]; } return arguments[0]; }; };
例子:
; html-script: false ] // abs(x) = Sqrt(x^2) var abs = compose(sqrt, square); abs(-2); // 2

熱AI工具

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

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

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

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

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

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

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

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

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

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

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

Python和JavaScript在開發環境上的選擇都很重要。 1)Python的開發環境包括PyCharm、JupyterNotebook和Anaconda,適合數據科學和快速原型開發。 2)JavaScript的開發環境包括Node.js、VSCode和Webpack,適用於前端和後端開發。根據項目需求選擇合適的工具可以提高開發效率和項目成功率。
