call,apply and bind in JavaScript

call,apply and bind in JavaScript

在ECMAScript中,每一個函數都包含兩個繼承而來的方法:apply() 和 call(),這兩個方法的用途都是在特定的做用域中調用函數,主要做用跟bind同樣,用來改變函數體內this的指向,或者說是在函數調用時改變上下文。

文章儘可能使用大量實例進行講解,它們的使用場景。同時,也會由淺入深的引導出一些理論,畢竟這幾個經常使用方法,在MDN上都能找到合理的解釋javascript

基本功能

改變this的指向

var fruit = {
    fruitName:"apple"
  }
  function getFruit() {
    console.log("I like "+this.fruitName)
  }

  getFruit();    // log   I like undefined
  getFruit.call(fruit)    // log   I like apple
  getFruit.apply(fruit)   // log   I like apple
  var newBind = getFruit.bind(fruit)
  newBind();              // log   I like apple

當 getFruit 並不是做爲一個對象的屬性,而是直接當作一個函數來調用,裏面的this就會被綁定到全局對象上,即window上, 因此直接調用 getFruit,裏面的this指向了全局對象上,返回 undefined前端

在嚴格模式下,函數被調用後,裏面的this默認是 undefined

後面,經過調用函數上的callapply方法,該變this指向,函數裏面的this指向fruitjava

區別:
bind一樣實現了改變this指向的功能,可是它不會當即執行,而是會從新建立一個綁定函數,新函數被調用時,使用bind()方法裏面的第一個參數做爲thisgit

接受參數

這三個方法,從接受的第二參數開始,都直接傳遞給函數,可是接受參數的方法卻很大的不一樣。github

call,從第二個參數開始,以參數列表的形式展現,數組

apply,則把傳遞的函數參數,放在一個數組裏面做爲第二個參數。閉包

fn.call(obj,arg1,arg2);
fn.apply(obj,[arg1,arg2])

bind,從第二個參數開始,一樣以參數列表的形式,可是會提早放在新綁定函數的參數以前app

var foo = function(name,age){
   console.log("name: "+name+"- age: "+age)
 }

 var p1 = foo.bind(this,"popo");   // "popo" 做爲新函數的第一個參數。
 p1(13);                       // logs    name: popo- age: 13
 p1("bobo",14)                 // logs    name: popo- age: bobo

應用場景

  • 綁定事件回調中

    $('.div-class').on('click',function(event) {
            /*TODO*/
            }.bind(this));
          }
      }

    一般,咱們在改變函數上下文以前,都會使用相似that = this,或者self,_this,來把this賦值給一個變量。利用.bind(),能夠傳入外層的上下文。函數

  • 循環回調

    循環中利用閉包來處理回調ui

    for(var i = 0;i < 10;i++){
     (function(j){
         setTimeout(function(){            
             console.log(j);
         },600);
     })(i)
    }

    每次循環,都會產生一個當即執行的函數,函數內部的局部變量j保存不一樣時期i的值,循環過程當中,setTimeout回調按順序放入消息隊列中,等for循環結束後,堆棧中沒有同步的代碼,就去消息隊列中,執行對應的回調,打印出j的值。

    同理,能夠利用bind,每次都建立新的函數,而且已經預先設置了參數,傳入不一樣的指針

    function func(i) {
        console.log(i)
      }
      for(var i =0 ;i< 10;i++) {
        setTimeout(func.bind(null,i),600)
      }
  • 實現繼承

    var Person = function(name,age) {
       this.name = name;
       this.age = age;
     }
    
     var P1 = function(name,age) {
       // 借用構造函數的方式實現繼承
       // 利用call 繼承了Person
       Person.call(this,name,age)
     }
     P1.prototype.getName = function() {
       console.log("name: "+this.name+", age: "+this.age);
     }
    
     var newPerson = new P1("popo",20);   // logs name: popo, age: 20
     newPerson.getName();

    實質上,能夠當作經過call()或者apply()方法,在即將新建的對象,即這裏的newPerson上,執行超類型的構造函數,分別在當前上下文this上添加nameage屬性。

  • 數組驗證的終極方法

    function isArray(value) {
        return Object.prototype.toString.call(value) == "[object Array]"
      }

    借用了Object原生的toString()方法,打印出對應變量的構造函數名,

  • 類數組轉換爲數組

    // 實現一個簡單的數組 'unshift'方法
      Array.prototype.unshift = function(){
        this.splice.apply(this,
          [0,0].concat(Array.prototype.slice.apply(arguments)));
          return this.length;
      }

    首先,利用this.splice.apply(),其中splice,能夠直接從數組中移除或者插入變量。apply()則以數組的形式傳遞參數,須要利用concat拼接數組。

    當函數被調用時,在函數內部會獲得類數組arguments,它擁有一個length屬性,可是沒有任何數組的方法。因此,將slice方法中的this指向arguments,獲取到arguments的長度,從而肯定方法的startend下標,獲得一個數組變量。

    一樣適用的還有,DOM裏面的NodeList對象,它也是一種類數組對象。

深刻理解

實現bind 方法

bind方法在ECMAScript5裏面被引入,前面提到過,調用該方法時,返回一個新的函數,能夠簡單使用下面方法實現其改變this指向的功能。

Function.prototype.bind = function(scope) {
    var fn = this;
    return function() {
      return fn.apply(scope)
    }
  }

接着,就能夠利用concat把bind傳遞的預置參數拼接到新函數的參數列表中。

Function.prototype.bind = function(scope) {
      var args = Array.prototype.slice.call(arguments,1)
      var fn = this
      return function() {
        return fn.apply(scope,args.concat(Array.prototype.slice.call(arguments)))
      }
   }

參考連接

相關文章
相關標籤/搜索