1. 程式人生 > >JS call方法原理

JS call方法原理

原文轉自:https://blog.csdn.net/u010377383/article/details/80646415

前言
本來計劃是, 先把深入React技術棧過完, 
但是,現在在滿足RN app開發情況下,我還是先深入js一個系列。

(一)call原始碼解析
首先上一個call使用

function add(c, d) {
  return this.a + this.b + c + d;
}

const obj = { a: 1, b: 2 };

console.error(add.call(obj, 3, 4)); // 10

大統上的說法就是,call改變了this的指向。然後,介紹this xxx什麼一大堆名詞,反正不管你懂不懂,成功繞暈你就已經ok了,但是實際發生的過程,可以看成下面的樣子。

const o = {
  a: 1,
  b: 2,
  add: function(c, d) {
    return this.a + this.b + c + d
  }
};

給o物件新增一個add屬性,這個時候 this 就指向了 o,
o.add(5,7)得到的結果和add.call(o, 5, 6)相同。
但是給物件o添加了一個額外的add屬性,這個屬性我們是不需要的,所以可以使用delete刪除它。
so, 基本為以下三部。

// 1. 將函式設為物件的屬性
 o.fn = bar
// 2. 執行該函式
 o.fn()
// 3. 刪除該函式
 delete o.fn

所以我們基於ES3 實現call

Function.prototype.es3Call = function (context) {
  var content = context || window;
  content.fn = this;
  var args = [];
  // arguments是類陣列物件,遍歷之前需要儲存長度,過濾出第一個傳參
  for (var i = 1, len = arguments.length ; i < len; i++) {
    // 避免object之類傳入
    args.push('arguments[' + i + ']');
  }
  var result = eval('content.fn('+args+')');
  delete content.fn;
  return result;
}
console.error(add.es3Call(obj, 3, 4)); // 10
console.log(add.es3Call({ a: 3, b: 9 }, 3, 4)); // 19
console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4

基於ES6 實現call, 其實差別不大,es6新增… rest,這就可以放棄eval的寫法,如下

// ES6 call 實現
Function.prototype.es6Call = function (context) {
  var context = context || window;
  context.fn = this;
  var args = [];
  for (var i = 1, len = arguments.length; i < len; i++) {
    args.push('arguments[' + i + ']');
  }
  const result = context.fn(...args);
  return result;
}

console.error(add.es6Call(obj, 3, 4));
console.log(add.es3Call({ a: 3, b: 9 }, {xx: 1}, 4)); // 12[object Object]4

(二)apply原始碼解析
apply和call區別在於apply第二個引數是Array,而call是將一個個傳入

// 基於es3實現

Function.prototype.es3Apply = function (context, arr) {
  var context = context || window;
  context.fn = this;
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    if (!(arr instanceof Array)) throw new Error('params must be array');
    result = eval('context.fn('+arr+')');
  }
  delete context.fn;
  return result
}

console.log(add.apply(obj, [1, 2])); // 6

// 基於es6實現


Function.prototype.es6Apply = function (context, arr) {
  var context = context || window;
  context.fn = this;
  var result;
  if (!arr) {
    result = context.fn();
  } else {
    if (!(arr instanceof Array)) throw new Error('params must be array');
    result = context.fn(...arr);
  }
  delete context.fn;
  return result
}

console.error(add.es6Apply(obj, [1, 2])); // 6

(三)bind原始碼解析
bind() 方法會建立一個新函式。 
當這個新函式被呼叫時,bind() 的第一個引數將作為它執行時的 this, 
之後的一序列引數將會在傳遞的實參前傳入作為它的引數。 
先看一個使用bind方法的例項

function foo(c, d) {
  this.b = 100
  console.log(this.a)
  console.log(this.b)
  console.log(c)
  console.log(d)
}
// 我們將foo bind到{a: 1}
var func = foo.bind({a: 1}, '1st'); 
func('2nd'); // 1 100 1st 2nd
// 即使再次call也不能改變this。
func.call({a: 2}, '3rd'); // 1 100 1st 3rd


// 當 bind 返回的函式作為建構函式的時候,
// bind 時指定的 this 值會失效,但傳入的引數依然生效。
// 所以使用func為建構函式時,this不會指向{a: 1}物件,this.a的值為undefined。如下

// new func('4th'); //undefined 100 1st 4th

首先使用ES3實現

Function.prototype.es3Bind = function (context) {
  if (typeof this !== "function") throw new TypeError('what is trying to be bound is not callback');
  var self = this;
  var args = Array.prototype.slice.call(arguments, 1);
  const fBound = function () {
    // 獲取函式的引數
    var bindArgs = Array.prototype.slice.call(arguments);
    // 返回函式的執行結果
    // 判斷函式是作為建構函式還是普通函式
    // 建構函式this instanceof fNOP返回true,將繫結函式的this指向該例項,可以讓例項獲得來自繫結函式的值。
    // 當作為普通函式時,this 指向 window,此時結果為 false,將繫結函式的 this 指向 context
    return self.apply(this instanceof fNOP ? this: context, args.concat(bindArgs));
  }
  // 建立空函式
  var fNOP = function () {};
  // fNOP函式的prototype為繫結函式的prototype
  fNOP.prototype = this.prototype;
  // 返回函式的prototype等於fNOP函式的例項實現繼承
  fBound.prototype = new fNOP();
  // 以上三句相當於Object.create(this.prototype)
  return fBound;
}

var func = foo.es3Bind({a: 1}, '1st');
func('2nd');  // 1 100 1st 2nd
func.call({a: 2}, '3rd'); // 1 100 1st 3rd
new func('4th');  //undefined 100 1st 4th

es6實現

Function.prototype.es6Bind = function(context, ...rest) {
  if (typeof this !== 'function') throw new TypeError('invalid invoked!');
  var self = this;
  return function F(...args) {
    if (this instanceof F) {
      return new self(...rest, ...args)
    }
    return self.apply(context, rest.concat(args))
  }
}

var func = foo.es3Bind({a: 1}, '1st');
func('2nd');  // 1 100 1st 2nd
func.call({a: 2}, '3rd'); // 1 100 1st 3rd
new func('4th');  //undefined 100 1st 4th
--------------------- 
作者:justin-1 
來源:CSDN 
原文:https://blog.csdn.net/u010377383/article/details/80646415 
版權宣告:本文為博主原創文章,轉載請附上博文連結!