call, apply都屬於Function.prototype的方法,因為屬於Function.prototype,所以每個Function對象實例,也就是每個方法都有call, apply屬性啦。
如果不明白,請見“Javascript之一切皆為對象3”。
而且它們的作用都是一樣的,只是使用方式不同而已。
作用:借用別人的方法來調用,就像自己有這個方法一樣。
咦,那它們怎樣才能達到這目的呢?
對象。
對象?
是的,其實就是改變執行上下文對象的內部指針,因為在Javascript中,代碼總有一個執行上下文對象,那麼當我手動改變它時,就可以改變這個執行上下文啦,也就可以利用非自己的方法成為自己的方法哦。
我們一起來寫個Demo。
假如,我有一個方法a,它的作用是輸出對象的名字this.name;那麼當我使用call或者apply改變它的執行上下文對象時,它的輸出結果是不一樣的。
什麼意思?
詳情請見下代碼:
<!DOCTYPE html>
<head>
<title>call&apply</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
var name = 'windowName';
//方法a的作用,是輸出對象的名字
function a(){
console.log(this.name);
}
function b(){
this.name = 'bName';
}
//將a方法的執行上下文對象指向window
a.call(window);
//將a方法的執行上下文對象指向new b()
a.call(new b());
</script>
</body>
</html>
執行上述代碼,結果如下:

看見了麼?所以說call,apply的作用就是借用別人的方法,改變別人方法的執行上下文對象為自己,成為自己的方法,為己所用。
注意: call或apply的第一個參數傳的是什麼,它們就會將其默認為執行上下文對象。倘若我們沒有指明call或apply的執行上下文對象,即,call和apply的第一個參數是null、undefined或為空時,在非嚴格模式下,函數內的this指向window或global,浏覽器就是window。嚴格模式下,null為null,undefined或空為undefined。
什麼意思,請見下面的demo(僅以call舉例且為非嚴格模式):
<!DOCTYPE html>
<head>
<title>call&apply</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
function print(){
console.log(this);
};
//將第一個參數數字1,作為執行上下文對象
print.call(0,1,2);
//將第一個參數字符串'123',作為執行上下文對象
print.call('123');
//將第一個參數true,作為第執行上下文對象
print.call(true);
//將第一個參數對象,作為執行上下文對象
print.call(new Object());
//將null傳入
print.call(null);
//將undefined傳入
print.call(undefined);
//不傳任何參數
print.call();
</script>
</body>
</html>
看見了麼,我上面傳入的依次是數字,字符串,true,對象,null,undefined和空,得到下面的結果:
那麼,call與apply既然作用一樣,那它們有什麼區別呢?
它們的第一個參數,毋庸置疑,都是傳入的執行上下文對象,區別是從第二個參數開始的。call方法的其它參數依次傳遞給借用的方法作參數,而apply就兩個參數,第二個參數為一個數組傳遞。
簡單點,就是:
fun.call(obj, arg1, arg2…) === fun.apply(obj, [arg1, arg2…]) === obj.fun(arg1, arg2…);
咦,call和apply的區別是,參數的傳遞不同,有什麼用呢?
根據它們傳遞參數的區別,當參數明確的時候,使用call;當傳遞的參數不明確時,用 apply咯,即傳遞arguments給apply作為第二個參數。
好了,光說不做沒用,我們寫個demo看看。
<!DOCTYPE html>
<head>
<title>call&apply</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
function print(name, age, time){
console.log("name: "+ name +" age: "+ age +" time: "+ time );
};
function fn(a, b, c){
//使用call,參數明確
print.call(this,a);
//使用apply,參數明確
print.apply(this,[a, b]);
//使用apply,參數不明確
print.apply(this,arguments);
}
fn('monkey',24,'1992');
</script>
</body>
</html>
執行上述代碼,結果如下:

call與apply,這下明白了麼?
二、bindbind,最開始認識它的時候,理解就是改變執行上下文的對象。
比如,當我們使用setTimeout時,默認匿名函數裡的this指向的是window,但使用對象的方法時,我想將this指向對象呢,怎麼辦呢?其中的一個方法就是使用bind。
(關於setTimeout的理解,見“setTimeout那些事兒”)。
如:
<!DOCTYPE html>
<head>
<title>bind</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
var name = 'window';
var obj = {
name:'monkey',
print: function(){
//在這裡使用bind,顯示地將this指向obj,所以console.log會輸出'monkey'
setTimeout(function(){
console.log(this.name);
}.bind(this),100);
}
};
obj.print();
</script>
</body>
</html>
執行上述代碼結果為:

好了,既然談到bind是改變執行上下文中的對象,我靠,那我們怎麼不使用call或apply呢?
call或apply不也是改變執行上下文的對象麼?
是的,我們將上面的demo修改下,將bind換成call,代碼如下:
<!DOCTYPE html>
<head>
<title>bind</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
var name = 'window';
var obj = {
name:'monkey',
print: function(){
setTimeout(function(){
console.log(this.name);
}.call(this)/*在這裡將bind換成call*/,100);
}
};
obj.print();
</script>
</body>
</html>
打開chrome調試器,得下結果:

咦,我靠,這不是和bind一樣麼?
是的,但如果我們將setTimeout的延遲時間,換成2秒,或者更長呢?打開chrome調試器,運行修改後的代碼,你就會發現區別,call或apply是立馬呈現’monkey’,而bind是在延遲相應時間後,呈現’monkey’。
Why?
因為call或apply是將執行上下文對象換了後,立即執行;而bind是將執行上下文對象換了後,創建一個新函數。
我們再一起寫個demo看看。
<!DOCTYPE html>
<head>
<title>bind</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
function fun(){
console.log(this.name);
}
function obj1(){
this.name = 'call||apply';
}
function obj2(){
this.name = 'bind';
}
var o1 = new obj1();
var o2 = new obj2();
fun.call(o1);
fun.bind(o2);
</script>
</body>
</html>
執行上述代碼,結果為:
咦,怎麼只打印了一個’call||apply’呢?
因為我們在上面的代碼中,bind我只是綁定了對象o2,但是它又不立即執行,而是返回一個新函數哦。
我們修改以上代碼,手動執行bind返回後的新函數看看。
<!DOCTYPE html>
<head>
<title>bind</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
<script>
function fun(){
console.log(this.name);
}
function obj1(){
this.name = 'call||apply';
}
function obj2(){
this.name = 'bind';
}
var o1 = new obj1();
var o2 = new obj2();
fun.call(o1);
//手動調用bind創建的新函數
fun.bind(o2)();
</script>
</body>
</html>
運行代碼:

嘿嘿,這下對了吧。
所以,一定要記住bind方法會創建一個新函數,稱為綁定函數,當調用這個綁定函數時,綁定函數會以創建它時傳入的第一個參數作為this,即執行上下文對象。
好了,晚安everyone~