前言
在 JavaScript 中 this 指向问题一直困扰了很多人。虽然我们现在的编码格式都是函数式编程,使用箭头函数,就很少去特意关注它的 this 指向问题。
this原理主要有5种情况
- 全局作用域内 -- 当在全部作用域内使用
this时,它将会指向全局对象,即window对象 - 函数调用 -- 当在全局作用域内调用函数时,
this也会指向全局对象。 - 方法调用 --
this指向调用该方法的对象。 - 调用构造函数 -- 在构造函数内部,
this指向新创建的对象。 - 显式的设置this指向 -- 当使用
call或者apply方法时,函数内的this将会被显式设置为函数调用的第一个参数
本文就不着重介绍以上五种情况了,主要从数据结构上介绍一下
this的情况。
this 的场景和由来
提起 this 下面两段代码我相信都不会陌生。
var obj = {
foo: function () {}
};
var foo = obj.foo;
调用 foo
// 写法一
obj.foo()
// 写法二
foo()
在上面的代码中,obj.foo和foo指向同一个函数,但是执行结果确不一样!!!
接下为对象增加一个属性 name = Tom
var obj = {
foo: function () { console.log(this.name) },
name: "Tom"
};
var foo = obj.foo;
var name = 'Jarry';
obj.foo() // Tom
foo() // Jarry
从上面我们可以看出,出现不同情况的差异是因为foo函数内部调用了this的原因。
对于obj.foo()来说,obj调用了foo,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境,因此执行的结果必然是不同的。
内存中的数据结构
常见的对象储存结构
数据类型我们主要分为两类:
-
基本数据类型包含:
undefined,null,Boolean,Number、String,
它们的值保存在栈空间,我们通过按值来访问的. -
引用类型:
对象 Object、数组 Array、函数 function 等
对应的值,则必须在堆内存中为这个值分配空间。由于引用类型值得大小不固定(对象有很多属性和方法),因此不能把它们保存在栈内存中。但是内存地址大小是固定的,因此可以将内存地址保存在栈内存中。
简而言之:栈内存中存放的是基本数据类型值,堆内存中存放引用类型值,引用类型值在内存中的地址存放在栈中,也就是我们常说的对象引用(指针)。
var a = 5;
var b = a;
console.log(a+"---"+b); // 5---5
b = 6; // 这里重新给b赋值,a值并没有改变
console.log(a+"---"+b);// 5---6
var obj = { name: "lisi" };
var obj2 = obj; // 这里是引用赋值,obj和obj2指向同一个对象
console.log(obj.name + "---" + obj2.name); // lisi---lisi
obj2.name = "wangwu";
console.log(obj.name + "---" + obj2.name);//wangwu---wangwu
从上面例子可以看出:在变量复制方面,基本类型和引用类型也有所不同,基本类型复制的是值本身,而引用类型复制的是内存地址。
接下来我们回到 this 这个问题上,
var obj = { name: 'Tom' }
我们把一个对象赋值给一个叫obj变量,JS引擎会先在内存里面,生成一个对象{ name: 'Tom' },然后把这个对象的内存地址赋值给变量obj。
在后续读取过程中,变量obj是一个地址引用(reference)。后面如果要读取obj.name,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的name属性。
原始对象的存储实际上是以下形式
{
name: {
[[value]]: ’Tom‘
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
注意:
name属性的值保存在属性描述对象的value属性里面。
函数的数据存储
var obj = { foo: function () { } };
JS引擎会将函数单独保存在内存(堆)中,然后再将函数的引用地址赋值给foo属性的value属性
{
foo: {
[[value]]: 函数的地址
[[writable]]: true
[[enumerable]]: true
[[configurable]]: true
}
}
因此,函数就是一个单独的值了,我们可以任意的地方去使用它。
当使用函数的时候,在不同的地方使用,它就有了不同的运行环境使用的时候就有了不同的执行上下文,这个时候this 就出现了。它的设计目的就是在函数体内部,指代函数当前的运行环境。
此时,我们再回过头来看一下开始的示例代码
function () { console.log(this.name) }
代码中的name永远指的是 执行上下文(this)中的name!!!
结语
所以,文章开篇提到的 5 种情况,最后可以归结为一句话:
谁调用这个函数或方法,this关键字就指向谁
以上就是当初自己学习 this 关键字的历程,希望可以帮到受到困惑的朋友。
如果您觉得对您有所帮助,请鼓励一下吧,给一个 👍
文章如有不当之处,敬请指正!🤝🤝🤝