• 使用JS简单实现一下apply、call和bind方法


    使用JS简单实现一下apply、call和bind方法

    1.方法介绍

    apply、call和bind都是系统提供给我们的内置方法,每个函数都可以使用这三种方法,是因为apply、call和bind都实现在了Function的原型上(Function.prototype),而他们的作用都是给我们函数调用时显式绑定上this。下面先介绍一下它们的基本用法:

    • apply方法:调用一个具有给定this值的函数,以及以一个数组(或类数组对象)的形式提供的参数。

      • 使用语法:func.apply(thisArg, [argsArray])

        • thisArg:在func函数调用时绑定的this值;
        • [argsArray]:一个数组或者类数组对象,其中的数组元素将作为单独的参数传给func函数;
      • 使用效果:

        function foo(x, y ,z) {
          console.log(this, x, y, z)
        }
        
        const obj = { name: 'curry', age: 30 }
        /**
         * 1.将obj对象绑定给foo函数的this
         * 2.数组中的1 2 3分别传递给foo函数对应的三个参数
         */
        foo.apply(obj, [1, 2, 3])
        

    • call方法:使用一个指定的 this值和单独给出的一个或多个参数来调用一个函数。

      • 使用语法:func.call(thisArg, arg1, arg2, ...)

        • thisArg:在func函数调用时绑定的this值;
        • arg1, arg2, ...:指定的参数列表,将作为参数传递给func函数;
      • 使用效果:

        function foo(x, y ,z) {
          console.log(this, x, y, z)
        }
        
        const obj = { name: 'curry', age: 30 }
        /**
         * 1.将obj对象绑定给foo函数的this
         * 2.call剩余参数中的a b c分别传递给foo函数对应的三个参数
         */
        foo.call(obj, 'a', 'b', 'c')
        

    • bind方法创建一个新的函数,在bind()被调用时,这个新函数的this被指定为bind()的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

      • 使用语法:func.bind(thisArg[, arg1[, arg2[, ...]]])

        • thisArg:调用func函数时作为this参数传递给目标函数的值;
        • arg1, arg2, ...:当目标函数被调用时,被预置入func函数的参数列表中的参数;
      • 使用效果:

        function foo(...args) {
          console.log(this, ...args)
        }
        
        const obj = { name: 'curry', age: 30 }
        /**
         * 1.将obj对象绑定给foo函数的this
         * 2.bind剩余参数中的1 2 3分别传递给foo函数中参数
         * 3.也可在newFoo调用时传入参数,这时bind传递的参数会与newFoo调用时传递的参数进行合并
         */
        const newFoo = foo.bind(obj, 1, 2, 3)
        newFoo()
        newFoo('a', 'b', 'c')
        

    总结:

    • apply和call主要用于在函数调用时给函数的this绑定对应的值,两者作用类似,主要区别就是除了第一个参数,apply方法接受的是一个参数数组,而call方法接受的是参数列表。
    • bind也是给函数指定this所绑定的值,不同于apply和call的是,它会返回一个新的函数,新函数中的this指向就是我们所指定的值,且分别传入的参数会进行合并。

    2.apply、call和bind方法的实现

    为了所有定义的函数能够使用我们自定义的apply、call和bind方法,所以需要将自己实现的方法挂在Function的原型上,这样所有的函数就可以通过原型链找到自定义的这三个方法了。

    2.1.apply的实现

    Function.prototype.myApply = function(thisArg, argArray) {
      // 1.获取当前需要被执行的函数
      // 因为myApply是需要被当前函数进行调用的,根据this的隐式绑定,此处的this就是指向当前需要被执行的函数
      const fn = this
    
      // 2.对传入的thisArg进行边界判断
      if (thisArg === null || thisArg === undefined) {
        // 当传入的是null或者undefined是,被执行函数的this直接指向全局window
        thisArg = window
      } else {
        // 将传入的thisArg对象化,方便后面在thisArg添加属性
        thisArg = Object(thisArg)
      }
      // 也可简单写成三元运算符:
      // thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)
    
      // 3.将获取的fn添加到thisArg对象上
      // 这里使用Symbol的原因是避免外部传入的thisArg中的属性与添加fn有冲突
      const fnSymbol = Symbol()
      Object.defineProperty(thisArg, fnSymbol, {
        enumerable: false,
        configurable: true,
        writable: false,
        value: fn
      })
      // 也可简单写成
      // thisArg[fnSymbol] = fn
    
      // 4.对argArray进行判断
      // 看是否有传入值,没有值传入就默认 []
      argArray = argArray || []
    
      // 5.调用获取的fn函数,并将对应传入的数组展开传递过去
      const result = thisArg[fnSymbol](...argArray)
      // 调用完后删除添加的属性
      delete thisArg[fnSymbol]
    
      // 6.将结果返回
      return result
    }
    

    测试:虽然打印出来的对象中还存在Symbol属性,实际上已经通过delete删除了,这里是对象引用的问题。

    function foo(x, y, z) {
      console.log(this, x, y, z)
    }
    
    foo.myApply({name: 'curry'}, [1, 2, 3])
    

    2.2.call的实现

    call方法的实现和apply方法的实现差不多,主要在于后面参数的处理。

    Function.prototype.myCall = function(thisArg, ...args) {
      // 1.获取当前需要被执行的函数
      const fn = this
    
      // 2.对传入的thisArg进行边界判断
      thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)
    
      // 3.将获取的fn添加到thisArg对象上
      const fnSymbol = Symbol()
      thisArg[fnSymbol] = fn
    
      // 4.调用获取的fn函数,并将对应传入的args传递过去
      const result = thisArg[fnSymbol](...args)
      // 调用完后删除添加的属性
      delete thisArg[fnSymbol]
    
      // 5.将结果返回
      return result
    }
    

    测试:

    function foo(x, y, z) {
      console.log(this, x, y, z)
    }
    
    foo.myCall({name: 'curry'}, 1, 2, 3)
    

    2.3.bind的实现

    bind方法的实现稍微复杂一点,需要考虑到参数合并的问题。

    Function.prototype.myBind = function(thisArg, ...argsArray) {
      // 1.获取当前的目标函数,也就是当前使用myBind方法的函数
      const fn = this
    
      // 2.对传入的thisArg进行边界判断
      thisArg = (thisArg === null || thisArg === undefined) ? window : Object(thisArg)
    
      // 3.将获取的fn添加到thisArg对象上
      const fnSymbol = Symbol()
      thisArg[fnSymbol] = fn
    
      // 4.定义一个新的函数
      function newFn(...args) {
        // 4.1.合并myBind和newFn传入的参数
        const allArgs = [...argsArray, ...args]
        // 4.2.调用真正需要被调用的函数,并将合并后的参数传递过去
        const result = thisArg[fnSymbol](...allArgs)
        // 4.3.调用完后删除添加的属性
        delete thisArg[fnSymbol]
    
        // 4.4.将结果返回
        return result
      }
    
      // 6.将新函数返回
      return newFn
    }
    

    测试:

    function foo(x, y, z) {
      console.log(this, x, y, z)
    }
    
    const newFoo = foo.myBind({ name: 'curry' }, 1, 2)
    newFoo(3)
    


    __EOF__

    本文作者MomentYY
    本文链接https://www.cnblogs.com/MomentYY/p/15913787.html
    关于博主:评论和私信会在第一时间回复。或者直接私信我。
    版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
    声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
  • 相关阅读:
    云计算与大数据第15章 分布式大数据处理平台Hadoop习题带答案
    第十一章·组合模式
    【postgresql 基础入门】从了解数据库访问权限,访问数据库,到认识数据库的所有者及属性,从此打开了数据库使用的大门
    [ C++ ] STL _ Vector使用及其模拟实现
    Linux网络编程3-select模型
    Java -基础知识之类的初始化顺序
    【猫狗分类】Pytorch VGG16 实现猫狗分类3-生成器+数据增强
    全卷积神经网络图像去噪研究-含Matlab代码
    Java生成二维码工具
    select在socket中的server多路复用
  • 原文地址:https://www.cnblogs.com/MomentYY/p/15913787.html