# 数据类型-值类型

JavaScript 中有八种基本的数据类型(译注:前七种为简单数据类型,而 object 为复杂数据类型)。

  • number 用于任何类型的数字:整数或浮点数,在 ±(253-1) 范围内的整数。
  • bigint 用于任意长度的整数。
  • string 用于字符串:一个字符串可以包含 0 个或多个字符,所以没有单独的单字符类型。
  • boolean 用于 true 和 false。
  • null 用于未知的值 —— 只有一个 null 值的独立类型。
  • undefined 用于未定义的值 —— 只有一个 undefined 值的独立类型。
  • symbol 用于唯一的标识符。
  • object 用于更复杂的数据结构。

# 包装类型

JavaScript 允许我们像使用对象一样使用原始类型(字符串,数字等)。

JavaScript 还提供了这样的调用方法。我们很快就会学习它们,但是首先我们将了解它的工作原理,毕竟原始类型不是对象(在这里我们会分析地更加清楚)。

我们来看看原始类型和对象之间的关键区别。

一个原始值:

是原始类型中的一种值。

在 JavaScript 中有 7 种原始类型:numberstringbooleansymbolbigintnullundefined

而一个对象:

能够存储多个值作为属性。 可以使用大括号 {} 创建对象,例如:{name: "John", age: 30}。JavaScript 中还有其他种类的对象,例如函数就是对象。

# 当作对象的原始类型

以下是 JavaScript 创建者面临的悖论:

  • 人们可能想对诸如字符串或数字之类的原始类型执行很多操作。最好将它们作为方法来访问。
  • 原始类型必须尽可能的简单轻量。

而解决方案看起来多少有点尴尬,如下:

  • 原始类型仍然是原始的。与预期相同,提供单个值
  • JavaScript 允许访问字符串,数字,布尔值和 symbol 的方法和属性。
  • 为了使它们起作用,创建了提供额外功能的特殊“对象包装器”,使用后即被销毁。

“对象包装器”对于每种原始类型都是不同的,它们被称为 String、Number、Boolean 和 Symbol。因此,它们提供了不同的方法。

let str = "Hello";
alert(str.toUpperCase()); // HELLO

以下是 str.toUpperCase() 中实际发生的情况:

  • 字符串 str 是一个原始值。因此,在访问其属性时,会创建一个包含字符串字面值的特殊对象,并且具有有用的方法,例如 toUpperCase()。
  • 该方法运行并返回一个新的字符串(由 alert 显示)。
  • 特殊对象被销毁,只留下原始值 str。

# 对象包装器

构造器 String/Number/Boolean 仅供内部使用

像 Java 这样的一些语言允许我们使用 new Number(1) 或 new Boolean(false) 等语法,明确地为原始类型创建“对象包装器”。 在 JavaScript 中,由于历史原因,这也是可以的,但极其 不推荐。因为这样会出问题。

let a = 1
typeof 1 'number'
let b  = Number(1) //  注意这里是Number当作了方法调用
typeof b 'number'
let c = new Number(1) // 而这里作为了构造器
typeof c 'object'

# 总结

基本类型不是对象。 基本类型不能存储数据。 所有的属性/方法操作都是在临时对象的帮助下执行的。

# Number

在现代 JavaScript 中,数字(number)有两种类型:

  • JavaScript 中的常规数字以 64 位的格式 IEEE-754 存储,也被称为“双精度浮点数”。
  • BigInt 数字,用于表示任意长度的整数。有时会需要它们,因为常规数字不能超过 253 或小于 -253。

# 科学计数法

let billion = 1000000000;
let billion = 1e9; // 10 亿,字面意思:数字 1 后面跟 9 个 0
let ms = 0.000001;
let ms = 1e-6; // 1 的左边有 6 个 0

# 更多的进制

十六进制数字在 JavaScript 中被广泛用于表示颜色,编码字符以及其他许多东西。所以自然地,有一种较短的写方法:0x,然后是数字。

alert(0xff); // 255
alert(0xff); // 255(一样,大小写没影响)

二进制八进制数字系统很少使用,但也支持使用 0b 和 0o 前缀:

let a = 0b11111111; // 二进制形式的 255
let b = 0o377; // 八进制形式的 255

# toString(base)

方法 num.toString(base) 返回在给定 base 进制数字系统中 num 的字符串表示形式。base=36 是最大进制

let num = 255;
alert(num.toString(16)); // ff
alert(num.toString(2)); // 11111111

# 精度丢失

在内部,数字是以 64 位格式 IEEE-754 表示的,所以正好有 64 位可以存储一个数字:其中 52 位被用于存储这些数字,其中 11 位用于存储小数点的位置(对于整数,它们为零),而 1 位用于符号。

比如下面这个经典例子

console.log(0.1 + 0.2); // 0.30000000000000004

为什么会这样呢? 一个数字以其二进制的形式存储在内存中,一个 1 和 0 的序列。但是在十进制数字系统中看起来很简单的 0.1,0.2 这样的小数,实际上在二进制形式中是无限循环小数

# isFinite 和 isNaN

  • Infinity(和 -Infinity)是一个特殊的数值,比任何数值都大(小)。
  • NaN 代表一个 error

检查是否是 NaN isNaN(value) 将其参数转换为数字,然后测试它是否为 NaN

alert(isNaN(NaN)); // true
alert(isNaN("str")); // true
alert(NaN === NaN); // false

检查是否是常规数字 isFinite(value) 将其参数转换为数字,如果是常规数字,则返回 true

alert(isFinite("15")); // true
alert(isFinite("str")); // false,因为是一个特殊的值:NaN
alert(isFinite(Infinity)); // false,因为是一个特殊的值:Infinity

# parseInt 和 parseFloat

在现实生活中,我们经常会有带有单位的值,例如 CSS 中的 "100px" 或 "12pt"。并且,在很多国家,货币符号是紧随金额之后的,所以我们有 "19€",并希望从中提取出一个数值。

这就是 parseInt 和 parseFloat 的作用。

它们可以从字符串中“读取”数字,直到无法读取为止。如果发生 error,则返回收集到的数字。函数 parseInt 返回一个整数,而 parseFloat 返回一个浮点数

alert(parseInt("100px")); // 100
alert(parseFloat("12.5em")); // 12.5

alert(parseInt("12.3")); // 12,只有整数部分被返回了
alert(parseFloat("12.3.4")); // 12.3,在第二个点出停止了读取

parseInt()进制解析

parseInt() 函数具有可选的第二个参数。它指定了数字系统的基数,因此 parseInt 还可以解析十六进制数字、二进制数字等的字符串:

alert(parseInt("0xff", 16)); // 255
alert(parseInt("ff", 16)); // 255,没有 0x 仍然有效

alert(parseInt("2n9c", 36)); // 123456

# String

字符串就是零个或多个排在一起的字符,放在单引号、双引号或反引号中。 如果要在引号字符串的内部,使用引号,就必须在内部的引号前面加上反斜杠,用来转义。

连接运算符(+)可以连接多个单行字符串

在 JavaScript 中,文本数据被以字符串形式存储,单个字符没有单独的类型。

字符串的内部格式始终是 UTF-16,它不依赖于页面编码。

# 模板字符串

模板字符串使用反引号 (_) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。

// 在模版字符串内使用反引号(`)时,需要在它前面加转义符(\)
`\`` === "`"; // --> true

更高级的形式的模板字符串是带标签的模板字符串。标签使您可以用函数解析模板字符串。 标签函数的第一个参数包含一个字符串值的数组。其余的参数与表达式相关。

var person = "Mike";
var age = 28;

function myTag(strings, personExp, ageExp) {
  var str0 = strings[0]; // "that "
  var str1 = strings[1]; // " is a "
  var ageStr;

  if (ageExp > 99) {
    ageStr = "centenarian";
  } else {
    ageStr = "youngster";
  }
  return str0 + personExp + str1 + ageStr;
}

var output = myTag`that ${person} is a ${age}`;

console.log(output);
// that Mike is a youngster

# 字符数组

字符串可以被视为字符数组,因此可以使用数组的方括号运算符,用来返回某个位置的字符(位置编号从 0 开始)。

"hello"[1]; // "e"
"abc"[3]; // undefined
"abc"[-1]; // undefined
"abc"["x"]; // undefined

# 字符串是不可变的

在 JavaScript 中,字符串不可更改。改变原有字符串中字符是不可能的。

let str = "Hi";

str[0] = "h"; // error
alert(str[0]); // 无法运行

# 字符串长度是不可变的

length 属性返回字符串的长度,该属性是无法改变的,即不可以通过包装对象修改。

var s = "hello";
s.length; // 5
s.length = 3;
s.length; // 5

# 特殊字符

我们仍然可以通过使用“换行符(newline character)”,以支持使用单引号和双引号来创建跨行字符串。换行符写作 \n,用来表示换行:

let guestList = "Guests:\n * John\n * Pete\n * Mary";

alert(guestList); // 一个多行的客人列表

# 大小写转换

toLowerCase() 和 toUpperCase() 方法可以改变大小写:

alert("Interface".toUpperCase()); // INTERFACE
alert("Interface".toLowerCase()); // interface

组合一下,我们可以实现首字符大写

function capitalize(str) {
  return str[0]?.toUpperCase() + str.substr(1);
}
console.log(capitalize("hello"));

# 查找子字符串

  • subStr.indexOf(str)

从给定位置 pos 开始,在 str 中查找 substr,如果没有找到,则返回 -1,否则返回匹配成功的位置

  • str.includes(subStr),str.startsWith(subStr),str.endsWith(subStr)

str.includes(substr)根据 str 中是否包含 substr 来返回 true/false

alert(str.indexOf("Widget")); // 0,因为 'Widget' 一开始就被找到
alert("Midget".includes("id")); // true
alert("Widget".startsWith("Wid")); // true,"Widget" 以 "Wid" 开始
alert("Widget".endsWith("get")); // true,"Widget" 以 "get" 结束

# 获取子字符串

JavaScript 中有三种获取字符串的方法:substring、substr 和 slice。

str.slice(start [, end])[start,end)
返回字符串从 start 到(但不包括)end 的部分。

# 比较字符串

字符串按字母顺序逐字比较

// 主要字母的比较规则是字典序。
alert("a" > "Z"); // true
// 一些像 Ö 的字母与主要字母表不同。这里,它的代码比任何从 a 到 z 的代码都要大。
alert("Österreich" > "Zealand"); // true

执行字符串比较的“正确”算法比看起来更复杂,因为不同语言的字母都不相同。

因此浏览器需要知道要比较的语言。

幸运的是,所有现代浏览器(IE10- 需要额外的库 Intl.JS) 都支持国际化标准 ECMA-402。

它提供了一种特殊的方法来比较不同语言的字符串,遵循它们的规则。

  • localeCompare
alert("Österreich".localeCompare("Zealand")); // -1

# Base64 转码

有时,文本里面包含一些不可打印的符号,比如 ASCII 码 0 到 31 的符号都无法打印出来,这时可以使用 Base64 编码,将它们转成可以打印的字符。
JavaScript 原生提供两个 Base64 相关的方法。

  • btoa():任意值转为 Base64 编码
  • atob():Base64 编码转为原来的值

# Boolean

TIP

TODO 待续

# Null

TIP

null 没有包装类型

# Undefined

TIP

undefined 没有包装类型

# BigInt

BigInt 是一种特殊的数字类型,它提供了对任意长度整数的支持。

创建 bigint 的方式有两种:在一个整数字面量后面加 n 或者调用 BigInt 函数,该函数从字符串、数字等中生成 bigint。

const bigint = 1234567890123456789012345678901234567890n;

const sameBigint = BigInt("1234567890123456789012345678901234567890");

# 数学运算

BigInt 大多数情况下可以像常规数字类型一样使用,例如:

console.log(1n + 2n); // 3n
console.log(5n / 2n); // 2n

注意,我们不可以把 bigint 和常规数字类型混合使用:

alert(1n + 2); // Error: Cannot mix BigInt and other types

因此,我们可以使用BIgInt()Number()方法来互相转换他们

let big_number = 756416579646579646n;
let normal_number = Number(big_number);
console.log(normal_number); // 756416579646579600

转换操作始终是静默的,绝不会报错,但是如果 bigint 太大而数字类型无法容纳,则会截断多余的位,因此我们应该谨慎进行此类转换。

# 比较运算

比较运算符,例如 < 和 >,使用它们来对 bigint 和 number 类型的数字进行比较没有问题:

console.log(2n > 1n); // true
console.log(2n > 1); // true

但是请注意,由于 number 和 bigint 属于不同类型,它们可能在进行 == 比较时相等,但在进行 ===(严格相等)比较则必然不相等

console.log(1 == 1n); // true
console.log(1 === 1n); // false

# 布尔运算

当在 if 或其他布尔运算中时,bigint 的行为类似于 number。

console.log(1n || 2); // 1(1n 被认为是真)
console.log(0n || 2); // 2(0n 被认为是假)

# Symbol

symbol 是一种基本数据类型(primitive data type)。Symbol() 函数会返回 symbol 类型的值,该类型具有静态属性和静态方法。