首頁 web前端 js教程 函數式JavaScript:.apply()、.call() 和arguments對象

函數式JavaScript:.apply()、.call() 和arguments對象

Dec 14, 2016 am 09:21 AM

我們正在尋求調校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
登入後複製


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

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

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
JavaScript引擎:比較實施 JavaScript引擎:比較實施 Apr 13, 2025 am 12:05 AM

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

Python vs. JavaScript:學習曲線和易用性 Python vs. JavaScript:學習曲線和易用性 Apr 16, 2025 am 12:12 AM

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

從C/C到JavaScript:所有工作方式 從C/C到JavaScript:所有工作方式 Apr 14, 2025 am 12:05 AM

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

JavaScript和Web:核心功能和用例 JavaScript和Web:核心功能和用例 Apr 18, 2025 am 12:19 AM

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

JavaScript在行動中:現實世界中的示例和項目 JavaScript在行動中:現實世界中的示例和項目 Apr 19, 2025 am 12:13 AM

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

了解JavaScript引擎:實施詳細信息 了解JavaScript引擎:實施詳細信息 Apr 17, 2025 am 12:05 AM

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

Python vs. JavaScript:社區,圖書館和資源 Python vs. JavaScript:社區,圖書館和資源 Apr 15, 2025 am 12:16 AM

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

Python vs. JavaScript:開發環境和工具 Python vs. JavaScript:開發環境和工具 Apr 26, 2025 am 12:09 AM

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

See all articles