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

先实现个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
  }
posted @ 2019-03-31 22:03  yangrenmu  阅读(436)  评论(0编辑  收藏  举报