4.集合引用类型
4.1 Object
let person = new Object();
person.name = "Nicholas";
person.age = 29;
//对象字面量(object literal)表示法
let person = {
name: "Nicholas",
age: 29
};
在使用对象字面量表示法定义对象时,并不会实际调用 Object 构造函数。
在对象字面量表示法中,属性名可以是字符串或数值,注意,数值属性会自动转换为字符串。
一般情况下属性名加引号和不加引号是都可以的,效果是一样的。当且仅当你的属性名是非法怪异的名字时候,会报错。如果属性名是数字,则必须有双引号,并且用[] 方括号访问。console.log(obj["333"]);
虽然属性一般是通过点语法来存取的,这也是面向对象语言的惯例,但也可以使用中括号来存取属性。在使用中括号时,要在括号内使用属性名的字符串形式。从功能上讲,这两种存取属性的方式没有区别。使用中括号的主要优势就是可以通过变量访问属性, 就像下面这个例子中一样,如果属性名中包含可能会导致语法错误的字符,或者包含关键字/保留字时,也可以使用中 括号语法。
console.log(person["name"]); // "Nicholas"
console.log(person.name); // "Nicholas"
let propertyName = "name";
console.log(person[propertyName]); // "Nicholas"
person["first name"] = "Nicholas"; //first name中间有空格,直接使用.会报错
4.2 Array
4.2.1 创建数组
let colors = new Array();
使用 Array 构造函数
let colors = ["red", "blue", "green"];
使用数组字面量(array literal)表示法,与对象一样,在使用数组字面量表示法创建数组不会调用 Array 构造函数。
from() and of()
Array 构造函数还有两个 ES6 新增的用于创建数组的静态方法:from()和 of()。from()用于将 类数组结构转换为数组实例,而 of()用于将一组参数转换为数组实例
Array.from()的第一个参数是一个类数组对象,即任何可迭代的结构,或者有一个 length 属性 和可索引元素的结构。
(浅复制是指当对象的字段值被复制时,字段引用的对象不会被复制·例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅复制,那麽两个对象将引用同一个字符串)
// 字符串会被拆分为单字符数组
console.log(Array.from("Matt")); // ["M", "a", "t", "t"]
// 可以使用 from()将集合和映射转换为一个新数组
const m = new Map().set(1, 2)
.set(3, 4);
const s = new Set().add(1)
.add(2)
.add(3)
.add(4);
console.log(Array.from(m)); // [[1, 2], [3, 4]]
console.log(Array.from(s)); // [1, 2, 3, 4]
// Array.from()对现有数组执行浅复制
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1);
console.log(a1); // [1, 2, 3, 4]
alert(a1 === a2); // false
// 可以使用任何可迭代对象
const iter = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
yield 4;
}
};
console.log(Array.from(iter)); // [1, 2, 3, 4]
// arguments 对象可以被轻松地转换为数组
function getArgsArray() {
return Array.from(arguments);
}
console.log(getArgsArray(1, 2, 3, 4)); // [1, 2, 3, 4]
// from()也能转换带有必要属性的自定义对象
const arrayLikeObject = {
0: 1,
1: 2,
2: 3,
3: 4,
length: 4
};
console.log(Array.from(arrayLikeObject)); // [1, 2, 3, 4]
Array.from()还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,而无须像 调用 Array.from().map()那样先创建一个中间数组。还可以接收第三个可选参数,用于指定映射函 数中 this 的值。但这个重写的 this 值在箭头函数中不适用。
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1, x => x**2);
const a3 = Array.from(a1, function(x) {return x**this.exponent}, {exponent: 2}); //不同使用箭头函数
console.log(a2); // [1, 4, 9, 16]
console.log(a3); // [1, 4, 9, 16]
Array.of()可以把一组参数转换为数组。这个方法用于替代在 ES6之前常用的 Array.prototype. slice.call(arguments),一种异常笨拙的将 arguments 对象转换为数组的写法
console.log(Array.of(1, 2, 3, 4)); // [1, 2, 3, 4]
console.log(Array.of(undefined)); // [undefined]
//可以使用ES6中的扩展运算符(...)
4.2.2数组空位
使用数组字面量初始化数组时,可以使用一串逗号来创建空位(hole)。ECMAScript 会将逗号之间 相应索引位置的值当成空位。ES6 新增方法普遍将这 些空位当成存在的元素,只不过值为 undefined
4.2.3 检测数组
if (value instanceof Array){
// 操作数组
}
if (Array.isArray(value)){
// 操作数组
}
4.2.4 迭代
Array 的原型上暴露了 3 个用于检索数组内容的方法:keys()、values()和 entries()。keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而 entries()返回 索引/值对的迭代器
const aKeys = Array.from(a.keys());
const aValues = Array.from(a.values());
const aEntries = Array.from(a.entries());
console.log(aKeys); // [0, 1, 2, 3]
console.log(aValues); // ["foo", "bar", "baz", "qux"]
console.log(aEntries); // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]
4.2.5 复制填充
批量复制方法 copyWithin(),以及填充数组方法 fill()
使用 fill()方法可以向一个已有的数组中插入全部或部分相同的值
copyWithin()会按照指定范围浅复制数组中的部分内容,然后将它们插入到指 定索引开始的位置。
4.2.6 栈与队列方法
push()方法接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。
pop()方法则 用于删除数组的最后一项,同时减少数组的 length 值,返回被删除的项。
shift(),它会删除数 组的第一项并返回它,然后数组长度减 1。
unshift()就是执行跟 shift()相反的 操作:在数组开头添加任意多个值,然后返回新的数组长度。
4.2.7 排序方法
reverse()和 sort()。
reverse()是将数组元素反向排列,对原数组进行修改
sort()
let values = [0, 1, 5, 10, 15];
values.sort();
alert(values); // 0,1,10,15,5
sort()会按照这些数值的字符串形式重新排序。字符串是按字符来比较。
sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面。比较函数接收两个参数,如果第一个参数应该排在第二个参数前面,就返回负值;如果两个参数相 等,就返回 0;如果第一个参数应该排在第二个参数后面,就返回正值。
4.2.8 操作方法
concat() 多维数组转一维数组
concat()方法可以在现有数组全部元素基础上 创建一个新数组。它首先会创建一个当前数组的副本,然后再把它的参数添加到副本末尾,最后返回这 个新构建的数组。如果传入一个或多个数组,则 concat()会把这些数组的每一项都添加到结果数组。 如果参数不是数组,则直接把它们添加到结果数组末尾。
let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
console.log(colors); // ["red", "green","blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", "black", "brown"]
slice()
slice()用于创建一个包含原有数组中一个或多个元素的新数组。slice()方法可以 接收一个或两个参数:返回元素的开始索引和结束索引。
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1);
let colors3 = colors.slice(1, 4);
alert(colors2); // green,blue,yellow,purple
alert(colors3); // green,blue,yellow
如果不传入参数,则默认浅复制原数组
splice()
删除。需要给 splice()传 2 个参数:要删除的第一个元素的位置和要删除的元素数量。可以从 数组中删除任意多个元素,比如 splice(0, 2)会删除前两个元素。
插入。需要给 splice()传 3 个参数:开始位置、0(要删除的元素数量)和要插入的元素,可 以在数组中指定的位置插入元素。第三个参数之后还可以传第四个、第五个参数,乃至任意多 个要插入的元素。比如,splice(2, 0, “red”, “green”)会从数组位置 2 开始插入字符串 “red"和"green”。
替换。splice()在删除元素的同时可以在指定位置插入新元素,同样要传入 3 个参数:开始位 置、要删除元素的数量和要插入的任意多个元素。要插入的元素数量不一定跟删除的元素数量 一致。比如,splice(2, 1, “red”, “green”)会在位置 2 删除一个元素,然后从该位置开始 向数组中插入"red"和"green"。
let colors = ["red", "green", "blue"];
let removed = colors.splice(0,1); // 删除第一项
alert(colors); // green,blue
alert(removed); // red,只有一个元素的数组
removed = colors.splice(1, 0, "yellow", "orange"); // 在位置 1 插入两个元素
alert(colors); // green,yellow,orange,blue
alert(removed); // 空数组
removed = colors.splice(1, 1, "red", "purple"); // 插入两个值,删除一个元素
alert(colors); // green,red,purple,orange,blue
alert(removed); // yellow,只有一个元素的数组
4.2.9 搜索和位置方法
indexOf()和 lastIndexOf()都返回要查找的元素在数组中的位置,如果没找到则返回1。 includes()返回布尔值,表示是否至少找到一个与指定元素匹配的项。在比较第一个参数跟数组每一 项时,会使用全等(===)比较,也就是说两项必须严格相等。
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
alert(numbers.indexOf(4)); // 3
alert(numbers.lastIndexOf(4)); // 5
alert(numbers.includes(4)); // true
alert(numbers.indexOf(4, 4)); // 5
alert(numbers.lastIndexOf(4, 4)); // 3
alert(numbers.includes(4, 7)); // false
let person = { name: "Nicholas" };
let people = [{ name: "Nicholas" }];
let morePeople = [person];
alert(people.indexOf(person)); // -1
alert(morePeople.indexOf(person)); // 0
alert(people.includes(person)); // false
alert(morePeople.includes(person)); // true
断言函数
断言函数接收 3 个参数:元素、索引和数组本身。其中元素是数组中当前搜索的元素,索引是当前 元素的索引,而数组就是正在搜索的数组。断言函数返回真值,表示是否匹配。
find()和 findIndex()方法使用了断言函数。这两个方法都从数组的最小索引开始。find()返回 第一个匹配的元素,findIndex()返回第一个匹配元素的索引。这两个方法也都接收第二个可选的参数, 用于指定断言函数内部 this 的值。
//找到匹配项后,这两个方法都不再继续搜索
const evens = [2, 4, 6];
// 找到匹配后,永远不会检查数组的最后一个元素
evens.find((element, index, array) => {
console.log(element);
console.log(index);
console.log(array);
return element === 4;
});
// 2
// 0
// [2, 4, 6]
// 4
// 1
// [2, 4, 6]
4.2.10 迭代方法
every():对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。
filter():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。
forEach():对数组每一项都运行传入的函数,没有返回值。
map():对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组。
some():对数组每一项都运行传入的函数,如果有一项函数返回 true,则这个方法返回 true
每个方法接收两个参数:以每一项为参数运行的函数, 以及可选的作为函数运行上下文的作用域对象(影响函数中 this 的值)。传给每个方法的函数接收 3 个参数:数组元素、元素索引和数组本身。
every()和 some()
every()和 some()是最相似的,都是从数组中搜索符合某个条件的元素。对 every() 来说,传入的函数必须对每一项都返回 true,它才会返回 true;否则,它就返回 false。而对 some() 来说,只要有一项让传入的函数返回 true,它就会返回 true。
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let everyResult = numbers.every((item, index, array) => item > 2);
alert(everyResult); // false
let someResult = numbers.some((item, index, array) => item > 2);
alert(someResult); // true
filter()
filter()方法。这个方法基于给定的函数来决定某一项是否应该包含在它返回的数 组中。
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let filterResult = numbers.filter((item, index, array) => item > 2);
alert(filterResult); // 3,4,5,4,3
map()
接下来 map()方法也会返回一个数组。这个数组的每一项都是对原始数组中同样位置的元素运行传 入函数而返回的结果。
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
let mapResult = numbers.map((item, index, array) => item * 2);
alert(mapResult); // 2,4,6,8,10,8,6,4,2
forEach()
forEach()方法。这个方法只会对每一项运行传入的函数,没有返回值。本质 上,forEach()方法相当于使用 for 循环遍历数组。
4.2.11 归并
reduce()和 reduceRight()。这两个方法都会迭代数 组的所有项,并在此基础上构建一个最终返回值。reduce()方法从数组第一项开始遍历到最后一项。 而 reduceRight()从最后一项开始遍历至第一项。对每一项都会运行的归并函数,以及可选的以之为归并起点的初始值。 传给 reduce()和 reduceRight()的函数接收 4 个参数:上一个归并值、当前项、当前项的索引和数 组本身。这个函数返回的任何值都会作为下一次调用同一个函数的第一个参数。如果没有给这两个方法 传入可选的第二个参数(作为归并起点值),则第一次迭代将从数组的第二项开始,因此传给归并函数 的第一个参数是数组的第一项,第二个参数是数组的第二项。
let values = [1, 2, 3, 4, 5];
let sum = values.reduce((prev, cur, index, array) => prev + cur);
alert(sum); // 15
4.3 Map
作为 ECMAScript 6 的新增特性,Map 是一种新的集合类型,为这门语言带来了真正的键/值存储机 制。Map 的大多数特性都可以通过 Object 类型实现,但二者之间还是存在一些细微的差异。
使用 new 关键字和 Map 构造函数可以创建一个空映射: const m = new Map();
初始化之后,可以使用 set()方法再添加键/值对。另外,可以使用 get()和 has()进行查询,可 以通过 size 属性获取映射中的键/值对的数量,还可以使用 delete()和 clear()删除值。
const m = new Map();
alert(m.has("firstName")); // false
alert(m.get("firstName")); // undefined
alert(m.size); // 0
m.set("firstName", "Matt")
.set("lastName", "Frisbie");
alert(m.has("firstName")); // true
alert(m.get("firstName")); // Matt
alert(m.size); // 2
m.delete("firstName"); // 只删除这一个键/值对
alert(m.has("firstName")); // false
alert(m.has("lastName")); // true
alert(m.size); // 1
m.clear(); // 清除这个映射实例中的所有键/值对
alert(m.has("firstName")); // false
alert(m.has("lastName")); // false
alert(m.size); // 0
在映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改时 仍然保持不变(仍保持映射关系不变)。
const m = new Map();
const objKey = {},
objVal = {},
arrKey = [],
arrVal = [];
m.set(objKey, objVal);
m.set(arrKey, arrVal);
objKey.foo = "foo";
objVal.bar = "bar";
arrKey.push("foo");
arrVal.push("bar");
console.log(m.get(objKey)); // {bar: "bar"}
console.log(m.get(arrKey)); // ["bar"]
4.3.1 迭代
与 Object 类型的一个主要差异是,Map 实例会维护键值对的插入顺序,因此可以根据插入顺序执 行迭代操作。映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以 通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器
const m = new Map([
["key1", "val1"],
["key2", "val2"],
["key3", "val3"]
]);
alert(m.entries === m[Symbol.iterator]); // true
for (let pair of m.entries()) {
alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
for (let pair of m[Symbol.iterator]()) {
alert(pair);
}
// [key1,val1]
// [key2,val2]
// [key3,val3]
映射转换成数组
const m = new Map([
["key1", "val1"],
["key2", "val2"],
["key3", "val3"]
]);
console.log([...m]); // [[key1,val1],[key2,val2],[key3,val3]]
4.4 Set
Set
和Map
类似,也是一组key的集合,但不存储value。由于key不能重复,所以,在Set
中,没有重复的key。
初始化之后,可以使用 add()增加值,使用 has()查询,通过 size 取得元素数量,以及使用 delete() 和 clear()删除元素
const s = new Set();
alert(s.has("Matt")); // false
alert(s.size); // 0
s.add("Matt")
.add("Frisbie");
alert(s.has("Matt")); // true
alert(s.size); // 2
s.delete("Matt");
alert(s.has("Matt")); // false
alert(s.has("Frisbie")); // true
alert(s.size); // 1
s.clear(); // 销毁集合实例中的所有值
alert(s.has("Matt")); // false
alert(s.has("Frisbie")); // false
alert(s.size); // 0
4.4.1 迭代
集合实例可以提供一个迭代器(Iterator),能以插入顺序生成集合内容。可以通过 values()方 法及其别名方法 keys()(或者 Symbol.iterator 属性,它引用 values())取得这个迭代器
const s = new Set(["val1", "val2", "val3"]);
alert(s.values === s[Symbol.iterator]); // true
alert(s.keys === s[Symbol.iterator]); // true
for (let value of s.values()) {
alert(value);
}
// val1
// val2
// val3
for (let value of s[Symbol.iterator]()) {
alert(value);
}
// val1
// val2
// val3
Set转换成数组
const s = new Set(["val1", "val2", "val3"]);
console.log([...s]); // ["val1", "val2", "val3"]
4.5 迭代与扩展操作
4 种原生集合类型定义了默认迭代器: Array 所有定型数组 Map Set
这意味着上述所有类型都支持顺序迭代,都可以传入 for-of 循环
let iterableThings = [
Array.of(1, 2),
typedArr = Int16Array.of(3, 4),
new Map([[5, 6], [7, 8]]),
new Set([9, 10])
];
for (const iterableThing of iterableThings) {
for (const x of iterableThing) {
console.log(x);
}
}
// 1
// 2
// 3
// 4
// [5, 6]
// [7, 8]
// 9
// 10
这也意味着所有这些类型都兼容扩展操作符。扩展操作符在对可迭代对象执行浅复制时特别有用, 只需简单的语法就可以复制整个对象
let arr1 = [1, 2, 3];
let arr2 = [...arr1];
console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3]
console.log(arr1 === arr2); // false