js手写call、bind、apply

目录

call和apply和bind有两种实现方式,第一种是隐式绑定,第二种是通过new

无论是通过隐式绑定实现还是通过new实现,核心都是针对this的绑定规则

具体关于this的绑定规则可以看我这一篇博客
this绑定规则

call与apply

我们先回想一下call的使用方法

javascript 复制代码
function foo() {
    return this
}
console.log(foo.call({ name: 18 }))

接下来让我们一步步实现

首先call是一个实例方法,所以我们的myCall也需要设置在Function.prototype上

javascript 复制代码
Function.prototype.myCall = function () {
}

call会传入多个参数,第一个是被绑定的this,剩下的是函数调用的参数列表

javascript 复制代码
Function.prototype.myCall = function (that, ...args) {
}

接下来我们就需要在myCall中调用函数,我们可以通过this()的形式直接调用函数,因为foo.myCall本身就是一种隐式绑定

因为函数可能会有返回结果,所以我们需要将返回结果给返回出去

javascript 复制代码
Function.prototype.myCall = function (that, ...args) {
    const value = this(...args)
    return value
}

接下来我们需要将this的this绑定到我们指定的that身上,我们可以在that上新增一个属性,用这个属性来存放函数

然而我们并不能确定that身上拥有哪些属性,没有哪些属性,如果我们的函数存放到了原有的属性上则可能会发生错误,这表明了我们无法确定存放函数的属性名

幸运的是ES6中推出了新的基本类型Symbol,使用它我们可以很轻松的得到唯一的属性名

具体关于Symbol可以看我这篇文章
Symbol

javascript 复制代码
Function.prototype.myCall = function (that, ...args) {
    const fn = Symbol("fn")
    that[fn] = this
    const value = that[fn](...args)
    return value
}

我们并不希望用户能在函数中访问到我们的fn,在fn运行完之后我们也需要将fn删除,所以我们需要使用属性描述符来配置fn

具体属性描述符可以看我这篇文章
属性描述符

javascript 复制代码
Function.prototype.myCall = function (that, ...args) {
    const fn = Symbol("fn")
    Object.defineProperty(that, fn, {
        value: this,
        enumerable: false,
        configurable: true
    })
    const value = that[fn](...args)
    delete that[fn]
    return value
}

最后我们还需要针对that来判断一下,如果传入的that是null或者undefined,我们需要将函数绑定到window,如果传入的不是一个对象,我们就需要将它装箱

javascript 复制代码
Function.prototype.myCall = function (that, ...args) {
    that = (typeof that === "null" || typeof that === "undefined") ? window : Object(that)
    const fn = Symbol("fn")
    Object.defineProperty(that, fn, {
        value: this,
        enumerable: false,
        configurable: true
    })
    const value = that[fn](...args)
    delete that[fn]
    return value
}

最后我们来测试一下

javascript 复制代码
function foo() {
    return this
}
console.log(foo.myCall({ name: 18 }))

apply

apply和call类似,只有在传入参数上不一样,call是将函数参数一个一个传入,而apply是将参数合为一个数组传入

javascript 复制代码
Function.prototype.myApply = function (that, args) {
    that = (typeof that === "null" || typeof that === "undefined") ? window : Object(that)
    const fn = Symbol("fn")
    Object.defineProperty(that, fn, {
        value: this,
        enumerable: false,
        configurable: true
    })
    const value = that[fn](...args)
    delete that[fn]
    return value
}

bind

bind和以上两个很类似,区别在于bind返回一个函数,这个函数不能再通过call和apply来更改this指向

javascript 复制代码
Function.prototype.myBind = function (that, ...args) {
    that = (typeof that === "null" || typeof that === "undefined") ? window : Object(that)
    const fn = Symbol("fn")
    return (...args2) => {
        const allArgs = args.concat(args2)
        Object.defineProperty(that, fn, {
            value: this,
            enumerable: false,
            configurable: false
        })
        const value = that[fn](...allArgs)
        return value
    }
}

需要注意的是,我们返回的函数不能删除fn,如果删除了fn的话运行会报错,因为找不到这个属性

如果在返回的函数上进行call或者apply也不会更改新函数的this,因为无论是call还是apply都是在原先函数上套了一个新函数,由新函数隐式调用原函数,但经过bind绑定过后的函数也被一个新函数包裹,经历了两次调用,所以原函数的this指向不会丢失

测试结果

javascript 复制代码
function foo() {
    return this
}
const newFoo = foo.myBind({ name: 18 })
console.log(newFoo())
console.log(newFoo.myCall(window))
console.log(newFoo.apply(window))
相关推荐
Evand J18 分钟前
【MATLAB例程】三维快速扩展随机树(RRT)路径规划与到达角(AOA)、到达时间差(TDOA)融合定位,附下载链接
开发语言·matlab·代码·定位·例程
执明wa20 分钟前
Android RecyclerView 实战:点击频道栏切换对应内容
android·开发语言·windows·microsoft·android studio
开开心心就好22 分钟前
图片白底怎么去掉?抠图工具抠完背景透明
java·服务器·开发语言·pdf·ocr·散列表·启发式算法
朝朝辞暮i24 分钟前
C++ 第 22 课:const —— 不允许随便修改的数据
开发语言·c++·算法
web打印社区29 分钟前
JS 静默打印怎么做:纯前端为什么不行,以及最小可跑通写法
开发语言·前端·javascript·websocket·网络协议·http·pdf
大圣编蚕31 分钟前
C语言怎么写小游戏?从零开始做一个贪吃蛇
c语言·开发语言
web打印社区35 分钟前
HTML5 静默打印:前端页面怎么调用打印机且尽量不弹窗
前端·javascript·vue.js·pdf·html·html5
Chen—LSN1 小时前
数据结构——拿捏复杂度
java·c语言·开发语言·数据结构·经验分享·笔记·算法
阿钱真强道1 小时前
25 嵌入式操作系统 | 设备面板:用 Qt 控件把“设备“画出来(本节不接串口)
开发语言·qt·flask·架构设计·协议契约
ym hyd 1111 小时前
宝宝早教服务系统源码 Java+SpringBoot+Vue3 前后分离
java·开发语言·spring boot