在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
在說區別以前,先簡單的說下三者的共同之處吧:git
下面說下區別: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, ...)
複製代碼
簡言之,call
和bind
傳參同樣;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
的區分點主要是上面的這兩點,歡迎有想法的讀者進行補充~😊
這裏是簡單的實現下相關方法的封裝,爲了簡潔,我這裏儘可能使用了ES6的語法進行編寫,詳細的參考代碼能夠直接戳airuikun大牛的airuikun/Weekly-FE-Interview issues。
在上面的瞭解中,咱們很清楚了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
方法和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
的實現和上面的兩種就有些差異,雖然和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的。不過在現代的高版本瀏覽器上面,二者的差別並不大。
而在兼容性方面,二者都好啦,別說IE了哈。
在使用的方面仍是得按照需求來使用call和apply
,畢竟技術都在更新。適合業務的就是最好的~囧
客官能夠star下github的博文倉庫否,歡迎提意見共同成長啊~😄
airuikun/Weekly-FE-Interview issues
《JavaScript高級程序設計》