js手写call、bind、apply

目录

callapplybind有两种实现方式,第一种是隐式绑定,第二种是通过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
}

接下来我们需要将thisthis绑定到我们指定的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来判断一下,如果传入的thatnull或者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

applycall类似,只有在传入参数上不一样,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返回一个函数,这个函数不能再通过callapply来更改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))
相关推荐
SilentSamsara25 分钟前
Python 环境搭建完整指南:从下载安装到运行第一个程序
开发语言·python
小短腿的代码世界38 分钟前
Qt文件系统与IO深度解析:从QFile到异步文件操作
开发语言·qt
harder3212 小时前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
jinanwuhuaguo2 小时前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社2 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
豹哥学前端3 小时前
用猜数字游戏,一口气掌握 JavaScript 核心知识点(附完整代码)
前端·javascript
忆往wu前4 小时前
从0到1一步步拆解搭建,梳理一个 Vue3 简易图书后台全开发流程
前端·javascript·vue.js
淘矿人4 小时前
从0到1:用Claude启动你的第一个项目
开发语言·人工智能·git·python·github·php·pygame
cany10004 小时前
C++ -- 模板的声明和定义
开发语言·c++
澈2074 小时前
深耕进阶 Day1:C 与 C++ 核心差异 + C++ 入门基石
c语言·开发语言·c++