JavaScript中call、apply、bind实现原理详解

2022-04-15 0 441
目录
  • 前言
  • call
    • 用法
    • 实现
      • 简单的实现版本:
      • 升级版:
  • apply
    • 用法
      • 实现
      • bind
        • 用法
          • 基本版:
          • 升级版:
      • 总结

        前言

        众所周知 call、apply、bind 的作用都是‘改变’作用域,但是网上对这这‘改变’说得含糊其辞,并未做详细说明,‘改变’是直接替换作用域?谁替换谁?怎么产生效果?这些问题如果不理解清楚,就算看过手写实现,估计也记不长久

        所以本文介绍了call、apply、bind的用法和他们各自的实现原理。

        call

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

        即:可以改变当前函数的this指向;还会让当前函数执行。

        用法

        function fun() {
          console.log(this.name, arguments)
        }
        let obj = { name: 'clying' }
        fun.call(obj, 'deng', 'deng')
        // clying [Arguments] { '0': 'deng', '1': 'deng' }
        

        实现

        call和apply的实现,都是使用将函数放到字面量obj的某个属性中,使函数中的this指向obj这个字面量对象。

        简单的实现版本:

        Function.prototype.mycall = function (context) {
          context = (context == null || context == undefined) ? window : new Object(context)
          context.fn = this
          context.fn()
          delete context.fn
        }
        

        给函数原型添加mycall方法,创建一个上下文对象context,如果传入的对象不存在时,将指向全局window。通过给context添加fn属性,context的fn引用调用该方法的函数fun,并执行fun。执行完成之后删除该属性fn。

        当中需要先获取传入的参数,那它变成字符串数组。

        执行方法使用的是eval函数,再通过eval计算字符串,并执行其中代码,返回计算结果。

        升级版:

        给call中传入参数。

        Function.prototype.mycall = function (context) {
          context = (context == null || context == undefined) ? window : new Object(context)
          context.fn = this
          let arr = []
          for (let i = 1; i < arguments.length; i++) {
            arr.push('argument[' + i + ']') //  ["arguments[1]", "arguments[2]"]
          }
          let r = eval('context.fn(' + arr + ')') // 执行函数fun,并传入参数
          delete context.fn
          return r
        }
        

        此外,也可以通过解构的语法来实现call。

        Function.prototype.mycall = function (context, ...args) {
          context = (context == null || context == undefined) ? window : new Object(context)
          context.fn = this
          context.fn(...args)
          delete context.fn
        }
        

        如果想要能够多次调用call方法,可以将context.fn(…args)保存到变量中,最后返回即可。

        Function.prototype.mycall = function (context, ...args) {
          context = (context == null || context == undefined) ? window : new Object(context)
          context.fn = this
          let r = context.fn(...args)
          delete context.fn
          return r
        }
        

        apply

        与call方法类似,call方法接收的是一个参数列表,而apply方法接收的是一个包含多个参数的数组。

        用法

        将函数中的this指向传入的第一个参数,第二个参数为数组。

        function fun() {
          console.log(this.name, arguments);
        }
        let obj = {
          name: 'clying'
        }
        fun.apply(obj, [22, 1])
        // clying Arguments(2) [22, 1]
        

        实现

        自己实现一个apply方法myapply。实现方法与call类似,不过在接收参数时,可以使用一个args作为传入的第二个参数。直接判断如果未传入第二个参数,直接执行函数;否则使用eval执行函数。

        Function.prototype.myapply = function (context, args) {
         context = (context == null || context == undefined) ? window : new Object(context)
          context.fn = this
          if(!args) return context.fn()
          let r = eval('context.fn('+args+')')
          delete context.fn
          return r
        }
        

        bind

        bind() 方法创建一个新的函数,不自动执行,需要手动调用bind() 。这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

        用法

        将obj绑定到fun函数的this上,函数fun可以使用obj内部的属性,和传入的变量。

        function fun() {
          console.log(this.name, arguments);
        }
        let obj = {
          name: 'clying'
        }
        let b = fun.bind(obj,2)
        b(3)
        // clying Arguments(2) [2, 3]
        

        此外,bind方法绑定的函数还可以new一个实例,不过此时的this会发生改变。

        升级版-使用原型属性用法:

        function fun() {
          console.log(this.name, arguments);
        }
        let obj = {
          name: 'clying'
        }
        fun.prototype.age = 23
        let b = fun.bind(obj, 3)
        let instance = new b(4)
        console.log(instance.age);
        //undefined Arguments(2) [3, 4]
        // 23
        

        实现

        基本版:

        bind的实现可以基于call和apply的基础上实现。

        因为bind不是立即执行的,所以可以通过返回一个函数,让用户手动执行。在返回函数中利用call或者apply传入指定的this对象和参数。

        apply实现bind

        Function.prototype.mybind = function (context) {
          let that = this
          let bindargs = Array.prototype.slice.call(arguments, 1)
          return function () {
            let args = Array.prototype.slice.call(arguments)
            return that.apply(context, bindargs.concat(args))
          }
        }
        

        利用apply方法,主要是在获取处理bind传入的参数,以及用户执行函数传入的参数。利用Array原型方法的slice方法,截取所需的参数。

        在获取bind传入的参数时,需要从第二个参数开始截取,所以开始位置为1。

        call实现bind

        Function.prototype.mybind = function (context, ...args1) {
          let that = this
          return function (...args2) {
            return that.call(context, ...args1, ...args2)
          }
        }
        

        call实现直接将参数拼接call方法的后面即可。

        升级版:

        bind除了可以改变this指向、用户可以在bind后面传入参数也可以在用户执行时传入参数外。还可以让执行函数进行new操作。

        当一个绑定函数是用来构建一个值的,原来提供的 this 就会被忽略。不过提供的参数列表仍然会插入到构造函数调用时的参数列表之前。

        apply

        Function.prototype.mybind = function (context) {
          let that = this
          let bindargs = Array.prototype.slice.call(arguments, 1)
          function fBind() {
            let args = Array.prototype.slice.call(arguments)
            // 如果使用的是new,那么this会指向fBind实例,this作为当前实例传入 不是的话,使用context上下文对象
            return that.apply(this instanceof fBind ? this : context, bindargs.concat(args))
          }
          return fBind
        }
        

        在使用new操作符时,注意的是需要改变this的指向问题,如果是new,那么this指向的是实例,不使用new则指向bind当前传入的第一个参数。

        此外,还牵扯到原函数可以添加自身方法属性。如果想要能够使用fun自身的原型方法还需要使用fBind.prototype = this.prototype,实现原型共用。但是对于引用类型属性值共享,不能在不改变其他实例情况下改变(一个原型方法或属性改变,所有引用的都会发生改变)。

        Function.prototype.mybind = function (context) {
          let that = this
          let args = Array.prototype.slice.call(arguments, 1)
          function fBind() { // 执行bind函数
            let bindargs = Array.prototype.slice.call(arguments)
            return that.apply(this instanceof fBind ? this : context, args.concat(bindargs))
          }
          function Fn(){} // 两个类的原型并未公用,而是通过原型链的方式找到该原型方法
          Fn.prototype = this.prototype
          fBind.prototype = new Fn()
          return fBind
        }
        

        对于上述情况,可以使用一个函数中间件的形式,利用原型链去找到原函数原型方法或属性。

        call

        call与apply的差别只是处理参数的不同,其他均类似。

        Function.prototype.mybind = function (context, ...args1) {
          let that = this
          function fBind(...args2) {
            return that.call(this instanceof fBind ? this : context, ...args1, ...args2)
          }
          function Fn() { }
          Fn.prototype = this.prototype
          fBind.prototype = new Fn()
          return fBind
        }
        

        总结

        到此这篇关于JavaScript中call、apply、bind实现原理的文章就介绍到这了,更多相关call、apply、bind原理内容请搜索NICE源码以前的文章或继续浏览下面的相关文章希望大家以后多多支持NICE源码!

        免责声明:
        1、本网站所有发布的源码、软件和资料均为收集各大资源网站整理而来;仅限用于学习和研究目的,您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。 不得使用于非法商业用途,不得违反国家法律。否则后果自负!

        2、本站信息来自网络,版权争议与本站无关。一切关于该资源商业行为与www.niceym.com无关。
        如果您喜欢该程序,请支持正版源码、软件,购买注册,得到更好的正版服务。
        如有侵犯你版权的,请邮件与我们联系处理(邮箱:skknet@qq.com),本站将立即改正。

        NICE源码网 JavaScript JavaScript中call、apply、bind实现原理详解 https://www.niceym.com/29211.html