【JS】apply、call和bind原理

262 阅读1分钟

原理

要使this绑定到目标对象,我们可以利用this的隐式绑定这一特性,想办法让目标对象成为函数调用时的上下文对象。我们以Array.prototype.forEach为例,来对如何改变this绑定进行说明。

// forEach调用时的上下文对象为arr
// 所以this会绑定到arr
const arr = ["h", "e", "l", "l", "o"];
arr.forEach();

// 我们将Array的forEach方法
// 给到String.prototype上
String.prototype.forEach = Array.prototype.forEach
// 这样一来,String也有自己的forEach方法了
const str = String("hello"),
// 当str调用forEach时,上下文对象为str
// 所以this绑定成功绑定到了str
str.forEach()

这亦是函数callapplybind的实现原理,接下来我们如上代码进行一定的封装。

Function.prototype.call

// Function.prototype.myCall
// @params { Any } obj: this绑定到的目标对象
// @params { Any } ...args: 参数
Function.prototype.myCall = function (obj, ...args) {
  // 如果无上下文对象,则应用默认绑定
  obj = obj || window;
  // 如果obj为基本类型,先将其包装成对象
  obj = Object(obj);
  // 避免func与obj原有属性发生命名冲突
  const func = Symbol();
  // 将this,即function给到obj上
  obj[func] = this;
  // 调用funcion,此时的上下文对象是obj,this也绑定在obj上
  const result = obj[func](...args);
  // 用完记得删除[func]这一属性
  delete obj[func];
  // 返回结果
  return result;
};
// 测试
const str = "hello";
Array.prototype.forEach.myCall(str, function (val) {
  console.log(val);
});

Function.prototype.apply

// Function.prototype.myApply
// @params { Any } obj: this绑定到的目标对象
// @params { Array } args: 参数
Function.prototype.myApply = function (obj, args) {
  obj = obj || window;
  obj = Object(obj);
  const func = Symbol();
  obj[func] = this;
  const result = obj[func](...args);
  delete obj[func];
  return result;
};
// 测试
const str = "hello";
Array.prototype.forEach.myApply(str, [
  function (val) {
    console.log(val);
  },
]);

Function.prototype.bind

// Function.prototype.myBind
// @params { Any } obj: this绑定到的目标对象
// @params { Any } ...args: 参数
Function.prototype.myBind = function (obj, ...args) {
  obj = obj || window;
  obj = Object(obj);
  const func = Symbol();
  obj[func] = this;
  // 支持函数柯里化
  return function (...newArgs) {
    obj[func](...args, ...newArgs);
  };
};
// 测试
const str = "hello";
const strForEach = Array.prototype.forEach.myBind(str);
strForEach(function (val) {
  console.log(val);
});