this 关键字是JavaScript中最灵活也最令人困惑的知识点。
this指代的是什么
首先看如下代码:
var a = 20; //(1)
function fun() {
var a = 10;
console.log(this.a); //(2) 思考下此处this指代的是什么
}
fun(); //(3)
在浏览器中运行此段代码会得到
20
下面详细讲解这段代码:
-
var a = 20
这行代码看似平常。先不进一步解释这行代码。在上述代码片段的最后一行加入:console.log(window.a);
会得到如下输出
20
是不是和a变量本身的值一致呢?
事实上在浏览器环境中运行相当于window.a = 20
。浏览器环境中会默认存在全局变量window
。
window
是浏览器对象模型。所有的现代浏览器都支持该对象。并且JavaScript在浏览器运行环境中运行时,window
均为全局对象。任何定义的变量或者是函数都是window
这个对象的成员变量。
- 对于JavaScript中的
this
,完全不能按照Java中的this
进行类比理解。在Java中,this
指代的是当前对象的实例,且this
所指代的对象永远是不变化的值。而在JavaScript中,this
指代的是调用该方法的对象。如果当前方法是直接调用的话,this
指代的是全局对象。在浏览器环境中指的是window
对象。
解释console.log(this.a)
之前,需要先明白(3)处的方法调用。
fun()
方法调用没有明确指明调用该方法的对象。这么来说,fun()
方法调用实际上相当于window.fun()
。
到现在为止(2)这行代码中this
的指向问题应该很清楚了。该处的this
指向的正是window
对象。
再看看这一段代码,试着确定下该代码的输出内容,检测下自己对上述内容的理解程度:
var a = 10;
function fun() {
this.a = 20;
console.log(this.a);
}
fun(); //输出什么?
console.log(a); //这里呢?
console.log(this.a); //这里又该是什么呢?
下面给出这段代码的解释:
var a = 10; //相当于window.a = 10
function fun() {
this.a = 20;
console.log(this.a);
}
fun(); //相当于window.fun();此时的方法调用this的指向为window对象,于是变量a的值会被覆盖成20
console.log(a); //相当于window.a。a=20
console.log(this.a); //相当于window.a。a=20
进阶内容
请看下面这段代码:
var person1 = {
name: "Peter",
sayName: function() {
console.log(this.name);
}
}
var person2 = {
name: "Mary",
sayName: person1.sayName
}
// 尝试推断以下输出
person1.sayName(); //(1)
person2.sayName(); //(2)
var fun = person1.sayName;
fun(); //(3)
解析:
- (1)处的输出为Peter。调用该方法的对象为person1,故
this
指向person1。所以输出为Peter。 - (2)处的输出为Mary。虽然此处调用的方法为person1中的方法sayName,但是this却是在被调用的时候才会绑定。此处调用该方法的对象为person2,所以输出为Mary。
- (3)处的输出为undefined。该处的方法调用相当于
window.fun()
。this指向的是window对象,而window中并没有name这个变量,所以输出为undefined。
绑定this之call, apply与bind
call与apply
看下面这段代码:
function sayName(greetings, otherMessages) {
console.log(greetings + " " + this.name);
console.log(otherMessages);
}
var obj = {
name: "Paul"
};
sayName.call(obj, "Hello", "I am 20"); //(1)
sayName.apply(obj, ["Hello", "I am 25"]); //(2)
(1)和(2)处的代码均相当于把sayName这个函数的this绑定到obj这个对象上再执行。
总结一下来说call和apply方法能够绑定函数的this。不同之处在于call方法调用函数,函数的参数用逗号分隔的参数列表传入。而apply方法调用的话函数的参数用数组的形式传入。
bind方法
bind方法同样能够绑定function中的this变量,但是又有所不同。函数的this一旦被bind,即便再使用call或者是apply再次尝试重新绑定this,仍然不会生效。
示例代码如下:
function sayName(greetings) {
console.log(greetings + " " + this.name);
}
var obj = {
name: "Paul"
};
var obj2 = {
name: "Kate"
};
var fun = sayName.bind(obj); //此处fun这个函数的this被绑定到了obj这个对象上
fun("Hello", "I am 20"); //输出为Hello Paul。fun函数的this已经被绑定到了obj对象
fun.call(obj2, "Hello", "I am 20"); //此处尝试使用obj2来绑定this,但是fun的this已经被绑定至obj,因此输出仍然是Hello Paul
new关键字的背后
和在Java中单纯的创建新实例不同,JavaScript中的new关键字所做的事情要复杂多了。
使用new关键字会执行如下步骤:
- 生成一个空的object,即
{}
- 设置新对象的prototype为构造函数的prototype
- 将构造函数中的this绑定至新创建的object
- 执行构造函数
- 返回创建的新object
示例代码如下:
function Person() {
this.name = "Paul";
this.age = "20";
this.sayName = function() {
console.log(this.name);
}
}
Person.prototype.sayAge = function() {
console.log(this.age);
}
var person = new Person();
person.sayName();
person.sayAge();
在new这个对象的时候发生了如下事情,使用代码来描述如下:
var person = {}; //创建空的object
person.prototype = Person.prototype; //新创建object的prototype指向构造函数的prototype
Person.call(person); //执行构造函数,同时函数中的this指向新创建的object
问题来了,如果构造函数有返回值会发生什么事情呢?例如这段代码:
function Obj1() {
this.a = 10;
return "modified";
}
var obj1 = new Obj1(); //此处obj1为{a: 10},并没有受到返回值的影响
function Obj2() {
this.a = 10;
return {
b: 20
};
}
var obj2 = new Obj2(); //此处的obj2为{b: 20},构造函数的返回值影响了新创建的object
总结如下:
- 如果构造函数返回值不为object类型,则构造函数的返回值会被忽略
- 如果构造函数返回值是object类型,则新创建的object会被替换为构造函数返回的object
箭头函数
想之前讨论的那样,传统的this绑定问题在实际应用时特别棘手。有个例子可以说明:
var obj = {
a: 10,
b: [1, 2, 3],
c: function() {
return this.b.map(function(item) {
item + this.a; //(1)
});
}
}
console.log(obj.c()); //输出是[11, 12, 13]吗?实际上该处的输出为[undefined, undefined, undefined]
// 因为(1)处的代码this指向的是window,所以说该处this.a的值为undefined
ES6中引入了新的箭头函数,能够解决this绑定这一大难题。
var obj = {
a: 10,
b: [1, 2, 3],
c: function() {
return this.b.map((item) => this.a + item);
}
}
console.log(obj.c()); //输出为[11, 12, 13]
事实上箭头函数执行时候不绑定this,而是在自动寻找上层的this(如果没有继续向上层寻找)并且绑定,箭头函数中的this
会向上寻找到obj.c这个函数,在该函数中this.a=10,所以说该处代码能够返回我们想要的结果。
为了验证这段话,请看下面的代码:
function test() {
this.a = 10;
var obj1 = {
a: 20,
b: function() {
return () => console.log(this.a);
},
c: function() {
console.log(this.a);
}
};
var obj2 = {
a: 30,
b: obj1.b(),
c: obj1.c
};
obj1.b()(); //返回20。obj1的b函数执行时this为obj1对象。其返回的箭头函数中的this也指向obj1。
obj1.c(); //返回20。obj1的c函数中的this指向obj1。
var f = obj1.b;
f()(); //返回10。相当于var fun = window.f(); fun(); f函数执行时this绑定到window,其返回的箭头函数中的this依照原则指向的同样是window对象。
f = obj1.c;
f(); //返回10。相当于window.f()
obj2.b(); //返回20。obj1.b在obj2对象定义的时候执行,obj1.b执行的时候this绑定至obj1对象,其中箭头函数的this同样指向的是obj1对象。
obj2.c(); //返回30。obj1的c函数在执行时候绑定,因此this指向obj2。
}
test();
本文总结
- 普通函数中的
this
变量在定义的时候不能确定,而是在函数被执行的时候才绑定。 - 普通函数中的
this
在执行时绑定至调用该函数的object。 - call、apply和bind可以改变函数的this绑定。bind绑定的this无法再显式绑定(
new
除外)。 - 箭头函数执行时不绑定this,而是自动向上寻找this,直到找到为止。应用场景多为回调函数,例如map,forEach和reduce。
本文为原创内容,欢迎大家讨论、批评指正与转载。转载时请注明出处。