JavaScript基础之this的原理 | 8月更文挑战

190 阅读4分钟

前言

JavaScriptthis 指向问题一直困扰了很多人。虽然我们现在的编码格式都是函数式编程,使用箭头函数,就很少去特意关注它的 this 指向问题。

this原理主要有5种情况

  1. 全局作用域内 -- 当在全部作用域内使用 this 时,它将会指向全局对象,即 window 对象
  2. 函数调用 -- 当在全局作用域内调用函数时,this 也会指向全局对象。
  3. 方法调用 -- this 指向调用该方法的对象。
  4. 调用构造函数 -- 在构造函数内部,this 指向新创建的对象。
  5. 显式的设置this指向 -- 当使用 call 或者 apply 方法时,函数内的 this 将会被显式设置为函数调用的第一个参数

本文就不着重介绍以上五种情况了,主要从数据结构上介绍一下this的情况。

this 的场景和由来

提起 this 下面两段代码我相信都不会陌生。

var obj = {
  foo: function () {}
};
var foo = obj.foo;
调用 foo
// 写法一
obj.foo()
// 写法二
foo()

在上面的代码中,obj.foofoo指向同一个函数,但是执行结果确不一样!!!

接下为对象增加一个属性 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 关键字的历程,希望可以帮到受到困惑的朋友。

如果您觉得对您有所帮助,请鼓励一下吧,给一个 👍

文章如有不当之处,敬请指正!🤝🤝🤝