概述

一个变量可以存放两种类型的值,基本类型的值(primitive values)和引用类型的值(reference values)

基本数据类型值指的是简单的数值段,而引用数据类型值指那些可能由多个值构成的对象

基本类型的特点:

  • 基本类型是保存在栈内存(stack)中的
  • 基本类型的值是不可变的
  • 基本类型的比较是值的比较

引用类型的特点:

  • 而引用类型是保存在堆内存中的
  • 引用类型的值是可变的
  • 引用类型的比较是引用的比较

Javascript 中有 7 种基本数据类型:

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • BigInt
  • Symbol(ECMAScript 2016新增)

除上面的基本数据类型外,剩下的就是引用类型了,统称为 Object 类型。细分的话,有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型等

基本类型

基本类型是保存在栈内存中的,所以可以操作保存在变量中的实际的值
**
假设有以下几个基本类型的变量:

  1. var name = 'jozo';
  2. var city = 'guangzhou';
  3. var age = 22;

那么它的存储结构如下图:

image.png

栈区包括了 变量的标识符和变量的值

示例:

  1. var a = 10;
  2. var b = a; b = 20;
  3. console.log(a); // 10

上面示例中,b 获取值是 a 值的一份拷贝,虽然两个变量的值是相等,但是两个变量保存两不同的基本数据类型值。b 只是保存了 a 复制的一个副本,所以当 b 的值改变时,a 的值依然是 10

下图演示了基本数据类型赋值的过程:

基本类型和引用类型的区别 - 图2

基本类型的值是不可变的

  1. var name = "zs";
  2. name.toUpperCase(); // ZS
  3. console.log(name); // zs

会发现原始的name并未发生改变,而是调用了 toUpperCase() 方法后返回的是一个新的字符串

基本类型虽然也可以添加属性和方法,也不会报错,但是添加之后却是无法访问的

  1. var a = 12;
  2. a.name = "myname";
  3. a.method = function() {};
  4. console.log(a.name); // undefined
  5. console.log(a.method); // undefined

基本类型的比较是值的比较,只有在它们的值相等的时候它们才相等
**

  1. var a = 1;
  2. var b = true;
  3. console.log(a == b); // true
  4. console.log(a === b); // false

上面 a 和 b 的数据类型不同,但是也可以进行值的比较,这是因为在 == 比较之前,自动进行了数据类型的隐式转换

引用类型

引用数据类型是保存在堆内存中的对象,JavaScript 不允许访问内存中的位置,在操作对象时,实际上是在操作对象的引用而不是实际的对象,而这个引用和存储该对象的变量都保存在栈区。通过这个引用地址可以快速查找到保存在堆内存中的对象。但在为对象添加属性时,操作的是实际的对象

*注:在很多语言中,字符串以对象的形式来表示,因此被认为是引用类型。ECMAScript 放弃了这一传统

假设有几个引用类型的变量:

  1. var person1 = {name:'jozo'};
  2. var person2 = {name:'xiaom'};
  3. var person3 = {name:'xiaoq'};

则这三个对象的在内存中保存的情况如下图:

image.png

示例:

  1. var obj1 = new Object();
  2. var obj2 = obj1;
  3. obj2.name = "zs";
  4. console.log(obj1.name); // zs

上面示例中,当把 obj1 赋值给 obj2 时,实际上是把 obj1 的引用(堆内存地址)赋值给了 obj2,所以它们同时指向了一个相同的对象。此时操作 obj2 相当于操作 obj1

下图演示了引用数据类型赋值过程:

基本类型和引用类型的区别 - 图4

引用类型的值是可变的

  1. var person = {};//创建个控对象 --引用类型
  2. person.name = 'jozo';
  3. person.age = 22;
  4. person.sayName = function(){console.log(person.name);}
  5. person.sayName();// 'jozo'
  6. delete person.name; //删除person对象的name属性
  7. person.sayName(); // undefined

上面代码说明引用类型可以拥有属性和方法,并且是可以动态改变的

引用类型的比较是引用的比较

  1. var person1 = {};
  2. var person2 = {};
  3. console.log(person1 == person2); // false

person1 和 person2 在堆内存中地址是不同的,所以结果是 false

传递参数

在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量。在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部,示例:

  1. function addTen(num) {
  2. num += 10;
  3. return num;
  4. }
  5. var count = 20;
  6. var result = addTen(count);
  7. alert(count); //20,没有变化
  8. alert(result); //30

使用引用类型值时:

  1. function setName(obj) {
  2. obj.name = "Nicholas";
  3. }
  4. var person = new Object();
  5. setName(person);
  6. alert(person.name); // "Nicholas"

有很多开发人员错误地认为:在局部作用域中修改的对象会在全局作用域中反映出来,就说明参数是按引用传递的。为了证明对象是按值传递的,示例:

  1. function setName(obj) {
  2. obj.name = "Nicholas";
  3. // 修改了以下部分
  4. obj = new Object();
  5. obj.name = "Greg";
  6. }
  7. var person = new Object();
  8. setName(person);
  9. alert(person.name); // "Nicholas"

实际上,当在函数内部重写 obj 时,这个变量引用的就是一个局部对象了。而这个局部对象会在函数执行完毕后立即被销毁

检测类型

typeof 操作符经常用来检测一个变量是不是基本数据类型

  1. var a;
  2. typeof a; // undefined
  3. a = null;
  4. typeof a; // object
  5. a = true;
  6. typeof a; // boolean
  7. a = 666;
  8. typeof a; // number
  9. a = "hello";
  10. typeof a; // string
  11. a = Symbol();
  12. typeof a; // symbol
  13. a = function(){}
  14. typeof a; // function
  15. a = [];
  16. typeof a; // object
  17. a = {};
  18. typeof a; // object
  19. a = /aaa/g;
  20. typeof a; // object

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上

简单说就是判断一个引用类型的变量具体是不是某种类型的对象

  1. ({}) instanceof Object // true
  2. ([]) instanceof Array // true
  3. (/aa/g) instanceof RegExp // true
  4. (function(){}) instanceof Function // true

如果使用 instanceof 操作符检测基本类型的值,则该操作符始终会返回 false,因为基本类型不是对象。

参考文章