这里的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
提供的call
,apply
和bind
方法
Function.prototype.call()
这个方法用来调用(执行)方法,他的第一个参数用来规定这个函数执行的this
的指向,也就是规定函数执行的环境(Context
)。
如果第一个参数为null
,undefined
,或者什么都不传,那么默认使用全局对象,浏览器上就是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();
将bind
和call
结合使用可以创造出来"静态导入"的效果
例如下面的方法,将push
方法绑定到call
方法上,这个时候push
可以用call
函数一样的参数形式来调用了。
var push = Function.prototype.call.bind(Array.prototype.push);
var a = [1 ,2 ,3];
push(a, 4);