本文参考链接 彻底理解js中this的指向 和 知乎回答 以及 JavaScript中的对象查找
下面所说的全部为标准模式下的情况,至于严格模式下的this,请参阅另一篇文章最后一部分内容
首先必须要说的是,this的指向在函数定义的时候是确定不了的,只有函数执行的时候才能确定this到底指向谁,实际上this的最终指向的是那个调用它的对象。
既然this最终指向调用它的对象,那么我就首先看下,JS中函数的调用分那几种情况。
JS中函数调用共分为四种类型
一:Function Invocation Pattern
诸如foo()
的调用形式被称为Function Invocation Pattern,是函数最直接的使用形式,注意这里的foo是作为单独的变量出现,而不是属性,前面并无调用对象。
在这种模式下,foo函数体中的this永远为Global对象,在浏览器中就是window对象。比如下面的代码:
function foo(){
var user = "追梦子";
console.log(this.user); //undefined
console.log(this); //Window
}
foo();
即使在函数内部调用,由于前面并无调用对象,this还是指向window全局对象,如以下代码
function printThis() {
console.log(this)
let print = function () { console.log(this) };
print();
}
printThis() // 第一行打印 window,第二行打印window
printThis.call([1]); // 第一行打印[1],第二行打印window
printThis.call([2]); // 第一行打印[2],第二行打印window
在上面的代码中,第一条将打印printThis的this,当通过call函数的时候,参数作为this传递到函数中,因此分别打印[1]和[2]。第二条因为没有对象调用print,因此一直打印window。
即使将print改为匿名函数,比如下面的代码,结果依然没有变化。因此此时匿名函数依然由window调用
function printThis() {
console.log(this)
(function () {
console.log(this)
})(); // 匿名函数和上面的print都由window调用
}
printThis() // 第一行打印 window,第二行打印window
printThis.call([1]); // 第一行打印[1],第二行打印window
printThis.call([2]); // 第一行打印[2],第二行打印window
但是在ES6中,随着箭头函数的引入,这种情况有所变化,将上面的函数改为下面的格式:
function printThis() {
console.log(this)
let print = () => console.log(this);
print();
}
printThis() // 第一行打印 window,第二行打印window
printThis.call([1]); // 第一行打印[1],第二行打印[1]
printThis.call([2]); // 第一行打印[2],第二行打印[2]
此时的结果和上面就有所不同,这是因为在箭头函数中,抛弃了自己的this属性,而是直接使用封闭执行上下文的this值。所谓的封闭执行上下文,就是箭头函数出现的地方的代码域。此时第一条和第二条打印内容相同。
在箭头函数中,所有的this原则,无论是标准模式还是严格模式下,都不再生效。
二:Method Invocation Pattern
诸如foo.bar()
的调用形式被称为Method Invocation Pattern,注意其特点是被调用的函数作为一个对象的属性出现,必然会有“.”或者“[]”这样的关键符号。
在这种模式下,bar函数体中的this永远为“.”或“[”前的那个对象,如上例中就一定是foo对象。比如下面代码:
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
o.b.fn();
虽然首先调用的对象为o
,但最终的函数fn
是对象b
的一个属性,因此this.a
为12。
然后我们再看下下面的代码
var o = {
a:10,
b:{
a:12,
fn:function(){
console.log(this.a); //12
}
}
}
var j = o.b.fn; // 只是定义,并未调用执行,真正的调用执行在下面的j()
j();
此时的this.a
应该是多少呢?此时的this
其实为window,this.a
为undefined。为什么如此呢?回顾一下,在文章的最开始,我们就提到了this的最终指向的是那个调用它的对象。而我们在使用o.b.fn
的时候,并没有调用执行,真正的调用执行是在下面的j(),此时的情况和第一种方式一样。
三:Constructor Pattern
new foo()
这种形式的调用被称为Constructor Pattern,其关键字new
就很能说明问题,非常容易识别。
在这种模式下,foo函数内部的this永远是new foo()返回的对象。比如下面的代码:
function Foo () {
this.x = 1;
}
Foo.prototype.print = function () {
console.log(this.x);
}
let foo = new Foo();
foo.print();
foo.print.call({x: 2});
此时,第一条打印为1,表示this为foo
实例对象。this.x
为constructor函数中赋值的1。第二条打印为2,因此为使用了{x: 2}
作为对象替换了foo
实例对象中的this
。
在这里需要注意Function创建对象的一种特殊情况
function Foo () {
this.x = 1;
return {x: 2};
}
Foo.prototype.print = function () {
console.log(this.x);
}
let foo = new Foo();
console.log(foo.x); // 2
foo.print(); // funciton print undefined
如果构建函数Foo返回的是一个对象,那么foo就会被这个对象所替换掉,此时foo为{x: 2}
,其中只有一个x属性等于2,并无print这个方法属性。但是如果我们修改下代码,改成下面的格式:
function Foo () {
this.x = 1;
return 1;
}
Foo.prototype.print = function () {
console.log(this.x);
}
let foo = new Foo();
console.log(foo.x); // 1
foo.print(); // 1
因为构建函数Foo返回的是并不是一个对象,那么foo就不会被替换掉,依然是Foo的一个实例对象,此时的foo.x
和this.x
全部为1。
四:Apply Pattern
foo.call(thisObject)
和foo.apply(thisObject)
的形式被称为Apply Pattern,使用了内置的call
和apply
函数。
在这种模式下,call
和apply
的第一个参数就是foo函数体内的this,如果thisObject是null
或undefined
,那么会变成window对象。具体代码,我们在上面的三种模式中已经顺带阐述,因此不再赘述。
练习部分
查看下下面的代码,分析下打印的结果,然后实际运行下,看下是否和结果一致
var x = 0;
function Foo () {
this.x = 1;
}
Foo.prototype.print = function () {
console.log(this);
console.log(this.x);
(function () {
console.log(this);
console.log(this.x)
})()
}
let foo = new Foo();
foo.print.call({x: 2});
查看代码,首先注意到Foo对象的print中,第三条和第四条打印是在一个匿名函数中,此时该匿名函数的调用者为window全局变量,因此第四条打印中的this.x
为window.x = 0
。再往下看,发现使用{x:2}
代替了print函数中的this
,因此第一条打印为{x: 2}
,第二条打印为2。
如果修改下代码,将print的定义改为箭头函数呢?结果又如何?代码如下:
var x = 0;
function Foo () {
this.x = 1;
}
Foo.prototype.print = () => {
console.log(this);
console.log(this.x);
(function () {
console.log(this);
console.log(this.x)
})()
}
let foo = new Foo();
foo.print.call({x: 2});
首先,我们知道箭头函数不包含this,它的this为执行上下文中的this。而且箭头函数的执行上下文的判定,就在其定义的时刻决定,我们发现,它是在脚本中定义的,此时的作用域为全局,因此此时,前两条打印和后两条打印一样,都为0和window。
网友评论