this绑定规则和call、apply、bind实现

216 阅读2分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

this的四种绑定规则

function(){}在运行时(被调用的时候)会基于绑定规则动态进行 this 绑定。

有两点强调:

  • this 是在运行时绑定的,不是定义时。
  • 要看 this 的指向,关键在于看函数调用的位置和方式。

根据函数调用位置和方式不同,有四种绑定策略。

默认绑定

如果直接调用函数,则将 this 绑定到全局对象,在 Node 中是 global,在浏览器中是 window。

function foo() {
  console.log(this)
}

// 运行时绑定
foo()

隐式绑定

如果有上下文对象调用函数,则将 this 绑定到上下文对象。

const obj = {
  foo: function() {
    console.log(this)
  },
}

// 运行时绑定
obj.foo()

显式绑定

通过调用函数的 call,apply,bind 方法进行绑定。

function foo() {
  console.log(this)
}
const obj = { a: 1 }

// 运行时绑定
foo.call(obj)
foo.apply(obj)
foo.bind(obj)()

new绑定

new 的过程也叫函数的“构造调用”,经历了以下几个步骤:

  1. 创建一个空对象 obj
  2. 将对象 obj 的 __proto__ 关联到构造函数的 prototype
  3. 将对象绑定到构造函数的 this,执行构造函数
  4. 如果构造函数没有返回对象,则返回对象 obj
function Foo() {
  console.log(this)
}

// 运行时绑定
foo = new Foo()

call和apply实现

显示绑定中的call和apply可以指定this对象进行函数执行,下面来模拟实现一下。

call和apply的区别在于传参,call传递可变参数,apply传递参数数组,其它都一样。所以我们重点实现call,apply就调用call就行了。

fn.call(obj, 'zhangsan', 18)
fn.apply(obj, ['zhangsan', 18])

apply其实传递的是类数组对象,比如fn.apply(obj, { 0: 'zhangsan', 1: 18, length: 2 })这种也行。

思路很简单,我们看上面四种绑定中,只有隐式绑定能派上用场,所以显示绑定就要依赖隐式绑定来进行。

Function.prototype.myCall = function (thisArg, ...argArray) {
  const context = thisArg || window; // 防御代码
  context.fn = this; 
  const result = context.fn(...argArray); // 核心就是用隐式绑定的方式调用函数
  delete context.fn;
  return result;
};

Function.prototype.myApply = function (thisArg, argArray) {
  argArray = argArray || [];
  if (!Array.isArray(argArray)) throw 'argArray is not Array';
  return this.myCall(thisArg, ...argArray); // apply直接调用call就行了
};

bind实现

bind会将函数和指定this绑定,然后返回一个新的函数。既然返回一个新的函数,那自然就和闭包联系在一起,bind的核心就是返回一个闭包,通过闭包引用自由变量的方式替换this。

Function.prototype.myBind = function (thisArg, ...argArray) {
  const fn = this;
  return function () { // 核心就是返回一个闭包
    return fn.myCall(thisArg, ...argArray); // 将thisArg和原函数fn作为自由变量进行引用
  };
};