编程语言都具有内建的数据结构,但各种编程语言的数据结构常有不同之处。
在计算机科学中,数据结构(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
常在返回类型应是一个对象,但没有关联的值的地方使用。
undefined
与 null
的区别
undefined
表示变量已声明但未定义,null
表示变量空对象。null
是关键字,undefined
是全局对象的属性。null
转换为数值是0
,undefined
转换为数值是NaN
。
3. 布尔数据(Boolean)
在计算机科学中,布尔数据类型(Boolean data type)是一种取值仅能为 true
或 false
的数据类型,它赋予了编程语言在逻辑上表达 true
或 false
的能力。如果没有这种能力,很多功能将无法被实现。
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()
可以将任何类型转换为字符串,而null
和undefined
没有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。
- 浮点数可以有以下的组成部分。
- 一个十进制整数,可以带正负号(即前缀
+
或-
) - 小数点(
.
) - 小数部分(由一串十进制数表示)
- 指数部分以
e
或E
开头,后面跟着一个整数,可以有正负号。浮点数字面量至少有一位数字,而且必须带小数点或者e
或E
- 一个十进制整数,可以带正负号(即前缀
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
是一种内置对象,它提供了一种方法来表示大于 - 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
实例混合运算,两者必须转换成同一种类型。BigInt
和 Number
不是严格相等的,但是宽松相等的。
在两种类型来回转换时要小心,因为 BigInt
变量在转换成 Number
变量时可能会丢失精度。由于在 Number
与 BigInt
之间进行转换会损失精度,因而建议仅在值可能大于 时使用 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']
这个方法无法区分 null
和 undefined
、Array
和 Object
,所有实例对象都是 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]
这个方法不能辨别 null
和 undefined
。
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()]
这个方法不能辨别 null
和 undefined
。
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
);- 如果操作数具有相同的类型,则按以下方式进行比较:
Object
:仅当两个操作数引用相同的对象时,才返回true
。String
:仅当两个操作数具有相同的字符并且顺序相同,才返回true
。 -Number
:仅当两个操作数具有相同的值时,才返回true
。+0
和-0
被视为相同的值。如果任一操作数为NaN
,则返回false
;因此NaN
永远不等于NaN
。Boolean
:仅当操作数都是true
或false
时,才返回true
。BigInt
:仅当两个操作数具有相同的值时,才返回true
。Symbol
:仅当两个操作数引用相同的symbol
时,才返回true
。
- 如果操作数之一为
null
或undefined
,则另一个操作数必须为null
或undefined
才返回true
。否则返回false
。 - 如果操作数之一是对象,而另一个是原始值,则将对象转换为原始值。
- 在这一步骤中,两个操作数都被转换为原始值(
String
、Number
、Boolean
、Symbol
和BigInt
之一)。剩余的转换将分情况完成。
- 如果它们是相同类型的,则使用步骤 1 进行比较。
- 如果操作数中有一个是
Symbol
,但另一个不是,则返回false
。 - 如果操作数之一是
Boolean
,而另一个不是,则将Boolean
转换为Number
:true
转换为1
,false
转换为0
。然后再次对两个操作数进行宽松比较。
- 三等号(
===
)做的比较与双等号相同(包括对NaN
、-0
和+0
的特殊处理)但不进行类型转换;如果类型不同,则返回false
;- 两个被比较的值在比较前都不进行隐式转换。
- 如果两个被比较的值具有不同的类型,这两个值是不相等的。否则,如果两个被比较的值类型相同,值也相同,并且都不是
number
类型时,两个值相等。 - 如果两个值都是
number
类型,当两个都不是NaN
,并且数值相同,或是两个值分别为+0
和-0
时,两个值被认为是相等的。 Number
转String
:将String
转换为Number
。转换失败会得到NaN
,这将确保相等性为false
。Number
转BigInt
:按照其数值进行比较。如果Number
是±Infinity
或NaN
,返回false
。String
转BigInt
: 使用与BigInt()
构造函数相同的算法将字符串转换为BigInt
。如果转换失败,则返回false
。
Object.is()
既不进行类型转换,也不对NaN
、-0
和+0
进行特殊处理(这使它和===
在除了那些特殊数字值之外的情况具有相同的表现)。
类似于同值相等,但 +0
和 -0
被视为相等。零值相等与严格相等的区别在于其将 NaN
视作是相等的,与同值相等的区别在于其将 -0
和 0
视作相等的。这使得它在搜索期间通常具有最实用的行为,特别是在与 NaN
一起使用时。