首頁 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

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

熱工具

記事本++7.3.1

記事本++7.3.1

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

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

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

前端熱敏紙小票打印遇到亂碼問題怎麼辦? 前端熱敏紙小票打印遇到亂碼問題怎麼辦? Apr 04, 2025 pm 02:42 PM

前端熱敏紙小票打印的常見問題與解決方案在前端開發中,小票打印是一個常見的需求。然而,很多開發者在實...

神秘的JavaScript:它的作用以及為什麼重要 神秘的JavaScript:它的作用以及為什麼重要 Apr 09, 2025 am 12:07 AM

JavaScript是現代Web開發的基石,它的主要功能包括事件驅動編程、動態內容生成和異步編程。 1)事件驅動編程允許網頁根據用戶操作動態變化。 2)動態內容生成使得頁面內容可以根據條件調整。 3)異步編程確保用戶界面不被阻塞。 JavaScript廣泛應用於網頁交互、單頁面應用和服務器端開發,極大地提升了用戶體驗和跨平台開發的靈活性。

誰得到更多的Python或JavaScript? 誰得到更多的Python或JavaScript? Apr 04, 2025 am 12:09 AM

Python和JavaScript開發者的薪資沒有絕對的高低,具體取決於技能和行業需求。 1.Python在數據科學和機器學習領域可能薪資更高。 2.JavaScript在前端和全棧開發中需求大,薪資也可觀。 3.影響因素包括經驗、地理位置、公司規模和特定技能。

如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? 如何使用JavaScript將具有相同ID的數組元素合併到一個對像中? Apr 04, 2025 pm 05:09 PM

如何在JavaScript中將具有相同ID的數組元素合併到一個對像中?在處理數據時,我們常常會遇到需要將具有相同ID�...

JavaScript難以學習嗎? JavaScript難以學習嗎? Apr 03, 2025 am 12:20 AM

學習JavaScript不難,但有挑戰。 1)理解基礎概念如變量、數據類型、函數等。 2)掌握異步編程,通過事件循環實現。 3)使用DOM操作和Promise處理異步請求。 4)避免常見錯誤,使用調試技巧。 5)優化性能,遵循最佳實踐。

如何實現視差滾動和元素動畫效果,像資生堂官網那樣?
或者:
怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? 如何實現視差滾動和元素動畫效果,像資生堂官網那樣? 或者: 怎樣才能像資生堂官網一樣,實現頁面滾動伴隨的動畫效果? Apr 04, 2025 pm 05:36 PM

實現視差滾動和元素動畫效果的探討本文將探討如何實現類似資生堂官網(https://www.shiseido.co.jp/sb/wonderland/)中�...

JavaScript的演變:當前的趨勢和未來前景 JavaScript的演變:當前的趨勢和未來前景 Apr 10, 2025 am 09:33 AM

JavaScript的最新趨勢包括TypeScript的崛起、現代框架和庫的流行以及WebAssembly的應用。未來前景涵蓋更強大的類型系統、服務器端JavaScript的發展、人工智能和機器學習的擴展以及物聯網和邊緣計算的潛力。

console.log輸出結果差異:兩次調用為何不同? console.log輸出結果差異:兩次調用為何不同? Apr 04, 2025 pm 05:12 PM

深入探討console.log輸出差異的根源本文將分析一段代碼中console.log函數輸出結果的差異,並解釋其背後的原因。 �...

See all articles