老生常谈? bind、call、apply 手写实现

97 阅读2分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战」。

bind、call、apply 异同点

相同点:

  • 调用者都是且必须是函数
  • 改变函数执行时的上下文,即改变this指向

区别:

call: fun.call(thisArg, param1, param2, ...)

使用一个指定的this值和单独给出的一个或多个参数来调用一个函数,返回调用结果

apply: fun.apply(thisArg, [param1,param2,...])

使用一个指定的this值和单独给出的 一个或多个参数的数组 来调用一个函数,返回调用结果

bindfun.bind(thisArg, param1, param2, ...)

改变调用功函数的this指向,返回一个新的函数,不需要调用

如何实现

call:

原本功能

function fn(a, b) {
  console.log('a ', a);
  console.log('b ', b);
  console.log('this ', this);
  
  return 'test'
}
console.log(fn('d', 'e'))
// a  d
// b  e
// this Window {…}
// test

const res = fn.call({ c: 'c' }, 'd', 'e')
console.log('res ', res)
// a  d
// b  e
// this  { c: 'c' }
// res  test

const res1 = fn.call(null, 'd', 'e')
console.log('res1 ', res1)
// a  d
// b  e
// this  Window {…}
// res1  test

实现思路 myCall(ctx, ...arg)

  • 设置this指向
  • ctx设置临时函数,将函数的this隐式绑定指向到ctx上(此处为了防止绑定的对象中本身就存在与临时函数相同的字段,可以使用Symbol规避)
  • 执行临时函数并传递单数
  • 删除临时函数
  • 返回结果
Function.prototype.myCall = function (ctx, ...args) {
  if (ctx === null || ctx === undefined) {
    ctx = globalThis
  }
  let fun = Symbol(1)
  ctx[fun] = this
  const result = ctx[fun](...args)
  delete ctx[fun]
  return result
}
const res2 = fn.myCall({ c: 'c' }, 1, 2)
console.log('res2 ', res2)
// a  1
// b  2
// this  { c: 'c', fun: [Function: fn] }
// res2  test

const res3 = fn.myCall(null, 1, 2)
console.log('res3 ', res3)
// a  1
// b  2
// this  Window {…}
// res3  test

apply

原本功能

function fn(...args) {
 console.log('arr ', ...args);
 console.log('this ', this);
 
 return 'test apply'
}
console.log(fn(['d', 'e']))
// arr  (2) ['d', 'e']
// this  Window { … }
// test apply

const res = fn.apply({ a: 11 }, [3, 4, 'e'])
console.log('res ', res)
// arr  3 4 e
// this  { a: 11 }
// res  test apply

实现思路 myApply(ctx, ...arg)

  • 实现思路跟call完全一样
  • arguments是一个类数组,es6中函数可以使用解构获取arguments,因此其实现代码除了参数,其他都一样

Function.prototype.myApply = function (ctx, args = []) {
  if (args && !(args instanceof Array)) {
    throw(`${args} 不是一个数组!`)
  }
  if (ctx === null || ctx === undefined) {
    ctx = globalThis
  }
  let fun = Symbol(1)
  ctx[fun] = this
  const result = ctx[fun](...args)
  delete ctx[fun]
  return result
}

const res2 = fn.myApply({ a: 121 }, [4,'r'])
console.log('res2 ', res2)
// arr  4 r
// this  {a: 121, fun: ƒ}
// res2  test apply

const res3 = fn.myApply(null, [4,'r'])
console.log('res3 ', res3)
// arr  4 r
// this  Window { … }
// res3  test apply

bind

原本功能

for (var i = 1; i <= 5; i++) {
   // 缓存参数
   setTimeout(function (i) {
       console.log('bind', i) // 依次输出:1 2 3 4 5
   }.bind(null, i), i * 1000);
}

实现思路 myBind(ctx, ...args)

  • 区别于callapply,bind只需要返回绑定后的函数,不需要执行
  • bind接收的参数都需要传递到绑定后的函数
Function.prototype.myBind = function (ctx, ...args1) {
 if (ctx === null || ctx === undefined) {
   ctx = globalThis
 }
 return (...args2) => {
   let fun = Symbol(1)
   ctx[fun] = this
   ctx[fun](...args1.concat(args2))
   delete ctx[fun]
 }
}

好了,这就是本次的手写分享~~