this的理解+call/bind/apply

127 阅读2分钟

this的六大场景

1.  全局上下文
2.  直接调用函数
3.  对象.方法的形式调用
4.  DOM事件绑定(特殊)
5.  new构造函数绑定
6.  箭头函数

1. 全局上下文 全局上下文默认this指向window, 严格模式下指向undefined。

2. 直接调用函数 this相当于全局上下文的情况。

3.对象.方法的形式调用 this指向这个对象/方法

4. DOM事件绑定

onclick和addEventerListener中 this 默认指向绑定事件的元素。 IE比较奇异,使用attachEvent,里面的this默认指向window。

5. new+构造函数

此时构造函数中的this指向实例对象。

  1. 箭头函数

箭头函数没有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 > 对象.方法 > 直接调用。

  • call/bind/apply
call 的参数是直接放进去的,第二第三第 n 个参数全都用逗号分隔,直接放到后面 
apply 的所有参数都必须放在一个数组里面传进去
bind 除了返回是函数以外,它 的参数和 call 一样
  • 模拟实现一个 bind 的效果

bind所做的事情:

1.  对于普通函数,绑定this指向
2.  对于构造函数,要保证原函数的原型对象上的属性不能丢失
Function.prototype.bind = function (context, ...args) {
    // 异常处理
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }
    // 保存this的值,它代表调用 bind 的函数
    var self = this;
    var fNOP = function () {};

    var fbound = function () {
        self.apply(this instanceof self ? 
            this : 
            context, args.concat(Array.prototype.slice.call(arguments)));
    }

    fNOP.prototype = this.prototype;
    fbound.prototype = new fNOP();

    return fbound;
}

也可以这么用 Object.create 来处理原型:

Function.prototype.bind = function (context, ...args) {
    if (typeof this !== "function") {
      throw new Error("Function.prototype.bind - what is trying to be bound is not callable");
    }

    var self = this;

    var fbound = function () {
        self.apply(this instanceof self ? 
            this : 
            context, args.concat(Array.prototype.slice.call(arguments)));
    }

    fbound = Object.create(this.prototype);

    return fbound;
}
  • call/apply 函数

//大佬的代码
Function.prototype.call = function (context) {
    var context = context || window;
    context.fn = this;

    var args = [];
    for(var i = 1, len = arguments.length; i < len; i++) {
        args.push('arguments[' + i + ']');
    }

    var result = eval('context.fn(' + args +')');

    delete context.fn
    return result;
}

不过我认为换成 ES6 的语法会更精炼一些:

Function.prototype.call = function (context, ...args) {
  var context = context || window;
  context.fn = this;

  var result = eval('context.fn(...args)');

  delete context.fn
  return result;
}

类似的,有apply的对应实现:

Function.prototype.apply = function (context, args) {
  let context = context || window;
  context.fn = this;
  let result = eval('context.fn(...args)');

  delete context.fn
  return result;
}