JS學習筆記之call、apply的用法

xuerensusu發表於2018-05-11

1、call和apply的區別

call和apply唯一的區別是傳入引數的形式不同。

apply接受兩個引數,第一個引數指定了函式體內this物件的指向,第二個引數為一個帶下標的集合,可以是陣列,也可以是類陣列,apply方法會把集合中的元素作為引數傳遞給被呼叫的函式。

 var func = function (a, b, c) {
    console.log ([a, b, c]); // 輸出 [1, 2, 3]
 };
 func.apply(null, [1, 2, 3]); // 陣列中的1,2,3分別對應引數列表中的a,b,c
複製程式碼

call跟apply相同的是,第一個引數也是代表函式體內的this指向,第二個引數開始往後,引數依次被傳入函式內,傳入的引數數量不固定。

 var func = function (a, b, c) {
     console.log ([a, b, c]); //輸出:[1, 2, 3]
 }
 func.call (null, 1, 2, 3);
複製程式碼

當呼叫函式時,Javascript直譯器並不會計較形參和實參的數量、型別及順序,在Javascript內部統一都是用陣列表示。所以call其實就是包裝在apply上的一顆語法糖。 在使用call和apply的時候,如果傳入的第一個引數是null,函式體內的this會預設指向宿主物件,在瀏覽器環境裡,就是window。

var func = function (a, b, c) {
    console.log (this === window); //輸出:true
};
func.apply(null, [1, 2, 3]);
複製程式碼

如果是嚴格模式下,函式體內的this還是null

var func = function (a, b, c) {
    'use strict';
    console.log (this === null); //輸出:true
};
func.apply(null, [1, 2, 3]);
複製程式碼

有時候call和apply還可以借用其他物件的方法,我們可以傳入null來替代某個具體物件:

Math.min.apply(null, [5, 2, 1, 3, 4]); //輸出:1
複製程式碼

2、call和apply的用途

(1) 可以改變this指向

call和apply最常見的用法就是用來改變函式內部的this指向。

var obj1 = {
    name: 'lq'
};
var obj2 = {
    name: 'xiaoming'
};
window.name = 'angelababy';
var getName = function () {
    console.log (this.name);
};
getName(); // 輸出:angelababy
getName.call(obj1); // 輸出:lq
getName.call(obj2); // 輸出:xiaoming
複製程式碼

(2) Function.prototype.bind

幾乎所有的高階瀏覽器都內建了Function.prototype.bind方法用來指定函式內部this指向問題。如果不支援原生的Function.prototype.bind,我們可以自己實現一個:

Function.prototype.diyBind = function (context) {
    var _this = this; // 儲存原函式
    return function () { // 返回一個新的函式
        return _this.apply(context, arguments); // 執行新的函式時,會將之前傳入的context當作新函式體內的this
    }
};
var obj = {
    name: 'lq'
}
var func = function () {
    console.log (this.name);
}.diyBind(obj);

func();
複製程式碼

上面是簡化版的,下面是稍微複雜點的版本:

Function.prototype.binds = function(){
   var _this = this, // 儲存原來的函式
   context = [].shift.call(arguments), //擷取第一個引數,即是繫結的this上下文
   args = [].slice.call(arguments); //剩餘的引數轉成陣列
   return function(){ //返回一個新的函式
       return _this.apply(context, [].concat.call(args,[].slice.call(arguments))); // 執行新函式的時候,會將之前傳入的context最為新函式體內的this,在此例中就是obj。用concat合併兩次傳入的引數,最為新函式的引數
   }
}
var obj = {
   name: 'lq'
}
var func = function(a, b, c, d){
   console.log(this.name); // 輸出:lq
   console.log([a, b, c, d]); // 輸出:[1, 2, 3, 4]
}.binds(obj,1, 2);

func(3,4);
複製程式碼

(3)借用其他物件的方法

借用的第一種場景是“借用建構函式”:

var A = function (name) {
    this.name = name;
}
var B = function () {
    A.apply(this, arguments);
}
B.prototype.getName = function () {
    return this.name;
}
var b = new B('xiaoming');
console.log (b.getName()); //輸出:xiaoming
複製程式碼

借用的第二種場景是“借用Array.prototype物件上的方法”,比如:

(function(){
    Array.prototype.push.call(arguments, 3);
    console.log (arguments); // 輸出:[1, 2, 3]
})(1, 2)
複製程式碼

在操作arguments時我們經常會借用Array.prototype物件上的各種方法。比如想把arguments轉成真正陣列時,可以借用Array.prototype.slice方法。想截去arguments列表中的第一個元素,可以借用Array.prototype.shift方法。

相關文章