call,apply詳解
javascript中,call和apply方法的用途是為了擴充函數賴以生存的作用域,通俗點來說,就是為了動態改變函數體內部this的指向。
用途: 擴充函數賴以生存的作用域。
好處: 對象與方法之間不需要任何的耦合關系。
javascript的一大特點是,函數存在定義時上下文,運行時上下文,和上下文是可變的,這樣的概念。
先上一個例子:
function showColor(){
console.log("my color is:" + this.color);
}
window.color = "red";
showColor.call(window); //my color is:red
上面的例子中,shouwColor函數并沒有對外部調用自己傳入的參數進行接收,那么調用者是怎么樣通過調用該函數來得到想要的結果呢?
先來解釋下,上面這段代碼。代碼中,定義了一個showColor函數,以及一個掛載在window下的一個全局對象color,showColor.call(this)執行了shouwColor函數,并且改變了shouwColor函數中this的指向,call方法傳入的第一個參數,始終代表著this的指向。當傳入的第一個參數是window的時候,shouwColor函數中的this就指向了window,那么this.color就相當于window.colorl了,打印出來的結果就自然是window.color的值了。
var colorObj = {"color":"blue"};
showColor.call(colorObj); //my color is:blue
那么我們再來定義一個colorObj 對象,對象中,有一個屬性color,call方法中第一個參數是colorObj 對象,那么一樣的道理,shouwColor函數中的this就指向了**colorObj **對象了。這就是call方法的簡單運用了。
call,apply方法的定義
call方法:
語法:call([thisObj[,arg1[, arg2[, [,.argN]]]]])
定義:調用一個對象的一個方法,以另一個對象替換當前對象。
說明:
call 方法可以用來代替另一個對象調用一個方法。call 方法可將一個函數的對象上下文從初始的上下文改變為由 thisObj 指定的新對象。
如果沒有提供 thisObj 參數,那么 window 對象被用作 thisObj。
apply方法:
語法:apply([thisObj[,argArray]])
定義:應用某一對象的一個方法,用另一個對象替換當前對象。
說明:
如果 argArray 不是一個有效的數組或者不是 arguments 對象,那么將導致一個 TypeError。
如果沒有提供 argArray 和 thisObj 任何一個參數,那么 window 對象將被用作 thisObj, 并且無法被傳遞任何參數。
call,apply方法的區別
對于 apply、call 二者而言,作用完全一樣,只是接受參數的方式不太一樣。例如,有一個函數定義如下:
function test(arg1,arg2){
......
}
就可以通過以下方式調用了
test.call(this,arg1,arg2);
test.apply(this,[arg1,arg2]);
其中 this 是你想指定的上下文,可以是任何一個 JavaScript 對象(JavaScript 中一切皆對象),call 需要把參數按順序傳遞進去,而 apply 則是把參數放在數組里。
call,apply方法的示例
1.數組的合并
var facebookImg = ["fb1.jpg","fb2.jpg","fb3.jpg"];
var youtubeImg = ["yt1.jpg","yt2.jpg","yt3.jpg"];
Array.prototype.push.apply(facebookImg,youtubeImg); //返回值為合并后的facebookImg數組的長度
facebookImg的值為: ["fb1.jpg","fb2.jpg","fb3.jpg","yt1.jpg","yt2.jpg","yt3.jpg"]
或者合并后賦值給一個新數組(不改變原數組的值)
var facebookImg = ["fb1.jpg","fb2.jpg","fb3.jpg"];
var youtubeImg = ["yt1.jpg","yt2.jpg","yt3.jpg"];
var allImg = [];
allImg.push.apply(allImg,facebookImg);
allImg.push.apply(allImg,youtubeImg);
2.獲取數組中的最大值和最小值
js獲取數組中的最大值和最小值,有多種方法,比如冒泡排序等,使用apply和call方法也是可以達到相同的效果的。
var numbers = [5, 458 , 120 , -215 ];
var applyMaxInNumbers = Math.max.apply(Math, numbers); //458
var applyMinInNumbers = Math.min.apply(Math,numbers); //-215
number 本身沒有 max 方法,但是 Math 有,我們就可以借助 call 或者 apply 使用其方法。
3.類(偽)數組使用數組方法
Javascript中存在一種名為偽數組的對象結構。比較特別的是 arguments 對象,還有像調用 getElementsByTagName , document.childNodes 之類的,它們返回NodeList對象都屬于偽數組。不能應用 Array下的 push , pop 等方法。
但是我們能通過 Array.prototype.slice.call 轉換為真正的數組的帶有 length 屬性的對象,這樣 domNodes 就可以應用 Array 下的所有方法了。
4.定義一個 log 方法,讓它可以代理 console.log 方法
一般我們都會去這樣的實現:
function log(msg){
console.log(msg);
}
log("testData1"); //testData1
log("data1","data2"); //data1
上面的這種實現的方式在傳入一個參數的時候是滿足要求的,但是當傳入的參數是多個的話,就失效了,因為log方法只接受了第一個且唯一一個參數。那么如果要在傳入多個參數的情況下也能夠實現,就可以用到call和apply方法了,注意,當不清楚參數的具體個數時,建議最好運用apply方法。
function log(){
console.log.apply(window,arguments);
}
log("testData1"); //testData1
log("data1","data2","data3"); //data1,data2,data3
接下來的要求是給每一個 log 消息添加一個"(doView)"的前輟,比如:
log("hello world"); //(doView)hello world
那么就可以利用偽數組轉為標準數組的方式,然后再使用unshift方法操作數組
function log(){
var argsData = Array.prototype.slice.call(arguments);
argsData.unshift('(doView)');
console.log.apply(window, args);
};
5.借用構造函數繼承的實現
javascript中的繼承方式實現由多種,包括原型繼承,借用構造函數繼承與組合繼承等。那么現在就用call和apply方法來簡單實現下javascript中的構造函數的繼承吧。
function superType(name){
this.name = name;
}
function subType(name,age){
this.age = age;
superType.call(this,name);
}
var types1 = new subType("yqxcn", 30);
console.log("age:" + types1.age); //age:30
console.log("name:" + types1.name); //name:yqxcn,繼承自superType屬性值
var types2 = new subType("wx272252", 18);
console.log("age:" + types2.age); //age:18
console.log("name:" + types2.name); //name:wx272252,繼承自superType屬性值
bind的用法
bind方法雖然與call,apply的用法有點不同,但是其作用也是一樣的,也是可以改變函數體內this的指向。
MDN的解釋是:bind()方法會創建一個新函數,稱為綁定函數,當調用這個綁定函數時,綁定函數會以創建它時傳入 bind()方法的第一個參數作為 this,傳入 bind() 方法的第二個以及以后的參數加上綁定函數運行時本身的參數按照順序作為原函數的參數來調用原函數。
以最開始的例子為例:
function showColor(){
console.log("my color is:" + this.color);
}
window.color = "red";
showColor.bind(window); //這里并不會打印出想要的結果
這個例子就可以很好的證明了上面MDN的解釋了,當瀏覽器執行"showColor.bind(window); "這一句代碼的時候,bind()創建了一個綁定函數,也就是說這句代碼只是創建了一個函數,卻并沒有調用執行該函數,就像我們自己定義了一個普通函數而沒有調用它,所以這句代碼需要改成如下:
showColor.bind(window)(); //my color is:red
這也是apply、call、bind的不同之處。bind方法是創建一個函數,然后可以在需要調用的時候再執行函數,并非是立即執行函數;而call,apply是在改變了上下文中的this指向后并立即執行函數。
總結
- call,apply,bind都是可以改變函數體內this的指向。
- call,apply,bind使用時,傳入的第一個參數都是用來傳遞this的指向的,也就是對上下文的指定。
- call,apply,bind都是可以傳入多個參數,不同的是,call和bind的后續參數都是按照順序傳參,而apply的傳參類型是數組;bind的參數可以在函數執行的時候再次添加。
原創文章,站在前輩們的經驗上的總結,文中如有不正之處,還望指正!