JS高级程序设计——阅读笔记四


本系列博客主要是面向自己创作,实属自己的读书笔记,注重记录一些重点,并摘录引用一些大佬对于部分知识点的解释,帮助自己翻阅和理解,一定要配合原著食用。

第六章 集合引用类型

6.1 Object

在对象字面量定义的数值属性会自动转换为字符串。

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

这二者是等价的。

6.2 Array

6.2.1 创建数组

使用new来创建,传入的参数可以是数组的长度,也可以是数组的元素。

//Array构造函数
new Array()
let colors = new Array(20);
let colors = new Array("red", "blue", "green");

//在使用 Array 构造函数时,也可以省略 new 操作符。结果是一样的,比如:
let colors = Array(3); // 创建一个包含 3 个元素的数组
let names = Array("Greg"); // 创建一个只包含一个元素,即字符串"Greg"的数组

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

但是如果创建数组时给构造函数传递一个值就会存在歧义,如果这个值是数值,那么就会被作为数组的长度,如果是其他类型,就会创建一个只包含该值的数组。

与对象一样,使用数组字面量创建数组不会调用数组类型对象的构造函数。

from()函数
// 字符串会被拆分为单字符数组
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.map()方法的简写方式。
Array.from()还可以接收第三个可选参数,用于指定映射函数中 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]

由于书上写的第三个参数的用法比较晦涩,我们来写个demo观察输出来输出看一下第三个参数的形式:

const a1 = [1, 2, 3, 4]; 
const a3 = Array.from(a1, function(x) {
    console.log(this);
    return x**this.exponent
}, {exponent: 2});

输出为:
在这里插入图片描述
可以看出输出的对象就是from()的第三个参数。但是我们有另外的发现,这个函数输出的次数是整个数组的长度,这说明第二个参数的函数会根据数组的长度进行函数的调用,这也许在长度过长且函数内容较为附加的情况下出现阻塞后续代码运行的现象。

6.2.2 数组空位

该部分主要需要注意,数组空值由于行为不一致和存在性能隐患,所以在实践过程中要尽量避免使用。如果实在需要,最好显式的使用undefined。

6.2.3 数组索引

数组的length属性并不只是可读的,可以通过修改length属性,来删除数组末尾元素。

let colors = ["r","b","g"];
colors[99] = "black";
console.log(colors.length)//100
console.log(colors[50])//undefined

可以看到数组的长度改变了,而且中间的值都被填充为了undefined。

6.2.4 检测数组
if(value instanceof Array) {}

或者

if(Array.isArray(value)){}
6.2.5 迭代器方法

这部分最好通过代码示例来解释:

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

以及拆分键值对的方法:

const a = ["foo", "bar", "baz", "qux"];
for (const [idx, element] of a.entries()) {
	alert(idx);
	alert(element);
}
6.2.6 复制和填充方法

这部分主要包含两种方法,fill()和copyWithin()

fill() 可以向一个已有的数组中插入全部或部分相同的值。开始索引用于指定开始填充
的位置,它是可选的。如果不提供结束索引,则一直填充到数组末尾。负值索引从数组末尾开始计算。也可以将负索引想象成数组长度加上它得到的一个正索引。

copyWithin() 会按照指定范围浅复制数组中的部分内容,然后将它们插入到指 定索引开始的位置。开始索引和结束索引则与fill()使用同样的计算方法。

6.2.7 转换方法

Array类型的转换方法主要有三个,分别是toString()、valueOf()、join()。
toString()是输出数组样式的字符串。
valueOf()是输出原数组。
join()通过将接收的一个参数作为分隔符,将数组转化为每个元素由分隔符隔开的字符串。

6.2.8 栈方法

JS的数组可以作为栈和队列数据结构进行使用,对于栈数据结构而言,后进先出数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个地方发生,即栈顶。而栈顶元素的获取方法就是输出array[length-1]。

注意,pop()和push()方法都会对原数组进行修改。

6.2.9 队列方法

对于队列数据结构而言,先进先出push()、shift() 移除数组中的第一项并返回该项。
unshift() 在数组顶端添加任意项并返回,返回新数组的长度pop()。

6.2.10 排序方法

包含reverse()sort()

reverse()

该方法可以对数组元素进行转置。

sort()

默认按照升序排列顺序,而且sort()方法默认比较的是字符串的顺序,可以观察以下demo:
在这里插入图片描述
原数组是升序的,但是经过sort排序后,顺序反而变乱了,然后又验证了字符串'5'和字符串'10'的大小,发现虽然数值5<10,但是字符串'5'>'10'
所以,想要正常使用升序排列数值的sort方法需要再sort()的参数中接受一个比较函数,用于判断哪个值放在前面:传入的比较函数包含两个参数,如果第一个参数应该排在第二个参数前面,就返回负值;如果两个参数相等,就返回0;如果第一个参数应该排在第二个参数后面,就返回正值。

function compare(value1, value2) {
	if (value1 < value2) {
		return -1;
	} else if (value1 > value2) {
		return 1;
	} else {
		return 0;
	}
}

注意,比较函数就是要返回小于0、大于0、等于0的数值,因此再数组元素是数值的情况下,使用减法和箭头函数更加简单:

arr.sort((a,b) => b-a);
6.2.11 操作方法
concat()

该方法不会修改原数组,而且该方法会打平数组参数,但实际上只能打平一层:

let colors = ['r','g','b'];
let colors2 = colors.concat('yellow',['black','pink'],[['a','b'],'c']);
console.log(colors);
console.log(colors2);

这说明concat()方法内部是将传入为数组的参数逐个元素添加到原数组的拷贝上,而不会进行更深层的判断。

//打平数组参数的行为可以重写
//Symbol.isConcatSpreadable
let colors=[1,2,3]
let newColors=[3,4]
let moreNewColors={
	[Symbol.isConcatSpreadable]:true,
	length:2,
	0:5,
	1:6
}
newColor[Symbol.isConcatSpreadable]=false
//强制不打平数组
let colors2=colors.concat(newColors)
//强制打平类数组对象
let colors3=colors.concat(moreNewColors)

这种打平数组参数的方式有点复杂,实际也没有使用过,可以先做为了解。

slice()

该方法可以传递两个参数分别表示截取的开始索引和结束索引,而且该操作也不会影响原始数组。

splice()

该方法可以实现删除、插入和替换功能。
这些功能的实现是递进的,比如插入操作的实现方式是删除功能的基础上传递一个新的插入元素,并将传递的第二个参数-用来表示删除元素数量的参数值设置为0,表示只新增不删除,就实现了插入。替换功能则是先删除再插入,只需改变第二个参数的值即可。

6.2.12 搜索和位置方法
全等操作

indexOf()
参数:要查找的项,[查找起点位置的索引]
返回:要查找的元素在数组中的位置,如果没找到则返回1。

lastIndexOf()
没找到-1

includes()
返回布尔值,表示是否至少找到一个与指定元素匹配的项

这三者再比较时是需要保证严格相等,也就是===的。

断言函数

find()
返回第一个匹配的元素
这两个方法也都接收第二个可选的参数,
用于指定断言函数内部 this 的值。

findIndex()
返回第一个匹配元素的索引

6.2.13 迭代方法

参数:1.要在每项上运行的函数 2.[运行该函数的作用域对象,影响this指向]
函数的参数:1.数组项的值 2.该项在数组中的位置 3.数组本身都不修改数组包含的值

every() 对数组中的每一项运行给定函数,该函数对每一项都返回true,则返回true

filter() 返回该函数会返回true的项组成的数组

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

map() 对数组每一项都运行传入的函数,返回由每次函数调用的结果构成的数组

some() 任意一项返回true,则返回true

6.2.14 归并方法

reduce()、reduceRight()方法迭代数组的所有项,构建一个最终的返回值。
参数:1.一个在每项上调用的函数 2.[作为缩小基础的初始值]

函数的参数:前一个值,当前值,项的索引,数组对象
这个函数返回的任何值都会作为第一个参数自动传给下一项,第一次迭代发生在数组的第二项上,第一个参数是数组的第一项,第二个参数是数组的第二项。

var value=[1,2,3];
var sum=value.reduce((pre,cur,index,arr)=>{
	return pre+cur;
})

6.3 定型数组

这是第一次接触定型数组的概念,定型数组提供了适用面更广的API和更高的性能。**设计定型数组的目的就是提高与WebGL等原生库交换二进制数据的效率。**由于定型数组的二进制表示对操作系统而言是一种容易使用的格式,JavaScript引擎可以重度优化算术运算、按位运算和其他对定型数组的常见操作,因此使用它们速度极快。
JavaScript二进制数组(1)ArrayBuffer-----------传送门
JavaScript二进制数组(2)TypedArray视图-----传送门
JavaScript二进制数组(3)DataView视图-------传送门

6.4 Map

6.4.1 基本API

通过new关键字调用Map构造函数可以创建空映射。
初始化映射的方式有两种,一种是嵌套数组初始化:

const m1=new Map([
["key1","val1"],
["key2","val2"],
["key3","val3"]
])
//可以通过 size 属性获取映射中的键/值对的数量
alert(m1.size); // 3

另一种是使用自定义迭代器初始化映射:

const m2=new Map([
	[Symbol.iterator]:function*(){
		yield ["key1","val1"];
		yield ["key2","val2"];
		yield ["key3","val3"];
	}
])

通常使用的API包含set()方法来添加键值对,get()和has()方法可以进行查询,size属性可以查询键值对数量,delete()和clear()方法可以进行删除值。
注意,set()方法可以链式调用:

const m = new Map().set("key1", "val1"); 
m.set("key2", "val2") 
 .set("key3", "val3"); 
alert(m.size); // 3

这说明set()方法返回值是操作后的Map实例
在这里插入图片描述
object只能使用数值、字符串、符号作为键,Map可以使用任何JS数据类型作为键。
映射中用作键和值的对象及其他“集合”类型,在自己的内容或属性被修改时仍然保持不变。

6.4.2 顺序于迭代

Object与Map的一大不同是,Map实例会维护键值对的插入顺序,所以可以依据输入顺序进行迭代,这一点是以前很少注意到的。
映射实例可以提供一个迭代器(Iterator),能以插入顺序生成[key, value]形式的数组。可以
通过 entries()方法(或者 Symbol.iterator 属性,它引用 entries())取得这个迭代器:

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
console.log(m.entries());

在这里插入图片描述
entries()函数返回一个MapIterator实例,该实例不仅包含每一组键值对,而且是可迭代的。
通过扩展操作可以直接把映射转换为数组:

const m = new Map([ 
 ["key1", "val1"], 
 ["key2", "val2"], 
 ["key3", "val3"] 
]); 
console.log([...m]);

keys()方法能返回一个映射实例
在这里插入图片描述
values()方法也一样
在这里插入图片描述
键和值在迭代器遍历时是可以修改的,但映射内部的引用则无法修改。当然,这并不妨碍修改作为
键或值的对象内部的属性,因为这样并不影响它们在映射实例中的身份:

  • 作为键的字符串原始值是不能修改的
const m1 = new Map([ 
 ["key1", "val1"] 
]); 
for (let key of m1.keys()) { 
 key = "newKey"; 
 alert(key); // newKey 
 alert(m1.get("key1")); // val1 
} 
  • 修改了作为键的对象的属性,但对象在映射内部仍然引用相同的值
const keyObj = {id: 1}; 
const m = new Map([ 
 [keyObj, "val1"] 
]); 
for (let key of m.keys()) { 
 key.id = "newKey"; 
 alert(key); // {id: "newKey"} 
 alert(m.get(keyObj)); // val1 
} 
alert(keyObj); // {id: "newKey"}
6.4.3 Object和Map的选择

1.内存:
给定大小内存下,Map的存储量大于Object约50%。
2.插入性能:
Map更佳。
3.查找速度:
Object较快。
4.删除性能:
Map较优。

6.5 WeakMap

弱映射的区别在于JS垃圾回收机制对该类型的对待方式。
注意,弱映射的键只能是Object或者继承自Object的类型。

实例化的构造函数是WeakMap(),添加键值对使用set(),get()和has()可以进行查询,delete()方法可以进行删除。
WeakMap 的键名引用的对象是弱引用

什么是强引用?

在这里插入图片描述

麻烦的操作势必会造成问题,当忘记了手动删除引用,就会造成内存泄漏

总的来说, WeakMap 保持了对键名所引用的对象的弱引用,即垃圾回收机制不将该引用考虑在内。只要所引用的对象的其他引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap 里面的键名对象和所对应的键值对会自动消失,不用手动删除引用。

  • Map 的键可以是任意类型,WeakMap 只接受对象作为键(null除外),不接受其他类型的值作为键
  • Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;WeakMap
    的键是弱引用,键所指向的对象可以被垃圾回收,此时键是无效的
  • Map 可以被遍历, WeakMap 不能被遍历

参考文章:详解 Map 和 WeakMap 区别以及使用场景

6.5.4 弱映射的使用场景
私有变量

首先再仔细了解一遍私有变量:JS的私有变量
其中就使用了弱映射来存储私有变量,以对象实例为键,以私有成员字典为值。

DOM节点元数据

使用弱映射保存DOM节点实例对象的好处在于,如果页面通过JS进行了改变,某个DOM元素被从DOM树中移除了,但是该DOM元素被保存Map()中,并关联了元数据,那么在移除后,如果不进行手动的清楚或者销毁映射本身,那么内存中还会保留对应的DOM节点,这对宝贵的内存空间有所浪费,弱映射就是用来解决这类问题,只要节点在DOM树中被删除,那么就可以立即释放其内存。

我们可以仔细观察两个类型的区别:
在这里插入图片描述
可以发现有关迭代和大小的方法都不存在于弱映射中,因为弱映射存储的对象实例随时有可能被清楚,所以有可能时刻都在发生变化,那么这类方法是没有存在意义的。

6.6 Set

Set和Map很是相似,这边就只介绍以下Set使用中需要注意的地方:
API有些许不同:

const s = new Set(); 
alert(s.has("Matt")); // false 
alert(s.size); // 0 

//add()增加值 返回集合的实例,所以可以将多个添加操作连缀起来,包括初始化:
s.add("Matt") 
 .add("Frisbie"); 
alert(s.has("Matt")); // true 

//size 取得元素数量
alert(s.size); // 2 
s.delete("Matt"); 

//has()查询
alert(s.has("Matt")); // false 
alert(s.has("Frisbie")); // true 
alert(s.size); // 1 

//delete()和 clear()删除元素:
s.clear(); // 销毁集合实例中的所有值
alert(s.has("Matt")); // false 
alert(s.has("Frisbie")); // false 
alert(s.size); // 0
弱集合的使用

通常用于为DOM元素对象实例打标签时,效果与弱映射相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值