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

JavaScript中call、apply、bind的简单实现 #6

Open
yangrenmu opened this issue Mar 31, 2019 · 0 comments
Open

JavaScript中call、apply、bind的简单实现 #6

yangrenmu opened this issue Mar 31, 2019 · 0 comments

Comments

@yangrenmu
Copy link
Owner

yangrenmu commented Mar 31, 2019

先实现个call

call:可以指定函数运行时的this。与apply之间的区别是传参方式不同,call的参数是若干参数列表,apply接受的是一个包含多个参数的数组。
首先,我们先实现第一个功能,指定函数运行时的this:

Function.prototype.fakeCall = function(obj) {
    // 在传入的 obj 上创建一个属性,将该属性指向调用的函数
    obj.fn = this
    // 然后执行 fn,则会将调用的函数的 this 指向 obj
    obj.fn()
    // 最后将创建的 fn 属性删除
    delete obj.fn
}

尝试下效果:

Function.prototype.fakeCall = function(obj) {
    obj.fn = this
    obj.fn()
    delete obj.fn
}
let foo = {
    value: 1
}
function bar(name, age) {
    console.log(this) // {value: 1, fn: ƒ}
    console.log(this.value) // 1
}
bar.fakeCall(foo)

跟预想的一样,已将barthis强行改变成了foo
原生的call方法还可以接受参数,现在实现这个功能。很简单,没错,就是用es6去实现es3,当然用eval也可以。

Function.prototype.fakeCall = function(obj) {
    // 取出除 obj 参数外剩下的参数
    let args = [].slice.call(arguments, 1)
    obj.fn = this
    // 传入参数
    obj.fn(...args)
    delete obj.fn
}

看下效果如何:

Function.prototype.fakeCall = function(obj) {
    let args = [].slice.call(arguments, 1)
    obj.fn = this
    obj.fn(...args)
    delete obj.fn
}
let foo = {
    value: 1
}
function bar(name, age) {
    console.log(name) // xuedinge
    console.log(age) // 20
}
bar.fakeCall(foo, "xuedinge", "20")

可以看出,基本上已经实现了原生的call了。现在考虑一些特殊情况。
1、当调用函数有返回值时,会怎么样。(会是undefined
2、当调用函数传入的this参数是null或者是其他基本数据类型时,会发生什么。(会报错)
根据上面个特殊情况,我们对fakeCall稍作调整。

Function.prototype.fakeCall = function(obj) {
    // 处理传入的值是基本数据类型的情况,特别是 null
    obj = typeof obj !== "object" ? window : obj || window
    let args = [].slice.call(arguments, 1)
    obj.fn = this
    // 将调用函数的返回值保存下来,然后用 return 返回
    let result = obj.fn(...args)
    delete obj.fn
    return result
}

看下效果如何。

Function.prototype.fakeCall = function(obj) {
    obj = typeof obj !== "object" ? window : obj || window
    let args = [].slice.call(arguments, 1)
    obj.fn = this
    let result = obj.fn(...args)
    delete obj.fn
    return result
    }
let foo = {
    value: 1
}
function bar(name, age) {
    console.log(name) // xuedinge
    console.log(age) // 20
    return {
        color: "yuanliangse"
    }
}
console.log(bar.fakeCall(undefined, "xuedinge", "20")) // {color: "yuanliangse"}

apply的实现

aplly:跟call的实现基本相同,区别是对除this外,剩余的参数处理方式不同。直接上代码。

Function.prototype.fakeApply = function(obj) {
    obj = typeof obj !== "object" ? window : obj || window
    let args = [].slice.call(arguments, 1)
    obj.fn = this
    // args的第一个值就是传入的数组
    let result = obj.fn(...args[0])
    delete obj.fn
    return result
}

bind的实现

bind:bind方法返回一个新的函数,在调用时,设置this为提供的值。新函数在调用时,将给定的参数列表作为原函数的参数序列的前若干项。
也就是说,bind具有以下功能:

  • 返回一个新函数
  • 可以为新函数指定this的值
  • bind方法可以传参,返回的新函数也可以传参

根据这些特性,我们可以实现一个简单的bind方法先。

  Function.prototype.fakeBind = function(context) {
    const self = this
    // bind 方法传的参数
    const bindArgs = [].slice.call(arguments, 1)
    return function() {
      // bind 方法返回的函数传入的参数
      const newArgs = [].slice.call(arguments)
      return self.apply(context, bindArgs.concat(newArgs))
    }
  }

看下效果如何,从下面的结果看,还是可以的。

  const foo = {
    value: 1
  }

  function bar(name, age) {
    console.log(this.value)
    console.log(name)
    console.log(age)
  }

  const bindTar = bar.fakeBind(foo, "daisy")
  bindTar("19") // 1、daisy、19

但是,现在有个问题,就是bind方法返回的新函数当构造函数使用时,bind方法提供的this要失效的,this要指向new构造出来的实例。关于这一点,简单来说就是新的函数this指向的问题,那么我们在给新的函数绑定this时,判断下是不是当构造函数使用就可以了。

  Function.prototype.fakeBind = function(context) {
    const self = this
    // bind 方法传的参数
    const bindArgs = [].slice.call(arguments, 1)
    const fBound = function() {
      // bind 方法返回的函数传入的参数
      const newArgs = [].slice.call(arguments)
      return self.apply(
        // 当作构造函数使用时,this 指向实例,this instanceof fBound 为 true
        this instanceof fBound ? this : context,
        bindArgs.concat(newArgs)
      )
    }
    // 将实例的原型指向绑定函数的原型
    fBound.prototype = this.prototype
    return fBound
  }

这里需要注意一点,我们修改fBound的原型时,也会修改绑定函数的原型,所以,我们使用一个空函数中转一下绑定函数的原型。最终版的代码如下:

  Function.prototype.fakeBind = function(context) {
    const self = this
    // bind 方法传的参数
    const bindArgs = [].slice.call(arguments, 1)
    const fn = function() {}
    const fBound = function() {
      // bind 方法返回的函数传入的参数
      const newArgs = [].slice.call(arguments)
      return self.apply(
        this instanceof fn ? this : context,
        bindArgs.concat(newArgs)
      )
    }
    fn.prototype = this.prototype
    fBound.prototype = new fn()
    return fBound
  }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant