编程语言都具有内建的数据结构,但各种编程语言的数据结构常有不同之处。
在计算机科学中,数据结构(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 一起使用时。