ES5读书简记·this

CY 2018年03月16日 170次浏览

这里的this关键字统统不涉及到箭头函数。

this对象的含义

this关键字可以用在构造函数之中,这时this表示实例对象

this不管在哪里,都是一个对象。

this的指向是可变的,如果将A对象中的一个方法中存在this,将这个方法赋值到B对象中,在B对象中调用这个方法,那么原先A对象方法中的this的指向就发生了改变,this的指向从A对象变为了B对象。

如果在HTML的事件属性中使用了this,那么这个this就表示当前的元素对象,比如说input元素对象

this对象的实质

由于函数单独存储在内存中,一切引用了函数的变量或常量,实际上只引用了函数的地址,这就导致函数可以在不同的环境下运行,可以在某个对象中运行,也可以在全局环境执行。

因为函数可以在不同的环境执行,那么就需要一种机制来让函数知道自己当前运行在什么样的环境下,所以this就出现了。

this在不同环境中的指向

如果在全局环境中使用this,他指向的就是顶层对象window

如果this构造函数中,他就指的是实例对象,使用this.属性就相当于操作了实例对象中的一个属性

如果this对象的方法中,那么this指向的就是运行时所在的对象,注意这里是运行时,不是定义时。

  • 调用的时候使用obj.foo()这样this才会指向obj对象,有几种情况依然不会指向obj对象:

    // 情况一
    (obj.foo = obj.foo)() // window
    // 情况二
    (false || obj.foo)() // window
    // 情况三
    (1, obj.foo)() // window
    
  • 如果有this关键字的方法不在对象的第一层,而是在对象中的对象中,那么这个this指向的是对象中的对象,而不是最外层的对象。

this使用注意点

尽量不要使用多层this,这个可以使用严格模式来避免,严格模式下,this时永远不可能指向顶层对象的,严格模式下this如果指向了顶层对象,那么这个时候的this就是undefined

尽量不要在数组的处理方法中使用this,如果在数组处理方法(比如forEach)中使用了this,这个this也相当于指向了顶层对象,如果非要去使用this,有两种解决方法:

  • 使用中间变量固定this,例如var _this = this;
  • 在数组处理方法的第二个参数中传入this,每一个数组处理方法的第二个参数都是?thisArg

尽量不要在回调函数中使用this,因为this指向的是执行时候的上下文,但是回调函数并不确定在什么环境下执行,这句话成立的前提时不考虑箭头函数

怎么绑定this

可以使用JavaScript提供的callapplybind方法

Function.prototype.call()

这个方法用来调用(执行)方法,他的第一个参数用来规定这个函数执行的this的指向,也就是规定函数执行的环境(Context)。

如果第一个参数为nullundefined,或者什么都不传,那么默认使用全局对象,浏览器上就是window对象。

如果第一个参数传递一个原始的数值(PrimitiveValue),那么这个数值会转换成包装类型,这个被call调用的函数中的this就等于这个数值的包装类型

除了第一个参数外的其他参数都用来作为调用方法的时候传递的参数。

call方法的一个应用就是调用对象的原生方法

var obj = {};
obj.hasOwnProperty('toString') // false

// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
  return true;
};
obj.hasOwnProperty('toString') // true 因为方法被覆写了,所以这里的执行结果是true

Object.prototype.hasOwnProperty.call(obj, 'toString') // false

Function.prototype.apply()

call()方法的不同之处只有一点,apply的第二个参数是一个数组,而不是一个rest参数(可变参数)。

应用:

  • 【常用】数组中的最大值
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
  • 将数组的empty元素变为undefined,因为数组遍历的时候empty元素是不会被遍历的,所以要转换一下。
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]
  • 转换类数组对象

document.getElementsByTagName()arguments等这些都是类数组对象,也就是说有下标,有长度,就是没有数组的方法。

Array.prototype.slice.apply({0: 1, length: 1}) // [1]
Array.prototype.slice.apply({0: 1}) // []
Array.prototype.slice.apply({0: 1, length: 2}) // [1, undefined]
Array.prototype.slice.apply({length: 1}) // [undefined]
  • 绑定回调函数的this,但是这需要使用一个函数做包装(如下),所以还是使用bind好点
var o = new Object();

o.f = function () {
  console.log(this === o);
}

var f = function () { // 因为apply和call都会立即执行函数,所以使用一个函数将原来的函数包装起来
  o.f.apply(o);
  // 或者 o.f.call(o);
};

// jQuery 的写法
$('#button').on('click', f);

Function.prototype.bind()

bind()和上面两个方法都不一样,他不会立即执行函数,而是会返回一个新的函数

第一个参数和上面两个方法都一样,第二个参数可以用来绑定某个函数的参数,然后返回一个新的函数,这个新的函数只需要传递没有被绑定的参数即可,例如:

function add(x, y) {
  return x + y;
}

var plus5 = add.bind(null, 5); // 返回了一个新的函数,相当于add函数中的x已经被绑定为了5
plus5(10) // 15 这时只用传递参数y即可

所以,可以使用bind()函数做函数的柯里化。

注意不要直接将bind用在监听事件的回调中,因为bind会每次返回一个新的函数,所以就无法取消监听了,应该写在外面。

element.addEventListener('click', o.m.bind(o)); // 错误写法
var listener = o.m.bind(o);
element.addEventListener('click', listener); // 正确写法

如果一个回调函数中包含this,可以使用bind为这个this绑定一个对象,防止这个this的指向发生改变。

以下的写法效果相同:

obj.name = "张三";
obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this.name);
  }.bind(this));
};
obj.print();
obj.name = "张三";
obj.print = function () {
  this.times.forEach(function (n) {
    console.log(this.name);
  }, this);
};
obj.print();
obj.name = "张三";
obj.print = function () {
  this.times.forEach(n => void console.log(this.name));
};
obj.print();

bindcall结合使用可以创造出来"静态导入"的效果

例如下面的方法,将push方法绑定到call方法上,这个时候push可以用call函数一样的参数形式来调用了。

var push = Function.prototype.call.bind(Array.prototype.push);
var a = [1 ,2 ,3];
push(a, 4);