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~