手把手教你彻底掌握手写 JS bind、call、apply,面试官100%满意!

318 阅读2分钟

一、call、apply、bind 究竟搞啥的?

  • call:改变函数 this 指向,并立即执行。参数按顺序传递。
  • apply:同样改变 this 指向,但参数必须是数组。
  • bind:改变 this 指向,但返回新函数,并能预绑定部分参数。需要手动调用才会执行。

二、手写 call:面试官最爱考的基本功

实现原理

  1. 把要调用的函数临时挂到 context(目标对象)上,避免 this 被污染。
  2. 执行这个临时函数。
  3. 删除这个临时属性,避免污染 context 对象。
  4. 返回执行结果。
Function.prototype.myCall = function (context, ...args) {
  context = context || globalThis; // 兼容非对象 / 全局
  const fnSymbol = Symbol('fn'); // 避免重名
  context[fnSymbol] = this; // 绑定this到context
  const result = context[fnSymbol](...args); // 调用函数
  delete context[fnSymbol]; // 删除临时属性
  return result; // 返回结果
};

// 用法展示
function greet(greeting, punctuation) {
  console.log(greeting, this.name, punctuation);
}
greet.myCall({ name: 'Alice' }, 'Hello', '!'); // Hello Alice !

面试加分点:用 Symbol 防止属性冲突,体现细心!


三、手写 apply:思路如 call,参数变换而已

区别就是参数不是一串,而是一个数组!

Function.prototype.myApply = function(context, args) {
  context = context || globalThis;
  const fnSymbol = Symbol('fn');
  context[fnSymbol] = this;
  let result;
  if (Array.isArray(args)) {
    result = context[fnSymbol](...args);
  } else {
    result = context[fnSymbol]();
  }
  delete context[fnSymbol];
  return result;
};

// 用法展示
function show(name, age) {
  console.log('My name is', name, 'and I am', age);
}
show.myApply(null, ['Jack', 22]); // My name is Jack and I am 22

四、手写 bind:面试场的王炸题,考你闭包思维+继承意识!

实现难度升级:“返回一个新函数,并可以预先绑定参数”。还要兼容构造函数(使用 new 时)!

普通版(不考虑 new)

Function.prototype.myBind = function(context, ...bindArgs) {
  const self = this;
  return function(...callArgs) {
    return self.apply(context, [...bindArgs, ...callArgs]);
  }
};

// 用法
function sum(a, b, c) {
  console.log(this.x, a, b, c);
}
const boundSum = sum.myBind({x: 100}, 7, 8);
boundSum(9); // 100 7 8 9

升级版(完美兼容 new)

  1. 如果用 new,this 还得指向新对象,而不是 context。
  2. 保证原型链正确。
Function.prototype.myBind = function(context, ...bindArgs) {
  const self = this;
  function bound(...callArgs) {
    // 用于 new 关键字时,this 指向新对象
    return self.apply(this instanceof bound ? this : context, [...bindArgs, ...callArgs]);
  }
  bound.prototype = Object.create(self.prototype); // 继承原型
  return bound;
}

// 用法
function Person(name, age) {
  this.name = name;
  this.age = age;
}
const BoundPerson = Person.myBind(null, 'Tom');
const p = new BoundPerson(20);
console.log(p.name, p.age); // Tom 20

关键考点

  • this instanceof bound 判断是否用 new 调用
  • 原型链继承必须用 Object.create

如果你觉得这篇文章有用,记得点赞、收藏、分享,关注我查看更多前端干货!