JavaScript引用数据类型和构造函数的秘密

525 阅读5分钟

前言

今天我们就来聊聊JavaScript中的引用数据类型和构造函数,引用数据类型和原始类型有什么区别呢?构造函数创造变量时,前面为什么要加new呢?

JavaScript中的数据类型分为两大类:原始数据类型(Primitive Data Types)引用数据类型(Reference Data Types)。

  • 原始数据类型
  1. 字符串(String) : 用于表示文本,用单引号(')或双引号(")括起来。

    let name = 'John';
    
  2. 数字(Number) : 用于表示数值,包括整数和浮点数。

    let age = 25;
    
  3. 布尔值(Boolean) : 用于表示真(true)或假(false)。

    let isStudent = true;
    
  4. 未定义(Undefined) : 表示一个变量已声明但未赋值。

    let variable;
    
  5. 空值(null) : 表示一个变量没有值或对象为空。

     let emptyValue = null;
    

引用数据类型

  1. 对象(Object) : 用于表示复杂数据结构,可以包含多个属性和方法。

    const person = {
      name: 'Alice',
      age: 30,
    };
    
  2. 数组(Array) : 用于存储多个值的有序列表。

    const numbers = [1, 2, 3, 4, 5];
    
  3. 日期(Date) : 用于表示日期和时间。

    let today = new Date();
    

还有函数等等引用数据类型,我们下次再介绍。

引用数据类型跟原始数据类型有什么区别呢? 这里我们拿对象来解释一下,我们先来看一道题。

let obj = {
    name: '菌菌'
}
let lw = obj
obj.name = '来颗奇趣蛋'
console.log(lw.name);

如果不太了解引用数据类型的小伙伴们,这里可能就会觉得输出菌菌了。让我们来看看输出结果:

image.png

我们可以看到,这里输出了来颗奇趣蛋。这是为什么呢,我们修改obj.name,为什么lw.name也被修改了呢?这其实就是引用数据类型和原始数据类型的区别了,我们可以用两句话来概括一下。

  • 原始类型的值是存在调用栈中的
  • 引用类型的值是存在堆当中,但是会将引用地址存在栈中

这里我们画张图来理解一下:(图画的不是很好,请见谅)

image.png

实际上,引用数据的值存放在堆区,也就是图中黄色框框所在的位置,堆区中存在着一段连续的地址,而这些地址又对应引用数据的值,如图中的1005,而栈区中变量存储的是堆区中的地址,就如图中obj = 1005,所以通过这些地址值,我们就可以访问堆区中的值,这里也可以叫指针

为什么改变obj.name会影响到lw.name呢?从图中观察到,obj和lw存储着相同的指向堆区的地址。当let obj = {...}时,obj会将变量的值的地址存储进去,这里为obj = 1005,而真正的变量值存储在堆区。当赋值语句lw = obj时,lw被赋值为obj相同的地址,lw = 1005.

而当执行到obj.name = '来颗奇趣蛋'时,先在栈区寻找变量obj,发现obj存储的是一段地址,然后会顺着obj存储的地址来到堆区,将堆区中name的值改为'来颗奇趣蛋'.所以当输出lw.name时,发现lw的值为1005,为地址,顺着地址找到了堆区中name的值,但此时name的值已经被修改为'来颗奇趣蛋',所以输出'来颗奇趣蛋',引用数据类型共享变量。

构造函数

我们想创建一个对象,有几种方法呢?

  1. 对象字面量(对象直接量)
var obj = {} //对象字面量||对象直接量
  1. 构造函数
 let obj = new Object()  //构造函数
  1. 自定义构造函数

我们来写一个构造车对象的构造函数

function Car(name,color,size) {
    this.name = name
    this.color = color
    this.size = size
}

let car1 = new Car('BMW','red','long')   // 实例对象
let car2 = new Car('奔驰','green','short')

自定义构造函数跟构造函数方法差不多,只是自定义构造函数是我们人为的写出一个方法,而构造函数可以直接使用。我们将用构造函数声明出的对象,叫做实例对象。我们输出一下用自定义构造函数声明出的两个对象:

image.png 构造函数就像工厂,可以批量化生成对象

在上面的例子中,我们可以发现,使用构造函数创建对象时,必须要在前面加上一个new,那new的作用是什么呢?

构造函数中new的作用

我们首先可以试试,当使用构造函数时,不加new会怎么样:

function Car(name,color,size) {
    this.name = name
    this.color = color
    this.size = size
}

let car = Car('BMW','red','short')

输出一下 image.png

我们可以发现,当我们输出car时,它的值为undefined,这里我们可以想一下,因为这个构造函数里面并没有返回值,那我们输出创建的对象时,应该是找不到这个值的。那为什么在构造函数创建对象时,在前面加一个new时,就可以创建一个有值的对象呢?

其实,当我们加入new关键字时,它会在构造函数内创建一个this对象,然后将属性传入this对象里面,最后return this对象,所以就有值了

function Car(name,color,size) {
    this.name = name
    this.color = color
    this.size = size

    // let this = {
    //     name: name,
    //     color: color
    //     size: size
    // }
    // return this
}

let car =  new Car('BMW', 'red', 'short')
console.log(car);

而这里的this对象其实就是由构造函数创造的实例对象,关于this小伙伴们可以先这样理解,后面的文章我们会仔细分析。所以,当我们在构造函数前加入new关键字时,在函数内部就会有一个返回值,所以我们创造出的实例对象是有值的。

总结

  • 原始类型的值是存在调用栈中的
  • 引用类型的值是存在堆当中,但是会将引用地址存在栈中
  • new会在函数内部生成一个this对象,最后返回this对象

今天的内容就到这啦,如果你觉得小编写的还不错的话,或者对你有所启发,请给小编一个辛苦的赞吧