# 数据类型-值类型
JavaScript 中有八种基本的数据类型(译注:前七种为简单数据类型,而 object 为复杂数据类型)。
- number 用于任何类型的数字:整数或浮点数,在 ±(253-1) 范围内的整数。
- bigint 用于任意长度的整数。
- string 用于字符串:一个字符串可以包含 0 个或多个字符,所以没有单独的单字符类型。
- boolean 用于 true 和 false。
- null 用于未知的值 —— 只有一个 null 值的独立类型。
- undefined 用于未定义的值 —— 只有一个 undefined 值的独立类型。
- symbol 用于唯一的标识符。
- object 用于更复杂的数据结构。
# 包装类型
JavaScript 允许我们像使用对象一样使用原始类型(字符串,数字等)。
JavaScript 还提供了这样的调用方法。我们很快就会学习它们,但是首先我们将了解它的工作原理,毕竟原始类型不是对象(在这里我们会分析地更加清楚)。
我们来看看原始类型和对象之间的关键区别。
一个原始值:
是原始类型中的一种值。
在 JavaScript 中有 7 种原始类型:number
,string
,boolean
,symbol
,bigint
,null
和 undefined
。
而一个对象:
能够存储多个值作为属性。
可以使用大括号 {} 创建对象,例如:{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 类型的值,该类型具有静态属性和静态方法。