call、apply 和 bind 能做什么?如何使用?之间的区别?模拟一个?

解析:这里我给大家画了一张思维导图:
在这里插入图片描述

结合这张图来说明,会清楚得多:

call、apply 和 bind,都是用来改变函数的 this 指向的。

call、apply 和 bind 之间的区别比较大,前者在改变 this 指向的同时,也会把目标函数给执行掉;后者则只负责改造 this,不作任何执行操作。

call 和 apply 之间的区别,则体现在对入参的要求上。前者只需要将目标函数的入参逐个传入即可,后者则希望入参以数组形式被传入。

进阶编码题:模拟实现一个 call/apply/bind 方法

在实现 call 方法之前,我们先来看一个 call 的调用示范:

var me = {
  name: 'xiuyan'
}

function showName() {
  console.log(this.name)
}

showName.call(me) // xiuyan

结合 call 表现出的特性,我们首先至少能想到以下两点:

  • call 是可以被所有的函数继承的,所以 call 方法应该被定义在 Function.prototype 上

  • call 方法做了两件事:

  1. 改变 this 的指向,将 this 绑到第一个入参指定的的对象上去;
  2. 根据输入的参数,执行函数。

结合这两点,我们一步一步来实现 call 方法。首先,改变 this 的指向

showName 在 call 方法调用后,表现得就像是 me 这个对象的一个方法一样。

所以我们最直接的一个联想是,如果能把 showName 直接塞进 me 对象里就好了,像这样:

var me = {
  name: 'xiuyan',
  showName: function() {
    console.log(this.name)
  }
}

me.showName()

但是这样做有一个问题,因为在 call 方法里,me 是一个入参:

showName.call(me) // xiuyan

用户在传入 me 这个对象的时候, 想做的仅仅是让 call 把 showName 里的 this 给改掉,而不想给 me 对象新增一个 showName 方法。所以说我们在执行完 me.showName 之后,还要记得把它给删掉。遵循这个思路,我们来模拟一下 call 方法(注意看注释):

Function.prototype.myCall = function(context) {
    // step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
    context.func = this
    // step2: 执行函数
    context.func()
    // step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
    delete context.func
}

有兴趣的同学,可以测试一下我们的 myCall:

showName.myCall(me) // xiuyan

在我们这个例子里,myCall 的执行结果结果与 call 无差,撒花~~

到这里,我们已经实现了 “改变 this 的指向” 这个功能点。现在我们的 myCall 还需要具备读取函数入参的能力,类比于 call 的这种调用形式:

var me = {
  name: 'Chris'
}

function showFullName(surName) {
  console.log(`${this.name} ${surName}`)
}

showFullName.call(me, 'Lee') // Chris Lee

读取函数入参,具体来说其实是读取 call 方法的第二个到最后一个入参。要做到这一点,我们可以借助数组的扩展符

// '...'这个扩展运算符可以帮助我们把一系列的入参变为数组
function readArr(...args) {
    console.log(args)
}

readArr(1,2,3) // [1,2,3]

我们把这个逻辑用到我们的 myCall 方法里:

Function.prototype.myCall = function(context, ...args) {
    ...
    console.log('入参是', args)
}

就能通过 args 这个数组拿到我们想要的入参了。把 args 数组代表的目标入参重新展开,传入目标方法里,就大功告成了:

Function.prototype.myCall = function(context, ...args) {
    // step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
    context.func = this
    // step2: 执行函数,利用扩展运算符将数组展开
    context.func(...args)
    // step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
    delete context.func
}

现在我们来测试一下功能完备的 myCall 方法:

Function.prototype.myCall = function(context, ...args) {
    // step1: 把函数挂到目标对象上(这里的 this 就是我们要改造的的那个函数)
    context.func = this
    // step2: 执行函数,利用扩展运算符将数组展开
    context.func(...args)
    // step3: 删除 step1 中挂到目标对象上的函数,把目标对象”完璧归赵”
    delete context.func
}

var me = {
  name: 'Chris'
}

function showFullName(surName) {
  console.log(`${this.name} ${surName}`)
}

showFullName.myCall(me, 'Lee') // Chris Lee

结果与 call 方法无差!

以上,我们就成功模拟了一个 call 方法出来。

基于这个最基本的 call 思路,大家还可以为这个方法作能力扩充:

比如如果我们第一个参数传了 null 怎么办?是不是可以默认给它指到 window 去?函数如果是有返回值的话怎么办?是不是新开一个 result 变量存储一下这个值,最后 return 出来就可以了?等等等等 —— 这些都是小事儿。当面试官问你 “如何模拟 call 方法的实现的时候”,他最想听的其实就楼上这两个核心功能点的实现思路,其它的,都是锦上添花~

基于对 call 方法的理解,写出一个 apply 方法(更改读取参数的形式) 和 bind 方法(延迟目标函数执行的时机)不是什么难事,只需要大家在上面这段代码的基础上作改造即可。(前提是你对 apply 方法和 bind 方法的特性和用法要心知肚明~)。

Function.prototype.myCall = function (context, ...args) {
    context.func = this;
    context.func(...args);
    delete context.func;
}
Function.prototype.myApply = function (context, arr) {
    context.func = this;
    context.func(...arr);
    delete context.func;
}
Function.prototype.myBind = function (context, ...args) {
    let self = this;
    return function () {
        self.call(context, ...args)
    }
}

Function.prototype.myApply(context, arr){
    var context = context | window
    context.func = this
    var result
    if (!arr) {
        result = context.func()
    } else {
        context.func(arr)
    }
    delete context.func
    return result
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值