原始类型与引用类型

[toc]

JS 数据类型

每种编程语言都具有内建的数据类型,而根据使用数据的方式从两个不同的维度将语言进行分类。

动态/静态:
动态类型:运行过程中需要检查数据类型
静态类型:使用之前就需要确认其变量数据类型
强/弱:
强类型:不支持隐式类型转换
弱类型:支持隐式类型转换
隐式类型转换 : 在赋值过程中, 编译器会把 int 型的变量转换为 bool 型的变量

每个变量只不过是一个用于保存任意值的命名占位符。

数据类型分类

六种数据类型:

数值 (Number)、字符串 (String)、布尔 (Boolean)、空 (Null)、未定义 (Undefined)、对象 (Object)。
数据类型分为原始类型与引用类型两大类,除了对象以外其他五个基础数据类型都是原始类型。

ECMAScript 有 8 种数据类型

  1. Undefined
  2. Null
  3. Boolean
  4. String
  5. Number
  6. Symbol (ES6 新增)
  7. BigInt (ES2020 新增)
  8. Object (基本引用类型、)
    根据数据存储位置的不同,我们将 JS 数据类型分为两大类:

基本数据类型(primary) 存放在栈内存中,类型 1-7
复杂数据类型/引用类型 存放在堆内存中, 类型 8

1
2
3
4
let sy = Symbol('789'),
bi = 789n;
typeof sy; // "symbol"
typeof bi; // "bigint"

特例分析: null 值表示一个空对象指针。所以针对 typeof null 返回了一个”object”。

原始类型

数值 (Number)、字符串 (String)、布尔 (Boolean)、空 (Null)、未定义 (Undefined)。

引用类型

对象 (Object)又有 Array、Date、Math 等等。

原始类型与引用类型的区别

  1. 赋值的区别
    原始类型赋值,引用类型赋的是引用。

    原始类型的赋值方式:

    1
    2
    3
    4
    5
    let str1 = "Hello";
    let str2 = str1; // str2: hello
    str1 = "World";
    console.log(str1) // World
    console.log(str2) // Hello

    引用类型的赋值方式与原始类型的不一样

    1. obj1 可以链接到内存空间{name: “John”}(obj1 只是引用{name: “John”}),
    2. 把 obj1 赋值给 obj2,就是可以让 obj2 链接到 obj1 引用的内存空间{name: “John”},
    3. 当把 Korey 赋值给 obj1.name,修改的是 obj1 引用的内存空间{name: “John”}的 name 属性,也就是说 obj1 引用的内存空间{name: “John”}变成了 obj1 引用的内存空间{name: “Korey”}。
    4. 当输出 obj1 的 name 时访问的是 obj1 引用的内存空间,输入 obj2 的 name 访问的也是 obj2 引用的内存空间,结果是一样的是因为 obj1 和 obj2 引用的内存空间是一样的。
    1
    2
    3
    4
    5
    6
    7
    let obj1 = {
    name: "John"
    };
    let obj2 = obj1;
    obj1.name = "Korey";
    console.log(obj1.name); // Korey
    console.log(obj2.name); // Korey
  2. 比较的区别
    原始类型比较的是值,引用类型比较的是是否指向同一个对象。

    1
    2
    3
    let str1 = "hello";
    let str2 = "hello";
    console.log(str1 === str2); // true

    引用类型的比较,比较的是是否指向同一个对象。

    • 例 1 类比:班里出现同名的情况,同名不同人。

      1
      2
      3
      4
      5
      6
      7
      let obj1 = {
      name: "John"
      };
      let obj2 = {
      name: "John"
      };
      console.log(obj1 === obj2); // false
      1. obj1 引用的是第一个内存空间{name: “John”},obj2 引用的是第二个内存空间{name: “John”}
      2. 因为它们引用的内存空间不一样所以是 false
    • 例 2

      1
      2
      3
      4
      5
      let obj1 = {
      name: "John"
      };
      let obj2 = obj1;
      console.log(obj1 === obj2); // true
      1. obj1 引用的是内存空间{name: “John”}
      2. 把 obj1 赋值给 obj,让 obj2 链接到 obj1 引用的内存空间{name: “John”}
      3. 因为它俩引用的是同一个内存空间,所以是 true
  3. 传参的区别
    原始类型的传参不管怎样传都不会影响到外面的值的变化。
    引用类型的传参,不管在内部还是外部,它指向都是同一个内存空间,同一个对象。

    1. 把 number 传到 fun 函数里面,然后把 100 赋值给 number,所以 fun 里的 x 是 100,但不影响外面的 number。

      1
      2
      3
      4
      5
      6
      7
      function fun(x) {
      x = 100;
      console.log(`x的值是${x}`);
      }
      let number = 10;
      fun(number); //x的值是100
      console.log(`number的值是${number}`); //number的值是10
    2. 因为函数内部的受到影响了外部的也会被影响。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      let obj = {
      name: "John"
      };

      function fun(o) {
      o.name = "Koke";
      console.log(`o的名字是${o.name}`); // Koke
      }
      fun(obj);
      console.log(`obj的名字是${obj.name}`); // Koke

原始类型与引用类型的类型检测

  1. 原始数据类型检测:typeof(值)
    经常用来检测一个变量是不是最基本的数据类型
    对于 null 来说,虽然它是基本类型,但是会显示 object,这是一个存在很久了的 Bug
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let num = 100;
    let str = "Hello World";
    let und; // undefined 不用赋值就是undefined
    let nu = null;
    let bol = true;
    console.log(typeof (num)); // number
    console.log(typeof (str)); // string
    console.log(typeof (und)); // undefined
    console.log(typeof (nu)); // !! 不是null 是object
    console.log(typeof (bol)); // boolean
    引用类型用 typeof()检测的结果都是 object,所以没啥用,可以用 值 instanceof(类型)来检测。
    1
    2
    3
    4
    5
    6
    let arr = [1, 2, 3];
    let reg = /123/;
    let date = new Date();
    console.log(typeof (arr)); // object
    console.log(typeof (reg)); // object
    console.log(typeof (date)); // object
  2. 引用数据类型检测:值 instanceof(类型)
    用来判断某个构造函数的 prototype 属性所指向的对象是否存在于另外一个要检测对象的原型链上。简单说就是判断一个引用类型的变量具体是不是某种类型的对象。Array RegExp Date … extends Object; 它们都继承成于 Object,所以都是 true。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    let arr = [1, 2, 3];
    let reg = /123/;
    let date = new Date();
    console.log(arr instanceof Array); // true
    console.log(reg instanceof RegExp); // true
    console.log(date instanceof Date); // true
    // Array RegExp Date ... extends Object; 它们都继承成于Object 所以都是true
    console.log(date instanceof Object); // true
    console.log(reg instanceof Object); // true
    console.log(date instanceof Object); // true
    // !!! console.log(null instanceof Object); // false
  3. Object.prototype.toString.call(xx)
    若参数(xx)不为 null 或 undefined,则将参数转为对象,再作判断。转为对象后,取得该对象的 [Symbol.toStringTag] 属性值(可能会遍历原型链)作为 tag,然后返回 “[object “ + tag + “]” 形式的字符串。

针对基本数据类型,通过装箱过程转为对象类型。

1
2
3
4
Object.prototype.toString.call(null) //[ojbect Null]
Object.prototype.toString.call(undefined) //[object Undefined]
Object.prototype.toString.call(true) // [object Boolean]
Object.prototype.toString.call(() => {}) // [object Function]

通过 Object.prototype.toString 可以将数据类型很容易的分开。但是,每次进行判断的时候,多了一堆额外的信息。所以,我们可以对该方法进行改进。

1
2
3
4
5
6
7
8
9
10
function getDataType(type) {
return Object.prototype.toString.call(type)
.split(' ')[1]
.slice(0, -1)
.toLocaleLowerCase();
}
getDataType(null) //null
getDataType(undefined) // undefined
getDataType(true) // boolean
getDataType(() => {}) // function

例 1 数组存储学生列表

  1. 数组 StudentList
  2. 点击按钮,将表单中【姓名】和【年龄】赋值给 Student 对象,并 push 到 StudentList 中。
1
2
3
4
5
<div>
<input type="text" class="name" placeholder="姓名">
<input type="text" class="age" placeholder="年龄">
<button class="btn">添加</button>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
let inputName = document.querySelector(".name");
let inputAge = document.querySelector(".age");
let btn = document.querySelector(".btn");

// let studentList = [];
// btn.onclick = function () {
// let student = {};
// student.name = inputName.value;
// student.age = inputAge.value;
// studentList.push(student);
// console.log(studentList);
// }
let studentList = [];
class Student {
constructor(name, age) {
this.name = name;
this.age = age;
}
}

btn.onclick = function () {
// 每点击一次添加就会new一个新的学生对象
let student = new Student(inputName.value, inputAge.value);
studentList.push(student);
console.log(studentList);
}

课后练习

  • 制作一个学生列表,要求如下:
    1. 在表单中输入【姓名】和【年龄】,点击添加【添加】按钮,创建 Student 对象。
    2. 将 Student 对象添加到列表中(ul li)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div>
<!-- select下拉框,name属性用于服务器-->
<select name="" id="personType">
<!-- option元素定义下拉列表中的一个选项(一个条目),value属性:定义送往服务器的选项值。 -->
<option value="0">Teacher</option>
<option value="1">Student</option>
</select>
<input type="text" class="name" placeholder="Name">
<input type="text" class="age" placeholder="Age">
<button class="btn">添加</button>
<h1>Student List</h1>
<ul class="student-list">
</ul>
<h1>Teacher List</h1>
<ul class="teacher-list">
</ul>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
let ul = document.querySelector('.student-list');
let inputName = document.querySelector('.name');
let inputAge = document.querySelector('.age');
let btn = document.querySelector('.btn');

// class Student{
// constructor(name,age){
// this.name = name;
// this.age = age;
// }
// }
// btn.onclick = function () {
// let name = inputName.value;
// let age = inputAge.value;
// let student = new Student(name, age);
// // 创建元素节点
// let li = document.createElement(`li`);
// // 创建文本节点
// let txt = document.createTextNode(`${student.name}:${student.age}`);
// // 在ul中添加li元素
// ul.appendChild(li);
// // 在li中追加txt文本
// li.appendChild(txt);
// }

let ul1 = document.querySelector('.teacher-list');
let x = document.getElementById("personType");
class shcoolPerson {
constructor(name, age, type) {
this.name = name;
this.age = age;
this.type = type;
}
}
btn.onclick = function () {
let name = inputName.value;
let age = inputAge.value;
let type = x[x.selectedIndex].text;
addFun(name, age, type);
}

function addFun(name, age, type) {
let person = new shcoolPerson(name, age, type);
// 创建元素节点
let li = document.createElement(`li`);
// 创建文本节点
let txt = document.createTextNode(`${person.name}:${person.age}`);
if (x.selectedIndex == 1) {
// 在ul中添加li元素
ul.appendChild(li);
console.log("Add a student.");
} else if (x.selectedIndex == 0) {
ul1.appendChild(li);
console.log("Add a teacher.");
}
// 在li中追加txt文本
li.appendChild(txt);
}

欢迎关注我的其它发布渠道