詳解call bind apply - 區別/使用場景/es6實現/es3實現

call,apply,bind的區別

  • apply接收數組 func.apply(obj, [arus])
  • call一連串參數 func.call(obj, param1, param2....)
  • bind返回一個函數 func.bind(obj,param...)(parms...)

call,apply,bind的使用場景

  • 將類數組/含有length屬性的對象轉化爲數組javascript

    類數組:(例如經過document.getElementsByTagName獲取的元素、含有length屬性的對象)具備length屬性,而且能夠經過0、一、2…下標來訪問其中的元素,可是沒有Array中的push、pop等方法。java

    注意:可是這個不適用於IE6~8,會報錯,只能使用循環來解決es6

    // 類數組
    let trueArr = Array.prototype.slice.call(arrayLike)
    // 含有length屬性的對象
    let obj4 = {
        0: 1,
        1: 'thomas',
        2: 13,
        length: 3 // 必定要有length屬性
    };
    console.log(Array.prototype.slice.call(obj4)); // [1, "thomas", 13]
  • 求數組中的最大和最小值數組

    注意:邊界問題,臨界值大概在 [ 參數個數限制在65536]app

    let arr = [1,2,3,89,46]
    let max = Math.max.apply(null,arr)//89
    let min = Math.min.apply(null,arr)//1
  • 數組追加函數

    數組方法contact比較:contact返回新數組,不修改原數組測試

    let arr1 = [1,2,3]
    let arr2 = [4,5,6]
    let total = [].push.apply(arr1, arr2) //6
  • 利用call和apply作繼承this

    function Person(name,age){
        // 這裏的this都指向實例
        this.name = name
        this.age = age
        this.sayAge = function(){
            console.log(this.age)
        }
    }
    function Female(){
        Person.apply(this,arguments)//將父元素全部方法在這裏執行一遍就繼承了
    }
    let dot = new Female('Dot',2)
  • 判斷變量類型prototype

    function isArray(obj){
        return Object.prototype.toString.call(obj) == '[object Array]'
    }
    isArray([]) // true
    isArray('dot') // false
  • 其餘:使用 log 代理 console.log代理

    function log(){
      console.log.apply(console, arguments);
    }
    // 固然也有更方便的 let log = console.log()

bind 實現

  • 特色:

    1. 返回一個函數
    2. 能夠傳入參數(使用bind時和bind新生成的函數均可以傳參)
    3. 當 bind 返回的函數做爲構造函數的時候,bind 時指定的 this 值會失效,但傳入的參數依然生效

      var bindFoo = bar.bind(foo, 'daisy');
      var obj = new bindFoo('18');
  • 注意:bind這個方法在IE6~8下不兼容
// 使用apply和call來實現this指向問題
Function.prototype.bind2 = function (context) {
    if (typeof this !== "function") {
      throw new Error("what is trying to be bound is not callable");
    }
    var self = this;
   // 得到bind的參數從第二個參數到最後一個參數
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function () {};
    var fBound = function () {
        // 指bind返回的函數傳入的參數
        var bindArgs = Array.prototype.slice.call(arguments);
       // 看成爲構造函數時,this 指向實例,此時結果爲 true,將綁定函數的 this 指向該實例,可讓實例得到來自綁定函數的值
        // 以上面的是 demo 爲例,若是改爲 `this instanceof fBound ? null : context`,實例只是一個空對象,將 null 改爲 this ,實例會具備 habit 屬性
        // 看成爲普通函數時,this 指向 window,此時結果爲 false,將綁定函數的 this 指向 context
        // new bind返回的函數,this失效,但傳入的參數生效
        return self.apply(this instanceof fNOP ? this : context,                                args.concat(bindArgs));
    }
    
    // fBound.prototype = this.prototype;
    // 保證繼承,原型鏈,讓 fBound 構造的實例可以繼承綁定函數的原型中的值,下面兩行代碼等同於Object.creater()  fbound.prototype = Object.create(this.prototype);
    // 咱們直接修改 fBound.prototype 的時候,也會直接修改綁定函數的 prototype。這個時候,咱們能夠經過一個空函數來進行中轉
    fNOP.prototype = this.prototype;
    fBound.prototype = new fNOP();
    return fBound;
}

// es6實現
Function.prototype.bind = function(context) {
    if(typeof this !== "function"){
       throw new TypeError("not a function");
    }
    let self = this;
    let args = [...arguments].slice(1);
    function Fn() {};
    Fn.prototype = this.prototype;
    let bound = function() {
        let res = [...args, ...arguments]; //bind傳遞的參數和函數調用時傳遞的參數拼接
        context = this instanceof Fn ? this : context || this;
        return self.apply(context, res);
    }
    //原型鏈
    bound.prototype = new Fn();
    return bound;
}

call 實現

  • 實現思路

    1. 將函數設爲對象的屬性 foo.fn = bar
    2. 執行該函數 foo.fn()
    3. 刪除該函數 delete foo.fn
  • 注意的點

    1. 接受不定長參數 - Arguments 對象中取值,第二個到最後一個參數,而後放到一個數組裏
    2. this 參數能夠傳 null,當爲 null 的時候,視爲指向 window
    3. 函數是能夠有返回值的!
  • 難點解析 - 接受不定長參數

    var args = [];
    // 爲了拼出一個參數字符串,arguments類數組,不能使用
    for(var i = 1, len = arguments.length; i < len; i++) {
          // args: ["arguments[1]", "arguments[2]", .....]
        args.push('arguments[' + i + ']');
    }
    // 1. context.fn(args.join(',')) es6語法實現es3的call方法不合適
    // 2. 這裏 args 會自動調用 Array.toString() 這個方法
    // 3. eval做用:當作是<script>標籤,只接受原始字符串做爲參數,用JavaScript的解析引擎來解析這一堆字符串裏面的內容
    var result = eval('context.fn(' + args +')');
  • call總體實現

    Function.prototype.call2 = function (context) {
      // 首先要獲取調用call的函數,用this能夠獲取
        var context = context || window;
        context.fn = this;
        var args = [];
        for(var i = 1, len = arguments.length; i < len; i++) {
            args.push('arguments[' + i + ']');
        }
        var result = eval('context.fn(' + args +')');
        delete context.fn
        return result;
    }
    
    // 測試
    bar.call2(null); // 2
    console.log(bar.call2(obj, 'kevin', 18));
    
    // es6
    Function.prototype.call = function (context) {
        if (!context) {
            context = typeof window === 'undefined' ? global : window;
        }
        context.fn = this;
        let rest = [...arguments].slice(1);// 空數組slice後返回的仍然是空數組
        let result = context.fn(...rest); 
        delete context.fn;
        return result;
    }

apply實現

Function.prototype.apply = function (context, arr) {
    var context = Object(context) || window;
    context.fn = this;
    var result;
    if (!arr) {
        result = context.fn();
    }
    else {
        var args = [];
        for (var i = 0, len = arr.length; i < len; i++) {
            args.push('arr[' + i + ']');
        }
        result = eval('context.fn(' + args + ')')
    }
    delete context.fn
    return result;
}

// es6:
Function.prototype.apply = function (context, rest) {
    if (!context) {
        //context爲null或者是undefined時,設置默認值
        context = typeof window === 'undefined' ? global : window;
    }
    context.fn = this;
    let result;
    if(rest === undefined || rest === null) {
        //undefined 或者 是 null 不是 Iterator 對象,不能被 ...
        result = context.fn(rest);
    }else if(typeof rest === 'object') {
        result = context.fn(...rest);
    }
    delete context.fn;
    return result;
}

補充:

  • new實現
  • 繼承和原型鏈知識
相關文章
相關標籤/搜索