【JS红宝书学习笔记】第6章 集合引用类型

第6章 集合引用类型

对象
数组与定型数组
Map、WeakMap、Set 以及 WeakSet 类型

1. object

很适合存储和在应用程序间交换数据。
显式创建object的两种方式:
(1)new操作符

let person = new Object();
person.name = "Nicholas";
person.age = 29;

(2)对象字面量即对象定义的简写方式,属性名可以是字符串或数值。下面的例子会得到一个带有属性 name、age 和 5 的对象。注意,数值属性会自动转换为字符串。

let person = {name: "Nicholas",age: 29,5: true
};
let person = {}; // 与 new Object()相同
person.name = "Nicholas";
person.age = 29;

开发时多采用对象字面量表示法:

function displayInfo(args) {let output = "";// 用typeof测试每个属性是否存在if (typeof args.name == "string"){output += "Name: " + args.name + "\n";}if (typeof args.age == "number") {output += "Age: " + args.age + "\n";}alert(output);
}
displayInfo({name: "Nicholas",age: 29
});
displayInfo({name: "Greg"
});

可使用点语法或者中括号来存取对象属性,使用中括号时要用字符串形式:

console.log(person["name"]); // "Nicholas"
console.log(person.name); // "Nicholas"

在这里插入图片描述

2. Array(面试重点:构建、判断)

ECMAScript 数组是一组有序的数据,但跟其他语言不同的是,数组中每个槽位可以存储任意类型的数据。这意味着可以创建一个数组,它的第一个元素是字符串,第二个元素是数值,第三个是对象。ECMAScript 数组也是动态大小的,会随着数据添加而自动增长。

(1)创建数组

方式1:使用Array构造函数

let colors1 = new Array();
let colors2 = new Array(20);
let colors3 = new Array("red", "blue", "green");// 也可省略new操作符
let colors = Array(3); // 创建一个包含 3 个元素的数组
let names = Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组

方式2:使用数组字面量表示法

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个元素的数组
let names = []; // 创建一个空数组
let values = [1,2,]; // 创建一个包含 2 个元素的数组

Array 构造函数还有两个 ES6 新增的用于创建数组的静态方法:from()和 of()

方式3: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]

方式4: 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]

(2)数组空位

方式1:使用一串逗号

const options = [,,,,,]; // 创建包含 5 个元素的数组
console.log(options.length); // 5
console.log(options); // [,,,,,]

ES6 新增的方法和迭代器与早期 ECMAScript 版本中存在的方法行为不同。ES6 新增方法普遍将这些空位当成存在的元素,只不过值为 undefined:

方式2:利用ES6的Array.from()+一串逗号

const options = [1,,,,5];
for (const option of options) {console.log(option === undefined);
}
// false
// true
// true
// true
// falseconst a = Array.from([,,,]); // 使用 ES6 的 Array.from()创建的包含 3 个空位的数组
for (const val of a) {alert(val === undefined);
}
// true
// true
// true
alert(Array.of(...[,,,])); // [undefined, undefined, undefined]
for (const [index, value] of options.entries()) {alert(value);
}
// 1
// undefined
// undefined
// undefined
// 5

(3)数组索引

let array = []
alert(array.length);  // 0

数组 length 属性的独特之处在于,它不是只读的。通过修改 length 属性,可以从数组末尾删除或添加元素。

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
colors.length = 2;
alert(colors[2]); // undefined,这里相当于删除了末元素let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
colors.length = 4;
alert(colors[3]); // undefined,这里相当于增加了一个元素let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
colors[99] = "black"; // 添加一种颜色(位置 99)
alert(colors.length); // 100

(4)检测数组(面试题)

方式1:instanceof

if (value instanceof Array){// 操作数组
}

使用 instanceof 的问题是假定只有一个全局执行上下文。如果网页里有多个框架,则可能涉及两个不同的全局执行上下文,因此就会有两个不同版本的 Array 构造函数。如果要把数组从一个框架传给另一个框架,则这个数组的构造函数将有别于在第二个框架内本地创建的数组。

方式2: Array.isArray()
确定一个值是否为数组,而不用管它是在哪个全局执行上下文中创建的。

if (Array.isArray(value)){// 操作数组
}

(5)迭代器方法

在 ES6 中,Array 的原型上暴露了 3 个用于检索数组内容的方法:keys()、values() 和 entries()。keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而 entries()返回索引/值对的迭代器(键值对):

const a = ["foo", "bar", "baz", "qux"];
// 因为这些方法都返回迭代器,所以可以将它们的内容
// 通过 Array.from()直接转换为数组实例
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"]]
使用 ES6 的解构可以非常容易地在循环中拆分键/值对:
const a = ["foo", "bar", "baz", "qux"];
for (const [idx, element] of a.entries()) {alert(idx);alert(element);
}
// 0
// foo
// 1
// bar
// 2
// baz
// 3
// qux

(6) 复制和填充方法

ES6 新增了两个方法:批量复制方法 copyWithin(),以及填充数组方法 fill()。
(1)fill() 填充数组
使用 fill()方法可以向一个已有的数组中插入全部或部分相同的值。开始索引用于指定开始填充的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。负值索引从数组末尾开始计算。也可以将负索引想象成数组长度加上它得到的一个正索引:
参数:填充的数字(必须) 填充开始索引(可选,默认为0) 填充结束索引(可选,默认到末尾)(左闭右开,超出范围忽略)

const zeroes = [0, 0, 0, 0, 0];
// 用 5 填充整个数组
zeroes.fill(5);
console.log(zeroes); // [5, 5, 5, 5, 5]
zeroes.fill(0); // 重置
// 用 6 填充索引大于等于 3 的元素
zeroes.fill(6, 3);
console.log(zeroes); // [0, 0, 0, 6, 6]
zeroes.fill(0); // 重置
// 用 7 填充索引大于等于 1 且小于 3 的元素(左闭右开)
zeroes.fill(7, 1, 3);
console.log(zeroes); // [0, 7, 7, 0, 0];
zeroes.fill(0); // 重置
// 用 8 填充索引大于等于 1 且小于 4 的元素
// (-4 + zeroes.length = 1)
// (-1 + zeroes.length = 4)
zeroes.fill(8, -4, -1);
console.log(zeroes); // [0, 8, 8, 8, 0];// fill()静默忽略超出数组边界、零长度及方向相反的索引范围:
const zeroes = [0, 0, 0, 0, 0];
// 索引过低,忽略
zeroes.fill(1, -10, -6);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引过高,忽略
zeroes.fill(1, 10, 15);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引反向,忽略
zeroes.fill(2, 4, 2);
console.log(zeroes); // [0, 0, 0, 0, 0]
// 索引部分可用,填充可用部分
zeroes.fill(4, 3, 10)
console.log(zeroes); // [0, 0, 0, 4, 4]

(2)copywith() 批量复制
copyWithin()会按照指定范围浅复制数组中的部分内容,然后将它们插入到指
定索引开始的位置。开始索引和结束索引则与 fill() 使用同样的计算方法:
参数:插入复制内容位置的开始索引(必须) 复制内容开始索引(可选,默认0) 复制内容结束索引(可选,默认到末尾) (左闭右开,不合理都忽略)

let ints,reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();// 从 ints 中复制索引 0 开始的内容,插入到索引 5 开始的位置
// 在源索引或目标索引到达数组边界时停止
ints.copyWithin(5);
console.log(ints); // [0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
reset();// 从 ints 中复制索引 5 开始的内容,插入到索引 0 开始的位置
ints.copyWithin(0, 5);
console.log(ints); // [5, 6, 7, 8, 9, 5, 6, 7, 8, 9]
reset();
// 从 ints 中复制索引 0 开始到索引 3 结束的内容
// 插入到索引 4 开始的位置
ints.copyWithin(4, 0, 3);
alert(ints); // [0, 1, 2, 3, 0, 1, 2, 7, 8, 9]
reset();
// JavaScript 引擎在插值前会完整复制范围内的值
// 因此复制期间不存在重写的风险
ints.copyWithin(2, 0, 6);
alert(ints); // [0, 1, 0, 1, 2, 3, 4, 5, 8, 9]
reset();
// 支持负索引值,与 fill()相对于数组末尾计算正向索引的过程是一样的
ints.copyWithin(-4, -7, -3);
alert(ints); // [0, 1, 2, 3, 4, 5, 3, 4, 5, 6]// copyWithin()静默忽略超出数组边界、零长度及方向相反的索引范围:
let ints,reset = () => ints = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引过低,忽略
ints.copyWithin(1, -15, -12);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset()
// 索引过高,忽略
ints.copyWithin(1, 12, 15);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引反向,忽略
ints.copyWithin(2, 4, 2);
alert(ints); // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
reset();
// 索引部分可用,复制、填充可用部分
ints.copyWithin(4, 7, 10)
alert(ints); // [0, 1, 2, 3, 7, 8, 9, 7, 8, 9];

(7)转换方法

所有对象都有 toLocaleString()、toString()和 valueOf()方法。其中,valueOf()
返回的还是数组本身。而 toString()返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。也就是说,对数组的每个值都会调用其 toString()方法,以得到最终的字符串。

let colors = ["red", "blue", "green"]; // 创建一个包含 3 个字符串的数组
alert(colors.toString()); // red,blue,green
alert(colors.valueOf()); // red,blue,green
alert(colors); // red,blue,green

toLocaleString()方法也可能返回跟 toString()和 valueOf()相同的结果,但也不一定。在调用数组的 toLocaleString()方法时,会得到一个逗号分隔的数组值的字符串。它与另外两个方法唯一的区别是,为了得到最终的字符串,会调用数组每个值的 toLocaleString()方法,而不是toString()方法。

let person1 = {toLocaleString() {return "Nikolaos";},toString() {return "Nicholas";}
};
let person2 = {toLocaleString() {return "Grigorios";},toString() {return "Greg";
}
};
let people = [person1, person2];
alert(people); // Nicholas,Greg
alert(people.toString()); // Nicholas,Greg
alert(people.toLocaleString()); // Nikolaos,Grigorios

继承的方法 toLocaleString()以及 toString()都返回数组值的逗号分隔的字符串。如果想使用不同的分隔符,则可以使用 join()方法。join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串。

let colors = ["red", "green", "blue"];
alert(colors.join(",")); // red,green,blue
alert(colors.join("||")); // red||green||blue

(8)栈方法

栈(后进先出)
push():插入,返回数组的最新长度
pop():删除末项,返回被删除的项

let colors = new Array(); // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
alert(count); // 2count = colors.push("black"); // 再推入一项
alert(count); // 3let item = colors.pop(); // 取得最后一项
alert(item); // black
alert(colors.length); // 2

(9) 队列方法

队列(先进先出)
push():末尾添加数据,返回数组最新长度
pop():删除末项,返回被删除的项
shift():删除首项,返回该项,长度-1
unshift():开头添加任意多个值,返回数组新长度

let colors = new Array(); // 创建一个数组
let count = colors.push("red", "green"); // 推入两项
alert(count); // 2count = colors.push("black"); // 再推入一项
alert(count); // 3let item = colors.shift(); // 取得第一项
alert(item); // red
alert(colors.length); // 2let colors = new Array(); // 创建一个数组
let count = colors.unshift("red", "green"); // 从数组开头推入两项
alert(count); // 2count = colors.unshift("black"); // 再推入一项,此时应该是black,red,green
alert(count); // 3let item = colors.pop(); // 取得最后一项
alert(item); // green
alert(colors.length); // 2

(10)排序方法

reverse():反向排序
sort():排序,默认升序
reverse()和 sort()都返回调用它们的数组的引用。(即改变原数组的值)

let values = [1, 2, 3, 4, 5];
values.reverse();
alert(values); // 5,4,3,2,1
let values = [0, 1, 5, 10, 15];
values.sort();
alert(values); // 0,1,10,15,5

一开始数组中数值的顺序是正确的,但调用 sort()会按照这些数值的字符串形式重新排序。因此,即使 5 小于 10,但字符串"10"在字符串"5"的前头**(先比较第一位)**,所以 10 还是会排到 5 前面。很明显,这在多数情况下都不是最合适的。为此,sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面。

//  升序
function compare(value1, value2) {if (value1 < value2) {return -1;} else if (value1 > value2) {return 1;} else {return 0;}
}
let values = [0, 1, 5, 10, 15];
values.sort(compare);
alert(values); // 0,1,5,10,15//降序
function compare2(value1, value2) {if (value1 < value2) {return 1;} else if (value1 > value2) {return -1;} else {return 0;}
}
let values = [0, 1, 5, 10, 15];
values.sort(compare2);
alert(values); // 15,10,5,1,0

箭头函数写法:

let values = [0, 1, 5, 10, 15];
values.sort((a, b) => a < b ? 1 : a > b ? -1 : 0);
alert(values); // 15,10,5,1,0

(11)操作方法

  • 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"]

不想打平数组,即不把加入的数组元素抽出来加进去,而是直接将原数组作为新元素加入,可在参数数组上指定一个特殊的符号为false:Symbol.isConcatSpreadable。把这个值设置为 true 可以强制打平类数组对象

let colors = ["red", "green", "blue"];
let newColors = ["black", "brown"];
let moreNewColors = {[Symbol.isConcatSpreadable]: true,length: 2,0: "pink",1: "cyan"
};
newColors[Symbol.isConcatSpreadable] = false;
// 强制不打平数组
let colors2 = colors.concat("yellow", newColors);
// 强制打平类数组对象
let colors3 = colors.concat(moreNewColors);
console.log(colors); // ["red", "green", "blue"]
console.log(colors2); // ["red", "green", "blue", "yellow", ["black", "brown"]]
console.log(colors3); // ["red", "green", "blue", "pink", "cyan"]
  • 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():在数组中插入元素
    删除:2个参数,开始位置 结束位置(左闭右开)
    插入:3个参数,开始位置 删除元素数量(一般为0) 插入元素
    替换:3个参数,开始位置 删除元素数量 插入任意多个元素
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,只有一个元素的数组

(12)搜索和位置方法

两类搜索:严格相等、断言函数

  • 严格相等
    ECMAScript 提供了 3 个严格相等的搜索方法:indexOf()、lastIndexOf()和 includes()。其中,前两个方法在所有版本中都可用,而第三个方法是ECMAScript 7 新增的。这些方法都接收两个参数:要查找的元素和一个可选的起始搜索位置。
    indexOf() 和 includes() 方法从数组前头(第一项)开始向后搜索,而 lastIndexOf() 从数组末尾(最后一项)开始向前搜索。
    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():返回第一个匹配元素的索引
const people = [{name: "Matt",age: 27},{name: "Nicholas",age: 29}
];
alert(people.find((element, index, array) => element.age < 28));
// {name: "Matt", age: 27}
alert(people.findIndex((element, index, array) => element.age < 28));
// 0

(13)迭代方法(面试题)

every():对数组每一项都运行传入的函数,如果对每一项函数都返回 true,则这个方法返回 true。
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():对数组每一项都运行传入的函数,函数返回 true 的项会组成数组之后返回。适合从数组中帅选满足条件的元素。

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

forEach():对数组每一项都运行传入的函数,没有返回值。

let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
numbers.forEach((item, index, array) => {
// 执行某些操作
});

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

(14)归并方法

reduce():从数组第一项遍历到最后一项

// 累加数组所有数值
let values = [1, 2, 3, 4, 5];
let sum = values.reduce((prev, cur, index, array) => prev + cur);
alert(sum); // 15

reduceRight():从最后一项遍历到第一项

let values = [1, 2, 3, 4, 5];
let sum = values.reduceRight(function(prev, cur, index, array){return prev + cur;
});
alert(sum); // 15

3. 定型数组TypedArray(似乎不重要)

定型数组(typed array)是 ECMAScript 新增的结构,目的是提升向原生库传输数据的效率。实际上,JavaScript 并没有“TypedArray”类型,它所指的其实是一种特殊的包含数值类型的数组。

4. Map

键值对存储(ES6新特性)。Map 的大多数特性都可以通过 Object 类型实现,但二者之间还是存在一些细微的差异。

(1)基本API

使用 new 关键字和 Map 构造函数可以创建一个空映射:

const m = new Map();
// 使用嵌套数组初始化映射
const m1 = new Map([["key1", "val1"],["key2", "val2"],["key3", "val3"]
]);
alert(m1.size); // 3// 使用自定义迭代器初始化映射
const m2 = new Map({[Symbol.iterator]: function*() {yield ["key1", "val1"];yield ["key2", "val2"];yield ["key3", "val3"];}
});
alert(m2.size); // 3// 映射期待的键/值对,无论是否提供
const m3 = new Map([[]]);
alert(m3.has(undefined)); // true
alert(m3.get(undefined)); // undefined

初始化之后,set():添加键值对
get() / has() :查询,get获取键对应的值没有返回undefined,has看键对应的值是否存在返回true/false
size():获取键值对数量
delete() / clear():删除值,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); // 2m.delete("firstName"); // 只删除这一个键/值对
alert(m.has("firstName")); // false
alert(m.has("lastName")); // true
alert(m.size); // 1m.clear(); // 清除这个映射实例中的所有键/值对alert(m.has("firstName")); // false
alert(m.has("lastName")); // false
alert(m.size); // 0

Object 只能使用数值、字符串或符号作为键不同,Map 可以使用任何 JavaScript 数据类型作为键。Map 内部使用 SameValueZero 比较操作(ECMAScript 规范内部定义,语言中不能使用),基本上相当于使用严格对象相等的标准来检查键的匹配性。与 Object 类似,映射的值是没有限制的。

const m = new Map();
const functionKey = function() {};
const symbolKey = Symbol();
const objectKey = new Object();
m.set(functionKey, "functionValue");
m.set(symbolKey, "symbolValue");
m.set(objectKey, "objectValue");
alert(m.get(functionKey)); // functionValue
alert(m.get(symbolKey)); // symbolValue
alert(m.get(objectKey)); // objectValue
// SameValueZero 比较意味着独立实例不冲突
alert(m.get(function() {})); // undefined

(2)顺序与迭代

Map实例会维护键值对的插入顺序,但是object不会。

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]

keys()和 values()分别返回以插入顺序生成键和值的迭代器:

const m = new Map([["key1", "val1"],["key2", "val2"],["key3", "val3"]
]);
for (let key of m.keys()) {alert(key);
}
// key1
// key2
// key3
for (let key of m.values()) {alert(key);
}
// value1
// value2
// value3

(3)选择Object还是Map

在这里插入图片描述
☆☆Map和传统的Object有什么区别呢?(或者说,为什么要出现Map数据结构呢?)

  • Object只能使用字符串,数值,Symbol作为键,而Map可以使用任何数据类型作为键
  • Map使用了类似于哈希表的算法实现了快速查找和删除操作,所以在操作大量数据时,Map的性能跟好。
  • Map对象保证了插入顺序,在遍历Map对象时,不需要担心顺序问题。
  • Map对象内置了迭代器,可以使用for-of或者其他迭代器方法进行迭代,使得处理Map对象更加灵活。
  • Map提供的操作键值对的api,能够对键值对更简单的操作。

5. WeakMap(面试题)

ES6新增了弱映射。WeakMap 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱映射”中键的方式。这些键不属于正式的引用,不会阻止垃圾回收。
Object 只能使用数值、字符串或符号作为键
弱映射的键只能是object或者继承自object的类型,尝试使用非对象设置键会抛出TypeError。
初始化:

const key1 = {id: 1},key2 = {id: 2},key3 = {id: 3};
// 使用嵌套数组初始化弱映射
const wm1 = new WeakMap([[key1, "val1"],[key2, "val2"],[key3, "val3"]
]);
alert(wm1.get(key1)); // val1
alert(wm1.get(key2)); // val2
alert(wm1.get(key3)); // val3
// 初始化是全有或全无的操作
// 只要有一个键无效就会抛出错误,导致整个初始化失败
const wm2 = new WeakMap([[key1, "val1"],["BADKEY", "val2"],[key3, "val3"]
]);
// TypeError: Invalid value used as WeakMap key
typeof wm2;
// ReferenceError: wm2 is not defined
// 原始值可以先包装成对象再用作键
const stringKey = new String("key1");
const wm3 = new WeakMap([stringKey, "val1"
]);
alert(wm3.get(stringKey)); // "val1"

set():添加键值对
get() / has() 查询
delete() 删除

const wm = new WeakMap();
const key1 = {id: 1},
key2 = {id: 2};
alert(wm.has(key1)); // false
alert(wm.get(key1)); // undefined
wm.set(key1, "Matt").set(key2, "Frisbie");
alert(wm.has(key1)); // true
alert(wm.get(key1)); // Matt
wm.delete(key1); // 只删除这一个键/值对
alert(wm.has(key1)); // false
alert(wm.has(key2)); // true

因为 WeakMap 中的键/值对任何时候都可能被销毁,所以没必要提供迭代其键/值对的能力。当然,也用不着像 clear()这样一次性销毁所有键/值的方法。WeakMap 确实没有这个方法。因为不可被迭代,所以也不可能在不知道对象引用的情况下从弱映射中取得值。即便代码可以访问 WeakMap 实例,也没办法看到其中的内容。

☆☆Map和weakMap的区别:
Map和weakMap是ES6出现的数据结构。和object类似,都是可以对键值对的存储。与object的区别就是:
(1)object只能使用字符串,数字,Symbol作为键,而Map可以使用任何数据类型作为键或值;
(2)Map对象保证了插入顺序,在遍历Map对象时,不需要担心顺序问题;
(3)Map对象内置了迭代器,可以使用for-of或者其他迭代器方法进行迭代,使得处理Map对象更加灵活;
(4)Map提供的操作键值对的api,能够对键值对更简单的操作;
(5)Map使用类似哈希表的算法实现了快速查找和删除操作,在大量操作数据时比object更为高效。
weakMap是一种弱引用的数据结构,它和Map的区别:
(1)weakMap只能使用引用类型作为键,当这个键在其他地方没有被引用时,这个键值对将会被回收;
(2)weakMap对象的键值对,任何时候可能被回收,所以没有内置迭代器。
(3)如果你想要作为键的对象可以被垃圾回收,你应该使用 WeakMap。如果你想要可以获取键的列表,你应该使用 Map 而不是 WeakMap。

6. Set

ES6新增。Set 在很多方面都像是加强的 Map,这是因为它们的大多数 API 和行为都是共有的。

(1)基本API

const m = new Set();

初始化:

// 使用数组初始化集合
const s1 = new Set(["val1", "val2", "val3"]);
alert(s1.size); // 3
// 使用自定义迭代器初始化集合
const s2 = new Set({[Symbol.iterator]: function*() {yield "val1";yield "val2";yield "val3";}
});
alert(s2.size); // 3

add():增加值
has():查询是否存在,返回true/false
size:取得元素数量
delete() / clear():删除元素

const s = new Set();
alert(s.has("Matt")); // false
alert(s.size); // 0s.add("Matt").add("Frisbie");alert(s.has("Matt")); // true
alert(s.size); // 2s.delete("Matt");alert(s.has("Matt")); // false
alert(s.has("Frisbie")); // true
alert(s.size); // 1s.clear(); // 销毁集合实例中的所有值alert(s.has("Matt")); // false
alert(s.has("Frisbie")); // false
alert(s.size); // 0

连续添加:

const s = new Set().add("val1");s.add("val2").add("val3");alert(s.size); // 3

和Map一样,Set也可以包含任何数据类型作为值。

const s = new Set();const functionVal = function() {};
const symbolVal = Symbol();
const objectVal = new Object();s.add(functionVal);
s.add(symbolVal);
s.add(objectVal);alert(s.has(functionVal)); // true
alert(s.has(symbolVal)); // true
alert(s.has(objectVal)); // true// SameValueZero 检查意味着独立的实例不会冲突
alert(s.has(function() {})); // false

(2)顺序与迭代

Set 会维护值插入时的顺序,因此支持按顺序迭代。 集合实例可以提供一个迭代器(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

add():

// 创建一个 Set
let mySet = new Set();// 向 Set 中添加元素
mySet.add('apple');
mySet.add('banana');
mySet.add('orange');// 创建一个迭代器
let iterator = mySet.values();// 使用迭代器按顺序迭代 Set 中的值
for (let item of iterator) {console.log(item);
}

因为 values()是默认迭代器,所以可以直接对集合实例使用扩展操作,把集合转换为数组:

const s = new Set(["val1", "val2", "val3"]);
console.log([...s]); // ["val1", "val2", "val3"]

(3)定义正式集合操作

交、差、并、补

7. WeakSet

ES6新增。WeakSet 是 Set 的“兄弟”类型,其 API 也是 Set 的子集。WeakSet 中的“weak”(弱),描述的是 JavaScript 垃圾回收程序对待“弱集合”中值的方式。

(1)基本API

可以使用 new 关键字实例化一个空的 WeakSet:

const ws = new WeakSet();

初始化:

const val1 = {id: 1},val2 = {id: 2},val3 = {id: 3};
// 使用数组初始化弱集合
const ws1 = new WeakSet([val1, val2, val3]);alert(ws1.has(val1)); // true
alert(ws1.has(val2)); // true
alert(ws1.has(val3)); // true// 初始化是全有或全无的操作
// 只要有一个值无效就会抛出错误,导致整个初始化失败
const ws2 = new WeakSet([val1, "BADVAL", val3]);
// TypeError: Invalid value used in WeakSet
typeof ws2;
// ReferenceError: ws2 is not defined// 原始值可以先包装成对象再用作值
const stringVal = new String("val1");
const ws3 = new WeakSet([stringVal]);
alert(ws3.has(stringVal)); // true

add():添加新值
has():查询
delete():删除

const ws = new WeakSet();
const val1 = {id: 1},val2 = {id: 2};
alert(ws.has(val1)); // falsews.add(val1).add(val2);alert(ws.has(val1)); // true
alert(ws.has(val2)); // truews.delete(val1); // 只删除这一个值alert(ws.has(val1)); // false
alert(ws.has(val2)); // true// add()方法返回弱集合实例,因此可以把多个操作连缀起来,包括初始化声明:
const val1 = {id: 1},
val2 = {id: 2},
val3 = {id: 3};
const ws = new WeakSet().add(val1);
ws.add(val2).add(val3);alert(ws.has(val1)); // true
alert(ws.has(val2)); // true
alert(ws.has(val3)); // true

WeakSet 中“weak”表示弱集合的值是“弱弱地拿着”的。意思就是,这些值不属于正式的引用,不会阻止垃圾回收。
不可迭代值:因为 WeakSet 中的值任何时候都可能被销毁,所以没必要提供迭代其值的能力。
使用弱集合,给对象打标签:

const disabledElements = new Set();
const loginButton = document.querySelector('#login');
// 通过加入对应集合,给这个节点打上“禁用”标签,不可被垃圾回收
disabledElements.add(loginButton);// 可以被垃圾回收
const disabledElements = new WeakSet();
const loginButton = document.querySelector('#login');
// 通过加入对应集合,给这个节点打上“禁用”标签
disabledElements.add(loginButton);

☆☆介绍下 Set、Map、WeakSet、WeakMap 的区别
参考答案:
Set

  • 成员唯一、无序且不重复
  • 键值与键名是一致的(或者说只有键值,没有键名)
  • 可以遍历,方法有 add, delete,has

WeakSet

  • 成员都是对象
  • 成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏
  • 不能遍历,方法有 add, delete,has

Map

  • 本质上是健值对的集合,类似集合
  • 可以遍历,方法很多,可以跟各种数据格式转换

WeakMap

  • 只接受对象作为健名(null 除外),不接受其他类型的值作为键名
  • 键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾机制回收,此时键名是无效的
  • 不能遍历,方法有 get、set、has、delete

8. 迭代与扩展操作

数组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 = [{}];
let arr2 = [...arr1];
arr1[0].foo = 'bar';
console.log(arr2[0]); // { foo: 'bar' }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/22842.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySQL是怎么保证持久性的(redo log日志相关)

Mysql中 事务的很多实现&#xff0c;都是因为有日志的支撑&#xff0c;比如binlog、undo log、redo log等 MySQL是怎么保证持久性的 持久性是指&#xff0c;事务一旦提交&#xff0c;它对数据库的改变就应该是永久性的&#xff0c;接下来的其他操作或故障不能对其有影响。In…

大数据数据治理

大数据数据治理介绍 大数据数据治理是一个复杂的过程&#xff0c;涉及到数据的标准化、融通、关联、解析、聚合等一系列活动。其核心目标是在确保数据安全的基础上&#xff0c;提高大数据资源和资产的可用性、易用性和可靠性&#xff0c;从而显著提升大数据资源和资产的价值7。…

Ktor库的高级用法:代理服务器与JSON处理

在现代网络编程中&#xff0c;Ktor是一个高性能且易于使用的框架&#xff0c;它提供了对异步编程、WebSockets、HTTP客户端和服务器等特性的原生支持。Ktor是使用Kotlin语言编写的&#xff0c;充分利用了Kotlin的协程特性来简化异步编程。本文将深入探讨Ktor库的高级用法&#…

AlDente Pro for Mac(电池最大充电限制工具)v1.24激活版

AlDente Pro for Mac是一款运行在MacOS平台上专业的电池最大充电限制工具。通过 AlDente Pro 您可以设置电池的最大充电百分比设置为 20&#xff05; 至 100&#xff05;&#xff0c;然后&#xff0c;它将保持在所需的电池百分比&#xff0c;然后再次使用电源适配器进行充电。 …

安徽某高校数据挖掘作业4-5 (与一些碎碎念)

1. 编写程序求函数、、的极限。 解答&#xff1a; import sympy as sp# 定义符号变量 x x sp.symbols(x)# 定义函数 f1 sp.sin(20 * x) / x f2 (1 4 * x)**(2 / x) f3 (1 4 / x)**(2 * x)# 计算极限 limit1 sp.limit(f1, x, 0) limit2 sp.limit(f2, x, 0) limit3 sp…

跨域请求解决方法----不允许有多个 ‘Access-Control-Allow-Origin‘ CORS 头

后端配置了代码&#xff1a; spring:application:name: spzx-server-gatewaycloud:nacos:discovery:server-addr: localhost:8848gateway:discovery:locator:enabled: trueglobalcors:cors-configurations:[/**]:allowedOriginPatterns: "*"# 允许请求中携带的头信息…

连锁门面预付费电表管理系统

1.什么叫连锁门面预付费电表管理系统? 连锁门面预付费电表管理系统是一种前沿的电力管理解决方案&#xff0c;尤其适用于拥有众多分店的服务提供商。该系统容许用户使用电力以前预先支付花费&#xff0c;有效解决传统式后付模式的收支明细纠纷案件和扣费难题。 2.系统的核心…

非递归实现快排排序及归并排序(尾篇)

1.快速排序&#xff08;双指针实现&#xff09; 2.非递归实现快排 3.递归实现归并排序 4.非递归实现归并排序 5.总代码 1.快速排序&#xff08;双指针实现&#xff09; 俩有个指针一前一后的排放着&#xff0c;cur先走并且去找比kye对应值小的数组值&#xff0c;一旦找到后…

x86国产化麒麟系统上安装docker及问题解决

以前感觉安装docker没有问题&#xff0c;所以没有记录怎么安装的&#xff0c;最近在国产化系统上安装docker总是失败&#xff0c;经过仔细研究完全解决了该问题&#xff0c;特此记录。 参考链接&#xff1a; 在 OpenKylin 上安装 Docker 按照上面的链接可以知道整个docker安装…

EMC整改学习-笔记

EMC整改学习-笔记 来自赛盛技术的笔记 如果我拿到一个产品超标的一个频谱图的话&#xff0c;首先我们可以对比做一个分析。来确定你干扰源的一个分类和定义是哪些。是你这个产品类型&#xff0c;什么样的电路对应什么样的一个。从我们的一个大量的一个测试数据的经验来看&…

超全面,编程语言汇总,看看哪些语言适合GIS开发?

最近总有很多人关心GIS开发语言的问题&#xff0c;这个确实很重要&#xff0c;毕竟学习一门编程语言需要花费不少时间和精力&#xff0c;找不到合适GIS的编程语言意味着浪费时间。 首先我们来简单看一下常见的编程语言有哪些&#xff0c;后续再给大家介绍哪些语言适合GIS开发&a…

如何获知SylixOS某API调用方法?

问题背景 SylixOS从2006年韩总在大学时开始开发&#xff0c;到今年2024年已走过18个年头&#xff0c;这18年一直在不断迭代更新&#xff0c;也经过无数实际项目的考验&#xff0c;SylixOS无论从功能还是性能上看&#xff0c;都毫无疑问是世界一流的大型实时操作系统。 但实时…

AI大模型还没有到卷长文的时候

AI风口上&#xff0c;国内大模型技术突飞猛进&#xff0c;很多人都沉浸在用AI来辅助办公&#xff0c;辅助学习等等工具化应用落地&#xff0c;但也有趁着风口想大赚一笔&#xff0c;为了估值什么都敢说的。 前几天&#xff0c;Kimi对外宣称自己的技术狂飙到能读200万字甚至100…

标准发布 | 高氨氮废水厌氧氨氧化处理应用技术规范

高氨氮废水厌氧氨氧化处理应用技术规范 Technical specification for anammox treatment of high strength ammonia nitrogen wastewater 一、编制单位 本文件由中华环保联合会水环境治理专业委员会提出。 本文件由中华环保联合会归口。 本文件主编单位&#xff1a;北京城市…

SSRF思路及步骤

什么是SSRF&#xff1f; SERVER SIDE REQUEST FORGERY的简写 因为服务器本身提供了资源获取相关的服务&#xff0c;在资源地址能够被用户控制时&#xff0c;将可能导致攻击者利用服务器身份获取预期外的资源的后果 危害&#xff1f; 条件满足的情况下&#xff1a;突破网络防…

佛教祭拜小程序-寺庙小程序-纪念馆小程序

大家好&#xff0c;我是程序员小孟。 现在有很多的产品或者工具都开始信息话了&#xff0c;寺庙或者佛教也需要小程序吗&#xff1f; 当然了&#xff01; 前面我们还开发了很多寺庙相关的小程序&#xff0c;都有相关的介绍&#xff1a; 1,优质的寺庙小程序-H5寺庙网页 今天…

机器学习多场景实战

机器学习已不再局限于理论探讨&#xff0c;而是广泛渗透到我们生活的方方面面&#xff0c;成为解决复杂问题、优化决策过程的强有力工具。从智能推荐系统个性化推送你可能喜爱的电影和商品&#xff0c;到金融风控领域精准识别欺诈交易&#xff1b;每一个应用场景都是机器学习技…

(学习笔记)数仓建模

数仓建模 OLAP数仓分层数据模型数据模型建设方法模型建设具体流程模型数据域事实表设计事实表拉链表 数据模型规范表命名(采用阿里one-data设计)字段命名(采用阿里one-data设计)数据模型标注规范 数据模型发展周期 OLAP OLTP&#xff1a;概念全称OnLine Transaction Processin…

实验七、创建小型实验拓扑《计算机网络》

早检到底是谁发明出来的。 一、实验目的 完成本实验后&#xff0c;您将能够&#xff1a; • 设计逻辑网络。 • 配置物理实验拓扑。 • 配置 LAN 逻辑拓扑。 • 验证 LAN 连通性。 二、实验任务 在本实验中&#xff0c;将要求您连接网络设备并配置主机实现基本的网络…

【YOLO系列】YOLOv10论文超详细解读(翻译 +学习笔记)

前言 研究AI的同学们面对的一个普遍痛点是&#xff0c;刚开始深入研究一项新技术&#xff0c;没等明白透彻&#xff0c;就又迎来了新的更新版本——就像我还在忙着逐行分析2月份发布的YOLOv9代码&#xff0c;5月底清华的大佬们就推出了全新的v10。。。 在繁忙之余&#xff0…