call,apply,bind用法及原理詳解

前言

在js中,全部的函數在被調用的時候都會默認傳入兩個參數,一個是this,還有一個是arguments。在默認狀況下this都是指當前的調用函數的對象。可是有時候咱們須要改變this的指向,也就是說使函數能夠被其餘對象來調用,那麼咱們應該怎樣作呢?這時候咱們就可使用call,apply和bind方法了。
那麼call,apply和bind來自哪裏?
在js中全部的函數都是Function的實例,並且對於Function來講,它的原型即Function.prototype中含有不少東西,其中call,apply和bind方法就是Function原型中的方法,因此根據原型的規則,全部的函數均可以使用原型中屬性和方法,因此來講,對於全部的函數均可以使用call,apply和bind方法。數組

1、方法定義

apply方法

使用 apply, 你能夠繼承其餘對象的方法:app

注意這裏apply()的第一個參數是null,在非嚴格模式下,第一個參數爲null或者undefined時會自動替換爲指向全局對象,apply()的第二個參數爲數組或類數組。函數

function fruits() {}
fruits.prototype = {
    color: "red",
    say: function() {
        console.log("My color is " + this.color);
    }
}
var apple = new fruits;
apple.say();    //My color is red

可是若是咱們有一個對象banana= {color : "yellow"},咱們不想對它從新定義say方法,那麼咱們能夠經過callapplyapplesay方法:ui

banana = {
    color: "yellow"
}
apple.say.call(banana);     //My color is yellow
apple.say.apply(banana);    //My color is yellow

因此,能夠看出callapply是爲了動態改變this而出現的,當一個object沒有某個方法(本案例中banana沒有say方法),可是其餘的有(本案例中applesay方法),咱們能夠藉助callapply用其它對象的方法來操做this

call方法

call()是apply()的一顆語法糖,做用和apply()同樣,一樣可實現繼承,惟一的區別就在於call()接收的是參數列表,而apply()則接收參數數組。prototype

bind方法

bind()的做用與call()和apply()同樣,都是能夠改變函數運行時上下文,區別是call()和apply()在調用函數以後會當即執行,而bind()方法調用並改變函數運行時上下文後,返回一個新的函數,供咱們須要時再調用。code

this.num = 9; 
var mymodule = {
  num: 81,
  getNum: function() { 
    console.log(this.num);
  }
};

mymodule.getNum(); // 81

var getNum = mymodule.getNum;
getNum(); // 9, 由於在這個例子中,"this"指向全局對象

var boundGetNum = getNum.bind(mymodule);
boundGetNum(); // 81

bind()方法會建立一個新函數,稱爲綁定函數,當調用這個綁定函數時,綁定函數會以建立它時傳入bind()方法的第一個參數做爲this,傳入bind()方法的第二個以及之後的參數加上綁定函數運行時自己的參數按照順序做爲原函數的參數來調用原函數。對象

2、call,apply,bind區別

一、call的arg傳參需一個一個傳,apply則直接傳一個數組。繼承

function hello(name,age){
  console.log(name);
  console.log(age);
}
hello.call(this,"tsrot",24);
hello.apply(this,["tsrot",24]);

二、call和apply直接執行函數,而bind須要再一次調用。get

var obj = {
    x: 81,
};
var foo = {
    getX: function() {
        return this.x;
    }
}
console.log(foo.getX.bind(obj)());  
console.log(foo.getX.call(obj));    
console.log(foo.getX.apply(obj));

三、如何選用

  • 若是不須要關心具體有多少參數被傳入函數,選用apply();
  • 若是肯定函數可接收多少個參數,而且想一目瞭然表達形參和實參的對應關係,用call();
  • 若是咱們想要未來再調用方法,不需當即獲得函數返回結果,則使用bind();

3、運用場景

一、實現繼承

function Animal(name) {
  this.name = name;
  this.showName = function () {
    console.log(this.name);
  }
  }
function Cat(name) {
  Animal.call(this, name); 
  }
var cat = new Cat('Black Cat');
cat.showName();

二、數組追加

var array1 = [1 , 2 , 3, 5];
var array2 = ["xie" , "li" , "qun" , "tsrot"];
Array.prototype.push.apply(array1, array2);
console.log(array1);

三、獲取數組中的最大值和最小值

var num = [1,3,5,7,2,-10,11];
var maxNum = Math.max.apply(Math, num);
var minNum = Math.min.apply(Math, num);
console.log(maxNum); 
console.log(minNum);

四、將僞數組轉化爲數組

var fakeArr = {0:'a',1:'b',length:2};
var arr1 = Array.prototype.slice.call(fakeArr);
console.log(arr1[0]); 
var arr2 = [].slice.call(fakeArr);
console.log(arr2[0]); 
arr1.push("c");
console.log(arr1);

五、保存this變量

var foo = {
    bar : 1,
    eventBind: function(){
        var _this = this ;
        $('.someClass').on('click',function(event) {
            console.log(_this.bar);     
        });
    }
}
var foo = {
    bar : 1,
    eventBind: function(){
        $('.someClass').on('click',function(event) {
            console.log(this.bar);      
        }.bind(this));
    }
}

4、手寫call,apply及bind函數

首先從如下幾點來考慮如何實現這幾個函數

  • 不傳入第一個參數,那麼上下文默認爲window
  • 改變了this指向,讓新的對象能夠執行該函數,並能接受參數

先來實現call

Function.prototype.myCall = function(context) { 
    if (typeof  this !== 'function') { 
        throw  new  TypeError('Error') 
    } 
    context = context || window ;
    context.fn = this  ;
    const args = [...arguments].slice(1); 
    const result = context.fn(...args) ;
    delete context.fn ;
    return result ;
}

如下是對實現的分析:

  • 首先context爲可選參數,若是不傳的話默認上下文爲window
  • 接下來給context建立一個fn屬性,並將值設置爲須要調用的函數
  • 由於call能夠傳入多個參數做爲調用函數的參數,因此須要將參數剝離出來
  • 而後調用函數並將對象上的函數刪除

以上就是實現call的思路,apply的實現也相似,區別在於對參數的處理,因此就不一一分析思路了

Function.prototype.myApply = function(context) { 
        if (typeof  this !== 'function') { 
            throw  new  TypeError('Error') 
        } 
        context = context || window ;
        context.fn = this;  
        let result // 處理參數和 call 有區別  
        if (arguments[1]) { 
            result = context.fn(...arguments[1]) 
        } else { 
            result = context.fn() 
        } 
        delete context.fn ;
        return result; 
    }

bind的實現對比其餘兩個函數略微地複雜了一點,由於bind須要返回一個函數,須要判斷一些邊界問題,如下是bind的實現

Function.prototype.myBind = function (context) {
    if (typeof this !== 'function') {
        throw new TypeError('Error')
    }
    const _this = this
    const args = [...arguments].slice(1)
    // 返回一個函數
    return function F() {
        // 由於返回了一個函數,咱們能夠 new F(),因此須要判斷
        if (this instanceof F) {
        return new _this(...args, ...arguments)
        }
        return _this.apply(context, args.concat(...arguments))
    }
}

如下是對實現的分析:

  • 前幾步和以前的實現差很少,就不贅述了
  • bind返回了一個函數,對於函數來講有兩種方式調用,一種是直接調用,一種是經過new的方式,咱們先來講直接調用的方式
  • 對於直接調用來講,這裏選擇了apply的方式實現,可是對於參數須要注意如下狀況:由於bind能夠實現相似這樣的代碼f.bind(obj, 1)(2),因此咱們須要將兩邊的參數拼接起來,因而就有了這樣的實現args.concat(...arguments)
  • 最後來講經過new的方式,對於new的狀況來講,不會被任何方式改變this,因此對於這種狀況咱們須要忽略傳入的this

將不斷更新完善,期待您的批評指正!

相關文章
相關標籤/搜索