前言
在Javascript中,一切皆对象,运行环境也是对象,所以函数都是在某个对象之中运行,this就是函数运行时所在的对象(环境)。
this的使用场景
1.全局环境。
全局环境中使用this,它指的就是顶层对象window。
this === window // true
function f() {
console.log(this === window);
}
f() // true
不管是不是在函数内部,只要是在全局环境下运行,this就是指顶层对象window。
2.构造函数
构造函数中的this,指的是实例对象。
var Person = function(name){
this.name = name;
}
var p = new Person("Tom");
console.log(p.name); //Tom
上面Person
就是一个构造函数,在函数内部定义了this.name,就相当于定义实例对象有一个name属性。
3.对象的方法
如果对象的方法里包含了this
,此时this
的指向就是方法运行时所在的对象,若将该方法赋值给另一个对象,就会改变this
的指向。
var obj = {
fn:function(){
console.log(this);
}
};
//case 0
obj.fn() //obj
//case 1
(obj.fn = obj.fn)();//window
//case 2.
(false||obj.fn)();//window
//case.3
(1,obj.fn)() //window
在case 0时执行obj.fn方法时,内部this指向obj,在case1-3中,obj.fn就是一个值。在调用的时候运行环境不再是obj,而是全局环境,所以指向window。
使用注意
1.避免使用多层this
因为this的指向时不确定的,所以不要在函数中嵌套多层this。
var o = {
f1: function () {
console.log(this);
var f2 = function () {
console.log(this);
}();
}
}
o.f1()
第一个this指向o,第二个this指向window。
实际执行代码:
var temp = function () {
console.log(this);
};
var o = {
f1: function () {
console.log(this);
var f2 = temp();
}
}
因为var具有变量提升,所以可以提升至前面。
Object Window
如果想要f2也指向o,可以在第二层改用一个指向外层this的变量。
var o = {
f1: function() {
console.log(this);
var that = this;
var f2 = function() {
console.log(that);
}();
}
}
o.f1()
//Object
//Object
上面代码定义了变量that,固定指向外层的this,然后在内层使用that,就不会发生this指向的改变。
使用一个变量固定this的值,然后内层函数调用这个变量,是非常好的发方法。
避免数组处理方法中的this
1.数组的map和forEach方法,允许提供一个函数作为参数。这个函数内部不应该使用this。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
});
}
}
o.f()
//undefined
//undefined
上述forEach 中的this指向的是window对象,因此取不到o.v的值。原因跟上一段的多层this是一样的,就是内层的this不指向外部,而指向顶层对象。
解决办法:
(1)使用中间变量固定this。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
var that = this;
this.p.forEach(function (item) {
console.log(that.v+' '+item);
});
}
}
o.f()
// hello a1
// hello a2
(2)将this当作forEach方法的第二个参数,固定运行环境。
var o = {
v: 'hello',
p: [ 'a1', 'a2' ],
f: function f() {
this.p.forEach(function (item) {
console.log(this.v + ' ' + item);
}, this);
}
}
o.f()
// hello a1
// hello a2
3.避免回调函数中的this
回调函数中的this往往改变指向,要避免使用。
4.绑定this的方法
**this的动态切换,固然为 JavaScript 创造了巨大的灵活性,**但也使得编程变得困难和模糊。有时,需要把this固定下来,避免出现意想不到的情况。JavaScript 提供了call、apply、bind这三个方法,来切换/固定this的指向。
4.1 Function.prototype.call()
函数实例中的call,可以指定函数内部this的指向(即函数执行时所在的作用域),然后在该作用域中调用该函数。
call方法的参数,**应该是一个对象。如果参数为空、null和undefined,**则默认传入全局对象。
var n = 123;
var obj = { n: 456 };
function a() {
console.log(this.n);
}
a.call() // 123
a.call(null) // 123
a.call(undefined) // 123
a.call(window) // 123
a.call(obj) // 456
如果call方法的参数是一个原始值,那么这个原始值会自动转成对应的包装对象,然后传入call方法。
call方法还可以接受多个参数。
格式如下:
func.call(thisValue, arg1, arg2, ...)
call的第一个参数就是this
所要指向的那个对象,后面的参数则是函数调用时所需的参数。
应用:
call方法的一个应用是调用对象的原生方法。
var obj = {};
obj.hasOwnProperty('toString') // false
// 覆盖掉继承的 hasOwnProperty 方法
obj.hasOwnProperty = function () {
return true;
};
obj.hasOwnProperty('toString') // true
Object.prototype.hasOwnProperty.call(obj, 'toString') // false
4.2Function.prototype.apply()
和call类似,唯一区别是:它接收一个数组作为函数执行时的参数,使用格式如下:
func.apply(thisValue, [arg1, arg2, ...])
apply方法的第一个参数也是this所要指向的那个对象,如果设为null或undefined,则等同于指定全局对象。第二个参数则是一个数组,该数组的所有成员依次作为参数,传入原函数。原函数的参数,在call方法中必须一个个添加,但是在apply方法中,必须以数组形式添加。
function f(x, y){
console.log(x + y);
}
f.call(null, 1, 1) // 2
f.apply(null, [1, 1]) // 2
apply使用场景
(1)找出数组中最大元素
JavaScript 不提供找出数组最大元素的函数。结合使用apply方法和Math.max方法,就可以返回数组的最大元素。
var a = [10, 2, 4, 15, 9];
Math.max.apply(null, a) // 15
(2)将数组的空元素变为undefined
通过apply方法,利用Array构造函数将数组的空元素变成undefined。
Array.apply(null, ['a', ,'b'])
// [ 'a', undefined, 'b' ]
空元素与undefined的差别在于,数组的forEach方法会跳过空元素,但是不会跳过undefined。因此,遍历内部元素的时候,会得到不同的结果。
var a = ['a', , 'b'];
function print(i) {
console.log(i);
}
a.forEach(print)
// a
// b
Array.apply(null, a).forEach(print)
// a
// undefined
// b
(3)转换类似数组的对象
另外,利用数组对象的slice方法,可以将一个类似数组的对象(比如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]
上面代码的apply方法的参数都是对象,但是返回结果都是数组,这就起到了将对象转成数组的目的。从上面代码可以看到,这个方法起作用的前提是,被处理的对象必须有length属性,以及相对应的数字键。
(4)绑定回调函数的对象
var o = new Object();
o.f = function () {
console.log(this === o);
}
var f = function (){
o.f.apply(o);
// 或者 o.f.call(o);
};
// jQuery 的写法
$('#button').on('click', f);
上面代码中,点击按钮以后,控制台将会显示true。由于apply()方法(或者call()方法)不仅绑定函数执行时所在的对象,还会立即执行函数,因此不得不把绑定语句写在一个函数体内。
4.3Function.prototype.bind()
bind()方法用于将函数体内的this绑定到某个对象,然后返回一个新函数。
var d = new Date();
d.getTime() // 1481869925657
var print = d.getTime;
print() // Uncaught TypeError: this is not a Date object.
bind()方法可以解决这个问题。
var print = d.getTime.bind(d);
print() // 1481869925657
bind()还可以接受更多的参数,将这些参数绑定原函数的参数。
var add = function (x, y) {
return x * this.m + y * this.n;
}
var obj = {
m: 2,
n: 2
};
var newAdd = add.bind(obj, 5);
newAdd(5) // 20
如果bind()方法的第一个参数是null或undefined,等于将this绑定到全局对象,函数运行时this指向顶层对象(浏览器为window)
bind()方法的注意事项
(1)每次返回一个新函数
(2)结合回调函数使用
obj.print = function () {
this.times.forEach(function (n) {
console.log(this.name);
}.bind(this));
};
obj.print()
// 张三
// 张三
// 张三
(3)结合call()方法使用
利用bind()方法,可以改写一些 JavaScript 原生方法的使用形式,以数组的slice()方法为例。
[1, 2, 3].slice(0, 1) // [1]
// 等同于
Array.prototype.slice.call([1, 2, 3], 0, 1) // [1]
call()方法实质上是调用Function.prototype.call()方法,因此上面的表达式可以用bind()方法改写。
var slice = Function.prototype.call.bind(Array.prototype.slice);
slice([1, 2, 3], 0, 1) // [1]
上面代码的含义就是,将Array.prototype.slice变成Function.prototype.call方法所在的对象,调用时就变成了Array.prototype.slice.call。类似的写法还可以用于其他数组方法。
var push = Function.prototype.call.bind(Array.prototype.push);
var pop = Function.prototype.call.bind(Array.prototype.pop);
var a = [1 ,2 ,3];
push(a, 4)
a // [1, 2, 3, 4]
pop(a)
a // [1, 2, 3]
总结:
1.this 永远指向最后调用它的那个对象。
2.改变this的指向方法
- 使用ES6箭头函数
- 在函数内部使用that = this;
- 使用apply、call、bind方法
- new 实例化一个对象
3.箭头函数中的this始终指向函数定义时的this,而非执行时。
参考
https://wangdoc.com/javascript/oop/this.html