{T}

字符串与复杂类型

String 类型

String 类型表示零或多个 16 位 Unicode 字符序列。字符串是不可变的(immutable)。

基本特性

javascript
// 字符串字面量
let firstName = "John"
let lastName = "Doe"
let fullName = `John Doe` // ES6 模板字符串

// 字符串不可变性
let lang = "Java"
lang = lang + "Script"

// 实际过程:
// 1. 创建包含 "Java" 的字符串
// 2. 创建包含 "Script" 的字符串
// 3. 创建包含 "JavaScript" 的新字符串
// 4. 销毁原始字符串

转义序列

转义序列含义
\n换行
\t制表符
\b退格
\r回车
\f换页
\\反斜杠
\'单引号
\"双引号
\xnn十六进制字符码
\unnnnUnicode 字符
javascript
let text = "First line\nSecond line"
let path = "C:\\Users\\John\\Desktop"
let quote = 'He said, "Hello"'

字符串方法

字符方法

javascript
let str = "Hello World"

// 访问字符
str.charAt(0) // "H"
str[0] // "H"(ES5)
str.charCodeAt(0) // 72(ASCII 码)
str.codePointAt(0) // 72(ES6,支持 Unicode)

// fromCharCode / fromCodePoint
String.fromCharCode(72, 101, 108, 108, 111) // "Hello"
String.fromCodePoint(0x1f600) // "😀"

字符串操作方法

javascript
let str = "Hello World"

// 拼接
str.concat("!", "!") // "Hello World!!"
str + "!" + "!" // 更常用

// 截取
str.slice(0, 5) // "Hello"
str.substring(0, 5) // "Hello"
str.substr(0, 5) // "Hello"(不推荐使用)

// 位置方法

  // ... 中间省略 ...

// 重复
str.repeat(3) // "Hello WorldHello WorldHello World"(ES6)

// 填充
str.padStart(15, "*") // "****Hello World"(ES2017)
str.padEnd(15, "*") // "Hello World****"(ES2017)

字符串分割和替换

javascript
let str = "Hello,World,JavaScript"

// 分割
str.split(",") // ["Hello", "World", "JavaScript"]
str.split(",", 2) // ["Hello", "World"]
str.split("") // ["H", "e", "l", "l", "o", ...]

// 替换
let text = "cat sat on the mat"
text.replace("at", "ond") // "cond sat on the mat"(只替换第一个)
text.replace(/at/g, "ond") // "cond sond on the mond"(全局替换)

// replaceAll(ES2021)
text.replaceAll("at", "ond") // "cond sond on the mond"

模式匹配

javascript
let str = "Hello World"

// match
str.match(/o/g) // ["o", "o"]
str.match("o") // ["o", index: 4, input: "Hello World", groups: undefined]

// search
str.search(/o/) // 4

// matchAll(ES2020)
const matches = str.matchAll(/o/g)
for (const match of matches) {
  console.log(match)
}

转为字符串

toString() 方法

javascript
// 基本用法
let age = 11
age.toString() // "11"

let found = true
found.toString() // "true"

// 数值的 toString() 可以指定进制
let num = 10
num.toString() // "10"
num.toString(2) // "1010"(二进制)
num.toString(8) // "12"(八进制)
num.toString(16) // "a"(十六进制)

// null 和 undefined 没有 toString() 方法
// null.toString() // TypeError
// undefined.toString() // TypeError

String() 函数

javascript
// String() 可以处理 null 和 undefined
String(10) // "10"
String(true) // "true"
String(null) // "null"
String(undefined) // "undefined"

// 等价于
let value1 = 10
let value2 = true
let value3 = null
let value4

String(value1) // "10"
String(value2) // "true"
String(value3) // "null"
String(value4) // "undefined"

模板字面量(ES6)

模板字面量使用反引号(`)定义,支持多行字符串和字符串插值。

基本用法

javascript
// 多行字符串
let myMultiLineString = "first line\nsecond line"
let myMultiLineTemplateLiteral = `first line
second line`

console.log(myMultiLineString === myMultiLineTemplateLiteral) // true

// HTML 模板
let pageHTML = `
<div>
  <a href="#">
    <span>Jake</span>
  </a>
</div>
`

⚠️ 注意空格:模板字面量会保留反引号内的所有空格和换行。 ```javascript // 这个模板字面量在换行符之后有 25 个空格符 let myTemplateLiteral = first line second line console.log(myTemplateLiteral.length) // 47 // 这个模板字面量以一个换行符开头 let secondTemplateLiteral = first line second line console.log(secondTemplateLiteral[0] === "\n") // true

code

字符串插值

javascript
let value = 5
let exponent = "second"

// 旧方式
let interpolatedString = value + " to the " + exponent + " power is " + value * value

// 新方式(模板字面量)
let interpolatedTemplateLiteral = `${value} to the ${exponent} power is ${value * value}`

console.log(interpolatedString) // "5 to the second power is 25"
console.log(interpolatedTemplateLiteral) // "5 to the second power is 25"

// 任何 JavaScript 表达式都可以用于插值
console.log(`Hello, ${`World`}!`) // "Hello, World!"

// 函数调用
function capitalize(word) {
  return `${word[0].toUpperCase()}${word.slice(1)}`
}
console.log(`${capitalize("hello")}, ${capitalize("world")}!`) // "Hello, World!"

// 对象的 toString()
let foo = { toString: () => "World" }
console.log(`Hello, ${foo}!`) // "Hello, World!"

标签函数

标签函数可以自定义模板字面量的插值行为。

javascript
let a = 6
let b = 9

// 标签函数接收参数:
// - strings: 被插值分隔的字符串数组
// - ...expressions: 每个插值表达式的值
function simpleTag(strings, ...expressions) {
  console.log(strings) // ["", " + ", " = ", ""]
  expressions.forEach((expr) => console.log(expr)) // 6, 9, 15
  return "foobar"
}

let taggedResult = simpleTag`${a} + ${b} = ${a + b}`
console.log(taggedResult) // "foobar"

// 实现默认行为
function zipTag(strings, ...expressions) {
  return strings[0] + expressions.map((e, i) => `${e}${strings[i + 1]}`).join("")
}

let untaggedResult = `${a} + ${b} = ${a + b}`
let taggedResult = zipTag`${a} + ${b} = ${a + b}`
console.log(untaggedResult) // "6 + 9 = 15"
console.log(taggedResult) // "6 + 9 = 15"

原始字符串

String.raw 标签函数可以获取原始的模板字面量内容。

javascript
// 转义序列不会被转换
console.log(`\u00A9`) // ©
console.log(String.raw`\u00A9`) // \u00A9

console.log(`first line\nsecond line`)
// first line
// second line

console.log(String.raw`first line\nsecond line`) // "first line\nsecond line"

// 注意:实际的换行符不会被转换
console.log(String.raw`first line
second line`)
// first line
// second line

字符串迭代器(ES6)

javascript
let str = "😀hello"

// for...of 迭代(正确处理 Unicode)
for (let char of str) {
  console.log(char) // 😀, h, e, l, l, o
}

// 转换为数组
let chars = [...str] // ["😀", "h", "e", "l", "l", "o"]
let charsArray = Array.from(str) // ["😀", "h", "e", "l", "l", "o"]

Symbol 类型

Symbol(符号)是 ES6 新增的原始类型,表示唯一的、不可变的标识符。

基本用法

javascript
// 创建符号
let sym1 = Symbol()
let sym2 = Symbol("description") // 可选的描述字符串
let sym3 = Symbol("description")

console.log(sym2 === sym3) // false(每个 Symbol 都是唯一的)

// typeof 检测
console.log(typeof sym1) // "symbol"

Symbol 的特点

javascript
// 1. 每个符号都是唯一的
const sym1 = Symbol("id")
const sym2 = Symbol("id")
console.log(sym1 === sym2) // false

// 2. 符号不能使用 new 创建
// const sym = new Symbol() // TypeError

// 3. 符号可以转换为字符串或布尔值
console.log(String(sym1)) // "Symbol(id)"
console.log(sym1.toString()) // "Symbol(id)"
console.log(Boolean(sym1)) // true

// 4. 符号不能转换为数值
// console.log(Number(sym1)) // TypeError

作为对象属性名

javascript
const sym = Symbol("id")

const user = {
  name: "John",
  [sym]: 12345 // 使用符号作为属性名
}

console.log(user[sym]) // 12345
console.log(user.sym) // undefined(不能使用点号访问)

// 符号属性不会被常规方法枚举
for (let key in user) {
  console.log(key) // 只输出 "name"
}

console.log(Object.keys(user)) // ["name"]
console.log(Object.getOwnPropertyNames(user)) // ["name"]

// 获取符号属性
console.log(Object.getOwnPropertySymbols(user)) // [Symbol(id)]
console.log(Reflect.ownKeys(user)) // ["name", Symbol(id)]

全局符号注册表

javascript
// 创建全局符号
let globalSym1 = Symbol.for("foo")
let globalSym2 = Symbol.for("foo")

console.log(globalSym1 === globalSym2) // true

// 获取全局符号的键
console.log(Symbol.keyFor(globalSym1)) // "foo"

// 普通符号不在全局注册表中
let localSym = Symbol("foo")
console.log(Symbol.keyFor(localSym)) // undefined

内置符号(Well-known Symbols)

ES6 定义了一组内置符号,用于暴露 JavaScript 内部行为。

javascript
// Symbol.iterator - 定义对象的默认迭代器
const myIterable = {
  [Symbol.iterator]() {
    let step = 0
    return {
      next() {
        step++
        if (step <= 3) {
          return { value: step, done: false }
        }
        return { value: undefined, done: true }
      }

  // ... 中间省略 ...

  static [Symbol.hasInstance](instance) {
    return Array.isArray(instance)
  }
}

console.log([] instanceof MyArray) // true

Symbol 使用场景

1. 私有属性

javascript
// 在 ES2022 之前,Symbol 常用于模拟私有属性
const _privateField = Symbol("private")

class MyClass {
  constructor() {
    this[_privateField] = "private value"
  }

  getPrivateField() {
    return this[_privateField]
  }
}

const obj = new MyClass()
console.log(obj[_privateField]) // "private value"(仍可访问)
console.log(Object.keys(obj)) // [](不可枚举)

2. 避免属性冲突

javascript
// 当扩展第三方对象时,避免覆盖已有属性
const libraryObject = { name: "Library" }
const myId = Symbol("myId")

libraryObject[myId] = "my unique identifier"
console.log(libraryObject[myId]) // "my unique identifier"

3. 元数据

javascript
// 为对象添加元数据而不影响对象结构
const metadata = Symbol("metadata")

function addMetadata(obj, data) {
  obj[metadata] = data
}

function getMetadata(obj) {
  return obj[metadata]
}

const user = { name: "John" }
addMetadata(user, { createdAt: Date.now(), version: 1 })
console.log(getMetadata(user)) // { createdAt: ..., version: 1 }

BigInt 类型

BigInt 是 ES2020 新增的数据类型,用于表示任意精度的整数。

创建 BigInt

javascript
// 方法 1:在数字后面加 n
let bigInt1 = 9007199254740991n
let bigInt2 = 123456789012345678901234567890n

// 方法 2:使用 BigInt() 函数
let bigInt3 = BigInt(9007199254740991)
let bigInt4 = BigInt("123456789012345678901234567890")
let bigInt5 = BigInt("0x1fffffffffffff") // 十六进制

BigInt 的特点

1. 不能与 Number 混合运算

javascript
let bigInt = 100n
let number = 100

// console.log(bigInt + number) // TypeError: Cannot mix BigInt and other types

// 需要先转换
console.log(bigInt + BigInt(number)) // 200n
console.log(Number(bigInt) + number) // 200

// 注意:Number(bigInt) 可能丢失精度
let hugeBigInt = 9007199254740993n
console.log(Number(hugeBigInt)) // 9007199254740992(精度丢失!)

2. 比较操作

javascript
let bigInt = 100n
let number = 100

console.log(bigInt == number) // true(宽松相等,会进行类型转换)
console.log(bigInt === number) // false(严格相等,类型不同)
console.log(bigInt > 50) // true
console.log(bigInt > 50n) // true

// 与其他类型比较
console.log(100n > 99) // true
console.log(100n < 101) // true

3. 数学运算

javascript
let a = 10n
let b = 20n

console.log(a + b) // 30n
console.log(a - b) // -10n
console.log(a * b) // 200n
console.log(a / b) // 0n(注意:BigInt 除法会向下取整)
console.log(a % b) // 10n
console.log(7n / 2n) // 3n(向下取整)
console.log(-7n / 2n) // -4n(向下取整)

// 幂运算(ES2020)
console.log(2n ** 100n) // 1267650600228229401496703205376n

// 不支持的运算
// console.log(Math.sqrt(16n)) // TypeError
console.log(16n ** (1n / 2n)) // 4n(可以使用幂运算替代)

4. 类型检测

javascript
let bigInt = 123n

console.log(typeof bigInt) // "bigint"
console.log(bigInt instanceof BigInt) // false(原始类型)
console.log(BigInt(123) === 123n) // true

BigInt 使用场景

javascript
// 1. 处理大整数(超过 Number.MAX_SAFE_INTEGER)
console.log(Number.MAX_SAFE_INTEGER) // 9007199254740991

// Number 会丢失精度
console.log(9007199254740992 === 9007199254740993) // true(错误!)

// BigInt 可以精确表示
console.log(9007199254740992n === 9007199254740993n) // false(正确)

// 2. 加密计算
const largePrime = 2152302898747n

// 3. 高精度时间戳
const timestamp = 1625097600000000000n // 纳秒级时间戳

// 4. 处理大数据 ID
const bigId = BigInt("123456789012345678901234567890")

BigInt 与 Number 对比

特性NumberBigInt
精度双精度浮点数,最多 15-17 位有效数字任意精度整数
范围±1.7976931348623157e+308无限制(受内存限制)
小数支持小数只支持整数
科学记数法支持不支持
Math 方法支持不支持
JSON 序列化支持不支持(需要转换)
javascript
// BigInt 与 JSON
const bigIntValue = 123n

// JSON.stringify(bigIntValue) // TypeError

// 解决方案:转换为字符串
JSON.stringify({ value: bigIntValue.toString() }) // '{"value":"123"}'

// 或使用自定义序列化
function bigIntReplacer(key, value) {
  return typeof value === "bigint" ? value.toString() : value
}

JSON.stringify({ id: 123n }, bigIntReplacer) // '{"id":"123"}'