ECMAScript3 給Function原型上定義了兩個方法,他們是Function.prototype.call和Function.prototype.apply。數組
1 var func =function(a,b,c){ 2 alert([a,b,c]); 3 }; 4 func.apply(null,[1,2,3]);//apply 5 6 7 var func =function(a,b,c){ 8 alert([a,b,c]); 9 }; 10 func.call(null,1,2,3);//call
apply::兩個參數,第一個參數:指定了函數體內this對象的指向;瀏覽器
第二個參數:一個帶下標的集合,能夠爲數組,也能夠爲僞數組app
call:多個參數:第一個參數:指定了函數體內this對象的指向;ide
第二個參數開始日後,每個參數被依次傳入函數;函數
call和apply傳入的第一個參數爲null,函數體內this會指向默認的宿主對象,在瀏覽器中則是windowthis
若是在嚴格模式下,函數體內的this仍是爲null 1 var func = function(a,b,c){加密
2 alert(this === window);//true 3 }; 4 5 func.apply(null,[1,2,3]) 6 7 var func = function(a,b,c){ 8 "use stract";//若是在嚴格模式下,函數體內的this仍是爲null 9 alert(this === null);//true 10 }; 11 func.apply(null,[1,2,3]);
1.改變this的指向 spa
1 var obj1 = { 2 name:"seven" 3 }; 4 5 var oj2 = { 6 name:"anne" 7 }; 8 9 window.name = "window"; 10 11 var getName = function(){ 12 alert(this.name) 13 }; 14 15 getName();//window 16 getName.call(obj1);//seven 17 getName.apply(obj2);//anne
在實際開發中,常常會遇到this指向被不經意改變的場景,好比,有一個div節點,div節點的onclick事件中的this原本指向這個div的:prototype
1 dcument.getElementById("div1").onclick=function(){ 2 alert(this.id);//div1 3 } 4 //加入改時間函數中有一個內部函數func,在實踐內部調用func函數時,func函數體內的this就指向了window,而不是咱們預期的div 5 6 document.getElementById("div1").oonclick = function(){ 7 alert(this.id);//div1 8 var func = function(){ 9 alert(this.id) //undefined 10 }; 11 func(); 12 }; 13 //這時候咱們用call來修正func函數內的this,使其指向div 14 document.getElementById("div1").onclick=function(){ 15 var func = function(){ 16 alert(this.id);//div1 17 }; 18 func.call(this); 19 }
使用call來修正this的場景,咱們並非第一次遇到,code
1 document.getElementById = (function(){ 2 return function(){ 3 return func.apply(document,arguments); 4 } 5 })(document.getElementById); 6 7 var getId =document.getElementById; 8 var div = getId("div1"); 9 alert(div.id); //div1
2.Function.prototype.bind
大部分瀏覽器都實現了內置的Function.prototype.bind,用來指定函數內部的this指向,即便沒有原生的Function.prototype.bind實現。
1 Function.prototype.bind= function(context){ 2 var self = this;//保存原函數 3 return function(){//返回一個新函數 4 return self.apply(context,arguments);//執行新的函數的時候,會把以前的context當作新函數體內的this 5 }; 6 } 7 8 var obj = { 9 name:"seve" 10 }; 11 12 var a = function(){ 13 alert(this.name);//seve 14 }.bind(obj); 15 16 a();
咱們經過Function.prototype.bind來「包裝」func函數,而且傳入一個對象context看成參數,這個context對象就是咱們想修正的this對象。
在Function.prototype.bind的內部實現中,咱們先把func函數的引用保存起來,而後返回一個新函數。當咱們在未來執行a函數時,實際上先執行的是這個剛剛返回的新函數。在新函數內部,self.apply(context,arguments)這句話代碼是執行原來的a函數,而且指定context對象爲a函數體內的this.
下面是複雜一點的實現:
1 Function.prototype.bind = function(){ 2 var self = this, 3 context = [].shift.call(arguments), 4 args = [].slice.call(arguments), 5 return function(){ 6 return self.apply(context,[].concat.call(args,[].slice.call(arguments))); 7 } 8 }; 9 var obj = { 10 name:"seven" 11 }; 12 13 var func = function(){ 14 alert(his.name); 15 alert([a,b,c,d]) 16 }.bind(obj,1,2); 17 18 func(3,4);
3.借用其餘對象的方法
借用方法的第一種場景是「借用構造函數」,經過這種技術,能夠實現一些相似繼承的效果;
1 var A = function(name){ 2 this.name = name; 3 }; 4 5 var B = function(){ 6 A.apply(this,arguments); 7 }; 8 9 B.prototype.getName = function(){ 10 return this.name; 11 }; 12 13 var b = new B("seven"); 14 console.log(b.getName());
借用方法的第二種運用場景跟咱們的關係更加密切。
函數的參數列表arguments是一個類數組對象,雖然它也有「下標」,但它並不是真正的數組,因此也不像數組同樣,進行排序操做或者往集合裏添加一個新的元素。這種狀況下,咱們經常會借用Array.prototype對象上的方法。好比想往arguments中添加一個新的元素,一般會借用Array.prototype.push:
(function(){ Array.prototype.push.call(arguments,3); console.log(arguments); })(1,2)//在操做arguments的時候,咱們常常很是頻繁地找Array.prototype對象借用方法。
將數組轉成真正的數組:Array.prototype.slice
截取arguments列表的頭一個元素:Array.prototype.shift
v8的引擎源碼:以Array.prototype.push爲例
1 function ArrayPush(){ 2 var n = TO_UINT32(this.length);//被push的對象的length 3 var m = %_ArgumentsLength();//push的參數個數 4 for(var i =0;i<m;i++){ 5 this[i+n] = %_Arguments(i);//複製元素 (1) 6 } 7 this.length =n+m;//修正length屬性的值 (2) 8 return this.length; 9 }; 10 //經過這段代碼能夠看到,Array.prototype.push其實是一個屬性複製的過程,把參數按照下標一次添加到push的對象上面,順便修改了這個對象的length屬性。至於被修改的對象是誰,究竟是數組仍是類數組對象,這一點並不重要。 11 //由此能夠推斷出,咱們能夠把「任意」對象傳入Array.prototype.push: 12 var a = {}; 13 Array.prototype.push.call(a,"first"); 14 alert(a.length);//輸出:1 15 alert(a[0]);//first 16 //這段代碼在絕大部分瀏覽器裏都能順利執行,但因爲引擎的內部實現存在差別,若是在低版本 的IE瀏覽器中執行,必須顯示的給對象a設置length屬性; 17 var a = { 18 length:0 19 };
前面咱們之因此把「任意」兩個字加上引號,是由於能夠借用Array.prototype.push方法的對象還要知足一下兩個條件,從ArrayPush函數的(1)和(2)處也能夠猜到,這個對象至少還要知足:
對象自己要能夠存取屬性;
對象的length屬性可讀寫;