进阶学习 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() - 删除并返回第一个元素

    • push 和 unshift 方法都可以一次添加多个元素

      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进行操作,并将每一次的结果搬运到下一次调用。
    • 若不传入 initial 则accumulator初始为第 1 个元素,遍历从第 2 个元素开始。若传入initial则accumulator初始为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"

几乎所有调用函数的数组方法 ------ 比如 find,filter,map,flatMap都在最后可接受一个可选的附加参数 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:true,for..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 属性)。

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

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

  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 转换为字符串时
    • 支持的数据类型 :对象{ ... }、数组[ ... ]、字符串、数字、布尔值、Date、null
    • 其他数据类型会被忽略 ,如:function、Symbol、undefined、Map、Set
    • 重要的限制:不得有循环引用,若出现则会抛出错误,转换失败
  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"]
相关推荐
jay神14 分钟前
基于 YOLOv8 的轴承缺陷检测(Web 版)
前端·人工智能·yolo
jay神22 分钟前
基于 YOLOv8 的森林火灾检测系统(Web 版)
前端·yolo
code2cat26 分钟前
HTML基础篇之第一张网页:从index.html到浏览器里的任务清单
前端·html
miss1 小时前
AI 改完不敢上线?OpenSpec 规约驱动开发实战
前端·代码规范·前端工程化
AiMan2 小时前
腾讯ima还是不可以导出左侧所有对话,只能单条导出,方法不对吗
前端
去伪存真2 小时前
并发量一高就装死?Agent 全链路“防猝死”手册
前端·agent
Moment2 小时前
AI 编程时代,聊聊我的 Mac mini 双屏桌面
前端·后端
AiMan2 小时前
先确认能不能一次把Microsofat Copilot所有内容打包一个文件导出
前端
总要学点什么2 小时前
第97篇:三维数据加载-城市建筑白模自定义着色器——让一条光带沿高度扫上去,快慢却由帧率说了算
前端·gis·webgl·cesium·着色器·空间分析·白模
上海懒图科技3 小时前
工厂厂区电子地图定位导航:厂区人车精准定位,安全合规双保障
前端·arcgis·vue