js模擬call和apply

一、call和apply介紹

call()方法使用一個指定的 this 值和單獨給出的一個或多個參數來調用一個函數。

apply() 方法調用一個具有給定this值的函數,以及作爲一個數組(或類似數組對象)提供的參數

該方法的語法和作用與 apply() 方法類似,只有一個區別,就是 call() 方法接受的是一個參數列表,而 apply() 方法接受的是一個包含多個參數的數組。

二、call的使用

call是Function默認實現的函數,使用方式爲:fn.call(context, arg0, arg1, arg2...)。fn函數的上下文環境變成context,並且還會使用傳入的參數。

var foo = { value: 1 };

function fn() {
    console.log(this.value);
}

fn.call(foo); // value => 1

三、模擬call的實現

要模擬call的實現,首先明白call做了那些事情。

  • 只有使用call時,fn的上下文環境纔會改變
  • fn的實際參數來自於call的第二個參數一直到最後一個參數就是
  • call方法不傳入上下文的時候,默認使用全局上下文

上面的3點大概就是非嚴格模式下call的行爲。

先看第一點,如何改變fn的上下文。

改變fn的上下文,只需要三步:

  1. 打開冰箱(將fn掛在foo上,命名爲_fn)
  2. 大象放進去(執行foo._fn)
  3. 關上冰箱(刪除foo._fn)

寫成代碼就是如下

var foo = { 
    value: 1 
};

function fn() {
    console.log(this.value);
}

Function.propotype.myCall = function(that) {
    that._fn = this;
    that._fn();
    delete that._fn;
}

第一步就輕鬆的完成了,開不開心~

考慮有參數傳入的情況

首先得看收集參數,然後將參數一個一個傳遞給fn。畢竟參數的數量不清楚,所以使用arguments收集參數當然會好一點。問題就在於fn接收參數的時候是fn(context, arg0, arg1, arg2...),有什麼方法可以將參數變成這種形式呢。

**注意:**參數不是數組,不是收集起來直接放進去就可以的

api翻了一下,能滿足要求的就只有下面兩種方法:

  1. 使用eval函數
  2. 使用Function的構造函數

使用eval的方法會比較簡單,直接用字符串拼接一下參數,然後傳入eval運行就好。但是這種方法官方不推薦,存在被惡意攻擊的風險。這裏採用Function的方案實現。

Function.prototype.myCall = function(that) {
    var target = this;
    var args = arguments, argArr = [];
    
    var args = arguments, argNameArr = [], argArr = [];
    for (var i = 0; i < args.length; i++) {
        argNameArr.push('arguments[1][' + i + ']');
        argArr.push(arguments[i]);
    }
    
    argNameArr.pop(); // 去除多的arguments名字
    argArr.shift(); // 去除第一個參數 context
    
    that.fn = target; // 給that添加函數 fn
    var result = new Function('fn', 'return fn(' + argNameArr.join(',') + ')')(that.fn, argArr); // 立即調用執行方法
    delete that.fn; // 移除函數 fn
    return result;
}

考慮一下特殊情況,傳入的上下文爲空的時候,實現代碼如下:

value = 111;

var foo = {
  value: 1
}

function bar(p1, p2) {
  console.log(this.value + ',' + p1 + ',' + p2);
}

Function.prototype.myCall = function (that) {
  var target = this;
  // 如果target不是function
  if (typeof target !== 'function') {
    throw new TypeError(target + ' is not type of Function');
  }

  // 如果that爲空,將全局的this賦值給that
  if (!that) {
    that = function() {
      return this;
    }()
  }

  // 獲取參數
  var args = arguments, argNameArr = [], argArr = [];
  for (var i = 0; i < args.length; i++) {
    argNameArr.push('arguments[1][' + i + ']');
    argArr.push(arguments[i]);
  }

  argNameArr.pop(); // 去除多的arguments名字
  argArr.shift(); // 去除第一個參數 context

  that.fn = target; // 給that添加函數 fn
  var result = new Function('fn', 'return fn(' + argNameArr.join(',') + ')')(that.fn, argArr); // 立即調用執行方法
  delete that.fn; // 移除函數 fn
  return result;
}

bar.myCall(foo, 2, 3);

這樣一來,一個call的模擬實現就完成了。

四、apply的用法

apply是Function默認實現的函數,使用方式爲:fn.apply(thisArg, [argsArray])。實例代碼如下:

var foo = { value: 1 };

function fn() {
    console.log(this.value);
}

fn.apply(foo); // value => 1

五、模擬實現apply

apply和call的唯一區別在於接收的參數不同,那需要改的地方就只有對參數的處理就好了。

value = 111;

var foo = {
  value: 1
}

function bar(p1, p2) {
  console.log(this.value + ',' + p1 + ',' + p2);
}


Function.prototype.myApply = function (that) {
  var target = this;
  // 如果target不是function
  if (typeof target !== 'function') {
    throw new TypeError(target + ' is not type of Function');
  }

  // 如果that爲空,將全局的this賦值給that
  if (!that) {
    that = function () {
      return this; 
    }()
  }

  // apply第二個參數是數組
  var args = arguments.length > 1 ? arguments[1] : [], argNameArr = [];

  // 拼接參數名稱 arguments[1][0]、arguments[1][1]...
  for (var i = 0; i < args.length; i++) {
    argNameArr.push('arguments[1][' + i + ']');
  }

  // args 不是數組類型
  if (args instanceof Array) {
    that.fn = target;
    result = new Function('fn', 'return fn(' + argNameArr.join(',') + ')')(that.fn, args);
  } else {
    throw new ReferenceError('arguments 1 must be type of Array');
  }

  delete that.fn;
  return result;
}

bar.myApply(null, [2, 3]);

六、總結

想要深入的瞭解call和apply還是得實現一下,不然有很多地方總是會想不清楚。比如說函數的上下文是怎麼改變的。只是實現了這兩個函數,收穫如下:

  1. 如何獲取全局的this
  2. Function的構造函數使用
  3. arguments參數的使用
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章