Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

手写 call、apply、bind #3

Open
toFrankie opened this issue Oct 29, 2021 · 0 comments
Open

手写 call、apply、bind #3

toFrankie opened this issue Oct 29, 2021 · 0 comments
Labels
2021 2021 年撰写 手写系列 与手写实现相关的文章

Comments

@toFrankie
Copy link
Owner

toFrankie commented Oct 29, 2021

这三个货,都是用来绑定 this 的。区别如下:

  • call()apply() 都返回函数执行结果,区别在于参数不一样,前者接收参数列表,后者接收一个数组参数(也可以是类数组)。
  • bind() 返回一个新函数,但注意对使用 new 关键字调用时,绑定无效。其使用方法与 call() 一致,接受一个参数列表。

一、call 实现

假设没有 Function.prototype.call() 方法,我们利用 this 默认绑定的特性处理即可。

Function.prototype.myCall() {
  // 获取执行函数
  const fn = this

  // 获取绑定对象,及参数列表
  const [context, ...args] = arguments

  // 避免与绑定对象属性冲突,采用 Symbol 值作为属性键,并将执行函数赋予该属性。
  const key = Symbol()
  context[key] = fn

  // 执行函数并返回结果
  const res = context[key](...args)

  // 移除临时属性
  delete context[key]

  // 返回结果
  return res
}

还没完,请注意以下两种情况:

  • contextundefinednull 的情况。若处于非严格模式,context 应指向顶层对象 globalThis,在浏览器为 window 对象。
  • context 为其他原始值(不是 undefinednull),应该要将其转换为对应的引用值。
Function.prototype.myCall = function () {
  const fn = this
  const [ctx, ...args] = arguments
  const context = ctx == undefined ? window : Object(ctx)

  const key = Symbol()
  context[key] = fn

  const res = context[key](...args)
  delete context[key]

  return res
}

再简化一下:

Function.prototype.myCall = function (ctx, ...args) {
  const key = Symbol()
  const context = ctx == undefined ? window : Object(ctx)

  context[key] = this
  const res = context[key](...args)
  delete context[key]

  return res
}

二、apply 实现

我们知道,Function.prototype.apply()Function.prototype.call() 的区别仅在接收参数的形式不同,前者接收一个数组。

基于上面的实现,简单修改下函数执行的参数即可。

Function.prototype.myApply = function (ctx, ...args) {
  const key = Symbol()
  const context = ctx == undefined ? window : Object(ctx)

  context[key] = this
  const res = context[key](args) // 与 call 方法的区别点
  delete context[key]

  return res
}

三、bind 实现

我们知道,Function.prototype.bind() 参数形式与 Function.prototype.call() 相同,区别在于 bind() 返回一个绑定了 this 的新函数。

其实,我们可以很快就写出以下方法:

Function.prototype.myBind = function (ctx, ...args) {
  const fn = this
  return function (...newArgs) {
    return fn.apply(ctx, [...args, ...newArgs])
  }
}

但是,这是不完全正确的...

需要注意的是,bind() 方法返回的新函数,若通过 new 关键字进行调用,那么 this 绑定则不生效。

举个例子:

const person = {
  name: 'Frankie'
}

function Foo(name) {
  this.name = name
}

const Bar = Foo.bind(person)
const bar = new Bar('Mandy')

console.log(person.name) // "Frankie"
console.log(bar.name) // "Mandy"

假设 Foo.bind(person) 生效的话,那么 new Bar('Mandy')this.name 应该是 person.name = 'Mandy',修改的应该是 person 对象的 name 属性,但事实并非如此。

顺道总结一下 this 绑定的优先级:

  1. 只要使用 new 关键字调用,无论是否含有 bind 绑定,this 总指向实例化对象。
  2. 通过 callapply 或者 bind 显式绑定,this 指向该绑定对象。若第一个参数缺省时,则根据是否为严格模式,来确定 this 指向全局对象或者 undefined
  3. 函数通过上下文对象调用,this 指向(最后)调用它的对象。
  4. 如以上均没有,则会默认绑定。严格模式下,this 指向 undefined,否则指向全局对象。

因此,我们来完善一下 myBind() 方法:

Function.prototype.myBind = function (ctx, ...args) {
  const fn = this
  return function newFn(...newArgs) {
    // 若通过 new 关键字调用,有几种方式可以判断:
    // 1. this instanceof newFn
    // 2. this.__proto__.constructor === newFn
    // 3. new.target 不为 undefined
    if (new.target) {
      return new newFn(...args, ...newArgs)
    }

    return fn.apply(ctx, [...args, ...newArgs])
  }
}

去掉注释,就长这样:

Function.prototype.myBind = function (ctx, ...args) {
  const fn = this
  return function newFn(...newArgs) {
    if (new.target) {
      return new newFn(...args, ...newArgs)
    }

    return fn.apply(ctx, [...args, ...newArgs])
  }
}

总的来说,其实并不能,真正了解 this 原理,要手写这几个常考的面试题,其实很简单哈。

插个话,我认为对于初学者来说,千万不要把作用域(链)和 this 混为一谈,其实它们完全就是两回事。作用域(链)与闭包相关,它在函数被定义时就已“确定”,不会再变了。而 this 则与函数调用相关。

References

这里将此前写过关于 this 的文章列举出来:

@toFrankie toFrankie added the 手写系列 与手写实现相关的文章 label Mar 26, 2022
@toFrankie toFrankie added the 2021 2021 年撰写 label Apr 26, 2023
@toFrankie toFrankie changed the title 手写系列之 call、apply、bind 的实现 手写 call、apply、bind Apr 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2021 2021 年撰写 手写系列 与手写实现相关的文章
Projects
None yet
Development

No branches or pull requests

1 participant