Skip to content

集合引用类型

Array 类型

1. 数组的创建与初始化

javascript
// 字面量创建
let fruits = ['apple', 'banana', 'orange'];

// 构造函数创建
let numbers = new Array(3);  // 创建长度为3的空数组
let colors = new Array('red', 'green', 'blue');

// Array.from() 创建
let arrayFromSet = Array.from(new Set([1, 2, 3]));
let arrayFromString = Array.from('hello');  // ['h', 'e', 'l', 'l', 'o']

// Array.of() 创建
let nums = Array.of(1);      // [1]
let mixed = Array.of(1, 'a', true);  // [1, 'a', true]

// 类数组转换
function convertArgs() {
    let args = Array.from(arguments);
    return args;
}

2. 数组检测

javascript
// 检测数组
let arr = [1, 2, 3];
console.log(Array.isArray(arr));  // true
console.log(arr instanceof Array);  // true
console.log(Object.prototype.toString.call(arr) === '[object Array]');  // true

// 类数组对象
let arrayLike = {
    0: 'a',
    1: 'b',
    length: 2
};
console.log(Array.isArray(arrayLike));  // false

3. 数组方法详解

3.1 添加和删除元素

javascript
let arr = ['a', 'b', 'c'];

// 尾部操作
arr.push('d');              // 返回新长度 4
let last = arr.pop();       // 返回 'd'

// 头部操作
arr.unshift('x');          // 返回新长度 4
let first = arr.shift();   // 返回 'x'

// 任意位置操作
arr.splice(1, 1);          // 删除位置1的元素
arr.splice(1, 0, 'y');     // 在位置1插入'y'
arr.splice(1, 1, 'z');     // 替换位置1的元素

// 实际应用:维护一个固定大小的队列
class FixedQueue {
    constructor(size) {
        this.size = size;
        this.queue = [];
    }

    enqueue(item) {
        this.queue.push(item);
        if (this.queue.length > this.size) {
            this.queue.shift();
        }
        return this.queue;
    }
}

let messageQueue = new FixedQueue(3);
console.log(messageQueue.enqueue('A')); // ['A']
console.log(messageQueue.enqueue('B')); // ['A', 'B']
console.log(messageQueue.enqueue('C')); // ['A', 'B', 'C']
console.log(messageQueue.enqueue('D')); // ['B', 'C', 'D']

3.2 数组搜索和位置方法

javascript
let numbers = [1, 2, 3, 2, 1];

// 基本搜索
console.log(numbers.indexOf(2));        // 1
console.log(numbers.lastIndexOf(2));    // 3
console.log(numbers.includes(4));       // false

// 使用断言函数搜索
let people = [
    { name: 'John', age: 30 },
    { name: 'Jane', age: 25 },
    { name: 'Jim', age: 35 }
];

let youngPerson = people.find(person => person.age < 30);
console.log(youngPerson);  // { name: 'Jane', age: 25 }

let youngPersonIndex = people.findIndex(person => person.age < 30);
console.log(youngPersonIndex);  // 1

// 实际应用:查找最近的满足条件的元素
function findNearest(arr, target, compare) {
    return arr.reduce((nearest, current) => {
        return Math.abs(compare(current) - target) < Math.abs(compare(nearest) - target)
            ? current
            : nearest;
    });
}

let temperatures = [
    { city: 'New York', temp: 20 },
    { city: 'London', temp: 18 },
    { city: 'Paris', temp: 22 },
    { city: 'Tokyo', temp: 25 }
];

let targetTemp = 21;
let nearestCity = findNearest(temperatures, targetTemp, item => item.temp);
console.log(nearestCity);  // { city: 'New York', temp: 20 }

3.3 迭代方法

javascript
let numbers = [1, 2, 3, 4, 5];

// map: 转换数组元素
let doubled = numbers.map(num => num * 2);
console.log(doubled);  // [2, 4, 6, 8, 10]

// filter: 过滤数组元素
let evens = numbers.filter(num => num % 2 === 0);
console.log(evens);  // [2, 4]

// reduce: 累积操作
let sum = numbers.reduce((acc, cur) => acc + cur, 0);
console.log(sum);  // 15

// 链式调用示例
let result = numbers
    .filter(num => num % 2 === 0)    // 过滤偶数
    .map(num => num * 2)             // 每个数乘2
    .reduce((acc, cur) => acc + cur, 0);  // 求和
console.log(result);  // 12

// 实际应用:数据转换和统计
let orders = [
    { id: 1, total: 200, status: 'completed' },
    { id: 2, total: 300, status: 'pending' },
    { id: 3, total: 150, status: 'completed' },
    { id: 4, total: 450, status: 'pending' }
];

// 按状态分组并计算总金额
let orderStats = orders.reduce((stats, order) => {
    if (!stats[order.status]) {
        stats[order.status] = 0;
    }
    stats[order.status] += order.total;
    return stats;
}, {});

console.log(orderStats);  
// { completed: 350, pending: 750 }

Map 类型

1. Map 的基本用法

javascript
// 创建和初始化
let map = new Map([
    ['name', 'John'],
    ['age', 30]
]);

// 添加和获取元素
map.set('city', 'New York');
console.log(map.get('name'));  // 'John'

// 检查和删除元素
console.log(map.has('age'));   // true
map.delete('age');
console.log(map.size);         // 2

// 遍历
map.forEach((value, key) => {
    console.log(`${key}: ${value}`);
});

// 实际应用:缓存系统
class Cache {
    constructor(maxAge = 5000) {
        this.cache = new Map();
        this.maxAge = maxAge;
    }

    set(key, value) {
        this.cache.set(key, {
            value,
            timestamp: Date.now()
        });
    }

    get(key) {
        const item = this.cache.get(key);
        if (!item) return null;

        if (Date.now() - item.timestamp > this.maxAge) {
            this.cache.delete(key);
            return null;
        }

        return item.value;
    }

    clear() {
        this.cache.clear();
    }
}

let cache = new Cache(2000);  // 2秒过期
cache.set('user', { name: 'John' });

setTimeout(() => {
    console.log(cache.get('user'));  // null(已过期)
}, 3000);

Set 类型

1. Set 的基本用法

javascript
// 创建和初始化
let set = new Set([1, 2, 3, 3, 4]);  // 重复值会被忽略

// 添加和删除元素
set.add(5);
set.delete(1);
console.log(set.size);  // 4

// 检查元素
console.log(set.has(2));  // true

// 转换为数组
let array = Array.from(set);
// 或者
let array2 = [...set];

// 实际应用:数组去重
function uniqueArray(arr) {
    return [...new Set(arr)];
}

// 实际应用:标签系统
class TagManager {
    constructor() {
        this.tags = new Set();
    }

    addTag(tag) {
        this.tags.add(tag.toLowerCase());
    }

    removeTag(tag) {
        this.tags.delete(tag.toLowerCase());
    }

    hasTag(tag) {
        return this.tags.has(tag.toLowerCase());
    }

    getAllTags() {
        return [...this.tags];
    }
}

let tagManager = new TagManager();
tagManager.addTag('JavaScript');
tagManager.addTag('TypeScript');
tagManager.addTag('javascript');  // 不会重复添加
console.log(tagManager.getAllTags());  // ['javascript', 'typescript']

WeakMap 和 WeakSet

1. WeakMap 使用场景

javascript
// 私有数据存储
const privateData = new WeakMap();

class Person {
    constructor(name) {
        privateData.set(this, { name });
    }

    getName() {
        return privateData.get(this).name;
    }
}

let person = new Person('John');
console.log(person.getName());  // 'John'
// 当 person 被垃圾回收时,privateData 中的数据也会被回收

// DOM节点数据关联
const nodeData = new WeakMap();

function setNodeData(node, data) {
    nodeData.set(node, data);
}

function getNodeData(node) {
    return nodeData.get(node);
}

// 使用示例
let div = document.createElement('div');
setNodeData(div, { clicks: 0 });
// 当 div 被删除时,相关数据会被自动清理

2. WeakSet 使用场景

javascript
// 追踪对象实例
const instances = new WeakSet();

class Widget {
    constructor() {
        instances.add(this);
    }

    isValid() {
        return instances.has(this);
    }
}

let widget = new Widget();
console.log(widget.isValid());  // true

// 防止重复调用
const called = new WeakSet();

function oneTimeFunction(obj) {
    if (called.has(obj)) {
        throw new Error('Function can only be called once per object!');
    }
    called.add(obj);
    // 执行操作...
}

最佳实践

  1. 选择合适的集合类型

    • 需要键值对?使用 Map
    • 需要唯一值集合?使用 Set
    • 需要弱引用?使用 WeakMap/WeakSet
    • 需要数值索引?使用 Array
  2. 数组操作优化

    • 使用 Array.from() 代替 slice 复制数组
    • 使用解构和展开运算符进行数组操作
    • 适当使用 TypedArray 处理二进制数据
  3. Map vs Object

    • Map 的键可以是任何类型
    • Map 保持插入顺序
    • Map 在频繁增删键值对时性能更好
  4. Set vs Array

    • 使用 Set 存储唯一值
    • Set 提供了高效的查找和删除操作
    • Set 适合用于数学集合运算

性能优化

  • 预先设定数组大小可以提高性能
  • 使用适当的数组方法避免不必要的遍历
  • 考虑使用 TypedArray 处理大量数值数据

练习题

  1. 实现一个函数,找出数组中出现次数最多的元素。
  2. 使用 Map 实现一个简单的事件发布订阅系统。
  3. 实现一个函数,对嵌套数组进行扁平化处理。
  4. 使用 Set 实现两个数组的交集、并集和差集操作。

扩展阅读

本站内容采用 "署名-非商业性使用-禁止演绎 4.0 国际许可协议" 进行许可