MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

JavaScript数组元素添加删除的代码优化

2023-11-032.2k 阅读

JavaScript数组元素添加删除的基础方法

添加元素方法

  1. push()方法push() 方法可向数组的末尾添加一个或多个元素,并返回新的长度。例如:
let fruits = ['apple', 'banana'];
let newLength = fruits.push('cherry');
console.log(fruits); 
// 输出: ['apple', 'banana', 'cherry']
console.log(newLength); 
// 输出: 3

在这个例子中,我们将 'cherry' 添加到了 fruits 数组的末尾,push() 方法返回的是添加元素后数组的新长度。

  1. unshift()方法unshift() 方法可向数组的开头添加一个或多个元素,并返回新的长度。比如:
let numbers = [2, 3];
let newLength = numbers.unshift(1);
console.log(numbers); 
// 输出: [1, 2, 3]
console.log(newLength); 
// 输出: 3

这里我们在 numbers 数组的开头添加了 1unshift() 方法返回了新的数组长度。

删除元素方法

  1. pop()方法pop() 方法用于删除并返回数组的最后一个元素。示例如下:
let animals = ['dog', 'cat', 'bird'];
let removedAnimal = animals.pop();
console.log(animals); 
// 输出: ['dog', 'cat']
console.log(removedAnimal); 
// 输出: 'bird'

在这个代码中,pop() 方法删除了 animals 数组的最后一个元素 'bird' 并将其返回。

  1. shift()方法shift() 方法用于删除并返回数组的第一个元素。例如:
let colors = ['red', 'green', 'blue'];
let removedColor = colors.shift();
console.log(colors); 
// 输出: ['green', 'blue']
console.log(removedColor); 
// 输出:'red'

这里 shift() 方法删除并返回了 colors 数组的第一个元素 'red'

基于索引的添加和删除

基于索引添加元素

  1. 直接赋值:可以通过直接指定索引位置来添加元素。例如:
let arr = [1, 2];
arr[2] = 3;
console.log(arr); 
// 输出: [1, 2, 3]

如果索引值大于当前数组的长度,会在中间形成空位。比如:

let arr2 = [1, 2];
arr2[4] = 5;
console.log(arr2); 
// 输出: [1, 2, empty, empty, 5]
  1. splice()方法添加元素splice() 方法可以在指定位置添加元素。语法为 array.splice(start, deleteCount, item1, item2, ...)。其中 start 是开始改变数组的位置,deleteCount 是要删除的元素个数,后面的参数是要添加的元素。例如,在索引 1 的位置添加 'new'
let words = ['old', 'end'];
words.splice(1, 0, 'new');
console.log(words); 
// 输出: ['old', 'new', 'end']

基于索引删除元素

  1. 使用delete关键字delete 关键字可以删除数组指定索引位置的元素,但会留下空位。例如:
let nums = [1, 2, 3];
delete nums[1];
console.log(nums); 
// 输出: [1, empty, 3]
  1. splice()方法删除元素:通过设置 deleteCount 来删除元素。例如,删除从索引 1 开始的 1 个元素:
let values = [10, 20, 30];
values.splice(1, 1);
console.log(values); 
// 输出: [10, 30]

代码优化思路

性能优化

  1. 避免不必要的循环:在进行数组元素添加或删除操作时,如果可以直接使用数组原生方法,就避免使用循环来模拟操作。例如,使用 push() 方法比自己写一个循环来添加元素要高效得多。
// 不推荐
let arrToAdd = [];
for (let i = 0; i < 10; i++) {
    arrToAdd[arrToAdd.length] = i;
}
// 推荐
let arrToAdd2 = [];
for (let i = 0; i < 10; i++) {
    arrToAdd2.push(i);
}
  1. 批量操作优于多次单个操作:如果需要添加或删除多个元素,尽量使用能一次完成操作的方法。比如,使用 splice() 方法一次添加多个元素比多次调用 push()unshift() 要高效。
// 多次单个操作
let list1 = [];
list1.push('a');
list1.push('b');
list1.push('c');
// 批量操作
let list2 = [];
list2.splice(0, 0, 'a', 'b', 'c');

代码可读性优化

  1. 合理命名变量:在处理数组添加和删除操作时,给数组和操作相关的变量取有意义的名字。例如,将存储水果的数组命名为 fruits 而不是简单的 arr
// 不好的命名
let arr = ['apple', 'banana'];
arr.push('cherry');
// 好的命名
let fruits = ['apple', 'banana'];
fruits.push('cherry');
  1. 提取复杂逻辑到函数:如果数组操作涉及复杂的逻辑,比如在特定条件下添加或删除元素,可以将这些逻辑提取到一个函数中。
// 复杂逻辑直接写在主代码中
let numbersToFilter = [1, 2, 3, 4, 5];
let newNumbers = [];
for (let num of numbersToFilter) {
    if (num % 2 === 0) {
        newNumbers.push(num);
    }
}
// 提取到函数中
function filterEvenNumbers(arr) {
    let result = [];
    for (let num of arr) {
        if (num % 2 === 0) {
            result.push(num);
        }
    }
    return result;
}
let numbersToFilter2 = [1, 2, 3, 4, 5];
let newNumbers2 = filterEvenNumbers(numbersToFilter2);

内存优化

  1. 及时释放不再使用的数组:当一个数组不再被使用时,将其设置为 null,以便垃圾回收机制回收内存。
let largeArray = new Array(1000000).fill(1);
// 使用完 largeArray 后
largeArray = null;
  1. 避免创建过多临时数组:在进行数组操作时,尽量避免创建大量不必要的临时数组。例如,在进行数组拼接时,如果可以直接在原数组上操作,就不要创建新的数组。
// 创建临时数组
let arr1 = [1, 2];
let arr2 = [3, 4];
let newArr = arr1.concat(arr2);
// 直接在原数组操作
let arr3 = [1, 2];
let arr4 = [3, 4];
arr3.push(...arr4);

特定场景下的优化

频繁添加元素到数组开头

  1. 使用链表结构模拟:在 JavaScript 中虽然没有原生链表,但可以通过对象模拟链表结构。当频繁需要在开头添加元素时,链表结构的插入操作时间复杂度为 O(1),而数组的 unshift() 方法时间复杂度为 O(n)。以下是一个简单的链表模拟实现:
function Node(value) {
    this.value = value;
    this.next = null;
}
function LinkedList() {
    this.head = null;
    this.addToHead = function (value) {
        let newNode = new Node(value);
        newNode.next = this.head;
        this.head = newNode;
    };
    this.printList = function () {
        let current = this.head;
        while (current) {
            console.log(current.value);
            current = current.next;
        }
    };
}
let list = new LinkedList();
list.addToHead(3);
list.addToHead(2);
list.addToHead(1);
list.printList(); 
// 输出: 1 2 3
  1. 双端队列(Deque)数据结构:双端队列可以在两端进行添加和删除操作。虽然 JavaScript 没有原生双端队列,但可以通过 Array 和一些辅助方法模拟。例如:
class Deque {
    constructor() {
        this.items = [];
    }
    addFront(element) {
        this.items.unshift(element);
    }
    addBack(element) {
        this.items.push(element);
    }
    removeFront() {
        return this.items.shift();
    }
    removeBack() {
        return this.items.pop();
    }
    print() {
        console.log(this.items);
    }
}
let deque = new Deque();
deque.addFront(1);
deque.addBack(2);
deque.print(); 
// 输出: [1, 2]

从大型数组中删除特定元素

  1. 使用哈希表优化查找:如果要从大型数组中删除特定元素,先将数组元素存储到哈希表(JavaScript 中的对象可以模拟哈希表)中,这样查找元素的时间复杂度从 O(n) 降低到 O(1)。然后遍历数组,对于需要删除的元素,使用 splice() 方法删除。
let largeArrayToDelete = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let hashTable = {};
for (let num of largeArrayToDelete) {
    hashTable[num] = true;
}
let elementsToDelete = [3, 6, 9];
for (let del of elementsToDelete) {
    if (hashTable[del]) {
        let index = largeArrayToDelete.indexOf(del);
        if (index!== -1) {
            largeArrayToDelete.splice(index, 1);
        }
    }
}
console.log(largeArrayToDelete); 
// 输出: [1, 2, 4, 5, 7, 8, 10]
  1. 使用filter()方法filter() 方法可以创建一个新数组,其包含通过所提供函数实现的测试的所有元素。这在不改变原数组的情况下获取过滤后的数组很有用。如果要删除原数组中的元素,可以将 filter() 的结果重新赋值给原数组。
let arrayToFilter = [1, 2, 3, 4, 5];
let newArray = arrayToFilter.filter(num => num!== 3);
console.log(newArray); 
// 输出: [1, 2, 4, 5]
// 如果要修改原数组
arrayToFilter = arrayToFilter.filter(num => num!== 3);
console.log(arrayToFilter); 
// 输出: [1, 2, 4, 5]

优化实践案例

案例一:电商购物车功能优化

假设我们正在开发一个电商购物车功能,购物车使用数组存储商品信息。在添加商品时,可能会遇到重复添加的情况,在删除商品时,需要找到对应的商品并从数组中移除。

  1. 原始实现
let cart = [];
function addProduct(product) {
    cart.push(product);
}
function removeProduct(productId) {
    for (let i = 0; i < cart.length; i++) {
        if (cart[i].id === productId) {
            cart.splice(i, 1);
            break;
        }
    }
}
  1. 优化实现
let cartOptimized = [];
function addProductOptimized(product) {
    let existingProduct = cartOptimized.find(p => p.id === product.id);
    if (existingProduct) {
        // 这里可以处理数量增加等逻辑
        existingProduct.quantity++;
    } else {
        product.quantity = 1;
        cartOptimized.push(product);
    }
}
function removeProductOptimized(productId) {
    cartOptimized = cartOptimized.filter(product => product.id!== productId);
}

在这个优化实现中,添加商品时使用 find() 方法避免了重复添加,删除商品时使用 filter() 方法简化了逻辑并且提高了代码可读性。

案例二:游戏开发中角色管理优化

在游戏开发中,有一个数组存储所有游戏角色。可能会频繁地添加新角色和删除死亡角色。

  1. 原始实现
let gameCharacters = [];
function addCharacter(character) {
    gameCharacters.push(character);
}
function removeDeadCharacters() {
    for (let i = 0; i < gameCharacters.length; i++) {
        if (gameCharacters[i].isDead) {
            gameCharacters.splice(i, 1);
            i--; 
        }
    }
}
  1. 优化实现
let gameCharactersOptimized = [];
function addCharacterOptimized(character) {
    gameCharactersOptimized.push(character);
}
function removeDeadCharactersOptimized() {
    gameCharactersOptimized = gameCharactersOptimized.filter(character =>!character.isDead);
}

优化后的代码使用 filter() 方法删除死亡角色,避免了在循环中删除元素导致的索引问题,使代码更简洁和高效。

兼容性考虑

旧浏览器兼容性

  1. 数组方法兼容性:一些较新的数组方法如 find()filter() 在旧浏览器(如 Internet Explorer)中不支持。可以通过 polyfill 来解决。例如,find() 方法的 polyfill 如下:
if (!Array.prototype.find) {
    Array.prototype.find = function (callback) {
        for (let i = 0; i < this.length; i++) {
            if (callback(this[i], i, this)) {
                return this[i];
            }
        }
        return undefined;
    };
}
  1. 箭头函数兼容性:如果在数组操作代码中使用了箭头函数,在旧浏览器中也可能不支持。可以将箭头函数转换为普通函数。例如:
// 箭头函数
let numbers = [1, 2, 3];
let newNumbers = numbers.filter(num => num % 2 === 0);
// 普通函数
let numbers2 = [1, 2, 3];
let newNumbers2 = numbers2.filter(function (num) {
    return num % 2 === 0;
});

不同JavaScript引擎兼容性

  1. 性能差异:不同的 JavaScript 引擎(如 V8、SpiderMonkey 等)在执行数组操作时可能存在性能差异。一些引擎可能对某些数组方法进行了特别优化。例如,V8 引擎在处理大型数组的 push()pop() 操作时可能有更好的性能。在进行性能敏感的开发时,需要在不同引擎下进行测试。
  2. 实现细节差异:虽然 JavaScript 标准规定了数组方法的行为,但不同引擎在实现细节上可能略有不同。比如,在处理数组空位时,不同引擎对 map()forEach() 等方法的处理可能存在细微差别。在编写代码时,要尽量遵循标准行为,避免依赖特定引擎的实现细节。

通过对以上各个方面的优化,可以使 JavaScript 数组元素添加删除操作在性能、可读性和兼容性上都得到提升,从而编写出更高效、健壮的代码。在实际开发中,需要根据具体的应用场景和需求,灵活选择合适的优化策略。