前端面试----深信服面试题全概

15 篇文章 0 订阅
27 篇文章 0 订阅

这里结合了CSDN多位博主和自己当时被问到的的深信服面试题,为了锻炼自己的答题能力和技术点理解,这里对博主的各个题目进行了自己的解答(最好用自己的理解自己的话解答),部分解答借鉴了博主的回答,同时对部分问题进行了相关拓展延伸。记在word笔记的时候没有记录博主地址,但还是感谢分享,一起努力加油啊!

一、HTML

1、常见的HTML块级元素和行内元素

2、HTML5的新标签有什么了解,详述一下表单的新标签

3、Image标签中alt和title有什么区别

二、CSS

1、CSS盒子模型(IE盒子模型,标准盒子模型)

2、CSS元素居中(能说几种说几种)

3、CSS伪类

4、CSS定位

5、能说一下BFC吗,BFC除了清除浮动,防止margin重叠之外,你了解它的原理吗

6、css3的动画实现举例

7、100px的div变成圆 有几种方法

8、css的预处理器,能跟我讲一下你用它解决了什么问题吗,除了变量提取还有什么好用的功能

9、a页面有一个选择器,b有相同的选择器,多人合作导致样式冲突,怎么解决?(我答scoped,他说除了scoped还有什么方法?)

10、scoped的原理

11、鼠标悬浮按钮上,跳出下拉框,使用纯css实现

12、px、em、rem

13、图片有哪些格式,区别有哪些

14、CSS实现三角形

核心:宽高设置为0,由边框来控制大小,然后边框颜色改为透明,然后更改一遍的边框颜色为自己想要的颜色

div{
    width: 0;
    height: 0;
    border:50px solid transparent;
    border-bottom-color:red;
}

注意:上面这样设计的话,div的实际占位还是个方形,如下代码实现实际占位变为三角形

div{
    width: 0;
    height: 0;
    border-left:20px solid transparent;
    border-right:20px solid transparent;
    border-bottom-color:20px solid red;
}

三、JS

1、JavaScript的数据类型(ES6新增的数据类型)

2、数据隐式转换,数据类型判断

3、== 和 === 的区别

4、ES6的了解

5、事件委托

(1)事件流

HTML中与javascript交互是通过事件驱动来实现的,例如鼠标点击事件onclick、页面的滚动事件onscroll等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。

什么是事件流:从页面中接收事件的顺序

  • 事件捕获阶段
  • 处于目标阶段
  • 事件冒泡阶段

addEventListeneraddEventListener 是DOM2 级事件新增的指定事件处理程序的操作,这个方法接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。

IE只支持事件冒泡

(2)如何让事件先冒泡后捕获

在DOM标准事件模型中,是先捕获后冒泡。但是如果要实现先冒泡后捕获的效果,对于同一个事件,监听捕获和冒泡,分别对应相应的处理函数,监听到捕获事件,先暂缓执行,直到冒泡事件被捕获后再执行捕获事件。

(3)事件委托

  • 简介:事件委托指的是,不在事件的发生地(直接dom)上设置监听函数,而是在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的触发,通过判断事件发生元素DOM的类型,来做出不同的响应。

  • 举例:最经典的就是ul和li标签的事件监听,比如我们在添加事件时候,采用事件委托机制,不会在li标签上直接添加,而是在ul父元素上添加。

  • 好处:比较适合动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制。

6、JS的继承(多种实现方式)

7、什么是原型链原型链的继承,怎么防止原型链继承创建对象全都继承父类的属性

8、作用域链

9、高性能数组去重的方法?(隐式转换,高频考点)

10、数组的方法有哪些,举例,有哪些是对数组进行遍历

11、讲一下闭包用到什么场景,能够解决什么问题,你在项目中用到哪个地方,具体讲一下可以吗

12、对Promise的了解

13、手写一个Promise all

14、DOM结点树的增删改查

15、防抖和节流

  • 防抖背后的思路是指,减少函数在一定时间段内连续调用的次数,只执行一次
  • 节流是让连续执行的函数,变为固定时间段间断地执行

16、如何判断一个对象为空?

1)遍历属性法

一旦该对象的属性是可遍历的即说明该对象存在属性,则返回false,否则该对象为空对象。

function judgeObj(obj){
    for(var attr in obj){
          return  false
    }
    return true
}
 
console.log(judgeObj({}))


(2)JSON.stringify() 方法

function judgeObj2(obj){
    if(JSON.stringify(obj) == '{}')
        return true
    else 
        return false
}
console.log(judgeObj2({}))

(3)ES6 Object.keys()方法

Object.keys方法是JavaScript中用于遍历对象属性的一个方法 。

它传入的参数是一个对象,返回的是一个数组,数组中包含的是该对象所有的属性名。

function judgeObj3(obj) {
    if(Object.keys(obj).length == 0){
        return true
    } else {
        return false
    }
}

17、for...in 和for...of 的区别

  • for in :ES5标准 遍历对象得到键名key(直接拿到对象的属性名,再根据属性名获取属性值);不建议用来遍历数组(因为输出顺序不固定)
  • for of :ES6标准 遍历数组(数组、类数组对象,字符串、Set、Map 以及 Generator 对象)得到键值value;不建议用来遍历对象

来个直观简要的图例吧 ~

发现for of 遍历对象报错了——for of 只能用在可迭代对象上,而obj1 不可迭代

为什么 for in 可以呢?——for in 的原理是 Object.keys():返回给定对象所有可枚举属性的字符串数组

若要用 for of 遍历对象,要结合Object.keys()进行使用:

        for (let e of Object.keys(obj1)) {
            console.log(e); // 键名
            console.log(obj1[e]); // 键值(使用的是对象属性访问方法)
        }

回顾该部分的知识点,附上我的思维导图 note:

四、浏览器相关

1、从浏览器输入一个url到显示页面经历的过程

一次完整的HTTP请求过程  版本 1.0

  1. 浏览器对输入的网址进行DNS解析,得到对应的IP地址
  2. 根据这个IP,找到对应的服务器,发起TCP连接(TCP的三次握手)
  3. 建立TCP链接后,发起HTTP请求
  4. 服务器响应HTTP请求,浏览器得到 html 代码
  5. 浏览器解析 html 代码,并请求代码中的资源(js、css、图片等)--先得到 html 代码,才能去找这些资源
  6. 浏览器对页面进行渲染,呈现给用户
  7. 服务器关闭TCP连接(TCP的四次挥手)
HTTP请求过程--复习推演
1、url(www.baidu.com)=> ip (192.168.1.2)
2、拿到ip,与服务器建立TCP连接
3、TCP连接后,发送HTTP请求 (TCP是比http更底层的协议,ip是tcp下面一层)
4、HTTP连接后,服务器把html发给浏览器
5、浏览器解析html(js、css、图片 下载 运行)
6、浏览器渲染html
7、服务器关闭连接(TCP HTTP)

注:

(1)DNS怎么找到域名的? 

DNS域名解析采用的是递归查询的方式

过程:先去找DNS缓存->缓存找不到就去找根域名服务器->根域名又会去找下一级,这样递归查找之后,找到了,给我们的web浏览器

过程(面试):浏览器自身域名缓存区找 =》操作系统的域名缓存区找 =》hosts文件找 =》域名服务器找

(2)为什么HTTP协议要基于TCP来实现?

TCP是一个端到端的可靠的面相连接的协议,HTTP基于传输层TCP协议,不用担心数据传输的各种问题(当发生错误时,会重传)

(3)最后一步浏览器是如何对页面进行渲染的?

(4)TCP的三次握手和四次挥手

三次握手:

第一次握手:建立连接时,客户端发送syn包(syn=x)到服务器等待服务器确认

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=x+1),同时自己也发送一个SYN包(syn=y),即SYN+ACK包

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手

四次挥手:

第一次挥手:客户端进程发出连接释放报文(FIN报文),并且停止发送数据

第二次挥手:服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v

  • 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)

第三次挥手:服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1

第四次挥手:客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。

2、浏览器加载过程,着重讲一下前端如何渲染到页面(浏览器运行机制)

  1. 构建DOM树:渲染引擎解析HTML文档,将标签转换成DOM节点生成DOM树
  2. 构建渲染树:解析CSS文件,构建渲染树(Render树),使每个节点有自己的样式
  3. 布局渲染树:从根节点(body)递归调用,布局出每个节点在屏幕出现的位置
  4. 绘制渲染树:遍历渲染树,使用UI层绘制每个节点 ,呈现界面

3、对重绘和回流的了解

(1)概念

  • 重绘:元素外观(如背景颜色)改变引起的浏览器行为,使元素外观重新绘制
  • 回流(重排):渲染树中的元素尺寸、布局、隐藏等改变,需要重新构建

(2)注意点

  • 每个页面至少发生一次回流,即页面第一次加载的时候
  • 重排必定引发重绘,重绘不一定引发重排

(3)补充

触发重排的条件:任何页面布局或几何属性的改变都会触发重排

  • 页面渲染初始化(无法避免)
  • 添加或删除可见的DOM元素
  • 元素位置的改变,或使用动画
  • 元素尺寸的改变(大小,外边距,边框)
  • 浏览器窗口尺寸的变化(resize事件)
  • 填充内容的改变(文本或图片大小改变,引起计算值宽高改变)
  • 读取某些元素属性(offsetLeft/Top/Height/Width, clientLeft/Top/Height/Width,scrollLeft/Top/Height/Width等)

(4)扩展

重绘重排会造成耗时、浏览器卡顿,那么如何做优化呢?(尽量减少DOM操作)

1、浏览器优化:浏览器会把引起回流、重绘的操作放入一个队列,等队列中的操作到了一定数量或者时间间隔,就flush这个队列进行一个批处理。这样就可以让多次回流重绘变成一次。

2、代码优化:减少对渲染树的操作,可以合并多次DOM和样式的修改,并减少对style样式的请求。

  • 修改元素样式的时候,直接修改样式名className(尽量一次修改元素样式,不要一会改一点。也就是把新样式放在另一个样式名中)
  • 某些元素先设置成display: none,然后进行页面布局操作,再设置display: block(这样只会引发两次重绘重排)
  • 使用cloneNode和repalceChild技术(引发一次重绘重排)
  • 将需要多次重排的元素,position属性设为absoluted或fixed(元素脱离文档流,变化不会影响其他元素)
  • 当需要创建多个节点的时候,使用DocumentFragment创建完后一次性的加入(如循环创建一个li,让循环结束后所有的li都创建完了(fragment中)再一次性加入(文档))

4、什么是同源策略

5、什么是跨域,如果没有同源机制会怎样,具体说一下

6、跨域有几种方式(JSONP跨域的局限性)

7、jsonp的原理

8、cookie,session,sessionstorage,localstorage的区别

Web storage(HTML5)
cookiesessionsessionstoragelocalstorage
数据生命周期一般由服务器生成,在设置失效时间(expires)内有效(与窗口或浏览器是否关闭无关)。若在浏览器设置,默认浏览器关闭后失效除非被清除,否则永久保存(刷新页面数据依旧存在)仅在当前会话有效,关闭窗口或浏览器后清除除非web应用主动删除,否则永不失效
存放数据大小4k左右5M5M
与服务端通信保存在浏览器端。每次都会携带在HTTP请求头中,保存过多数据会有性能问题保存在服务器端,不参与服务器通信保存在客户端,不参与服务器通信
安全性安全性较低(cookie诈骗cookie截取)session安全性大于cookie
易用性一般接口需要自己封装接口可以直接使用
作用域在浏览器所有的同源窗口中共享不能在不同的浏览器窗口共享不能在不同的浏览器窗口共享,在同源窗口中可以共享
使用场景

主要用于保存登录信息

1、判断用户是否登录过网站,方便下次登录实现自动登录或记住密码

2、上次登录的时间等信息

3、上次查看的页面

4、浏览计数

用于保存每个用户的专用信息,变量的值保存在服务器端,通过sessionID来区分不同用户。

1、购物车

2、用户登录信息

3、将某些数据放入session中,供同一用户的不同页面使用

4、防止用户非法登录

敏感账号一次性登录、表单,对于那种只需要在用户浏览一组页面期间保存而关闭浏览器后就可以丢弃的数据,sessionStorage会非常方便

常用于长期登录(判断用户是否登录),适合长期保存在本地的数据。

购物车信息、HTML5游戏产生的一些本地数据

优点

1、存储空间大

2、节省网络流量

3、可在本地直接获取,不需要与服务器交互

4、获取速度快

5、安全性较高

6、更多丰富易用的API接口

7、支持事件通知机制,可以将数据更新的通知发送给监听者

8、操作方法:setItem、getItem、removeItem、clear、key、length

缺点

1、大小受限

2、用户可以禁用cookie,使功能受限

3、安全性较低

4、有些状态不能保存在客户端

5、同源请求时会被携带(服务端和客户端互传,不论是否需要),加大http流量,数据过多影响性能

6、cookie数据有路径(path)的概念,可以限制cookie只属于某个路径下

9、原生ajax的实现步骤

10、get和post的区别

  • 参数:最直观的区别就是GET把参数包含在URL中,POST通过request body传递参数
  • 操作数据:语义上对于GET用来获取数据,POST用来提交的数据
  • 大小:GET提交的数据(URL长度)大小受限(与HTTP协议无关,受浏览器或web服务器影响),而POST则没有此限制
  • 缓存方面的区别:

get请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。

post不同,post做的一般是修改和删除的工作,所以必须与数据库交互,所以不能使用缓存。因此get请求适合于请求缓存。

11、常见的状态码以及意思

状态码:状态码用于表示服务器对请求的处理结果

  • 1xx:指示信息——表示请求已经接受,继续处理
  • 2xx:成功——表示请求已经被成功接收、理解、接受。
  • 3xx:重定向——要完成请求必须进行更进一步的操作
  • 4xx:客户端错误——请求有语法错误或请求无法实现
  • 5xx:服务器端错误——服务器未能实现合法的请求。

列举几种常见的:

  • 200(没有问题) 
  • 302(要你去找别人) 
  • 304(要你去拿缓存) 
  • 307(要你去拿缓存) 
  • 403(有这个资源,但是没有访问权限) 
  • 404(服务器没有这个资源) 
  • 500(服务器这边有问题)

12、浏览器的缓存机制  包括缓存位置分类类型 强缓存、协商缓存

面试:性能提升 =》减少HTTP请求 =》HTTP缓存控制(浏览器缓存)?

回答:

  1. http缓存能够帮助服务器提高并发性能,很多资源不需要重复请求,直接从浏览器中拿缓存
  2. http缓存分类:强缓存、协商缓存
  3. 强缓存通过 Expires 和Cache-control 控制,协商缓存通过 Last-modify 和 Etag 控制

补充问题:

1、为什么有 expires 还要 cache-control

因为expires有服务器和浏览器时间不同步问题,expires是绝对时间,cache-control是相对时间

2、为什么有 last-modify 还要 etag

因为 last-modify 有精度问题  到秒,etag 没有精度问题,只要资源改变,etag值就会改变

详细理解:

(1)浏览器缓存机制

  • 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识

  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

(2)浏览器缓存策略

缓存策略都是通过设置 HTTP Header 来实现的,根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强缓存和协商缓存。

1、浏览器加载一个页面的简单流程如下:

  1. 浏览器先根据这个资源的http头信息来判断是否命中强缓存。如果命中则直接加在缓存中的资源,并不会将请求发送到服务器。(强缓存)

  2. 如果未命中强缓存,则浏览器会将资源加载请求发送到服务器。服务器来判断浏览器本地缓存是否失效。若可以使用,则服务器并不会返回资源信息,浏览器继续从缓存加载资源。(协商缓存)

  3. 如果未命中协商缓存,则服务器会将完整的资源返回给浏览器,浏览器加载新资源,并更新缓存。(新的请求)

2、强缓存:不会向服务器发送请求,直接从缓存中读取资源

强缓存可以通过设置两种 HTTP Header 实现:ExpiresCache-Control

  • Expires(旧 http1.0):缓存过期时间(绝对时间),用来指定资源到期的时间,是服务器端的具体的时间点(在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存取数据,而无需再次请求)。受限于本地时间,如果修改了本地时间,可能会造成缓存失效。
  • Cache-Control(新 http1.1):是一个相对时间,代表资源的有效期。 
    • 同时启用时cashe-control优先级更高

强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。

3、协商缓存:强制缓存未命中,浏览器携带缓存标识向服务器发起请求由服务器根据缓存标识决定是否使用缓存的过程。主要有以下两种情况:

  • 协商缓存生效,返回304和Not Modified
  • 协商缓存失效,返回200和请求结果

协商缓存--协商个啥呢?

浏览器问服务器,我缓存的文件有没有更新啊?

  1. 没有更新:浏览器可以用缓存  304
  2. 更新了:浏览器不能用缓存,服务器发新的给浏览器  200

协商缓存可以通过设置两种 HTTP Header 实现:Last-ModifiedETag (帮助浏览器跟服务器进行协商)

  • Last-Modified:浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间 。当浏览器再次请求该资源时,发送的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存。如果命中缓存,则返回http304,并且不会返回资源内容,并且不会返回Last-Modify。由于对比的服务端时间,所以客户端与服务端时间差距不会导致问题。但是有时候通过最后修改时间来判断资源是否修改还是不太准确(资源变化了最后修改时间也可以一致,精度问题  到秒)。于是出现了ETag/If-None-Match。
  • Etag(http1.1):服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),Etag/If-None-Match返回的是一个校验码(ETag: entity tag) 。ETag可以保证每一个资源是唯一的,只要资源有变化,Etag就会重新生成。服务器根据浏览器上发送的If-None-Match值来判断是否命中缓存。

4、HTTP1.1中Etag的出现主要是为了解决几个Last-Modified比较难解决的问题:

  • Last-Modified标注的最后修改只能精确到秒级,如果某些文件在1秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间

  • 如果某些文件会被定期生成,当有时内容并没有任何变化,但Last-Modified却改变了,导致文件没法使用缓存

  • 有可能存在服务器没有准确获取文件修改时间,或者与代理服务器时间不一致等情形

Etag是服务器自动生成或者由开发者生成的对应资源在服务器端的唯一标识符,能够更加准确的控制缓存。Last-Modified与ETag是可以一起使用的,服务器会优先验证ETag,一致的情况下,才会继续比对Last-Modified,最后才决定是否返回304。

5、缓存机制:强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。

6、如果什么缓存策略都没设置,那么浏览器会怎么处理?

对于这种情况,浏览器会采用一个启发式的算法,通常会取响应头中的 Date 减去 Last-Modified 值的 10% 作为缓存时间。

(3)实际应用场景

  • 频繁变动的资源
  • 不常变化的资源

(4)用户行为对浏览器缓存的影响

新开窗口有效有效
用户操作Expires/Cache-ControlLast-Modified/Etag
地址栏回车有效有效
页面链接跳转有效有效
前进、后退有效有效
F5 刷新无效有效
Ctrl+F5 刷新无效无效

13、TCP 和 UDP的区别?

  • TCP是面向连接的,UDP是无连接的。TCP需要有三次握手连接,UDP不需要
  • TCP提供可靠的服务,保证通过TCP连接传输的数据不会丢失、不会重复、保证传输有序,但不保证按序到达。(TCP:确认与重传机制、拥塞控制、流量控制)UDP不提供可靠性服务。
  • TCP是面向字节流的,UDP是面向报文段的(一次发送一个报文)。
  • UDP传输比TCP快,TCP首部开销20字节、UDP首部开销8字节

14、HTTP所有的请求方法类型?

HTTP1.0 中定义的方法:

  • GET: 完整请求一个资源 (常用)
  • HEAD: 仅请求响应首部
  • POST:提交表单 (常用 )

HTTP1.1 中新增的5种方法:

  • PUT: (webdav) 上传文件(但是浏览器不支持该方法)
  • DELETE:(webdav) 删除
  • OPTIONS:返回请求的资源所支持的方法的方法
  • TRACE: 追求一个资源请求中间所经过的代理(该方法不能由浏览器发出)
  • CONNECT 

15、什么是URL、URI、URN?

  • URI Uniform Resource Identifier 统一资源标识符
  • URL Uniform Resource Locator 统一资源定位符
  • URN Uniform Resource Name 统一资源名称

URL和URN 都属于 URI,为了方便就把URL和URI暂时都通指一个东西

五、VUE

1、vue生命周期,挂载

2、vue组件之间如何通信

3、Vue双向绑定的原理

4、VUE Router有几类,各自的原理

5、虚拟dom的优势和diff算法

六、数据结构与算法

1、二叉树的遍历,已知前序遍历12473568,中序遍历47215386,请问后序遍历

                          74256831

2、知道的排序算法,仔细讲下快速排序和归并排序

3、反转链表的原理思路,每一步是怎么一个结果,详细讲一下

4、查找一个数组中的a元素最长连续出现次数

5、动态规划,跳台阶

6、数组和链表的区别

7、合并两个有序数组


var merge = function(nums1, m, nums2, n) {
    let nums = []
    let i = 0
    let j = 0
    while(i < m && j < n){
        if(nums1[i] <= nums2[j]){
            nums.push(nums1[i++])
        } else {
            nums.push(nums2[j++])
        }
    }
    
    while(i < m){
        nums.push(nums1[i++])
    }
    while(j < n){
        nums.push(nums2[j++])
    }
    return nums
};

8、假设有一个大数组arr,长度很大,几百万个,循环执行pop方法,怎么样提高效率?

9、冒泡排序

10、树形结构与数组之间的转换

数组转树形结构

我们先建立这样一个对象数组

        const arr = [
            {
                id: '1',
                parent_id: 'root',
                name: 'hh'
            },
            {
                id: '2',
                parent_id: 'root',
                name: 'hh'
            },
            {
                id: '1-1',
                parent_id: '1',
                name: 'hh'
            },
            {
                id: '1-2',
                parent_id: '1',
                name: 'hh'
            },
            {
                id: '1-1-1',
                parent_id: '1-1',
                name: 'hh'
            },
            {
                id: '1-1-2',
                parent_id: '1-1',
                name: 'hh'
            },
            {
                id: '1-2-1',
                parent_id: '1-2',
                name: 'hh'
            },
            {
                id: '2-1',
                parent_id: '2',
                name: 'hh'
            },
            {
                id: '2-2',
                parent_id: '2',
                name: 'hh'
            },
            {
                id: '2-1-1',
                parent_id: '2-1',
                name: 'hh'
            },
            {
                id: '2-2-1',
                parent_id: '2-2',
                name: 'hh'
            },
            {
                id: '2-2-1-1',
                parent_id: '2-2-1',
                name: 'hh'
            },
            {
                id: '2-2-1-2',
                parent_id: '2-2-1',
                name: 'hh'
            },
            {
                id: '2-2-1-2-1',
                parent_id: '2-2-1-2',
                name: 'hh'
            },
            {
                id: '2-3',
                parent_id: '2',
                name: 'hh'
            },
            {
                id: '2-3-1',
                parent_id: '2-3',
                name: 'hh'
            },
            {
                id: '3',
                parent_id: 'root',
                name: 'hh'
            },
        ];

这是一个扁平化的数组,我们创建一个临时对象,再做一下处理

        function arrayToTree(array, id, parent_id) {
            let temp = {}; // 创建临时对象
            let tree = {}; // 创建需要返回的树形对象
            // 构造temp对象节点,如:
            // '2-3': { id: '2-3', parent_id: '2',... } 
            for (let i in array) { // 先遍历数组,将数组的每一项添加到temp对象中
                temp[array[i][id]] = array[i];
            }
            console.log(temp);

            return tree;
        }

打印看看结果如何

基于此开始创建树形结构

        function arrayToTree(array, id, parent_id) {
            let temp = {}; // 创建临时对象
            let tree = {}; // 创建需要返回的树形对象
            // 构造temp对象节点,如:
            // '2-3': { id: '2-3', parent_id: '2',... } 
            for (let i in array) { // 先遍历数组,将数组的每一项添加到temp对象中
                temp[array[i][id]] = array[i];
            }
            console.log(temp);
            // 遍历temp对象,将当前子节点与父节点建立连接, 并建立树形结构
            for (let i in temp) {
                if (temp[i][parent_id] !== 'root') { // 判断是否是根节点下的项
                    if (!temp[temp[i][parent_id].children]) {
                        temp[temp[i][parent_id]].children = new Array();
                    }
                     temp[temp[i][parent_id]].children.push(temp[i]);
                } else {
                    tree[temp[i][id]] = temp[i];
                }
            }
            return tree;
        }
        const obj = arrayToTree(arr, 'id', 'parent_id');
        console.log(obj);

打印结果

树形结构转数组

例如这样一个树形结构

        const treeObj = {
            id: '0',
            name: '0',
            children: [
                {
                    id: '1',
                    name: 'cc',
                    children: [
                        {
                            id: '1-1',
                            name: 'cc',
                            children: [
                                {
                                    id: '1-1-1',
                                    name: 'cc',
                                },
                                {
                                    id: '1-1-2',
                                    name: 'cc'
                                },
                            ]
                        },
                        {
                            id: '1-2',
                            name: 'cc',
                            children: [
                                {
                                    id: '1-2-1',
                                    name: 'cc',
                                },
                                {
                                    id: '1-2-2',
                                    name: 'cc'
                                },
                            ]
                        },
                    ]
                },
                {
                    id: '2',
                    name: 'cc',
                    children: [
                        {
                            id: '2-1',
                            name: 'cc',
                            children: [
                                {
                                    id: '2-1-1',
                                    name: 'cc',
                                },
                                {
                                    id: '2-1-2',
                                    name: 'cc'
                                },
                            ]
                        },
                        {
                            id: '2-2',
                            name: 'cc',
                            children: [
                                {
                                    id: '2-2-1',
                                    name: 'cc',
                                    children: [
                                        {
                                            id: '2-2-1-1',
                                            name: 'cc',
                                        },
                                        {
                                            id: '2-2-1-2',
                                            name: 'cc'
                                        },
                                    ]
                                },
                                {
                                    id: '2-2-2',
                                    name: 'cc'
                                },
                            ]
                        },
                        {
                            id: '2-3',
                            name: 'cc',
                            children: [
                                {
                                    id: '2-3-1',
                                    name: 'cc',
                                },
                                {
                                    id: '2-3-2',
                                    name: 'cc'
                                },
                            ]
                        },
                    ]
                },
                {
                    id: '3',
                    name: 'cc',
                    children: []
                }
            ]
        };
        

代码实现

        // 将treeObj中的所有对象,存入一个数组中,并根据 id 关联起父子关系
        function treeToArray(treeObj, rootid) {
            const temp = [];  // 设置临时数组,用来存放队列
            const out = [];    // 设置输出数组,用来存放要输出的一维数组
            temp.push(treeObj);
            // 首先把根元素存放入out中
            let pid = rootid;
            const obj = deepCopy(treeObj);
            obj.pid = pid;
            delete obj['children'];
            out.push(obj)
            // 对树对象进行广度优先的遍历
            while (temp.length > 0) {
                const first = temp.shift();
                const children = first.children;
                if (children && children.length > 0) {
                    pid = first.id;
                    const len = first.children.length;
                    for (let i = 0; i < len; i++) {
                        temp.push(children[i]);
                        const obj = deepCopy(children[i]);
                        obj.pid = pid;
                        delete obj['children'];
                        out.push(obj)
                    }
                }
            }
            return out
        }
        // 深拷贝
        function deepCopy(obj) {
            // 深度复制数组
            if (Object.prototype.toString.call(obj) === "[object Array]") {
                const object = [];
                for (let i = 0; i < obj.length; i++) {
                    object.push(deepCopy(obj[i]))
                }
                return object
            }
            // 深度复制对象
            if (Object.prototype.toString.call(obj) === "[object Object]") {
                const object = {};
                for (let p in obj) {
                    object[p] = obj[p]
                }
                return object
            }
        }

        console.log(treeToArray(treeObj, 'root'))

结果打印如下

七、其他

1、对懒加载的了解,如何做到懒加载?

2、命名法有哪些?

3、如何实现头像的放大缩小、剪裁、图片懒加载是怎实现的?

4、Webpack打包的流程?

5、Webpack如何提高打包效率、减少压缩体积?

6、你的项目是如何构建的?

7、页面长时间出现白屏如何排查问题?如果不能使用开发环境如何定位问题?

持续解答更新中。。。。。。

评论 5 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:1024 设计师:我叫白小胖 返回首页

打赏作者

huohuoit

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值