javascript 手写call、apply 和 bind

定义函数和对象如下:

let obj = {
  'a': 'a in obj'
}
window.a = 'a in window'

function fn (msg) {
  console.log(this.a, msg)
}

fn('raw')

手写 call

原生的call 方法有两个特征:

  • 改变this的指向
  • 通过多个参数的形式传递参数

那我们手写myCall方法模拟call,如何实现这两个特征呢?

  • 我们知道函数中this指向调用这个函数的对象,那我们可以将需要改变this的函数,暂时挂载到一个对象上,在通过对象.方法的形式调用这个函数,就可以改变函数执行时this指向了
  • 传递参数,可以巧妙地使用剩余运算符 和 展开运算符…`,不然的话,就需要使用arguments和eval等,还要拼接字符串,挺麻烦的
    直接上代码吧:
Function.prototype.myCall = function (...args) {
  // 通过剩余运算符拿到 myCall的全部参数,第一个参数是新的this指向,赋值给t
  const t = args.shift()
  // t 添加一个临时的属性 _fn 为this,这里的this就是fn.myCall(obj) 的 fn
  t._fn = this
  // 通过t 调用这个 函数,使用展开运算符... 向函数传递参数
  t._fn(...args)
  // 注意要删除掉这个临时属性
  delete t	._fn
}

调用 call 和 myCall

fn.call(obj, 'call') // a in obj call
fn.myCall(obj, 'myCall') // a in obj myCall

发现自己写的myCall方法没毛病
需要注意的是,obj中不能有 _fn属性,所以还需要判断obj中是不是有这个属性,要生成一个obj中没有的属代替’_fn’。这里我就忽略了

手写apply

前面手写了call,apply和call其实是几乎一样的,只是apply以数组的形式传递参数,也就是apply方法的第二个参数应该是个数组(或类数组)

Function.prototype.myApply = function (...args) {
  if (! (args[1] instanceof Object)) {
    throw new TypeError('CreateListFromArrayLike called on non-object')
  }
  const t = args[0]
  t._fn = this
  t._fn(...Array.from(args[1]))
  delete t._fn
}

测试一下:

fn.apply(obj, {0: 'apply', length: 1}) // a in obj apply
fn.myApply(obj, {0: 'myApply', length: 1}) // a in obj myApply
fn.apply(obj, ['apply']) // a in obj apply
fn.myApply(obj, ['myApply']) // a in obj myApply

手写bind

bind方法和前面的不一样,它不会立即执行函数,而是返回一个新函数

  • 返回一个新函数,这个新函数不管谁调用它,this都指向设定的那个对象
  • 实现柯里化,bind的第2个以后的参数为向函数传递的参数,新函数调用时传递的参数与bind方法中传递的参数拼接在一起
    实现代码如下:
Function.prototype.myBind = function (...args) {
  // 用剩余操作符... 获取myBind的参数,第一个参数时新指定的this
  const t = args.shift()
  // myBind应该返回一个函数
  return (...newArgs) => {
    // 返回的函数内部应该调用原来的函数,这里的this由于时箭头函数,在函数定义的地方找上级作用域,也就是myBind 内部的this,就是 xxx.myBind 的xxx那个函数
    // myBind的参数args 和 新生成的函数调用时候传递的参数应该concat到一起
    this.apply(t, args.concat(newArgs))
    // 如果这里使用刚刚自己手写的myApply,那就完全摆脱了对原生方法的依赖了哦
    // this.myApply(t, args.concat(newArgs))
  }
}

测试一下:

let fn1 = fn.bind(obj)
fn1('bind') // a in obj bind

let fn2 = fn.myBind(obj)
fn2('myBind') // a in obj myBind

let obj1 = {
  a: 'hhh',
  fn1: fn1,
  fn2: fn2
}
obj1.fn1('bind fn called by obj1') // a in obj bind fn called by obj1
obj1.fn2('myBind fn called by obj1') // a in obj myBind fn called by obj1
// 这里可以看到即使obj1.fn2() 也不会改变this指向为obj1 因为在myBind中(myApply中)函数是直接被t调用的,this的指向永远是离自己最近一层的对象--t

测试通过

总结:

  • 手写apply和call:通过为对象添加临时方法,并使用对象.方法调用,实现this的改变;apply是数组传参,call是多个参数传递,通过…运算符获取和传递参数
  • 手写bind,返回一个新函数,新函数内部通过apply或call改变this指向并调用原函数;通过myBind的参数和新函数的参数concat到一起,新函数内部调用原函数时传递所有的参数,从而实现柯里化
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值