ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

apply、bind、call的用法及实现原理

2021-05-03 16:36:41  阅读:167  来源: 互联网

标签:const bind ctx Bound call apply Test obj


参考资料

[1] 《JavaScript高级程序设计》

[2] js手动实现bind方法,超详细思路分析! --- 听风是风

PS:下面是我个人的总结,有些所以然都略过了,想理解得更透彻,建议看参考资料。

三者的用法及区别

const obj = { name: 'jack' }
function printMessage(age, sex) {
    console.log(`my name is ${this.name}, my age is ${age}, my sex is ${sex}.`);
}
printMessage.apply(obj,[12,'男']) // 数组传参,直接执行
printMessage.bind(obj,12,'男')() // 直接传参,返回函数
printMessage.call(obj,12,'男') // 直接传参,直接执行

下面用通过模拟apply()、bind()、call()的实现来理解它们的原理。

apply()原理

实现apply()的关键点是如何将第二个参数(装着参数的数组)拆开来放到调用函数时的括号里。解决办法就是用eval()函数来执行函数。

模拟实现:

Function.prototype.apply2 = function(ctx, argArr) {
    // 如果ctx为空,则指向window
    ctx = ctx || window
    // 给ctx加上一个临时变量fn,指向当前函数
    ctx.fn = this
    // 处理一下参数(因为函数不能传入数组作为参数列表,所以要自己拆开来)
    const args2 = []
    for (var i in argArr) {
        args2.push('argArr['+i+']')
    }
    // 执行函数,获得结果
    const result = eval('ctx.fn('+args2+')')
    // 清除临时变量
    delete ctx.fn
    // 返回结果
    return result;
}

测试代码:

const obj = { x: 1 }
function test(y, z) {
    console.log(this.x, y, z)
}
test.apply2(obj, [2, 3])

输出:

1 2 3

call()原理

原来想按a、b、c的顺序写apply()、bind()、call(),但由于call()和apply()的原理比较类似,且bind()比较复杂,因此call()放在这里了。

模拟实现:

Function.prototype.call2 = function(ctx) {
    // 如果ctx为空,则指向window
    ctx = ctx || window;
    // 给ctx加上一个临时变量fn,指向当前函数
    ctx.fn = this;
    // 处理一下参数
    const args = []
    const args2 = []
    for(let i=1; i<arguments.length; i++) {
        args.push(arguments[i])
        args2.push('args['+(i-1)+']')
    }
    // 执行获得结果
    const result = eval('ctx.fn('+args2+')')
    // 删除临时变量
    delete ctx.fn
    // 返回结果
    return result
}

测试代码:

const obj = { x: 1 }
function test(y, z) {
    console.log(this.x, y, z)
}
test.call2(obj, 2, 3)

输出结果:

1 2 3

bind()原理

先给出bind()的使用方法(有三个要点,因此实现起来比较复杂):

const obj = { x: 1 }
function Test(y, z) {
    console.log(this.x, y, z)
}
Test.prototype.w = 4

// 返回的是一个函数。
// 绑定时可以只传入一部分参数,在调用返回的函数时传入剩下的函数(这叫做函数柯里化)。
const Bound = Test.bind(obj, 2)
Bound(3)

// 用bound创建实例时,Test()内部的this指向的是Bound的实例,而不是之前绑定的对象obj。
const bound = new Bound(3)

// 用Bound创建的实例能够通过原型链访问Test.prototype。
console.log(bound.w)

模拟实现:

Function.prototype.bind2 = function(ctx) {
    // 如果ctx为空,则指向window
    ctx = ctx || window
    // 处理一下参数
    const args = []
    for(let i=1; i<arguments.length; i++) {
        args.push(arguments[i])
    }
    // 创建一个函数
    const fn = this // 因为返回的函数中this指向的是调用者,因此这里要用一个新变量暂存当前函数
    function fbound() {
        // 第一个参数用来处理当返回的函数被当做构造函数的情况
        // 第二个参数将在返回的参数中新传入的参数与调用bind2()时传入的参数合并
        fn.apply2(
            this instanceof fn ? this : ctx,
            args.concat(Array.prototype.slice.call2(arguments))
        )
    }
    // 通过fbound创建的实例,我们希望它也是当前函数fn()的实例
    // 因此我们用prototype来让fbound继承自fn
    // 但又不想修改fbound.prototype直接修改fn.prototype
    // 因此创建一个中间对象
    function FnProto () {}
    FnProto.prototype = fn.prototype
    fbound.prototype = new FnProto()
    
    return fbound
}

测试代码:

const obj = { x: 1 }
function Test(y, z) {
    console.log(this.x, y, z)
}
Test.prototype.w = 4
// 返回的是一个函数。
// 绑定时可以只传入一部分参数,在调用返回的函数时传入剩下的函数(这叫做函数柯里化)。
const Bound = Test.bind2(obj, 2)
Bound(3)

// 用bound创建实例时,Test()内部的this指向的是Bound的实例,而不是之前绑定的对象obj。
const bound = new Bound(3)

// 用Bound创建的实例能够通过原型链访问Test.prototype。
console.log(bound.w)

输出结果:

1 2 3 // Bound(3)输出
undefined 2 3 // new Bound(3)输出
4 // console.log(bound.w)输出

标签:const,bind,ctx,Bound,call,apply,Test,obj
来源: https://www.cnblogs.com/hdxg/p/14727652.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有