js call 和apply

   ECMAScript3  給Function原型上定義了兩個方法,他們是Function.prototype.call和Function.prototype.apply。數組

 

  • call和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]);

  • call和apply的用途

    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
View Code

在實際開發中,常常會遇到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屬性可讀寫;

相關文章
相關標籤/搜索