Javascript 更多引用类型
- Javascript 编程语言
- Javascript 基础知识
- Javascript 更多引用类型
- Javascript 函数进阶知识
- Javascript 引用类型进阶知识
- Javascript 面向对象
- Javascript 错误处理
- Javascript Promise与async
- Javascript 生成器 Generator
- Javascript 模块 Module
- 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;
。
数组的操作方法
更多创建方法
-
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)//[[],[],[]] // 还可以接收迭代器对象,详细见迭代器部分。
- 第二个参数可选,接收一个映射函数
-
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 展开运算符转变迭代器对象,详细见迭代器部分。
- 当调用
对原数组进行直接修改的方法(原地操作)
头尾操作方法
-
arr.push()
- 末尾添加元素,返回新长度 -
arr.pop()
- 删除并返回最后一个元素 -
arr.unshift()
- 开头添加元素,返回新长度 -
arr.shift()
- 删除并返回第一个元素-
push
和unshift
方法都可以一次添加多个元素jslet 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
。数组元素越多,花费时间越长,意味着内存操作越多。
-
数组转换
-
使用
delete
删除(不推荐):这是 Object 的方法,删除元素后对应位置变为undefined
,但数组length
不变,且元素位置没有改变。jslet arr = [3, 1, 2] delete arr[2] // true console.log(arr) // [3, 1, undefined]
-
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]
- 从索引
-
arr.sort(cmpFunc)
:原地排序- 可以传入一个比较函数,自定义比较规则。
- 比较函数的工作规则为检测负数 (a 应该排在 b 前面),0 (a 和 b 顺序不变),正数(b 应该排在 a 前面)
jsconst 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]
-
arr.reverse()
:反转数组js[1, 2, 3].reverse(); // [3, 2, 1]
-
arr.fill(item, start, end)
:将数组中指定范围的元素转变为传入的item
参数。start
默认为 0,end
默认为数组长度。
jsnew Array(3).fill(0); // [0, 0, 0] [1,2,3,4].fill(5,1,3); // [1, 5, 5, 4]
不修改原数组的方法(返回新数组)
数组转换
-
arr1.concat(arr2)
:将多个数组合并,返回一个新数组。- 参数内支持用逗号分隔,传入多个数组。
jslet arr = [1, 2].concat([3, 4], [5, 6]); // [1, 2, 3, 4, 5, 6]
-
arr.slice(start, end)
:从数组中提取指定范围的元素,返回一个新数组。start
默认为 0,end
默认为数组长度。
jslet arr = [1, 2, 3, 4]; let arr1 = arr.slice(1, 3); // [2, 3] let arr2 = arr.slice(); // [1, 2, 3, 4] 可用此方法快捷复制数组
-
arr.join(separator)
:将数组中的所有元素,通过分隔符连接成一个字符串。jslet str = ['a', 'b', 'c'].join('-'); // "a-b-c"
-
arr.toReversed()
:返回一个新数组,是反转后的原数组javascriptlet arr = [1, 2, 3].toReversed(); // [3, 2, 1]
-
arr.toSorted(cmpFunc)
:返回一个新数组,是排序后的原数组- 可以传入一个比较函数,参考
arr.sort
。
javascriptlet arr = [3, 1, 2].toSorted(); // [1, 2, 3]
- 可以传入一个比较函数,参考
-
arr.toSpliced()
:返回一个新数组,是增删改后的原数组javascriptlet arr = [1, 2, 3].toSpliced(1, 1, 4); // [1, 4, 3]
搜寻方法
-
根据传入 item 搜索
-
arr.indexOf(item, from)
:从索引from
开始搜索item
,返回找到的第一个元素的索引,找不到返回-1
。javascript[1, 2, 3].indexOf(2); // 1
-
arr.lastIndexOf(item, from)
:从索引from
开始搜索item
,返回找到的最后一个元素的索引,找不到返回-1
。javascript[1, 2, 1].lastIndexOf(1); // 2
-
-
根据条件迭代搜索
-
arr.find(func)
:查找第一个满足条件的元素,并返回元素值。- 接收一个类似
Array.from
内的函数function(item, index, array) {}
。 find
接收的函数内,若返回true
则返回当前的item
并停止迭代,若返回false
则返回undefined
。
javascriptconst numbers = [5, 12, 8, 130, 44]; // 搜索索引大于 1,且值大于 10 的第一个元素 const found = numbers.find((num,index,array) => { return index > 1 && num > 10; }); console.log(found); // 130
- 接收一个类似
-
arr.findIndex(func)
:与find
功能类似,但返回的是找到的第一个满足条件的元素的索引。 -
arr.findLast(func)
:与find
功能类似,但返回的是找到的最后一个满足条件的元素本身。 -
arr.findLastIndex(func)
:与find
功能类似,但返回的是找到的最后一个满足条件的元素的索引。
-
条件满足
-
arr.includes(item, from)
:从索引from
开始搜索item
,如果找到则返回true
。javascript[1, 2, 3].includes(2); // true
-
arr.every(func)
:是否所有元素满足条件- 类似
find
接收函数function(item, index, array) {}
javascript[1, 2, 3].every(x => x > 0); // true
- 类似
-
arr.some(func)
:是否有元素满足条件- 类似
find
接收函数function(item, index, array) {}
javascript[1, 2, 3].some(x => x > 2); // true
- 类似
迭代与转换
-
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']
- 类似
-
arr.forEach(func)
:遍历数组- 类似
find
接收函数function(item, index, array) {}
。 forEach
没有返回值。
javascriptlet arr = ["Bilbo", "Gandalf", "Nazgul"] arr.forEach((item, index, array) => { alert(`${item} is at index${index} in ${array}`); });
- 类似
-
arr.filter(func)
:过滤数组- 类似
find
接收函数function(item, index, array) {}
。 - 将满足条件的元素组成一个新的数组并返回。
javascriptlet arr = [1, 2, 3].filter(x => x > 1); // [2, 3]
- 类似
-
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)
- 接收一个函数
-
arr.reduceRight(func,initial)
:从右到左归约,reduce
的反向版本。javascriptlet arr = [1, 2, 3]; let result = arr.reduceRight((a, b) => a - b); alert(result); // 0 (3-2=1, 1-1=0)
-
arr.flat(depth)
:数组扁平化- 可以将多层嵌套的数组拆解,传入参数
depth
控制扁平化深度,默认为 1。
javascriptlet arr = [1, [2, [3, 4]]] let arr1 = arr.flat(); // [1, 2, [3, 4]] let arr2 = arr.flat(2); // [1, 2, 3, 4]
- 可以将多层嵌套的数组拆解,传入参数
-
arr.flatMap()
:映射后扁平化- 无论回调返回多少层嵌套,都只扁平化 1 层,无法控制深度。
- 类似
find
接收函数function(item, index, array) {}
。
javascriptconst arr = [1, 2, 3]; // 此处不能带嵌套 const result = arr.flatMap(x => [x, x * 2]); console.log(result); // [1, 2, 2, 4, 3, 6]
其他方法
-
toString()
- 转为字符串,数组有自己的toString
方法的实现,会返回以逗号隔开的元素列表。jslet 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()
时能修改上一层词法环境记录的当前索引。
迭代器的运行步骤:
- 我们需要为进行迭代的对象添加一个名为
Symbol.iterator
的方法; - 当
for..of
循环启动时,它会调用这个方法。该方法返回一个记录索引、包含next()
方法的对象; - 当
for..of
循环希望取得下一个数值,它就调用这个对象的next()
方法; - 等到
next()
返回done:true
,for..of
循环结束。
比如一个
range
对象,它代表了一个数字区间 (from
-to
),我们希望它在循环中输出范围内的所有整数。
jslet 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
对象自身。
jslet 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
等数组方法。如果我们有一个这样的对象,并想像数组那样操作它,那就非常不方便。
若想在这两种对象中使用数组方法,解决办法有两个:
-
将其转换为数组
-
Array.from
:这个方法也能使用迭代器进行转换(类数组在之前数组部分已讲解)。js// 转变之前的 range 示例 let arr = Array.from(range); // [1, 2, 3, 4, 5]
-
Array.of
:利用 ES6 的展开运算符转变可迭代对象(类数组不可)。js// 展开之前的 range 示例 let arr = Array.of(...range); // [1, 2, 3, 4, 5]
-
-
使用函数借用(具体在函数章节讲解)
映射 Map
Map 是一种带键的数据项集合,类似于对象(Object),都是用来存储键值对结构。
但其与对象又有着关键区别:
- Object 的键只能是字符串或Symbol,而 Map 允许任何类型的键。
- Object 可以用 字面量 或 构造函数 new 创建,Map 一般用 new。
- Object 的属性赋值取值直接用
object.prop
的方式,Map 则用get
/set
的方式。 - Object 的 key 顺序有特殊规则,Map 的 key 顺序就是插入顺序。
基础使用方法
map.set(key, value)
------ 存储键值对,并返回 map 本身。map.get(key)
------ 获取键对应的值(不存在则返回undefined
)map.has(key)
------ 检查键是否存在(返回布尔值)map.delete(key)
------ 删除指定键值对map.clear()
------ 清空整个Mapmap.size
------ 返回当前元素数量
每一次 map.set
调用都会返回 map 本身,所以我们可以进行链式调用。
初始化创建
-
先创建一个空 Map ,然后用 set 链式调用传入数据:
jslet map = (new Map()) .set('a', 500) // 字符串键 .set(1, 'num1') // 数字键 .set(true, 'bool'); // 布尔值键
-
直接在 Map 创建时通过二维数组传入数据:
jslet 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互转
-
从对象创建 Map
方法
Object.entries(obj)
返回对象的键/值对数组,该数组格式与Map
所需格式相符。jslet obj = { name: "John", age: 30 }; let arr = Object.entries(obj); // '[['name','John'],['age',30]]' let map = new Map(arr);
-
从 Map 创建对象
方法
Object.fromEntries
的作用是相反的:给定一个具有[key, value]
键值对的数组,它会根据给定数组创建一个对象jslet 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()
------ 清空整个 Setset.size
------ 返回集合中元素的数量
初始化创建
-
先创建一个空 Set ,然后用 add 链式调用传入数据:
jslet set = (new Set()) .add("oranges") .add("apples") .add("bananas");
-
直接在 Set 创建时通过数组传入数据:
jslet 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
对象。
-
无参形式 :
new Date()
不带参数,获取当前日期和时间javascriptlet now = new Date(); // 创建表示当前日期和时间的对象
-
时间戳形式 :
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
- 其时间等于
-
字符串形式 :
new Date(datestring)
传入一个字符串,使用日期字符串创建,该字符串会被自动解析。- 字符串格式为
"年-月-日T时:分:秒+/-时区"
"YYYY-MM-DD"
(只带日期,视为 UTC 时间,时间默认为00:00:00
)"YYYY-MM-DDTHH:mm:ss"
(带日期与时间,视为本地时间)"YYYY-MM-DDTHH:mm:ssZ"
(带Z
或时区,如+08:00
)
javascriptlet 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 表示东八区。
- 字符串格式为
-
多参数形式 :在
Date
中传入多项对应的参数,使用组件创建- 语法:
new Date(year, month, date, hours, minutes, seconds, ms)
year
: 四位数年份、month
: 0(一月)到11(十二月)、date
: 1到31(默认为1)、hours
/ ``minutes/
seconds/
ms` 默认为0。
javascriptlet date = new Date(2023, 3, 15, 13, 30, 0, 0); // 2023年4月15日13:30:00
- 语法:
注意:以上创建方法具有时区依赖差异。
- 依赖当前系统时区(中国是 UTC+8):无参形式、多参数形式、带时区的字符串形式
- 依赖世界标准时间(UTC+0 时区):时间戳形式、不带时区的字符串形式
访问日期组件
-
本地时间方法:返回 当前系统时区 下的时间(如中国用户默认是东八区 UTC+8)。受用户电脑/服务器时区设置影响,同一代码在不同时区运行结果不同。
javascriptlet 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=星期日)
-
UTC时间方法:返回 世界标准时间(UTC+0) 下的时间,无视本地时区,全球统一结果。
javascriptdate.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)
-
获取当前系统时区与 UTC+0 时间的分钟差:
javascriptlet diff = date.getTimezoneOffset();
-
访问时间戳:返回的是 自 1970-01-01 00:00:00 UTC 以来的毫秒数(即 Unix 时间戳),不依赖任何时区。
jslet ms = date.getTime();
-
虽然
getTime()
不依赖时区,但解析结果会受new Date()
影响,因为new Date()
依赖时区。 -
时间戳本身是可靠的,但构造
Date
对象时需注意输入格式。jsnew Date("2023-01-01").getTime(); // 1672531200000 UTC 时区 new Date(2023, 0, 1).getTime(); // 1672502400000 当前系统时区
-
设置日期组件
-
本地时间设置方法:设置 当前系统时区 下的时间(如中国用户默认是东八区 UTC+8)。
javascriptlet 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); // 使用时间戳设置整个日期
-
UTC时间设置方法:设置 世界标准时间(UTC+0) 下的时间。
javascriptdate.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 对象常用静态方法
-
Date.now()
,获取当前时间的 UTC 时间戳(毫秒数)。jslet timestamp = Date.now(); // 当前时间戳
-
Date.parse(dateString)
,解析日期字符串,返回对应的时间戳。- 此处按解析规则返回 UTC 时间戳 或 当前系统时间戳,具体查看 时间戳形式
new Date(timestamp)
。
jslet 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 表示东八区。
- 此处按解析规则返回 UTC 时间戳 或 当前系统时间戳,具体查看 时间戳形式
-
Date.UTC(year, month, ...)
,接受与new Date()
相同的多个参数,但返回 UTC 时间戳。jsconst utcTimestamp = Date.UTC(2023, 0, 1);
以上方法可以结合 时间戳形式
new Date(timestamp)
,进行对象创建。
jsconst 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}毫秒`);
格式化输出
-
基本格式化方法
-
date.toString()
:返回本地时间的完整字符串(包含时区)。jsnew Date().toString(); // "Mon Oct 10 2023 14:30:45 GMT+0800 (中国标准时间)"
-
date.toUTCString()
:返回 UTC 时间的完整字符串。jsnew Date().toUTCString(); // "Mon, 10 Oct 2023 06:30:45 GMT"
-
date.toISOString()
:返回 ISO 8601 格式的 UTC 时间字符串。jsnew Date().toISOString(); // "2023-10-10T06:30:45.123Z"
-
date.toDateString()
:返回本地时间的日期部分(不含时间)。jsnew Date().toDateString(); // "Mon Oct 10 2023"
-
date.toTimeString()
:返回本地时间的时间部分(含时区)。jsnew Date().toTimeString(); // "14:30:45 GMT+0800 (中国标准时间)"
-
-
本地化格式化方法
-
toLocaleString()
返回本地化的日期和时间(根据系统语言)。javascriptnew Date().toLocaleString(); // "2023/10/10 14:30:45"(中文环境) // "10/10/2023, 2:30:45 PM"(英文环境)
-
toLocaleDateString()
返回本地化的日期部分。javascriptnew Date().toLocaleDateString(); // "2023/10/10"(中文) // "10/10/2023"(英文)
-
toLocaleTimeString()
返回本地化的时间部分。javascriptnew Date().toLocaleTimeString(); // "14:30:45"(中文) // "2:30:45 PM"(英文)
-
-
自定义本地化格式
基本语法:
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
对象
javascriptconst 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 提供了两个核心方法:
JSON.stringify(obj, replacer, space)
- 将对象转换为 JSON 字符串obj
要编码的对象。replacer
(可选)过滤器参数。space
(可选)格式化参数。
JSON.parse(str, reviver)
- 将 JSON 字符串转换回对象str
要编码的字符串。reviver
(可选)还原器参数。
数据类型转换
- stringify 转换为字符串时
- 支持的数据类型 :对象
{ ... }
、数组[ ... ]
、字符串、数字、布尔值、Date
、null
- 其他数据类型会被忽略 ,如:
function
、Symbol
、undefined
、Map
、Set
- 重要的限制:不得有循环引用,若出现则会抛出错误,转换失败
- 支持的数据类型 :对象
- parse 转回对象时
- 各种支持的数据类型会被转为对应的数据结构,除了
Date
仍保持字符串形式。
- 各种支持的数据类型会被转为对应的数据结构,除了
过滤器:replacer
JSON.stringify
的第二个参数replacer
可传入数组或映射函数,实现过滤或者替换效果。
-
接收数组:若对象中的键不存在于传入的数组,则过滤掉。
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} }*/
-
传入函数 :该函数会调用每个键值对(包括嵌套对象和数组项),返回修改后的值,将原有值替换。如果值被跳过了,则为
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
仅用于日志记录和美化输出。
可传入一个数字或字符串,不输入的话转换的字符串不带格式(即一行输出)。
-
传入数字:将格式缩进为空格,传入每次缩进的空格数量,最大值为 10。
jslet user = { name: "John", roles: { isAdmin: false, } }; // 两个空格的缩进: alert(JSON.stringify(user, null, 2)); /*{ "name": "John", "roles": { "isAdmin": false } }*/
-
传入字符串:将格式缩进转为字符串内容,缩进数固定为 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"]