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

JavaScript数组元素添加删除的技巧

2024-05-115.8k 阅读

数组元素添加的技巧

使用 push 方法在数组末尾添加元素

在JavaScript中,push方法是最常用的在数组末尾添加元素的方式。push方法可以接受一个或多个参数,这些参数会依次被添加到数组的末尾。

let fruits = ['apple', 'banana'];
fruits.push('cherry');
console.log(fruits); 

上述代码中,我们先定义了一个包含'apple''banana'的数组fruits,然后使用push方法添加了'cherry',最终数组变为['apple', 'banana', 'cherry']

从本质上来说,push方法会修改原数组,并且返回修改后数组的新长度。它的实现原理涉及到JavaScript数组内部的存储机制。JavaScript数组本质上是一种特殊的对象,数组的索引实际上是对象的属性名,只不过这些属性名是按顺序排列的数字字符串。当使用push方法添加元素时,它会在数组的末尾创建一个新的属性,属性名是当前数组长度的字符串形式,属性值就是要添加的元素。

let numbers = [];
let length = numbers.push(1, 2, 3);
console.log(length); 
console.log(numbers); 

这里我们向空数组numbers中添加了123三个元素,push方法返回值length3,即添加元素后的数组长度,数组numbers变为[1, 2, 3]

使用 unshift 方法在数组开头添加元素

push方法相对应,unshift方法用于在数组的开头添加一个或多个元素。

let animals = ['dog', 'cat'];
animals.unshift('mouse');
console.log(animals); 

在这段代码中,unshift方法将'mouse'添加到了数组animals的开头,数组变为['mouse', 'dog', 'cat']

unshift方法同样会修改原数组,并返回修改后数组的新长度。在JavaScript数组的内部存储机制中,当使用unshift方法添加元素时,数组中已有的元素索引会依次增加1。例如,原数组['a', 'b'],索引分别为01,当使用unshift添加'c'后,'a'的索引变为1'b'的索引变为2,新添加的'c'索引为0

let letters = [];
let newLength = letters.unshift('a', 'b');
console.log(newLength); 
console.log(letters); 

这里向空数组letters开头添加了'a''b'unshift方法返回新长度2,数组letters变为['a', 'b']

使用 splice 方法在指定位置添加元素

splice方法是一个功能强大的数组操作方法,不仅可以用于删除元素,也可以用于在指定位置添加元素。

splice方法的语法为array.splice(start, deleteCount, item1, item2, ...),其中start表示从数组的哪个位置开始操作,deleteCount表示要删除的元素个数(这里为0表示不删除元素),item1item2等表示要添加的元素。

let colors = ['red', 'green', 'blue'];
colors.splice(1, 0, 'yellow');
console.log(colors); 

在上述代码中,我们从数组colors的索引1位置开始,不删除任何元素(deleteCount为0),添加'yellow'元素,最终数组变为['red', 'yellow', 'green', 'blue']

splice方法会修改原数组,并且返回一个包含被删除元素的数组(因为这里没有删除元素,所以返回空数组)。从本质上讲,splice方法在数组内部存储中,会根据startdeleteCount调整数组元素的索引,并将新元素插入到指定位置。当插入新元素时,后续元素的索引会相应地向后移动。

let nums = [1, 3];
let removed = nums.splice(1, 0, 2);
console.log(removed); 
console.log(nums); 

这里在数组nums的索引1位置插入2splice返回空数组,数组nums变为[1, 2, 3]

使用扩展运算符(...)合并数组来添加元素

扩展运算符(...)在JavaScript中提供了一种简洁的方式来合并数组,从而实现添加元素的效果。

let arr1 = [1, 2];
let arr2 = [3, 4];
let newArr = [...arr1, ...arr2];
console.log(newArr); 

在这段代码中,我们使用扩展运算符将arr1arr2合并成了一个新数组newArr,结果为[1, 2, 3, 4]

如果我们想在一个数组末尾添加另一个数组的元素,可以这样做:

let mainArr = ['a', 'b'];
let addArr = ['c', 'd'];
mainArr = [...mainArr, ...addArr];
console.log(mainArr); 

这里将addArr的元素添加到了mainArr的末尾,mainArr变为['a', 'b', 'c', 'd']

扩展运算符的原理是将可迭代对象(如数组)展开成一个个独立的元素。在合并数组时,它会依次将每个数组的元素展开并放入新的数组中。与其他添加元素的方法不同,使用扩展运算符不会修改原数组,而是返回一个新的数组。这在一些需要保持原数组不变的场景中非常有用。

let baseArr = [5, 6];
let newCombinedArr = [...baseArr, 7, 8];
console.log(newCombinedArr); 
console.log(baseArr); 

这里通过扩展运算符在baseArr的基础上创建了一个新数组newCombinedArr,包含了baseArr的元素以及新添加的78,而baseArr本身没有被修改。

数组元素删除的技巧

使用 pop 方法删除数组末尾的元素

pop方法是删除数组末尾元素的常用方式。

let cars = ['Toyota', 'Honda', 'Ford'];
let removedCar = cars.pop();
console.log(removedCar); 
console.log(cars); 

在上述代码中,pop方法删除了数组cars末尾的元素'Ford',并返回被删除的元素'Ford',数组cars变为['Toyota', 'Honda']

从JavaScript数组的内部存储角度来看,pop方法会将数组的长度减1,并且删除最后一个元素对应的属性。由于数组的索引是基于长度的,当长度减1后,最后一个元素的索引就超出了数组的有效范围,从而实现了删除效果。

let numbers = [1, 2, 3];
let lastNumber = numbers.pop();
console.log(lastNumber); 
console.log(numbers); 

这里pop方法删除了数组numbers末尾的3,返回3,数组numbers变为[1, 2]

使用 shift 方法删除数组开头的元素

shift方法用于删除数组开头的元素。

let fruits = ['apple', 'banana', 'cherry'];
let removedFruit = fruits.shift();
console.log(removedFruit); 
console.log(fruits); 

在这段代码中,shift方法删除了数组fruits开头的元素'apple',并返回被删除的元素'apple',数组fruits变为['banana', 'cherry']

pop方法类似,shift方法会修改原数组的长度并删除开头元素对应的属性。不同的是,shift方法会将数组中剩余元素的索引依次减1。例如,原数组['a', 'b', 'c'],索引分别为012,使用shift方法删除'a'后,'b'的索引变为0'c'的索引变为1

let letters = ['x', 'y', 'z'];
let firstLetter = letters.shift();
console.log(firstLetter); 
console.log(letters); 

这里shift方法删除了数组letters开头的'x',返回'x',数组letters变为['y', 'z']

使用 splice 方法删除指定位置的元素

我们前面提到splice方法可以用于添加元素,它同样也可以用于删除指定位置的元素。

let colors = ['red', 'green', 'blue', 'yellow'];
let removedColors = colors.splice(1, 2);
console.log(removedColors); 
console.log(colors); 

在上述代码中,splice方法从数组colors的索引1位置开始,删除了2个元素,即'green''blue'splice方法返回被删除元素组成的数组['green', 'blue'],而原数组colors变为['red', 'yellow']

splice方法删除元素的本质是通过调整数组元素的索引来实现的。当删除元素时,后续元素的索引会依次向前移动,填补被删除元素留下的空缺,同时数组的长度也会相应减少。

let nums = [10, 20, 30, 40];
let removedNums = nums.splice(2, 1);
console.log(removedNums); 
console.log(nums); 

这里从数组nums的索引2位置删除了1个元素30splice返回[30],数组nums变为[10, 20, 40]

使用 filter 方法过滤数组实现删除元素

filter方法并不是直接删除数组元素,而是通过创建一个新数组,包含原数组中满足特定条件的元素,从而间接实现“删除”不满足条件的元素。

let numbers = [1, 2, 3, 4, 5];
let filteredNumbers = numbers.filter((num) => num!== 3);
console.log(filteredNumbers); 
console.log(numbers); 

在这段代码中,filter方法遍历数组numbers,对于每个元素num,如果num不等于3,则将其添加到新数组filteredNumbers中。最终filteredNumbers[1, 2, 4, 5],而原数组numbers保持不变。

filter方法接受一个回调函数作为参数,这个回调函数会对数组中的每个元素进行调用,根据回调函数的返回值决定该元素是否被包含在新数组中。它不会修改原数组,而是返回一个新的数组,这在需要保留原数组数据的场景中非常有用。

let words = ['apple', 'banana', 'cherry', 'date'];
let newWords = words.filter((word) => word.length > 5);
console.log(newWords); 
console.log(words); 

这里filter方法过滤出长度大于5的单词,新数组newWords可能为['banana', 'cherry'](具体取决于单词长度),原数组words不变。

使用 delete 关键字删除数组元素

在JavaScript中,可以使用delete关键字删除数组中的元素。

let arr = [10, 20, 30];
delete arr[1];
console.log(arr); 

在上述代码中,使用delete关键字删除了数组arr索引为1的元素20。从输出结果来看,数组变为[10, undefined, 30]。这里需要注意的是,使用delete关键字删除元素后,数组的长度并不会改变,被删除元素的位置会留下一个空洞,值为undefined

与其他删除方法不同,delete关键字操作的是数组对象的属性。由于JavaScript数组本质上是对象,使用delete删除数组元素实际上是删除了对应索引的属性。这可能会导致一些意想不到的问题,例如在遍历数组时,空洞位置的undefined也会被遍历到,而使用popshiftsplice等方法删除元素后,数组结构会得到正确的调整。

let testArr = [5, 10, 15];
delete testArr[0];
for (let i = 0; i < testArr.length; i++) {
    console.log(testArr[i]); 
}

这里删除testArr索引0的元素后,遍历数组会输出undefined1015,而如果使用shift方法删除开头元素后遍历,就不会出现undefined。所以,在实际应用中,除非特殊需求,不建议使用delete关键字删除数组元素。

综合应用与性能考量

不同添加删除方法在实际场景中的应用

在实际开发中,选择合适的数组元素添加和删除方法取决于具体的场景。

如果需要在数组末尾添加元素,push方法是最直接和高效的选择。例如,在实现一个简单的日志记录功能,每次有新的日志信息时,将其添加到日志数组的末尾:

let log = [];
function logMessage(message) {
    log.push(message);
}
logMessage('User logged in');
logMessage('New item added to cart');
console.log(log); 

当需要在数组开头添加元素时,unshift方法是合适的。比如,在实现一个先进先出(FIFO)的数据结构时,新元素需要添加到数组开头:

let queue = [];
function enqueue(item) {
    queue.unshift(item);
}
function dequeue() {
    return queue.pop();
}
enqueue('Task 1');
enqueue('Task 2');
console.log(dequeue()); 
console.log(queue); 

splice方法适用于在数组中间插入或删除元素的场景。例如,在一个音乐播放列表中,需要在特定位置插入或删除一首歌曲:

let playlist = ['Song 1', 'Song 3'];
playlist.splice(1, 0, 'Song 2');
console.log(playlist); 
playlist.splice(1, 1);
console.log(playlist); 

扩展运算符合并数组添加元素适合在需要保持原数组不变且创建新数组的场景,比如在函数式编程中:

function addElements(arr, newElements) {
    return [...arr, ...newElements];
}
let originalArr = [1, 2];
let newArr = addElements(originalArr, [3, 4]);
console.log(originalArr); 
console.log(newArr); 

对于删除元素,如果要删除数组末尾元素,pop方法是首选;删除开头元素则用shift方法。例如,在实现一个栈数据结构(后进先出,LIFO)时,pop方法用于移除栈顶元素:

let stack = [1, 2, 3];
let topElement = stack.pop();
console.log(topElement); 
console.log(stack); 

filter方法适用于根据条件过滤数组元素,比如从用户列表中删除不符合年龄要求的用户:

let users = [
    {name: 'Alice', age: 25},
    {name: 'Bob', age: 18},
    {name: 'Charlie', age: 30}
];
let adultUsers = users.filter((user) => user.age >= 18);
console.log(adultUsers); 

性能考量

在性能方面,不同的数组添加和删除方法有不同的表现。

pushpop方法的性能相对较好,因为它们只操作数组的末尾,不需要调整其他元素的索引。在大多数现代JavaScript引擎中,这两个方法的时间复杂度接近O(1),即操作时间不随数组长度的增加而显著增加。

unshiftshift方法的性能相对较差,尤其是在数组长度较大时。因为它们需要调整数组中所有元素的索引,时间复杂度为O(n),其中n是数组的长度。每添加或删除一个元素,后续所有元素的索引都要更新。

splice方法在数组中间插入或删除元素时,性能也与数组长度有关。如果在数组开头或末尾操作,性能与unshiftshiftpushpop类似;但如果在数组中间操作,由于需要移动大量元素,时间复杂度也是O(n)。

扩展运算符合并数组创建新数组时,虽然简洁,但会占用额外的内存空间,尤其是在处理大数组时。不过,在一些场景下,其简洁性和不修改原数组的特性可能比性能更重要。

filter方法的性能取决于回调函数的复杂度和数组的长度。每次调用回调函数都需要一定的时间开销,所以如果数组非常大且回调函数复杂,性能可能会受到影响。

delete关键字虽然可以删除数组元素,但由于它不会调整数组长度和索引,在遍历数组等操作时可能会带来额外的性能开销,并且会使数组结构变得不规整,所以一般不建议用于性能敏感的场景。

在实际应用中,需要根据具体的性能需求和代码逻辑来选择合适的数组元素添加和删除方法。如果性能至关重要,对于频繁的添加删除操作,尽量选择pushpop等性能较好的方法;如果更注重代码的简洁性和可读性,并且性能不是瓶颈,扩展运算符、filter等方法也是不错的选择。

兼容性与最佳实践

在兼容性方面,现代JavaScript环境(如最新版本的浏览器和Node.js)对上述所有方法都有良好的支持。然而,在一些较旧的浏览器中,可能会存在兼容性问题。例如,filter方法在IE8及以下版本不被支持。在这种情况下,可以使用Polyfill来实现相同的功能:

if (!Array.prototype.filter) {
    Array.prototype.filter = function (callback) {
        let newArray = [];
        for (let i = 0; i < this.length; i++) {
            if (callback(this[i], i, this)) {
                newArray.push(this[i]);
            }
        }
        return newArray;
    };
}

最佳实践方面,首先要根据实际需求选择合适的方法,并且尽量保持代码的简洁和可读性。在性能敏感的代码段,要避免使用性能较差的方法,如在大数组中间频繁使用splice方法。另外,尽量避免使用delete关键字删除数组元素,除非有特殊需求。同时,在处理大数组时,要注意内存的使用,避免因创建过多新数组而导致内存泄漏。

在链式调用方面,一些数组方法可以进行链式调用,例如:

let numbers = [1, 2, 3, 4, 5];
let result = numbers.filter((num) => num % 2 === 0).map((num) => num * 2);
console.log(result); 

这里先使用filter方法过滤出偶数,再使用map方法将这些偶数乘以2。合理使用链式调用可以使代码更加简洁和直观,但也要注意不要过度使用,以免降低代码的可读性。

总之,熟练掌握JavaScript数组元素添加和删除的技巧,并根据实际场景和性能需求进行选择和优化,对于编写高效、健壮的JavaScript代码至关重要。