一文手撕call、apply、bind

前言

在面试中,this的指向是经常被考到的,然而call、apply、bind可以改变this的指向,所以有时候面试中可能会被要求我们自己手写call、apply、bind方法。今天我就来试着手撕这一道考题。

call、apply、bind用法

ini 复制代码
fn.call(thisArg,arg1,arg2,arg3,...)
fn.apply(thisArg,[arg1,agr2,...])
newFn=fn.bind(thisArg,arg1,arg2,arg3,...)

call、apply、bind对比

不同点:

  1. 三者参数不相同,call,bind接收一个参数列表,而apply接收一个数组参数
  2. call,apply会立即获得执行结果,而bind会返回一个已经指定this指向和参数的新函数

相同点:

三者都可以改变this的指向,而且都位于function的原型上,所有函数都可以通过原型链找到并调用。

实现myCall、myApply、myBind

myCall

首先我们先把自制myCall要挂载到func的原型上,可以让所有的函数调用。

javascript 复制代码
Function.prototype.myCall=function(thisArg,...args){
    console.log(this)
    console.log(thisArg)
}
fn.myCall(obj,arg1,arg2)

在myCall中的this指向fn函数,thisArg指向目标对象obj。我们想要让fn运行时候fn里面的this指向目标对象thisArg,即让fn成为obj这个对象的方法来运行。

我们可以通过给thisArg增加一个方法(f),把this(fn)传给它,然后运行这个方法(f),再把参数传进去,最终返回这个方法(f)让外界。

javascript 复制代码
Function.prototype.myCall=function(thisArg,...args){
    thisArg.f=this
    let res=thisArg.fn(...args)
    return res
}

此时call的方法就基本上完成了,但是还是存在着点问题。

  • 如果有多个函数同时调用这个方法(myCall),并且目标对象相同,那么目标对象的f属性可能会被覆盖。
  • 目标对象上会永远存在着f属性。

那么该如何解决这两个问题呢?我们可以使用Symbol来创建独一无二的值确保对象的唯一属性名,然后再返回res之前用delete删除该属性就解决了这两个问题。

javascript 复制代码
Function.prototype.myCall=function(thisArg,...args){
    const prop=Symbol()
    thisArg[prop]=this
    let res=thisArg[prop](...args)
    delete thisArg[prop]
    return res
}

最后我们再规范一下传入的参数,如果当传入的对象(thisArg)是undefined或者null,我们就要自动替换成全局对象。
最终版myCall

javascript 复制代码
Function.prototype.myCall=function(thisArg,..args){
    thisArg=thisArg||window
    const prop=Symbol()
    thisArg[prop]=this
    let res=thisArg[prop](...args)
    delete thisArg[prop]
    return res
}

myApply

由于call和apply的差别不大,仅传入参数有差异,所有我们可以沿用myCall的思路。

ini 复制代码
Function.prototype.myApply=function(thisArg,args){
    thisArg=thisArg||window
    args=args||[]
    const prop=Symbol()
    thisArg[prop]=this
    let res=thisArg[prop](...args)
    delete thisArg[prop]
    return res
}

myBind

myBind函数的实现是比较复杂的,因为bind函数它可以创建一个新的绑定this指向的函数并返回,而且这个返回的函数还可以继续传参,也还可以用new创建实例,要考虑的方面很多,那么我们来逐步实现myBind。

我们已经实现了myCall和myApply,我们可以选择一个来绑定this,再给它封装一层函返回,这样就实现了给个简易版的myBind。

javascript 复制代码
Function.prototype.myBind=function(thisArg,...args){
    const self=this
    return function(){
        return self.apply(thisArg,args)
    }
}

这个简易版的myBind有几处地方需要注意。

首先,我们apply的形参是数组形式,所以我们传入的是args而非...args。在return前,为什么需要self来保存this呢?是因为我们要用闭包把this(fn)保存起来,让myBind方法返回的函数在运行时的this值可以正确地指向fn。

javascript 复制代码
//如果不定义self
Function.prototype.myBind=function(thisArg,...args){
    return function(){
        return this.apply(thisArg,args)
    }
}
const f=fn.myBind(obj)
const f=function(){
    return this.apply(thisArg,args)
}
f()
//当我们调用f函数时候,会发现this的指向并非我们所期待的fn

由于bind返回的函数是可以传递参数的,在上个版本myBind中还未解决绑定返回的函数可以传参问题。

javascript 复制代码
Function.prototype.myBind=function(thisArg,...args){
    const self=this
    return function(...innerArgs){
        const finalArgs=[...args,...innerArgs]
        return self.apply(thisArg,finalArgs)
    }
}

由于bind创建的新的绑定指向的函数还可以通过new来创建实例,所以我们要考虑如何让我们的myBind可以实现new。

javascript 复制代码
//在我们的myBind函数里面
...
function(...innerArgs){
    const finalArgs=[...args,...innerArgs]
    return fn.apply(thisArg,finnalArg)
}
//用new来创建f的实例
const newFn=new f()

在new的过程中会执行构造函数的代码,因此此处绑定函数f中的代码会执行。而fn.apply(thisArg,finalArgs)里的thisArg会仍然有效,但是这就不符合bind的方法了。

我们面临着如何解决new创建绑定函数的实例时,要让先前传入的thisArg失效的问题。

对于绑定函数f来说,执行时的this的不确定的。如果我们直接执行f,那么绑定函数的this应该执行全局对象window。如果我们用new来创建f的实例,那么f中的this应该指向新创建的实例。

基于上述情况,我们决定修改myBind返回的绑定函数,在函数内部对this进行判断,从而区分是否使用了new运算符。

最终版myBind

javascript 复制代码
Function.prototype.myBind=function(thisArg,...args){
    const self=this
    const bound=function(...innerArgs){
        const finalArgs=[...args,...innerArgs]
        const isNew=this instanceof bound
        if(isNew){
            return new self(...finalArgs)
        }
        return self.apply(thisArgs,finalArgs)
    }
    return bound
}

Ending

以上内容就是手撕call、apply、bind三个绑定this函数的实现了。

相关推荐
zhanshuo3 分钟前
不依赖框架,如何用 JS 实现一个完整的前端路由系统
前端·javascript·html
火柴盒zhang4 分钟前
websheet在线电子表格(spreadsheet)在集团型企业财务报表中的应用
前端·html·报表·合并·spreadsheet·websheet·集团财务
讨厌吃蛋黄酥5 分钟前
智能前端新纪元:语音交互技术与安全实践全解析
javascript
khalil6 分钟前
基于 Vue3实现一款简历生成工具
前端·vue.js
拾光拾趣录13 分钟前
浏览器对队头阻塞问题的深度优化策略
前端·浏览器
用户81221993672213 分钟前
[已完结]后端开发必备高阶技能--自研企业级网关组件(Netty+Nacos+Disruptor)
前端
万少18 分钟前
2025中了 聊一聊程序员为什么都要做自己的产品
前端·harmonyos
1234Wu33 分钟前
React Native 接入 eCharts
javascript·react native·react.js
程序员爱钓鱼1 小时前
Go 语言泛型 — 泛型语法与示例
后端·面试·go
abigale032 小时前
webpack+vite前端构建工具 -11实战中的配置技巧
前端·webpack·node.js