JS实现call、apply和bind函数


JS 实现 call、apply 和 bind 函数

思路

  • 如果不传第一个参数,那么上下文对象默认是 window
  • 改变了 this 指向,让新的对象可以执行该函数,并能接受参数

实现 call

Function.prototype.myCall = function (context) {
  // this就是调用bind的函数
  if (typeof this !== "function") {
    throw new Error("this必须是函数");
  }
  context = context || window;
  context.fn = this;
  // 除去第一个参数
  const args = [...arguments].slice(1);
  const result = context.fn(...args);
  // 调用函数并删除绑定对象上的函数
  delete context.fn;
  return result;
};

// ES6 实现
Function.prototype.myCall = function (context, ...args) {
  context = (context ?? window) || new Object(context);
  const key = Symbol();
  context[key] = this;
  const result = context[key](...args);
  delete context[key];
  return result;
};

function bar() {
  console.log("bar");
}
function foo(params) {
  console.log("---foo---", params);
  console.log("---this---", this);
}

foo.myCall(bar, "testMyCall");
// ---foo--- testMyCall
// ---this--- function bar() {
// console.log("bar");
// }

实现 apply

apply 和 call 的实现方式大部分相同,不同的地方是 apply 需要对传入的参数进行判断

Function.prototype.myApply = function (context) {
  if (typeof this !== "function") {
    throw new Error("this必须是一个函数");
  }
  context = context || window;
  context.fn = this;
  const args = arguments[1];
  // 要判断传入参数的参数,如果只传入了一个参数,那么args应该是undefined
  const result = args ? context.fn(...args) : context.fn();
  delete context.fn;
  return result;
};

// ES6实现
Function.prototype.myApply = function (context) {
  context = (context ?? window) || new Object(context);
  const key = Symbol();
  const args = arguments[1];
  context[key] = this;
  const result = args ? context[key](...args) : context[key]();
  delete context[key];
  return result;
};

function bar() {
  console.log("bar");
}
function foo(params) {
  console.log("---foo---", params);
  console.log("---this---", this);
}

foo.myApply(bar, ["testMyApply"]);
// ---foo--- testMyApply
// ---this--- function bar() {
// console.log("bar");
// }

实现 bind

  • bind 和 apply、call 的区别在于,bind 不是立即执行的,而是返回一个函数,这个函数是还可以传参的,且可以用做构造函数使用。因此在实现时需要对此时的情况加以判断;
  • bind 函数的传参方式与 call 函数相同;
  • 过程:
    1. 使用 call / apply 指定 this
    2. 返回一个绑定函数
    3. 当返回的绑定函数作为构造函数被 new 调用,绑定的上下文指向实例对象
    4. 设置绑定函数的 prototype 为原函数的 prototype
Function.prototype.myBind = function (context) {
  if (typeof this !== "function") {
    throw new TypeError("this必须是一个函数");
  }
  const _this = this;
  // 这里的args是bind时传入的参数
  const args = [...arguments].slice(1);
  // 返回一个函数
  const bindFn = function () {
    // 因为返回了一个函数,我们可以 new F(),所以需要判断
    // 此时这里的arguments是bind之后的函数再传入的值
    if (this instanceof bindFn) {
      // 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
      return new _this(...args, ...arguments);
    }
    // 当作为普通函数时,this 指向 window,此时结果为 false,将绑定函数的 this 指向 context
    return _this.apply(context, args.concat(...arguments));
  };
  bindFn.prototype = Object.create(_this.prototype);
  return bindFn;
};

function bar() {
  console.log("bar");
}
function foo(name, age) {
  console.log("---name---", name);
  console.log("---age---", age);
  console.log("---this---", this);
}

const bindFoo = foo.myBind(bar, "testMyBind");
// 此时的this应该是指向bar
bindFoo();
console.log("============");
// 此时的bind之后的函数做构造函数使用,其this应该指向实例对象,也是就是foo函数
const bindFoo2 = new bindFoo(24);
// ---name--- testMyBind
// ---age--- undefined
// ---this--- function bar() {
//   console.log("bar");
// }
// ============
// ---name--- testMyBind
// ---age--- 24
// ---this--- foo {}

// ES6实现
Function.prototype.myBind = function (context, ...args) {
  const fn = this;
  const bindFn = function (...newFnArgs) {
    return fn.call(
      this instanceof bindFn ? this : context,
      ...args,
      ...newFnArgs
    );
  };
  bindFn.prototype = Object.create(fn.prototype);
  return bindFn;
};

参考


文章作者: CassielLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 CassielLee !
评论
  目录