JS手写call,apply,bind

啥是call,apply,bind?

call, apply, bind 都用于改变函数的this指向

call 、apply 、 bind 这三个函数的第一个参数都是 this 的指向对象

第二个参数:

call 表示: 你把参数都按顺序写进来我一个一个读取并执行

xxx.call(obj, 参数一,参数二,参数三,…) // 会立即执行

apply 表示: 你把参数写成数组传给我,我帮你遍历并执行

xxx.apply(obj, [参数一,参数二,参数三…] ) //会立即执行

bind 表示: 我接收参数跟call一样,但是我可不直接执行,需要执行你得自己操作

xxx.bind(obj, 参数一,参数二,参数三…) //会生成一个新的函数

但凡是学JS的,肯定对这三兄弟不陌生了,这里直接上栗子🌰

const p1 = {
  name: 'cxk',
  duration: '两年半',    // 定义方法不能用箭头函数,箭头函数没有this所以在创建时继承自上级作用域,这里是全局--window)
  say: function(person,str){      // 而普通函数的this执行时确定
    console.log(`${person}${str},我是练习时长${this.duration}的个人练习生:${this.name}`)
  }
}
const p2={
  name: 'ctrl',
  duration: '5年',
}
p1.say('全民制作人','大家好')                    // "全民制作人们大家好,我是练习时长两年半的个人练习生:cxk"
p1.say.call(p2,'全民制作人call','大家好')        //  "全民制作人call们大家好,我是练习时长5年的个人练习生:ctrl"
p1.say.apply(p2,['全面制作人apply','大家好'])    // "全面制作人apply们大家好,我是练习时长5年的个人练习生:ctrl"
const bd = p1.say.bind(p2,'全民制作人bind','大家好')
bd()                                           // "全民制作人bind们大家好,我是练习时长5年的个人练习生:ctrl"

先拿 call 来试试,其他都类似

如何着手?

在执行 p1.say.call的时候

请问 call 方法内部的this指向谁?

答: call方法由p1.say调用,所以call方法内部的this就指向p1.say这个目标函数
其实这个点我也没有理解很透彻,在call方法执行中,

//给Function的原型对象添加一个myCall的方法 这样所有普通函数都能用myCall改变this
Function.prototype.myCall = function(target,...args){   //传入执行的目标和需要的参数
  target._say=this      	//把这个函数保存给target的_say方法
  // 原型对象里面的方法以及构造函数调用时 this指向实例对象(这里p1.say就是Function.prototype的实例对象) 这个this就是p1.say这个函数  
  target._say(...args)		//用target执行_say  函数内部的this就指向target了
}

p1.say.myCall(p2,'myCall','大家不好')  // "myCall们大家不好,我是练习时长5年的个人练习生:ctrl"

思考:

以上代码有哪些缺陷?

1.每次调用myCall都会给target对象绑定一次方法,方法留在了target对象内.

2.如果恰好对象本身就有_say方法,我们却把它覆盖掉了,真很不好!

优化:

  1. 使用Symbol类型来保证该方法独一无二,不会覆盖target对象原有的某些方法
  2. 每次调用完成后删除掉挂载的方法
Function.prototype.myCall = function(target,...args){ 
    const say = Symbol()		//每次都是独一无二的
  	target[say]=this     		// Object[xx] 等同于 Object.xx
  	const res = target[say](...args)  // 等同于 target.say(...args)
    delete target[say]			// 删除对象上的该方法
    return res					// 这里一开始没注意到,需要把执行结果返回出去
}

p1.say.myCall(p2,'myCall','大家不好')  // "myCall们大家不好,我是练习时长5年的个人练习生:ctrl"

😎大功告…成?😯

现在来写 apply 原理类似

// apply	注意第二个参数需要是数组
Function.prototype.myApply = function(target,argsArray){
  const say = Symbol()
  target[say]=this
  const res = target[say](...argsArray)
  delete target[say]
  return res
}
p1.say.myApply(p2,['myApply','大家好'])  //"myApply们大家好,我是练习时长5年的个人练习生:ctrl"

最后是bind,这个就有点麻烦了,先看看有哪些注意点:

  1. 返回一个函数,即不会立即执行
  2. 如果调用者是一个构造函数,那么返回参数也要是构造函数
  3. 返回的构造函数需要实现原型继承
  4. 使用新返回的构造函数new一个实例时,需要继承构造函数原型对象的属性和方法
  5. bind调用的时候可以传参,调用之后返回的函数也可以传参

简单版本

Function.prototype.myBind = function(target,...args){
  let that = this
  return function(){
      let newArgs = [...args,...arguments]   // 拼接参数 也可以用concat
      return that.apply(target,newArgs)
  }
}
const bd = p1.say.myBind(p2)
bd('myBind','大家好')  //"myBind们大家好,我是练习时长5年的个人练习生:ctrl"

如果调用者是构造函数呢?

构造函数中 this 指向 new 创建的实例。所以可以通过在函数内判断 this 是否为当前函数的实例进而判断当前函数是否作为构造函数。

Function.prototype.myBind = function(target,...args){
  let that = this
  
  // 构造函数不能是匿名函数
  let Fn = function(){
      let newArgs = [...args,...arguments]   // 拼接参数 也可以用concat
      // 判断是否作为构造函数
      //通过在函数内判断 this 是否为当前函数的实例进而判断当前函数是否作为构造函数。
      target = this instanceof Fn ? this : target 
      return that.apply(target,newArgs)
  }
  // 返回的构造函数要继承调用myBind的构造函数的原型对象的属性和方法
  // Object.create() 这个方法把 调用此方法的构造函数的原型作为返回的构造函数的原型,实现继承
  Fn.prototype = Object.create( this.prototype )
  return Fn
}

const bd = p1.say.myBind(p2)
bd('myBind','大家好')  //"myBind们大家好,我是练习时长5年的个人练习生:ctrl"

Object.create() 方法用于创建一个新对象,使用现有的对象来作为新创建对象的原型(prototype)

终于结束了😫

其实细节上还有很多不足,比如判断调用者是否为函数…因为只有函数能用Function.prototype上面的call,apply,bind方法

这里面还涉及到很多关于 原型对象,原型继承,构造函数的重要知识,从多方面理解JavaScript较低层的原理,再回头思考这些问题,自然是迎刃而解。

这里推荐一本书

在这里插入图片描述

《JavaScript 编程精解(第三版)》

这本书搭配红宝书**《JavaScript高级程序设计》**一起看,并且认真去分析书中的示例,相信你会对JS这门语言理解更加到位。另外《JS编程精解》中还有很多有趣的案例(自动寻路机器人–、游戏制作–canvas相关、乌鸦互联网-Promise和http相关)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寂林Lin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值