原始类型的方法

除 null 和 undefined 以外的原始类型都提供了许多有用的方法。
从形式上讲,这些方法通过临时对象工作。
原始类型不是对象,它们不能存储额外的数据。

数字类型

JavaScript 中的常规数字以 64 位的格式 IEEE-754 存储,也被称为“双精度浮点数”。这是我们大多数时候所使用的数字,我们将在本章中学习它们。

编写数字的更多方法

在 JavaScript 中,我们通过在数字后附加字母 “e”,并指定零的数量来缩短数字:

  1. let billion = 1e9; // 10 亿,字面意思:数字 1 后面跟 9 个 0
  2. alert( 7.3e9 ); // 73 亿(7,300,000,000)

换句话说,”e” 把数字乘以 1 后面跟着给定数量的 0 的数字。

  1. 1e3 = 1 * 1000
  2. 1.23e6 = 1.23 * 1000000

非常小的数字: e 后面的负数表示除以 1 后面跟着给定数量的 0 的数字:

  1. let ms = 1e-6; // 1 的左边有 6 个 0
  2. // -3 除以 1 后面跟着 3 个 0 的数字
  3. 1e-3 = 1 / 1000 (=0.001)
  4. // -6 除以 1 后面跟着 6 个 0 的数字
  5. 1.23e-6 = 1.23 / 1000000 (=0.00000123)

十六进制,二进制和八进制数字

十六进制 数字在 JavaScript 中被广泛用于表示颜色,编码字符以及其他许多东西。有一种较短的写方法:0x,然后是数字。

  1. alert( 0xff ); // 255
  2. alert( 0xFF ); // 255(一样,大小写没影响)

二进制和八进制数字系统很少使用,但也支持使用 0b 和 0o 前缀

  1. let a = 0b11111111; // 二进制形式的 255
  2. let b = 0o377; // 八进制形式的 255
  3. alert( a == b ); // true,两边是相同的数字,都是 255

只有这三种进制支持这种写法。对于其他进制,我们应该使用函数 parseInt

toString(base)

方法 num.toString(base) 返回在给定 base 进制数字系统中 num 的字符串表示形式。base 的范围可以从 2 到 36。默认情况下是 10
如果我们想直接在一个数字上调用一个方法,比如上面例子中的 toString,那么我们需要在它后面放置两个点 ..

alert( 123456..toString(36) ); // 2n9c

也可以写成 (123456).toString(36)

舍入

Math.floor 向下舍入:3.1 变成 3,-1.1 变成 -2
Math.ceil 向上舍入:3.1 变成 4,-1.1 变成 -1
Math.round 向最近的整数舍入:3.1 变成 3,3.6 变成 4,-1.1 变成 -1

不精确的计算

请确保记住使用小数时会损失精度。

alert( 0.1 + 0.2 ); // 0.30000000000000004

使用二进制数字系统无法 精确 存储 0.10.2,就像没有办法将三分之一存储为十进制小数一样。
我们能解决这个问题吗?当然,最可靠的方法是借助方法 toFixed(n) 对结果进行舍入:

let sum = 0.1 + 0.2;
alert( sum.toFixed(2) ); // 0.30

请注意,toFixed 总是返回一个字符串。它确保小数点后有 2 位数字。

测试:isFinite 和 isNaN

  • Infinity(和 -Infinity)是一个特殊的数值,比任何数值都大(小)。
  • NaN 代表一个 error。

它们属于 number 类型,但不是“普通”数字,因此,这里有用于检查它们的特殊函数:

  • isNaN(value) 将其参数转换为数字,然后测试它是否为 NaN:
    alert( isNaN(NaN) ); // true
    alert( isNaN("str") ); // true
    
    值 “NaN” 是独一无二的,它不等于任何东西,包括它自身, so我们不能只使用 === NaN 比较
    alert( NaN === NaN ); // false
    
    有时 isFinite 被用于验证字符串值是否为常规数字: ```javascript let num = +prompt(“Enter a number”, ‘’);

// 结果会是 true,除非你输入的是 Infinity、-Infinity 或不是数字 alert( isFinite(num) );

在所有数字函数中,包括 isFinite,空字符串或仅有空格的字符串均被视为 0
<a name="h6J49"></a>
## parseInt 和 parseFloat
使用加号 + 或 Number() 的数字转换是严格的。如果一个值不完全是一个数字,就会失败.<br />parseInt 和 parseFloat,它们可以从字符串中“读取”数字,直到无法读取为止。函数 parseInt 返回一个整数,而 parseFloat 返回一个浮点数:
```javascript
alert( parseInt('100px') ); // 100
alert( parseFloat('12.5em') ); // 12.5

alert( parseInt('12.3') ); // 12,只有整数部分被返回了
alert( parseFloat('12.3.4') ); // 12.3,在第二个点出停止了读取

某些情况下,parseInt/parseFloat 会返回 NaN。当没有数字可读时会发生这种情况:

alert( parseInt('a123') ); // NaN,第一个符号停止了读取

其他数学函数

avaScript 有一个内建的 Math 对象,它包含了一个小型的数学函数和常量库。

字符串

在 JavaScript 中,文本数据被以字符串形式存储,单个字符没有单独的类型。
字符串的内部格式始终是 UTF-16,它不依赖于页面编码。

引号(Quotes)

字符串可以包含在单引号、双引号或反引号中

let single = 'single-quoted';
let double = "double-quoted";

let backticks = `backticks`;

单引号和双引号基本相同。但是,反引号允许我们通过 ${…} 将任何表达式嵌入到字符串中:

function sum(a, b) {
  return a + b;
}

alert(`1 + 2 = ${sum(1, 2)}.`); // 1 + 2 = 3.

使用反引号的另一个优点是它们允许字符串跨行,单引号和双引号不能。

let guestList = `Guests:
 * John
 * Pete
 * Mary
`;

alert(guestList); // 客人清单,多行

特殊字符

我们仍然可以通过使用“换行符(newline character)”,以支持使用单引号和双引号来创建跨行字符串。换行符写作 \n,用来表示换行

let guestList = "Guests:\n * John\n * Pete\n * Mary";

alert(guestList); // 一个多行的客人列表

字符串长度

请注意 str.length 是一个数字属性,而不是函数。后面不需要添加括号。

访问字符

要获取在 pos 位置的一个字符,可以使用方括号 [pos] 或者调用 str.charAt(pos) 方法。第一个字符从零位置开始:

let str = `Hello`;

// 第一个字符
alert( str[0] ); // H
alert( str.charAt(0) ); // H

// 最后一个字符
alert( str[str.length - 1] ); // o

我们也可以使用 for..of 遍历字符:

for (let char of "Hello") {
  alert(char); // H,e,l,l,o(char 变为 "H",然后是 "e",然后是 "l" 等)
}

字符串是不可变的

通常的解决方法是创建一个新的字符串,并将其分配给 str 而不是以前的字符串。

let str = 'Hi';

str = 'h' + str[1];  // 替换字符串

alert( str ); // hi

查找子字符串

str.indexOf

它从给定位置 pos 开始,在 str 中查找 substr,如果没有找到,则返回 -1,否则返回匹配成功的位置。
还有一个类似的方法 str.lastIndexOf(substr, position),它从字符串的末尾开始搜索到开头。它会以相反的顺序列出这些事件。

includes,startsWith,endsWith

更现代的方法 str.includes(substr, pos) 根据 str 中是否包含 substr 来返回 true/false。

获取子字符串

JavaScript 中有三种获取字符串的方法:substring、substr 和 slice

  • str.slice(start [, end])

返回字符串从 start 到(但不包括)end 的部分。

  • str.substring(start [, end])

返回字符串在 start 和 end 之间 的部分。这与 slice 几乎相同,但它允许 start 大于 end。

  • str.substr(start [, length])

返回字符串从 start 开始的给定 length 的部分。与以前的方法相比,这个允许我们指定 length 而不是结束位置。

比较字符串

所有的字符串都使用 UTF-16 编码。即:每个字符都有对应的数字代码。有特殊的方法可以获取代码表示的字符,以及字符对应的代码。
String.fromCodePoint(code) 通过数字 code 创建字符:

alert( String.fromCodePoint(90) ); // Z

正确的比较

调用 str.localeCompare(str2) 会根据语言规则返回一个整数,这个整数能指示字符串 str 在排序顺序中排在字符串 str2 前面、后面、还是相同:

  • 如果 str 排在 str2 前面,则返回负数。
  • 如果 str 排在 str2 后面,则返回正数。
  • 如果它们在相同位置,则返回 0。

    数组

    声明

    创建一个空数组有两种语法:

    let arr = new Array();
    let arr = [];  //绝大多数情况下使用的都是第二种语法。
    

    length 属性的值是数组中元素的总个数。
    数组可以存储任何类型的元素。

    pop/push, shift/unshift 方法

    队列

    (queue)是最常见的使用数组的方法之一。

  • push 在末端添加一个元素.

  • shift 取出队列首端的一个元素,整个队列往前移,这样原先排第二的元素现在排在了第一

    数组还有另一个用例,就是数据结构 栈。

  • push 在末端添加一个元素.

  • pop 从末端取出一个元素.

    作用于数组末端的方法

    pop
    取出并返回数组的最后一个元素
    push
    在数组末端添加元素

    作用于数组首端的方法

    shift
    取出数组的第一个元素并返回它
    unshift
    在数组的首端添加元素

    内部

    在 JavaScript 中只有 8 种基本的数据类型。
    数组是一个对象,因此其行为也像一个对象。
    我们可以给它们添加任何属性。

    性能

    push/pop 方法运行的比较快,而 shift/unshift 比较慢。
    作用于数组的末端会比首端快。

shift 操作必须做三件事:

  1. 移除索引为 0 的元素。
  2. 把所有的元素向左移动,把索引 1 改成 0,2 改成 1 以此类推,对其重新编号。
  3. 更新 length 属性。

循环

遍历数组最古老的方式就是 for 循环:

let arr = ["Apple", "Orange", "Pear"];

for (let i = 0; i < arr.length; i++) {
  alert( arr[i] );
}

但对于数组来说还有另一种循环方式,for..of:

let fruits = ["Apple", "Orange", "Plum"];

// 遍历数组元素
for (let fruit of fruits) {
  alert( fruit );
}

for..of 不能获取当前元素的索引,只是获取元素值,但大多数情况是够用的。而且这样写更短。

通常来说,我们不应该用 for..in 来处理数组
虽然,技术上来讲,因为数组也是对象,所以使用 for..in 也是可以的:

let arr = ["Apple", "Orange", "Pear"];

for (let key in arr) {
  alert( arr[key] ); // Apple, Orange, Pear
}

但这其实是一个很不好的想法。会有一些潜在问题存在:

  1. for..in 循环会遍历 所有属性,不仅仅是这些数字属性。在浏览器和其它环境中有一种称为“类数组”的对象,它们 看似是数组。也就是说,它们有 length 和索引属性,但是也可能有其它的非数字的属性和方法,这通常是我们不需要的。for..in 循环会把它们都列出来。所以如果我们需要处理类数组对象,这些“额外”的属性就会存在问题。
  2. for..in 循环适用于普通对象,并且做了对应的优化。但是不适用于数组,因此速度要慢 10-100 倍。当然即使是这样也依然非常快。只有在遇到瓶颈时可能会有问题。但是我们仍然应该了解这其中的不同。


关于 “length”

当我们修改数组的时候,length 属性会自动更新。
准确来说,它实际上不是数组里元素的个数,而是最大的数字索引值加一。
如果我们减少它,数组就会被截断。该过程是不可逆的,下面是例子:

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

不要使用 == 比较数组

如果我们使用 == 来比较数组,除非我们比较的是两个引用同一数组的变量,否则它们永远不相等。
比较数组时,不要使用 == 运算符(当然也不要使用 > 和 < 等运算符),因为它们不会对数组进行特殊处理。它们通常会像处理任意对象那样处理数组。
仅当两个对象引用的是同一个对象时,它们才相等 ==。
那么,我们应该如何对数组进行比较呢?
很简单,不使用 == 运算符。但是,我们可以使用 for..of 循环来逐项比较数组。

数组方法

添加/移除数组元素

数组是对象,所以我们可以尝试使用 delete 这很正常,因为 delete obj.key 是通过 key 来移除对应的值。对于对象来说是可以的。但是对于数组来说,我们通常希望剩下的元素能够移动并占据被释放的位置。我们希望得到一个更短的数组。

splice

arr.splice 方法可以说是处理数组的瑞士军刀。它可以做所有事情:添加,删除和插入元素。

arr.splice(start[, deleteCount, elem1, ..., elemN])

它从索引 start 开始修改 arr:删除 deleteCount 个元素并在当前位置插入 elem1, …, elemN。最后返回已被删除元素的数组。

slice

arr.slice 方法比 arr.splice 简单得多。

arr.slice([start], [end])

它会返回一个新数组,将所有从索引 start 到 end(不包括 end)的数组项复制到一个新的数组。
start 和 end 都可以是负数,在这种情况下,从末尾计算索引.

let arr = ["t", "e", "s", "t"];

alert( arr.slice(1, 3) ); // e,s(复制从位置 1 到位置 3 的元素)

alert( arr.slice(-2) ); // s,t(复制从位置 -2 到尾端的元素)

concat

arr.concat(arg1, arg2...)

它接受任意数量的参数 —— 数组或值都可以。
结果是一个包含来自于 arr,然后是 arg1,arg2 的元素的新数组。

遍历:forEach

arr.forEach 方法允许为数组的每个元素都运行一个函数。

// 对每个元素调用 alert
["Bilbo", "Gandalf", "Nazgul"].forEach(alert);

["Bilbo", "Gandalf", "Nazgul"].forEach((item, index, array) => {
  alert(`${item} is at index ${index} in ${array}`);
});

在数组中搜索

arr.indexOf、arr.lastIndexOf 和 arr.includes

arr.indexOfarr.lastIndexOfarr.includes 方法与字符串操作具有相同的语法,并且作用基本上也与字符串的方法相同,只不过这里是对数组元素而不是字符进行操作:

  • arr.indexOf(item, from) 从索引 from 开始搜索 item,如果找到则返回索引,否则返回 -1。
  • arr.lastIndexOf(item, from) —— 和上面相同,只是从右向左搜索。
  • arr.includes(item, from) —— 从索引 from 开始搜索 item,如果找到则返回 true(译注:如果没找到,则返回 false)

    find 和 findIndex

    ```javascript let users = [ {id: 1, name: “John”}, {id: 2, name: “Pete”}, {id: 3, name: “Mary”} ];

let user = users.find(item => item.id == 1);

alert(user.name); // John

[arr.findIndex](https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) 方法(与 arr.find 方法)基本上是一样的,但它返回找到元素的索引,而不是元素本身。并且在未找到任何内容时返回 -1。
<a name="or3dr"></a>
#### filter
find 方法搜索的是使函数返回 true 的第一个(单个)元素<br />如果需要匹配的有很多,我们可以使用 [arr.filter(fn)](https://developer.mozilla.org/zh/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)。<br />语法与 find 大致相同,但是 filter 返回的是所有匹配元素组成的数组。
```javascript
let results = arr.filter(function(item, index, array) {
  // 如果 true item 被 push 到 results,迭代继续
  // 如果什么都没找到,则返回空数组
});

let users = [
  {id: 1, name: "John"},
  {id: 2, name: "Pete"},
  {id: 3, name: "Mary"}
];

// 返回前两个用户的数组
let someUsers = users.filter(item => item.id < 3);

alert(someUsers.length); // 2

转换数组

让我们继续学习进行数组转换和重新排序的方法。

map

arr.map 方法是最有用和经常使用的方法之一。它对数组的每个元素都调用函数,并返回结果数组。

let result = arr.map(function(item, index, array) {
  // 返回新值而不是当前元素
})

在这里我们将每个元素转换为它的字符串长度

let lengths = ["Bilbo", "Gandalf", "Nazgul"].map(item => item.length);
alert(lengths); // 5,7,6

sort(fn)

arr.sort 方法对数组进行 原位(in-place) 排序,更改元素的顺序。(译注:原位是指在此数组内,而非生成一个新数组。) 它还返回排序后的数组,但是返回值通常会被忽略,因为修改了 arr 本身。
这些元素默认情况下被按字符串进行排序。

要使用我们自己的排序顺序,我们需要提供一个函数作为 arr.sort() 的参数

//例如,按数字进行排序
function compareNumeric(a, b) {
  if (a > b) return 1;
  if (a == b) return 0;
  if (a < b) return -1;
}

let arr = [ 1, 2, 15 ];

arr.sort(compareNumeric);

alert(arr);  // 1, 2, 15

// 实际上,比较函数只需要返回一个正数表示“大于”,一个负数表示“小于”。
// 通过这个原理我们可以编写更短的函数:
let arr = [ 1, 2, 15 ];

arr.sort(function(a, b) { return a - b; });

alert(arr);  // 1, 2, 15

箭头函数最好

arr.sort( (a, b) => a - b );

arr.sort (fn) 方法实现了通用的排序算法。我们不需要关心它的内部工作原理(大多数情况下都是经过 快速排序Timsort 算法优化的)

reverse

arr.reverse 方法用于颠倒 arr 中元素的顺序。

split

str.split(delim) 通过给定的分隔符 delim 将字符串分割成一个数组。

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', ');

for (let name of arr) {
  alert( `A message to ${name}.` ); // A message to Bilbo(和其他名字)
}

split 方法有一个可选的第二个数字参数 —— 对数组长度的限制。如果提供了,那么额外的元素会被忽略。但实际上它很少使用:

let arr = 'Bilbo, Gandalf, Nazgul, Saruman'.split(', ', 2);

alert(arr); // Bilbo, Gandalf

调用带有空参数 s 的 split(s),会将字符串拆分为字母数组:

let str = "test";

alert( str.split('') ); // t,e,s,t

join

arr.join(glue) 与 split 相反。它会在它们之间创建一串由 glue 粘合的 arr 项。

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';'); // 使用分号 ; 将数组粘合成字符串

alert( str ); // Bilbo;Gandalf;Nazgul

reduce/reduceRight

当我们需要遍历一个数组时 —— 我们可以使用 forEach,for 或 for..of。
当我们需要遍历并返回每个元素的数据时 —— 我们可以使用 map。
arr.reduce 方法和 arr.reduceRight 方法和上面的种类差不多,但稍微复杂一点。它们用于根据数组计算单个值。

let value = arr.reduce(function(accumulator, item, index, array) {
  // ...
}, [initial]);

该函数一个接一个地应用于所有数组元素,并将其结果“搬运(carry on)”到下一个调用。
参数:

  • accumulator —— 是上一个函数调用的结果,第一次等于 initial(如果提供了 initial 的话)。
  • item —— 当前的数组元素。
  • index —— 当前索引。
  • arr —— 数组本身。

应用函数时,上一个函数调用的结果将作为第一个参数传递给下一个函数。
我们通过一行代码得到一个数组的总和:

let arr = [1, 2, 3, 4, 5];

let result = arr.reduce((sum, current) => sum + current, 0);

alert(result); // 15

arr.reduceRightarr.reduce 方法的功能一样,只是遍历为从右到左。

Array.isArray

数组是基于对象的,不构成单独的语言类型。
所以 typeof 不能帮助从数组中区分出普通对象:

alert(typeof {}); // object  //[]表示数组 {}表示对象
alert(typeof []); // object

但是数组经常被使用,因此有一种特殊的方法用于判断:Array.isArray(value)。如果 value 是一个数组,则返回 true;否则返回 false。

alert(Array.isArray({})); // false

alert(Array.isArray([])); // true

大多数方法都支持 “thisArg”

几乎所有调用函数的数组方法 —— 比如 find,filter,map,除了 sort 是一个特例,都接受一个可选的附加参数 thisArg
完整语法,thisArg 参数的值在 func 中变为 this

arr.find(func, thisArg);
arr.filter(func, thisArg);
arr.map(func, thisArg);
// ...
// thisArg 是可选的最后一个参数

例如

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}
];

// 找到 army.canJoin 返回 true 的 user
let soldiers = users.filter(army.canJoin, army);  //filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

alert(soldiers.length); // 2
alert(soldiers[0].age); // 20
alert(soldiers[1].age); // 23

如果在上面的示例中我们使用了 users.filter(army.canJoin),那么 army.canJoin 将被作为独立函数调用,并且这时 this=undefined,从而会导致即时错误。
可以用 users.filter(user => army.canJoin(user)) 替换对 users.filter(army.canJoin, army) 的调用。前者的使用频率更高,因为对于大多数人来说,它更容易理解。

Iterable object(可迭代对象)

可迭代(Iterable) 对象是数组的泛化。这个概念是说任何对象都可以被定制为可在 for..of 循环中使用的对象。
。。。。跳过?Iterable object(可迭代对象)https://zh.javascript.info/iterable

Map and Set(映射和集合)

Map

Map 是一个带键的数据项的集合,就像一个 Object 一样。 但是它们最大的差别是 Map 允许任何类型的键(key)。
它的方法和属性如下:

  • new Map() —— 创建 map。
  • map.set(key, value) —— 根据键存储值。
  • map.get(key) —— 根据键来返回值,如果 map 中不存在对应的 key,则返回 undefined。
  • map.has(key) —— 如果 key 存在则返回 true,否则返回 false。
  • map.delete(key) —— 删除指定键的值。
  • map.clear() —— 清空 map。
  • map.size —— 返回当前元素个数。 ```javascript let map = new Map();

map.set(‘1’, ‘str1’); // 字符串键 map.set(1, ‘num1’); // 数字键 map.set(true, ‘bool1’); // 布尔值键

// 还记得普通的 Object 吗? 它会将键转化为字符串 // Map 则会保留键的类型,所以下面这两个结果不同: alert( map.get(1) ); // ‘num1’ alert( map.get(‘1’) ); // ‘str1’

alert( map.size ); // 3

键不会被转换成字符串。键可以是任何类型。Map 还可以使用对象作为键。
<a name="rdmiV"></a>
### 链式调用
每一次 map.set 调用都会返回 map 本身,所以我们可以进行“链式”调用:
```javascript
map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

Map 迭代

如果要在 map 里使用循环,可以使用以下三个方法:

  • map.keys() —— 遍历并返回所有的键(returns an iterable for keys),
  • map.values() —— 遍历并返回所有的值(returns an iterable for values),
  • map.entries() —— 遍历并返回所有的实体(returns an iterable for entries)[key, value],for..of在默认情况下使用的就是这个。

除此之外,Map 有内置的 forEach 方法,与 Array 类似:

// 对每个键值对 (key, value) 运行 forEach 函数
recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // cucumber: 500 etc
});

Object.entries:从对象创建 Map

可以像下面这样从一个对象创建一个 Map:

let obj = {
  name: "John",
  age: 30
};

let map = new Map(Object.entries(obj));

alert( map.get('name') ); // John

Object.fromEntries:从 Map 创建对象

let prices = Object.fromEntries([
  ['banana', 1],
  ['orange', 2],
  ['meat', 4]
]);

// 现在 prices = { banana: 1, orange: 2, meat: 4 }

alert(prices.orange); // 2

Set

每一个值只能出现一次

Set 是一个特殊的类型集合 —— “值的集合”(没有键),它的每一个值只能出现一次。
它的主要方法如下:

  • new Set(iterable) —— 创建一个 set,如果提供了一个 iterable 对象(通常是数组),将会从数组里面复制值到 set 中。
  • set.add(value) —— 添加一个值,返回 set 本身
  • set.delete(value) —— 删除值,如果 value 在这个方法调用的时候存在则返回 true ,否则返回 false。
  • set.has(value) —— 如果 value 在 set 中,返回 true,否则返回 false。
  • set.clear() —— 清空 set。
  • set.size —— 返回元素个数。

它的主要特点是,重复使用同一个值调用 set.add(value) 并不会发生什么改变。这就是 Set 里面的每一个值只出现一次的原因。

Set 迭代(iteration)

我们可以使用 for..of 或 forEach 来遍历 Set:

let set = new Set(["oranges", "apples", "bananas"]);

for (let value of set) alert(value);

// 与 forEach 相同:
set.forEach((value, valueAgain, set) => {
  alert(value);
});

在 Map 和 Set 中迭代总是按照值插入的顺序进行的,所以我们不能说这些集合是无序的,但是我们不能对元素进行重新排序,也不能直接按其编号来获取元素。

WeakMap and WeakSet(弱映射和弱集合)

WeakMap 是类似于 Map 的集合,它仅允许对象作为键,并且一旦通过其他方式无法访问它们,便会将它们与其关联值一同删除。
WeakSet 是类似于 Set 的集合,它仅存储对象,并且一旦通过其他方式无法访问它们,便会将其删除。
它们都不支持引用所有键或其计数的方法和属性。仅允许单个操作。

WeakMap 和 WeakSet 被用作“主要”对象存储之外的“辅助”数据结构。一旦将对象从主存储器中删除,如果该对象仅被用作 WeakMap 或 WeakSet 的键,那么它将被自动清除。

Object.keys,values,entries

对于普通对象,下列这些方法是可用的:

请记住,在 JavaScript 中,对象是所有复杂结构的基础。

转换对象

对象缺少数组存在的许多方法,例如 map 和 filter 等。

如果我们想应用它们,那么我们可以使用 Object.entries,然后使用 Object.fromEntries:

  1. 使用 Object.entries(obj) 从 obj 获取由键/值对组成的数组。
  2. 对该数组使用数组方法,例如 map。
  3. 对结果数组使用 Object.fromEntries(array) 方法,将结果转回成对象。

    解构赋值

    JavaScript 中最常用的两种数据结构是 Object 和 Array。 ```javascript let [firstName, surname] = “Ilya Kantor”.split(‘ ‘);

let arr = [“Ilya”, “Kantor”]

// let [firstName, surname] = arr; let firstName = arr[0]; let surname = arr[1];

这种语法叫做“解构赋值”,因为它通过将结构中的各元素复制到变量中来达到“解构”的目的。但数组本身是没有被修改的。

<a name="C5Zg9"></a>
### 数组中不想要的元素也可以通过添加额外的逗号来把它丢弃
```javascript
// 不需要第二个元素
let [firstName, , title] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert( title ); // Consul

剩余的 ‘…’

如果我们不只是要获得第一个值,还要将后续的所有元素都收集起来 — 我们可以使用三个点 “…” 来再加一个参数来接收“剩余的”元素:

let [name1, name2, ...rest] = ["Julius", "Caesar", "Consul", "of the Roman Republic"];

alert(name1); // Julius
alert(name2); // Caesar

// 请注意,`rest` 的类型是数组
alert(rest[0]); // Consul
alert(rest[1]); // of the Roman Republic
alert(rest.length); // 2

默认值 undefined

如果赋值语句中,变量的数量多于数组中实际元素的数量,赋值不会报错。未赋值的变量被认为是 undefined

let [firstName, surname] = [];

alert(firstName); // undefined
alert(surname); // undefined

对象解构

let options = {
  title: "Menu",
  width: 100,
  height: 200
};

let {title, width, height} = options;

alert(title);  // Menu
alert(width);  // 100
alert(height); // 200

日期和时间

JSON 方法,toJSON

JSON.stringify

JSON(JavaScript Object Notation)是表示值和对象的通用格式。客户端使用 JavaScript 而服务器端是使用 Ruby/PHP/Java 等语言编写的时,使用 JSON 可以很容易地进行数据交换。
JavaScript 提供了如下方法:

  • JSON.stringify 将对象转换为 JSON。
  • JSON.parse 将 JSON 转换回对象。

例如,在这里我们 JSON.stringify 一个 student 对象:

let student = {
  name: 'John',
  age: 30,
  isAdmin: false,
  courses: ['html', 'css', 'js'],
  wife: null
};

let json = JSON.stringify(student);

alert(typeof json); // we've got a string!

alert(json);
/* JSON 编码的对象:
{
  "name": "John",
  "age": 30,
  "isAdmin": false,
  "courses": ["html", "css", "js"],
  "wife": null
}
*/