精读《JavaScript 高级程序设计 第4版》第6章 集合引用类型(一)
6.1 Object
6.1.1 创建对象
使用 new 操作符与Object构造函数
let person = new Object(); //let person = {} 等效
person.name = "Nicholas";
person.age = 29;
使用对象字面量(最常用):
let person = {
name: "Nicholas",
age: 29,
5: true //数值属性会自动转换为字符串
};
6.1.2 属性的存取
- 点语法:
person.name
- 中括号语法:
person["name"]
-
- 中括号语法的主要优势在于可以通过变量访问属性,并且属性名可以是无效标识符(如包含空格、数字开头等)。
let propertyName = "name";
console.log(person[propertyName]); // "Nicholas"
person["first name"] = "Nicholas"; // 属性名包含空格,必须使用中括号
6.1.3 现代JavaScript(ES6+)的进步与增强
1. 属性定义的简写(Property Shorthand)
当变量名和属性名相同时,可以省略冒号和值。
const name = "Alice";
const age = 24;
// ES5
const personES5 = {
name: name,
age: age
};
// ES6+
const personES6 = {
name,
age
};
2. 可计算属性名(Computed Property Names)
在对象字面量中,可以直接使用中括号 [] 来定义动态的属性名。
const propPrefix = "user_";
const id = 123;
// ES5:需要两步
const objES5 = {};
objES5[propPrefix + id] = "value";
// ES6+:一步到位
const objES6 = {
[`${propPrefix}${id}`]: "value"
};
console.log(objES6); // { user_123: "value" }
3. 方法定义的简写(Method Shorthand)
在对象中定义函数可以省略 : function。
// ES5
const objES5 = {
sayHello: function() {
console.log("Hello!");
}
};
// ES6+
const objES6 = {
sayHello() { // 更简洁,与类的方法定义风格一致
console.log("Hello!");
},
// 也可以使用箭头函数,但需注意 `this` 的指向问题
sayGoodbye: () => {
console.log("Goodbye!");
}
};
6.2 Array
ECMAScript数组是一组有序的数据,但区别于其他语言的地方是数组中的每个槽位可以存储任意类型的数据。可以第一个元素为字符串,第二个元素为对象。且ECMAScript数组也是动态大小的,会随着数据添加而自动增长。
6.2.1 创建数组
- 使用
Array构造函数:
使用Array构造函数时也可以省略new操作符,结果一致。
let colors = new Array();
let colors = new Array(20); // 创建length为20的数组
let colors = new Array("red", "blue", "green");
- 使用数组字面量(推荐):
let colors = ["red", "blue", "green"];
let values = [1, 2, ]; // 注意:可能创建包含空位的数组
- Array.from() 的增强使用
Array.from() 不仅用于类数组转换(将数组结构转换为数组实例),还有强大的映射功能:
// 字符串会被拆分为单字符数组
const strArr = Array.from("Matt"); // ["M", "a", "t", "t"]
// 从类数组对象创建数组
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const realArray = Array.from(arrayLike); // ['a', 'b']
// 从可迭代对象创建数组
const set = new Set(['foo', 'bar', 'baz']);
const arrayFromSet = Array.from(set); // ['foo', 'bar', 'baz']
// 带映射函数的Array.from()
const squares = Array.from([1, 2, 3], x => x * x); // [1, 4, 9]
// 等价于:Array.from([1,2,3]).map(x => x * x)
// 对现有数组进行浅复制
const a1 = [1, 2, 3, 4];
const a2 = Array.from(a1); // [1, 2, 3, 4]
console.log(a1 === a2); //false
Array.of():
解决 new Array() 构造函数的行为不一致问题,将一组参数转换为数组实例
// 传统Array构造函数的问题
console.log(new Array(3)); // [空属性 × 3] - 创建长度为3的空数组
console.log(new Array(1, 2)); // [1, 2] - 创建包含两个元素的数组
// Array.of() 的行为一致
console.log(Array.of(3)); // [3] - 总是创建包含参数的数组
console.log(Array.of(1, 2)); // [1, 2]
6.2.2 数组空位
使用数组字面量初始化数组时,可以使用一串逗号来创建空位。
const options = [,,,,,]; // 创建包含5个空位的数组
console.log(options.length); // 5
ES6新增方法普遍将这些空位当成存在的元素,只不过值为undefined:
console.log(Array.of(...[,,,])); //[undefined, undefined, undefined]
实践中尽量避免使用数组空位,如果确实需要建议显式使用undefined值代替。
6.2.3 数组索引
要取得或设置数组的值,需要使用中括号并提供相应值的数字索引。且length属性并不是只读的,可以通过修改length属性值来进行末尾删除或添加操作。数组最后一个值的索引始终为length - 1。
let colors = ["red", "blue", "green"];
colors.length = 2; // 截断数组,删除最后一个元素
console.log(colors[2]); // undefined
colors.length = 4; // 扩展数组,新增的空位用undefined填充
console.log(colors[3]); // undefined
colors[99] = "black"; 数组长度变成了100,中间的未定义值访问时均返回undefined
6.2.4 检测数组
Array.isArray() 方法(ES5引入,比 instanceof 更可靠):
console.log(Array.isArray(colors)); // true
6.2.5 迭代器方法
在ES6中,Array原型上暴露了3个用于检索数组内容的方法:
keys()返回数组索引的迭代器、values()返回数组元素的迭代器、entries()返回索引/值对的迭代器:
const a = ["foo", "bar", "baz", "qux"];
// 获取索引迭代器
const aKeys = Array.from(a.keys()); // [0, 1, 2, 3]
// 获取值迭代器
const aValues = Array.from(a.values()); // ["foo", "bar", "baz", "qux"]
// 获取键/值对迭代器
const aEntries = Array.from(a.entries()); // [[0, "foo"], [1, "bar"], [2, "baz"], [3, "qux"]]
6.2.6 复制和填充方法
ES6新增的两个方法:批量浅复制方法copyWithin()和填充数组方法fill(),这两个函数都需要指定既有数组实例上的一个范围,包含开始索引,不包含结束索引,若不提供结束索引则默认到达数组边界为止。
copyWithin():按照指定范围浅复制数组的部分内容,然后插入到指定索引开始的位置。该方法静默忽略超出数组边界、零长度及方向相反的索引范围。
let ints,
reset = () => ints = [0,1,2,3,4,5,6,7,8,9];
reset();
//从ints中复制索引0开始的内容,插入到索引5开始的位置
ints.copyWithin(5,0);
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);
console.log(ints); //[0,1,2,3,0,1,2,7,8,9]
reset();
//支持负索引值
ints.copyWithin(-4,-7,-3);
console.log(ints); //[0,1,2,3,4,5,3,4,5,6]
reset();
//索引部分可用,复制、填充可用部分
ints.copyWithin(4,7,10);
console.log(ints); //[0,1,2,3,7,8,9,7,8,9]
reset();
fill():向数组指定的索引范围填充元素。开始索引是可选的,用于指定开始填充的位置;如果不提供结束索引则一直填充到数组末尾。负值索引从数组末尾开始计算。静默忽略超出数组边界、零长度及方向相反的索引范围。
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的部分
zeroes.fill(8,-4,-1);
console.log(zeroes); //[0,8,8,8,0]
zeroes.fill(0); //重置
//索引部分可用,填充可用部分
zeroes.fill(4,3,10);
console.log(zeroes); //[0,0,0,4,4]
6.2.7 转换方法
所有对象都有toLocaleString()、toString()和valueOf()方法。
- valueOf():返回数组本身
- toString():返回由数组中每个值的等效字符串拼接而成的一个逗号分隔的字符串。对每个元素会调用其toString()方法。
- toLocaleString():可能返回与上述两个方法一样的结果,但也不一定。这个方法也会得到一个逗号分隔的数组值的字符串,但是会调用数组每个元素的toLocaleString()方法而不是toString()方法。(要区分数组对象的toLocaleString()和toString()方法定义的情况来判断是否返回同样值)
let colors = ['red', 'green', 'blue'];
console.log(colors.valueOf()); //[ 'red', 'green', 'blue' ]
console.log(colors.toString()); //red,green,blue
console.log(colors.toLocaleString());//red,green,blue
//可以使用join方法来更换分隔符。
console.log(colors.join('-'));//red-green-blue
注意:如果数组中的某一项是null或者undefined,则在join()、toLocaleString()、toString()和valueOf()返回的结果中会以空字符串表示。
6.2.8 栈方法
栈是一种后进先出(LIFO)的结构,数据的插入(推入push)和删除(弹出pop)只在栈的一个地方发生,即栈顶。
- push():接收任意数量的参数,并将它们添加到数组末尾,返回数组的最新长度。
- pop():删除数组的最后一项,同时减少length值,返回被删除的数据项。
let colors = new Array();
// push() - 推入项到数组末尾
let count = colors.push("red", "green"); // count = 2
// pop() - 弹出最后一项
let item = colors.pop(); // item = "green"
6.2.9 队列方法
队列以先进先出(FIFO)的形式限制访问。队列在末尾添加数据,但从列表开头获取数据。
- push():向数组末尾添加数据
- shift():从数组开头删除数据,返回删除项
- pop():从数组末尾删除数据,返回删除项
- unshift():从数组开头添加数据
let colors = new Array();
// push() + shift() - 实现队列
colors.push("red", "green");
let item = colors.shift(); // item = "red"
// unshift() + pop() - 实现反向队列
colors.unshift("red", "green");
let item = colors.pop(); // item = "green"
6.2.10 排序方法
reverse():反转数组顺序
let values = [1, 2, 3, 4];
values.reverse();
console.log(values); //4, 3, 2, 1
sort():排序数组(默认按字符串Unicode码点)
默认情况下会按照升序排列数组元素,小的在前,大的在后。每一个元素都会调用String()转型函数,然后比较字符串来决定顺序,即使数组元素是数值。
let values = [0, 1, 5, 10, 15];
values.sort();
console.log(values); //0, 1, 10, 15, 5
sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面。比较函数接收两个参数,如果第一个参数应该排在第二个参数前面,则返回负值;相等返回0;相反位置返回正值。
let values = [0, 1, 5, 10, 15];
values.sort((a, b) => b - a);
console.log(values); //15,10,5,1,0
注意以上两个方法都返回调用它们的数组的引用。
6.2.11 操作方法
concat():合并数组,创建新数组。会先创建一个当前数组的副本,然后再添加参数到数组末尾。不改变原数组。
let colors = ["red", "green", "blue"];
let colors2 = colors.concat("yellow", ["black", "brown"]);
slice():创建数组的切片(浅拷贝)接收一个或两个参数,返回从开始索引到结束索引对应的所有元素,但不包含结束索引。如果参数为负值,则以数值加上数组长度即可确定位置。如5个元素的数组调用slice(-2, -1)相当于调用slice(3, 4)。若结束索引小于开始索引则返回空数组。
let colors = ["red", "green", "blue", "yellow", "purple"];
let colors2 = colors.slice(1); // ["green", "blue", "yellow", "purple"]
let colors3 = colors.slice(1, 4); // ["green", "blue", "yellow"]
splice():强大的数组修改方法。
let colors = ["red", "green", "blue"];
// 删除:splice(起始位置, 删除项数)
let removed = colors.splice(0, 1); // 删除第一项,removed = ["red"]
// 插入:splice(起始位置, 0, 要插入的项)
removed = colors.splice(1, 0, "yellow", "orange"); // 在位置1插入两项
// 替换:splice(起始位置, 要替换的项数, 要插入的项)
removed = colors.splice(1, 1, "red", "purple"); // 在位置1删除1项,插入两项
6.2.12 搜索和位置方法
- 严格相等:比较时会使用(===)全等比较,要求严格相等
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
// indexOf() - 从前向后找
console.log(numbers.indexOf(4)); // 3
// lastIndexOf() - 从后向前找
console.log(numbers.lastIndexOf(4)); // 5
// includes() - ES7新增
console.log(numbers.includes(4)); // true
- 断言函数(ES6):接收三个参数——元素(当前搜索的元素)、索引(当前元素的索引)和数组本身(正在搜索的数组)
const people = [
{ name: "Matt", age: 27 },
{ name: "Nicholas", age: 29 }
];
// find() - 找到第一个匹配项
console.log(people.find((element, index, array) => element.age < 28));
// { name: "Matt", age: 27 }
// findIndex() - 找到第一个匹配项的索引
console.log(people.findIndex((element, index, array) => element.age < 28));
// 0
6.2.13 迭代方法
定义了5个迭代方法,每个方法接收两个参数:以每一项为参数运行的函数,以及可选的作为函数运行上下文的作用域对象(影响函数中的this值)。传给每个方法的函数接收3个参数:数组元素、元素索引和数组本身。
let numbers = [1, 2, 3, 4, 5, 4, 3, 2, 1];
// every() - 每项都满足条件则返回true
let everyResult = numbers.every((item, index, array) => item > 2);
console.log(everyResult); // false
// some() - 至少一项满足条件则返回true
let someResult = numbers.some((item, index, array) => item > 2);
console.log(someResult); // true
// filter() - 返回满足条件的项组成的新数组
let filterResult = numbers.filter((item, index, array) => item > 2);
console.log(filterResult); // [3, 4, 5, 3]
// forEach() - 对每项执行操作,无返回值
numbers.forEach((item, index, array) => {
// 执行某些操作
});
// map() - 对每项执行操作,返回结果组成的新数组
let mapResult = numbers.map((item, index, array) => item * 2);
console.log(mapResult); // [2, 4, 6, 8, 10, 8, 6, 4, 2]
6.2.14 归并方法
reduce() 和 reduceRight():这两个方法都会迭代数组的所有项,并在此基础上构建一个最终返回值。reduce()从第一项遍历到最后一项,reduceRight()则相反。都接收两个参数:一个对每一项都会运行的归并函数,以及可选的以之为归并起点的初始值。这两个方法的归并函数都接收4个参数:上一个归并值、当前项、当前项的索引和数组本身。该函数返回的任何值都会作为下一次调用同一个函数的第一个参数。
let values = [1, 2, 3, 4, 5];
// reduce() - 从左向右归并
let sum = values.reduce((prev, cur, index, array) => prev + cur);
console.log(sum); // 15
// reduceRight() - 从右向左归并
let sumRight = values.reduceRight((prev, cur, index, array) => prev + cur);
console.log(sumRight); // 15
6.2.15 现代JavaScript(ES6+)的进步与增强
1. 扩展运算符(Spread Operator)
这是数组最重要的现代特性之一,极大地简化了数组操作:
// 数组浅拷贝
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // [1, 2, 3]
console.log(arr1 === arr2); // false
// 数组合并 - 替代 concat()
const moreNumbers = [4, 5, 6];
const combined = [...arr1, ...moreNumbers]; // [1, 2, 3, 4, 5, 6]
// 函数参数传递
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3 - 替代 apply()
// 与解构赋值结合
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first); // 1
console.log(rest); // [2, 3, 4, 5]
2.新的实例方法(ES2023+)
这些是相对较新但非常重要的补充:
findLast() 和 findLastIndex()(ES2023):
从数组末尾开始搜索,对于需要"最后一个匹配项"的场景非常有用:
const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 2 }];
// 找到最后一个value为2的元素
const lastMatch = array.findLast(item => item.value === 2);
console.log(lastMatch); // { value: 2 } (最后一个)
// 找到最后一个value为2的元素的索引
const lastIndex = array.findLastIndex(item => item.value === 2);
console.log(lastIndex); // 3
toReversed()、toSorted()、toSpliced()、with()(ES2023):
这些是相应可变方法的不可变版本,返回新数组而不修改原数组:
const original = [3, 1, 4, 1, 5];
// toReversed() - 不可变版本的 reverse()
const reversed = original.toReversed();
console.log(original); // [3, 1, 4, 1, 5] (未改变)
console.log(reversed); // [5, 1, 4, 1, 3]
// toSorted() - 不可变版本的 sort()
const sorted = original.toSorted();
console.log(original); // [3, 1, 4, 1, 5] (未改变)
console.log(sorted); // [1, 1, 3, 4, 5]
// with() - 不可变版本的 arr[index] = value
const updated = original.with(2, 99);
console.log(original); // [3, 1, 4, 1, 5] (未改变)
console.log(updated); // [3, 1, 99, 1, 5]
// toSpliced() - 不可变版本的 splice()
const spliced = original.toSpliced(1, 2, 6, 7);
console.log(original); // [3, 1, 4, 1, 5] (未改变)
console.log(spliced); // [3, 6, 7, 1, 5]
3.性能优化和最佳实践
现代JS引擎的优化使得某些模式更加高效:
// 使用数组字面量而不是new Array()
const good = []; // ✓ 推荐
const bad = new Array(); // ✗ 不推荐
// 使用push而不是直接赋值来扩展数组
const arr = [];
for (let i = 0; i < 1000; i++) {
arr.push(i); // ✓ 推荐 - 引擎优化更好
// arr[i] = i; // ✗ 在某些情况下较慢
}
// 使用类型化数组处理数值数据
const typedArray = new Int32Array(1000); // 对于数值计算更高效更多推荐



所有评论(0)