乐闻世界logo
搜索文章和话题

ES6面试题手册

说一下 splice 和 slice 的功能用法

splice() 和 slice() 都是 JavaScript 中用来处理数组的方法,但它们的功能和用法有所不同。splice()splice() 方法通过删除或替换现有元素或在数组中添加新元素来改变数组的内容。其基本语法如下:array.splice(start[, deleteCount[, item1[, item2[, ...]]]])start: 指定修改的开始位置(数组索引)。deleteCount: (可选)整数,表示要从数组中删除的元素数量。item1, item2, …: (可选)要添加进数组的新元素。示例:let myArray = ['a', 'b', 'c', 'd'];myArray.splice(1, 2, 'x', 'y'); // 从索引1开始删除2个元素,并添加'x'和'y'console.log(myArray); // 输出: ['a', 'x', 'y', 'd']slice()slice() 方法则返回一个新的数组,包含从开始到结束(不包括结束)选择的数组的一部分。原始数组不会被修改。其基本语法如下:array.slice(begin[, end])begin: 提取起始处的索引(从该索引开始提取元素)。end: (可选)提取结束处的索引(到该索引之前的元素会被提取)。示例:let myArray = ['a', 'b', 'c', 'd'];let newArray = myArray.slice(1, 3); // 提取从索引1到索引2的元素console.log(newArray); // 输出: ['b', 'c']console.log(myArray); // 原数组不变,输出: ['a', 'b', 'c', 'd']总结来说,splice() 是一个可以在任何位置添加或删除元素的方法,这会改变原数组,而 slice() 用于创建一个新的数组,包含原数组的一部分,原数组不会改变。
阅读 0·2月7日 16:44

JS 数组有哪些方法? 讲讲它们的区别跟使用场景

JavaScript数组作为核心数据结构,掌握其方法能显著提升代码效率和可维护性。本文将系统分析常用数组方法,深入探讨它们的区别、适用场景及最佳实践,帮助开发者写出更简洁、高性能的代码。所有方法基于ECMAScript标准,重点聚焦于函数式方法,避免常见陷阱。对于更详细的信息,可参考MDN文档。常见数组方法分类数组方法可大致分为以下几类,每类服务于特定需求:迭代方法:用于遍历和转换数组,如 map、filter、reduce、forEach,适合声明式编程。变更方法:直接修改原数组,如 push、pop、shift、unshift、splice、sort,适用于栈操作或原地修改。生成方法:创建新数组或字符串,如 slice、concat、join,常用于数据处理。其他方法:如 fill、from、includes、indexOf,提供额外功能。 关键提示:函数式方法(如 map、filter)返回新数组,不修改原数组,而变更方法(如 push)直接操作原数组。选择时需权衡性能和可读性。迭代方法详解迭代方法是数组处理的核心,强调纯函数特性,避免副作用。map作用:创建新数组,其中每个元素是调用回调函数的结果。不修改原数组。参数:回调函数(item, index, array)使用场景:数据转换,如数字列表转字符串、计算倍数。避免在回调中修改原数组,保持函数式纯度。代码示例:const numbers = [1, 2, 3];const doubled = numbers.map(num => num * 2);console.log(doubled); // [2, 4, 6]// 原数组未被修改console.log(numbers); // [1, 2, 3]filter作用:创建新数组,包含通过测试的元素。不修改原数组。参数:回调函数(item, index, array)使用场景:数据过滤,如筛选偶数、有效对象。与map对比:map转换所有元素,filter仅保留满足条件的元素。代码示例:const numbers = [1, 2, 3, 4];const evens = numbers.filter(num => num % 2 === 0);console.log(evens); // [2, 4]// 原数组未被修改console.log(numbers); // [1, 2, 3, 4]reduce作用:将数组元素归约成单个值(如总和、最大值)。不修改原数组。参数:回调函数(accumulator, currentValue, index, array),初始值可指定(如 0)。使用场景:聚合计算、链式操作。性能提示:对大型数组,避免嵌套循环,reduce 更高效。代码示例:const numbers = [1, 2, 3, 4];const sum = numbers.reduce((acc, num) => acc + num, 0);console.log(sum); // 10const max = numbers.reduce((acc, num) => Math.max(acc, num), -Infinity);console.log(max); // 4forEach作用:对数组每个元素执行回调,但不返回新数组。不修改原数组。参数:回调函数(item, index, array)使用场景:遍历操作,如DOM修改。避免使用:因无返回值,不适合链式操作,仅用于副作用。代码示例:const items = ['a', 'b', 'c'];items.forEach(item => { console.log(`Item: ${item}`);});// 输出: Item: a// Item: b// Item: c// 原数组未被修改console.log(items); // ['a', 'b', 'c']变更方法详解变更方法直接修改原数组,适用于原地操作,但可能破坏函数式纯度。push/pop作用:push添加元素到末尾,pop移除末尾元素(栈操作)。参数:push接收多个值;pop无参数。使用场景:栈实现、队列操作。性能提示:对于频繁操作,避免在循环中使用,考虑slice等替代方案。代码示例:const stack = [];stack.push('item1', 'item2');console.log(stack); // ['item1', 'item2']const last = stack.pop();console.log(last); // 'item2'console.log(stack); // ['item1']splice作用:插入、删除或替换数组元素,返回被移除的元素。参数:start索引,deleteCount,items(可选)。使用场景:动态数组修改。注意事项:修改原数组,可能导致意外副作用。代码示例:const arr = [1, 2, 3, 4];const removed = arr.splice(1, 2, 'a', 'b');console.log(removed); // [2, 3]console.log(arr); // [1, 'a', 'b', 4]sort作用:对数组元素排序,默认按字符串规则(需显式指定比较函数)。参数:可选比较函数(a, b)。使用场景:数据排序。性能提示:对大型数组,使用Array.prototype.sort可能慢,优先使用Array.from和稳定排序。代码示例:const nums = [3, 1, 4, 2];nums.sort((a, b) => a - b);console.log(nums); // [1, 2, 3, 4]// 对字符串排序const names = ['Alice', 'Bob', 'Charlie'];console.log(names.sort()); // ['Alice', 'Bob', 'Charlie']生成方法详解生成方法返回新数组或字符串,不修改原数组,适合数据处理。slice作用:返回新数组,包含从start到end(不含)的元素。参数:start(索引,负值表示倒数),end(可选,索引)。使用场景:复制数组片段、避免原地修改。关键区别:slice vs splice——slice不修改原数组,splice会修改。代码示例:const arr = [1, 2, 3, 4];const sub = arr.slice(1, 3);console.log(sub); // [2, 3]console.log(arr); // [1, 2, 3, 4] // 原数组未被修改concat作用:连接多个数组或值,返回新数组。参数:一个或多个数组/值。使用场景:合并数组、拼接数据。性能提示:对大型数组,避免嵌套concat,使用[...arr1, ...arr2]更高效。代码示例:const arr1 = [1, 2];const arr2 = [3, 4];const merged = arr1.concat(arr2);console.log(merged); // [1, 2, 3, 4]join作用:将数组元素连接成字符串,用指定分隔符。参数:分隔符(默认',')。使用场景:生成字符串、日志输出。注意事项:对大型数组,可能产生内存问题,避免过度使用。代码示例:const fruits = ['apple', 'banana', 'cherry'];const str = fruits.join(', ');console.log(str); // 'apple, banana, cherry'方法选择指南掌握方法区别后,需根据场景选择最优方案:map vs filter:map用于转换所有元素(如[1,2,3] → [2,4,6]),filter用于过滤(如[1,2,3,4] → [2,4])。选择建议:数据转换用map,数据筛选用filter。避免副作用:forEach适合遍历副作用(如DOM操作),但不适合链式操作;map和filter返回新数组,适合纯函数式代码。性能优化:对大型数组,优先使用slice(不修改原数组)而非splice(修改原数组)。计算聚合时,reduce 比 for 循环更高效且可读。避免在循环中使用push,改用array.map().push() 或 array.concat()。安全实践:始终优先使用函数式方法(map、filter),避免for循环,提升代码可测试性。对原地操作(如splice),确保数据副本,防止意外副作用。 实践建议:在开发中,使用console.log验证数组行为,例如:结论JavaScript数组方法是前端开发的核心工具。本文系统分析了关键方法的区别与使用场景,强调函数式方法的优势(如map、filter)和变更方法的适用性。最佳实践:优先使用声明式方法,避免副作用;性能敏感场景,选择高效操作;持续学习新特性(如Array.from和Array.of)。掌握这些方法,能显著提升代码质量,使开发更高效、可维护。记住:数组方法的正确选择是性能优化和代码健壮性的关键。 延伸阅读:在现代JavaScript中,数组方法与迭代器结合,可实现更高级的流式处理。例如,使用Array.from转换可迭代对象:​
阅读 0·2月7日 13:47

Promise 和 async/await 和 Callback 有什么区别?

Promise、async/await 和 Callback 都是在 JavaScript 中处理异步操作的机制。每种技术都有其特点和适用场景。CallbackCallback 是一种较老的异步编程技术,它是将一个函数作为参数传递给另一个函数,并在那个函数执行完毕后调用。它最常见的用途是在进行文件操作或者请求网络资源时。优点:简单易懂,易于实现。缺点:容易导致 "回调地狱"(Callback Hell),即多个嵌套的回调函数使代码难以阅读和维护。错误处理不方便,需要在每个回调中处理错误。例子:fs.readFile('example.txt', 'utf8', function(err, data) { if (err) { return console.error(err); } console.log(data);});PromisePromise 是异步编程的一种解决方案,比传统的解决方案 —— 回调函数和事件 —— 更合理和更强大。它表示一个尚未完成但预期将来会完成的操作的结果。优点:提供了更好的错误处理机制,通过 .then() 和 .catch() 方法链。避免了回调地狱,代码更加清晰和易于维护。支持并行执行异步操作。缺点:代码仍然有些冗长。可能不够直观,特别是对于新手。例子:const promise = new Promise((resolve, reject) => { fs.readFile('example.txt', 'utf8', (err, data) => { if (err) { reject(err); } else { resolve(data); } });});promise.then(data => { console.log(data);}).catch(err => { console.error(err);});async/awaitasync/await 是建立在 Promise 上的语法糖,它允许我们以更同步的方式写异步代码。优点:代码更加简洁、直观。更容易理解,特别是对于习惯了同步代码的开发者。方便的错误处理,可以用传统的 try/catch 块。缺点:可能会导致性能问题,因为 await 会暂停函数的执行,直到 Promise 解决。在某些情况下不够灵活,例如并行处理多个异步任务。例子:async function readFileAsync() { try { const data = await fs.promises.readFile('example.txt', 'utf8'); console.log(data); } catch (err) { console.error(err); }}readFileAsync();总结来说,Callback 是最基本的异步处理形式,Promise 提供了更强大的控制能力和错误处理机制,而 async/await 是在 Promise 基础上提高代码可读性和减少样板代码的语法糖。
阅读 49·2024年6月24日 16:43

let 块作用域是怎么实现的?

let 关键字在JavaScript中被引入是为了提供块作用域(block scope)的功能。块作用域意味着由 let 声明的变量仅在声明它们的代码块内部是可见的。代码块是被花括号 {} 包围的一段代码,例如在 if 语句、for 和 while 循环以及函数定义中都会用到代码块。在ES6之前,JavaScript主要依赖的是函数作用域(function scope),由 var 关键字声明的变量要么是全局的,要么是在函数内部局部的。这种设计有时会导致意料之外的问题,特别是在循环中。下面是一个使用 let 的例子来说明块作用域是如何工作的:function runLoop() { for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 100 * i); }}runLoop();在这个例子中,变量 i 是用 let 在 for 循环的块中声明的。这意味着每次循环迭代时,变量 i 都是一个新的变量,并且它被限制在这个循环的块作用域中。所以当 setTimeout 的回调函数执行时,它能够访问到循环迭代时对应的 i 的值。如果我们用 var 替换掉 let,结果将会不同:function runLoop() { for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 100 * i); }}runLoop();在这个例子中,由于 var 声明的变量 i 是函数作用域的,当 setTimeout 的回调函数执行时,它会打印出循环结束后变量 i 的最终值,即5,会打印五次5,而不是0到4。总结来说,let 关键字允许开发者在更细粒度的级别控制变量的作用域。这样做提高了代码的可读性和可维护性,并且减少了由于作用域导致的常见错误。
阅读 33·2024年6月24日 16:43

some、every、find、filter、map、forEach 有什么区别?

JavaScript 中的 some、every、find、filter、map 和 forEach 都是数组的方法,它们各自有不同的用途。somesome 方法用于检查数组中是否至少有一个元素满足提供的测试函数。如果满足则返回 true,否则返回 false。这个方法对于检查数组是否包含至少一个符合条件的元素很有用。例子:const hasNegativeNumbers = [1, 2, 3, -1, 4].some(num => num < 0); // trueeveryevery 方法用来检查数组中的所有元素是否都满足提供的测试函数。如果全部满足则返回 true,否则返回 false。这个方法适用于验证数组所有元素是否符合某个条件。例子:const allPositiveNumbers = [1, 2, 3].every(num => num > 0); // truefindfind 方法用于找到数组中第一个满足提供的测试函数的元素。如果找到了这样的元素,find 就会返回这个元素,否则返回 undefined。例子:const firstNegativeNumber = [1, 2, 3, -1, 4].find(num => num < 0); // -1filterfilter 方法创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。这个方法用于根据条件筛选数组中的元素。例子:const negativeNumbers = [1, 2, 3, -1, -2, 4].filter(num => num < 0); // [-1, -2]mapmap 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后的返回值。这个方法用于转换数组中的每个元素。例子:const squares = [1, 2, 3, 4].map(num => num * num); // [1, 4, 9, 16]forEachforEach 方法对数组的每个元素执行一次提供的函数,但它不返回任何值(即 undefined)。这只是一个简单的遍历数组的办法,通常用于执行副作用(如打印日志、更新UI等)。例子:[1, 2, 3, 4].forEach(num => console.log(num)); // 输出 1 2 3 4,但没有返回值每一个这些方法都有其特定的用途,选择哪个取决于您要解决的特定问题。
阅读 33·2024年6月24日 16:43

ES5 和 ES6 有什么区别

ES5(即ECMAScript 5)和ES6(也称为ECMAScript 2015或ECMAScript 6)是JavaScript语言的两个版本,它们之间有许多重要的区别。ES6引入了一系列新特性和语法改进,使得编程更加简洁和强大。以下是一些主要的区别:1. let 和 constES6引入了 let和 const关键字,用于声明变量。let提供了块级作用域,比ES5中的 var提供了更好的控制,尤其是在循环中。const用于声明常量,其值在设置之后不能改变。例子:在一个循环中使用 let可以避免常见的闭包问题。for (let i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000);}// 输出:0, 1, 2, 3, 4 (正确的顺序)2. 箭头函数(Arrow Functions)ES6引入了箭头函数,这是一种更简洁的函数写法,同时它也没有自己的 this,arguments,super或 new.target绑定。例子:// ES5var add = function(a, b) { return a + b;};// ES6const add = (a, b) => a + b;3. 模板字符串(Template Literals)在ES6中,模板字符串提供了一种构建字符串的新方法,可以使用反引号( `)来定义,它支持多行字符串和字符串插值。例子:// ES5var name = "World";var greeting = "Hello, " + name + "!";// ES6const name = "World";const greeting = `Hello, ${name}!`;4. 类(Classes)ES6引入了类的概念,这是一种使用原型继承的语法糖。它使创建对象和继承更加直观和方便。例子:// ES6class Person { constructor(name) { this.name = name; } greet() { return `Hello, ${this.name}!`; }}const person = new Person('Jane');console.log(person.greet()); // "Hello, Jane!"5. 默认参数值ES6允许函数参数有默认值,这在ES5中通常需要在函数体内部进行处理。例子:// ES6function greet(name = "World") { return `Hello, ${name}!`;}console.log(greet()); // "Hello, World!"console.log(greet("Jane")); // "Hello, Jane!"6. 解构赋值(Destructuring Assignment)ES6引入了解构赋值,它允许在单个语句中从数组或对象中提取数据,并设置到新的变量中。例子:// ES6const [a, b] = [1, 2];const { firstName, lastName } = { firstName: "John", lastName: "Doe" };console.log(a); // 1console.log(firstName); // "John"7. 模块导入和导出ES6标准化了模块系统,使用 import和 export语句来导入和导出模块成员。例子:// ES6// file: math.jsexport const add = (a, b) => a + b;// file: main.jsimport { add } from './math';console.log(add(2, 3)); // 58. Promises 和异步编程ES6引入了Promise作为处理异步操作的一种机制,它比ES5中的回调函数更具可读性和效率。
阅读 32·2024年6月24日 16:43

ES6 中的 Map 和原生的对象有什么区别?

在 ES6 中,Map 是一种新的数据结构,它提供了一些原生对象(如普通的 JavaScript 对象)所不具备的特性。以下是 Map 和原生对象之间一些主要的区别:键的类型:Map:可以使用任何类型的值(包括对象或原始值)作为键。对象:通常只能使用字符串或者 Symbol 作为键。虽然现代JavaScript引擎会自动将非字符串的键转换为字符串,但这可能导致键的冲突和预期之外的行为。键的顺序:Map:键值对是有序的,Map 对象遍历时会根据元素的插入顺序进行。对象:在 ES2015 之前,对象的属性没有特定的顺序;但从 ES2015 开始,对象的属性遍历顺序是根据属性被添加到对象的顺序(对于字符串键)和整数键的大小来确定的,非整数键则按照创建顺序排列。大小可获取:Map:可以直接获取到 Map 的大小,使用 map.size 属性。对象:通常需要手动计算属性的数量,例如通过 Object.keys(obj).length。性能:Map:在频繁添加和删除键值对的场景下,Map 通常提供更优的性能。特别是当涉及到大量键值对时,Map 的性能通常更稳定。对象:当作为少量属性的集合时,原生对象也可能表现出良好的性能。默认键:Map:不包含默认键,只包含显式插入的键。对象:原型链上的属性和方法可以被继承,对象默认会含有诸如 toString 或 hasOwnProperty 这样的方法,这可能会在某些使用场景中造成问题。迭代:Map:Map 对象可以直接被迭代,提供了几个迭代方法,包括 map.keys()、map.values() 和 map.entries(),以及 map.forEach() 方法。对象:对象的属性需要使用 for...in 循环或 Object.keys()、Object.values()、Object.entries() 加上 forEach 方法等进行迭代。序列化:Map:Map 对象不能直接使用 JSON.stringify 进行序列化。对象:对象可以直接被序列化为 JSON 字符串。例如,如果我们需要一个键值对集合来记录用户的唯一标识符(这些标识符可能是数字、字符串、甚至是对象),并且希望保持插入顺序,那么 Map 就特别适合这种用例。使用 Map 我们可以这样实现:let userRoles = new Map();let user1 = { name: "Alice" };let user2 = { name: "Bob" };// 添加用户角色userRoles.set(user1, 'admin');userRoles.set(user2, 'editor');// 获取Map的大小console.log(userRoles.size); // 2// 按插入顺序遍历用户角色for (let [user, role] of userRoles.entries()) { console.log(`${user.name}: ${role}`);}在这个例子中,我们使用对象 user1 和 user2 作为键,这在普通的对象中是无法做到的,因为对象的键会被转换为字符串。
阅读 40·2024年6月24日 16:43

es6 类继承中 super 的作用

ES6中,super关键字在类继承中扮演着非常重要的角色。它有两个主要的作用:在子类构造函数中调用父类的构造函数:在使用ES6类继承时,子类的构造函数需要调用父类的构造函数,这是通过super()实现的。这使得子类能够继承父类的属性。如果不调用super(),则子类的实例将无法正确构建,因为父类的一些初始化代码不会被执行。例如,假设我们有一个Person类和一个继承自Person的Student类: class Person { constructor(name) { this.name = name; } } class Student extends Person { constructor(name, studentID) { super(name); // 调用父类的构造函数来初始化父类中定义的属性 this.studentID = studentID; } } let student = new Student('Alice', '12345'); console.log(student.name); // 输出: Alice在这个例子中,super(name)调用了Person类的构造函数,初始化了name属性。在子类的方法中调用父类的方法:super也可以用来在子类中调用父类的方法。这对于扩展和重写父类行为非常有用。在子类的方法中,你可以通过super.methodName()的方式调用父类的方法。例如: class Person { greet() { console.log(`Hello, my name is ${this.name}.`); } } class Student extends Person { study() { console.log(`${this.name} is studying with student ID ${this.studentID}.`); } greet() { super.greet(); // 调用父类的greet方法 this.study(); // 然后调用子类的study方法 } } let student = new Student('Alice', '12345'); student.greet(); // 输出: // Hello, my name is Alice. // Alice is studying with student ID 12345.在这个例子中,我们重写了Student类的greet方法,在其中首先通过super.greet()调用了父类Person中的greet方法,然后调用了Student自己的study方法,这样就可以保留父类的行为的同时扩展新的行为。综上所述,super在ES6类继承中是极为重要的,它允许子类构造函数和方法访问和调用父类的构造函数和方法。
阅读 30·2024年6月24日 16:43

ES6 中有哪些解决异步的方法?

在ES6(ECMAScript 2015)及之后的版本中,引入了多种解决异步编程问题的方法。这些方法提高了代码的可读性、易维护性,并使得异步逻辑的处理变得更加直观。下面是几种主要的异步处理方法:1. PromisesES6正式引入了Promise对象,这是处理异步操作的一种方法。一个Promise代表了一个异步操作的最终完成 (或失败) 及其结果值。Promise有三种状态:pending(等待中), fulfilled(已成功), 和 rejected(已失败)。示例:const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('数据已获取'); }, 2000);});myPromise.then( (value) => { console.log(value); }, // 成功处理函数 (error) => { console.error(error); } // 失败处理函数);2. Generators虽然ES6的生成器(Generator)函数本身不是异步的,但它们可以用于控制异步调用的流程。生成器函数允许函数执行过程中暂停和恢复,这意味着可以在某个操作等待异步结果时“暂停”函数执行。示例:function* generatorFunction() { const result = yield myPromise; console.log(result);}// 使用生成器控制异步流程const iterator = generatorFunction();const prom = iterator.next().value; // 获取由yield语句返回的Promiseprom.then((response) => iterator.next(response));3. Async/Awaitasync/await是在ES7(ECMAScript 2017)中引入的,但它是基于ES6的Promise进一步发展的。这是一个通过更简洁的方式使用Promise的语法糖。任何一个标记为async的函数都会返回一个Promise。await关键字可以用来等待Promise的解决,并暂停函数的执行,直到Promise被解决或拒绝。示例:async function fetchData() { try { const response = await fetch('https://api.example.com/data'); // 等待Promise解决 const data = await response.json(); // 等待Promise解决 console.log(data); } catch (error) { console.error('请求失败:', error); }}fetchData();每一种方法都有它的用例和适用场景。Promise可以很好地解决单个或多个异步操作链的情况。当我们需要在异步操作中暂停和恢复函数执行时,Generator函数可以很好地帮助我们管理复杂的流程。而async/await提供了一种更加直观和简洁的方式去处理Promise,特别是在需要等待多个异步操作完成时,代码的可读性和可维护性大大提高。
阅读 31·2024年6月24日 16:43