一、JavaScript 数据类型

JavaScript 数据类型有两种,分别是基本数据类型和引用数据类型。

  • 基本数据类型
    • Number
    • String
    • Boolean
    • Undefined
    • Null
    • Symbol (ES6 新增,表示独一无二的值)
    • Biginit(ES10新增, 它提供了一种方法来表示大于 2- 1 的整数
  • 引用数据类型
    • Object
    • Function
    • Array

我们来详细了解一下这两种数据类型的特点

1.基本数据类型

值不可变

基本类型的值是不可变

  1. let name = 'javascript';
  2. name.toUpperCase(); // 'JAVASCRIPT'
  3. console.log(name); // 'javascript'

但是我们不是经常有 ++a 这种操作吗?比如

  1. let a = 1
  2. console.log(++a) // '2'

其实这个时候并不是 a 指向的 1 直接加了 1,而是 新建了一个 1+1 = 2 的值,再将 a 指向这个新建出来的 2,原来的那个 1 并没有发生改变,留由垃圾回收机制处理。也就是说不是 a 指向的值发生了改变,而是 a 变量指针指向了一个新的值,这和「基本类型的值是不可变的」这句话并不矛盾。

数据存放在栈区

原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储。

2.引用数据类型

值是可变的

  1. let a = { age : 20 };
  2. a.age = 21
  3. console.log(a.age) //21

同时保存在栈内存和堆内存

引用数据类型占据空间大,大小不固定,储存在堆内存,但是指向该引用数据类型的变量指针「a」是储存在栈内存中。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

二、数据的赋值

基本数据类型的赋值为复制了一个新的值,对新变量的修改不会影响原变量

  1. let a = 1;
  2. let b = a;
  3. b++
  4. console.log(a) // 1
  5. console.log(b) // 2

三、检验数据类型

typeof

这应该初学者首次接触的类型判断方法了,它返回一个表示数据类型的字符串,返回结果包括:

  • number
  • boolean
  • string
  • symbol
  • bigint
  • object
  • undefined
  • function

但是不能判断 array 和 null

  1. typeof 10n;
  2. // bigint 正确
  3. typeof Symbol();
  4. // symbol 正确
  5. typeof '';
  6. // string 正确
  7. typeof 1;
  8. // number正确
  9. typeof true;
  10. //boolean 正确
  11. typeof undefined;
  12. //undefined 正确
  13. typeof new Function();
  14. // function 正确
  15. typeof null;
  16. //object 无效
  17. typeof [] ;
  18. //object 无效
  19. typeof new Date();
  20. //object 无效
  21. typeof new RegExp();
  22. //object 无效

typeof 缺点

可以看到 Date 和 RegExp 对象都只是被是被成了 object 对象,也就是说除 function 以外的对象都会被识别成 object ,这样显然是不合理的,这时就需要 instanceof 来进行判断。

instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。 简单来说就是 instanceof 是用来判断 A 是否为 B 的实例,表达式为

A (object) instanceof B (constructor) 如果A是B的实例,则返回true,否则返回 false。

  1. // 定义构造函数
  2. function C(){}
  3. function D(){}
  4. var o = new C();
  5. o instanceof C; // true,因为 Object.getPrototypeOf(o) === C.prototype
  6. o instanceof D; // false,因为 D.prototype 不在 o 的原型链上
  7. o instanceof Object; // true,因为 Object.prototype.isPrototypeOf(o) 返回 true
  8. C.prototype instanceof Object // true,同上
  9. C.prototype = {};
  10. var o2 = new C();
  11. o2 instanceof C; // true
  12. o instanceof C; // false,C.prototype 指向了一个空对象,这个空对象不在 o 的原型链上.
  13. D.prototype = new C(); // 继承
  14. var o3 = new D();
  15. o3 instanceof D; // true
  16. o3 instanceof C; // true 因为 C.prototype 现在在 o3 的原型链上

现在就可以判断不同类别的对象了

  1. [] instanceof Array; //true
  2. {} instanceof Object;//true
  3. new Date() instanceof Date;//true
  4. new RegExp() instanceof RegExp//true

instanceof 的缺点

1.是否处于原型链上的判断方法不严谨

instanceof 方法判断的是是否处于原型链上,而不是是不是处于原型链最后一位,所以会出现下面这种情况:

  1. var arr = [1, 2, 3];
  2. console.log(arr instanceof Array) // true
  3. console.log(arr instanceof Object); // true
  4. function fn(){}
  5. console.log(fn instanceof Function)// true
  6. console.log(fn instanceof Object)// true

因为所有原型链的尽头都是 object,所以就造成了这种状况。当你自定义了一个类A,并且继承了一个原生类B,这个时候检测结果未必准确

  1. A instanceof A // true
  2. A instanceof B // true
  3. A instanceof Object true

2.无法判断字面量方式创建的基本数据类型

对于基本数据类型来说,字面量方式创建出来的结果和实例方式创建的是有一定区别

  1. console.log(1 instanceof Number)//false
  2. console.log(new Number(1) instanceof Number)//true

从严格意义上来讲,只有实例创建出来的结果才是标准的对象数据类型值,也是标准的Number这个类的一个实例;对于字面量方式创建出来的结果是基本的数据类型值,不是严谨的实例,但是由于JS的松散特点,导致了可以使用Number.prototype上提供的方法。

3. 无法检测 null 和 undefined

对于特殊的数据类型null和undefined,他们的所属类是Null和Undefined,但是浏览器把这两个类保护起来了,不允许我们在外面访问使用。
image.png

constructor

constructor 作用和 instanceof 非常相似。但 constructor 检测 Object 与 instanceof 不一样,还可以处理基本数据类型的检测。

  1. var aa=[1,2];
  2. console.log(aa.constructor===Array);//true
  3. console.log(aa.constructor===RegExp);//false
  4. console.log((1).constructor===Number);//true
  5. var reg=/^$/;
  6. console.log(reg.constructor===RegExp);//true
  7. console.log(reg.constructor===Object);//false

constructor的缺点

1.无法检测 null 和 undefined

null 和 undefined 是无效的对象,因此是不会有 constructor 存在的,这两种类型的数据需要通过其他方式来判断。

2.不稳定

函数的 constructor 是不稳定的,这个主要体现在把类的原型进行重写,在重写的过程中很有可能出现把之前的 constructor 给覆盖了,这样检测出来的结果就是不准确的,由此可知 instanceof 同样也存在这个问题。

  1. function Fn(){}
  2. Fn.prototype = new Array()
  3. var f = new Fn
  4. console.log(f.constructor)//Array

Object.prototype.toString.call()

这就是大名鼎鼎的全能方法,最准确最常用,据 MDN 描述 每个对象都有一个 toString() 方法,当该对象被表示为一个文本值时,或者一个对象以预期的字符串方式引用时自动调用。默认情况下,toString() 方法被每个 Object 对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 “[object type]”,其中 type 是对象的类型。 在 Number、String,Boolean,Array,RegExp、Date、Function 等对象上 toString() 方法都是被重写过了的,会按照一定的规则返回字符串。但是在 object 对象上,这个方式是返回当前方法执行的主体(方法中的this)所属类的详细信息即”[object Object]”,其中第一个object代表当前实例是对象数据类型的(这个是固定的 onject),第二个Object代表的是this所属的类型。
为了每个对象都能通过 Object.prototype.toString() 来检测,需要以 Function.prototype.call() 或者 Function.prototype.apply() 的形式来调用,传递要检查的对象作为第一个参数,称为 thisArg。

  1. Object.prototype.toString.call('') ; // [object String]
  2. Object.prototype.toString.call(1) ; // [object Number]
  3. Object.prototype.toString.call(1n); // [object BigInt]
  4. Object.prototype.toString.call(true) ; // [object Boolean]
  5. Object.prototype.toString.call(undefined) ; // [object Undefined]
  6. Object.prototype.toString.call(null) ; // [object Null]
  7. Object.prototype.toString.call(new Function()) ; // [object Function]
  8. Object.prototype.toString.call(new Date()) ; // [object Date]
  9. Object.prototype.toString.call([]) ; // [object Array]
  10. Object.prototype.toString.call(new RegExp()) ; // [object RegExp]
  11. Object.prototype.toString.call(new Error()) ; // [object Error]
  12. Object.prototype.toString.call(document) ; // [object HTMLDocument]
  13. Object.prototype.toString.call(window) ; //[object global] window是全局对象global的引用

总结

判断方法 缺点
typeof 不能判断 null 和区分 array/Date/RegExp 等
instanceof 无法检测 null 和 undefined,未必准确,无法判断字面量方式创建的基本数据类型
constructor 无法检测 null 和 undefined,未必准确
Object.prototype.toString.call()