第9章 集合处理(数组、Map、Set)

1. 数组

1.1 创建数组

  • 创建数组有两种基本方式:使用Array构造函数、使用数组字面量[]
// 通过构造函数
const subjects = new Array("Chinese", "Math", "English");
// 通过字面量
const fruits = ["apple", "banana", "pear"];
  • JS的数组是对象
const subjects = new Array("Chinese", "Math", "English");

const fruits = ["apple", "banana", "pear"];

console.log(typeof subjects);
// object
console.log(typeof fruits);
// object

// 对访问不存在的数组索引,会返回undefined
console.log(fruits[4]);
// undefined

// 可以写入超出数组索引的数据项
fruits[4] = "orange";
// 但中间项会自动填充为undefined
console.log(fruits[3]);
// undefined

// length属性返回数组的大小
console.log(subjects.length);
// 3
// 可以手动修改length的值
subjects.length = 4;
// 将length改为比原有值大的数,数组会被扩展,新扩展的元素均为undefined
console.log(subjects);
// ["Chinese", "Math", "English", empty]
// 将length改为比原有值小的数,数组会被裁剪,多余数据项将被抛弃
subjects.length = 2;
console.log(subjects);
// ["Chinese", "Math"]

使用数组字面量创建数组优于数组构造函数(字面量只需要2个字符)

1.2 在数组两端添加删除元素

方法 作用 返回值
push 末尾添加元素 添加元素后数组的长度
pop 末尾删除元素 被删除(弹出)的元素本身
unshift 开头添加元素 添加元素后数组的长度
shift 开头删除元素 被删除(弹出)的元素本身
const fruits = ["apple", "banana", "pear"];

let len = fruits.push("orange");
console.log(len);
// 4
console.log(fruits);
// ["apple", "banana", "pear", "orange"]

let lastFruit = fruits.pop();
console.log(lastFruit);
// orange
console.log(fruits);
// ["apple", "banana", "pear"]

len = fruits.unshift("orange");
console.log(len);
// 4
console.log(fruits);
// ["orange", "apple", "banana", "pear"]

let firstFruit = fruits.shift();
console.log(lastFruit);
// orange
console.log(fruits);
// ["apple", "banana", "pear"]

性能考虑:pop和push只影响数组最后一个元素,unshift和shift增减第一个元素,之后的每个元素的索引都需要调整。因此pop和push比unshift和shift快得多,非特殊情况下不建议使用unshift和shift。

1.3 在数组任意位置添加、删除元素

delete删除数组元素无效

const fruits = ["apple", "banana", "pear"];

delete fruits[1];

// 使用delete关键字并不能删除元素,只能删除对应索引的值,
console.log(fruits);
// ["apple", empty, "pear"]
// 被删除值的元素的值为undefined
console.log(fruits[1]);
// undefined

使用splice方法增、删、改元素

const fruits = ["apple", "banana", "pear"];

// 删除元素
// 两个参数,表示从索引2开始(索引>= 2),删除1个元素
// 返回值为被删去的元素(以数组的格式)
let elems = fruits.splice(2, 1);
console.log(fruits);
// ["apple", "banana"]
console.log(elems);
// ["pear"]

// 新增元素
// 三个以上参数,表示从索引1开始(索引>= 1),删除0个元素,新增后面的剩余参数
// 因为删除的元素个数为0,所以返回值是空数组
elems = fruits.splice(1, 0, "orange", "pear");
console.log(fruits);
// ["apple", "orange", "pear", "banana"]
console.log(elems);
// []

// 两者同时使用
// 三个以上参数,表示表示从索引1开始(索引>= 1),先删除3个元素,新增后面的剩余参数
// 返回值为被删去的元素(以数组的格式)
elems = fruits.splice(1, 3, "peach", "watermelon");
console.log(fruits);
// ["apple", "peach", "watermelon"]
console.log(elems);        
// ["orange", "pear", "banana"]

1.4 数组的常用操作

数组遍历(forEach)

const fruits = ["apple", "banana", "pear"];

// for循环
for(let i = 0; i < fruits.length; i++) {
    console.log(fruits[i]);
}

// forEach方法
fruits.forEach(fruit => {
    console.log(fruit);
});

映射数组(map方法,对数组成员执行操作,并返回结果组成的新数组)

const students = [
    {name: "Wango", age: 24},
    {name: "Lily", age: 25},
    {name: "Jack", age: 18},
];

// 使用forEach提取
let names = [];
students.forEach(student => {
    names.push(student.name);
});

console.log(names);
// ["Wango", "Lily", "Jack"]

// 使用map方法提取,自动将返回值装入数组并返回
let age = students.map(student => student.age);

console.log(age);
// [24, 25, 18]

测试数组元素(every、some)

const students = [
    {name: "Wango", age: 24},
    {name: "Lily", age: 25},
    {name: "Jack", age: 17},
];

// 使用every验证数组内是否每个元素都满足条件
// 回调函数对每个参数执行操作,当所有元素的回调结果都为true,every方法返回true
// 只要有一个为false,则every方法不再执行并返回false
let allStudentsAreAdult = students.every(student => student.age >= 18);

console.log(allStudentsAreAdult);
// fasle

// 使用some验证数组内是否有部分元素都满足条件
// 回调函数对每个参数执行操作,当所有元素的回调结果都为false,some方法返回false
// 只要有一个为true,则some方法不再执行并返回true
let someStudentsAreAdult = students.some(student => student.age >= 18);

console.log(someStudentsAreAdult);
// true

查找元素(find、filter、indexOf、lastIndexOf、findIndex)

const students = [
    {name: "Wango", age: 24},
    {name: "Lily", age: 25},
    {name: "Jack", age: 17},
];

// 使用find查找单一元素,返回满足条件的第一个元素
// 没找到就返回undefined
let seventeen = students.find(student => student.age === 17);
console.log(seventeen.name);
// Jack

// 使用filter查找(过滤其他)元素,返回满足条件的所有元素(数组形式)
// 没找到就返回空数组
let adults = students.filter(student => student.age >= 18);
console.log(adults.map(adult => adult.name));
// ["Wango", "Lily"] 

const apple = ["a", "p", "p", "l", "e"]


// indexOf接收一个元素值,返回顺序查找的特定元素的索引
// 没找到返回-1
let firstIndex = apple.indexOf("p");
console.log(firstIndex);
// 1

// lastIndexOf接收一个元素值,返回逆序查找的特定元素的索引
// 没找到返回-1
let lastIndex = apple.lastIndexOf("p");
console.log(lastIndex);
// 2

// 返回顺序查找的特定元素的索
// 接收回调函数作为参数,所以可以查找对象数组的索引
// 没找到返回-1
let idx = apple.findIndex(a => a === "l");
console.log(idx);
// 3

数组排序(sort)

// 如果回调函数的返回值小于0,第一个元素应该出现在第二个元素之前
// 如果回调函数返回0,两个元素的先后顺序保持不变
// 如果回调函数的返回值大于0,第一个元素应该出现在第二个元素之后

// 如果是纯数字数组,像下面这样写就行了
const nums = [5, 6, 7, 8, 1, 3, 5, 9, 6, 2,];
nums.sort((a, b) => a - b);
console.log(nums);
// [1, 2, 3, 5, 5, 6, 6, 7, 8, 9]

// 字符串没办法加减,只能比较大小,用if判断或者三元操作符判断一下然后返回
let chars = ["f", "a", "c", "u", "p"];
chars.sort((a, b) => {
    return a > b ? 1 : a < b ? -1 : 0;
});
console.log(chars);
// ["a", "c", "f", "p", "u"]

// 如果需要从大到小排序就换一下返回值

合计数组元素(reduce)

const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];

// reduce方法接收一个回调函数
// 回调函数有两个必需参数, 第一个为累加器,用于存储计算结果,
// 第二个为当前正在处理的元素
let sum = nums.reduce((acc, cur) => acc + cur);
console.log(sum);
// 45

1.5 复用内置的数组函数(复用已经编写的代码)

<input type="text" id="first">
<input type="button" id="second">

<script>
    // 创建一个可以复用数组方法的对象
    const elems = {
        length: 0,  // 模拟数组长度
        add: function(elem) {
            // 复用数组代码
            // 注意:原代码有返回值的,本代码也要有返回值
            // 避免造成混乱
            return Array.prototype.push.call(this, elem);
        },
        gather: function(id) {
            return this.add(document.getElementById(id));
        },
        find: function(callback) {
            return Array.prototype.find.call(this, callback);
        }
    }
    elems.gather("first");
    elems.gather("second");
    // 数组代码会对length属性进行操作,
    // 所以不需要再对length进行手动操作
    console.log(elems.length);
    // 2
    
    const first = elems.find(elem => elem.id === "first");
    console.log(first.type);
    // text
</script>

2. Map

2.1 对象不是Map

访问对象未显式定义的属性,返回非预期

// 从字面看,对象符合字典的要求:键值对
// 但作为对象,其具有很多隐式属性
const student = {
    wango: { id: 578, age: 24 },
    lily: { id: 629, age: 25 },
    jack: { id: 634, age: 22 }
}

// 对于未定义的属性,期望返回undefined,但
console.log(student.constructor);
// Object() { [native code] }

将对象的key映射为HTML节点造成key重复

const elems = {};

const first = document.getElementById("first");
const second = document.getElementById("second");

// HTML元素对象作为key,加入对象
elems[first] = "First Element";
elems[second] = "Second Element";

// 对象的key必须是字符串,非字符串类型会在后台静默调用toString方法
// 而相同类型元素的toString方法返回值相同,导致key重复
console.log(first.toString() === second.toString());
// true

// 前一个数据被后者替换
console.log(elems[first]);
// Second Element
console.log(elems[second]);
// Second Element

对象的原型有额外的继承属性、key仅支持字符串,所以通常不能使用对象作为map

2.2 创建map

<input type="text" id="first">
<input type="text" id="second">
<input type="text" id="third">

<script>
    // 使用Map构造函数创建空map
    const elemsMap = new Map();

    const first = document.getElementById("first");
    const second = document.getElementById("second");
    const third = document.getElementById("third");

    // 使用set方法创建映射,参数分别是:key, value
    elemsMap.set(first, "First Element");
    elemsMap.set(second, "Second Element");

    // size属性为映射数量
    console.log(elemsMap.size);
    // 2

    // get方法通过key获取value
    console.log(elemsMap.get(first));
    // First Element
    console.log(elemsMap.get(second));
    // Second Element

    // has方法判断指定key是否存在
    console.log(elemsMap.has(third));
    // false

    elemsMap.set(third, "Third Element");

    // delete方法通过key删除映射
    // 有这个key并删除成功返回true,没有这个key返回false
    let a = elemsMap.delete(first);
    console.log(a);
    // true

    console.log(elemsMap.size);
    // 2

    // clear方法清空map
    elemsMap.clear();

    console.log(elemsMap.size);
    // 0
</script>

JS中两个对象的内容相同,但两个对象仍然不相等,所以可以作为map的key

const map = new Map();

let currentLocation = location.href;

// 创建两个相同内容的对象
const firstURL = new URL(currentLocation);
const secondURL = new URL(currentLocation);

map.set(firstURL, { "description": "First" });
map.set(secondURL, { "description": "Second" });

// 两个相同内容的对象都可以称为map的key
console.log(map.get(firstURL).description);
// First
console.log(map.get(secondURL).description);
// Second

2.3 遍历map(for-of)

const students = new Map();

students.set("Wango", 578);
students.set("Lily", 649);
students.set("Jack", 312);

// 遍历所有的students,没有元素有两个值:key和value
for(let student of students) {
    console.log(student[0] + ": " + student[1]);
}
// Wango: 578
// Lily: 649
// Jack: 312

// 遍历所有的key
for(let name of students.keys()) {
    console.log(name);
}
// Wango
// Lily
// Jack

// 遍历所有的value
for(let id of students.values()) {
    console.log(id);
}
// 578
// 649
// 312

3. Set

3.1 使用对象模拟集合

// 用对象模拟集合
// 同样的问题,对象的key只能是字符串,这大大限制了模拟集合的能力

// 可以提供参数预填充集合
function Set(arr) {
    this.data = {};
    this.size = 0;
    if (Array.isArray(arr)) {
        // 数组去重
        for (var i = 0; i < arr.length; i++) {
            if (!this.has([arr[i]])) {
                this.data[arr[i]] = arr[i];
                this.size++;
            }
        }
    }
}

Set.prototype.has = function (item) {
    return typeof this.data[item] !== "undefined";
}

Set.prototype.add = function (item) {
    if (!this.has(item)) {
        this.data[item] = item;
        this.size++;
    }
    return this.values();
}

Set.prototype.delete = function (item) {
    if (this.has(item)) {
        delete this.data[item];               
        this.size--;
        return true;
    }
    return false;
}

Set.prototype.clear = function() {
    var keys = this.keys();
    this.size = 0;
    for(var i = 0; i < keys.length; i++) {
        delete this.data[keys[i]];
    }
}

Set.prototype.keys = Set.prototype.values = function() {
    return Object.getOwnPropertyNames(this.data);
}

Set.prototype.forEach = function(callback) {
    var props = this.values();
    for(var i = 0; i < props.length; i++) {
        callback(props[i], props[i]);
    }
}

3.2 创建Set

const students = new Set(["Wango", "Lily", "Jack"]);

console.log(students.has("Wango"));
// true
console.log(students.add("Jack"));
// Set(3) {"Wango", "Lily", "Jack"}
console.log(students.add("Tom"));
// Set(4) {"Wango", "Lily", "Jack", "Tom"}
console.log(students.delete("Wango"));
// true
console.log(students);
// Set(3) {"Lily", "Jack", "Tom"}

// 为了与map API保持一致,每个key和value的值相同
students.forEach((key, val) => {
    console.log(key + ": " + val);
});
// Lily: Lily
// Jack: Jack
// Tom: Tom

for(let student of students) {
    console.log(student);
}
// Lily
// Jack
// Tom

// 清空集合
students.clear();

3.2 并集、交集、差集

const firstGroup = new Set(["Wango", "Lily", "Tom"]);
const secondGroup = new Set(["Wango", "Lily", "Jack", "Mike"]);

// 并集,使用延展运算符打散两个集合,再合并成一个新的数组作为参数
// Set构造函数自动去重
const allStudents = new Set([...firstGroup, ...secondGroup]);

console.log(allStudents);
// {"Wango", "Lily", "Tom", "Jack", "Mike"}

// 交集,打散成数组后使用filter方法过滤掉secondGroup没有的元素
const sameStudents = new Set([...firstGroup].filter(stu => secondGroup.has(stu)));

console.log(sameStudents);
// {"Wango", "Lily"}

// 差集,只包含于集合A,不包含于集合B的元素,从A中过滤掉B中有的元素
const onlyInFirstGroup = new Set([...firstGroup].filter(stu => !secondGroup.has(stu)));

console.log(onlyInFirstGroup);
// {"Tom"}
原文地址:https://www.cnblogs.com/hycstar/p/14039315.html