本篇博文参考以下文章
https://www.cnblogs.com/w-yh/p/11913803.html
http://www.ruanyifeng.com/blog/2018/06/javascript-this.html
https://www.cnblogs.com/pssp/p/5216085.html
this指向核心法典
函数执行时运行环境是谁,this就指向谁(当存在运行环境嵌套的时候,遵循就近原则)
核心法典这个时候不理解没关系,接下来我们一步一步拨开它的心
为什么会有this
首先需要明确,this是干嘛的。举个例子,创建一个animal对象
let animal = {
name: 'Rabbit',
speed: 10,
run: function (name, speed) {
console.log('The ' + name + ' runs with speed ' + speed + '.')
}
}
上面代码运行run这个函数的时候,每一次都需要给函数传递参数
animal.run(animal.name, animal.speed) //The Rabbit runs with speed 10.
如果是别的函数调用animal的run方法,那传参就传吧,毕竟不是一个系统,但是大多数情况下,都是animal自己使用自己的run方法,这样还得给自己传参,不符合程序思想(主要是因为懒)。
改进一下上面的代码,修改一下形参
let animal = {
name: 'Rabbit',
speed: 10,
run: function (self) {
console.log('The ' + self.name + ' runs with speed ' + self.speed + '.');
}
}
这样再调用的时候,就只用传一个参数就可以了
animal.run(animal) //The Rabbit runs with speed 10.
然后呢,作为程序员,我们是善于研究的(还是因为懒)。这样还是需要传参,明明就是自己调用自己,凭什么要传参?
这里JS作者跟我们一样懒,所以增加一个this
作为语法糖。那由于要考虑当别的地方调用run
方法的时候也能按照我们预期的那样运行,所以this
的指向的便不能仅仅是自身那么简单了,而是提升到了指向运行环境。所以最终代码会变成下面这个样子。
let animal = {
name: 'Rabbit',
speed: 10,
run: function () {
console.log('The ' + this.name + ' runs with speed ' + this.speed + '.');
}
}
什么是运行环境
首先解释一下,为什么把this
提升到运行环境,而不是函数或者对象自身。上面栗子🌰animal(Rabbit)
有一个run
的方法,现在有另一个animalB(Pig)
也想用Rabbit
的方法run
跑一跑这个时候如果this是对象自身,那么实现的效果就是pig
调用了rabbit
的run
方法,然后rabbit
跑了,我们希望的是pig
调用rabbit
的run
方法,然后pig
跑了。
let animal = {
name: 'Rabbit',
speed: 10,
run: function () {
console.log('The ' + this.name + ' runs with speed ' + this.speed + '.');
}
}
let animalB = {
name: 'Pig',
speed: 3,
run: animal.run
}
animalB.run(); //The Pig runs with speed 3.
上面代码中虽然pig调用了rabbit的方法,但是最后输出的还是pig,这就是this的强大之处,指向当前函数运行时的运行环境。大家注意关注下面的代码
run方法是不是运行了,那运行环境是不是animalB提供的,所以this指向了animalB。
再看一下下面的例子
let animal = {
firstname: 'Rabbit', //这里修改为firstname是为了和window中的name区别开来
speed: 10,
run: function () {
console.log('The ' + this.firstname + ' runs with speed ' + this.speed + '.');
}
}
let animalB = {
firstname: 'Pig',
speed: 3,
run: animal.run
}
let sports = animalB.run;
sports();//The undefined runs with speed undefined.
上面代码中先关注最后两行,倒数第二行,animalB
把run
方法赋值给了sports
,问题来了,这里有没有运行环境?
答案是没有,这里只是给sports
这个变量赋值,函数连运行都没有,所以根本不存在运行环境,只是一个简单的赋值。
然后再看倒数第一行,sports()
运行了,那么存在运行环境了吧,运行环境是谁?
这里的运行环境是window
,为什么是window
?
sports
是定义在全局作用域当中的变量吧,全局作用域的运行环境就是window
,这里打印undefined
是因为window
里面没有firstname
和speed
导致的。
所以最开始我们的栗子🌰,可以写成
window.animalB.run();//The Pig runs with speed 3.
这里为什么运行环境不是window
,这里animalB
的运行环境是window
,而run()
的运行环境是animalB
。
你可以把运行环境理解为作用域。看一下我们文章最开始的一句话,
函数执行时运行环境是谁,this就指向谁(当存在运行环境嵌套的时候,遵循就近原则)就近原则就是这么来的。
从内存理解this
1.内存结构
let obj = {foo: 5};
上面过程中,JS引擎会先在内存中生成一个{foo: 1}
,然后把存放这个对象的内存地址赋值给obj
。
严格意义上来说,obj
存放的是一个地址,浏览器引擎会拿到obj
中的地址,从该地址读出原始对象,返回它的foo
属性。
每个对象的属性都有四个属性描述符,用来存放该属性的一些特征(值,是否可写,是否可枚举,是否可配置)上面栗子🌰的实际保存方式如下。
2.函数
当属性的值是一个函数的时候,那么结构就又发生变化了
由于函数是一个单独的值,他可以在不同的环境下执行
let f = function () {};
let obj = {f:f};
f();//单独执行
obj.f();//obj环境执行
3.环境变量
JavaScript的运行机制里,允许在函数内部引用当前环境的其他变量。这就需要一种机制能够在函数体内部获取当前运行环境,this
就应运而生,它涉及的目的就是在函数题内部,指代当前运行环境。
let f = function () {
console.log(this.x)
};
let x = 1;
let obj = {
f:f,
x: 2
};
//单独执行
f();// 1
//obj环境执行
obj.f();// 2
f()
的运行环境如下,粉色区域
obj.f()
的运行环境如下粉色区域
注意上方红线,不同环境下对于function
的引用不同,所以导致了获取到的this.x
不同。
构造函数this
构造函数中的this
,为什么其实例对象能拥有构造函数的属性呢,是因为在new
的过程中,this
的指向被改变了,指向了实例。下面这种情况比较特殊。
function Fn()
{
this.name = 'rabbit';
return {};
}
let f = new Fn;
console.log(f.name); //undefined
这里因为return返回的是一个对象,所以this就指向这个对象,而不是实例。