Javascript|JavaScript权威指南7(四) 第十一章 JavaScript 标准库

Set和Map类
用于表示一组值以及从一组值到另一组值的映射。
set 是值得集合,就像数组。但不同于数组,set 没有被排序或索引,并且它们不允许重复:值要么是集合的成员要么不是集合的成员,不能知道一个值在集合中出现多少次。
使用 Set() 创建 Set 对象
let s = new Set(); // 空set let t = new Set([1,s]); // 一set里有两个

Set() 构造函数的实参不必是数组:允许任何可迭代的对象
let t = new Set(s); // 复制s元素的新集合 let unique = new Set('Mississippi'); // 4个元素:“M”、“i”、“s”和“p”

set的 size 属性类似于数组的 length 属性,告诉你该集合包含多少值
unique.size // 4

创建set时无需初始化,可以随时使用add(), delete(), clear() 添加输出元素。集合不能包含重复项,因此添加已经包含在该 set 中的值是一个无效操作。
add() 方法有一个实参。如果传递数组,它将数组本身而不是单个数组元素添加到set中。add() 始终返回调用它的set,因此,如果要向 set 添加多个值,则可以使用方法链调用。
s.add('a').add('b').add'('c');

delete() 方法一次也只输出一个 set 元素。与add() 不同,delete() 返回一个布尔值。如果指定的值是 set 的成员,则会将其输出并返回 true,它不执行任何操作返回 false。
set 成员关系是基于严格的相等性检查 (=) , 数字1和字符串’1’是不同的值。对象或数组或函数它们也将用(=) 进行比较,这就是此代码集中为什么无法从删除数组元素的原因。为了使它起作用,必须传递完全相同的数组引用。
实际上,对 set 进行的最重要的操作不是在元素中添加和删除元素,而是检查指定的值是否是集合的成员。使用 has()。
关于 set 最重要的了解是,它们已针对成员资格测试进行了优化,并且无论 set 有多少成员,has() 方法都将非常快。数组的 includes() 方法也执行成员资格测试,但是要花费的时间与数组的大小成正比,并且将数组作为 set 使用比使用真正的 Set 对象要慢得多。
Set 类是可迭代的,这意味着可以使用for/of循环枚举集合中的所有元素。
也可以使用 … 展开运算符将它们转换为数组和实参列表。
JavaScript Set 未索引,无法像使用数组那样获取 Set 的第一个或第三个元素。但是它会始终记住插入元素的顺序,并且在遍历集合时始终使用此顺序,插入的第一个元素将是第一个遍历的元素,并且,最后插入的元素将是最后一个遍历的元素。
除了可迭代之外, Set 类还实现了一个 forEach() 方法,该方法类似于同名的 array方法。
数组的 forEach 将数组索引作为指定函数的第二个实参传递。集合没有索引,因此此方法的 Set 类版本仅将元素值作为第一个和第二个实参传递。
let p = new Set([1,8,5,4]) p.forEach((n,i)=>{ console.log(n,i); // 1 1 8 8 5 5 4 4 })

Map
Map 对象代表一组成为键的值,其中每个键具有与其关联的另一个值。从某种意义上讲,映射就像一个数组,但是映射不是使用一组连续的整数作为键,而是允许我们使用任意值作为索引。像数组一样,映射是快速的,无论映射有多大查找与键关联的值都很快(尽管不如索引数组快)。
let m = new Map(); // 创建一个新的map let n = new Map([['one',1],['two',2]]); // 用映射到数字的字符串键初始化的新映射

Map 构造函数的可选实参应该是一个可迭代对象,该对象可产生两个元素[key,value]数组。实际上,这意味着如果要在创建 map 时初始化,通常会将所需的键和映射的值写为数组的数组。但是,也可以使用map构造函数复制其他 map,或者从现有对象复制属性名称和值。
let copy = new Map(n); let o = {x:1, y:2}; // 具有两个属性的对象 let p = new Map(Object.entries(o)); // map([["x", 1], ["y", 2]])

创建 Map 对象后,可以用 get() 查询给定键的值,并可以用 set() 添加新的键值对。但是请记住,map 是一组键,每个键映射一个值。这与一组键值对不太相同。如果调用 set() 添加一个 map 中已存在的键的键值对,会改变这个键映射的值,而不是添加一个新的键值对映射。除了 get() 和 set(),Map 类也定义了类似于 Set 的方法:使用 has() 判断 map 是否包含指定键;使用 delete() 删除 map 中的一个键(和它映射的值);使用 clear() 来移除 map 中所有的键值对;使用 size 属性知道 map 中包含多少个键。
像 Set 的 add() 方法一样,Map 的 set() 方法也可以链式使用,这允许在不使用数组的情况下初始化 map:
let m = new Map().set("one", 1).set("two", 2).set("three", 3); m.size// => 3 m.get("two")// => 2

与 Set 一样,任何 JavaScript 的值都可以被用作 Map 的键或值。包括 null、undefined 和 NaN,也包括像对象和数组这种引用类型。并且和 Set 类一样,Map 通过标识来比较,不是通过相等,所以如果想要使用对象或数组作为键,它们会被认为不同于任何其他的对象和数组,即使那些属性或元素完全相同的引用:
let m = new Map(); // Start with an empty map. m.set({}, 1); // Map one empty object to the number 1. m.set({}, 2); // Map a different empty object to the number 2. m.size// => 2: there are two keys in this map m.get({})// => undefined: but this empty object is not a key m.set(m, undefined); // Map the map itself to the value undefined. m.has(m)// => true: m is a key in itself m.get(m)// => undefined: same value we'd get if m wasn't a key

Map 是可迭代对象,每一个迭代值是两个元素数组,第一个元素是键,第二个元素是键映射的值。如果对 Map 对象使用展开运算符,会得到一个数组的数组,就像传递给 Map() 构造函数的实参。当使用 for/of 循环遍历一个 map 时,常使用解构赋值将键和值赋值给展开变量:
let m = new Map([["x", 1], ["y", 2]]); [...m]// => [["x", 1], ["y", 2]]for(let [key, value] of m) { // 在第一次迭代中,键为“x”,值为1 // 在第二次迭代中,键为“y”,值为2 }

与 Set 类一样,Map 类按照插入顺序进行遍历。遍历的第一个键值对将是最早添加到 map 中的,而遍历的最后一个键值对将是最新添加的。
如果只想遍历 map 中的键或者映射的值,使用 keys() 和 values() 方法:它们返回按照插入顺序排列的键或值可迭代对象。(entries() 方法返回一个迭代对象,该对象迭代键值对,但这与直接迭代 map 完全相同。)
[...m.keys()]// => ["x", "y"]: just the keys [...m.values()]// => [1, 2]: just the values [...m.entries()]// => [["x", 1], ["y", 2]]: same as [...m]

Map 对象也可以使用 forEach() 方法进行遍历
m.forEach((value, key) => {// note value, key NOT key, value // On the first invocation, value will be 1 and key will be "x" // On the second invocation, value will be 2 and key will be "y" });

上面的代码中 value 参数位于 key 参数之前,这似乎很奇怪,因为对于 for/of 迭代,key 放在前面。如本节开头所述,可以将映射视为通用数组,其中整数数组索引被替换为任意键值。数组的 forEach() 方法首先传递数组元素,然后传递数组索引,因此,以此类推,map 的 forEach() 方法首先传递 map 的值,然后传递 map 键。
WeakMap 和 WeakSet
WeakMap 类是 Map 类的变体(但不是真正的子类),它不会阻止其键值被垃圾回收。垃圾回收是 JavaScript 解释器回收不再“可访问”并且无法由程序使用的对象的内存的过程。常规 map 保留对其键值的“强”引用,即使对它们的所有其他引用都已消失,它们仍然可以通过映射访问。相比之下,WeakMap 保留对其键值的“弱”引用,以使它们无法通过 WeakMap 获得,并且它们在 map 中的存在也不会阻止对其内存的回收。
WeakMap() 构造函数类似 Map() 构造函数,但是有一些重要的不同:
WeakMap 键必须是对象或数组;原始值不受垃圾回收的限制,不能用作键。
WeakMap 仅实现 get()、set()、has() 和 delete() 方法。 特别是,WeakMap 是不可迭代的,并且未定义 keys()、values() 或 forEach()。如果 WeakMap 是可迭代的,则其键将是可访问的,这让它不会“弱”。
同样,WeakMap 也不实现 size 属性,因为随着对象被垃圾回收,WeakMap 的大小可能随时更改。
WeakMap 的预期用途是允许将值与对象相关联而不会引起内存泄漏。例如,假设正在编写一个带有对象实参的函数,并且需要对该对象执行一些耗时的计算。为了提高效率,希望将计算出的值进行缓存以备后用。如果使用 Map 对象实现缓存,则将防止回收任何对象,但是通过使用 WeakMap,可以避免此问题。
WeakSet 实现了一组对象,这些对象不会阻止垃圾回收这些对象。 WeakSet() 构造函数的工作方式类似于 Set() 构造函数,但 WeakSet 对象与 Set 对象的区别与 WeakMap 对象与 Map 对象的区别相同:
WeakSet 不允许原始值作为成员。
WeakSet 仅实现 add()、has() 和 delete() 方法,并且不可迭代。
WeakSet 没有 size 属性。
WeakSet 并不经常使用:其用例类似于 WeakMap 的用例。例如,如果要标记(或“烙印”)对象具有某些特殊属性或类型,则可以将其添加到 WeakSet 中。然后,在其他位置,当要检查该属性或类型时,可以测试该 WeakSet 中的成员身份。使用常规 Set 执行此操作将防止所有标记的对象被垃圾回收,但这在使用 WeakSet 时不必担心。
类数组对象 成为 TypedArrays
表示二进制数据的数组,以及从非数组二进制数据中提取值的相关类
常规 JavaScript 数组可以具有任何类型的元素,并且可以动态增长或收缩。JavaScript 实现执行了大量优化,因此 JavaScript 数组的典型用法非常快。但是,它们仍然与诸如 C 和 Java 的低级语言的数组类型有很大不同。 ES6 [3] 中新增的类型化数组更接近那些语言的低级数组。从类型上讲,类型化数组不是数组(Array.isArray() 为其返回false),但是,它们在某些非常重要的方面与常规数组有所不同:
类型化数组的元素都是数字。 但是,与常规 JavaScript 数字不同,类型化数组允许指定要存储在数组中的数字的类型(有符号和无符号整数以及 IEEE-754 浮点数)和大小(8 位至 64 位)。
创建类型化数组时,必须指定其长度,并且该长度永远不会改变。
创建数组时,类型化数组的元素始终会初始化为 0。
JavaScript 没有定义 TypedArray 类。相反,有11种类型化数组,每种类型具有不同的元素类型和构造函数。
创建类型化数组的最简单方法是使用一个数字参数调用适当的构造函数,该参数指定数组中所需的元素数量:
let bytes = new Uint8Array(1024); // 1024 bytes let matrix = new Float64Array(9); // A 3x3 matrix let point = new Int16Array(3); // A point in 3D space let rgba = new Uint8ClampedArray(4); // A 4-byte RGBA pixel value let sudoku = new Int8Array(81); // A 9x9 sudoku board

正则表达式和 RegExp 类
定义了文本模式,对文本处理很有用。
省略…
Date
用于表示和操作日期和时间的类.
Date 类是用于处理日期和时间的 JavaScript API。 使用 Date() 构造函数创建一个 Date 对象。没有实参,它将返回一个 Date 对象,该对象代表当前日期和时间:
let now = new Date();

如果传递一个数字实参,则 Date() 构造函数将该参数解释为自 1970 年以来的毫秒数
let epoch = new Date(0); // Midnight, January 1st, 1970, GMT

如果指定两个或多个整数参数实参,则它们将被解释为本地时区中的年、月、日、小时、分钟、秒和毫秒,如下所示:
let century = new Date(2100,// Year 2100 0,// January 1,// 1st 2, 3, 4, 5); // 02:03:04.005, local time

Date API 的一个怪癖是一年的第一个月是数字 0,而一个月的第一天是数字 1。如果省略了时间字段,则 Date() 构造函数会将它们全部默认为 0,并将时间设置为到午夜。
请注意,当使用多个数字调用时,Date() 构造函数使用本地计算机设置为的时区解释它们。如果要以UTC(协调世界时,又称为GMT)指定日期和时间,则可以使用 Date.UTC()。 此静态方法采用与 Date() 构造函数相同的参数,以 UTC 解释它们,并返回可以传递给 Date() 构造函数的毫秒级时间戳:
let century = new Date(Date.UTC(2100, 0, 1));

最后,如果将字符串传递给 Date() 构造函数,它将尝试解析该字符串作为日期和时间规范。构造函数可以解析以 toString()、toUTCString() 和 toISOString() 方法产生的格式指定的日期:
let century = new Date("2100-01-01T00:00:00Z"); // An ISO format date

有了 Date 对象后,可以使用各种 get 和 set 方法查询和修改日期的年、月、日、小时、分钟、秒和毫秒字段。这些方法中的每一种都有两种形式:一种使用本地时间获取或设置,另一种使用 UTC 时间获取或设置。例如,要获取或设置 Date 对象的年份,可以使用 getFullYear()、getUTCFullYear()、setFullYear() 或 setUTCFullYear():
let d = new Date(); // Start with the current date d.setFullYear(d.getFullYear() + 1); // Increment the year

要获取或设置日期的其他字段,请将方法名称中的“FullYear”替换为“Month”、“Date”、“Hours”、“Minutes”、“Seconds”或“Milliseconds”。一些日期设置方法可以一次设置多个字段。 setFullYear() 和 setUTCFullYear() 还可以选择设置月和日。setHours() 和 setUTCHours() 允许除了指定小时数字段之外,还指定分钟,秒和毫秒字段。
请注意,查询日期的方法是 getDate() 和 getUTCDate()。听起函数 getDay() 和 getUTCDay() 更像查询日期,但是它们返回星期几( 0 为星期日,星期六为 6)。星期几是只读的,因此没有相应的 setDay() 方法。
JavaScript 在内部将日期表示为整数,指定自 1970 年 1 月 1 日午夜(或之前)以来的毫秒数,UTC 时间。支持大到 8,640,000,000,000,000 的整数,因此 JavaScript 在 270,000 年之内都不会用完毫秒。
对于任何 Date 对象,getTime() 方法返回这个内部值,setTime() 方法设置它。因此,可以使用如下代码为 Date 添加 30 秒,例如:
d.setTime(d.getTime() + 30000);

高分辨率时间戳
Date.now() 返回的时间戳以毫秒为单位。对于计算机而言,毫秒实际上是一个相对较长的时间,有时可能希望以更高的精度测量经过的时间。performance.now() 函数允许这样做:它还返回基于毫秒的时间戳,但返回值不是整数,因此它包含毫秒的小数部分。performance.now() 返回的值不是像 Date.now() 值那样的绝对时间戳。相反,它只是指示自加载网页或自 Node 进程启动以来已经过去了多少时间。
日期对象可以与 JavaScript 的标准<, <=, >, and >=比较运算符进行比较。可以从另一个 Date 对象中减去一个 Date 对象,以确定两个日期之间的毫秒数。(这是因为 Date 类定义了一个返回时间戳的 valueOf() 方法。)
如果想从日期中添加或减去指定的秒数、分钟数或小时数,通常最简单的方法是简单地修改时间戳,如前面示例中所示,当我们将 30 秒添加到日期时。如果你想增加天数,这种技术会变得更加麻烦,而且它在数月和数年内根本不起作用,因为它们有不同的天数。要进行涉及日、月和年的日期运算,可以使用 setDate()、setMonth() 和 setYear()。例如,这里的代码将当前日期增加了三个月和两周:
let d = new Date(); d.setMonth(d.getMonth() + 3, d.getDate() + 14);

日期设置方法即使溢出也能正常工作。当我们将三个月添加到当前月份时,我们最终会得到一个大于 11 的值(代表 12 月)。setMonth() 通过根据需要增加年份来处理这个问题。同样,当我们将月份中的日期设置为大于月份中的天数时,月份会适当增加。
如果使用 Date 类来实际跟踪日期和时间(而不是仅仅测量时间间隔),那么可能需要向代码的用户显示日期和时间。Date 类定义了许多用于将 Date 对象转换为字符串的不同方法。
toString()
此方法使用本地时区,但不以可识别区域设置的方式格式化日期和时间。
toUTCString()
此方法使用 UTC 时区,但不以可识别区域设置的方式格式化日期。
toISOString()
此方法以 ISO-8601 标准的标准年-月-日时:分:秒.ms 格式打印日期和时间。字母“T”将输出的日期部分与输出的时间部分分开。时间以 UTC 表示,并用字母“Z”作为输出的最后一个字母来表示。
toLocaleString()
此方法使用本地时区和适合用户区域设置的格式。
toDateString()
此方法仅格式化 Date 的日期部分并省略时间。它使用本地时区,并且不进行适合区域设置的格式设置。
toLocaleDateString()
此方法仅格式化日期。它使用本地时区和适合区域设置的日期格式。
toTimeString()
此方法仅格式化时间并省略日期。它使用本地时区,但不以区域设置方式格式化时间。
toLocaleTimeString()
此方法以可识别区域设置的方式格式化时间并使用本地时区。
在格式化显示给最终用户的日期和时间时,这些日期到字符串的方法都不是理想的。
最后,除了这些将 Date 对象转换为字符串的方法之外,还有一个静态 Date.parse() 方法,它以字符串为参数,尝试将其解析为日期和时间,并返回一个时间戳表示那个日期。Date.parse() 能够解析 Date() 构造函数可以解析的相同字符串,并且保证能够解析 toISOString()、toUTCString() 和 toString() 的输出。
11.5 错误类别
Error类及其各种子类
在JavaScript程序中发生错误时抛出它们的实例。
avaScript throw 和 catch 语句可以抛出和捕获任何 JavaScript 值,包括原始值。没有必须用于发出错误信号的异常类型。然而,JavaScript 确实定义了一个 Error 类,并且在使用 throw 发出错误信号时,通常使用 Error 或子类的实例。使用 Error 对象的一个??很好的理由是,当创建 Error 时,它会捕获 JavaScript 堆栈的状态,如果未捕获异常,堆栈跟踪将与错误消息一起显示,这将帮助调试问题。(请注意,堆栈跟踪显示了 Error 对象的创建位置,而不是 throw 语句将其抛出的位置。如果总是在使用 throw new Error() 抛出该对象之前创建该对象,则不会造成任何混淆。)
错误对象有两个属性:消息和名称,以及一个 toString() 方法。message 属性的值是传递给 Error() 构造函数的值,必要时转换为字符串。对于使用 Error() 创建的错误对象,名称属性始终为“Error”。toString() 方法只返回 name 属性的值,后跟一个冒号和空格以及 message 属性的值。
尽管它不是 ECMAScript 标准的一部分,但 Node 和所有现代浏览器也在 Error 对象上定义了一个堆栈属性。此属性的值是一个多行字符串,其中包含创建 Error 对象时 JavaScript 调用堆栈的堆栈跟踪。当捕获到意外错误时,这可能是记录的有用信息。
除了 Error 类之外,JavaScript 还定义了许多子类,用于指示 ECMAScript 定义的特定类型的错误。这些子类是 EvalError、RangeError、ReferenceError、SyntaxError、TypeError 和 URIError。如果它们看起来合适,可以在自己的代码中使用这些错误类。与基本的 Error 类一样,这些子类中的每一个都有一个构造函数,该构造函数接受一个消息参数。每个子类的实例都有一个 name 属性,其值与构造函数名称相同。
应该随意定义自己的错误子类,以最好地封装自己程序的错误条件。请注意,不仅限于名称和消息属性。如果创建子类,则可以定义新属性以提供错误详细信息。例如,如果正在编写解析器,可能会发现使用指定解析失败的确切位置的行和列属性定义 ParseError 类很有用。或者,如果正在处理 HTTP 请求,可能希望定义一个 HTTPError 类,该类具有一个包含失败请求的 HTTP 状态代码(例如 404 或 500)的状态属性。
class HTTPError extends Error { constructor(status, statusText, url) { super(`${status} ${statusText}: ${url}`); this.status = status; this.statusText = statusText; this.url = url; }get name() { return "HTTPError"; } }let error = new HTTPError(404, "Not Found", "http://example.com/"); error.status// => 404 error.message// => "404 Not Found: http://example.com/" error.name// => "HTTPError"

JSON对象,及其方法支持对象,数组,字符串,数字和布尔值
JavaScript数据结构序列化和反序列化。
当一个程序需要保存数据或需要通过网络连接向另一个程序传输数据时,它必须将其内存中的数据结构转换为可以保存或传输的字节或字符的字符串,然后再解析以恢复原始的内存数据结构。这种将数据结构转换为字节或字符流的过程称为序列化(或编组,甚至酸洗)。
在 JavaScript 中序列化数据的最简单方法是使用称为 JSON 的序列化格式。这个首字母缩写词代表“JavaScript Object Notation”,顾名思义,该格式使用 JavaScript 对象和数组字面量语法将由对象和数组组成的数据结构转换为字符串。JSON 支持原始数字和字符串以及值 true、false 和 null,以及从这些原始值构建的数组和对象。JSON 不支持其他 JavaScript 类型,如 Map、Set、RegExp、Date 或类型化数组。尽管如此,它已被证明是一种非常通用的数据格式,即使在非基于 JavaScript 的程序中也很常见。
JavaScript 使用两个函数 JSON.stringify() 和 JSON.parse() 支持 JSON 序列化和反序列化。给定一个不包含任何不可序列化值(如 RegExp 对象或类型化数组)的对象或数组(任意深度嵌套),可以简单地通过将对象传递给 JSON.stringify() 来序列化该对象。顾名思义,这个函数的返回值是一个字符串。并给定 JSON.stringify() 返回的字符串,可以通过将字符串传递给 JSON.parse() 来重新创建原始数据结构:
let o = {s: "", n: 0, a: [true, false, null]}; let s = JSON.stringify(o); // s == '{"s":"","n":0,"a":[true,false,null]}' let copy = JSON.parse(s); // copy == {s: "", n: 0, a: [true, false, null]}

如果我们省略将序列化数据保存到文件或通过网络发送的部分,我们可以使用这对函数作为创建对象深层副本的一种效率较低的方法:
// Make a deep copy of any serializable object or array function deepcopy(o) { return JSON.parse(JSON.stringify(o)); }

JSON 是 JAVASCRIPT 的子集
当数据被序列化为 JSON 格式时,结果是表达式的有效 JavaScript 源代码,计算结果为原始数据结构的副本。如果使用 var data = https://www.it610.com/article/作为 JSON 字符串的前缀并将结果传递给 eval(),将获得分配给变量 data 的原始数据结构的副本。但是,永远不应该这样做,因为这是一个巨大的安全漏洞——如果攻击者可以将任意 JavaScript 代码注入 JSON 文件,他们就可以让的程序运行他们的代码。只使用 JSON.parse() 来解码 JSON 格式的数据会更快更安全。
JSON 有时用作人类可读的配置文件格式。如果发现自己在手动编辑 JSON 文件,请注意 JSON 格式是 JavaScript 的一个非常严格的子集。不允许使用注释,并且属性名称必须用双引号引起来,即使 JavaScript 不需要这样做。
通常,只将一个参数传递给 JSON.stringify() 和 JSON.parse()。这两个函数都接受一个可选的第二个参数,它允许我们扩展 JSON 格式,这些将在下面进行描述。JSON.stringify() 还接受我们将首先讨论的可选第三个参数。如果希望 JSON 格式的字符串是人类可读的(例如,如果它被用作配置文件),那么应该传递 null 作为第二个参数,并传递一个数字或字符串作为第三个参数。第三个参数告诉 JSON.stringify() 它应该在多个缩进行上格式化数据。如果第三个参数是一个数字,那么它将为每个缩进级别使用该数量的空格。如果第三个参数是一个空格字符串(例如 ‘\t’),它将使用该字符串进行每一级缩进。
let o = {s: "test", n: 0}; JSON.stringify(o, null, 2)// => '{\n"s": "test",\n"n": 0\n}'

JSON.parse() 忽略空格,因此将第三个参数传递给 JSON.stringify() 不会影响我们将字符串转换回数据结构的能力。
国际化 API
JavaScript 国际化 API 由三个类 Intl.NumberFormat、Intl.DateTimeFormat 和 Intl.Collat??or 组成,它们允许我们以适合区域设置的方式格式化数字(包括货币金额和百分比)、日期和时间,并在区域设置中比较字符串- 适当的方式。这些类不是 ECMAScript 标准的一部分,但被定义为 ECMA402 标准的一部分,并且受到 Web 浏览器的良好支持。Node 也支持 Intl API,但在撰写本文时,预构建的 Node 二进制文件未附带使它们与美国英语以外的语言环境一起工作所需的本地化数据。因此,为了在 Node 中使用这些类,可能需要下载单独的数据包或使用自定义构建的 Node.js。
格式化数字
Intl.NumberFormat 类定义了一个 format() 方法,该方法考虑了所有这些格式化可能性。构造函数有两个参数。第一个参数指定数字应该被格式化的语言环境,第二个是一个对象,它指定关于数字应该如何格式化的更多细节。如果第一个参数被省略或未定义,则将使用系统语言环境(我们假设它是用户的首选语言环境)。如果第一个参数是字符串,则它指定所需的语言环境,例如“en-US”(美国使用的英语)、“fr”(法语)或“zh-Hans-CN”(中文,使用简体汉文字系统,在中国)。第一个参数也可以是语言环境字符串数组,在本例中为 Intl。
Intl.NumberFormat() 构造函数的第二个参数(如果指定)应该是定义以下一个或多个属性的对象:
style
指定所需的数字格式类型。默认值为“十进制”。指定“percent”以将数字格式化为百分比,或指定“currency”以将数字指定为金额。
货币
如果样式为“货币”,则此属性需要指定所需货币的三字母 ISO 货币代码(例如“USD”代表美元或“GBP”代表英镑)。
currencyDisplay
如果样式为“货币”,则此属性指定货币的显示方式。如果货币有一个,默认值“symbol”使用货币符号。值“code”使用三个字母的 ISO 代码,值“name”以长格式拼出货币名称。
useGrouping
如果不希望数字有千位分隔符(或其与区域设置对应的等效项),请将此属性设置为 false。
minimumIntegerDigits
用于显示数字整数部分的最小位数。如果数字的位数少于此数字,则将在左侧填充零。默认值为 1,但可以使用高达 21 的值。
minimumFractionDigits, maximumFractionDigits
这两个属性控制数字小数部分的格式。如果一个数字的小数位数少于最小值,它将在右侧用零填充。如果大于最大值,则小数部分将被舍入。两个属性的合法值都在 0 到 20 之间。默认最小值为 0,默认最大值为 3,除非在格式化货币金额时,小数部分的长度会因指定的货币而异。
minimumSignificantDigits, maximumSignificantDigits
这些属性控制格式化数字时使用的有效位数,例如,它们适用于格式化科学数据。如果指定,这些属性将覆盖前面列出的整数和小数位属性。合法值介于 1 到 21 之间。
一旦创建了具有所需语言环境和选项的 Intl.NumberFormat 对象,可以通过将一个数字传递给它的 format() 方法来使用它,该方法返回一个适当格式化的字符串。例如:
let euros = Intl.NumberFormat("es", {style: "currency", currency: "EUR"}); euros.format(10)// => "10,00 €": ten euros, Spanish formattinglet pounds = Intl.NumberFormat("en", {style: "currency", currency: "GBP"}); pounds.format(1000) // => "£1,000.00": One thousand pounds, English formatting

Intl.NumberFormat(以及其他 Intl 类)的一个有用特性是它的 format() 方法绑定到它所属的 NumberFormat 对象。因此,无需定义引用格式化对象的变量,然后对其调用 format() 方法,只需将 format() 方法分配给变量并像使用独立函数一样使用它,如本例所示:
let data = https://www.it610.com/article/[0.05, .75, 1]; let formatData = Intl.NumberFormat(undefined, { style:"percent", minimumFractionDigits: 1, maximumFractionDigits: 1 }).format; data.map(formatData)// => ["5.0%", "75.0%", "100.0%"]: in en-US locale

格式化日期和时间
Intl.DateTimeFormat 类很像 Intl.NumberFormat 类。Intl.DateTimeFormat() 构造函数采用与 Intl.NumberFormat() 相同的两个参数:一个语言环境或语言环境数组以及一个格式化选项对象。使用 Intl.DateTimeFormat 实例的方式是调用它的 format() 方法将 Date 对象转换为字符串。
此属性指定时区应如何以格式化的日期或时间显示。使用“long”表示完整拼写的时区名称,使用“short”表示缩写或数字时区。
hour12
此布尔属性指定是否使用 12 小时制时间。默认值取决于区域设置,但可以使用此属性覆盖它。
hourCycle
此属性允许指定午夜是写为 0 小时、12 小时还是 24 小时。默认值取决于区域设置,但可以使用此属性覆盖默认值。请注意,hour12 优先于此属性。使用值“h11”指定午夜为 0,午夜前的一小时为晚上 11 点。使用“h12”指定午夜为 12 点。使用“h23”指定午夜为 0 点,午夜前一小时为 23 点。使用“h24”指定午夜为 24 点。
例子:
let d = new Date("2020-01-02T13:14:15Z"); // January 2nd, 2020, 13:14:15 UTC// With no options, we get a basic numeric date format Intl.DateTimeFormat("en-US").format(d) // => "1/2/2020" Intl.DateTimeFormat("fr-FR").format(d) // => "02/01/2020"// Spelled out weekday and month let opts = { weekday: "long", month: "long", year: "numeric", day: "numeric" }; Intl.DateTimeFormat("en-US", opts).format(d) // => "Thursday, January 2, 2020" Intl.DateTimeFormat("es-ES", opts).format(d) // => "jueves, 2 de enero de 2020"// The time in New York, for a French-speaking Canadian opts = { hour: "numeric", minute: "2-digit", timeZone: "America/New_York" }; Intl.DateTimeFormat("fr-CA", opts).format(d) // => "8 h 14"

Intl.DateTimeFormat 可以使用基于基督教时代的默认儒略历以外的日历显示日期。尽管某些语言环境可能默认使用非基督教日历,但始终可以通过在语言环境中添加 -u-ca- 并在其后加上日历名称来明确指定要使用的日历。可能的日历名称包括“佛教”、“中国”、“科普特”、“埃塞俄比亚”、“格雷戈里”、“希伯来”、“印度”、“伊斯兰”、“iso8601”、“日本”和“波斯”。继续前面的例子,我们可以确定各种非基督教历法中的年份:
let opts = { year: "numeric", era: "short" }; Intl.DateTimeFormat("en", opts).format(d)// => "2020 AD" Intl.DateTimeFormat("en-u-ca-iso8601", opts).format(d)// => "2020 AD" Intl.DateTimeFormat("en-u-ca-hebrew", opts).format(d)// => "5780 AM" Intl.DateTimeFormat("en-u-ca-buddhist", opts).format(d)// => "2563 BE" Intl.DateTimeFormat("en-u-ca-islamic", opts).format(d)// => "1441 AH" Intl.DateTimeFormat("en-u-ca-persian", opts).format(d)// => "1398 AP" Intl.DateTimeFormat("en-u-ca-indian", opts).format(d)// => "1941 Saka" Intl.DateTimeFormat("en-u-ca-chinese", opts).format(d)// => "36 78" Intl.DateTimeFormat("en-u-ca-japanese", opts).format(d)// => "2 Reiwa"

比较字符串
与 Intl.NumberFormat() 和 Intl.DateTimeFormat() 一样,Intl.Collat??or() 构造函数采用两个参数。第一个指定一个语言环境或一个语言环境数组,第二个是一个可选对象,其属性准确地指定要进行哪种字符串比较。支持的属性如下:
// A basic comparator for sorting in the user's locale. // Never sort human-readable strings without passing something like this: const collator = new Intl.Collator().compare; ["a", "z", "A", "Z"].sort(collator)// => ["a", "A", "z", "Z"]// Filenames often include numbers, so we should sort those specially const filenameOrder = new Intl.Collator(undefined, { numeric: true }).compare; ["page10", "page9"].sort(filenameOrder)// => ["page9", "page10"]// Find all strings that loosely match a target string const fuzzyMatcher = new Intl.Collator(undefined, { sensitivity: "base", ignorePunctuation: true }).compare; let strings = ["food", "fool", "F?? Bar"]; strings.findIndex(s => fuzzyMatcher(s, "foobar") === 0)// => 2

一些语言环境有不止一种可能的排序规则。例如,在德国,电话簿使用的语音排序顺序比字典稍微多一些。在西班牙,在 1994 年之前,“ch”和“ll”被视为单独的字母,因此该国家现在有现代排序顺序和传统排序顺序。在中国,排序顺序可以基于字符编码、每个字符的基本部首和笔画,或者基于字符的拼音罗马化。这些排序规则变体不能通过 Intl.Collat??or 选项参数选择,但可以通过将 -u-co- 添加到语言环境字符串并添加所需变体的名称来选择。例如,在德国订购电话簿时使用“de-DE-u-co-phonebk”,在台湾订购拼音时使用“zh-TW-u-co-pinyin”。
// Before 1994, CH and LL were treated as separate letters in Spain const modernSpanish = Intl.Collator("es-ES").compare; const traditionalSpanish = Intl.Collator("es-ES-u-co-trad").compare; let palabras = ["luz", "llama", "como", "chico"]; palabras.sort(modernSpanish)// => ["chico", "como", "llama", "luz"] palabras.sort(traditionalSpanish) // => ["como", "chico", "luz", "llama"]

Console对象
其方法输出字符串的方式对调试程序和记录这些程序的行为特别有用。
控制台 API 定义了以下函数:
console.log()
这是最著名的控制台函数。它将其参数转换为字符串并将它们输出到控制台。它包括参数之间的空格,并在输出所有参数后开始新行。
console.debug()、console.info()、console.warn()、console.error()
这些函数几乎和console.log()一样。在 Node 中,console.error() 将其输出发送到 stderr 流而不是 stdout 流,但其他函数是 console.log() 的别名。在浏览器中,这些函数中的每一个生成的输出消息都可能带有一个指示其级别或严重性的图标作为前缀,并且开发人员控制台还可以允许开发人员按级别过滤控制台消息。
console.assert()
如果第一个参数是真的(即,如果断言通过),那么这个函数什么也不做。但是如果第一个参数是假的或者另一个假值,那么剩下的参数就会被打印出来,就好像它们被传递给了带有“断言失败”前缀的 console.error() 一样。请注意,与典型的 assert() 函数不同,console.assert() 在断言失败时不会抛出异常。
console.clear()
此函数在可能时清除控制台。当 Node 将其输出显示到终端时,这在浏览器和 Node 中有效。但是,如果 Node 的输出已重定向到文件或管道,则调用此函数无效。
console.table()
这个函数是一个非常强大但鲜为人知的生成表格输出的功能,它在需要生成汇总数据的输出的 Node 程序中特别有用。console.table() 尝试以表格形式显示它的参数(尽管,如果它不能这样做,它会使用常规的 console.log() 格式显示它)。当参数是一个相对较短的对象数组并且数组中的所有对象都具有相同(相对较小)的一组属性时,这种方法效果最好。在这种情况下,数组中的每个对象都被格式化为表格的一行,每个属性都是表格的一列。还可以将属性名称数组作为可选的第二个参数传递,以指定所需的列集。如果你传递一个对象而不是一个对象数组,那么输出将是一个表,其中一列用于属性名称,一列用于属性值。或者,如果这些属性值本身是对象,则它们的属性名称将成为表中的列。
console.trace()
这个函数像 console.log() 一样记录它的参数,此外,它的输出跟在堆栈跟踪后面。在 Node 中,输出到 stderr 而不是 stdout。
console.count()
此函数接受一个字符串参数并记录该字符串,然后是使用该字符串调用它的次数。这在调试事件处理程序时很有用,例如,如果需要跟踪事件处理程序被触发的次数。
console.countReset()
此函数接受一个字符串参数并重置该字符串的计数器。
console.group()
此函数将其参数打印到控制台,就好像它们已传递给 console.log(),然后设置控制台的内部状态,以便所有后续控制台消息(直到下一个 console.groupEnd() 调用) 将相对于它刚刚打印的消息缩进。这允许一组相关的消息在视觉上用缩进进行分组。在 Web 浏览器中,开发人员控制台通常允许将分组的消息作为一个组折叠和展开。console.group() 的参数通常用于为组提供解释性名称。
console.groupCollapsed()
这个函数的工作方式与 console.group() 类似,除了在 Web 浏览器中,默认情况下该组将被“折叠”并且它包含的消息将被隐藏,除非用户单击以展开该组。在 Node 中,这个函数是 console.group() 的同义词。
console.groupEnd()
这个函数没有参数。它不会产生自己的输出,但会结束由最近调用 console.group() 或 console.groupCollapsed() 引起的缩进和分组。
console.time()
这个函数接受一个字符串参数,记下用那个字符串调用它的时间,并且不产生任何输出。
console.timeLog()
这个函数接受一个字符串作为它的第一个参数。如果该字符串先前已传递给 console.time(),则它会打印该字符串,然后打印自 console.time() 调用以来经过的时间。如果 console.timeLog() 有任何额外的参数,它们会被打印出来,就好像它们已经传递给了 console.log()。
console.timeEnd()
这个函数接受一个字符串参数。如果该参数之前已传递给 console.time(),那么它会打印该参数和经过的时间。在调用了console.timeEnd()之后,再调用console.timeLog()而不先调用console.time()是不合法的。
URL类
简化了解析和操作URL的任务。
由于 JavaScript 在 Web 浏览器和 Web 服务器中如此普遍,因此 JavaScript 代码需要操作 URL 是很常见的。URL 类解析 URL,还允许修改(例如,添加搜索参数或更改路径)现有 URL。它还可以正确处理转义和取消转义 URL 的各种组件的复杂主题。
URL 类不是任何 ECMAScript 标准的一部分,但它适用于 Node 和除 Internet Explorer 之外的所有 Internet 浏览器。它在https://url.spec.whatwg.org标准化。
使用 URL() 构造函数创建一个 URL 对象,传递一个绝对 URL 字符串作为参数。或者将相对 URL 作为第一个参数传递,将其相对的绝对 URL 作为第二个参数传递。创建 URL 对象后,它的各种属性允许查询 URL 各个部分的未转义版本:
let url = new URL("https://example.com:8000/path/name?q=term#fragment"); url.href// => "https://example.com:8000/path/name?q=term#fragment" url.origin// => "https://example.com:8000" url.protocol// => "https:" url.host// => "example.com:8000" url.hostname// => "example.com" url.port// => "8000" url.pathname// => "/path/name" url.search// => "?q=term" url.hash// => "#fragment"

虽然不常用,但 URL 可以包含用户名或用户名和密码,并且 URL 类也可以解析这些 URL 组件:
let url = new URL("ftp://admin:1337!@ftp.example.com/"); url.href// => "ftp://admin:1337!@ftp.example.com/" url.origin// => "ftp://ftp.example.com" url.username// => "admin" url.password// => "1337!"

这里的 origin 属性是 URL 协议和主机的简单组合(如果指定了端口,则包括端口)。因此,它是一个只读属性。但是前面示例中演示的其他每个属性都是读/写的:可以设置这些属性中的任何一个来设置 URL 的相应部分:
let url = new URL("https://example.com"); // Start with our server url.pathname = "api/search"; // Add a path to an API endpoint url.search = "q=test"; // Add a query parameter url.toString()// => "https://example.com/api/search?q=test"

URL 类的重要特性之一是它可以在需要时正确添加标点符号并转义 URL 中的特殊字符:
let url = new URL("https://example.com"); url.pathname = "path with spaces"; url.search = "q=foo#bar"; url.pathname// => "/path%20with%20spaces" url.search// => "?q=foo%23bar" url.href// => "https://example.com/path%20with%20spaces?q=foo%23bar"

setTimeOut和相关函数
用于指定的时间间隔后执行的代码。
从 JavaScript 的早期开始,Web 浏览器就定义了两个函数——setTimeout() 和 setInterval()——允许程序在指定的时间过去后要求浏览器调用一个函数,或者在指定的时间重复调用该函数间隔。这些函数从未被标准化为核心语言的一部分,但它们可以在所有浏览器和 Node 中运行,并且是 JavaScript 标准库的事实上的一部分。
setTimeout() 的第一个参数是一个函数,第二个参数是一个数字,指定在调用函数之前应该经过多少毫秒。在指定的时间量之后(如果系统繁忙,可能会更长一点),该函数将在没有参数的情况下被调用。例如,这里是三个 setTimeout() 调用,它们在一秒、两秒和三秒后打印控制台消息:
setTimeout(() => { console.log("Ready..."); }, 1000); setTimeout(() => { console.log("set..."); }, 2000); setTimeout(() => { console.log("go!"); }, 3000);

请注意, setTimeout() 在返回之前不会等待时间过去。此示例中的所有三行代码几乎都立即运行,但在 1,000 毫秒过去之前什么都没有发生。
如果省略 setTimeout() 的第二个参数,则默认为 0。但这并不意味着指定的函数会立即被调用。相反,该函数被注册为“尽快”调用。如果浏览器特别忙于处理用户输入或其他事件,则调用该函数可能需要 10 毫秒或更长时间。
setTimeout() 注册一个要调用一次的函数。有时,该函数本身会调用 setTimeout() 来安排将来的另一个调用。然而,如果你想重复调用一个函数,使用 setInterval() 通常更简单。setInterval() 采用与 setTimeout() 相同的两个参数,但每次经过指定的毫秒数(大约)时都会重复调用该函数。
setTimeout() 和 setInterval() 都返回一个值。如果将此值保存在变量中,则可以稍后通过将其传递给 clearTimeout() 或 clearInterval() 来使用它来取消函数的执行。返回值通常是 Web 浏览器中的数字,并且是 Node.js 中的对象。实际类型无关紧要,应该将其视为不透明值。可以使用此值做的唯一事情是将其传递给 clearTimeout() 以取消使用 setTimeout() 注册的函数的执行(假设尚未调用它)或停止重复执行使用 setInterval 注册的函数()。
【Javascript|JavaScript权威指南7(四) 第十一章 JavaScript 标准库】下面的示例演示了如何使用 setTimeout()、setInterval() 和 clearInterval() 来通过 Console API 显示一个简单的数字时钟:
// Once a second: clear the console and print the current time let clock = setInterval(() => { console.clear(); console.log(new Date().toLocaleTimeString()); }, 1000); // After 10 seconds: stop the repeating code above. setTimeout(() => { clearInterval(clock); }, 10000); // We’ll see setTimeout() and setInterval() again when we cover asynchronous programming in Chapter 13.

    推荐阅读