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

前端面试题手册

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,但没有返回值每一个这些方法都有其特定的用途,选择哪个取决于您要解决的特定问题。
阅读 29·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中的回调函数更具可读性和效率。
阅读 29·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 作为键,这在普通的对象中是无法做到的,因为对象的键会被转换为字符串。
阅读 37·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类继承中是极为重要的,它允许子类构造函数和方法访问和调用父类的构造函数和方法。
阅读 25·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,特别是在需要等待多个异步操作完成时,代码的可读性和可维护性大大提高。
阅读 27·2024年6月24日 16:43

weak-Set、weak-Map 和 Set、Map 之间的区别是什么?

WeakSet 和 WeakMap 是 JavaScript 中的集合类型,与 Set 和 Map 相似,但它们之间有一些重要的区别。以下分别是 WeakSet/WeakMap 与 Set/Map 之间的主要区别:WeakSet 与 Set 的区别:弱引用:WeakSet:只能包含对象的弱引用。这意味着如果没有其他引用指向对象,这些对象是可以被垃圾回收机制回收的。Set:可以包含任意值的强引用,包括原始值或对象引用。只要 Set 存在,其中的元素就不会被垃圾回收机制回收。元素类型限制:WeakSet:只能存储对象,不能存储原始数据类型(如字符串、数字、布尔值等)。Set:可以存储任意类型的值,无论是原始数据类型还是对象。可枚举性:WeakSet:不能被迭代,也没有提供方法来获取大小(即没有 size 属性)或者清空集合的方法。Set:可迭代,且有 size 属性可以获取集合的大小,也提供了 clear 方法来清空集合。使用场景:WeakSet:适合用于存储没有任何其他引用的对象集合,通常用于管理对象的生命周期,防止内存泄漏。Set:适合于需要存储唯一值的场景,特别是当需要迭代或者获取集合大小时。WeakMap 与 Map 的区别:弱引用键:WeakMap:只接受对象作为键,并且这些键是弱引用的。如果没有其他引用指向键对象,那么这些键值对可以被垃圾回收机制回收。Map:可以接受任意类型的值作为键,包括原始数据类型和对象,这些键是强引用的。键类型限制:WeakMap:键必须是对象,不能是原始数据类型。Map:键可以是任意类型的值,包括原始数据类型和对象。可枚举性:WeakMap:同样不能被迭代,没有 size 属性,也不能清空整个集合。Map:可迭代,有 size 属性,并提供了 clear 方法。使用场景:WeakMap:经常用于缓存或者存储对象与数据的关联,同时不影响对象的垃圾回收。Map:适用于需要明确地将键映射到值,并且需要键的枚举、大小统计或者清空映射。示例:假设我们正在开发一个应用程序,该应用程序需要跟踪一组 DOM 元素是否被点击。我们可以使用 WeakSet 来存储这些 DOM 元素,如下所示:let clickedElements = new WeakSet();document.addEventListener('click', event => { if (event.target.tagName === 'BUTTON') { clickedElements.add(event.target); // ...执行一些操作... }});// 由于 WeakSet 对象的特性,当 DOM 元素被移除并且没有其他引用时,它将自动从 WeakSet 中移除,防止内存泄漏在这个例子中,如果没有 WeakSet,而是使用 Set,那么即使 DOM 元素被移除,它们也不会从集合中删除,这可能会导致内存泄漏。
阅读 38·2024年6月24日 16:43

var、let、const 之间的区别是什么?

varvar 是 JavaScript 早期版本中使用的变量声明关键字,它有几个特点:函数作用域:var 声明的变量是按照函数作用域进行绑定的,如果在函数外部声明,它就具有全局作用域。变量提升(Hoisting):使用 var 声明的变量会被提升至其作用域的顶部,但是只提升声明不提升初始化。重复声明:用 var 声明的变量可以在同一作用域中被重新声明。console.log(foo); // 输出 undefined 而不是抛出错误,因为变量提升var foo = 5;function testVar() { var bar = "hello";}console.log(bar); // 抛出错误,因为 bar 是在函数内部声明的,外部无法访问var foo = "world"; // 这是允许的,foo 被重新声明letlet 是 ES6 (ECMAScript 2015) 引入的关键字,用于声明变量,并且它带来了几个改进:块作用域:let 声明的变量是按照块作用域(如 {} 内部)进行绑定的。没有变量提升:let 声明的变量不会提升,它们必须在声明之后才能被使用。不能重复声明:在同一作用域下不能重新声明同一个变量。console.log(foo); // 抛出错误,foo 没有被提升let foo = 5;if (true) { let bar = "hello";}console.log(bar); // 抛出错误,bar 在 if 语句的块作用域外无法访问let foo = "world"; // 抛出错误,foo 不能被重新声明constconst 同样是在 ES6 引入的,用于声明常量,具有以下特性:块作用域:与 let 相同,const 声明的变量也是块作用域。没有变量提升:同样,const 声明的变量在声明之前不能被访问。不能重复声明:不能在相同作用域下重新声明。必须初始化:使用 const 声明变量时必须立即初始化,并且之后不能修改。const foo = 5;foo = 10; // 抛出错误,因为 const 声明的变量不能被重新赋值if (true) { const bar = "hello";}console.log(bar); // 抛出错误,因为 bar 在 if 语句的块作用域外无法访问const foo; // 抛出错误,因为 const 声明的变量必须在声明时初始化总结来说,let 和 const 是对 var 的一个改进,提供了块作用域特性,并解决了变量提升和重复声明所带来的问题。在现代 JavaScript 编程中,推荐使用 let 和 const 来声明变量,以便代码更加清晰和安全。
阅读 25·2024年6月24日 16:43

koa.js 如何实现文件上传的断点续传?

Koa.js 是一个轻量级的 Node.js web 框架,用于构建快速的 web 应用和 API。要在 Koa.js 中实现文件上传的断点续传,我们需要使用额外的中间件和库来管理文件的分片上传和断点续传的逻辑。下面是实现这个功能的一般步骤:1. 选择合适的中间件和库使用 koa-body 或 koa-multer 中间件来处理文件上传。选择支持断点续传的库,如 tus-node-server 或是使用流来手动处理。2. 设置文件上传中间件const Koa = require('koa');const koaBody = require('koa-body');const app = new Koa();app.use(koaBody({ multipart: true, formidable: { // 设置临时文件夹,用于保存上传的文件 uploadDir: './uploads', keepExtensions: true, }}));3. 实现分片上传逻辑分片的处理可以通过前端上传时携带分片信息,并在后端进行相应的处理:app.use(async ctx => { if (ctx.url === '/upload' && ctx.method === 'POST') { // 获取上传文件 const file = ctx.request.files.file; const { name, path } = file; // 获取分片信息等其他元数据,比如分片索引、总分片数、文件标识符等 const { index, total, identifier } = ctx.request.body; // 根据文件标识符和分片索引生成分片的唯一存储路径 const chunkPath = `./uploads/${identifier}_${index}`; // 将上传的分片文件移动到分片存储路径 const fs = require('fs'); const readable = fs.createReadStream(path); const writable = fs.createWriteStream(chunkPath); readable.pipe(writable); // 确认分片上传成功后,可以删除原上传的临时文件 fs.unlink(path, (err) => { if (err) throw err; }); ctx.body = '分片上传成功'; }});4. 断点续传和文件重组逻辑保存文件分片信息,可以使用数据库或者文件系统。定期检查已上传的分片,提供断点续传的信息给前端。前端在上传前检查已上传的分片,只上传未完成的部分。全部分片上传完毕后,后端合并分片。// 假设有一个合并分片的方法async function mergeChunks(chunks, dest) { // 合并所有分片 // ...}app.use(async ctx => { if (ctx.url === '/merge' && ctx.method === 'POST') { const { identifier, total } = ctx.request.body; const chunks = []; for (let i = 0; i < total; i++) { chunks.push(`./uploads/${identifier}_${i}`); } // 调用合并分片的方法 await mergeChunks(chunks, `./uploads/${identifier}.final`); ctx.body = '文件上传和合并成功'; }});5. 处理异常和错误在整个上传、续传、合并的过程中,要对可能发生的异常和错误进行处理,确保系统的稳定性。6. 前端实现前端需要使用支持断点续传的库(如 tus-js-client)或自己实现相关逻辑,包括如何分片、如何处理续传以及如何在上传完成后提示用户。以上是在 Koa.js 中实现文件上传断点续传的大体步骤。实际的逻辑可能会更加复杂,包括如何确保并发上传时分片的正确性,如何处理网络异常等多种情况。
阅读 67·2024年6月24日 16:43

一个 dom 必须要操作几百次,该如何解决,如何优化?

当面临需要进行几百次 DOM 操作的情况时,这通常意味着性能可能会受到影响,因为频繁的 DOM 更新可能会导致页面的重绘和重排,从而降低用户体验。为了优化这种情况,我们可以采取以下措施:1. 使用文档片段(DocumentFragment)文档片段是一个轻量级的DOM节点,它可以作为其他DOM节点的临时容器。我们可以在内存中构建整个DOM结构,然后一次性将其附加到DOM树上。这样可以减少页面的重绘次数。例子:// 创建一个新的空白的文档片段let fragment = document.createDocumentFragment();// 循环添加DOM节点到文档片段中for (let i = 0; i < 300; i++) { let div = document.createElement('div'); div.textContent = `Item ${i}`; fragment.appendChild(div);}// 最后,一次性将文档片段添加到DOM中document.body.appendChild(fragment);2. 批量更新样式修改元素的样式时,尽量避免逐个修改,而是使用类名(class)来对元素进行样式的修改,或者使用cssText一次性修改多个样式属性。例子:// 不推荐:逐个修改element.style.color = 'blue';element.style.fontSize = '14px';element.style.margin = '10px';// 推荐:使用cssText或者类名element.className = 'new-style';// 或者element.style.cssText = 'color: blue; font-size: 14px; margin: 10px;';3. 使用requestAnimationFrame如果DOM操作涉及到动画或者与视觉有关的更新,可以使用requestAnimationFrame。这个API会将DOM操作排队,以便在浏览器的下一个重绘之前执行,从而帮助避免不必要的重绘。例子:function update() { // 执行DOM操作}// 在下次重绘之前调用update函数requestAnimationFrame(update);4. 虚拟DOM库虚拟DOM技术(如React的虚拟DOM)可以帮助减少不必要的DOM操作,它通过在JavaScript内存中与实际DOM同步的数据结构来比较DOM的变化,并只更新发生变化的部分。例子:class MyComponent extends React.Component { render() { return ( <div> {this.props.items.map(item => <div key={item.id}>{item.text}</div>)} </div> ); }}5. 优化选择器和避免布局抖动尽量使用高效的选择器,避免使用复杂的CSS选择器,因为它们会增加查询DOM的时间。避免频繁地读写DOM属性,如offsetHeight,这可能会导致布局抖动(layout thrashing),应该将读操作和写操作分开进行。结论优化DOM操作的关键是减少重绘和重排的次数,以及减少与DOM的直接交互次数。以上提到的方法都是为了实现这个目标。在实际的项目中,我们可能需要根据具体的情况选择合适的优化策略。
阅读 8·2024年6月24日 16:43

module.exports和exports的区别是什么?export和export default的区别是什么?

module.exports vs exports在 Node.js 中,module.exports 和 exports 都是用于导出模块中的变量或者函数,以便其他文件可以使用 require 方法来引入和使用。但是它们之间存在一些区别:module.exports:这是真正用于定义模块导出的对象。在模块中,可以通过对 module.exports 赋值来指定导出的内容。如果你需要导出单个值或者一个完整的对象,通常会使用 module.exports。例子:假设你有一个工具模块,想要导出一个类。javascriptclass Tool { // ...}module.exports = Tool;exports:exports 是 module.exports 的一个引用,Node.js 默认在每个模块的头部创建了 exports = module.exports。它通常用于导出多个对象或函数。但是,如果你给 exports 直接赋一个新值,它就不再指向 module.exports,这就可能导致模块导出一个空对象 {}。例子:假设你有一个工具模块,想要导出多个函数。javascriptexports.function1 = function() { // ...};exports.function2 = function() { // ...};如果设置了 module.exports,exports 对象会被忽略。因此,不应该同时使用 module.exports 和 exports 导出不同的东西,以避免混淆或错误。export vs export default在 ES6 模块系统中,export 和 export default 用于导出模块内容,但它们的用途和语法有所不同:export:用于导出一个或多个命名的变量、函数、类等。导入时需要使用花括号 {} 并指定相应的名称。可以在一个模块中使用多个 export。例子:导出多个功能。javascriptexport const CONSTANT = 'constant value';export function myFunction() { // ...}export default:用于导出一个模块的默认输出。导入时不需要使用花括号,可以给导入的内容任意命名。一个模块只能有一个 export default。例子:导出一个模块的主要功能或类。javascriptexport default class MyClass { // ...}另一个例子是,在一个模块中即使用 export 导出多个值,也可以指定一个默认导出。javascriptexport const util1 = () => { /* ... */ };export const util2 = () => { /* ... */ };export default () => { /* ... */ }; // 这是默认导出在使用时,import myDefaultImport from 'my-module' 会导入 export default 的值,而 import { namedImport } from 'my-module' 会导入通过 export 命名导出的值。总结来说,module.exports 和 exports 用于 Node.js 的 CommonJS 模块系统,而 export 和 export default 用于 ES6 模块系统。选择哪一个取决于你的使用环境和特定的需求。
阅读 23·2024年6月24日 16:43