【面試題】手寫call、apply、bind

前端踩坑选手發表於2024-03-14

區別

相同點:

  • 都可以改變函式的this指向
  • 第一個引數都是this要指向的物件,如果沒有這個引數或引數為undefined或null,則預設指向全域性window
  • 都可以利用後續引數傳參

不同點:

  • call可以傳入多個引數;apply需要傳入一個引數物件(陣列或類陣列);bind可以分開傳參,例如:const foo = fn.bind(this, 1, 2); foo(3, 4)
  • call、apply會立即執行,bind不會立即執行,而是返回一個修改this後的函式

實現call

Function.prototype._call = function (thisArg, ...args) {
  // this指向呼叫_call的函式
  const fn = this
  // 如果要繫結的物件為undefined或null,則預設指向全域性物件
  thisArg = thisArg !== undefined && thisArg !== null ? Object(thisArg) : window
  // 利用this的隱式繫結規則,將函式的this繫結到指定物件上
  thisArg.fn = fn
  const res = thisArg.fn(...args)
  // 刪除新增的fn方法
  delete thisArg.fn

  return res
}

測試:

function foo(a, b) {
  console.log(this.name, a, b)
}

const obj = { name: 'xiaoming' }

foo.call(obj, 20, 30) // xiaoming 20 30
foo._call(obj, 20, 30) // xiaoming 20 30

實現apply

apply和call差別不大,只是傳入函式引數的形式不同

完整版:

Function.prototype._apply = function (thisArg, arrArg = []) {
    const fn = this
    thisArg = (thisArg !== undefined && thisArg != null) ? Object(thisArg) : window

    thisArg.fn = fn
    const res = thisArg.fn(...arrArg)
    delete thisArg.fn
    return res
}

簡化版:

Function.prototype._apply = function (thisArg, arrArg = []) {
  const fn = this;

  return fn._call(thisArg, ...arrArg);
}

測試:

function foo(a, b) {
    console.log(this.name, a, b)
}

const obj = { name: 'xiaohong' }

foo.apply(obj, [10, 20]) // xiaohong 10 20
foo._apply(obj, [10, 20]) // xiaohong 10 20

實現bind

完整版:

Function.prototype._bind = function (thisArg, ...args) {
    const fn = this
    thisArg = (thisArg !== null && thisArg !== undefined) ? Object(thisArg) : window
    
    // 返回一個新函式作為繫結函式
    return function (...otherArgs) {
        thisArg.fn = fn
        const res = thisArg.fn(...args, ...otherArgs)
        delete thisArg.fn
        return res
    }
}

簡化版:

Function.prototype._bind = function (thisArg, ...args) {
  const fn = this;
  
  return function (...otherArgs) {
    return fn._apply(thisArg, args.concat(otherArgs));
  };
};

測試:

function foo(a, b) {
    console.log(this.name, a, b)
}

const obj = { name: 'xiaoli' }

const res = foo.bind(obj, 30)
const res1 = foo._bind(obj, 30)

res(40) // xiaoli 30 40
res1(40) // xiaoli 30 40

相關文章