JS面试加工厂

call、aplly、bind 实现 && this理解

2021-06-07  本文已影响0人  斗伽

本质

call、aplly、bind 本质都是改变 this 的指向,不同点 call、aplly 是直接调用函数,bind 是返回一个新的函数。call 跟 aplly 就只有参数上不同。

手写实现

bind 实现

// bind 实现
Function.prototype.djBind = function (thisArg) {
  if (typeof this !== "function") {
    throw TypeError("Bind must be called on function");
  }

  // 拿到参数args,为了传给调用者
  const args = Array.prototype.slice.call(arguments, 1),
    self = this,
    nop = function () {},
    bound = function () {
      // this instanceof nop, 判断是否使用 new 来调用 bound
      // 如果是 new 来调用的话,this的指向就是其实例,
      // 如果不是 new 调用的话,就改变 this 指向到指定的对象 o
      return self.apply(
        this instanceof nop ? this : thisArg,
        args.concat(Array.prototype.slice.call(arguments))
      );
    };

  // 箭头函数没有 prototype,箭头函数this永远指向它所在的作用域
  if (this.prototype) {
      nop.prototype = this.prototype;
  } 
  // 修改绑定函数的原型指向
  bound.prototype = new nop();

  return bound;
};

Test bind

const bar = function() {
  console.log(this.name, arguments);
};
 
bar.prototype.name = 'bar';
 
const foo = {
  name: 'foo'
};
 
const bound = bar.djBind(foo, 22, 33, 44);
new bound(); // bar, [22, 33, 44]    this指向bar 
bound(); // foo, [22, 33, 44

call 实现

Function.prototype.djCall = function(thisArg) {
    if(typeof this !== 'function') {
        throw TypeError("call must be called on function");
    }

    const args = Array.prototype.slice.call(arguments, 1);
    thisArg = thisArg || window;
    
    // 将调用call函数的对象添加到thisArg的属性中 
    thisArg.fn = this;
    // 执行该属性, 在thisArg上调用this方法;
    const result = thisArg.fn(...args);

    delete thisArg.fn;
    return result;
}

问题

apply 实现

Function.prototype.djApply = function(thisArg) {
    if(typeof this !== 'function') {
        throw TypeError("call must be called on function");
    }

    const args = arguments[1];
    thisArg = thisArg || window;
    
    // 将调用call函数的对象添加到thisArg的属性中 
    thisArg.fn = this;
    // 执行该属性
    const result = thisArg.fn(...args);

    delete thisArg.fn;
    return result;
}

Test call && apply


const bar = function() {
  console.log(this.name, arguments);
};
 
bar.prototype.name = 'bar';
 
const foo = {
  name: 'foo'
};
 
bar.mycall(foo, 1, 2, 3);   // foo [1, 2, 3]
bar.myaplly(foo, [1, 2, 3]);   // foo [1, 2, 3]

this

JS中的this是一个非常简单的东西,只需要理解它的执行规则;
call/apply/bind可以显式绑定, 这里就不说了;如上所述;
主要这些场隐式绑定的场景讨论:

  1. 全局上下文
    全局上下文默认this指向window, 严格模式下指向undefined。
  2. 直接调用函数
    比如:
let obj = {
  a: function() {
    console.log(this);
  }
}
let func = obj.a;
func();

这种情况是直接调用。this相当于全局上下文的情况。

  1. 对象.方法的形式调用
    还是刚刚的例子,我如果这样写:
    obj.a();

这就是对象.方法的情况,this指向这个对象

  1. DOM事件绑定
    onclick和addEventerListener中 this 默认指向绑定事件的元素。
    IE比较奇异,使用attachEvent,里面的this默认指向window。
  2. new+构造函数
    此时构造函数中的this指向实例对象。
  3. 箭头函数?
    箭头函数没有this, 因此也不能绑定。里面的this会指向当前最近的非箭头函数的this,找不到就是window(严格模式是undefined)。比如:
let obj = {
  a: function() {
    let do = () => {
      console.log(this);
    }
    do();
  }
}
obj.a(); // 找到最近的非箭头函数a,a现在绑定着obj, 因此箭头函数中的this是obj

优先级: new > call、apply、bind > 对象.方法 > 直接调用。

上一篇下一篇

猜你喜欢

热点阅读