进阶学习 Javascript ? 来看看这篇系统复习笔记 [ 引用类型篇 ]

Javascript 更多引用类型

  • Javascript 编程语言
  • Javascript 浏览器

数组 Array

数组是一种特殊的对象,其在对象的基础上提供了特殊的方法来处理有序的数据集合以及 length 属性,并做内部实现的优化(这些元素一个接一个地存储在连续的内存区域),以使数组运行得非常快。

数组的基础操作

创建一个空数组有两种语法,绝大多数情况下使用的都是第二种语法。

js 复制代码
let arr = new Array();
let arr = [];

使用方括号 arr[0] 可以访问数组元素,这是源于对象的语法obj[key],其中 arr 是对象,而数字就是 key。

js 复制代码
let fruits = ["Apple", "Orange", "Plum"];
// 访问
alert( fruits[0] ); // Apple
// 修改
fruits[2] = 'Pear';

在 JavaScript 中,使用方括号访问数组时,括号内不能为负数。若为负数,结果将是 undefined,因为方括号中的索引是被按照其字面意思处理的。

使用 "at"方法可以传入负数来获取元素:

js 复制代码
arr.at(-1); // 类似于 arr[arr.length - 1];

数组的 length

当我们修改数组的时候,length 属性会自动更新。

length 不是数组里元素的个数,而是最大的数字索引值加一 。若一个数组只有一个元素,但是这个元素的索引值很大,那么这个数组的 length 也会很大。

length 属性是可写的。

如果手动增加length,不会发生任何事。但若手动减少它,数组就会被截断。

js 复制代码
let arr = [1, 2, 3, 4, 5];

arr.length = 2; // 截断到只剩 2 个元素
alert( arr ); // [1, 2]

arr.length = 5; // 又把 length 加回来
alert( arr[3] ); // undefined:被截断的那些数值并没有回来

所以,清空数组最简单的方法就是:arr.length = 0;

数组的操作方法

更多创建方法
  1. Array.from(arrayLike, mapFunction):从类数组或可迭代对象创建新数组

    • 第二个参数可选,接收一个映射函数 mapFunction(item, index, array){}
    • 映射函数接收三个参数,分别是当前遍历的值item,索引index,和数组本身array
    • 映射函数内每一次返回的值将作为新数组中对应位置的值。
    • 映射函数不会直接修改数组,但因为传入数组本身,所以支持对数组进行修改。
    js 复制代码
    // 字符串转换
    const newArray = Array.from("hello"); 
    console.log(newArray); // ['h', 'e', 'l', 'l', 'o']
    
    // 类数组转换
    const arrayLike = { 0: '1', 1: '2', 2: '3', length: 3 };
    const newArray = Array.from(arrayLike); 
    console.log(newArray); // ['1', '2', '3']
    
    // 使用映射函数
    const funcArray = Array.from(arrayLike, item => item*2); 
    console.log(funcArray); // ['2', '4', '6']
    
    // 从 set 创建
    const set = new Set([1, 2, 3]);
    const setArray = Array.from(set); 
    console.log(setArray); // [1, 2, 3]
    
    // 从 map 创建
    const map = new Map([[1,'a'], [2,'b'], [3,'c']]);
    const mapArray = Array.from(map);
    console.log(mapArray); // [[1, 'a'], [2, 'b'], [3, 'c']]
    
    // 数组里创建3个空数组
    const newArr = Array.from({length:3}, () =>[])
    console.log(newArr)//[[],[],[]]
    
    // 还可以接收迭代器对象,详细见迭代器部分。
  2. Array.of() - 根据一系列参数创建新数组

    • 当调用 new Array( )构造器时,若传入 1 个参数则将该参数视为数组长度,若传入 1 个以上参数,则将每个参数视为数组项。
    • Array.of()则总会创建一个包含所有传入参数的数组,而不管参数的数量与类型
    js 复制代码
    // 逐个传入值
    let items = Array.of(1, 2);
    console.log(items); // [1, 2]
    items = Array.of(2);
    console.log(items); // [2]
    
    // 还可以利用 ES6 展开运算符转变迭代器对象,详细见迭代器部分。
对原数组进行直接修改的方法(原地操作)
头尾操作方法
  1. arr.push() - 末尾添加元素,返回新长度

  2. arr.pop() - 删除并返回最后一个元素

  3. arr.unshift() - 开头添加元素,返回新长度

  4. arr.shift() - 删除并返回第一个元素

    • pushunshift 方法都可以一次添加多个元素

      js 复制代码
      let fruits = ["Apple"];
      fruits.push("Orange", "Peach");
      fruits.unshift("Pineapple", "Lemon");
      
      alert(fruits); // ["Pineapple", "Lemon", "Apple", "Orange", "Peach"]
      
      alert(fruits.pop());   // Peach
      alert(fruits.shift()); // Pineapple
      
      alert(fruits); // ["Lemon", "Apple", "Orange"]
    • JavaScript 中的数组允许从首端/末端来添加/删除元素。在计算机科学中,允许这样的操作的数据结构被称为 双端队列(deque)

      数组通过 pop/push 数组可以用作队列 ,而通过pop/shift方法,数组也可以用作

    • push/pop 不需要移动任何东西,添加元素时只需增加/清除索引值并修改 length

      shift/unshift 需要移动元素,修改元素索引值,更新length。数组元素越多,花费时间越长,意味着内存操作越多。

数组转换
  1. 使用delete删除(不推荐):这是 Object 的方法,删除元素后对应位置变为undefined,但数组length不变,且元素位置没有改变。

    js 复制代码
    let arr = [3, 1, 2]
    delete arr[2] // true
    console.log(arr) // [3, 1, undefined]
  2. arr.splice(start, deleteCount, elem1, ..., elemN):对元素组增删元素,返回被删除的元素组成的数组。

    • 从索引 start 开始修改 arr,删除 deleteCount 个元素并在 start 开始插入 elem1, ..., elemN
    js 复制代码
    // 以下操作从索引 1 开始,删除两个元素,之后插入 a,b。
    let arr = [1, 2, 3, 4].splice(1, 2, 'a', 'b'); // [1, 'a', 'b', 4]
    console.log(arr) // [2, 3]
  3. arr.sort(cmpFunc):原地排序

    • 可以传入一个比较函数,自定义比较规则。
    • 比较函数的工作规则为检测负数 (a 应该排在 b 前面),0 (a 和 b 顺序不变),正数(b 应该排在 a 前面)
    js 复制代码
    const numbers = [10, 2, 1, 20, 5];
    
    // 升序排序(默认)
    numbers.sort();
    console.log(numbers); // [1, 2, 5, 10, 20]
    
    // 降序排序
    numbers.sort((a, b) => b - a);
    console.log(numbers); // [20, 10, 5, 2, 1]
  4. arr.reverse():反转数组

    js 复制代码
    [1, 2, 3].reverse(); // [3, 2, 1]
  5. arr.fill(item, start, end):将数组中指定范围的元素转变为传入的item参数。

    • start 默认为 0,end默认为数组长度。
    js 复制代码
    new Array(3).fill(0); // [0, 0, 0]
    [1,2,3,4].fill(5,1,3); // [1, 5, 5, 4]
不修改原数组的方法(返回新数组)
数组转换
  1. arr1.concat(arr2):将多个数组合并,返回一个新数组。

    • 参数内支持用逗号分隔,传入多个数组。
    js 复制代码
    let arr = [1, 2].concat([3, 4], [5, 6]); // [1, 2, 3, 4, 5, 6]
  2. arr.slice(start, end):从数组中提取指定范围的元素,返回一个新数组。

    • start 默认为 0,end默认为数组长度。
    js 复制代码
    let arr = [1, 2, 3, 4];
    let arr1 = arr.slice(1, 3); // [2, 3]
    let arr2 = arr.slice();     // [1, 2, 3, 4] 可用此方法快捷复制数组
  3. arr.join(separator):将数组中的所有元素,通过分隔符连接成一个字符串。

    js 复制代码
    let str = ['a', 'b', 'c'].join('-'); // "a-b-c"
  4. arr.toReversed():返回一个新数组,是反转后的原数组

    javascript 复制代码
    let arr = [1, 2, 3].toReversed(); // [3, 2, 1]
  5. arr.toSorted(cmpFunc):返回一个新数组,是排序后的原数组

    • 可以传入一个比较函数,参考arr.sort
    javascript 复制代码
    let arr = [3, 1, 2].toSorted(); // [1, 2, 3]
  6. arr.toSpliced():返回一个新数组,是增删改后的原数组

    javascript 复制代码
    let arr = [1, 2, 3].toSpliced(1, 1, 4); // [1, 4, 3]
搜寻方法
  1. 根据传入 item 搜索

    1. arr.indexOf(item, from):从索引 from 开始搜索 item,返回找到的第一个元素的索引,找不到返回 -1

      javascript 复制代码
      [1, 2, 3].indexOf(2); // 1
    2. arr.lastIndexOf(item, from):从索引 from 开始搜索 item,返回找到的最后一个元素的索引,找不到返回 -1

      javascript 复制代码
      [1, 2, 1].lastIndexOf(1); // 2
  2. 根据条件迭代搜索

    1. arr.find(func):查找第一个满足条件的元素,并返回元素值。

      • 接收一个类似Array.from内的函数function(item, index, array) {}
      • find 接收的函数内,若返回true则返回当前的 item 并停止迭代,若返回false则返回 undefined
      javascript 复制代码
      const numbers = [5, 12, 8, 130, 44];
      // 搜索索引大于 1,且值大于 10 的第一个元素
      const found = numbers.find((num,index,array) => {
          return index > 1 && num > 10;
      });
      console.log(found); // 130
    2. arr.findIndex(func):与 find 功能类似,但返回的是找到的第一个满足条件的元素的索引。

    3. arr.findLast(func):与 find 功能类似,但返回的是找到的最后一个满足条件的元素本身。

    4. arr.findLastIndex(func):与 find 功能类似,但返回的是找到的最后一个满足条件的元素的索引。

条件满足
  1. arr.includes(item, from):从索引from开始搜索item,如果找到则返回 true

    javascript 复制代码
    [1, 2, 3].includes(2); // true
  2. arr.every(func):是否所有元素满足条件

    • 类似find接收函数function(item, index, array) {}
    javascript 复制代码
    [1, 2, 3].every(x => x > 0); // true
  3. arr.some(func):是否有元素满足条件

    • 类似find接收函数function(item, index, array) {}
    javascript 复制代码
    [1, 2, 3].some(x => x > 2); // true
迭代与转换
  1. arr.map(func):映射新数组

    • 类似find接收函数function(item, index, array) {}
    • map 将记录每次返回的 item ,并最终返回修改后的数组。因为返回数组,所以支持链式调用。
    javascript 复制代码
    // 将用户对象数组转换为名称数组
    const users = [{name: 'Alice'}, {name: 'Bob'}];
    const names = users.map((user,index,array) => {
        return `[${index}]`+user.name
    });
    console.log(names); // ['[0]Alice', '[1]Bob']
  2. arr.forEach(func):遍历数组

    • 类似find接收函数function(item, index, array) {}
    • forEach 没有返回值。
    javascript 复制代码
    let arr = ["Bilbo", "Gandalf", "Nazgul"]
    arr.forEach((item, index, array) => {
      alert(`${item} is at index${index} in ${array}`);
    });
  3. arr.filter(func):过滤数组

    • 类似find接收函数function(item, index, array) {}
    • 将满足条件的元素组成一个新的数组并返回。
    javascript 复制代码
    let arr = [1, 2, 3].filter(x => x > 1); // [2, 3]
  4. arr.reduce(func,initial):从左到右归约

    • 接收一个函数function(accumulator, item, index, array) {},同时接收可选初始化参数 initial
    • reduce遍历所有数组元素对accmulator进行操作,并将每一次的结果搬运到下一次调用。
    • 若不传入 initialaccumulator初始为第 1 个元素,遍历从第 2 个元素开始。若传入initialaccumulator初始为initial,遍历从第 1 个元素开始。
    javascript 复制代码
    // 例子,计算总和
    let arr = [1, 2, 3];
    let result = arr.reduce((sum, current, index, array) => {
        return sum + current;
    });
    alert(result); // 6 (0+1=1, 1+2=3, 3+3=6)
  5. arr.reduceRight(func,initial):从右到左归约,reduce的反向版本。

    javascript 复制代码
    let arr = [1, 2, 3];
    let result = arr.reduceRight((a, b) => a - b);
    alert(result); // 0 (3-2=1, 1-1=0)
  6. arr.flat(depth):数组扁平化

    • 可以将多层嵌套的数组拆解,传入参数depth控制扁平化深度,默认为 1。
    javascript 复制代码
    let arr = [1, [2, [3, 4]]]
    let arr1 = arr.flat(); // [1, 2, [3, 4]]
    let arr2 = arr.flat(2); // [1, 2, 3, 4]
  7. arr.flatMap():映射后扁平化

    • 无论回调返回多少层嵌套,都只扁平化 1 层,无法控制深度。
    • 类似find接收函数function(item, index, array) {}
    javascript 复制代码
    const arr = [1, 2, 3]; // 此处不能带嵌套
    const result = arr.flatMap(x => [x, x * 2]);
    console.log(result); // [1, 2, 2, 4, 3, 6]
其他方法
  1. toString() - 转为字符串,数组有自己的 toString 方法的实现,会返回以逗号隔开的元素列表。

    js 复制代码
    let arr = [1, 2, 3];
    alert( arr ); // 1,2,3
    alert( String(arr) === '1,2,3' ); // true
    alert( [1,2] + 1 ); // "1,21"

大多数方法都支持 "thisArg"

几乎所有调用函数的数组方法 ------ 比如 findfiltermapflatMap都在最后可接受一个可选的附加参数 thisArg。除了 sort 是一个特例。

thisArg 参数的值将在 func 中变为 this,确保函数运行状态正确。

js 复制代码
// 例,用 army 对象的 canJoin 方法对 users 进行年龄筛选,该方法需用到 army 内的属性。
let army = {
  minAge: 18,
  maxAge: 27,
  canJoin(user) {
    return user.age >= this.minAge && user.age < this.maxAge;
  }
};

let users = [
  {age: 16},
  {age: 20},
  {age: 23},
  {age: 30}
];

let soldiers = users.filter(army.canJoin, army);
alert(soldiers); // 20, 23

如果在上面的示例中我们使用了 users.filter(army.canJoin),那么 army.canJoin 将被作为独立函数调用,并且这时 this=undefined,从而会导致即时错误。

也可以用以下方法替换,在之后函数部分会讲解其原理。

js 复制代码
let soldiers = users.filter(user => army.canJoin(user))

迭代器对象 iterator

可迭代对象是数组概念的泛化,指任何可以被定制为能在 for..of 循环中使用的对象。

在JavaScript中,可迭代对象实现了 Symbol.iterator 方法。

Symbol.iterator

Symbol.iterator 是为对象定义默认迭代器的特殊属性。当 for...of 循环开始时,它会调用这个方法。该方法必须返回一个迭代器对象 ------包含 next() 方法的对象。

next() 方法返回的结果必须是一个格式为 {done: Boolean, value: any}的对象,当 done=true 时,表示循环结束,否则 value 是下一个值。

迭代器对象和与其进行迭代的对象是分开的,迭代器对象记录了当前索引与最后一个元素的索引,进行迭代的对象则需提供迭代规则。

next()方法利用了闭包的特性,调用next()时能修改上一层词法环境记录的当前索引。

迭代器的运行步骤

  1. 我们需要为进行迭代的对象添加一个名为 Symbol.iterator 的方法;
  2. for..of 循环启动时,它会调用这个方法。该方法返回一个记录索引、包含 next()方法的对象;
  3. for..of 循环希望取得下一个数值,它就调用这个对象的 next() 方法;
  4. 等到 next()返回done:truefor..of 循环结束。

比如一个 range 对象,它代表了一个数字区间 (from - to),我们希望它在循环中输出范围内的所有整数。

js 复制代码
let range = {
  from: 1,
  to: 5
};

// 实现Symbol.iterator方法
range[Symbol.iterator] = function() {
  // 返回迭代器对象
  return {
    current: this.from,
    last: this.to,

    // next()方法在每次迭代中被调用
    next() {
      if (this.current <= this.last) {
        return { done: false, value: this.current++ };
      } else {
        return { done: true };
      }
    }
  };
};

// 使用for..of循环
for (let num of range) {
  console.log(num); // 依次输出1, 2, 3, 4, 5
}

了解了迭代器的运行规则,那我们可以进行一点简化,将迭代器对象和与其进行迭代的对象合并,让Symbol.iterator返回的对象变为进行迭代的对象自己。

注意:简化方式不适合同时运行多个迭代,即使是异步的方式,因为它们会共享迭代状态。虽然这种情况十分少见。

简化上面的示例,让range[Symbol.iterator]() 返回的是 range 对象自身。

js 复制代码
let range = {
  from: 1,
  to: 5,

  [Symbol.iterator]() {
    this.current = this.from;
    return this;
  },

  next() {
    if (this.current <= this.to) {
      return { done: false, value: this.current++ };
    } else {
      return { done: true };
    }
  }
};

for (let num of range) {
  alert(num); // 1, 然后是 2, 3, 4, 5
}

显式调用迭代器

这段代码创建了一个字符串迭代器,并"手动"从中获取值。

这样操作,相比 for..of 拥有更多的控制权。

js 复制代码
let str = "Hello";

// 和 for..of 做相同的事
// for (let char of str) alert(char);

let iterator = str[Symbol.iterator]();

while (true) {
  let result = iterator.next();
  if (result.done) break;
  alert(result.value); // 一个接一个地输出字符
}

可迭代(iterable)和类数组(array-like)

  • Iterable 如上所述,是实现了 Symbol.iterator 方法的对象。
  • Array-like 是有索引和 length 属性的对象,所以它们看起来很像数组。

存在一些对象同时是类数组的,也是可迭代的。如字符串即是可迭代的(for..of 对它们有效),又是类数组的(它们有数值索引和 length 属性)。

可迭代对象和类数组对象通常都不是数组 ,它们没有 pushpop 等数组方法。如果我们有一个这样的对象,并想像数组那样操作它,那就非常不方便。

若想在这两种对象中使用数组方法,解决办法有两个:

  1. 将其转换为数组

    1. Array.from:这个方法也能使用迭代器进行转换(类数组在之前数组部分已讲解)。

      js 复制代码
      // 转变之前的 range 示例
      let arr = Array.from(range);  // [1, 2, 3, 4, 5]
    2. Array.of:利用 ES6 的展开运算符转变可迭代对象(类数组不可)。

      js 复制代码
      // 展开之前的 range 示例
      let arr = Array.of(...range); // [1, 2, 3, 4, 5]
  2. 使用函数借用(具体在函数章节讲解)

映射 Map

Map 是一种带键的数据项集合,类似于对象(Object),都是用来存储键值对结构

但其与对象又有着关键区别

  1. Object 的键只能是字符串或Symbol,而 Map 允许任何类型的键。
  2. Object 可以用 字面量 或 构造函数 new 创建,Map 一般用 new。
  3. Object 的属性赋值取值直接用 object.prop 的方式,Map 则用 get/set 的方式。
  4. Object 的 key 顺序有特殊规则,Map 的 key 顺序就是插入顺序。

基础使用方法

  • map.set(key, value) ------ 存储键值对,并返回 map 本身。
  • map.get(key) ------ 获取键对应的值(不存在则返回undefined
  • map.has(key) ------ 检查键是否存在(返回布尔值)
  • map.delete(key) ------ 删除指定键值对
  • map.clear() ------ 清空整个Map
  • map.size ------ 返回当前元素数量

每一次 map.set 调用都会返回 map 本身,所以我们可以进行链式调用

初始化创建

  1. 先创建一个空 Map ,然后用 set 链式调用传入数据:

    js 复制代码
    let map = (new Map())
      .set('a', 500)      // 字符串键
      .set(1, 'num1')     // 数字键
      .set(true, 'bool'); // 布尔值键
  2. 直接在 Map 创建时通过二维数组传入数据:

    js 复制代码
    let map = new Map([
      ['a', 500],
      ['b', 350],
      ['c', 50]
    ]);

内部结构

js 复制代码
let map = new Map([
  ['a', 500],
  [1, 'num1'],
])
// map 创建后,其内部结构如下:
{
  [[Entries]] : {
    0: {"a" => 500}
    1: {1 => "num1"}
  }
  size: 2
}
  • [[Entries]]: 存储所有键值对的内部槽,通常是一个动态数据结构(如哈希表或类似结构),保证插入顺序(ES6 规范要求 Map 保持插入顺序)。
  • size: 记录当前键值对的数量。

因为使用get/set取用值,Map 不会与原型链上的属性,无需担心键名冲突。

迭代方法

Map 保留了元素的插入顺序,提供了多种迭代方式:

  • map.keys() ------ 遍历并返回一个包含所有键的可迭代对象,
  • map.values() ------ 遍历并返回一个包含所有值的可迭代对象,
  • map.entries() ------ 遍历并返回一个包含所有实体 [key, value] 的可迭代对象,for..of 在默认情况下使用的就是这个。
  • map.forEach(func) ------ 类似数组的forEach,Map 也有内建的该方法。
javascript 复制代码
let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion', 50]
]);

// 遍历所有键
for (let vegetable of recipeMap.keys()) {
  console.log(vegetable); // cucumber, tomatoes, onion
}

// 遍历所有值
for (let amount of recipeMap.values()) {
  console.log(amount); // 500, 350, 50
}

// 遍历所有键值对(默认方式)
for (let entry of recipeMap) { // 等同于 recipeMap.entries()
  console.log(entry); // ['cucumber', 500], ['tomatoes', 350]...
}

// 使用forEach方法
recipeMap.forEach((value, key, map) => {
  console.log(`${key}: ${value}`); // cucumber: 500...
});

Map与Object互转

  1. 从对象创建 Map

    方法 Object.entries(obj)返回对象的键/值对数组,该数组格式与 Map 所需格式相符。

    js 复制代码
    let obj = {
      name: "John",
      age: 30
    };
    
    let arr = Object.entries(obj); // '[['name','John'],['age',30]]'
    
    let map = new Map(arr);
  2. 从 Map 创建对象

    方法Object.fromEntries 的作用是相反的:给定一个具有 [key, value] 键值对的数组,它会根据给定数组创建一个对象

    js 复制代码
    let map = new Map([['name','John'],['age',30]])
    
    let obj = Object.fromEntries(map); // 相当于传入了 map.entries()
    
    console.log(obj) // {name: 'John', age: 30}

集合 Set

Set 是一种特殊的集合类型,它存储唯一值的集合(没有键),每个值只能出现一次。

Set 的替代方法可以是一个用户数组,用 arr.find 在每次插入值时检查是否重复。但是这样性能会很差,因为这个方法会遍历整个数组来检查每个元素。Set 内部对唯一性检查进行了更好的优化。

基础使用方法

  • set.add(value) ------ 添加一个值,返回 Set 本身(支持链式调用)
  • set.delete(value) ------ 删除指定值,如果值存在则返回 true,否则返回 false
  • set.has(value) ------ 检查值是否存在,存在则返回 true,否则返回 false
  • set.clear() ------ 清空整个 Set
  • set.size ------ 返回集合中元素的数量

初始化创建

  1. 先创建一个空 Set ,然后用 add 链式调用传入数据:

    js 复制代码
    let set = (new Set())
      .add("oranges")
      .add("apples")
      .add("bananas");
  2. 直接在 Set 创建时通过数组传入数据:

    js 复制代码
    let set = new Set(["oranges", "apples", "bananas"]);

内部结构

Set 的内部结构与 Map 类似,如下:

js 复制代码
let set = new Set(["oranges", "apples"]);
// set 创建后,其内部结构如下:
{
  [[Entries]] : {
    0: "oranges"
    1: "apples"
  }
  size: 2
}

迭代方法

Set 支持与 Map 类似的迭代方法:

  • set.keys() ------ 返回包含所有值的可迭代对象
  • set.values() ------ 与 set.keys() 功能相同(为保持与 Map 的接口一致)
  • set.entries() ------ 返回 [value, value] 形式的可迭代对象(为兼容 Map)
  • map.forEach(func) ------ 与 map 的forEach类似,但 set 的该方法接收的回调函数中,本来传入 key 的值变为了 value 值,这是为了保持与 map 的借口一致:function(value, valueAgain, set){}

WeakMap 与 WeakSet

弱映射 WeakMap

WeakMap 的键必须是对象,不能是原始值。

当作为键的对象没有其他引用时,该对象会被自动从内存和 WeakMap 中清除。

传统 Map 会强引用其键,即使外部不再引用这些对象,Map 仍会保持它们不被回收。WeakMap 的弱引用特性避免了这种内存泄漏风险。

主要用于需要将额外数据与对象关联,但又不希望影响对象垃圾回收的情况。

可用方法
  • weakMap.get(key) ------ 获取键对应的值
  • weakMap.set(key, value) ------ 设置键值对
  • weakMap.delete(key) ------ 删除指定键值对
  • weakMap.has(key) ------ 检查是否存在指定键

WeakMap 不支持迭代以及 keys()values()entries() 方法。所以没有办法获取 WeakMap 的所有键或值。

WeakMap 没有 size 属性,无法获取存储大小。

弱映射 WeakSet

WeakSet 的值必须是对象,不能是原始值。

对象只有在其它某个(些)地方能被访问的时候,才能留在 WeakSet 中。

可用方法
  • weakMap.add(value) ------ 设置键值对
  • weakMap.delete(key) ------ 删除指定键值对
  • weakMap.has(key) ------ 检查是否存在指定键

WeakSet 同样不支持迭代以及 keys()values()entries() 方法。

WeakSet 也没有 size 属性,无法获取存储大小。

日期和时间

Javascript 使用 Date 对象来对日期和时间进行操作。

创建方式

调用 new Date() 来创建一个新的 Date 对象。

  1. 无参形式new Date()不带参数,获取当前日期和时间

    javascript 复制代码
    let now = new Date(); // 创建表示当前日期和时间的对象
  2. 时间戳形式new Date(timestamp)传入一个整数,该整数被称为时间戳

    • 其时间等于 1970-01-01 00:00:00 之后经过的毫秒数(1/1000 秒)。
    javascript 复制代码
    // 自1970年1月1日UTC+0以来的毫秒数
    let date = new Date(1609459200000); // 对应2021-01-01 00:00:00 UTC
  3. 字符串形式new Date(datestring)传入一个字符串,使用日期字符串创建,该字符串会被自动解析。

    • 字符串格式为 "年-月-日T时:分:秒+/-时区"
      • "YYYY-MM-DD"(只带日期,视为 UTC 时间,时间默认为00:00:00
      • "YYYY-MM-DDTHH:mm:ss"(带日期与时间,视为本地时间)
      • "YYYY-MM-DDTHH:mm:ssZ"(带 Z 或时区,如 +08:00
    javascript 复制代码
    let date = new Date("2023-04-15"); // 只带日期,UTC 时间
    let date = new Date("2023-04-15T13:30:00"); // 包含时间,本地时间
    let date = new Date("2023-04-15T13:30:00+08:00"); // 带时区 +08:00 表示东八区。
  4. 多参数形式 :在 Date 中传入多项对应的参数,使用组件创建

    • 语法: new Date(year, month, date, hours, minutes, seconds, ms)
    • year: 四位数年份、month: 0(一月)到11(十二月)、date: 1到31(默认为1)、hours / ``minutes/seconds/ms` 默认为0。
    javascript 复制代码
    let date = new Date(2023, 3, 15, 13, 30, 0, 0); // 2023年4月15日13:30:00

注意:以上创建方法具有时区依赖差异。

  1. 依赖当前系统时区(中国是 UTC+8):无参形式、多参数形式、带时区的字符串形式
  2. 依赖世界标准时间(UTC+0 时区):时间戳形式、不带时区的字符串形式

访问日期组件

  1. 本地时间方法:返回 当前系统时区 下的时间(如中国用户默认是东八区 UTC+8)。受用户电脑/服务器时区设置影响,同一代码在不同时区运行结果不同。

    javascript 复制代码
    let date = new Date();
    
    date.getFullYear();    // 四位数的年份
    date.getMonth();       // 月份(0-11)
    date.getDate();        // 日期(1-31)
    date.getHours();       // 小时(0-23)
    date.getMinutes();     // 分钟(0-59)
    date.getSeconds();     // 秒数(0-59)
    date.getMilliseconds();// 毫秒(0-999)
    date.getDay();         // 星期几(0-6, 0=星期日)
  2. UTC时间方法:返回 世界标准时间(UTC+0) 下的时间,无视本地时区,全球统一结果。

    javascript 复制代码
    date.getUTCFullYear();    // UTC四位数的年份
    date.getUTCMonth();       // UTC月份(0-11)
    date.getUTCDate();        // UTC日期(1-31)
    date.getUTCHours();       // UTC小时(0-23)
    date.getUTCMinutes();     // UTC分钟(0-59)
    date.getUTCSeconds();     // UTC秒数(0-59)
    date.getUTCMilliseconds();// UTC毫秒(0-999)
    date.getUTCDay();         // UTC星期几(0-6)
  3. 获取当前系统时区与 UTC+0 时间的分钟差

    javascript 复制代码
    let diff = date.getTimezoneOffset();
  4. 访问时间戳:返回的是 自 1970-01-01 00:00:00 UTC 以来的毫秒数(即 Unix 时间戳),不依赖任何时区。

    js 复制代码
    let ms = date.getTime();
    • 虽然getTime()不依赖时区,但解析结果会受new Date() 影响,因为new Date()依赖时区。

    • 时间戳本身是可靠的,但构造 Date 对象时需注意输入格式。

      js 复制代码
      new Date("2023-01-01").getTime(); // 1672531200000  UTC 时区
      new Date(2023, 0, 1).getTime(); // 1672502400000  当前系统时区

设置日期组件

  1. 本地时间设置方法:设置 当前系统时区 下的时间(如中国用户默认是东八区 UTC+8)。

    javascript 复制代码
    let date = new Date();
    
    date.setFullYear(2023, 3, 15); // 年,月(可选),日(可选)
    date.setMonth(3, 15);          // 月(0-11),日(可选)
    date.setDate(15);              // 日(1-31)
    date.setHours(13, 30, 0, 0);   // 时,分(可选),秒(可选),毫秒(可选)
    date.setMinutes(30, 0, 0);     // 分,秒(可选),毫秒(可选)
    date.setSeconds(0, 0);         // 秒,毫秒(可选)
    date.setMilliseconds(0);       // 毫秒
    date.setTime(1609459200000);   // 使用时间戳设置整个日期
  2. UTC时间设置方法:设置 世界标准时间(UTC+0) 下的时间。

    javascript 复制代码
    date.setUTCFullYear(2023, 3, 15);
    date.setUTCMonth(3, 15);
    date.setUTCDate(15);
    date.setUTCHours(13, 30, 0, 0);
    date.setUTCMinutes(30, 0, 0);
    date.setUTCSeconds(0, 0);
    date.setUTCMilliseconds(0);

Date 对象常用静态方法

  1. Date.now(),获取当前时间的 UTC 时间戳(毫秒数)。

    js 复制代码
    let timestamp = Date.now(); // 当前时间戳
  2. Date.parse(dateString),解析日期字符串,返回对应的时间戳。

    • 此处按解析规则返回 UTC 时间戳 或 当前系统时间戳,具体查看 时间戳形式 new Date(timestamp)
    js 复制代码
    let date = Date.parse("2023-04-15"); // 只带日期,UTC 时间
    let date = Date.parse("2023-04-15T00:00:00"); // 包含时间,本地时间
    let date = Date.parse("2023-04-15T00:00:00+08:00"); // 带时区 +08:00 表示东八区。
  3. Date.UTC(year, month, ...),接受与 new Date() 相同的多个参数,但返回 UTC 时间戳。

    js 复制代码
    const utcTimestamp = Date.UTC(2023, 0, 1);

以上方法可以结合 时间戳形式 new Date(timestamp),进行对象创建。

js 复制代码
const utcTimestamp = Date.UTC(2023, 0, 1); // 1672531200000
const dateFromUTC = new Date(utcTimestamp); // 转为 Date 对象

自动校准特性

自动校准Date 对象的一个非常方便的特性。Date对象会自动处理超出范围的数值,将超出值转化:

javascript 复制代码
let date = new Date(2023, 0, 32); // 1月32日 → 自动转为2月1日
date.setDate(0); // 设置为上个月的最后一天

日期与数值转换

Date对象可以转换为数值(时间戳):

javascript 复制代码
let date = new Date();
let timestamp = +date; // 等同于date.getTime()

所以 Date 对象之间可以用 number 的运算方法进行时间间隔测量。

js 复制代码
let start = Date.now();

// 执行一些操作
for (let i = 0; i < 100000; i++) {
  let calc = i * i * i;
}

let end = Date.now();
console.log(`耗时: ${end - start}毫秒`);

格式化输出

  1. 基本格式化方法

    1. date.toString():返回本地时间的完整字符串(包含时区)。

      js 复制代码
      new Date().toString();
      // "Mon Oct 10 2023 14:30:45 GMT+0800 (中国标准时间)"
    2. date.toUTCString():返回 UTC 时间的完整字符串。

      js 复制代码
      new Date().toUTCString();
      // "Mon, 10 Oct 2023 06:30:45 GMT"
    3. date.toISOString():返回 ISO 8601 格式的 UTC 时间字符串。

      js 复制代码
      new Date().toISOString();
      // "2023-10-10T06:30:45.123Z"
    4. date.toDateString():返回本地时间的日期部分(不含时间)。

      js 复制代码
      new Date().toDateString();
      // "Mon Oct 10 2023"
    5. date.toTimeString():返回本地时间的时间部分(含时区)。

      js 复制代码
      new Date().toTimeString();
      // "14:30:45 GMT+0800 (中国标准时间)"
  2. 本地化格式化方法

    1. toLocaleString()返回本地化的日期和时间(根据系统语言)。

      javascript 复制代码
      new Date().toLocaleString();
      // "2023/10/10 14:30:45"(中文环境)
      // "10/10/2023, 2:30:45 PM"(英文环境)
    2. toLocaleDateString()返回本地化的日期部分。

      javascript 复制代码
      new Date().toLocaleDateString();
      // "2023/10/10"(中文)
      // "10/10/2023"(英文)
    3. toLocaleTimeString()返回本地化的时间部分。

      javascript 复制代码
      new Date().toLocaleTimeString();
      // "14:30:45"(中文)
      // "2:30:45 PM"(英文)
  3. 自定义本地化格式

    基本语法:new Intl.DateTimeFormat(locales, options).format(date)

    • locales (可选): 语言代码,如 'zh-CN'(中文)、'en-US'(英文)

    • options (可选): 配置对象,控制日期时间的显示方式

      属性 可选值 说明
      year 'numeric', '2-digit' 年份
      month 'numeric', '2-digit', 'long', 'short', 'narrow' 月份
      day 'numeric', '2-digit' 日期
      weekday 'long', 'short', 'narrow' 星期
      hour 'numeric', '2-digit' 小时
      minute 'numeric', '2-digit' 分钟
      second 'numeric', '2-digit'
      timeZone 'Asia/Shanghai', 'UTC', 'America/New_York' 指定时区
      hour12 true(12小时制), false(24小时制) 是否使用 AM/PM
      timeZoneName 'short', 'long' 时区显示
    • date: 要格式化的 Date 对象

    javascript 复制代码
    const date = new Date();
    const options = {
      year: 'numeric',
      month: 'long',
      day: 'numeric',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      timeZoneName: 'short'
    };
    
    new Intl.DateTimeFormat('zh-CN', options).format(date);
    // "2023年10月10日 14时30分45秒 GMT+8"

JSON 方法

**JSON(JavaScript Object Notation)**是一种轻量级的数据交换格式,是表示值和对象的通用格式。

最初它是为 JavaScript 而创建的,但许多其他编程语言也开始使用 JSON 进行数据交换。

JSON 基于 JavaScript 对象语法,但独立于语言, RFC 4627 标准对其进行了规范。

JSON 格式需满足:

  • 必须是字符串格式;
  • 键必须使用双引号包裹(单引号会报错),值除数字外需双引号包裹。

JSON 转换方法

JavaScript 提供了两个核心方法:

  1. JSON.stringify(obj, replacer, space) - 将对象转换为 JSON 字符串
    • obj 要编码的对象。
    • replacer (可选)过滤器参数。
    • space (可选)格式化参数。
  2. JSON.parse(str, reviver) - 将 JSON 字符串转换回对象
    • str 要编码的字符串。
    • reviver (可选)还原器参数。
数据类型转换
  1. stringify 转换为字符串时
    • 支持的数据类型 :对象{ ... }、数组[ ... ]、字符串、数字、布尔值、Datenull
    • 其他数据类型会被忽略 ,如:functionSymbolundefinedMapSet
    • 重要的限制:不得有循环引用,若出现则会抛出错误,转换失败
  2. parse 转回对象时
    • 各种支持的数据类型会被转为对应的数据结构,除了 Date 仍保持字符串形式。
过滤器:replacer

JSON.stringify的第二个参数replacer可传入数组或映射函数,实现过滤或者替换效果。

  1. 接收数组:若对象中的键不存在于传入的数组,则过滤掉。

    js 复制代码
    // 此处创建一个循环引用,在之后转换时,使用 replacer 过滤掉循环引用
    let room = {
      number: 23
    };
    let meetup = {
      title: "Conference",
      participants: [{name: "John"}, {name: "Alice"}],
      place: {number: 23},
    };
    room.occupiedBy = meetup; // room 循环引用了 meetup
    
    // 此处转换过于严格。过滤规则应用于整个对象结构,导致 participants 为空
    alert( JSON.stringify(meetup, ['title', 'participants']) );
    // {"title":"Conference","participants":[{},{}]}
    
    // 此处转换传入除了会导致循环引用的 room.occupiedBy 之外的所有属性
    alert( JSON.stringify(meetup, ['title', 'participants', 'place', 'name', 'number']) );
    /*{
      "title":"Conference",
      "participants":[{"name":"John"},{"name":"Alice"}],
      "place":{"number":23}
    }*/
  2. 传入函数 :该函数会调用每个键值对(包括嵌套对象和数组项),返回修改后的值,将原有值替换。如果值被跳过了,则为 undefined

    js 复制代码
    // 此处创建一个循环引用,在之后转换时,使用 replacer 过滤掉循环引用
    let room = {
      number: 23
    };
    
    let meetup = {
      title: "Conference",
      participants: [{name: "John"}, {name: "Alice"}],
      place: room // meetup 引用了 room
    };
    
    room.occupiedBy = meetup; // room 引用了 meetup
    
    // 通过映射函数找出循环引用,将其转为 undefined。
    alert( JSON.stringify(meetup, function replacer(key, value) {
      alert(`${key}: ${value}`);
      return (key == 'occupiedBy') ? undefined : value;
    }));
格式化:space

JSON.stringify的第三个参数space仅用于日志记录和美化输出。

可传入一个数字或字符串,不输入的话转换的字符串不带格式(即一行输出)。

  1. 传入数字:将格式缩进为空格,传入每次缩进的空格数量,最大值为 10。

    js 复制代码
    let user = {
      name: "John",
      roles: {
        isAdmin: false,
      }
    };
    
    // 两个空格的缩进:
    alert(JSON.stringify(user, null, 2));
    /*{
      "name": "John",
      "roles": {
        "isAdmin": false
      }
    }*/
  2. 传入字符串:将格式缩进转为字符串内容,缩进数固定为 1。

    js 复制代码
    // 用制表符 \t 缩进
    alert(JSON.stringify(user, null, '\t'));
    
    // 随意传入一个字符串作为缩进内容
    alert(JSON.stringify(user, null, '123'));
    /*{
    123"name": "John",
    123"roles": {
    123123"isAdmin": false
    123}
    /*}
还原器 riviver

JSON.parse的第二个参数 riviver接收一个函数,对值进行转换处理。

js 复制代码
// 通过还原器将日期字符串正确转化为日期对象
const jsonString = '{"name":"John","birth":"1990-01-01T00:00:00.000Z"}';

const obj = JSON.parse(jsonString, (key, value) => {
  if (key === 'birth') return new Date(value);
  return value;
});

自定义转换规则 toJSON

toString 进行字符串转换,对象也可以提供 toJSON 方法来进行 JSON 转换。

toJSON() 应该返回一个可以被 JSON.stringify() 进一步处理的值,比如返回一个对象,而不是已经字符串化的 JSON。

如果toJSON可用,JSON.stringify 会自动调用它。

js 复制代码
let room = {
  number: 23,
  toJSON() {
    return { number: this.number };
  }
};

alert( JSON.stringify(room) ); // {"number":23}

对于一些没有 toJSON 的数据结构,如 Map ,可以尝试添加 toJSON 方法。

js 复制代码
// 默认情况下 Map 无法转为 JSON
const map = new Map([['a', 1], ['b', 2]]);
console.log(JSON.stringify(map)); // "{}"

// 添加 toJSON,返回转为 Object 的 map 内容。
map.toJSON = function(){
    return Object.fromEntries(this);
}
alert( JSON.stringify(map) ) // {"a":1,"b":2}

也可以尝试在其原型prototype上添加该方法(不推荐,可能会与之后的版本更新冲突)。

js 复制代码
Map.prototype.toJSON = function() {
    return Object.fromEntries(this);
}

Set 可以尝试转为数组。

js 复制代码
Set.prototype.toJSON = function() {
    return Array.from(this);
}

let set = new Set(["oranges", "apples"]);
alert( JSON.stringify(set) ) // ["oranges","apples"]
相关推荐
互联网搬砖老肖38 分钟前
Selenium2+Python自动化:利用JS解决click失效问题
javascript·python·自动化
pink大呲花1 小时前
使用 Axios 进行 API 请求与接口封装:打造高效稳定的前端数据交互
前端·vue.js·交互
samuel9181 小时前
uniapp通过uni.addInterceptor实现路由拦截
前端·javascript·uni-app
泯泷1 小时前
JavaScript随机数生成技术实践 | 为什么Math.random不是安全的随机算法?
前端·javascript·安全
benben0442 小时前
Unity3D仿星露谷物语开发35之锄地动画
前端·游戏·游戏引擎
WebInfra2 小时前
🔥 Midscene 重磅更新:支持 AI 驱动的 Android 自动化
android·前端·测试
八了个戒2 小时前
「数据可视化 D3系列」入门第八章:动画效果详解(让图表动起来)
开发语言·前端·javascript·数据可视化
八了个戒2 小时前
「数据可视化 D3系列」入门第九章:交互式操作详解
javascript·信息可视化·数据可视化·d3
拉不动的猪3 小时前
无缝适配 PC 和移动端‌我们要注意哪些点呢
前端·javascript·面试
酱酱们的每日掘金4 小时前
🔥 4 月精选:AICoding Cursor上新与 MCP 实战揭秘!- AI Coding 周刊第 5 期
前端·ai编程·mcp