# JavaScript 数据类型 及 判断

# 数据类型

JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。

JavaScript有八种基本类型 StringNumberBooleanNullUndefinedSymbolBigIntObject,前七种为基本数据类型,Object为引用类型。

其中 Symbol 和 BigInt 是ES6 中新增的数据类型:

  • Symbol 代表创建后独一无二且不可变的数据类型,它主要是为了解决可能出现的全局变量冲突的问题。
  • BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数,使用 BigInt 可以安全地存储和操作大整数,即使这个数已经超出了 Number 能够表示的安全整数范围。

这些数据可以分为原始数据类型和引用数据类型:

  • 栈:原始数据类型(Undefined、Null、Boolean、Number、String、Symbol)
  • 堆:引用数据类型(对象、数组和函数)

两种类型的区别在于 存储位置的不同:

  • 原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
  • 引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:

  • 在数据结构中,栈中数据的存取方式为先进后出。
  • 堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。

在操作系统中,内存被分为栈区和堆区:

  • 栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。

# 判断

# typeof

typeof 对于基本类型,除了 null 都可以显示正确的类型

typeof 1            // 'number'
typeof NaN;         // "number"
typeof '1'          // 'string'
typeof undefined    // 'undefined'
typeof true         // 'boolean'
typeof Symbol()     // 'symbol'
typeof b            // b 没有声明,但是还会显示 undefined
typeof BigInt(11)   // bigint

NaN 是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即 x === x 不成立)的值。而 NaN !== NaN 为 true。

typeof null // 'object'

在 JavaScript 最初的实现中,JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。对象的类型标签是 0。由于 null 代表的是空指针(大多数平台下值为 0x00),因此,null 的类型标签是 0,typeof null 也因此返回 "object"。

typeof 对于对象,除了函数都会显示 `object

typeof [] // 'object'
typeof {} // 'object'
typeof console.log // 'function'

# instanceof

instanceof 可以正确判断 对象 的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型

console.log(2 instanceof Number);                    // false
console.log(true instanceof Boolean);                // false 
console.log('str' instanceof String);                // false 
console.log(null instanceof Object);                 // false
 
console.log([] instanceof Array);                    // true
console.log(function(){} instanceof Function);       // true
console.log({} instanceof Object);                   // true

可以看到,instanceof 只能正确判断引用数据类型,而不能判断基本数据类型。

实现原理

instanceof 运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype 属性。

function instanceof(left, right) {
    // 获取构造函数的 prototype 对象。
    let prototype = right.prototype
    // 获得对象的原型。也可以使用 Object.getPrototypeOf(left) 方法
    left = left.__proto__
    // 判断对象的类型是否等于类型的原型
    while (true) {
    	if (left === null)
    		return false
    	if (prototype === left)
    		return true
      // 如果没有找到,就继续从其原型上找
    	left = left.__proto__
    }
}

# constructor

console.log((2).constructor === Number);     // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array);     // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object);    // true

constructor有两个作用,一是判断数据的类型,二是对象实例通过 constrcutor 对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor就不能用来判断数据类型了:

function Fn(){};
 
Fn.prototype = new Array();
 
var f = new Fn();
 
console.log(f.constructor === Fn);    // false
console.log(f.constructor === Array); // true

# Object.prototype.toString.call()

Object.prototype.toString.call() 使用 Object 对象的原型方法 toString 来判断数据类型:

var a = Object.prototype.toString;
 
console.log(a.call(2));             // [object Number]
console.log(a.call(true));          // [object Boolean]
console.log(a.call('str'));         // [object String]
console.log(a.call([]));            // [object Array]
console.log(a.call(function(){}));  // [object Function]
console.log(a.call({}));            // [object Object]
console.log(a.call(undefined));     // [object Undefined]
console.log(a.call(null));          // [object Null]


var a1 = [2,3];

console.log(a1.toString());         // 2,3
console.log(a.call(a1));            // [object Array]

同样是检测对象 obj 调用 toString 方法,obj.toString() 的结果和 Object.prototype.toString.call(obj) 的结果不一样,这是为什么?

这是因为 toStringObject 的原型方法,而 Arrayfunction类型作为 Object 的实例,都重写了 toString 方法。不同的对象类型调用 toString 方法时,根据原型链的知识,调用的是对应的重写之后的 toString 方法( function 类型返回内容为函数体的字符串,Array 类型返回元素组成的字符串),而不会去调用 Object 上原型 toString 方法(返回对象的具体类型),所以采用obj.toString() 不能得到其对象类型,只能将 obj 转换为字符串类型;因此,在想要得到对象的具体类型时,应该调用 Object 原型上的 toString 方法。

# 常见面试题

# null 和 undefined 区别

首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。

undefined 代表的含义是未定义,null 代表的含义是 空对象。一般变量声明了但还没有定义的时候会返回 undefined,null 主要用于赋值给一些可能会返回对象的变量,作为初始化。

undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。

当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。

# 判断数组的方式有哪些

  • 通过 Object.prototype.toString.call() 做判断

    Object.prototype.toString.call(obj).slice(8,-1) === 'Array';

  • 通过原型链做判断

    obj.proto === Array.prototype

  • 通过 ES6Array.isArray() 做判断

    Array.isArrray(obj)

  • 通过 instanceof 做判断

    obj instanceof Array

  • 通过 Array.prototype.isPrototypeOf

    Array.prototype.isPrototypeOf(obj)

# typeof null 的结果为什么是 Object?

在 JavaScript 第一个版本中,所有值都存储在 32 位的单元中,每个单元包含一个小的 类型标签(1-3 bits) 以及当前要存储值的真实数据。类型标签存储在每个单元的低位中,共有五种数据类型:

000: object   - 当前存储的数据指向一个对象。
  1: int      - 当前存储的数据是一个 31 位的有符号整数。
010: double   - 当前存储的数据指向一个双精度的浮点数。
100: string   - 当前存储的数据指向一个字符串。
110: boolean  - 当前存储的数据是布尔值。

如果最低位是 1,则类型标签标志位的长度只有一位;如果最低位是 0,则类型标签标志位的长度占三位,为存储其他四种数据类型提供了额外两个 bit 的长度。

有两种特殊数据类型:

  • undefined的值是 (-2)30(一个超出整数范围的数字);
  • null 的值是机器码 NULL 指针(null 指针的值全是 0)

那也就是说 null 的类型标签也是 000,和 Object 的类型标签一样,所以会被判定为 Object。

# isNaN 和 Number.isNaN 函数的区别?

Number.isNaN() === isNaN() // fasle

  • 函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

  • 函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

上次更新: 11/6/2024, 4:10:52 PM