談談JavaScript中的call、apply和bind

JavaScript中,若是想要改變當前函數調用的上下文對象的時候,咱們都會聯想到call、apply和bind。好比下面👇javascript

var name = 'window name';
var obj = {
    name: 'call_me_R'
};
function sayName(){
    console.log(this.name);
}
sayName(); // window name
sayName.call(obj); // call_me_R
複製代碼

那麼,call, apply和bind有什麼區別呢?java

call,apply和bind的區別

在說區別以前,先簡單的說下三者的共同之處吧:git

  1. 都是用來改變函數的this對象的指向
  2. 第一個參數都是this要指向的對象
  3. 均可以利用後續參數進行傳參

下面說下區別:github

參數的傳遞

參考下MDN web docs -- Function:web

call方法傳參是傳一個或者是多個參數,第一個參數是指定的對象,如開篇的obj數組

func.call(thisArg, arg1, arg2, ...)
複製代碼

apply方法傳參是傳一個或兩個參數,第一個參數是指定的對象,第二個參數是一個數組或者類數組對象。瀏覽器

func.apply(thisArg, [argsArray])
複製代碼

bind方法傳參是傳一個或者多個參數,跟call方法傳遞參數同樣。app

func.bind(this.thisArg, arg1, arg2, ...)
複製代碼

簡言之,callbind傳參同樣;apply若是要傳第二個參數的話,應該傳遞一個類數組。ide

調用後是否立執行

call和apply在函數調用它們以後,會當即執行這個函數;而函數調用bind以後,會返回調用函數的引用,若是要執行的話,須要執行返回的函數引用。函數

變更下開篇的demo代碼,會比較容易理解:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};
function sayName(){
    console.log(this.name);
}
sayName(); // window name
sayName.call(obj); // call_me_R
sayName.apply(obj); // call_me_R
console.log('---divided line---');
var _sayName = sayName.bind(obj);
_sayName(); // call_me_R
複製代碼

在筆者看來,call, apply 和 bind的區分點主要是上面的這兩點,歡迎有想法的讀者進行補充~😊

手寫call, apply, bind方法

這裏是簡單的實現下相關方法的封裝,爲了簡潔,我這裏儘可能使用了ES6的語法進行編寫,詳細的參考代碼能夠直接戳airuikun大牛的airuikun/Weekly-FE-Interview issues

call方法實現

在上面的瞭解中,咱們很清楚了call的傳參格式和調用執行方式,那麼就有了下面的實現方法:

Function.prototype.call2 = function(context, ...args){
	context = context || window; // 由於傳遞過來的context有多是null
	context.fn = this; // 讓fn的上下文爲context
	const result = context.fn(...args);
	delete context.fn;
	return result; // 由於有可能this函數會有返回值return
}
複製代碼

咱們來測試下:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};

// Function.prototype.call2 is here ...

function sayName(a){
    console.log(a + this.name);
    return this;
}
sayName(''); // window name
var _this = sayName.call2(obj, 'hello '); // hello call_me_R
console.log(_this); // {name: "call_me_R"}
複製代碼

apply方法實現

apply方法和call方法差很少,區分點是apply第二個參數是傳遞數組:

Function.prototype.apply2 = function(context, arr){
    context = context || window; // 由於傳遞過來的context有多是null
    context.fn = this; // 讓fn的上下文爲context
    arr = arr || []; // 對傳進來的數組參數進行處理
    const result = context.fn(...arr); // 至關於context.fn(arguments[1], arguments[2], ...)
    delete context.fn;
    return result; // 由於有可能this函數會有返回值return
}
複製代碼

一樣的,咱們來測試下:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};

// Function.prototype.apply2 is here ...

function sayName(){
    console.log((arguments[0] || '') + this.name);
    return this;
}
sayName(); // window name
var _this = sayName.apply2(obj, ['hello ']); // hello call_me_R
console.log(_this); // {name: "call_me_R"}
複製代碼

bind方法實現

bind的實現和上面的兩種就有些差異,雖然和call傳參相同,可是bind被調用後返回的是調用函數的指針。那麼,這就說明bind內部是返回一個函數,思路打開了:

Function.prototype.bind2 = function(context, ...args){
    var fn = this; 
    return function () { // 這裏不能使用箭頭函數,否則參數arguments的指向就很尷尬了,指向父函數的參數
        fn.call(context, ...args, ...arguments);
    }
}
複製代碼

咱們仍是來測試一下:

var name = 'window name';
var obj = {
    name: 'call_me_R'
};

// Function.prototype.bind2 is here ...

function sayName(){
    console.log((arguments[0] || '') + this.name + (arguments[1] || ''));
}
sayName(); // window name
sayName.bind2(obj, 'hello ')(); // hello call_me_R
sayName.bind2(obj, 'hello ')('!'); // hello call_me_R!
複製代碼

美滋滋😄,成功地簡單實現了call、apply和bind的方法,那麼你可能會對上面的某些代碼有疑問❓

疑惑點

1. 問:call中爲何說 context.fn = this; // 讓fn的上下文爲context 呢?

答:

咱們先來看看下面這段代碼--

var name = 'window name';
var obj = {
    name: 'call_me_R',
    sayHi: function() {
        console.log('Hello ' + this.name);
    }
};
obj.sayHi(); // Hello call_me_R
window.fn = obj.sayHi;
window.fn(); // Hello window name
複製代碼

嗯,神奇了一丟丟,操做window.fn = obj.sayHi;改變了this的指向,也就是this由指向obj改成指向window了。

簡單來講:this的值並非由函數定義放在哪一個對象裏面決定的,而是函數執行時由誰來喚起來決定的。

2. 問:bind中返回的參數爲何是傳遞(context, ...args, ...arguments), 而不是(context, ...args)呢?

答:

這是爲了包含返回函數也能傳參的狀況,也就是bind()()中的第二個括號能夠傳遞參數。

call和apply哪一個好?

據調查--call和apply的性能對比,在分不一樣傳參的狀況下,call的性能是優於apply的。不過在現代的高版本瀏覽器上面,二者的差別並不大。

而在兼容性方面,二者都好啦,別說IE了哈。

在使用的方面仍是得按照需求來使用call和apply,畢竟技術都在更新。適合業務的就是最好的~囧

後話

文章首發:github.com/reng99/blog…

更多內容:github.com/reng99/blog…

客官能夠star下github的博文倉庫否,歡迎提意見共同成長啊~😄

參考

MDN web docs -- Function

airuikun/Weekly-FE-Interview issues

《JavaScript高級程序設計》

相關文章
相關標籤/搜索