JavaScript数据结构及类型判断

359 阅读12分钟

javascript-data-types.jpg

编程语言都具有内建的数据结构,但各种编程语言的数据结构常有不同之处。

在计算机科学中,数据结构(Data structure)是一种数据组织、管理和存储格式,通常被选择用于有效访问数据。更准确地说,数据结构是数据值、它们之间的关系以及可应用于该数据值的函数或操作的集合。

  • 数据结构是抽象数据类型(ADT)的基础。 ADT 定义了数据类型的逻辑形式。数据结构实现了数据类型的物理形式。
  • 不同类型的数据结构适合不同类型的应用程序,有些数据结构高度专门用于特定任务。
  • 数据结构提供了一种有效管理大量数据的方法,适用于大型数据库和互联网索引服务等用途。

基本类型

在 JavaScript 中,基本类型(基本数值、基本数据类型)是一种既非对象也无方法或属性的数据。

1. 未定义(Undefined)

在计算中,未定义值(Undefined value)是一种表达式没有正确值的情况,尽管它在语法上是正确的。undefined:一个声明未定义的变量的初始值,或没有实际参数的形式参数。未定义的值不得与空字符串 ''、布尔值false 或其他“空”值混淆。

let x
let y = void 1

void 运算符对给定的表达式进行求值,然后返回 undefined

2. 空引用(Null)

在计算机学科,null 值一直是一个被讨论点,通常来说,空指针或空引用(Null pointer)表示一个不存在或者无效 object 或者地址引用。值 null 特指对象的值未设置。它是 JavaScript 基本类型,在布尔运算中被认为是 falsy

let foo = null

null 是一个字面量,不像 undefined,它不是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用。

undefinednull 的区别

  • undefined 表示变量已声明但未定义,null 表示变量空对象。
  • null 是关键字,undefined 是全局对象的属性。
  • null 转换为数值是 0undefined 转换为数值是 NaN

3. 布尔数据(Boolean)

在计算机科学中,布尔数据类型(Boolean data type)是一种取值仅能为 truefalse 的数据类型,它赋予了编程语言在逻辑上表达 truefalse 的能力。如果没有这种能力,很多功能将无法被实现。

let isFalse = Boolean('')
let isTrue = !~[1, 2].indexOf(0)
if (Math.random() > 0.5) {}

Boolean() 构造函数创建 Boolean 对象。当作为函数调用时,它返回 boolean 类型的原始值。

4. 字符串(String)

在任何计算机编程语言中,字符串(String)传统上是字符序列,可以是文字常量,也可以是某种变量。在 JavaScript 中,字符串是基本类型的其中一个,而 String 是 wrapper 围绕字符串的基本形式。

let hello = 'Hello World'
let template = `<template />`
let str = String(100)
let stringify = JSON.stringify([{}])

String() 构造函数创建 String 对象。当作为函数调用时,它返回 string 类型的原始值。String() 是唯一一种可以将 Symbol 转换为字符串而不抛出异常的方式,因为它非常明确。

String()toString() 的区别

  • String() 可以将任何类型转换为字符串,而 nullundefined 没有 toString 方法。
  • String() 方法是全局对象的一个属性,而 toString() 方法是 Object.prototype 对象的一个属性。
  • toString 方法可以被重写,因此 toString 方法可能不是转为字符串的最好方式。
  • toString() 可以指定要用于数字到字符串的转换的基数 (从 2 到 36)。

5. 数值(Number)

几乎所有编程语言都提供一种或多种整数数据类型(Numeric types)。在 JavaScript 中,数值(Number)是一种 定义为 64 位双精度浮点型(double-precision 64-bit floating point format)(IEEE 754)的数字数据类型。在其他编程语言中,有不同的数字类型存在,比如:整型(Integers),单精度浮点型(Floats),双精度浮点型(Doubles),大数(Bignums)。

  • 整数可以用十进制(基数为 10)、十六进制(基数为 16)、八进制(基数为 8)以及二进制(基数为 2)表示。
    • 十进制整数字面量由一串数字序列组成,且没有前缀 0。
    • 八进制的整数以 0(或 0O、0o)开头,只能包括数字 0-7。严格模式下,八进制整数字面量必须以 0o 或 0O 开头,而不能以 0 开头。
    • 十六进制整数以 0x(或 0X)开头,可以包含数字(0-9)和字母(a-f 或 A-F)。
    • 二进制整数以 0b(或 0B)开头,只能包含数字 0 和 1。
  • 浮点数可以有以下的组成部分。
    • 一个十进制整数,可以带正负号(即前缀 +-
    • 小数点(.
    • 小数部分(由一串十进制数表示)
    • 指数部分以 eE 开头,后面跟着一个整数,可以有正负号。浮点数字面量至少有一位数字,而且必须带小数点或者 eE
let nbr = 3.1415926
let num = Number('100')
let int = parseInt('100')
let float = parseFloat('100')

Number() 构造函数包含常量和处理数值的方法。其他类型的值可以使用 Number() 函数转换为数值。parseInt() 解析一个字符串并返回指定基数的十进制整数,radix 是 2-36 之间的整数,表示被解析字符串的基数。

6. 大整数(BigInt)

在其他编程语言中,可以存在不同的数字类型,为了独立于体系结构细节,可以提供 Bignum 或任意精度 numeric 类型。例如:整数、浮点数、双精度数或大斐波数。在JavaScript中,BigInt 是一种数字类型的数据,它可以表示任意精度格式的整数。

BigInt 是一种内置对象,它提供了一种方法来表示大于 2532^{53} - 1 的整数,BigInt() 不与 new 运算符一起使用。。这原本是 Javascript 中可以用 Number 表示的最大数字。BigInt 可以表示任意大的整数,可以用在一个整数字面量后面加 n 的方式定义一个 BigInt

const n = 9007199254740991n
const bigint = BigInt(9007199254740991)
const hex = BigInt("0x1fffffffffffff")
const bin = BigInt('0b1111111111111111111111111111111111')

它在某些方面类似于 Number ,但是也有几个关键的不同点:不能用于 Math 对象中的方法;不能和任何 Number 实例混合运算,两者必须转换成同一种类型。BigIntNumber 不是严格相等的,但是宽松相等的。

在两种类型来回转换时要小心,因为 BigInt 变量在转换成 Number 变量时可能会丢失精度。由于在 NumberBigInt 之间进行转换会损失精度,因而建议仅在值可能大于 2532^{53} 时使用 BigInt 类型,并且不在两种类型之间进行相互转换。

BigInt 在需要转换成 Boolean 的时表现跟 Number 类似:如通过 Boolean 函数转换;用于 ||, &&, 和 ! 的操作数;或者用于在像 if statement 这样的条件语句中。

7. 符号(Symbol)

symbol 是一种基本数据类型(primitive data type),每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的。

Symbol() 函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。它的静态属性会暴露几个内建的成员对象;它的静态方法会暴露全局的 symbol 注册,且类似于内建对象类,但作为构造函数来说它并不完整,因为它不支持语法:new Symbol()

const symbol1 = Symbol()
const symbol2 = Symbol(42)

所有基本类型的值都是不可改变的。但需要注意的是,基本类型本身和一个赋值为基本类型的变量的区别。变量会被赋予一个新值,而基本类型不能像数组、对象以及函数那样被改变。基本类型没有方法,但仍然表现得像有方法一样。当在基本类型上访问属性时,JavaScript 自动将值装入包装器对象中,并访问该对象上的属性。

对象(Object)

在计算机科学中,对象(object)是指内存中的可以被标识符引用的一块区域。在 JavaScript 中,对象是唯一可变的值。事实上,函数(function)也是具有额外可调用能力的对象。Object 是 JavaScript 的一种数据类型。它用于存储各种键值集合和更复杂的实体。可以通过 Object() 构造函数或者使用对象字面量的方式创建对象。

const obj = new Object({a: 1})
const obj1 = {}
const obj2 = Object(function () {})
const obj3  = Object.create(() => {})

数据类型判断

class P {
  constructor(a) {
    this.a = a
  }
}
class C extends P {
  constructor(a, b) {
    super(a)
    this.b = b
  }
}

function F() { }

const f1 = new F()
const c1 = new C(1, 2)
const d1 = new Date('2023/08/23 20:00:00')
const a1 = new Array(3)

const T = [
  null, undefined, '999', 1, true, Symbol('symbol'),
  BigInt('666'), F, f1, c1, d1, a1, /\d+/g
]
const O = [
  Object, Object, String, Number, Boolean, Symbol,
  BigInt, Function, F, C, Date, Array, RegExp
]

1. typeof

typeof 运算符返回一个字符串,表示操作数的类型。

console.log(T.map(v => typeof v))

// ['object', 'undefined', 'string', 'number', 'boolean', 'symbol', 
// 'bigint', 'function', 'object', 'object', 'object', 'object', 'object']

这个方法无法区分 nullundefinedArrayObject,所有实例对象都是 object

2. Array.isArray

Array.isArray() 静态方法用于确定传递的值是否是一个 Array

console.log(T.map(v => Array.isArray(v)))

// [false, false, false, false, false, false, 
// false, false, false, false, false, true, false]

这个方法只是针对数组的判断。

3. instanceof

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

console.log(T.map((v, i) => v instanceof O[i]))

// [false, false, false, false, false, false, 
// false, true, true, true, true, true, true]

instanceof 只能对实例进行判断,不能对基本类型进行判断。

4. Object.getPrototypeOf

Object.getPrototypeOf() 静态方法返回指定对象的原型(即内部 [[Prototype]] 属性的值)。

console.log(T.map((v, i) => {
  const prototype = v ? Object.getPrototypeOf(v) : ''
  return prototype === O[i].prototype
}))
// [false, false, true, true, true, true, 
// true, true, true, true, true, true, true]

这个方法不能辨别 nullundefined

5. Object.prototype.constructor

Object 实例的 constructor 数据属性返回一个引用,指向创建该实例对象的构造函数。

console.log(T.map(v => v.constructor))
// [,, ƒ String(), ƒ Number(), ƒ Boolean(), ƒ Symbol(), 
// ƒ BigInt(), ƒ Function(), ƒ F(), class C, ƒ Date(), ƒ Array(), ƒ RegExp()]

这个方法不能辨别 nullundefined

6. Object.prototype.toString

toString() 方法返回一个表示该对象的字符串。该方法旨在重写(自定义)派生类对象的类型转换的逻辑。

function getType(v) {
  return Object.prototype.toString.call(v)
    .replace(/.+[ ](\w+)\]$/ig, '$1').toLowerCase()
}
console.log(T.map(v => getType(v)))
// ['null', 'undefined', 'string', 'number', 'boolean', 'symbol', 
// 'bigint', 'function', 'object', 'object', 'date', 'array', 'regexp']

这个方法能获取原始类型和内置对象的类型,自定义类的实例对象都是 object

相等性判断

JavaScript 提供三种不同的值比较运算:

  • === —— 严格相等(三个等号)
  • == —— 宽松相等(两个等号)
  • Object.is()

选择哪个运算取决于需要什么样的比较。简单来说:

  • 在比较两个操作数时,双等号(==)将执行类型转换,并且会按照 IEEE 754 标准对 NaN-0+0 进行特殊处理(故 NaN != NaN,且 -0 == +0);
    1. 如果操作数具有相同的类型,则按以下方式进行比较:
    • Object:仅当两个操作数引用相同的对象时,才返回 true
    • String:仅当两个操作数具有相同的字符并且顺序相同,才返回 true。 - Number:仅当两个操作数具有相同的值时,才返回 true+0-0 被视为相同的值。如果任一操作数为 NaN,则返回 false;因此 NaN 永远不等于 NaN
    • Boolean:仅当操作数都是 truefalse 时,才返回 true
    • BigInt:仅当两个操作数具有相同的值时,才返回 true
    • Symbol:仅当两个操作数引用相同的 symbol 时,才返回 true
    1. 如果操作数之一为 nullundefined,则另一个操作数必须为 nullundefined 才返回 true。否则返回 false
    2. 如果操作数之一是对象,而另一个是原始值,则将对象转换为原始值。
    3. 在这一步骤中,两个操作数都被转换为原始值(StringNumberBooleanSymbolBigInt 之一)。剩余的转换将分情况完成。
    • 如果它们是相同类型的,则使用步骤 1 进行比较。
    • 如果操作数中有一个是 Symbol,但另一个不是,则返回 false
    • 如果操作数之一是 Boolean,而另一个不是,则将 Boolean 转换为 Numbertrue 转换为 1false 转换为 0。然后再次对两个操作数进行宽松比较。
  • 三等号(===)做的比较与双等号相同(包括对 NaN-0+0 的特殊处理)但不进行类型转换;如果类型不同,则返回 false
    • 两个被比较的值在比较前都不进行隐式转换。
    • 如果两个被比较的值具有不同的类型,这两个值是不相等的。否则,如果两个被比较的值类型相同,值也相同,并且都不是 number 类型时,两个值相等。
    • 如果两个值都是 number 类型,当两个都不是 NaN,并且数值相同,或是两个值分别为 +0-0 时,两个值被认为是相等的。
    • NumberString:将 String 转换为 Number。转换失败会得到 NaN,这将确保相等性为 false
    • NumberBigInt:按照其数值进行比较。如果 Number±InfinityNaN,返回 false
    • StringBigInt: 使用与 BigInt() 构造函数相同的算法将字符串转换为 BigInt。如果转换失败,则返回 false
  • Object.is() 既不进行类型转换,也不对 NaN-0+0 进行特殊处理(这使它和 === 在除了那些特殊数字值之外的情况具有相同的表现)。

类似于同值相等,但 +0-0 被视为相等。零值相等与严格相等的区别在于其将 NaN 视作是相等的,与同值相等的区别在于其将 -00 视作相等的。这使得它在搜索期间通常具有最实用的行为,特别是在与 NaN 一起使用时。