最近更新: 2006-11-29

Function.prototype.call() and Function.prototype.apply()

JavaScript 調用 function object 的方式,除了傳統的 () 算符 [若用 C++ 的表達方式,即 operator() ],還可以藉由 call()apply() 兩種個體行為調用。

call()apply() 的差別主要在於 call() 只接受一個參數,即 call(thisArg) ;而 apply() 接受兩個參數,即 apply(thisArg, argArray) 。透過 call()apply() 調用函數的主要目的,在於改變函數內部的 this 名稱所指涉的對象。對一般函數而言,當 programmer 在函數內部使用 this 名稱時,指涉對象是 global objectglobal object 是運行環境中最頂層的個體,在瀏覽器環境中,global object 就是 window 此一個體。但是 call()apply() 可以改變 this 名稱所指涉的對象。

If thisArg is null or undefined, the called function is passed the global object as the this value. Otherwise, the called function is passed ToObject(thisArg) as the this value. ECMAScript Language Specification - Standard ECMA-262 3rd Edition. 15.3.4.3 & 15.3.4.4

首先,先來一段測試程式實證上面的標準規範內容。

function myFunc() {
    window.alert(this.toString());
}

myFunc();

var hello = 'hello world';
myFunc.call(hello);
 

結果證實規範內容所言無誤。因此,我們可以利用 call()apply() 改變函數內部的 this 名稱所指涉的對象。此一技巧最常運用在事件處理函數中,例如《Rendering images with title and box》就是這種技巧的實踐。在該例中,使用 call() 調用 renderImage() ,而將圖像索引值設定為屬性 myIndex 。其實像這種需要傳遞參數的情形,可以改用 apply() ,將要傳遞給 function object 的參數按順序放置在 apply() 行為的第二個 argArray 參數中。因此《Rendering images with title and box》用 apply() 改寫如下所示。

function renderImage(index) {
    var box = document.createElement('div');
    for (var styleName in this.style) {
        try {box.style[styleName] = this.style[styleName];}
        catch(e) {}
    }
    with (box) {
        className = this.className;
        style.border = '1px solid #FF0000';
        style.padding = '5px';
        style.backgroundColor = '#D3D3D3';
        style.width = this.width;
        appendChild(this.parentNode.replaceChild(box, this));
        appendChild(document.createElement('br'));
        //appendChild(document.createTextNode('Image.'.concat(this.myIndex+1, ': ', this.alt)));

        appendChild(document.createTextNode('Image.'.concat(index+1, ': ', this.alt)));
    }
}

for (var i = 0, img = document.images[i];
    i < document.images.length;
    img = document.images[++i])
{
    //img.myIndex = i;

    if (img.complete) { 
        window.alert('already loaded');
        //renderImage.call(img);

        renderImage.apply(img, [i]);
    }
    else {
        (function(){
            this.onload = function() {
                window.alert(this.src.concat(' [alt=',this.alt,'] is loaded.'));
                //renderImage.call(this);

                renderImage.apply(this, [i]);
            }
            this.onerror = function() {
                window.alert(this.src.concat(' [alt=',this.alt,'] can not be loaded.'));
            }
        //}).call(img);

        }).apply(img, [i]);
    }
}

由於 apply() 的第二個 argArray 參數必須是陣列形式,所以要將 i 放入陣列中。其次, apply() 是按照變數在 argArray 陣列中的順序代換參數列的變數,故參數列的第一個參數內容,等於 argArray 中的第 0 個元素,以下類推。因此在上例中 renderImage(index) 中的 index 等於 argArray 中的第 0 個元素 (即 i) 。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2532303.html

樂多舊回應
未留名 (#comment-17804311)
Sat, 25 Oct 2008 06:45:32 +0800
The call method takes one or more arguments, thisArg and (optionally) arg1, arg2 etc, and performs a function call using the [[Call]] property of the object. If the object does not have a [[Call]] property, a TypeError exception is thrown. The called function is passed arg1, arg2, etc. as the arguments.
ECMAScript Language Specification - Standard ECMA-262 3rd Edition. 15.3.4.4