Function

# Function

测试用例

function person(a, b, c, d) {
  console.log(this.name);
  console.log(a, b, c, d);
}

var a = {
  name: "lamb",
};

person.call(a, "1", "2", "3", "4");

# call

# 原理分析

以测试用例来说明,person.call(a)做了这么几件事:

  1. 给 a 添加 person 方法;

  2. 让 a 调用 person 方法;

相当于:

var a = {
  name: "lamb",
  person: function () {
    console.log(this.name);
  },
};

a.person();

# 注意事项

  1. call 第一个参数为空;

  2. 其余参数如何传入

  3. 函数如果有返回值,就不能只是执行流程,要保存执行结果

# 老版本实现

Function.prototype.newCall = function (obj) {
  //解决第一个参数为空的问题
  var obj = obj || window;

  // 这个this是函数person
  // console.log(this);

  // 给传进的形参添加一个属性,就是该函数
  obj.p = this;

  // 利用arguments参数将参数保存起来,并不要第一个this参数
  var newArguments = [];
  for (var i = 1; i < arguments.length; i++) {
    // 这里加引号是因为字符串与数组进行相加时,数组会调用toString方法
    newArguments.push("arguments[" + i + "]");
  }

  // 由该形参对象调用该函数,this指向就变为了obj对象
  // 并要将保存的参数传入由obj调用的方法
  // 考虑兼容性,先不使用es6的rest参数
  // 需要写成obj.p(arguments[1], arguments[2], arguments[3], arguments[4])
  // 但是参数个数不固定 -> 字符串拼接,然后利用eval()函数
  // 并且要考虑到函数有返回的问题
  var result = eval("obj.p(" + newAarguments + ")");
  // 用完之后再给人家删掉
  delete obj.p;
  return result;
};

person.newCall(a);

# 新版本实现

Function.prototype.lamb_call = function (obj, ...args) {
  obj = obj || window;

  const fn = Symbol();
  obj[fn] = this;

  const res = obj[fn](...args);
  delete obj[fn];

  return res;
};

# apply

# 原理分析

除了传入参数的形式与 call 不一致,其他思路基本一致

# 老版本实现

Function.prototype.newApply = function (obj, arr) {
  // 分别解决第一个参数为空,以及arr不穿的问题
  var obj = obj || window,
    result;
  obj.p = this;
  if (!arr) {
    result = obj.p();
  } else {
    var newArguments = [];
    for (var i = 0; i < arr.length; i++) {
      newArguments.push("arr[" + i + "]");
    }
    result = eval("obj.p(" + newArguments + ")");
  }
  delete obj.p;
  return result;
};

# 新版本实现

Function.prototype.lamb_apply = function (obj, args) {
  obj = obj || window;

  const fn = Symbol();
  obj[fn] = this;

  const res = obj[fn](...args);
  delete obj[fn];

  return res;
};

# bind

# 注意事项

  1. 因为返回的是一个函数,和容易造成 this 丢失,所以先保存下来

  2. 返还函数内部也就是执行 call || apply

  3. arguments 参数是一个对象

  4. bind 具有柯里化特性

  5. bind 可以配合 new 使用 (本例没有考虑,因为 lamb 已经晕了)

# 实现

Function.prototype.newBind = function (obj) {
  var that = this,
    // 将数组的slice方法以call的形式给arguments对象,并去除第一项(即this)
    arr = Array.prototype.slice.call(arguments, 1);
  return function () {
    // 返回的函数也有一个arguments对象,即bind函数的柯里化
    var arr2 = Array.prototype.slice.call(arguments),
      // 将参数合并为一个数组
      arrSum = arr.concat(arr2);
    that.apply(obj, arrSum);
  };
};

var b = person.newBind(a, "1")("2", "3", "4");