2022-09-18笔试题00
请注意,本文编写于 530 天前,最后修改于 530 天前,其中某些信息可能已经过时。

面试高频手撕代码题

实现一下继承 call apply bind

实现 call

来看下 call 的原生表现形式:

var foo = {
  value: 1,
};

function bar() {
  console.log(this.value);
}

bar.call(foo); // 1

从上面代码的执行结果,我们可以看到,call 首先改变了 this 的指向,使函数的 this 指向了 foo,然后使 bar 函数执行了。

总结如下:

  1. call 改变函数 this 指向,
  2. 调用函数

思考一下:我们如何实现上面的效果呢?代码改造如下:

//将bar函数挂载到foo对象上,使其成为foo的方法,用foo.bar来调用
var foo = {
  value: 1,
  bar: function () {
    console.log(this.value);
  },
};
foo.bar(); //1

为了模拟 call 方法,我们可以这样做:

  1. 将函数设为某个对象的属性(或者方法)
  2. 通过该对象的属性调用该函数
  3. 删除该对象上的这个属性(或者方法)

代码如下:

Function.prototype.myCall = function (context) {
  const fn = Symbol('fn'); // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
  context = context || window; // 若没有传入this, 默认绑定window对象
  context.fn = this; // 将函数挂载到对象的fn属性上
  const args = [...arguments].slice(1); // 处理传入的参数
  const result = context.fn(...args); // 通过对象的属性调用该方法
  delete context.fn; // 删除该属性
  return result;
};

// 测试
function test(arg1, arg2) {
  console.log(arg1, arg2);
  console.log(this.a, this.b);
}

test.myCall(
  {
    a: 'a',
    b: 'b',
  },
  1,
  2
);

我们看一下上面的代码:

  1. 首先我们对参数 context 做了兼容处理,不传值,context 默认值为 window。
  2. 然后我们将函数挂载到 context 上面,context.fn = this;
  3. 处理参数,将传入 myCall 的参数截取,去除第一位,然后转为数组;
  4. 调用 context.fn,此时 fn 的 this 指向 context;
  5. 删除对象上的属性 delete context.fn
  6. 将结果返回。

实现 apply

apply 和 call 实现类似,只是传入的参数形式是数组形式,而不是逗号分隔的参数序列。

因此,借助 es6 提供的...运算符,就可以很方便的实现数组和参数序列的转化。

Function.prototype.myApply = function (context) {
  const fn = Symbol('fn'); // 声明一个独有的Symbol属性, 防止fn覆盖已有属性
  context = context || window; // 若没有传入this, 默认绑定window对象
  context.fn = this; // 将函数挂载到对象的fn属性上
  const args = [...arguments].slice(1); // 处理传入的参数
  const result = context.fn(args); // 通过对象的属性调用该方法
  delete context.fn; // 删除该属性
  return result;
};

// 测试
function test(arr) {
  console.log(arr);
  console.log(this.a, this.b);
}

test.myApply(
  {
    a: 'a',
    b: 'b',
  },
  [1, 2]
);

实现 bind

在模拟 bind 的实现之前,先看一下 bind 的使用案例:

var obj = { a: 1 };
function bar() {
  console.log(this.a);
}
bar.bind(obj)(); //here

bind 函数虽然也能改变 bar 函数的 this,但是改变后,函数并不会执行,只是返回一个新的函数,想执行就得继续调用,仔细观察第五行代码的写法。

根据上面的使用案例,我们先实现一个简单版本的 bind:

Function.prototype.myBind = function (context) {
  return () => {
    // 要用箭头函数,否则 this 指向错误
    return this.call(context);
  };
};
var obj = { a: 1 };
function bar() {
  console.log(this.a);
}
bar.myBind(obj)();

但是这样比较简陋,函数的参数一多就不能处理了,如下面这种情况:

bar.bind(obj, 2)(2);
// or
bar.bind(obj)(2, 2);

为了兼容 bind 调用时满足参数传递的不同方式,代码修改如下:

Function.prototype.myBind = function (ctx, ...arg1) {
  return (...arg2) => {
    return this.call(ctx, ...arg1, ...arg2);
  };
};
//测试代码
var obj = { a: 1 };
function bar(b, c) {
  console.log(this.a + b + c);
}
bar.myBind(obj)(20, 30);
bar.myBind(obj, 20, 30)();

本文作者:前端小毛

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!