zoukankan      html  css  js  c++  java
  • 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
      }
    
  • 相关阅读:
    自定义jquery插件
    jquery中的编程范式,即jquery的牛逼之处
    $.ajax 完整参数
    URL参数获取/转码
    hello world
    此博客已不更新,作者的个人域名LIZHONGC.COM已经启用。
    岁月记录
    下雪往事
    《x86汇编语言:从实模式到保护模式》检测点和习题答案
    《穿越计算机的迷雾》第二版再版说明
  • 原文地址:https://www.cnblogs.com/yangrenmu/p/10633526.html
Copyright © 2011-2022 走看看