JavaScript中this的指向问题
参考资料:
JavaScript 的 this 原理 - 阮一峰的网络日志 (ruanyifeng.com)
Javascript 的 this 用法 - 阮一峰的网络日志 (ruanyifeng.com)
JS中的this指向问题(详细版)_LoveyL0201的博客-CSDN博客
《你不知道的JavaScript》-上,推荐读这本书
本文记录了JavaScript中this指向问题的学习心得,为以后的复习做准备。
讨论this的时候要注意是否在严格模式下。下面代码大部分情况下都不在严格模式下。
毫无疑问,在缺乏清晰认识的情况下,this 对你来说完全就是一种魔法–《你不知道的JavaScript》
this的原理
this
指的是函数运行时所在的环境。看下述代码。
let obj = {
bar: 1,
f: function(){
console.log(this.bar);
}
}
obj.f();
var bar = 2;// 这里不能使用let,let声明的变量是不会称为window的对象的,当然也不能使用nodejs执行,nodejs是没有window的。
let f = obj.f;
f();
结果为:
1
2
通过这个例子我们就可以看出,对于obj.f()
来说它的运行环境就是在obj
这个对象里面,里面的this.bar = 1
,对于f()
来说,它的运行环境就是在全局作用域,那么它的this.bar = window.bar = 2
。
但是为什么是这样的呢?在那个环境执行到底是由什么决定的呢?阮一峰
大佬为我们做了详细的解释。其实就是查看调用栈的位置。可以把调用栈想象成一个函数调用链。
内存的数据结构
JavaScript之所以有this
,和内存脱不了关系。
let obj = {foo:5};
对于这段代码来说,JavaScript到底到底做了什么操作呢?
JavaScript引擎会现在堆里面生成一个对象
{foo:5}
,然后在将它内存的地址赋值给栈里面的obj
变量。
也就是说对于obj
来说,它只保存了对象的地址,当访问对象属性或者方法时,它就是用这个地址找到堆中的对象,然后访问其属性或者方法。
原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。
{
foo: {
[[value]]: 5
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
对于对象的方法来说:
方法是一个函数,而函数在JavaScript是一个对象,那么它保存方式也不难想到,如果这个属性名对应的是一个函数,那么这个属性保存值就是这个函数在内存中的地址。
{
foo: {
[[value]]: 函数的地址
...
}
}
环境变量
JavaScript 允许在函数体内部,引用当前执行环境的其他变量。
function f(){
console.log(x);
}
f();
我们看看上述代码,在f()
中没有x变量,那么它只有去的它所以运行的环境中找x
变量。由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this
就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。也就是你到底想要哪里得x
变量,是之前函数环境中的x
,还是当前环境中的x
。如果是之前环境中的x
,那么就是用console.log(x)
,如果是当前内环境中的,就是用console.log(this.x)
。
回到开头的代码,obj.foo()
是通过obj
找到foo
,所以就是在obj
环境执行。一旦var foo = obj.foo
,变量foo
就直接指向函数本身,所以foo()
就变成在全局环境执行。
其实总的来说,谁调用就指向谁。
this指向情况
阮一峰
大佬帮我们总结一些情况,我又在网络上收集一些情况。
this
是 JavaScript 语言的一个关键字。它是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。
总的来说,this
就是函数运行时所在的环境对象
情况一:纯粹的函数调用(默认绑定)
这是函数的最通常用法,属于全局性调用,因此this
就代表全局对象。
var x = 1;
function f(){
console.log(this.x);
console.log(this);
}
f();// 1 window对象
但是如果使用严格模式
'use strict'
var x = 1;
function f(){
console.log(this.x);
console.log(this);
}
f(); // 报错
这样就会报错,因为严格模式下,全局环境下的函数不再指向window,它的值是undefined
情况二:作为对象方法的调用(隐式绑定)
函数还可以作为某个对象的方法调用,这时this
就指这个上级对象。
function f(){
console.log(this.x);
}
var obj = {
x : 1,
f:f
}
var x = 2;
obj.f();//1
情况三:作为构造函数调用(new,优先级最高)
所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,this
就指这个新对象。
var x = 1;
function F(){
this.x = 2;
}
let f = new F();
console.log(f.x);
x;//1,可以看到与全局环境下的x是没有关系的哈
new()的过程大概可以表示为:调用构造函数,在堆内为这个创建一个空对象,并使用this
指向这个空对象,然后执行构造函数语句,最后返回this。(返回this是自动的,不需要在构造函数中写明)。
情况四:apply 、bind,call调用(显示绑定)
这几个函数可以改变函数的指向,如果不填指向谁,那么就指向window(也就是全局对象)。
可以使用apply,和call实现bind(),也就是返回一函数。
bind()还可以用来实现柯里化函数(先传如部分参数)。
总结一下上面四种情况:
谁调用我,我就指向谁。我在那个环境运行,我们指向谁。
情况五:箭头函数
箭头函数它没有自己的this,它this就是上下文中定义的this,也就是说头函数中所使用的this来自于函数作用域链。网上有些人说箭头函数的this是外层第一个普通函数的this。也就是说它会继承外层函数调用的this绑定。这个其实和ES6之前的let that = this
是相同的。由于这种特性,箭头函数常用语回调函数中。
需要注意的是箭头函数的绑定无法再次修改。
[代码来自]((2条消息) JS中的this指向问题(详细版)_LoveyL0201的博客-CSDN博客)
var div = document.querySelector('div');
var o={
a:function(){
var arr=[1];
//就是定义所在对象中的this
//这里的this—>o
arr.forEach(item=>{
//所以this -> o
console.log(this);
})
},
//这里的this指向window o是定义在window中的对象
b:()=>{
console.log(this);
},
c:function() {
console.log(this);
}
}
div.addEventListener('click',item=>{
console.log(this);//this->window 这里的this就是定义上文window环境中的this
});
o.a(); //this->o
o.b();//this->window
o.c();//this->o 普通函数谁调用就指向谁
情况六:事件绑定中的this
this指向事件源,也就是谁绑定了这个事件。应该就是隐式绑定。
情况七:定时器中的this
定时器中的this->window,因为定时器中采用回调函数作为处理函数,而回调函数的this->window。
绑定优先级
new > 显式绑定 > 隐士绑定 > 默认绑定 。
总结(来自《你不知道的JavaScript》)
- 由 new 调用?绑定到新创建的对象。
- 由 call 或者 apply(或者 bind)调用?绑定到指定的对象。
- 由上下文对象调用?绑定到那个上下文对象。
- 默认:在严格模式下绑定到 undefined,否则绑定到全局对象。
一定要注意,有些调用可能在无意中使用默认绑定规则(比如说赋值的时候)。
如果想“更安全”地忽略 this 绑 定,你可以使用一个 DMZ 对象,比如 ø = Object.create(null)《这样创建不会有原型》,以保护全局对象。
ES6 中的箭头函数并不会使用四条标准的绑定规则,而是根据当前的词法作用域来决定 this,具体来说,箭头函数会继承外层函数调用的 this 绑定(无论 this 绑定到什么)。