前端面经真题解析6:字节-抖音-电商实习(2万字长文)

文章涵盖了前端面试中常见的技术话题,包括自我介绍时强调的技术专长、Vue3相对于Vue2的改进、TypeScript与JavaScript的区别、响应式布局的实现和布局单位的使用。此外,还讨论了其他编程语言特性,如解释型语言、类数组对象、数组去重方法和链表判断环的算法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 自我介绍

当在前端面试中进行自我介绍时,你可以按照以下结构进行组织:

  • 介绍自己:

开始时,简要介绍自己的姓名和背景。你可以提及自己的学历、专业背景以及工作经验。

  • 技术专长:

提及你在前端领域的技术专长和经验。列举你熟悉的编程语言、前端框架、库和工具等。可以着重强调你在哪些方面特别擅长,例如前端开发、响应式设计、用户界面设计、性能优化等。

  • 项目经验:

介绍你在过去的项目中所承担的角色和贡献。可以选择其中几个有代表性的项目来详细介绍,包括项目的规模、技术栈、解决的问题以及你在项目中的具体工作。

  • 学习与成长:

提及你的学习态度和成长经历。你可以分享你如何持续学习新技术、参与开发社区、阅读技术文章和书籍等。强调你对于前端领域的持续关注和追求,以及你在学习过程中遇到的挑战和如何克服它们。

  • 团队合作与沟通能力:

强调你在团队合作中的角色和能力。你可以提及你在过去的团队合作中的贡献,以及你如何与其他团队成员协作、解决问题和有效沟通的能力。

  • 总结与展望:

总结你的自我介绍,并表达你对前端领域的热情和对未来的展望。你可以提及你对前端技术的热爱和对新技术的追求,以及你希望在未来的工作中继续学习和成长。

自我介绍需要简明扼要地表达你的关键信息,同时展现你的技术实力、学习能力和团队合作能力。在介绍过程中,注意保持清晰的表达、自信的姿态和积极的态度。记得准备并练习自我介绍,以确保你能在面试中自信地展现自己。

2. 介绍自己项目,根据自己项目问了一些问题。(主要问了我微信小程序登入那块的逻辑)

  • 项目背景:首先,简要介绍你所参与的项目的背景和目的。说明该项目是什么,它解决了什么问题,以及它在业务或技术上的重要性。

  • 技术栈:列出你在项目中使用的主要技术栈和工具。这包括编程语言、框架、库和其他相关技术。说明你选择这些技术的原因以及它们在项目中的作用。

  • 功能和模块:简要描述项目的主要功能和模块。说明你在项目中负责的具体部分,以及你如何与团队协作开发这些功能和模块。

  • 技术亮点:强调项目中的一些技术亮点或你在项目中遇到的挑战。这可以是你解决的技术难题、性能优化、跨浏览器兼容性或响应式设计等方面的问题。

  • 成果和影响:列举项目的成果和影响。这可以是用户数量增长、用户反馈的改善、网站性能提升等。如果有具体的数据或统计结果支持,更好地展示项目的价值。

  • 学习和成长:分享你在项目中学到的经验和取得的成长。说明你在项目中遇到的挑战,以及你如何克服这些挑战并学到新的知识和技能。

  • 自我评价:最后,对你在项目中的贡献和经验进行自我评价。突出你在项目中发挥的作用,以及你对前端开发的热情和承诺。

确保在介绍项目时,简明扼要地表达你的观点,并与面试官保持良好的沟通。提供实际的例子和具体的细节,以支持你的介绍。同时,准备好回答与项目相关的问题,展示你对项目的深入了解。

3. 你参加过什么计算机比赛?acm?(没有,只参加过数学建模)

4. 你用过的后端语言?

nodejs;
Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,用于在服务器端运行 JavaScript 代码。它提供了一组强大的特性和 API,使得开发者可以构建高性能的网络应用程序。

以下是 Node.js 的一些关键特点和优势:

1.非阻塞式、事件驱动的 I/O:

Node.js 使用事件驱动的方式处理 I/O 操作,使得应用程序能够以非阻塞的方式处理大量并发请求。这种设计使得 Node.js 在处理高并发的场景下表现出色,并具有较低的资源消耗。

2.单线程:

Node.js 使用单线程模型处理请求,但它通过利用异步和非阻塞的特性,可以处理大量并发连接。此外,Node.js 还提供了一些工具和模块来充分利用多核系统。

3.轻量高效:

Node.js 以 JavaScript 作为开发语言,并且专注于高效性能。它采用了 V8 引擎,该引擎是由 Google Chrome 使用的 JavaScript 引擎,具有快速的执行速度。

4.跨平台:

Node.js 可以运行在多个操作系统平台上,包括 Windows、Mac OS 和 Linux。这使得开发人员可以在不同的环境中构建和部署 Node.js 应用程序。

5.丰富的模块生态系统:

Node.js 拥有庞大的模块生态系统,即 npm(Node Package Manager)。开发人员可以使用 npm 安装和管理各种开源模块,从而快速构建复杂的应用程序。

Node.js 可以用于构建各种类型的应用程序,例如 Web 服务器、实时聊天应用、API 服务、后端服务和命令行工具等。它已经成为流行的选择,因为它简化了前后端开发的一致性,并提供了高效的开发工具和架构。

同时,Node.js 的活跃社区和丰富的文档资源,为开发人员提供了广泛的支持和解决方案,使得 Node.js 成为现代 Web 开发的重要工具之一。

5. 什么是解释型语言?Java和c++是什么类型语言?

解释型语言是一种编程语言,其代码在运行之前不需要显式的编译过程,而是通过解释器逐行解释并执行。解释型语言的代码通常以文本形式保存,并在运行时逐行逐句地解释和执行。

常用的一些解释型语言包括:

1.JavaScript:JavaScript是一种广泛用于前端和后端开发的解释型脚本语言,用于网页交互、动态内容生成等。

2.Python:Python是一种易于学习和使用的高级解释型语言,适用于各种领域,包括科学计算、Web开发、自动化脚本等。

3.Ruby:Ruby是一种简洁而优雅的解释型脚本语言,注重开发者的幸福感,常用于Web开发和脚本编程。

4.PHP:PHP是一种广泛用于服务器端Web开发的解释型语言,用于创建动态网页和Web应用程序。

5.Perl:Perl是一种功能强大的解释型脚本语言,广泛用于文本处理、系统管理和网络编程等。

6.Shell脚本:Shell脚本是在Unix/Linux环境下运行的解释型脚本语言,用于自动化系统管理和任务处理。

这些解释型语言通常具有简单易学、快速开发的特点,因为它们不需要显式的编译步骤,可以直接运行和测试代码。然而,相对于编译型语言,解释型语言在运行时可能会牺牲一些性能。

常用的一些语言的类型:

这些语言涵盖了多种类型的编程语言。以下是它们的分类:

1.Java:Java是一种静态类型的编译型语言。它在编写代码时需要声明和定义变量的类型,并且需要将源代码编译为字节码,然后在Java虚拟机(JVM)上运行。

2.C++:C++是一种静态类型的编译型语言,它是C语言的扩展。它具有面向对象编程的特性,支持直接的硬件访问,并提供了高性能和内存管理的灵活性。

3.C:C是一种静态类型的编译型语言,它是现代编程语言的基础。C语言注重效率和底层控制,适用于系统级编程和嵌入式开发。

4.Go:Go是一种静态类型的编译型语言,由Google开发。它具有简洁的语法和高效的并发编程支持,适用于构建可靠和高性能的系统。

5.Node.js:Node.js是一个基于Chrome V8 JavaScript引擎的平台,用于构建服务器端和网络应用程序。它使用JavaScript作为脚本语言,并提供了访问底层系统资源的功能。

6.HTML:HTML(超文本标记语言)是一种标记语言,用于创建网页结构和内容。HTML本身不是一种编程语言,而是一种用于描述网页的标记语言。

需要注意的是,Java、C++、C、Go和Node.js属于编程语言,而HTML是一种标记语言。编程语言用于编写算法和控制计算机行为,而标记语言用于描述文档结构和内容。

6. vue3.0相对于vue2.0的优势?

Vue 3 是 Vue.js 的下一个主要版本,相对于 Vue 2,它带来了一些重要的变化和改进。以下是 Vue 2 和 Vue 3 版本之间的一些主要区别:

1.性能优化:

Vue 3 在性能方面进行了一些优化。通过使用 Proxy 替代 Object.defineProperty,提供更高效的响应式系统。Vue 3 还引入了静态树提升(Static Tree Hoisting)和更好的代码压缩,以提高渲染性能。

2.Composition API:

Vue 3 引入了 Composition API,它是一种基于函数的 API 风格,使组件的逻辑可以更好地组织和重用。与 Vue 2 中的 Options API 相比,Composition API 更加灵活和可组合,可以更好地处理复杂的逻辑和状态管理。

3.更好的 TypeScript 支持:

Vue 3 在 TypeScript 方面进行了一些改进,提供了更好的类型推断和类型检查支持,使开发者可以更轻松地使用 TypeScript 开发 Vue 应用程序。

4.更小的包体积:

Vue 3 中的包体积相对较小,通过移除一些不常用的特性和优化打包方式,可以减少应用程序的大小。

5.Teleport:

Vue 3 引入了 Teleport 组件,它可以在组件树中的任何位置渲染内容,而不会受到父组件的样式和限制。

6.Fragments:

Vue 3 支持使用 Fragments 来包裹多个组件根元素,而不需要额外的包裹元素。

7.更好的 TypeScript 支持:

Vue 3 在 TypeScript 方面进行了一些改进,提供了更好的类型推断和类型检查支持,使开发者可以更轻松地使用 TypeScript 开发 Vue 应用程序。

需要注意的是,由于 Vue 3 引入了一些重大的改变,从 Vue 2 迁移到 Vue 3 可能需要进行一些代码的调整和迁移工作。Vue 官方提供了一些工具和指南,帮助开发者顺利迁移他们的应用程序到 Vue 3。

7. typescript和JavaScript的区别?

JavaScript和TypeScript是两种相关的编程语言,具有一些相似之处,但也有一些重要的区别。

相似之处:

1.基础语法:

TypeScript是JavaScript的超集,它继承了JavaScript的基本语法和结构。因此,大部分合法的JavaScript代码也可以作为合法的TypeScript代码。

2.运行环境:

JavaScript和TypeScript都可以在浏览器和服务器端(如Node.js)中运行。

3.客户端开发:

两者都广泛用于前端开发,用于创建交互式网页、处理用户输入、操作DOM等。

4.面向对象编程:

JavaScript和TypeScript都支持面向对象编程范式,包括类、继承、多态等。

不同之处:

1.类型系统:

最显著的区别是TypeScript具有静态类型系统,而JavaScript是动态类型语言。TypeScript允许在代码编写阶段对变量、函数参数、返回值等进行类型注解,并在编译时进行类型检查。这可以提供更早的错误检测和更好的代码可靠性。

2.类型注解和推断:

TypeScript提供了类型注解的能力,可以明确指定变量的类型。JavaScript没有这个特性,变量的类型是在运行时动态推断的。

3.编译过程:

JavaScript代码可以直接在浏览器或Node.js环境中执行,而TypeScript代码需要经过编译过程,将TypeScript代码转换为JavaScript代码,然后再运行。

4.新特性支持:

由于TypeScript是JavaScript的超集,它支持最新的ECMAScript(JavaScript的标准)语法和特性,但同时还提供了额外的特性,例如接口、泛型、枚举等。

总体而言,TypeScript是建立在JavaScript之上的语言,它通过引入静态类型和其他增强功能来提供更好的开发体验和代码可靠性。尽管在语法上有些差异,但对于熟悉JavaScript的开发者来说,学习和使用TypeScript通常是相对容易的。

补充:类型系统介绍

类型系统是一种编程语言的特性,用于定义和检查变量、表达式和函数参数的类型。它确定了变量可以存储的数据类型以及在运行时如何进行类型检查和类型转换。

在静态类型语言中,如TypeScript,类型检查发生在编译时,即在代码被执行之前。开发者需要在代码中明确指定变量、函数参数、返回值等的类型,并且编译器会根据这些类型注解进行静态类型检查。如果代码中存在类型不匹配的情况,编译器会发出错误或警告,提供更早的错误检测和代码质量保证。

静态类型系统具有以下优势:

1.更早的错误检测:

通过在编译时进行类型检查,可以在代码执行之前捕获许多潜在的类型错误,减少运行时错误和调试时间。

2.更好的代码可读性和维护性:

类型注解可以提供代码的自文档性,让其他开发者更容易理解代码的意图和预期的数据类型。

3.自动化工具支持:

静态类型系统使得开发工具(如编辑器和集成开发环境)能够提供更强大的代码补全、自动重构和调试支持。

相比之下,动态类型语言(如JavaScript)在运行时对变量和表达式的类型进行推断和检查。变量的类型是根据实际赋值进行动态推断的,而不需要在代码中显式地指定类型。这种灵活性使得动态类型语言具有更快的开发速度和更灵活的代码编写方式。但它也可能导致一些潜在的类型错误在运行时才被发现,增加了调试的复杂性。

总的来说,静态类型系统和动态类型系统在类型检查的时机、代码可读性、工具支持和错误检测等方面存在差异。静态类型系统在编译时提供更严格的类型检查,而动态类型系统在运行时更加灵活。选择使用哪种类型系统取决于开发者对于代码可靠性、开发速度和团队协作的偏好和需求。

8. 你常用的布局?响应式布局呢?

1.在前端开发中,常用的布局方式包括:

1.盒子模型布局(Box Model Layout):

基于CSS的盒子模型,使用盒子元素的宽度、高度、内边距(padding)、边框(border)和外边距(margin)等属性来布局页面元素。

2.流式布局(Fluid Layout):

使用相对单位(如百分比)定义元素的宽度和高度,使得页面元素可以根据浏览器窗口大小的变化而自适应调整。

3.栅格布局(Grid Layout):

基于网格系统,将页面分为若干列和行,通过定义元素在网格中的位置和大小来布局页面。常见的栅格系统有Bootstrap的栅格系统和CSS Grid布局。

4.弹性盒子布局(Flexbox Layout):

使用CSS3中的弹性盒子模型,通过指定容器的属性来控制容器内子元素的布局方式和排列顺序,使得页面布局更加灵活和响应式。

5.层叠布局(Layered Layout):

使用CSS的定位属性(如position)和层叠顺序(z-index),将元素定位于页面的特定位置,用于创建重叠效果和特定的布局结构。

2.响应式布局

响应式布局是一种可以自适应不同设备和屏幕尺寸的布局方式。它通过使用媒体查询(Media Queries)、流式布局、弹性盒子布局等技术,使得网页在不同的屏幕尺寸下具有良好的显示效果和用户体验。常见的响应式布局模式有:

1.流式布局:

使用百分比单位定义元素的宽度,使得页面元素可以自适应不同屏幕尺寸。

2…媒体查询布局:

根据不同的屏幕尺寸、设备类型或其他条件,通过媒体查询来应用不同的样式规则,从而调整页面布局。

3.移动优先布局:

首先设计和开发适用于移动设备的布局,然后通过媒体查询等方式逐渐增加适用于大屏幕设备的布局样式。

4.栅格系统布局:

使用响应式栅格系统,根据屏幕尺寸调整列的数量和宽度,实现页面的自适应布局。

响应式布局可以提供更好的用户体验,使网页在不同设备上具有一致性和可访问性。

3.代码示例

1.盒子模型布局(Box Model Layout):

盒子模型布局是基于CSS的盒子模型,通过设置元素的宽度、高度、内边距、边框和外边距等属性来布局页面元素。

<div class="box"></div>
.box {
  width: 200px;
  height: 100px;
  padding: 10px;
  border: 1px solid black;
  margin: 20px;
}

2.流式布局(Fluid Layout):

流式布局使用相对单位(如百分比)定义元素的宽度和高度,使得页面元素可以根据浏览器窗口大小的变化而自适应调整。

<div class="container">
  <div class="box"></div>
</div>
.container {
  width: 100%;
}

.box {
  width: 50%;
  height: 200px;
}

3.栅格布局(Grid Layout):

栅格布局通过将页面划分为若干列和行,使用网格系统来布局页面。常见的栅格系统有Bootstrap的栅格系统和CSS Grid布局。

<div class="container">
  <div class="row">
    <div class="col">Column 1</div>
    <div class="col">Column 2</div>
  </div>
</div>
.container {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  grid-gap: 10px;
}

.col {
  height: 200px;
}

4.弹性盒子布局(Flexbox Layout):

弹性盒子布局使用CSS3中的弹性盒子模型,通过设置容器的属性来控制容器内子元素的布局方式和排列顺序。

<div class="container">
  <div class="box">Box 1</div>
  <div class="box">Box 2</div>
  <div class="box">Box 3</div>
</div>
.container {
  display: flex;
  justify-content: space-between;
}

.box {
  flex: 1;
  height: 100px;
}

5.响应式布局(Responsive Layout):

响应式布局是一种可以自适应不同设备和屏幕尺寸的布局方式。它通过使用媒体查询、流式布局、弹性盒子布局等技术,使得网页在不同的屏幕尺寸下具有良好的显示效果和用户体验。

<div class="container">
  <div class="box">Box 1</div>
  <div class="box">Box 2</div>
</div>
.container {
  display: flex;
  flex-wrap: wrap;
}

.box {
  flex: 1 0 50%;
  height: 200px;
}

@media (max-width: 768px) {
  .box {
    flex-basis: 100%;
  }
}

6.多列布局(Multi-column Layout):

使用CSS的多列布局属性(如column-count、column-gap)将内容分为多个列进行布局,类似于报纸的版面排版。

<div class="container">
  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
  <p>Nullam dictum tristique leo, at volutpat turpis ultricies vitae.</p>
  <p>Etiam blandit quam non ex pellentesque aliquam.</p>
</div>
.container {
  column-count: 2;
  column-gap: 20px;
}

7.表格布局(Table Layout):

使用HTML的表格元素(table、tr、td)进行布局,适用于需要展示结构化数据的情况。

<table>
  <tr>
    <td>Cell 1</td>
    <td>Cell 2</td>
  </tr>
  <tr>
    <td>Cell 3</td>
    <td>Cell 4</td>
  </tr>
</table>
table {
  width: 100%;
}

8.瀑布流布局(Masonry Layout):

一种动态排列的布局方式,类似于瀑布流的效果,不同大小的元素会根据可用空间进行自动排列。

<div class="masonry">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <div class="item">Item 3</div>
  <div class="item">Item 4</div>
</div>
.masonry {
  column-count: 3;
  column-gap: 20px;
}

.item {
  break-inside: avoid-column;
}

以上是一些常见的前端布局方法,它们可以根据具体的需求和情况选择使用。这些布局方法的代码示例提供了基本的结构和样式,但实际开发中可能需要根据具体情况进行适当的调整和样式定制。

9. px,rem,em,vw,vh的区别?

1.前端常用单位及样式

1.像素(Pixel,px):

像素是屏幕上的最小显示单元。在CSS中,像素通常用于定义绝对长度或固定大小的元素。1个像素等于1个物理像素点。

2.相对单位:

相对单位基于某种参考值,可以根据上下文的变化而自适应调整。

  • rem(root em):相对于根元素(html)的字体大小的单位。例如,如果根元素的字体大小为16px,1rem等于16px。
  • em(em):相对于元素自身的字体大小的单位。例如,如果一个元素的字体大小为16px,1em等于16px。
  • vw(viewport width):相对于视口宽度的百分比单位。1vw等于视口宽度的1%。
  • vh(viewport height):相对于视口高度的百分比单位。1vh等于视口高度的1%。

3.百分比(Percentage,%):

百分比单位基于父元素的相对值。它可以用于定义元素的宽度、高度、边距等属性。1%等于相对于父元素的1%。

4.其他单位:

  • cm(centimeter):厘米单位。
  • mm(millimeter):毫米单位。
  • pt(point):印刷单位,1pt等于1/72英寸。
  • pc(pica):印刷单位,1pc等于12点。

这些单位可以根据具体的需求和设计要求来选择使用。绝对单位(如像素)通常用于固定大小的元素或需要精确控制的情况,而相对单位和百分比单位则可以用于实现响应式布局和根据上下文进行自适应的效果。

2.小程序单位

在微信小程序中,常用的长度单位是rpx(responsive pixel)。rpx是微信小程序特有的单位,用于在不同设备上实现自适应布局。

rpx与px之间的转换关系是:在iPhone6等设备上,1rpx等于物理像素的1/2,而在iPhone6 Plus等高像素密度设备上,1rpx等于物理像素的1/3。微信小程序框架会根据设备的像素密度自动将rpx转换为相应的px值,从而实现在不同设备上的统一显示效果。

例如,如果设置一个元素的宽度为100rpx,那么在iPhone6上,该元素的宽度将相当于50px,而在iPhone6 Plus上,该元素的宽度将相当于33.33px。

使用rpx单位可以方便地实现微信小程序在不同设备上的适配,无需手动计算不同设备的像素密度和屏幕尺寸。通过设置元素的样式时,直接使用rpx单位即可。

.element {
  width: 100rpx;
  height: 200rpx;
  font-size: 30rpx;
  margin-top: 20rpx;
}

以上样式设置会自动根据设备的像素密度转换为相应的px值,从而实现在不同设备上的一致显示效果。

10. object和map的区别

在JavaScript中,Map 是一种独立的数据结构,它并不属于 Object 类型。Map 是 ES6 引入的一种键值对的数据结构,与 Object 在用途和实现上有一些不同。

虽然 Map 和 Object 都可以用于存储键值对数据,但它们有以下几个区别:

键的类型:在 Object 中,键只能是字符串或者符号(Symbol)类型,而在 Map 中,键可以是任意类型,包括对象、函数、基本类型等。

插入顺序:在 Object 中,对象的属性是无序的,无法保证属性的顺序。而在 Map 中,键值对的插入顺序会被保留,可以通过迭代器按照插入顺序遍历。

方法和属性:Map 提供了一系列方法和属性用于操作和访问键值对,如 set()、get()、has()、delete() 等,而 Object 则有一些特殊的方法和属性,如 Object.keys()、Object.values()、Object.entries() 等。

原型继承:Map 并不具有原型继承的特性,不会继承额外的属性和方法。而 Object 是所有对象的原型,可以通过原型链访问和继承属性和方法。

虽然 Map 和 Object 有一些共同的特性,但它们是独立的数据结构,适用于不同的场景和需求。Map 更适合于需要保持顺序并且键的类型多样化的情况,而 Object 则更适合于简单的键值对存储和原型继承的需求。

11. 什么是类数组

在 JavaScript 中,类数组(Array-like)是指类似数组的对象,它们具有类似数组的特点,例如按照索引访问元素、具有 length 属性等,但它们不是真正的数组。类数组对象在结构上类似于数组,但缺少数组对象上的方法和功能。

1.类数组对象常见的特点包括:

索引访问:类数组对象可以通过数字索引访问元素,就像访问数组元素一样。例如:obj[0]。

length 属性:类数组对象具有 length 属性,表示对象中元素的个数。

迭代:类数组对象可以通过 for 循环或 forEach 等迭代方法进行遍历。

缺少数组方法:类数组对象不具备数组对象上的方法,如 push、pop、slice 等。

2.常见的类数组对象包括:

函数的 arguments 对象、DOM 元素集合(如通过 querySelectorAll 获取的节点列表)、NodeList、HTMLCollection 等。

例如,以下是一个类数组对象的示例:

var arrayLike = {
  0: "apple",
  1: "banana",
  2: "orange",
  length: 3
};

console.log(arrayLike[0]); // 输出: "apple"
console.log(arrayLike.length); // 输出: 3

classList不是类数组,它没有按照索引访问的特性。

尽管类数组对象具有类似数组的特性,但它们无法直接使用数组方法和操作。如果需要在类数组对象上使用数组的方法,可以通过将类数组对象转换为真正的数组,或者使用数组的方法进行操作。例如,可以使用 Array.from() 方法将类数组对象转换为数组,或者使用 Array.prototype.slice.call() 方法将其转换为数组。

12. 数组去重(当时说了四种)

1.使用 Set:

Set 是 ES6 引入的一种数据结构,它可以存储唯一值的集合。通过将数组转换为 Set,然后再将 Set 转换回数组,就可以实现数组去重。

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

2.使用 filter:
可以使用 Array 的 filter 方法结合 indexOf 或者 includes 方法来过滤出不重复的元素。

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.filter((value, index, self) => {
  return self.indexOf(value) === index;
});
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

在这个代码片段中,self.indexOf(value) === index 是用于判断当前元素是否为第一次出现的条件。当数组中存在重复元素时,indexOf 方法返回的是第一个匹配到的索引值。如果当前元素是第一次出现(即索引与 indexOf 返回的索引相等),则该元素被保留,否则被过滤掉。

具体解释如下:

self.indexOf(value):indexOf 方法用于查找数组中指定元素的索引。如果找到了匹配的元素,则返回其第一次出现的索引;如果未找到,则返回 -1。

self.indexOf(value) === index:index 是 filter 方法提供的当前元素的索引。通过比较 indexOf 返回的索引与当前元素的索引是否相等,可以判断当前元素是否是第一次出现的元素。

如果当前元素是第一次出现,即索引与 indexOf 返回的索引相等,条件表达式返回 true,当前元素被保留到最终的过滤结果中。

这样通过 filter 方法进行遍历和过滤,只保留第一次出现的元素,从而实现了数组的去重操作。

需要注意的是,这种方法会保留第一次出现的元素,并且保持了原数组中的顺序。如果需要保留最后一次出现的元素,可以使用 lastIndexOf 方法进行判断。

3.使用 reduce:

使用 Array 的 reduce 方法结合一个空数组,通过判断元素是否已经存在于新数组中,来构建去重后的数组。

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = array.reduce((accumulator, value) => {
  if (!accumulator.includes(value)) {
    accumulator.push(value);
  }
  return accumulator;
}, []);
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

补充介绍reduce:

reduce() 是 JavaScript 数组的一个高阶函数,用于对数组中的每个元素执行一个回调函数,并将结果累积为单个值。它接受两个参数:回调函数和初始值。

回调函数接受四个参数:累加器(accumulator)、当前值(currentValue)、当前索引(currentIndex)和原始数组(array)。回调函数可以执行任意操作,并返回更新后的累加器值。reduce() 方法会遍历数组,对每个元素依次调用回调函数,并将回调函数的返回值作为下一次调用的累加器值,最终返回累加器的最终值。

示例代码如下:

const array = [1, 2, 3, 4, 5];
const sum = array.reduce((accumulator, currentValue, currentIndex, array) => {
  return accumulator + currentValue;
}, 0);

console.log(sum); // 输出: 15

在上述示例中,reduce() 方法计算了数组 array 中所有元素的总和。初始值为 0(作为第二个参数传递给 reduce()),累加器 accumulator 从初始值开始累加,每次加上当前值 currentValue。

在 reduce() 的回调函数中,accumulator 是一个累加器变量,它存储了每次回调函数返回的累积结果。在第一次调用回调函数时,初始值将作为累加器的初始值。随后,每次调用回调函数时,累加器将持有上一次回调函数的返回值。

在本例中,初始值为 0,回调函数将累加每个元素的值到累加器中,并返回更新后的累加器值。最终,reduce() 方法返回累加器的最终值,即数组中所有元素的总和。

需要注意的是,如果数组为空且没有提供初始值,则 reduce() 方法将抛出 TypeError。因此,为了处理空数组的情况,建议始终提供初始值作为 reduce() 的第二个参数。

请注意,reduce() 是一种非常强大和灵活的方法,它可以用于解决各种累积计算的问题,如求和、求平均值、查找最大/最小值等。

4.使用 indexOf 和循环:

可以通过遍历数组,使用 indexOf 方法检查当前元素是否在数组中已经存在,如果不存在则将其添加到新数组中。

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [];
for (let i = 0; i < array.length; i++) {
  if (uniqueArray.indexOf(array[i]) === -1) {
    uniqueArray.push(array[i]);
  }
}
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

5.使用 includes 和循环:

类似于使用 indexOf,可以使用 includes 方法来检查当前元素是否已经存在于新数组中。

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [];
for (let i = 0; i < array.length; i++) {
  if (!uniqueArray.includes(array[i])) {
    uniqueArray.push(array[i]);
  }
}
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

6.使用 Map:

可以使用 Map 数据结构来实现数组去重。遍历数组,将每个元素作为 Map 的键,并将其值设为 true。然后,将 Map 的键转换为数组。

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = Array.from(new Map(array.map(item => [item, true])).keys());
console.log(uniqueArray); // 输出: [1, 2, 3, 4, 5]

13. 数组和链表的区别?链表查询的时间复杂度,数组的时间复杂度?

数组和链表是两种不同的数据结构,它们在存储和访问数据上有着不同的特点。

1.数组(Array):

  • 数组是一种线性数据结构,元素在内存中连续存储。

  • 数组的元素可以通过索引访问,索引是基于零的整数值。

  • 数组适用于需要快速随机访问元素的场景,因为可以通过索引直接访问到指定位置的元素。

  • 在插入和删除元素时,需要移动其他元素来维持连续的存储空间,所以在性能上可能受到影响。

2.链表(Linked List):

  • 链表是一种非连续的数据结构,元素通过节点和指针相连。

  • 链表中的每个节点包含数据和指向下一个节点的指针。

  • 链表的元素不能像数组那样通过索引直接访问,只能通过遍历链表来访问。

  • 链表适用于频繁的插入和删除操作,因为在插入和删除元素时,只需要修改节点的指针即可,不需要移动其他元素。

3.链表和数组在查询操作的时间复杂度上有所区别。

(1)链表的查询时间复杂度:

最坏情况下,链表的查询时间复杂度为 O(n),其中 n 是链表的长度。这是因为链表中的元素并非连续存储,每次查询都需要从头节点开始遍历,直到找到目标元素或者到达链表的末尾。

(2)数组的查询时间复杂度:

数组的查询时间复杂度为 O(1)。由于数组的元素在内存中是连续存储的,并且可以通过索引直接访问,因此可以在常量时间内直接获取指定位置的元素。

需要注意的是,在特定情况下,数组的查询时间复杂度可能为 O(n)。例如,在非排序数组中进行线性搜索,或者在未知元素位置的情况下进行查找。但是,对于已知索引的元素访问,数组的查询时间复杂度仍然是 O(1)。

综上所述:

链表的查询时间复杂度为 O(n),其中 n 是链表的长度。
数组的查询时间复杂度为 O(1),前提是已知元素的索引。

因此,在需要频繁进行查询操作的情况下,数组通常具有更高的效率。而链表适用于需要频繁进行插入和删除操作的场景。

4.补充
JavaScript 中的标准库中没有内置的链表数据结构。然而,你可以使用对象和引用来手动创建链表。例如,可以定义一个节点对象,其中包含一个值属性和一个指向下一个节点的引用属性。通过操作节点和引用,可以实现链表的基本功能。

下面是一个使用 JavaScript 实现链表的简单示例:

class Node {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

class LinkedList {
  constructor() {
    this.head = null;
  }

  add(value) {
    const newNode = new Node(value);
    if (this.head === null) {
      this.head = newNode;
    } else {
      let currentNode = this.head;
      while (currentNode.next !== null) {
        currentNode = currentNode.next;
      }
      currentNode.next = newNode;
    }
  }

  print() {
    let currentNode = this.head;
    while (currentNode !== null) {
      console.log(currentNode.value);
      currentNode = currentNode.next;
    }
  }
}

const list = new LinkedList();
list.add(1);
list.add(2);
list.add(3);
list.print();

在上述示例中,我们定义了一个 Node 类表示链表中的节点,以及一个 LinkedList 类表示链表。LinkedList 类具有添加元素和打印链表的方法。通过创建节点对象并连接节点,可以创建一个简单的链表,并打印链表的值。

虽然 JavaScript 中没有原生支持链表的内置实现,但你可以使用对象和引用手动实现链表的功能。

14. 怎么判断链表有环?我以为要做算法题,说了下快慢指针的思路。

要判断一个链表是否包含环,可以使用快慢指针的方法。该方法通过使用两个指针,一个快指针和一个慢指针,在遍历链表时检测它们是否会相遇来判断链表是否有环。

以下是判断链表是否有环的算法步骤:

(1)初始化快指针(fast)和慢指针(slow)都指向链表的头节点。

(2)使用一个循环,快指针每次移动两步,慢指针每次移动一步。

(3)在每一步移动之后,检查快指针和慢指针是否相等。

(4)如果相等,说明链表中存在环,返回 true。

(5)如果快指针(或快指针的下一个节点)到达链表的末尾(即为 null),则说明链表无环,返回 false。

下面是使用 JavaScript 实现判断链表是否有环的代码示例:

class ListNode {
  constructor(value) {
    this.value = value;
    this.next = null;
  }
}

function hasCycle(head) {
  let fast = head;
  let slow = head;

  while (fast && fast.next) {
    fast = fast.next.next;
    slow = slow.next;

    if (fast === slow) {
      return true;
    }
  }

  return false;
}

在上述示例中,我们定义了一个 ListNode 类表示链表的节点,并实现了一个 hasCycle 函数来判断链表是否有环。函数接受链表的头节点作为参数。

使用快慢指针的方法,我们在循环中让快指针每次移动两步,慢指针每次移动一步。如果存在环,快指针最终会追上慢指针,进入相同的节点,此时返回 true。如果快指针到达链表的末尾,说明链表无环,返回 false。

这种方法的时间复杂度为 O(n),其中 n 是链表的长度。快慢指针的方法可以有效地判断链表是否包含环,并且在发现环时具有较高的效率。

15. 算法题一:有效字符串(里面就是有字符串和数字,例’{134[]}'就是有效的)我当时先过滤数字,在用栈来判断有效括号

没找到原题,换一个类似的。

1.题目

给你一个只包含三种字符的字符串,支持的字符类型分别是 '('')''*'。请你检验这个字符串是否为有效字符串,如果是有效字符串返回 true 。

有效字符串符合如下规则:

任何左括号 '(' 必须有相应的右括号 ')'。
任何右括号 ')' 必须有相应的左括号 '(' 。
左括号 '(' 必须在对应的右括号之前 ')''*' 可以被视为单个右括号 ')' ,或单个左括号 '(' ,或一个空字符串。
一个空字符串也被视为有效字符串。
 

示例 1:

输入:s = "()"
输出:true

2.解法1:

  • min: 表示未匹配的左括号的最小值。

  • max:表示未匹配的左括号的最大值。

  • 当遇到左括号,最小值和最大值同时加1。

  • 当遇到星号,星号可能是作为左括号增加一个未匹配左括号,也可能作为右括号消除一个左括号。所以最小值-1,最大值+1;

  • 当遇到右括号,最小值和最大值都-1;

  • 在遍历过程中,任何时候,最大值都不应当小于0,否则返回false。如果最小值小于0,应当重新赋为0。

  • 遍历结束后,最小值应当刚好等于0;否则返回false。

var checkValidString = function(s) {
    let max = 0, min = 0;
    for ( i of s) {
        if (i === '(') {
            min++;
            max++;
        } else if (i === ')') {
            min = min-1 < 0? 0: min-1;
            max--;
        } else {
            min = min-1 < 0? 0: min-1;
            max++;
        }
        if (max < 0) {
            return false;
        } 
    }
   return min === 0?  true: false;
};

3.解法2:

这个方法的思想是两次遍历,

  • l代表未被匹配的左括号,r代表未被匹配的右括号,x代表可以充当癞子牌的星号。
  • 第一次从左遍历,看是否所有右括号都能被匹配。
  • 第二次从右遍历,看是否所有左括号都能被匹配。

注意,这种思路是有效的,有的同学可能会想,有没有可能会出现这种情况,就是一个星号,既被当作左括号,又被当作右括号的情况,但是实际上,它只能作为癞子牌出现一次。

  • 这种情况是不会出现的。我们分情况讨论:

  • 1.第一次遍历时,左括号不够,有星号被作为左括号用来匹配右括号了。那么,当第二次遍历时,左括号一定能被右括号完全匹配,不会使用到星号,因为左括号匹配右括号都不够,更不要说多出来了需要星号去抵消了。

  • 2.第一次遍历时,左括号多余右括号,那不需要用到星号。第二次遍历时会用到星号,与第一次无关,不会冲突。

  • 当第一次遍历结束时,能够证明右括号可以被完全匹配,而此时如果未匹配的左括号数量也等于0,那么已经可以说明未true,不需要进行第二次遍历。

var checkValidString = function(s) {
    let l = 0, x = 0;
    for (let i=0; i < s.length; i++) {
        if (s[i] === '(') {
            l++;
        } else if (s[i] === ')') {
            l--;
        } else {
            x++;
        }
        if (l < 0) {
            l++;
            x--;
        }
        if (x < 0) {
            return false;
        }
    }
    if (l===0) {
        return true;
    }
    let r = 0;
    x= 0;
    for (let i=s.length-1; i>=0; i--) {
       if (s[i] === ')') {
            r++;
        } else if (s[i] === '(') {
            r--;
        } else {
            x++;
        }
        if (r < 0) {
            r++;
            x--;
        }
        if (x < 0) {
            return false;
        }
    }
    return true;
};

16. 算法题二:找到数组最近的目标值(简单的算法题)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值