草庐IT

前端主流面试官必问超详细面试题(整理完以秃头)持续更新中

殿君不是殿军 2023-04-10 原文

前端优化

我们可以使用以下几种方式做前端优化

CDN:

CDN利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户。

CDN的作用:

(1)在性能方面,引入CDN的作用在于:

  • 用户收到的内容来自最近的数据中心,延迟更低,内容加载更快
  • 部分资源请求分配给了CDN,减少了服务器的负载

(2)在安全方面,CDN有助于防御DDoS网络攻击:

  • 针对DDoS:通过监控分析异常流量,限制其请求频率

懒加载:

懒加载也叫做延迟加载、按需加载,指的是在长网页中延迟加载图片数据,是一种较好的网页性能优化的方式。 比如:在较长的网页中。如果有很多图片,所有的图片都被加载出来,而用户只能看到可视窗口的那一部分图片数据,这样就浪费了性能。

如果我们使用图片的懒加载就可以解决这个问题,在滚动屏幕之前,可视化区域之外的图片不会进行加载,在屏幕滚动时才加载,这样减少了服务器的负载,使网页的加载速度更快

回流与重绘

在渲染树中,部分或全部元素的尺寸、结构或者属性发生变化时,浏览器会重新渲染文档的过程就叫回流

下面这些操作会导致回流:

  • 页面的首次渲染
  • 浏览器的窗口大小发生变化
  • 元素的内容发生变化
  • 元素的尺寸或者位置发生变化
  • 元素的字体大小发生变化
  • 激活CSS伪类
  • 查询某些属性或者调用某些方法
  • 添加或者删除可见的DOM元素

当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘

下面这些操作会导致重绘:

  • color、background 相关属性:background-color、background-image 等
  • outline 相关属性:outline-color、outline-width 、text-decoration
  • border-radius、visibility、box-shadow

注意: 当触发回流时,一定会触发重绘,但是重绘不一定会引发回流。

如何避免回流与重绘?

减少回流与重绘的措施:

  • 操作DOM时,尽量在低层级的DOM节点进行操作
  • 不要使用table布局, 一个小的改动可能会使整个table进行重新布局
  • 使用CSS的表达式
  • 不要频繁操作元素的样式,对于静态页面,可以修改类名,而不是样式。
  • 使用absolute或者fixed,使元素脱离文档流,这样他们发生变化就不会影响其他元素
  • 避免频繁操作DOM,可以创建一个文档片段documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
  • 将元素先设置display: none,操作结束后再把它显示出来。因为在display属性为none的元素上进行的DOM操作不会引发回流和重绘。
  • 将DOM的多个读操作(或者写操作)放在一起,而不是读写操作穿插着写。这得益于浏览器的渲染队列机制

如何优化动画?

对于如何优化动画,我们知道,一般情况下,动画需要频繁的操作DOM,就就会导致页面的性能问题,我们可以将动画的position属性设置为absolute或者fixed,将动画脱离文档流,这样他的回流就不会影响到页面了。

节流与防抖

  • 函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。
  • 函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。(持续触发的函数每隔一段时间触发一次)节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

图片优化

如何对项目中的图片进行优化?

  • 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
  • 小图使用 base64 格式
  • 将多个图标文件整合到一张图片中(雪碧图)
  • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
  • 照片使用 JPEG

Webpack优化

如何提⾼webpack的打包速度?

  • 优化 Loader

    可以将 Babel 编译过的文件缓存起来,下次只需要编译更改过的代码文件即可,这样可以大幅度加快打包时间

对于 Loader 来说,影响打包效率首当其冲必属 Babel 了。因为 Babel 会将代码转为字符串生成 AST(AST (Abstract Syntax Tree)是抽象语法树 ),然后对 AST 继续进行转变最后再生成新的代码,项目越大,转换代码越多,效率就越低。当然了,这是可以优化的。

  • 代码压缩

    在 Webpack3 中,一般使用 UglifyJS 来压缩代码,但是这个是单线程运行的,为了加快效率,可以使用 webpack-parallel-uglify-plugin这个插件来并行运行 UglifyJS,从而提高效率。

如何⽤webpack来优化前端性能?

⽤webpack优化前端性能是指优化webpack的输出结果,让打包的最终结果在浏览器运⾏快速⾼效。

压缩代码:删除多余的代码、注释、简化代码的写法等等⽅式。可以利⽤webpack的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩JS⽂件, 利⽤ cssnano (css-loader?minimize)来压缩css

利⽤CDN加速: 在构建过程中,将引⽤的静态资源路径修改为CDN上对应的路径。可以利⽤webpack对于 output 参数和各loader的 publicPath 参数来修改资源路径

Tree Shaking: 将代码中永远不会⾛到的⽚段删除掉。可以通过在启动webpack时追加参数 --optimize-minimize 来实现

Code Splitting: 将代码按路由维度或者组件分块(chunk),这样做到按需加载,同时可以充分利⽤浏览器缓存

提取公共第三⽅库: SplitChunksPlugin插件来进⾏公共模块抽取,利⽤浏览器缓存可以⻓期缓存这些⽆需频繁变动的公共代码

HTTP协议常问的面试题(吐血整理)

1、http协议请求方式 :

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法
HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT

下面是它们的作用(背的时候可以挑常见的请求去背诵)

GET: 通常用于请求服务器发送某些资源
POST: 发送数据给服务器
PUT: 用于新增资源或者使用请求中的有效负载替换目标资源的表现形式
PATCH: 用于对资源进行部分修改
DELETE: 用于删除指定的资源
HEAD: 请求资源的头部信息, 并且这些头部与 HTTP GET 方法请求时返回的一致. 该请求方法的一个使用场景是在下载一个大文件前先获取其大小再决定是否要下载, 以此可以节约带宽资源
OPTIONS: 用于获取目的资源所支持的通信选项
CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
TRACE: 回显服务器收到的请求,主要用于测试或诊断

2、GET和POST有什么区别?

数据传输方式不同:GET请求通过URL传输数据,而POST的数据通过请求体传输。

安全性不同:POST的数据因为在请求主体内,所以有一定的安全性保证,而GET的数据在URL中,通过历史记录,缓存很容易查到数据信息。

数据类型不同:GET只允许 ASCII 字符,而POST无限制

GET无害: 刷新、后退等浏览器操作GET请求是无害的,POST可能重复提交表单

特性不同:GET是安全(这里的安全是指只读特性,就是使用这个方法不会引起服务器状态变化)且幂等(幂等的概念是指同一个请求方法执行多次和仅执行一次的效果完全相同),而POST是非安全非幂等

3、什么是无状态协议,HTTP 是无状态协议吗,怎么解决

无状态协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息。状态协议解决办法:通过1、Cookie 2、通过Session会话保存。

无状态协议(Stateless Protocol) 就是指浏览器对于事务的处理没有记忆能力。举个例子来说就是比如客户请求获得网页之后关闭浏览器,然后再次启动浏览器,登录该网站,但是服务器并不知道客户关闭了一次浏览器。
HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力。可能大多数用户不相信,他可能觉得每次输入用户名和密码登陆一个网站后,下次登陆就不再重新输入用户名和密码了。这其实不是 HTTP 做的事情,起作用的是一个叫做 小甜饼(Cookie) 的机制。它能够让浏览器具有记忆能力。

4、UDP 和 TCP 的区别

UDP 是什么?

UDP 的全称是 User Datagram Protocol,用户数据报协议。它不需要所谓的握手操作,从而加快了通信速度,允许网络上的其他主机在接收方同意通信之前进行数据传输。

数据报是与分组交换网络关联的传输单元。

UDP 的特点主要有:
UDP 能够支持容忍数据包丢失的带宽密集型应用程序
UDP 具有低延迟的特点
UDP 能够发送大量的数据包
UDP 能够允许 DNS 查找,DNS 是建立在 UDP 之上的应用层协议。

TCP 是什么?

TCP 的全称是Transmission Control Protocol ,传输控制协议。它能够帮助你确定计算机连接到 Internet 以及它们之间的数据传输。通过三次握手来建立 TCP 连接,三次握手就是用来启动和确认 TCP 连接的过程。一旦连接建立后,就可以发送数据了,当数据传输完成后,会通过关闭虚拟电路来断开连接。


TCP 的主要特点有:
TCP 能够确保连接的建立和数据包的发送
TCP 支持错误重传机制
TCP 支持拥塞控制,能够在网络拥堵的情况下延迟发送
TCP 能够提供错误校验和,甄别有害的数据包。

TCP 和 UDP 的区别(重点来了)

TCP 是面向连接的协议 。 UDP 是无连接的协议
TCP 在发送数据前先需要建立连接,然后再发送数据 。 UDP 无需建立连接就可以直接发送大量数据
TCP 会按照特定顺序重新排列数据包 。 UDP 数据包没有固定顺序,所有数据包都相互独立
TCP 传输的速度比较慢 。 UDP 的传输会更快
TCP 的头部字节有 20 字节 。 UDP 的头部字节只需要 8 个字节
TCP 是重量级的,在发送任何用户数据之前,TCP需要三次握手建立连接。 UDP 是轻量级的。没有跟踪连接,消息排序等。
TCP 会进行错误校验,并能够进行错误恢复 。 UDP 也会错误检查,但会丢弃错误的数据包。
TCP 有发送确认。 UDP 没有发送确认
TCP 会使用握手协议,例如 SYN,SYN-ACK,ACK。 UDP无握手协议
TCP 是可靠的,因为它可以确保将数据传送到路由器。 UDP 中不能保证将数据传送到目标。

5、说一下Http协议中302状态?

http协议中,返回状态码302表示重定向。这种情况下,服务器返回的头部信息中会包含一个Location字段,内容是重定向到的url。

6、Http协议有什么组成?

请求报文包含三部分:请求行:包含请求方法、URI、HTTP版本信息;请求首部字段;请求内容实体。

7、cookies机制和session机制的区别是什么?

(1)cookies数据保存在客户端,session数据保存在服务端;

(2)cookies可以减轻服务器压力,但是不安全,容易进行cookies欺骗;

(3)session安全一点,但是占用服务器资源。

8、HTTP协议有什么特点?

(1)http无连接:限制每次连接只处理一个请求,服务端完成客户端的请求后,即断开连接。(传输速度快,减少不必要的连接,但也意味着每一次访问都要建立一次连接,效率降低);

(2)http无状态:对于事务处理没有记忆能力。每一次请求都是独立的,不记录客户端任何行为;

(3)客户端/服务端模型:客户端支持web浏览器或其他任何客户端;

(4)简单快速;

(5)灵活:可以传输任何类型的数据。

9、http和https有什么区别?

(1)https有ca证书,http一般没有;

(2)http是超文本传输协议,信息是明文传输。https则是具有安全性的ssl加密传输协议;

(3)http默认80端口,https默认443端口。

10、为什么有了HTTP为什么还要HTTPS?

https是安全版的http,因为http协议的数据都是明文进行传输的,所以对于一些敏感信息的传输就很不安全,HTTPS就是为了解决HTTP的不安全而生的。

11、HTTP的keep-alive是干什么的?

在早期的HTTP/1.0中,每次http请求都要创建一个连接,而创建连接的过程需要消耗资源和时间,为了减少资源消耗,缩短响应时间,就需要重用连接。在后来的HTTP/1.0中以及HTTP/1.1中,引入了重用连接的机制,就是在http请求头中加入Connection: keep-alive来告诉对方这个请求响应完成后不要关闭,下一次咱们还用这个请求继续交流。协议规定HTTP/1.0如果想要保持长连接,需要在请求头中加上Connection: keep-alive。

keep-alive的优点:

  • 较少的CPU和内存的使用(由于同时打开的连接的减少了)
  • 允许请求和应答的HTTP管线化
  • 降低拥塞控制 (TCP连接减少了)
  • 减少了后续请求的延迟(无需再进行握手)
  • 报告错误无需关闭TCP连

12、http的请求报文是什么样的?

请求报文有4部分组成:

  • 请求行

  • 请求头部

  • 空行

  • 请求体

  • 请求行包括:请求方法字段、URL字段、HTTP协议版本字段。它们用空格分隔。例如,GET /index.html HTTP/1.1。

  • 请求头部:请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔

  1. User-Agent:产生请求的浏览器类型。
  2. Accept:客户端可识别的内容类型列表。
  3. Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。
  • 请求体: post put等请求携带的数据

13、http的响应报文是什么样的?

响应报文有4部分组成:

  • 响应行

  • 响应头

  • 空行

  • 响应体

  • 响应行: 由协议版本,状态码和状态码的原因短语组成,例如HTTP/1.1 200 OK

  • 响应头:响应部首组成

  • 响应体:服务器响应的数据

14、聊一聊HTTP的部首有哪些?

通用首部字段(General Header Fields):请求报文和响应报文两方都会使用的首部

  • Cache-Control 控制缓存 ✨
  • Connection 连接管理、逐条首部 ✨

请求首部字段(Reauest Header Fields):客户端向服务器发送请求的报文时使用的首部

  • User-Agent 客户端程序信息 ✨

  • Host 请求资源所在服务器 ✨

  • If-Match 比较实体标记(ETage) ✨

    If-None-Match 比较实体标记(ETage)与 If-Match相反 ✨

    If-Modified-Since 比较资源更新时间(Last-Modified)✨

    If-Unmodified-Since比较资源更新时间(Last-Modified),与 If-Modified-Since相反 ✨

响应首部字段(Response Header Fields):从服务器向客户端响应时使用的字段

  • Server 服务器的信息 ✨
  • Location 令客户端重定向的URI ✨

实体首部字段(Entiy Header Fields):针对请求报文和响应报文的实体部分使用首部

  • Last-Modified 资源最后的修改资源 ✨
  • Expires 实体主体的过期资源 ✨
  • Allow 资源可支持http请求的方法 ✨

15、聊一聊HTTP的状态码有哪些?

2XX 成功

  • 200 OK,表示从客户端发来的请求在服务器端被正确处理 ✨
  • 201 Created 请求已经被实现,而且有一个新的资源已经依据请求的需要而建立
  • 202 Accepted 请求已接受,但是还没执行,不保证完成请求
  • 204 No content,表示请求成功,但响应报文不含实体的主体部分
  • 206 Partial Content,进行范围请求 ✨

3XX 重定向

  • 301 moved permanently,永久性重定向,表示资源已被分配了新的 URL
  • 302 found,临时性重定向,表示资源临时被分配了新的 URL ✨
  • 303 see other,表示资源存在着另一个 URL,应使用 GET 方法丁香获取资源
  • 304 not modified,表示服务器允许访问资源,但因发生请求未满足条件的情况
  • 307 temporary redirect,临时重定向,和302含义相同

4XX 客户端错误

  • 400 bad request,请求报文存在语法错误 ✨
  • 401 unauthorized,表示发送的请求需要有通过 HTTP 认证的认证信息 ✨
  • 403 forbidden,表示对请求资源的访问被服务器拒绝 ✨
  • 404 not found,表示在服务器上没有找到请求的资源 ✨
  • 408 Request timeout, 客户端请求超时
  • 409 Confict, 请求的资源可能引起冲突

5XX 服务器错误

  • 500 internal sever error,表示服务器端在执行请求时发生了错误 ✨
  • 501 Not Implemented 请求超出服务器能力范围,例如服务器不支持当前请求所需要的某个功能,或者请求是服务器不支持的某个方法
  • 503 service unavailable,表明服务器暂时处于超负载或正在停机维护,无法处理请求
  • 505 http version not supported 服务器不支持,或者拒绝支持在请求中使用的 HTTP 版本

16、TCP 三次握手和四次挥手

TCP 三次握手和四次挥手也是面试题的热门考点,它们分别对应 TCP 的连接和释放过程。下面就来简单认识一下这两个过程

TCP 三次握手

在了解具体的流程前,我们需要先认识几个概念


消息类型 描述

SYN 这个消息是用来初始化和建立连接的。
ACK 帮助对方确认收到的 SYN 消息
SYN-ACK 本地的 SYN 消息和较早的 ACK 数据包
FIN 用来断开连接


SYN:它的全称是 Synchronize Sequence Numbers,同步序列编号。是 TCP/IP 建立连接时使用的握手信号。在客户机和服务器之间建立 TCP 连接时,首先会发送的一个信号。客户端在接受到 SYN 消息时,就会在自己的段内生成一个随机值 X。
SYN-ACK:服务器收到 SYN 后,打开客户端连接,发送一个 SYN-ACK 作为答复。确认号设置为比接收到的序列号多一个,即 X + 1,服务器为数据包选择的序列号是另一个随机数 Y。
ACK:Acknowledge character, 确认字符,表示发来的数据已确认接收无误。最后,客户端将 ACK 发送给服务器。序列号被设置为所接收的确认值即 Y + 1。

看了上面是不是人都傻掉了 接下来我用一个简单的例子去帮大家通俗易懂的去理解:happy:

小明👩 - 客户端

小红👨 - 服务端

小明给小红打电话,接通了后,小明说喂,能听到吗,这就相当于是连接建立。

小红给小明回应,能听到,你能听到我说的话吗,这就相当于是请求响应。

小明听到小红的回应后,好的,这相当于是连接确认。在这之后小明和小红就可以通话/交换信息了。

TCP 四次挥手

在连接终止阶段使用四次挥手,连接的每一端都会独立的终止。下面我们来描述一下这个过程。

首先,客户端应用程序决定要终止连接(这里服务端也可以选择断开连接)。这会使客户端将 FIN 发送到服务器,并进入 FIN_WAIT_1 状态。当客户端处于 FIN_WAIT_1 状态时,它会等待来自服务器的 ACK 响应。

然后第二步,当服务器收到 FIN 消息时,服务器会立刻向客户端发送 ACK 确认消息。

当客户端收到服务器发送的 ACK 响应后,客户端就进入 FIN_WAIT_2 状态,然后等待来自服务器的 FIN 消息

服务器发送 ACK 确认消息后,一段时间(可以进行关闭后)会发送 FIN 消息给客户端,告知客户端可以进行关闭。

当客户端收到从服务端发送的 FIN 消息时,客户端就会由 FIN_WAIT_2 状态变为 TIME_WAIT 状态。处于 TIME_WAIT 状态的客户端允许重新发送 ACK 到服务器为了防止信息丢失。客户端在 TIME_WAIT 状态下花费的时间取决于它的实现,在等待一段时间后,连接关闭,客户端上所有的资源(包括端口号和缓冲区数据)都被释放。

还是可以用上面那个通话的例子来进行描述

小明对小红说,我所有的东西都说完了,我要挂电话了。
小红说,收到,我这边还有一些东西没说。
经过若干秒后,小红也说完了,小红说,我说完了,现在可以挂断了
小明收到消息后,又等了若干时间后,挂断了电话。

浏览器兼容问题

面试官问我我该这样回答:

我会针对浏览器不同的版本去选择合适的框架(比如大于等于ie8版本的我会选择bootstrap)(大于等于ie9的我会去选择vue)

面对不同的浏览器,他们对CSS的解析认识不完全一样,因此会导致生成的页面效果不一样,得不到我们所需要的页面效果。 这个时候我们就需要对不同的浏览器去写不同的CSS让他们能在不同的浏览器中也能得到我们想要的页面效果

不同浏览器的扩展功能(例如 -moz-webkit 开头的样式),对W3C标准也是个推进;说它是坏事,因为,多个浏览器同时存在,这些浏览器在处理一个相同的页面时,表现有时会有差异。

我们把引起这些差异的问题统称为“浏览器兼容性问题”。

  • 什么是兼容想问题?

    就是同样的代码,在不同的浏览器上显示的页面效果是不一样的

  • 不一样的原因是什么?

    因为各浏览器的内核不同,他们处理同一件事情的时候思路不同

  • 浏览器的兼容性大体分为样式兼容性(css),交互兼容性(javascript),浏览器 hack 三个方面。

    • 1.样式兼容性(css兼容)
    因为历史原因,不同浏览器样式存在差异,我们可以通过Normalize.css抹平差异,也可以定制自己的reset.css比如我们可以通过通配符选择器,全局重置样式
    
    • 2.交互性差异(JavaScript兼容)

      1.事件兼容的问题,我们通常需要会封装一个适配器的方法,过滤事件句柄绑定、移除、冒泡阻止以及默认事件行为处理
      
      2.addEventListener 与 attachEvent 区别:
      
      attachEvent ——兼容:IE7、IE8;不兼容firefox、chrome、IE9、IE10、IE11、safari、opera。
      
      addEventListener——兼容:firefox、chrome、IE、safari、opera;不兼容IE7、IE8
      
      3.集合类对象问题:
      在ie可以使用() 和 []获取集合类对象,Firefox下,只能使用 [ ] 获取集合类对象。
      
      解决方案:统一使用 [] 获取集合类对象。
      
      4.自定义属性问题:
      IE下,可以使用获取常规属性的方法来获取自定义属性,也可以使用 getAttribute() 获取自定义属性;
      
      Firefox下,只能使用 getAttribute() 获取自定义属性。
      
      解决方案:统一通过 getAttribute() 获取自定义属性。
      
      5.const问题
      问题说明:Firefox下,可以使用 const 关键字或 var 关键字来定义常量;IE下,只能使用 var 关键字来定义常量。
      解决方案:统一使用 var 关键字来定义常量。
      
      6.window.location.href 问题:
      问题说明:IE或者Firefox2.0.x下,可以使用 window.location 或 window.location.href;Firefox1.5.x下,只能使用 window.location。解决方案:使用 window.location 来代替 window.location.href 。当然也可以考虑使用 location.replace()方法。
      
      

数组去重的方法

  • 利用Set()+Array.from()

    Set中的元素只会出现一次,即Set中的元素是唯一的。
    
    Array.from() 方法:对一个类似数组或可迭代对象创建一个新的,浅拷贝的数组实例。
    
  • 利用两层循环+数组的splice方法

    通过两层循环对数组元素进行逐一比较,然后通过splice方法来删除重复的元素。此方法对NaN是无法进行去重的,因为进行比较时NaN !== NaN。
    
  • 利用数组的indexOf方法

    新建一个空数组,遍历需要去重的数组,将数组元素存入新数组中,存放前判断数组中是否已经含有当前元素,没有则存入。此方法也无法对NaN去重。
    
    function removeDuplicate(arr) {
    
       const newArr = []
    
       arr.forEach(item => {
    
         if (newArr.indexOf(item) === -1) {
         newArr.push(item)
        }
     })
     return newArr // 返回一个新数组
    }
    
  • 利用数组的includes方法

    用includes方法来判断是否包含重复元素。
    includes()方法:用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false。
    
  • 利用Map()

    Map对象是JavaScript提供的一种数据结构,结构为键值对形式,将数组元素作为map的键存入
    然后结合has()和set()方法判断键是否重复。
    
    使用Map()也可对NaN去重,原因是Map进行判断时认为NaN是与NaN相等的
    
  • 利用sort

    利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。
    

样式的深层组件,但是不能通过class修改?

  • 如果是css,一般采用>>>的写法,示例如下:前面的类一般是后面组件的父类或祖先类。 .aaa >>> .bbb
  • 如果是less或其他预处理语言,需要使用/deep/语法,一般使用如下示例:deep前面可以不用写类名,有时候使用deep可能会报错,可以改成::v-deep,用法与deep一致。
  • 有时候会出现以上三种情况都不能生效的问题,这个时候需要考虑组件的样式是否是在嵌套的组件中了。 如果设置了scoped属性,可能会被影响,如果不生效,可以尝试去掉scoped这一属性,但这并不是一个好的写法

less sass 除了用到它的嵌套功能(嵌套写法[],还会用到他们自身的哪些功能?比如说函数样式计算 或者参数等等) 还会用到什么功能?

Less和Sass在语法上有些共性,比如下面这些:

1、混入(Mixins)——class中的class;

2、参数混入——可以传递参数的class,就像函数一样;

3、嵌套规则——Class中嵌套class,从而减少重复的代码;

4、运算——CSS中用上数学;

5、颜色功能——可以编辑颜色;

6、名字空间(namespace)——分组样式,从而可以被调用;

7、作用域——局部修改样式;

8、JavaScript 赋值——在CSS中使用JavaScript表达式赋值。

swiper怎么在vue中使用

在vue2中swiper5的版本比较稳定好用,7和8是针对vue3

  • 首先下包:npm i swiper@5

  • 在组件中用import导入swiper这个构造函数import swiper from 'swiper'

  • 接着引入swiper.css: import 'swiper/css/swiper.min.css'

  • 容器修改为swiper-container<div class="swiper-container"> 这是swiper5版本的要求

  • 把大小也复制一下(可以自行修改)

  • 然后初始化swiper在vue生命周期mounted中去使用

    mounted(){
        new Swiper ('.swiper-container', {
        direction: 'vertical', // 垂直切换选项
        loop: true, // 循环模式选项
        
        // 如果需要分页器
        pagination: {
          el: '.swiper-pagination',
        },
        
        // 如果需要前进后退按钮
        navigation: {
          nextEl: '.swiper-button-next',
          prevEl: '.swiper-button-prev',
        },
        
        // 如果需要滚动条
        scrollbar: {
          el: '.swiper-scrollbar',
        },
      })        
    }
    
    // 注意不需要创建变量因为创建变量的时候vue会报错
    
  • 默认方向是纵向,我们可以用direction去控制方向

  • 在created里发请求去遍历渲染swiper-slide

  • 在created里的请求是异步的,而在初始化swiper的mounted是同步的这样打开页面是没有轮播图效果的

  • 那么就需要我们更新完毕banner图之后去初始化swiper,在生命周期函数updated中去做初始化,可以拿到swiper-container的真实DOM结构

  • 最后根据官方提供的API文档去修改相应的效果

项目打包测试,遇到报错无法定位到准确行怎么处理

我们在开发过程中代码出现了错误,在浏览器中只能定位到打包后的代码中出现错误的地方,而无法定位到打包前代码的错误位置,这使得我们在查找错误点时相当的麻烦。


然而有了SourceMap,它可以对打包前后的文件进行映射。Sourcemap就是一个信息文件,里面储存着位置信息。也就是说,转换后的代码的每一个位置,所对应的转换前的位置。有了这种映射关系后,出错时,可以在控制台直接显示源代码中出错的位置。

前端大文件上传

  • 普通表单上传:

    使用PHP来展示常规的表单上传是一个不错的选择。首先构建文件上传的表单,并指定表单的提交内容类型为enctype="multipart/form-data",表明表单需要上传二进制数据。
    然后编写index.php上传文件接收代码,使用move_uploaded_file方法即可
    
    注意:
    form表单上传大文件时,很容易遇见服务器超时的问题。通过xhr,前端也可以进行异步上传文件的操作,一般由两个思路。
    
  • 文件编码上传:

    将文件进行编码,然后在服务端进行解码
    主要实现原理就是将图片转换成base64进行传递
    base64编码的缺点在于其体积比原图片更大(因为Base64将三个字节转化成四个字节,因此编码后的文本,会比原文本大出三分之一左右),对于体积很大的文件来说,上传和解析的时间会明显增加。
    
    
    除了进行base64编码,还可以在前端直接读取文件内容后以二进制格式上传
    
  • formData异步上传:

    FormData对象主要用来组装一组用 XMLHttpRequest发送请求的键/值对,可以更加灵活地发送Ajax请求。可以使用FormData来模拟表单提交。
    
    let files = e.target.files // 获取input的file对象
    let formData = new FormData();
    formData.append('file', file);
    axios.post(url, formData);
    
  • iframe无刷新页面

    让用户体验异步上传文件的感觉,可以通过framename指定iframe来实现。把form的target属性设置为一个看不见的iframe,那么返回的数据就会被这个iframe接受,因此只有该iframe会被刷新,至于返回结果,也可以通过解析这个iframe内的文本来获取。
    
  • 大文件切片上传

    编码方式上传中,在前端我们只要先获取文件的二进制内容,然后对其内容进行拆分,最后将每个切片上传到服务端即可。
    在JavaScript中,文件FIle对象是Blob对象的子类,Blob对象包含一个重要的方法slice,通过这个方法,我们就可以对二进制文件进行拆分。
    

组件封装后涉及到组件传值的操做,怎么做?

父组件向子组件传值

父组件通过属性的方式向子组件传值,子组件通过 props 来接收。
1.在父组件的子组件标签中绑定自定义属性
// 父组件
<user-detail :myName="name" />
2.在子组件中使用props(可以是数组也可以是对象)接收即可。可以传多个属性。

子组件向父组件传值

1.子组件绑定一个事件,通过 this.$emit() 来触发
2.通过 $parent / $children 或 $refs 访问组件实例 (这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。)
注:这种方式的组件通信不能跨级
3.$attrs / $listeners
- 如果子组件想被绑定点击的DOM不是根组件,那么需要绑定$listeners(this.$listeners拿到的是父组件传递的原生事件)需要在对应的DOM标签上添加v-on="$listeners"
- this.$attes拿的是父组件除了子组件props定义之外的属性和值

兄弟组件之间传值

1.还是通过 $emit 和 props 结合的方式
在父组件中给要传值的两个兄弟组件都绑定要传的变量,并定义事件
2.通过一个 空 vue 实例
创建一个 EventBus.js 文件,暴露一个 vue 实例
步骤:
import Vue from 'Vue'  
export default new Vue()
在要传值的文件里导入这个空 vue 实例,绑定事件并通过 $emit 触发事件函数
(也可以在 main.js 中全局引入该 js 文件,我一般在需要使用到的组件中引入)
在接收传值的组件中也导入 vue 实例,通过 $on 监听回调,回调函数接收所有触发事件时传入的参数
3.使用 vuex

多层父子组件通信

有时需要实现通信的两个组件不是直接的父子组件,而是祖父和孙子,或者是跨越了更多层级的父子组件
这时就需要用到 vue 提供的更高阶的方法:provide/inject。(依赖注入)
允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深
简单来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量,不管组件层级有多深,在父组件生效的生命周期内,这个变量就一直有效。
// 父组件
export default {
  provide: { // 它的作用就是将 **name** 这个变量提供给它的所有子组件。
    name: 'Jack'
  }
}
// 子组件
export default {
  inject: ['name'], // 注入了从父组件中提供的name变量
  mounted () {
    console.log(this.name);  // Jack
  }
}
注意:provide 和 inject 绑定并不是可响应的。即父组件的name变化后,子组件不会跟着变。

你认为什么样的情况会封装一个组件?

我认为一段代码在项目中出现两次就要开始考虑是否应该进行封装,出现三次就肯定要封装组件,大到一个页面,一个组件,小到一个函数和一个css样式

封装出来的组件必须具有高性能,低耦合的特性

封装组件的好处:

  • 降低系统各个功能的耦合性
  • 提高了前端工程化
  • 代码维护难度降低
  • 开发效率以及开发成本的提升

写好的页面发现样式不生效,你从哪几个方面来着手处理这个问题?

首先,我会清除缓存,重启浏览器吗,如果无效在排查是否确认关联了样式表、选择器是否写错了。关联位置,样式名称是否正确,是否有语法错误 样式表编码格式是否utf-8,是否有语法错误、是否样式被层叠了

vue-loader 是什么?使用它的用途有哪些?

答:解析.vue 文件的一个加载器,跟 template/js/style 转换 成 js 模块。

用途:js 可以写 es6、style 样式可以 scss 或 less、template 可以加 jade

详细说一下你对生命周期的理解?

答:总共分为 8 个阶段创建前/后,载入前/后,更新前/后, 销毁前/后

创建前/后:在 beforeCreated 阶段,vue 实例的挂载元素$el 和数据对象 data 都为 undefined,还未初始化。在 created 阶段,vue 实例的数据对象 data 有了,$el 还没有 。

载入前/后:在 beforeMount 阶段,vue 实例的$el 和 data 都初始化了,但还是挂载之前为虚拟的dom节点,data.message 还未替换。在mounted阶段,vue实例挂载完成,data.message 成功渲染 。

更新前/后:当 data 变化时,会触发 beforeUpdate 和 updated 方法 。

销毁前/后:在执行 destroy 方法后,对 data 的改变不会再 触发周期函数,说明此时 vue 实例已经解除了事件监听以及 和 dom 的绑定,但是 dom 结构依然存在 。

自定义指令(v-check、v-focus)的方法有哪些?它有 哪些钩子函数?还有哪些钩子函数参数?

答:

全局定义指令:

在 vue 对象的 directive 方法里面有两个 参数,一个是指令名称,另外一个是函数。

组件内定义指令:

directives 钩子函数:bind(绑定事件触发)、inserted(节点插入的时候 触发)、update(组件内相关更新) 钩子函数参数:el、binding

vue-router 有哪几种导航钩子?

三种

  • 第一种是全局导航钩子:router.beforeEach(to,from,next) 作用:调转前进行判断拦截
  • 第二种:组件内的钩子
  • 第三种:单独路由独享组件

简述一下Vuex的原理和使用方法

Vuex的数据是单向流动的,一个应用可以看作是由三部分组成:View、Actions、State,数据的流动也是从View-->Actions-->State-->View以此达到数据的单向流动

但是项目较大的,组件嵌套过多的时候,多组件共享同一个State会在数据传递时出现很多问题。Vuex就是为了解决这些问题而产生的

Vuex可以看作项目中所有组件的数据中心,我们将所有组件中共享的State抽离出来,任何组件都可以访问和操作我们的数据中心

Vuex的组成:一个实例化的Vuex.Store由state、mutations、和actions三个属性组成

  • State中保存着共享数据
  • 改变state中的数据只有通过mutations中的方法,且mutations中的方法必须是同步的
  • 如果要写异步的方法,需要在actions中,并通过commit到mutations中进行state中数据的更改

什么是TCP?什么是TCP连接的三次握手?

TCP即传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通讯协议。

TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种断点我们叫作套接字(socket),它的定义为端口号拼接到IP地址即构成了套接字,例如,若IP地址为192.3.4.16 而端口号为80,那么得到的套接字为192.3.4.16:80。

三次握手:

三次握手的原文是 three-way handshake,整个名词的可以翻译为:需要三个步骤才能建立握手/连接的机制。当然,三次握手也可以叫 three-message handshake,通过三条消息来建立的握手/连接。

进行三次握手的主要作用就是为了确认双方的接收能力和发送能力是否正常、指定自己的 初始化序列号(Init Sequense Number, ISN) 为后面的可靠性传输做准备。

1)第一次握手:客户端向服务端发送一个 SYN 报文(SYN = 1),并指明客户端的初始化序列号 ISN(x),即图中的 seq = x,表示本报文段所发送的数据的第一个字节的序号。此时客户端处于 SYN_Send 状态。

SYN-SENT :在发送连接请求后等待匹配的连接请求

2)第二次握手:服务器收到客户端的 SYN 报文之后,会发送 SYN 报文作为应答(SYN = 1),并且指定自己的初始化序列号 ISN(y),即图中的 seq = y。同时会把客户端的 ISN + 1 作为确认号 ack 的值,表示已经收到了客户端发来的的 SYN 报文,希望收到的下一个数据的第一个字节的序号是 x + 1,此时服务器处于 SYN_REVD 的状态。

SYN-RECEIVED:在收到和发送一个连接请求后等待对连接请求的确认

3)第三次握手:客户端收到服务器端响应的 SYN 报文之后,会发送一个 ACK 报文,也是一样把服务器的 ISN + 1 作为 ack 的值,表示已经收到了服务端发来的的 SYN 报文,希望收到的下一个数据的第一个字节的序号是 y + 1,并指明此时客户端的序列号 seq = x + 1(初始为 seq = x,所以第二个报文段要 +1),此时客户端处于 Establised 状态。

服务器收到 ACK 报文之后,也处于 Establised 状态,至此,双方建立起了 TCP 连接。

一个页面从输入URL到页面加载显示完成,这个过程中都发生了什么

  1. 浏览器查找域名对应的IP地址(DNS查询:浏览器缓存–>系统缓存–>路由器缓存–>ISP DNS缓存–>根域名服务器)
  2. 浏览器向web服务器发送一个HTTP请求(TCP三次握手)
  3. 服务器301重定向(从http://baidu.com)重定向到(http://www.baidu.com)
  4. 浏览器跟踪重定向地址,请求另一个带www的网址
  5. 服务器处理请求(通过路由读取资源)
  6. 服务器返回一个HTTP响应(报头中把Content-type设置为‘text/html’)
  7. 浏览器进行DOM树构建
  8. 浏览器发送请求获取嵌在HTML中的资源(图片、音频、视频、CSS、JS等)
  9. 浏览器显示完成页面
  10. 浏览器发送异步请求

$route 和$router 的区别

$router 为 VueRouter 实例,想要导航到不同 URL, 则使用 $router.push 方法

$route 为当前 router 跳转对象里面可以获 取 name 、 path 、 query 、 params

对vue生命周期的理解

vue实例有一个完整的生命周期,生命周期也就是指一个实例从开始创建到销毁的过程

beforeCreated() 在实例创建之间执行,数据未加载状态

created() 在实例创建、数据加载后,能初始化数据,dom 渲染之前执行

beforeMount() 虚拟 dom 已创建完成,在数据渲染前最后一 次更改数据

mounted() 页面、数据渲染完成,真实 dom 挂载完成

beforeUpadate() 重新渲染之前触发

updated() 数据已经更改完成,dom 也重新 render 完成,更 改数据会陷入死循环

beforeDestory() 和 destoryed() 前者是销毁前执行(实例仍 然完全可用),后者则是销毁后执行

vue如何去除url中的#

vue-router默认使用hash模式,所以在路由加载的时候,项目中的url会自带#。如果不想使用#,可以使用vue-router的另一种模式history模式

new Router({
    mode:'history',
    routesL[]
})

需要注意的是,当我们启用 history 模式的时候,由 于我们的项目是一个单页面应用,所以在路由跳转的 时候,就会出现访问不到静态资源而出现 404 的情 况,这时候就需要服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该 返回同一个 index.html

对比 jQuery ,Vue 有什么 不同

jQuery专注视图层,通过操作DOM去实现页面的一些逻辑渲染,
Vue专注数据层,通过数据的双向绑定,最终表现在DOM层面,减少了DOM操作
Vue实现了组件化思想,使得项目子集职责清晰,提高了开发效率,方便重复利用,便于协同开发

请求拦截器:

作用:统一给有权限的接口注入请求头token

添加判断条件:

如果用户没有登录,token值为null,报错,所以要添加判断条件if(user && user.token){} === if(null && null.token) 如果为null 则退出判断条件

在config配置项里添加headers请求头

在请求拦截器添加headers后就可以把api/user.js中的headers注释掉了

无感刷新(响应拦截器)

作用:无感刷新、统一处理错误、统一对数据进行过滤

短token:用来发请求,在请求头里携带,一般两小时过期

refresh_token:长token 用来获取新的短token 一般14天过期

能不能把短token过期时间加长 比如14天?

答:不能,因为涉及到安全问题,防止token被盗 (只有两个一起盗取才会泄漏信息)我们可以对登录密码加密—md5.js加密,(MD5加密本质是一个插件,调用他的方法就可以生成加密后的代码) 我们需要和后端配合(后端解密)

使用长token的思路:

  1. 用户发请求,后端返回401,说明token过期 这时要统一监测后端返回的401代码
  2. 监测到401报错,就拿长token refresh_token 发请求获取最新的token
  3. 自动的再发一次用户上次发的请求
  4. 添加响应拦截器
    • 主要作用:
      • 无感刷新
      • 对后端返回的数据进行统一的处理
      • 对后端数据的错误进行统一的处理

使用长token的思路(具体步骤):

  1. 当用户发请求时,如果后端返回401,就说明token过期 这时我们要统一监测后端返回的401代码

    • 我们需要添加响应拦截器,在拦截器里的报错函数中监测判断token是否过期,代码如下:

      if (error.response.status === 401) {
               console.log('token过期')
      }
      
  2. 监测到401报错,就拿长token refresh_token 发请求获取最新的token

    • 注意点:不能用request发,会死循环,因为request设置了请求拦截器,会注入老token (前提是自己封装了request)代码示例如下:

      // 用axios发请求
          const { data } = await axios({
            url: 'http://toutiao.itheima.net/v1_0/authorizations',
            method: 'PUT',
            headers: {
              Authorization: `Bearer ${store.state.token.refresh_token}` /* 怎么拿refresh_token */
            }
          })
      
  3. 接着用新token存到vuex中,存到本地存储,在js文件中不能用辅助函数 ,利用解构赋值覆盖旧token ,代码如下:

     store.commit('setUser', { ...store.state.token, token: data.data.token })
    
  4. 自动的再发一次用户上次发的请求

    return request(error.config)
    

完整代码如下:

// 添加响应拦截器
request.interceptors.response.use(function (response) {
  // 2xx 范围内的状态码都会触发该函数。
  // 对响应数据做点什么
  return response
}, async function (error) {
  /* //监测token过期 */
  if (error.response.status === 401) {
    // console.log('token过期')
    // 发请求,刷新token
    // 注意:不能用request发,会死循环,因为request设置了请求拦截器,会注入老token
    // 用axios发请求
    const { data } = await axios({
      url: 'http://toutiao.itheima.net/v1_0/authorizations',
      method: 'PUT',
      headers: {
        Authorization: `Bearer ${store.state.token.refresh_token}` /* //怎么拿refresh_token */
      }
    })
    // 新token存到vuex中,存到本地存储,在js文件中不能用辅助函数
    // 利用解构赋值覆盖旧token
    store.commit('setUser', { ...store.state.token, token: data.data.token })
    // 再发一次上次发送过得请求(error里有上一次发送的地址和错误的token)
    return request(error.config)
  }
  // 超出 2xx 范围的状态码都会触发该函数。
  // 对响应错误做点什么
  return Promise.reject(error)
})

请简述vuex的使用原理

数据单向流动

一个应用可以看作是由上面三部分组成: View, Actions,State,数据的流动也是从 View => Actions => State =>View 以此达到数据的单向流动.

但是项目较大的, 组件嵌套过多的时候, 多组件共享同一个 State 会在数据传递时出现很多问题.Vuex 就是为了解决这些问题而产生的.

Vuex 可以被看作项目中所有组件的数据中心,我们将所有组件中共享的 State 抽离出来,任何组件都可以访问和操作我们的数据中心

Vuex 的组成:一个实例化的 Vuex.Store 由 state, mutations 和 actions 三个属性组成:

  • state 中保存着共有数据
  • 改变 state 中的数据有且只有通过 mutations 中的方法,且 mutations 中的方法必须是同步的
  • 如果要写异步的方法,需要些在 actions 中, 并通过 commi t 到 mutations 中进行 state 中数据的更

请列举出 3 个 Vue 中常用的生命周期钩子函数

  • created: 在实例创建、数据加载后,能初始化数据,dom 渲染之前执行
  • mounted: 页面、数据渲染完成,真实 dom 挂载完成
  • activated::keep-alive 组件激活时调

<keep-alive></keep-alive> 的作用是什么?

<keep-alive></keep-alive> 包裹动态组件时,会缓存不活 动的组件实例,主要用于保留组件状态或避免重新渲染。

大白话: 比如有一个列表和一个详情,那么用户就会经常执 行打开详情=>返回列表=>打开详情…这样的话列表和详情 都是一个频率很高的页面,那么就可以对列表组件使用 <keep-alive></keep-alive>进行缓存, 这样用户每次返回列表的时候,都能从缓存中快速渲染,而 不是重新渲染

v-show 和 v-if 指令的共同点和不同点?

  • v-show 指令是通过修改元素的 displayCSS 属性让其显 示或者隐藏
  • v-if 指令是直接销毁和重建DOM 达到让元素显示和隐藏 的效果(注意:v-if 可以实现组件的重新渲染)

vue挂载和卸载父子组件生命周期钩子执行顺序

执行顺序:父组件先创建,然后子组件创建;子组件先挂载,然后父组件挂载,

即“父beforeCreate-> 父create -> 子beforeCreate-> 子created -> 子mounted -> 父mounted”。

加载渲染过程 :

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

销毁过程 :

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

为什么Vue中的v-if不能和v-for一起在同一个元素上使用

原因:

v-for比v-if的优先级高,这意味着 v-if 将分别重复运行于每个 v-for 循环中 ,如果在同一个元素上使用,会带来性能上的浪费(每次渲染都会先循环在进行条件判断)

如何避免:

在外层嵌套template(页面渲染而不生成DOM节点),在这一层进行v-if判断,然后在内部进行v-for循环

<template v-if="isShow">
    <p v-for="item in items">
</template>

defer和async的区别,以及它们的加载和执行时机

给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行

Vuex

使用vuex的核心概念

1)store

vuex 中最关键的是 store 对象,这是 vuex 的核心。可以说,vuex 这个插件其实就是一个 store 对象,每
个 vue 应用仅且仅有一个 store 对象。

(1)创建store

const store = new Vuex.Store({...});

可见,store是Vuex.Store这个构造函数new出来的实例。在构造函数中可以传一个对象参数。这个参数中可以包含5个对象:

  • state – 存放状态
  • getters – state的计算属性
  • mutations – 更改状态的逻辑,同步操作
  • actions – 提交mutation,异步操作
  • mudules – 将store模块化

关于store,需要先记住两点:

  • store 中存储的状态是响应式的,当组件从store中读取状态时,如果store中的状态发生了改变,
    那么相应的组件也会得到更新;
  • 不能直接改变store中的状态。改变store中的状态的唯一途径是提交(commit)mutations。这样使
    得我们可以方便地跟踪每一个状态的变化。

作用:

管理公共数据,可以是在组件之间共享

特点:

  • 数据是响应式的

数据怎么存储

vuex + 本地存储,vuex是响应式,本地存储可以持久化

要熟练两种写法:

原因:辅助函数的写法只能写在组件里不能写在.js文件中,原始写法都可以

state:存储共享状态的数据

组件使用state里的数据:

  • 原始方法

    • 在插值表达式里写 {{$store.state.数据名}}
  • 辅助函数

    • 在组件里引入mapState方法

      • import { mapState} from "vuex";
    • 在计算属性中使用mapState方法,…mapState([‘’,]) 里面的名字要对应

      •     computed: {
              ...mapState(['num','count','age']) //返回的是一个对象
               
            }
        
  //相当于
computed :{
    num(){
        return this.$store.state.num
    }
}

mutations:修改state数据

state数据的修改只能通过mutations,并且mutations必须是同步更新,目的是形成数据快照

组件里使用mutations修改过的数据

  • mutations对象方法里有两个参数
  • 第一个参数就是state 指的是当前vuex中的state对象
  • 第二个参数是payload
  • 原始方法

    • 在methods 中调用 使用this.$store.commit('方法名',payload值)

      • //在组件中使用    
        methods: {
                //点击触发
                btn(){
                    // 调用mutations
                    // 载荷就是在调用mutations时传递的参数
         //调用mutations方法,第一个参数是方法名,第二个参数就是payload           
                    this.$store.commit('addCount',1)
                }
            }
        
        //在router/index.js
            mutations: {
                // 载荷就是在调用mutations时传递的参数
                addCount(state, payload) {
                    state.num += payload
                }
            }
        
  • 用辅助函数法

    • 在组件里引入mapMutations 方法

      • import { mapMutations } from "vuex";
    • 要在methods 方法里调用mapMutations

      • methods:{
            ...mapMutations(['修改state数据的方法名(addCount)'])
        }
        
        //在template里绑定事件触发
        <button @click="addCount(100)">+100(辅助函数)</button> (方法名就是引入过来的名字)
        //注意:这里不传参数的话点击之后传入的就是事件参数对象 vue中的方法第一个参数默认是$event(事件参数)
        
        //也可以在方法里调用mapMutations里的函数名
        btn(){
            this.addCount(this.a) //括号里是对应的payload值,可以传递多个 在data函数里声明数据
        }
        
  • 载荷payload

    •     mutations: {
              // 载荷就是在调用mutations时传递的参数
              addCount(state, payload) {
                  state.num += payload
              }
          }
      
    • <button @click="addCount(100)">+100(辅助函数)</button> 100就是payload的值

actions:负责进行异步操作

与mutations一样里面也是一个个的方法

actions做异步操作,异步请求数据 必须经过mutations

  • 在store/index.js中的actions配置项中操作

    • actions: {
       //  获取异步的数据 context表示当前的store的实例 可以通过 context.state 获取状态 也可以通过context.commit 来提交mutations, 也可以 context.diapatch调用其他的action
         getAsyncCount (context) {
           setTimeout(function(){
             // 一秒钟之后 要给一个数 去修改state
             context.commit('addCount', 123)
           }, 1000)
         }
      } 
      
  • 原始方法

    • addAsyncCount () {
          this.$store.dispatch('getAsyncCount')
      }
      
  • 辅助函数 -mapActions

actions也有辅助函数,可以将action导入到组件中

import { mapActions } from 'vuex'
//在methods方法中操作
methods: {
    ...mapActions(['getAsyncCount'])
}

<button @click="getAsyncCount(222)">异步调用(辅助函数)</button>
//注意:这里不传参数的话点击之后传入的就是事件参数对象 默认是$event

getters :存放了vuex中的所有计算方法

  • 原始方式:

    • 在store/index.js中操作 与state、mutations、actions同级

      •     getters: {
                // Es6写法
                filterList: state => state.list.filter(item => item > 5)
            }
        
    • <div>{{ $store.getters.filterList }}</div>

  • 辅助函数

    •     computed:{
            // 将vuex里的计算方法引入到vue组件的计算属性中
            ...mapGetters(['filterList'])
          }
      

模块化

如果把所有的状态都放在state中,当项目变得越来越大的时候,Vuex会变得越来越难以维护

由此,又有了Vuex的模块化

  • **使用模块化:**我们可以在modules中定义一个user模块 模块名字需要见名知意,为了代码的简洁性,我们可以在store文件下创建一个modules/user.js文件,把state getters actions mutations 存放在user.js中
export default {
  state: {
    name: 'zs',
    age: 20
  },
  mutations: {},
  actions: {},
  getters: {}
}
  • 我们在导入的时候名字和模块的名字一样我们就可以使用ES6的语法
import user from './modules/user'

    modules: {
        // 模块名:对象
        user
    }
  • 我们在组件去渲染模块里的数据

    • 我们可以使用传统写法<p>{{ $store.state.user.name }}</p> $store.state.模块名称.属性名来获取
    • 但是这样比较繁琐,我们可以使用之前学过的getters来改变一下
    //在getters中去定义计算属性
    getters:{
        userName: state => state.user.name,
        userAge: state => state.user.age
    }
    

    请注意:这个getters是根级别的getters哦

  • 然后我们在组件中就可以去这样使用

    1.先导入 
    import { mapGetters } from 'vuex'
    2.在计算属性中调用
        ...mapGetters(['newList', 'userAge', 'userName'])
    3.在template去使用
        <p>{{ userName }}</p>
        <p>{{ userAge }}</p>
    

注意:

(这里我们可以也不用快捷访问,直接在组件调用...MapState('user',['token']) ) user就是模块名

分别访问和调用主模块和子模块的数据和方法

注意:

  • 在js文件下我们调用子模块的方法需要加路径(也就是子模块的名字哦)先把store对象导入

    //如:调用子模块actions中的方法
    store.commit('user/actions中的方法',需要传递的参数)
    
    

注意:全局命名空间

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。

这句话的意思是 我们在子模块创建的user模块还是setting模块,它的 action、mutation 和 getter 其实并没有区分,都可以直接通过全局的方式调用

我们需要加入namespaced:true

export default {
  namespaced: true,
  state: {
    list: [1, 300, 244, 500, 200]
  },
  getters: {
    // newList (state) {
    //   return state.list.filter(item => item > 100)
    // }
    // ES6写法
    newList: state => state.list.filter(item => item > 100)
  },
  mutations: {
    modify (state, payLoad) {
      state.list.push(payLoad)
    }
  },
  actions: {
    modifyMutations ({ commit }, payLoad) {
      commit('modify', payLoad)
    }
  }
}

为什么vue中的data是一个函数,而不是一个对象

因为 js 本身的特性带来的,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将data作为一个函数返回一个对象,那么每一个实例的data属性都是独立的,不会相互影响了。

因为组件是用来复用的,且JS里对象是引用关系,如果组件中data是一个对象,那么这样作用域没有隔离,子组件中的data属性值会相互影响,如果组件中data选项是一个函数,那么每个实例可以维护一份被返回对象的独立的拷贝,组件实例之间的data属性值不会互相影响;而newVue的实例,是不会被复用的,因此不存在引用对象的问题。

知识点:

  • 一个vue组件就是一个vue实例。

  • 在JS当中实例是通过构造函数来创建的,每个构造函数可以new出很多个实例,那么每个实例都会继承原型上的方法或属性。

    vue的data数据其实是vue组件原型上的属性,数据存在于内存当中

    vue组件为了保证每个实例上的data数据的独立性,规定了必须使用函数,而不是对象。

    因为使用对象的话,每个实例(组件)上使用的data数据是相互影响的,这当然就不是我们想要的了。对象是对于内存地址的引用,直接定义个对象的话组件之间都会使用这个对象,这样会造成组件之间数据相互影响。

如何解决跨域问题?

相关知识点:

  • 1.通过 jsonp 跨域
  • 2.document.domain + iframe 跨域
  • 3.location.hash + iframe
  • 4.window.name + iframe 跨域
  • 5.postMessage 跨域
  • 6.跨域资源共享(CORS)
  • 7.nginx 代理跨域
  • 8.nodejs 中间件代理跨域
  • 9.WebSocket 协议跨域

具体:

解决跨域的方法我们可以根据我们想要实现的目的来划分。

首先我们如果只是想要实现主域名下的不同子域名的跨域操作,我们可以使用设置 document.domain 来解决。

(1)将 document.domain 设置为主域名,来实现相同子域名的跨域操作,这个时候主域名下的 cookie 就能够被子域名所访问。同时如果文档中含有主域名相同,子域名不同的 iframe 的话,我们也可以对这个 iframe 进行操作。 如果是想要解决不同跨域窗口间的通信问题,比如说一个页面想要和页面的中的不同源的 iframe 进行通信的问题,我们可以使用 location.hash 或者 window.name 或者 postMessage 来解决。

(2)使用 location.hash 的方法,我们可以在主页面动态的修改 iframe 窗口的 hash 值,然后在 iframe 窗口里实现监听函数来实现这样一个单向的通信。因为在 iframe 是没有办法访问到不同源的父级窗口的,所以我们不能直接修改父级窗口的 hash 值来实现通信,我们可以在 iframe 中再加入一个 iframe ,这个 iframe 的内容是和父级页面同源的,所以我们可以 window.parent.parent 来修改最顶级页面的 src,以此来实现双向通信。

(3)使用 window.name 的方法,主要是基于同一个窗口中设置了 window.name 后不同源的页面也可以访问,所以不同源的子页面可以首先在 window.name 中写入数据,然后跳转到一个和父级同源的页面。这个时候父级页面就可以访问同源的子页面中 window.name 中的数据了,这种方式的好处是可以传输的数据量大。

(4)使用 postMessage 来解决的方法,这是一个 h5 中新增的一个 api。通过它我们可以实现多窗口间的信息传递,通过获取到指定窗口的引用,然后调用 postMessage 来发送信息,在窗口中我们通过对 message 信息的监听来接收信息,以此来实现不同源间的信息交换。 如果是像解决 ajax 无法提交跨域请求的问题,我们可以使用 jsonp、cors、websocket 协议、服务器代理来解决问题。

(5)使用 jsonp 来实现跨域请求,它的主要原理是通过动态构建 script 标签来实现跨域请求,因为浏览器对 script 标签的引入没有跨域的访问限制 。通过在请求的 url 后指定一个回调函数,然后服务器在返回数据的时候,构建一个 json 数据的包装,这个包装就是回调函数,然后返回给前端,前端接收到数据后,因为请求的是脚本文件,所以会直接执行,这样我们先前定义好的回调函数就可以被调用,从而实现了跨域请求的处理。这种方式只能用于 get 请求。

(6)使用 CORS(主流) 的方式,CORS 是一个 W3C 标准,全称是"跨域资源共享"。CORS 需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,因此我们只需要在服务器端配置就行。浏览器将 CORS 请求分成两类:简单请求和非简单请求。对于简单请求,浏览器直接发出 CORS 请求。具体来说,就是会在头信息之中,增加一个 Origin 字段。Origin 字段用来说明本次请求来自哪个源。服务器根据这个值,决定是否同意这次请求。对于如果 Origin 指定的源,不在许可范围内,服务器会返回一个正常的 HTTP 回应。浏览器发现,这个回应的头信息没有包含 Access-Control-Allow-Origin 字段,就知道出错了,从而抛出一个错误,ajax 不会收到响应信息。如果成功的话会包含一些以 Access-Control- 开头的字段。 非简单请求,浏览器会先发出一次预检请求,来判断该域名是否在服务器的白名单中,如果收到肯定回复后才会发起请求。

(7)使用 websocket 协议,这个协议没有同源限制。

(8)使用服务器来代理跨域的访问请求,就是有跨域的请求操作时发送请求给后端,让后端代为请求,然后最后将获取的结果发返回。

Map和Object的区别

es6提供了一个Map类,这是新增的一个数据结构,用起来有点像Object

区别:

Object本质上是哈希结构的键值对的集合,它只能用字符串、数字或者Symbol等简单数据类型当作键,这就带来了很大的限制。

Map类继承了Object,并对Object功能做了一些拓展,Map的键可以是任意的数据类型。

二者的区别主要有以下几点:

同名碰撞

我们知道,对象其实就是在堆开辟了一块内存,其实Map的键存的就是这块内存的地址。只要地址不一样,就是两个不同的键,这就解决了同名属性的碰撞问题,而传统的Object显然做不到这一点。

可迭代

Map实现了迭代器,可用for…of遍历,而Object不行。

长度

Map可以直接拿到长度,而Object不行。

有序性

填入Map的元素,会保持原有的顺序,而Object无法做到。

可展开

Map可以使用省略号语法展开,而Object不行。

Map和Set的区别

==Map==是一组键值对的结构,具有极快的查找速度。初始化Map需要一个二维数组,或者直接初始化一个空Map。

Map具有以下方法:

var m = new Map(); // 空Map
m.set('Adam', 67); // 添加新的key-value
m.set('Bob', 59);
m.has('Adam'); // 是否存在key 'Adam': true
m.get('Adam'); // 67
m.delete('Adam'); // 删除key 'Adam'
m.get('Adam'); // undefined

由于一个key只能对应一个value,所以多次对一个key放入value,后面的值会把前面的值冲掉

var m = new Map();
m.set('Adam', 67);
m.set('Adam', 88);
m.get('Adam'); // 88

==Set==也是一组key的集合,与Map类似。但是区别是Set不存储value,并且它的key不能重复。 创建一个Set,需要提供一个Array作为输入,或者直接创建一个空Set:

var s1 = new Set(); // 空Set
var s2 = new Set([1, 2, 3]); // 含1, 2, 3

重复元素会在Set中自动被过滤**(注:数字3和字符串’3’是不同的元素)**:

var s = new Set([1, 2, 3, 3, '3']);
s; // Set {1, 2, 3, "3"}

Set具有以下方法:

//通过add(key)方法可以添加元素到Set中,可以重复添加,但不会有效果
s.add(4);
s; // Set {1, 2, 3, 4}
s.add(4);
s; // 仍然是 Set {1, 2, 3, 4}

//通过delete(key)方法可以删除元素
var s = new Set([1, 2, 3]);
s; // Set {1, 2, 3}
s.delete(3);
s; // Set {1, 2}


add(value):添加某个值,返回Set结构本身。 
delete(value):删除某个值,返回一个布尔值,表示删除是否成功。 
has(value):返回一个布尔值,表示该值是否为Set的成员。 
clear():清除所有成员,没有返回值。

总结:

1、Map 对象是键值对集合,和 JSON 对象类似,但是 key 不仅可以是字符串还可以是其他各种类型的值包括对象都可以成为Map的键 其方法有:set get has delete

var map = new Map();
var obj = { name: '小缘', age: 14 };
map.set(obj, '小缘喵');
map.get(obj); // 小缘喵
map.has(obj); // true
map.delete(obj) ;// true
map.has(obj); // false

2、Set 对象类似于数组,且成员的值都是唯一的

数组的filter、every、flat的作用是什么

filter的作用

创建了一个新数组,新数组中是符合条件的所有元素

every的作用

查询数组中元素是否都满足要求,都满足则返回true,否则返回false

some的作用

查询数组中是否有元素满足要求,有满足则返回true并且不再往下执行,否则返回false

flat的作用

扁平化数组,例如将二维数组转换成一维数组,三维数组转成一维二维数组。接受一个参数,不传入默认是1,表示拉平的层数。

ES6新特性

1.let关键字

let关键字用来声明变量,使用 let声明的变量有几个特点:

  1. 不允许重复声明
  2. 块级作用域
  3. 不存在变量提升(不能在未定义之前使用)
  4. 不影响作用域链

2.const关键字

const 关键字用来声明常量, const声明有以下特点:

  1. 声明必须赋初始值
  2. 标识符一般为大写
  3. 不允许重复声明
  4. 值不允许修改
  5. 块级作用域

3.变量的解构赋值

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。

//1. 数组的结构
const Hua = ['小花','刘花','赵花','宋花'];
let [xiao, liu, zhao, song] = Hua;
// 结构赋值完,可以直接使用
console.log(xiao);
console.log(liu);
console.log(zhao);
console.log(song);

//2. 对象的解构
const zhao = {
    name: '赵本山',
    age: '不详',
    xiaopin: function(){
        console.log("我可以演小品");
    }
};

let {name, age, xiaopin} = zhao;
console.log(name);
console.log(age);
console.log(xiaopin);
xiaopin();

let {xiaopin} = zhao;
xiaopin();

4.模板字符串

模板字符串(template string)是增强版的字符串,用反引号(`)标识 ,特点:

  1. 字符串中可以出现换行符
  2. 可以使用 ${xxx} 形式输出变量

5.简化对象写法

ES6 允许在大括号里面,直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。

6.箭头函数

ES6 允许使用 「 箭头 」(=>)定义函数 。箭头函数的写法,也就两种,省略小括号,省略大括号。(注意:它没有自己的this)

箭头函数的注意点:

  1. 如果形参只有一个,则小括号可以省略(省略小括号)
  2. 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果(省略大括号)
  3. 箭头函数 this指向声明时所在作用域下 this 的值
  4. 箭头函数不能作为构造函数实例化
  5. 不能使用 arguments

7.Symbol

  1. Symbol基本使用
    ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值。它是JavaScript 语言的第七种数据类型,是一种类似于字符串的数据类型。

Symbol使用场景: 给对象添加属性和方法

Symbol特点:

Symbol的值是唯一的,用来解决命名冲突的问题
Symbol值不能与其他数据进行运算
Symbol定义的对象属性不能使用 for…in循环遍历,但是可以使用Reflect.ownKeys来获取对象的所有键名

注: 遇到唯一性的场景时要想到 Symbol

USONB you are so niubility(JavaScript七种数据类型)

U undefined

S string symbol

O object

N null number

B boolean

8.Promise

Promise是 ES6引入的异步编程的新解决方案 。语法上 Promise是一个构造函数,用来封装异步操作并可以获取其成功或失败的结果

Promise构造函数 : Promise (excutor) {}
Promise.prototype.then方法
Promise.prototype.catch独享守卫:

 //实例化 Promise 对象,成功是resolve,失败是reject
const p = new Promise(function(resolve, reject){
    setTimeout(function(){
        // let data = '数据库中的用户数据';
        // resolve(data);

        let err = '数据读取失败';
        reject(err);
    }, 1000);
});

//调用 promise 对象的 then 方法
p.then(function(value){
    console.log(value);
}, function(reason){
    console.error(reason);
})

// 发送ajax请求,接口地址: https://api.apiopen.top/getJoke
const p = new Promise((resolve, reject) => {
    //1. 创建对象
    const xhr = new XMLHttpRequest();
    //2. 初始化
    xhr.open("GET", "https://api.apiopen.top/getJ");
    //3. 发送
    xhr.send();
    //4. 绑定事件, 处理响应结果
    xhr.onreadystatechange = function () {
        //判断
        if (xhr.readyState === 4) {
            //判断响应状态码 200-299
            if (xhr.status >= 200 && xhr.status < 300) {
                //表示成功
                resolve(xhr.response);
            } else {
                //如果失败
                reject(xhr.status);
            }
        }
    }
})

//指定回调
p.then(function(value){
    console.log(value);
}, function(reason){
    console.error(reason);
});

常见 Promise 面试题

  1. Promise 解决了什么问题?
  2. Promise 的业界实现都有哪些?
  3. Promise 常用的 API 有哪些?
  4. 能不能手写一个符合 Promise/A+ 规范的 Promise?
  5. Promise 在事件循环中的执行过程是怎样的?
  6. Promise 有什么缺陷,可以如何解决?

你在项目中遇到什么问题?是怎么解决的

  1. 处理token

  2. 数据驱动视图不是立即的(组件异步渲染拿不到DOM元素)

    解决方案:使用updated钩子函数 this.$nextTick() setTimeOut()

    想要立即操作DOM Vue是异步更新的

    数据更新之后,因为视图更新是异步的,所以需要一个叫this.$nextTick(()=>{可以拿到更新之后的DOM})

    注意点:如果有v-if的话就不可以使用

  3. 关于后端返回数据大数字问题

    js的大数字问题:(超出js安全整数范围)

    后端返回了一些id值,然后我拿id值请求响应的数据,发现请求不到,又和后端去协调,发现我转换成数值型的id和后端给的字符串id不匹配

    一直搞不懂什么原因造成的,后来去百度,翻文档发现是因为js不能精确的表示超过2的53次方的数据好

    我又去找后端,让他把id修改,但是后端表示改不动

    只能自己找解决办法,后来找到一个插件来解决这个问题:npm i json-bigint

    ①导入 import jsonBig from 'json-bigint'

    ②转成数值:const obj =jsonBig.parse(JSON数据) obj.属性名.toString() —>值

    大数字的数据转JSON数据 使用JSON.stringify(JSONBig.parse(jsonStr)) ----->JSON数据 ----->传给后端

手写一个promise

new Promise(function(resolve,reject){
    resolve(data)
}).then(result=>{ // result就是resolve里的data
    return result + 1 
}).then(result=>{
    return result + 1
}).then(result=>{
    // 如果想终止在某个执行链的位置,可以用Promise.reject(new Error())
    return Promise.reject(new Error(result + '失败'))
}).then(result=>{
	// 会直接跳过这个then执行catch里的语句
    return result + 1
}).catch(error=>{
    alert(error)
})


// 手写一个promise
var promise = new Promise((resolve,reject){
    if(操作成功){
        reslove(data)
    }else {
        reject(error)
    }
})
promise.then(result=> {
    // reslove中的data
},result=>{
    // reject中的error
})

Promise.all和Promise.race的区别和使用

Promise.all

比如当数组里的P1,P2都执行完成时,页面才显示。

值得注意的是,返回的数组结果顺序不会改变,即使P2的返回要比P1的返回快,顺序依然是P1,P2

Promise.all成功返回成功数组,

失败返回失败数据,一但失败就不会继续往下走

        let p1 = Promise.resolve('aaa')
        let p2 = Promise.resolve('bbb')
        let p3 = Promise.reject('ccc')
        let p4 = Promise.resolve('ddd')
        Promise.all([p1, p2, p3, p4]).then(res => {
            console.log(res); //返回数组
        }).catch(err => {
            console.log(err);
        })

promise.race()

Promise.race是赛跑的意思,也就是说Promise.race([p1, p2, p3])里面的结果哪个获取的快,就返回哪个结果,不管结果本身是成功还是失败

使用场景:

我们做一个操作可能得同时需要不同的接口返回的数据,为了考虑返回数据的顺序问题,这时我们就可以使用Promise.all

有好几个服务器的好几个接口都提供同样的服务,我们不知道哪个接口更快,就可以使用Promise.race,哪个接口的数据先回来我们就用哪个接口的数据。

v-mode的本质(原理)是什么

v-model本质就是 v-bind :value=“” 和 @input事件的语法糖

当一个组件上面同时有value属性和input自定义事件的时候,且操作的是同一个变量,即可使用v-model来简化

如何修改v-model 自定义的默认的规则属性 通过model进行修改

 model: {
    prop: 'isFollowed',
    event: 'changed'
  }

知识点小记

less语法:@import ’ '; 一定要加引号

在main.js中引入import ,import 之间只允许有注释不许有其他语句

import导入规则:

一、下载的第三方包:import: ‘包名’ (去node_modules去找index.js文件)

二、手动导入:import: ‘./文件名’

推送提交:

git push -u origin master 的含义:

完整写法:git push --set-upstream origin master:master

解析:

master:master 是将本地master提交到远程仓库主分支master

–set-upstream 是对于每个最新的或成功推送的分支,添加上游(跟踪)引用 简写为-u (简单理解为记住本次提交信息,下次提交就可以直接git push)

origin: 就是添加的远程仓库地址

创建实例:

const request = axios.create ({})

==好处:==可以根据不同的路径去请求不同的服务器 ,如果写在main.js中会显得代码臃肿或者这种:axios.defaults.baseURL:会直接把路径写死

懒加载:

好处:提高首屏速度 Vue是单页面(只有一个html)

怎么提升加载速度?

如果使用的是原始导入方式,webpack在打包的时候会把所有的组件打包成1个js文件,在页面刚刷新的时候就把这个js加载完毕了

懒加载:webpack会把懒加载的组件单独打包成一个一个的js文件,在用户跳转到对应的组件的时候才会去加载

具名插槽:

新语法:必须带template标签包裹 v-slot:名字 简写为#名字

旧语法:在具体标签里 slot=“名字”

例:<i slot="名字"></i>

JSON数据:

双引号的格式:属性和值都是带双引号的

json数据不支持undefined和函数

对象和数组转json数据 通过:JSON.stringfly() 相反是:JSON.parse

深拷贝:

  • 正反序列化(缺点:不能含有undefined和函数的数据)
  • 通过递归实现深拷贝

封装请求传参要求:

  • 在data里如果是直接写如下①,在封装的函数里传参就要是②
//①
data(){
    return {
        a:'',
        b:''
    }
}
//②
export const login = (tel,code) => {
  return request({
    method: 'POST',
    url: '/v1_0/authorizations',
    data:{
      mobile:tel,
      code:code
    }
  })
}
  • 精简写法:

在data里写一个对象把数据写到对象里 如下:

  data () {
    return {
      user: {
        mobile: '',
        code: ''
      }
    }
  },
  
  
  //
  export const login = data => {
  return request({
    method: 'POST',
    url: '/v1_0/authorizations',
    data
  })
}

token值相关知识点:

第一次登录成功后服务器返回token值

为了防止刷新token值丢失,都需要把数据备份到本地存储window.localStorage.setItem('仓库名字',JSON.stringify())

注意: 本地存储只能存储字符串格式JSON.stringify() 序列化

即存储的时候序列化JSON.stringify()

取的时候反序列化JSON.parse

vuex和本地存储的区别:

vuex:①vuex是响应式数据 ②临时存储

本地存储:

sessionStorage:生命周期-浏览器关闭数据消失,刷新数据不消失,数据是当前页面共享,内存5M,存储数据是JSON数据

localStorage:生命周期-永久,相同浏览器数据共享(同一域名,必须相同主机名),内存大小20M,存储数据是JSON数据

token在你的项目内是怎么样存储的?

一、先存vuex 二、存本地存储

解构的属性名必须和response里的属性名一致{data}

密码加密(md5.js)

如果后端需要密码加密,就需要去下载这个插件

本地存储如何做时效处理?

①存token—>额外的时间戳( timer: +new Date() )

②用token的时候先拿到当前的时间戳 - 存储里面的时间戳 / 1000 / 60 (分) > 时间差

③如果超时了,跳转页面让用户重新登录,如果没有超时,正常使用token

在vue中的css使用图片路径:background: url("~@/assets/banner.png");

路由的跳转:

name和path都是路由的跳转,但是name更好维护

点击登录跳转到另一个页面使用编程式:@click="$router.push('/login')" 这是简写方法

具体写法:

this.$router.push({
    path:'/login',
    ...需要传递的参数
})

跳转完后需要回到另一个页面:$router.back() 或者:$router.go(-1) -1回退一次

差值表达式里不需要写this

什么是单页面应用(SPA ):

单页面应用(SPA)的核心思想之一,就是更新视图而不重新请求页面,简单来说,它在加载页面时,不会加载整个页面,只会更新某个指定的容器中的内容。对于大多数单页面应用,都推荐使用官方支持的vue-router。

其中网易音乐中就使用到了单页面应用

Vue 的路由实现模式:hash 模式和 history 模式

hash模式:

vue-router默认为hash模式,使用URL的hash来模拟一个完整的URL,当URL改变时,页面不会重新加载。#就是hash符号,中文名为哈希符或者锚点,在hash符号后的值称为hash值。

路由的hash模式是利用了window 可以监听onhashchange事件来实现的,也就是说hash值是用来指导浏览器动作的,对服务器没有影响,HTTP请求中也不会包括hash值,同时每一次改变hash值,都会在浏览器的访问历史中增加一个记录,使用“后退”按钮,就可以回到上一个位置。所以,hash模式是根据hash值来发生改变,根据不同的值,渲染指定DOM位置的不同数据。

history模式:

hash模式的URL中会自带#号,影响URL的美观,而history模式不会出现#号,这种模式充分利用了history.pushState()来完成URL的跳转,而且无须重新加载页面。使用history模式时,需要在路由规则配置中增加mode:‘history’,实例代码如下:

// main.js 文件
const router = new VueRouter ({
    mode:'history',
    routes: [...]
})

HTML5中history有两个新增的API,分别是history.pushState()history.replaceState() 可以对浏览器历史记录栈进行修改,以 及 popState 事件的监听到状态变更 .

它们都接收3个参数,即状态对象(state object)、标题(title)和地址(URL)。下面我们就简单介绍这3个参数的含义。

(1)状态对象(state object):一个JavaScript对象,与用pushState()方法创建的新历史记录条目关联。

(2)标题(title):FireFox浏览器目前会忽略该参数。为了安全性考虑,建议传一个空字符串。或者也可以传入一个简短的标题,标明将要进入的状态。

(3)地址(URL):新的历史记录条目的地址。

vue全家桶是指什么?有哪些东西?

Vue全家桶一般来说指的是脚手架vue-cli、路由vue-Router、状态管理模式vuex、Axios、elementUI等ui框架和打包工具webpack

命令包:

npm i -g nrm —切换镜像源

npm ls 查看

npm i 装包

或者 npm i -g cnpm

js文件里怎么访问vuex的数据?

导入vuex的仓库的store

在组件内修改全局的外来组件样式,需要加/deep/(必须是less语法)(在scss语法中用::v-deep)

JSON.parse()是反序列化,是将json格式的字符串转化为对象 (json格式字符串必须是双引号,其属性和值都是双引号包裹) 报错的一种比如:JSON.parse('sfdsfsa') 这种用单引号包裹的就是错误的json格式 就会报错

为什么列表滚动会相互影响?

因为他们并不是在自己内部滚动,而是整个body页面在滚动。

记住列表的滚动位置:
方法:
让每个标签内容文章列表产生自己的滚动容器,这样就不会相互影响了

如何给文章列表产生自己的滚动容器:
设置固定高度,height:xxx ; (如果没有效果,可以考虑用视口单位vh,vw 他们不受父级的影响)

.article-list {
height: xxx ;
overflow-y:auto;  //垂直溢出滚动
}

使用解构的方式添加数据:

this.list = [...this.list, ...data.data.results] 把新的数据追加到尾部

给标签内容添加高度小技巧

给局部内容设置高度,在任何设备下都可以适应

height: calc(100vh - 274px);
  overflow-y: auto;

过滤器在全局和局部怎么去使用?

配置好全局的dayjs后,要在全局使用的话一定要导出

下拉刷新数据会遇到留白的情况:

这是因为数据不够,数据撑不开内容得高度

解决办法:需要把数据填满内容得高度

如何处理相对时间

Day.js (opens new window)是一个轻量的处理时间和日期的 JavaScript 库,和 Moment.js (opens new window)的 API 设计保持完全一样

  1. 安装
npm i dayjs
  1. 创建 utils/dayjs.js
import Vue from 'vue'
import dayjs from 'dayjs'

// 加载中文语言包
import 'dayjs/locale/zh-cn'

import relativeTime from 'dayjs/plugin/relativeTime'

// 配置使用处理相对时间的插件
dayjs.extend(relativeTime)

// 配置使用中文语言包
dayjs.locale('zh-cn')

// 全局过滤器:处理相对时间
Vue.filter('relativeTime', value => {
  return dayjs().to(dayjs(value))
})
  1. 在 main.js 中加载初始化
import './utils/dayjs'
  1. 使用
<span>{{ article.pubdate | relativeTime }}</span>

拓展如何在组件局部使用

首先要把配置好的dayjs默认导出

export default dayjs

接着要在组件中导入:

// 组件内使用过滤时间的插件
import dayjs from '@/utils/dayjs'

然后再methods里定义处理相对时间的函数:

methods: {
    relativeTime (value) { // value就是管道符前面的数据
      return dayjs().to(dayjs(value)) //to为多少年前
    }
  }

最后在需要的地方使用相对时间函数:

<span>{{ article.pubdate | relativeTime }}</span>

计算属性:

计算属性会观测内部依赖数据的变化,如果依赖的数据发生变化,则计算属性会重新执行

用sync修饰符修改父组件的数据

子组件:

用sync修饰符修改父组件的数据

this.$emit('update:active', index)

父组件:

:actives.sync="active"

注意:update后面更新的是 属性名

this.$parent:

在当前组价可以访问父组件的所有数据和方法

拓展知识点:

  1. .sync修饰符: 做父传子数据的时候, 能够实现子组件修改父组件数据功能
    父组件: :属性名.sync = “基本数据类型” 子组件: ①props: {属性名} 接收 ②修改父组件对应数据 this.$emit('update:属性名', 要修改的值)注意不能使用sync操作复杂数据类型,比如:数组、对象)
  2. this.$parent 在当前组件可以访问父组件的所有数据包括方法, 可以在子组件实现修改父组件数据的功能, 还能调用父组件方法
  3. this.$children 是一个数组, 可以拿到当前组件的所有亲儿子组件
  4. this.$refs ①可以拿DOM元素 ②也可以拿组件

搜索框需要做哪些优化?

  1. 防抖:持续输出的事件,只执行一次(最后一次),核心定时器,每一次事件触发1清空上一次的定时器
  2. 搜索历史优化:搜索的内容保存到本地存储,用户每一次搜索的时候,先从本地存储有没有搜过的关键字,如果有直接从本地存取,如果没有,就发请求拿数据渲染

axios的post和get带参数的请求方法是不大一样的。

GET方式带参数的请求是 params

POST方式带参数的请求是data

PATCH也是data

PUT也是data

适配:

自适应、响应式

自适应技术:

一、通过媒体查询切换html的font-size + rem

缺点:要写很多媒体查询,繁琐

二、手淘flexible.js + rem flexible.js通过js监听(通过onresize事件)屏幕尺寸变化,**核心算法:**html的font-size=屏幕宽度/10

三、vw/vh 自适应屏幕宽度高度 视口宽度/10 (视口单位)

使用 postcss-pxtorem (opens new window)将 px 转为 rem:不会转行内样式

rem计算原则:(元素像素)px / 根字号=rem

rem适配原理(重要)

目标:实现在不同宽度的设备中,网页元素尺寸等比缩放效果

rem单位尺寸

  1. 根据视口宽度,设置不同的HTML标签字号
  2. 书写代码,尺寸是rem单位
    • 确定设计稿对应的设备的HTML标签(基准根字号)字号
    • 查看设计稿宽度 → 确定参考设备宽度(视口宽度) → 确定基准根字号(1/10视口宽度)
    • rem单位的尺寸 (N为rem)
      • N * 37.5 = 68 → N = 68 / 37.5
      • rem单位的尺寸 = px单位数值 / 基准根字号
    • rem单位的尺寸 = px单位数值 / 基准根字号 (结果取3位小数)

(设计稿为750px 把一行分为10份,1份为75 ,一般来说750的设计稿要先除2为375 在除10,一份也就是37.5)

Vue 原理

1.组件化基础=>(MVVM模型)

传统组件,知识静态渲染,更新依赖于操作DOM。

Vue的核心理念是数据驱动的理念,所谓的数据驱动的理念:当数据发生变化的时候,用户界面也会发生相应的变化,开发者并不需要手动的去修改dom。

优点:

    不需要在代码中去频繁的操作dom了,这样提高了开发的效率,同时也避免了在操作Dom的时候出现的错误。

Vue.js的数据驱动是通过MVVM这种框架来实现的,MVVM 框架主要包含三部分:Model, View, ViewMode

数据驱动视图 - Vue MVVM

MVVM是Model-View-ViewModel缩写,也就是把MVC中的Controller演变成ViewModel。Model代表数据模型,View代表UI组件,ViewModel是View和Model层的桥梁,数据会绑定到ViewModel层并自动将数据渲染到页面中,视图变化的时候通知viewModel层更新数据。

2. Vue的响应式原理

核心实现类:

Observer : 它的作用是给对象的属性添加 getter 和 setter,用于依赖收集和派发更新。
Dep : 用于收集当前响应式对象的依赖关系,每个响应式对象包括子对象都拥有一个 Dep 实例(里面 subs 是 Watcher 实例数组),当数据有变更时,会通过 dep.notify()通知各个 watcher。
Watcher : 观察者对象 , 实例分为渲染 watcher (render watcher),计算属性 watcher (computed watcher),侦听器 watcher(user watcher)三种
Watcher 和 Dep 的关系
watcher 中实例化了 dep 并向 dep.subs 中添加了订阅者,dep 通过 notify 遍历了 dep.subs 通知每个 watcher 更新。

依赖收集(getter)
initState 时,对 computed 属性初始化时,触发 computed watcher 依赖收集
initState 时,对侦听属性初始化时,触发 user watcher 依赖收集
render()的过程,触发 render watcher 依赖收集
re-render 时,vm.render()再次执行,会移除所有 subs 中的 watcer 的订阅,重新赋值。
派发更新(setter)
组件中对响应的数据进行了修改,触发 setter 的逻辑
调用 dep.notify()
遍历所有的 subs(Watcher 实例),调用每一个 watcher 的 update 方法。

原理:

    当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

    每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。

Object.defineProperty 实现响应式

监听对象,监听数组
复杂对象,深度监听

const obj = {};
const data = {};
const name = 'zhangsan';
Object.defineProperty(data, 'name', {
    get: function () {
        console.log('get');
        return name;
    },
    set: function (newVal) {
        console.log('set');
        obj.name = newVal;
    }
})
console.log(data.name);
data.name = 'lisi';
console.log(obj.name);  

Object.defineProperty 的缺点

深度监听需要递归到底,一次性计算量大
无法监听新增属性、删除属性(要使用 Vue.set Vue.delete)
无法原生监听数组,需要特殊处理【对数组的方法重写,[‘push’, ‘pop’, ‘shift’, ‘unshift’, ‘splice’,‘sort’,‘reverse’] 这几个方法更改数组才会响应式变化,直接更改索引不会响应式改变】

总结:

Vue双向数据绑定的原理:
vue.js 采用数据劫持结合发布-订阅模式,通过 Object.defineproperty 来劫持各个属性的 setter,getter,在数据变动时发布消息给订阅者,触发响应的监听回调。    
    

Vue双向数据绑定的原理(重要)

我们都知道 Vue 是数据双向绑定的框架,双向绑定由三个重要部分构成

  • 数据层(Model):应用的数据及业务逻辑
  • 视图层(View):应用的展示效果,各类UI组件
  • 业务逻辑层(ViewModel):框架封装的核心,它负责将数据与视图关联起来

而上面的这个分层的架构方案,可以用一个专业术语进行称呼:MVVM这里的控制层的核心功能便是 “数据双向绑定” 。自然,我们只需弄懂它是什么,便可以进一步了解数据绑定的原理

理解ViewModel

它的主要职责就是:

  • 数据变化后更新视图
  • 视图变化后更新数据

当然,它还有两个主要部分组成

  • 监听器(Observer):对所有数据的属性进行监听
  • 解析器(Compiler):对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数

Vue实现双向数据绑定是采用数据劫持和发布者-订阅者模式。数据劫持是利用ES5的Object.defineProperty(obj,key,val)方法来劫持data选项中每个属性的getter和setter,在数据变动时发布消息给订阅者,从而触发相应的回调来更新视图

总之就是,在创建Vue实例的时候给传入的data进行数据劫持,同时视图编译的时候,对于使用到data中数据的地方进行创建Watcher对象,然后在数据劫持的getter中添加到发布者对象中,当劫持到数据发生变化的时候,就通过发布订阅模式以回调函数的方式通知所有观察者操作DOM进行更新,从而实现数据的双向绑定。

/**
 * 对Object.defineProperty()进行封装
 */
function defineReactive(obj, key, value) {
    //递归 - 对象的属性仍是对象
    observe(value);
    //变化侦测
    Object.defineProperty(obj, key, {
        get() {
            return value;
        },
        set(newVal) {
            if (newVal !== value) {
                updateView();
                value = newVal;
                observe(newVal)
            }
        }
    })
}

/**
 * 对一个对象所有属性的变化侦测
 */
function observe(target) {
    //非对象,直接返回
    if (typeof target !== 'object') {
        return target;
    }
    //将每个属性转换为getter和setter形式
    for (let key in target) {
        defineReactive(target, key, target[key])
    }
}
//模拟更新视图的方法
function updateView() {
    console.log("更新视图");
}

通过直接调用observe侦测对象属性的变化

存在的问题

  1. 性能较差
  2. 对象上新增属性无法侦测
  3. 改变数组的length属性无法被侦测
当创建 Vue 实例时,vue 会遍历 data 选项的属性,利用 Object.defineProperty 为属性添加 getter 和 setter 对数据的读取进行劫持(getter 用来依赖收集,setter 用来派发更新),并且在内部追踪依赖,在属性被访问和修改时通知变化。

每个组件实例会有相应的 watcher 实例,会在组件渲染的过程中记录依赖的所有数据属性(进行依赖收集,还有 computed watcher,user watcher 实例),之后依赖项被改动时,setter 方法会通知依赖与此 data 的 watcher 实例重新计算(派发更新),从而使它关联的组件重新渲染。

记录鼠标滚动的位置

一级路由跳转会销毁重建,需要给一级路由App.vue 包裹上缓存组件<keep-alive> 会触发两个钩子函数,分别为activated deactivated

可以通过组件内的导航守卫beforeRouteLeave

// 离开这个组件的路由导航守卫
  beforeRouteLeave (to, from, next) {
    // 跳走的时候获取组件的滚动位置
    this.top = this.$refs.article[this.active].$el.scrollTop
    next()
  },
  // 缓存组件激活触发的钩子函数
  activated () {
    if (this.$refs.article) {
      this.$refs.article[this.active].$el.scrollTop = this.top
    }
  }

浏览器的事件循环(JS的执行机制)

浏览器中的事件循环会涉及到一个任务队列(task queue)的概念。所有的异步任务执行完毕后都会将对应的回调函数放到任务队列中,并进行排队依次处理。而任务队列又分为宏任务队列微任务队列,并且只有在主线程的调用栈被清空的时候,才会执行任务队列中的任务,这也就是所谓的JavaScript的运行机制


浏览器宏任务主要有setTimeout()setInterval()
浏览器微任务主要有Promise.then()requestAnimationFrame()、process.nextTick

并且微任务优先级要高于宏任务。当主线程调用栈空闲时,就会检测任务队列中是否有任务要执行,首先看一下微任务队列中是否有任务要执行,如果有则执行微任务队列中的任务,直到微任务队列清空为止,然后接着开始执行宏任务队列中的任务,宏任务清空后,又接着检测微任务队列中是否有任务,如此往复下去形成事件循环

事件循环第一种

  • 首先判断JS是同步还是异步,同步就进入主线程,异步就进入event table(宿主环境中)
  • 异步任务在event table中注册函数,当满足触发条件后,被推入event queue(任务队列)
  • 同步任务进入主线程后一直执行,直到主线程空闲时,才会去event queue中查看是否有可执行的异步任务,如果有就推入主线程中

以上三步循环执行,这就是event loop

事件循环第二种

 setTimeout(function(){
     console.log('A')
 });
 
 new Promise(function(resolve){
     console.log('B');
     for(var i = 0; i < 10000; i++){
         i == 99 && resolve();
     }
 }).then(function(){
     console.log('C')
 });
 
 console.log('D');

上面代码执行结果为:B D C A

解析一下原因为什么执行循序是这样的?

这是因为异步任务又分为宏任务和微任务

  • macro-task(宏任务):包括整体代码script,setTimeout,setInterval
  • micro-task(微任务):Promise.then,process.nextTick
  • 按照这种分类方式:JS的执行机制是 :
  • 执行一个宏任务,过程中如果遇到微任务,就将其放到微任务的【事件队列】里
  • 当前宏任务执行完成后,会查看微任务的【事件队列】,并将里面全部的微任务依次执行完

重复以上2步骤,结合event loop(1) event loop(2) ,就是更为准确的JS执行机制了。

具体执行过程分析为:

首先执行script下的宏任务,遇到setTimeout,将其放到宏任务的【队列】里

遇到 new Promise直接执行,打印"B"

遇到then方法,是微任务,将其放到微任务的【队列里】

遇到同步任务console.log('D') 直接打印 "D"

本轮宏任务执行完毕,查看本轮的微任务,发现有一个then方法里的函数, 打印"C"

到此,本轮的event loop 全部完成。


下一轮的循环里,先执行一个宏任务,发现宏任务的【队列】里有一个 setTimeout里的函数,执行打印"A"

所以执行结果为:B D C A

看一个复杂的例子:

console.log('1');
setTimeout(function() {
 
 
console.log('2');
process.nextTick(function() {
 
console.log('3');
 
 
})
new Promise(function(resolve) {
 
console.log('4');
resolve();
}).then(function() {
 
console.log('5')
})
})
//1 2 4 3 5 

执行过程:

1、宏任务同步代码 console.log(‘1’)

2、setTimeout,加入宏任务 Event Queue,没有发现微任务,第一轮事件循环走完

3、第二轮事件循环开始,先执行宏任务,从宏任务 Event Queue 中读取出 setTimeout 的回 调函数

4、同步代码 console.log(‘2’),发现 process.nextTick,加入微任务 Event Queue

5、new Promise,同步执行 console.log(‘4’),发现 then,加入微任务 Event Queue

6、宏任务执行完毕,接下来执行微任务,先执行 process.nextTick,然后执行 Promise.then

7、微任务执行完毕,第二轮事件循环走完,没有发现宏任务,事件循环结束

总结

事件循环先执行宏任务,其中同步任务立即执行,异步任务加载到对应的 Event Queue 中, 微任务也加载到对应的微任务的 Event Queue 中,所有的同步微任务执行完之后,如果发现微任 务的 Event Queue 中有未执行完的任务,先执行他们这样算是完成了一轮事件循环。接下来查看 宏任务的队列中是否有异步代码,有的话执行第二轮的事件循环,以此类推。

SQL基本语法

-- SELECT * FROM my_db_01.users;
-- 1.查询整个表所有的数据
-- select * from users
-- 2.查询具体的字段名字
-- select username,password from users
-- 3.查询id是1的这个字段 查询的条件写在where后面
-- select * from users where id=1
-- 4.查询users表里username这个字段 模糊查询 Z%表示以z开头的 %Z写在后面后面表示以z结尾的
-- select * from users where username like 'Z%'
-- 5.条件查询
-- select username,password from users where id>1 and status=0
-- 6.排序 desc(降序) asc(升序)
-- select * from users order by id desc
-- select * from users order by id asc
-- 7.添加
-- insert into users(username,password,status) values('zl',111,1)
-- 8.删除
-- delete from users where id=3
-- 9.修改(更新)update 表明 set 字段属性(多个字段用英文逗号隔开) where (修改的字段对应的id) 如果没有where 将修改整个表
-- update users set username='zs',password=555 where id=1
-- 10.函数count(*)
-- select count(*) from users where status=0

watch、methods 和 computed 的区别?

从作用机制上:

  1. methods,watch 和 computed 都是以函数为基础的,但各自却都不同
  2. watch 和 computed 都是以 Vue 的依赖追踪机制为基础的,当某一个数据发生变化的时候,所有依赖这个数据的“相关”数据“自动”发生变化,也就是自动调用相关的函数去实现数据的变动
  3. 对 methods:methods 里面是用来定义函数的,它需要手动调用才能执行。而不像 watch 和 computed 那样,“自动执行”预先定义的函数,相比于 watch / computed,methods 不处理数据逻辑关系,只提供可调用的函数

从性质上:

  1. methods 里面定义的是函数,仍然需要去调用它。
  2. computed计算属性,事实上和 data 对象里的数据属性是同一类的(使用上)。
  3. **watch:**类似于监听机制+事件机制

watch 和 computed 区别

  1. 功能上:computed是计算属性,watch是监听一个值的变化,然后执行对应的回调。
  2. 是否调用缓存:computed中的函数所依赖的属性如果没有发生变化,那么调用当前的函数会从缓存中读取,而watch在每次监听的值发生变化的时候都会执行回调。
  3. 是否调用return:computed中的函数必须要用return返回,watch中的函数不是必须要用return
  4. watch擅长处理的场景:一个数据影响多个数据 -------搜索框。
  5. computed擅长处理的场景:一个数据受多个数据影响 – 使用场景:当一个值受多个属性影响的时候--------购物车商品结算

**watch:**属性监听

  1. watch中的函数名称必须要和data中的属性名一致,因为watch是依赖data中的属性,当data中的属性发生改变的时候,watch中的函数就会执行。
  2. watch中的函数有两个参数,前者是newVal,后者是oldVal。
  3. watch中的函数是不需要调用的。
  4. watch只会监听数据的值是否发生改变,而不会去监听数据的地址是否发生改变。也就是说,watch想要监听引用类型数据的变化,需要进行深度监听。“obj.name”(){}------如果obj的属性太多,这种方法的效率很低,obj:{handler(newVal){},deep:true}------用handler+deep的方式进行深度监听。
  5. 特殊情况下,watch无法监听到数组的变化,特殊情况就是说更改数组中的数据时,数组已经更改,但是视图没有更新。更改数组必须要用splice()或者 s e t 。 t h i s . a r r . s p l i c e ( 0 , 1 , 100 ) − − − − − 修 改 a r r 中 第 0 项 开 始 的 1 个 数 据 为 100 , t h i s . set。this.arr.splice(0,1,100)-----修改arr中第0项开始的1个数据为100,this. setthis.arr.splice(0,1,100)arr01100this.set(this.arr,0,100)-----修改arr第0项值为100。
  6. immediate:true 页面首次加载的时候做一次监听。
  7. 一个数据影响多个数据 — 应用场景:搜索框、表单输入、异步更新、动画
  8. 如果监听的是复杂数据类型内需要开启深度监听deep:true

computed:计算属性

  • 据依赖关系进行缓存的计算,并且只在需要的时候进行更新。
  • 一个计算属性里面可以完成各种复杂的逻辑,最终返回一个结果;计算属性可以依赖多个vue实例的数据,只要其中一个任何一个数据发生变化,计算属性就会重新执行,视图也会更新。除此之外,计算属性还可以依赖其他计算属性和其他实例的数据
  • 一个数据受多个数据影响 ---- 购物车结算 受到单价 数量 还有是否被选中的影响

watch能监听computed的属性吗

watch可以监视computed

  • computed能完成的功能,watch都可以完成
  • watch能完成的功能,computed不一定能完成,如:watch可以进行异步操作

HTTP 与 HTTPS 的区别(必会)

很多人总以为http就是https。其实http和https有着很大的区别的,下面我们来看一下https和http有什么区别?

一:先说一下什么是https和http

https是http的安全版本,也叫超文本安全传输,具有加密传输协议的通道,并且SSL提供了安全加密基础,作用是用于http的传输。

http是一种普通的超文本传输协议,在互联网上,所有的文件都要遵守这个HTTP协议,作用就是实现客户端和服务器的相互请求。

二:然后https和http有什么区别

https和http的区别:

**1、**https的端口是443,而http的端口是80,并且两者的连接方式不同 http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议 ;

**2、**HTTP协议以明文方式发送内容,不提供任何方式的数据加密,举个例子:如果黑客截取了Web浏览器和网站服务器之间的传输报文,就可以直接读取其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:银行卡号卡号、密码等支付信息。 ,为了解决HTTP协议的这一缺陷 HTTPS在HTTP的基础上加入了SSL协议,为浏览器和服务器之间的通信进行加密。 HTTPS协议之所以安全,是因为HTTPS协议对传输的数据进行加密,而加密过程是由非对称加密实现的。

**3、**https是需要到CA申请证书的,而http不需要。

SSL证书是什么?(拓展)

因为HTTP协议以明文方式发送内容,使用HTTP协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL协议 ,用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。 其中S代表安全。security

SSL证书是数字证书的一种,类似于我们驾驶证的电子副本。因为配置在服务器上,也称为SSL服务器证书。 作用就是用于保护网络交易、数据传输和登录。所以SSL逐渐已成为一种规范(就如ECMAscript是js语法的规范)

nextTick的原理

$nextTick 是在下次 DOM 更新循环结束之后执行 延迟回调,在修改数据之后使用 $nextTick,则可以 在回调中获取更新后的 DO

由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick()方法。

vue中有一个较为特殊的API,nextTick。根据官方文档的解释,它可以在DOM更新完毕之后执行一个回调

尽管MVVM框架并不推荐访问DOM,但有时候确实会有这样的需求,尤其是和第三方插件进行配合的时候,免不了要进行DOM操作。而nextTick就提供了一个桥梁,确保我们操作的是更新后的DOM。

应用场景:

  • 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中
    • **原因:**是created()钩子函数执行时DOM其实并未进行渲染。
  • 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作应该放在Vue.nextTick()的回调函数中。
    • 原因:Vue异步执行DOM更新,只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,只会被推入到队列中一次。

总结

以上就是vue的nextTick方法的实现原理了,总结一下就是:

  1. vue用异步队列的方式来控制DOM更新和nextTick回调先后执行
  2. microtask因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
  3. 因为兼容性问题,vue不得不做了microtask向macrotask的降级方案

模块拖拽的原理

拖拽功能主要是用在让用户做一些自定义的动作,比如拖动排序弹出框拖动移动等等,效果还是蛮不错的。下面就来讲一下拖拽的原理:

一、拖拽的流程动作

①鼠标按下
②鼠标移动
③鼠标松开

这里要注意:移动事件要写在按下事件的里面

二、拖拽流程中对应的JS事件

①鼠标按下会触发onmousedown事件

②鼠标移动会触发onmousemove事件

③鼠标松开会触发onmouseup事件

三、实现的原理讲解

拖拽其实是通过获取鼠标移动的距离来实现的,即计算移动前的位置的坐标(x,y)与移动中的位置的坐标(x,y)差值。
当鼠标按下或鼠标移动时,都可以获取到当前鼠标的位置,即移动前的位置与移动中的位置。

还有一点,被拖拽的元素的样式要设置成绝对或相对位置才有效果。

总结:

①在页面中拖拽的原理: 鼠标按下并且移动, 之后松开鼠标

触发事件是鼠标按下 mousedown, 鼠标移动mousemove 鼠标松开 mouseup

③拖拽过程: 鼠标移动过程中,获得最新的值赋值给模态框的left和top值, 这样模态框可以跟着鼠标走了

鼠标在页面的坐标 减去 鼠标在盒子内的坐标, 才是模态框真正的位置。

⑤鼠标按下,我们要得到鼠标在盒子的坐标。

⑥鼠标移动,就让模态框的坐标 设置为 : 鼠标在页面的坐标 减去盒子坐标即可,注意移动事件写到按下事件里面

⑦鼠标松开,就停止拖拽,就是可以让鼠标移动事件解除

核心代码

 		// 鼠标按下获取鼠标在盒子内的坐标
        obj.addEventListener('mousedown', function(e) {
          //获取鼠标在盒子中的坐标=鼠标在页面中的坐标减去盒子在页面中的坐标  
            var x = e.pageX - box.offsetLeft
            var y = e.pageY - box.offsetTop
                // 鼠标移动的时候,把鼠标在页面中的坐标,减去 鼠标在盒子内的坐标就是模态框的left和top值
                // 在全局页面中拖拽时要用document,如果用了obj,事件会来不及触发,导致鼠标脱离
            document.addEventListener('mousemove', move)
		function move(e) {
            box.style.left = e.pageX - x + 'px'
            box.style.top = e.pageY - y + 'px'
        }
        // 鼠标弹起,就让鼠标移动事件移除
        document.addEventListener('mouseup', function() {
            document.removeEventListener('mousemove', move)
        })

内存泄漏

什么是内存泄漏

应用程序不再需要占用内存的时候,由于某些原因,内存没有被操作系统或可用内存池回收,就叫做内存泄漏

哪些操作会造成内存泄漏

1、全局变量引起的内存泄 漏
2、闭包引起的内存泄露
3、被遗忘的定时器或回调
4、dom清空或删除时,事件未清除导致的内存泄漏
5、反复重写同一个属性会造成内存大量占用(但关闭IE后内存会被释放)

6、setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏

JS内存泄漏的解决方式

1、注意程序逻辑,避免“死循环”之类的
2、减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收
3、避免创建过多的对象 原则:不用了的东西要及时归还

new操作符具体干了什么

1、创建一个空对象
2、this指向这个新对象
3、执行构造函数里的代码,为新对象添加属性和方法
4、返回新对象:判断返回值,返回对象就用该对象,没有的话就创建一个对象

堆和栈的区别

  • 栈区(stack):由编译器自动分配和释放,存放函数的参数值、局部变量的值等。其操作方式类似于数据结构中的栈。
  • 堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。它与数据机构中的堆是两回事,分配方式类似于链表。

他们的区别

1申请方式

  • 栈:由系统自动分配。例如在声明函数的一个局部变量int b,系统自动在栈中为b开辟空间。
  • 堆:需要程序员自己申请,并指明大小,在C中用malloc函数;在C++中用new运算符。

2申请后系统的响应

  • 栈:只要栈的剩余空间大于所申请的空间系统将为程序提供内存,否则将报异常提示栈溢出。
  • 堆:操作系统有一个记录空间内存地址的链表,当系统收到程序的申请时,会遍历链表,寻找第一个空间大于所申请空间的堆节点,然后将节点从内存空闲节点链表中删除,并将该节点的空间分配给程序

总结:

(1)heap是堆,stack是栈;

(2)栈的空间由操作系统自动分配/释放,堆上的空间手动分配/释放;

(3)栈空间有限,堆是很大的自由内存区;

(4)C中的malloc函数分配的内存空间即在堆上,C++中对应的是new操作符。 程序在编译对变量和函数分配内存都在栈上进行,且内存运行过程中函数调用时参数的传递在栈上进行。

堆和栈的概念存在于数据结构中和操作系统内存中。

在数据结构中,栈中数据的存取方式为先进后出。而堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。完全二叉树是堆的一种实现方式。

在操作系统中,内存被分为栈区和堆区。

栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。

堆区内存一般由程序员分配释放,若程序员不释放,程序结束时可能由垃圾回收机制回收。
复制代码

如何上传文件

​ 基于服务端的裁切使用 getData 方法获取裁切参数

​ console.log(this.cropper.getData())

​ 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象

  • 首先看接口文档

  • 看请求头:
    如果接口要求 Content-Type 是 application/json
    则传递普通 JavaScript 对象
    axios会自动转为json对象
    
    如果接口要求 Content-Type 是 multipart/form-data
    则你必须传递 FormData 对象
    
    const formData = new FormData()
            formData.append('photo', blob)
    
    // 点击完成时间
    onConfirm () {
      // 基于服务端的裁切使用 getData 方法获取裁切参数
      // console.log(this.cropper.getData())

      // 纯客户端的裁切使用 getCroppedCanvas 获取裁切的文件对象
      this.cropper.getCroppedCanvas().toBlob(async blob => {
        // console.log(blob) // 裁剪后的结果信息
        // 弹出loading页面
        this.$toast.loading({
          message: '保存中...',
          forbidClick: true, // 禁止背景点击
          duration: 0 // 持续展示
        })
        try {
          // 1.创建formData对象
          const obj = new FormData()
          // 2.给对象obj添加key和value
          obj.append('photo', blob)
          // 3.发请求
          const { data } = await updateUserPhoto(obj)
          // 4.更新父组件对象
          this.$emit('update:photo', data.data.photo)
          // 5.关闭弹层
          this.$emit('update:close', false)
          this.$toast.success('修改成功')
        } catch (error) {
          this.$toast.fail('修改失败')
        }
      })
    }

什么是BFC

块格式化上下文(Block Formatting Context,BFC) 是 Web 页面的可视 CSS 渲染的一部分,是块级盒子的布局过程发生的区域,也是浮动元素与其他元素交互的区域。

下列方式会创建块格式化上下文:

  • 根元素(<html>
  • 浮动元素(float 值不为 none
  • 绝对定位元素(position 值为 absolutefixed
  • 行内块元素(display 值为 inline-block
  • 表格单元格(display 值为 table-cell,HTML 表格单元格默认值)
  • 表格标题(display 值为 table-caption,HTML 表格标题默认值)
  • 匿名表格单元格元素(display 值为 tabletable-rowtable-row-grouptable-header-grouptable-footer-group(分别是 HTML table、tr、tbody、thead、tfoot 的默认值)或 inline-table
  • overflow 值不为 visibleclip 的块元素
  • display 值为 flow-root 的元素
  • contain 值为 layoutcontentpaint 的元素
  • 弹性元素(display 值为 flexinline-flex 元素的直接子元素),如果它们本身既不是 flexgrid 也不是 table 容器
  • 网格元素(display 值为 gridinline-grid 元素的直接子元素),如果它们本身既不是 flexgrid 也不是 table 容器
  • 多列容器(column-countcolumn-width (en-US) 值不为 auto,包括column-count1
  • column-span 值为 all 的元素始终会创建一个新的 BFC,即使该元素没有包裹在一个多列容器中 (规范变更, Chrome bug)

格式化上下文影响布局,通常,我们会为定位和清除浮动创建新的 BFC,而不是更改布局,因为它将:

有三种情况会形成外边距重叠:

  1. 相邻的两个元素之间的外边距重叠,除非后一个元素加上clear-fix 清除浮动
  2. 没有内容将父元素和后代元素分开 ,就会出现父块元素和其内后代块元素外边界重叠,重叠部分最终会溢出到父级块元素外面。
  3. 空的块级元素 :当一个块元素上边界margin-top 直接贴到元素下边界margin-bottom时也会发生边界折叠。这种情况会发生在一个块元素完全没有设定边框border、内边距padding、高度height、最小高度min-height 、最大高度max-height 、内容设定为 inline 或是加上clear-fix的时候。

==注意:==有设定float浮动和position=absolute绝对定位的元素不会产生外边距重叠行为。

js 延迟加载的方式有哪些?

js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。 我了解到的几种方式是:

第一种方式是我们一般采用的是将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

第二种方式是给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

第三种方式是给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行

第四种方式是动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

Ajax 是什么? 如何创建一个 Ajax?

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。

创建一个 ajax 有这样几个步骤 :

//1:创建Ajax对象
let xhr = new XMLHttpRequest();

// 2:配置 Ajax请求地址
xhr.open("GET", SERVER_URL, true);

// 3:发送请求xhr.send
xhr.send(null)

//4:监听请求,接受响应xhr.onreadysatechange=function(){
     if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 )
          console.log(xhr.responsetXML)

什么是防抖和节流?

介绍

在 JavaScript 中,防抖和节流其实是一个很重要的概念。主要应用场景就是会频繁触发的事件,比如监听滚动、点赞功能,总不能点一次赞就向后台发送一次数据,这时候就要用到防抖和节流。

防抖和节流的核心就是定时器,我们要知道定时器的一个概念,就是在定时之后,在没触发之前清除定时器,这个定时器方法不会被触发。

防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着 N 秒内函数只会被执行一次(最后一次),如果 N 秒内再次被触发,则重新计算延迟时间。

节流函数的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。

在vue中我们可以使用lodash中的一个_debounce

注意第一个参数是函数,第二个参数是延迟时间

预解析

​ 1. 我们js引擎运行js 分为两步: 预解析 代码执行

​ (1). 预解析 js引擎会把js 里面所有的 var 还有 function 提升到当前作用域的最前面

​ (2). 代码执行 按照代码书写的顺序从上往下执行

​ 2. 预解析分为 变量预解析(变量提升) 和 函数预解析(函数提升)

​ (1) 变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作

​ (2) 函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数

  • 注意
    • 全局变量访问不到局部变量
    • 函数内部加了var关键字就是局部的,形参也是局部的(内部不加var 声明变量赋值也是全局变量)
    • 没有加var关键字或者没有传递参数 此时 a=1 就是全局的

原型链

  • 只要是__proto__原型 都指向原型对象,但是Object.prototype.__proto__ 指向的为空(null)

​ 每一个实例对象都有一个__proto__属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找就形成了原型链

构造函数实例和原型对象三角关系

1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数

原型链和成员的查找机制

__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层层向上查找就会形成一个链式结构,我们称为原型链



查找原则:
	实例对象访问属性:
		先去本身构造函数上去查找,如果本身没有要查找的属性或者方法,会自动去原型对象去查找
		如果原型对象也没有,再去原型对象的原型对象去查找。
		如果都没有返回undefined。 如果找到了停止查找
设置原则:
	给实例对象设置一个属性,本质上是给实例添加属性,并不会修改原型的内容。

b/CSS/min-height) 、最大高度max-height 、内容设定为 inline 或是加上clear-fix的时候。

==注意:==有设定float浮动和position=absolute绝对定位的元素不会产生外边距重叠行为。

js 延迟加载的方式有哪些?

js 的加载、解析和执行会阻塞页面的渲染过程,因此我们希望 js 脚本能够尽可能的延迟加载,提高页面的渲染速度。 我了解到的几种方式是:

第一种方式是我们一般采用的是将 js 脚本放在文档的底部,来使 js 脚本尽可能的在最后来加载执行。

第二种方式是给 js 脚本添加 defer 属性,这个属性会让脚本的加载与文档的解析同步解析,然后在文档解析完成后再执行这个脚本文件,这样的话就能使页面的渲染不被阻塞。多个设置了 defer 属性的脚本按规范来说最后是顺序执行的,但是在一些浏览器中可能不是这样。

第三种方式是给 js 脚本添加 async 属性,这个属性会使脚本异步加载,不会阻塞页面的解析过程,但是当脚本加载完成后立即执行 js 脚本,这个时候如果文档没有解析完成的话同样会阻塞。多个 async 属性的脚本的执行顺序是不可预测的,一般不会按照代码的顺序依次执行

第四种方式是动态创建 DOM 标签的方式,我们可以对文档的加载事件进行监听,当文档加载完成后再动态的创建 script 标签来引入 js 脚本。

Ajax 是什么? 如何创建一个 Ajax?

我对 ajax 的理解是,它是一种异步通信的方法,通过直接由 js 脚本向服务器发起 http 通信,然后根据服务器返回的数据,更新网页的相应部分,而不用刷新整个页面的一种方法。

创建一个 ajax 有这样几个步骤 :

//1:创建Ajax对象
let xhr = new XMLHttpRequest();

// 2:配置 Ajax请求地址
xhr.open("GET", SERVER_URL, true);

// 3:发送请求xhr.send
xhr.send(null)

//4:监听请求,接受响应xhr.onreadysatechange=function(){
     if(xhr.readySate==4&&xhr.status==200 || xhr.status==304 )
          console.log(xhr.responsetXML)

什么是防抖和节流?

介绍

在 JavaScript 中,防抖和节流其实是一个很重要的概念。主要应用场景就是会频繁触发的事件,比如监听滚动、点赞功能,总不能点一次赞就向后台发送一次数据,这时候就要用到防抖和节流。

防抖和节流的核心就是定时器,我们要知道定时器的一个概念,就是在定时之后,在没触发之前清除定时器,这个定时器方法不会被触发。

防抖函数的作用就是控制函数在一定时间内的执行次数。防抖意味着 N 秒内函数只会被执行一次(最后一次),如果 N 秒内再次被触发,则重新计算延迟时间。

节流函数的作用是规定一个单位时间,在这个单位时间内最多只能触发一次函数执行,如果这个单位时间内多次触发函数,只能有一次生效。

在vue中我们可以使用lodash中的一个_debounce

注意第一个参数是函数,第二个参数是延迟时间

预解析

​ 1. 我们js引擎运行js 分为两步: 预解析 代码执行

​ (1). 预解析 js引擎会把js 里面所有的 var 还有 function 提升到当前作用域的最前面

​ (2). 代码执行 按照代码书写的顺序从上往下执行

​ 2. 预解析分为 变量预解析(变量提升) 和 函数预解析(函数提升)

​ (1) 变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作

​ (2) 函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数

  • 注意
    • 全局变量访问不到局部变量
    • 函数内部加了var关键字就是局部的,形参也是局部的(内部不加var 声明变量赋值也是全局变量)
    • 没有加var关键字或者没有传递参数 此时 a=1 就是全局的

原型链

  • 只要是__proto__原型 都指向原型对象,但是Object.prototype.__proto__ 指向的为空(null)

​ 每一个实例对象都有一个__proto__属性,指向的构造函数的原型对象,构造函数的原型对象也是一个对象,也有__proto__属性,这样一层一层往上找就形成了原型链

构造函数实例和原型对象三角关系

1.构造函数的prototype属性指向了构造函数原型对象
2.实例对象是由构造函数创建的,实例对象的__proto__属性指向了构造函数的原型对象
3.构造函数的原型对象的constructor属性指向了构造函数,实例对象的原型的constructor属性也指向了构造函数

原型链和成员的查找机制

__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。

任何对象都有原型对象,也就是prototype属性,任何原型对象也是一个对象,该对象就有__proto__属性,这样一层一层往上找,就形成了一条链,我们称此为原型链;

当访问一个对象的属性(包括方法)时,首先查找这个对象自身有没有该属性。如果没有找到,则会去它的__proto__隐式原型上查找,即它的构造函数的prototype,如果还没有找到就会再在构造函数的prototype的__proto__中查找,这样一层层向上查找就会形成一个链式结构,我们称为原型链



查找原则:
	实例对象访问属性:
		先去本身构造函数上去查找,如果本身没有要查找的属性或者方法,会自动去原型对象去查找
		如果原型对象也没有,再去原型对象的原型对象去查找。
		如果都没有返回undefined。 如果找到了停止查找
设置原则:
	给实例对象设置一个属性,本质上是给实例添加属性,并不会修改原型的内容。

有关前端主流面试官必问超详细面试题(整理完以秃头)持续更新中的更多相关文章

  1. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  2. 在VMware16虚拟机安装Ubuntu详细教程 - 2

    在VMware16.2.4安装Ubuntu一、安装VMware1.打开VMwareWorkstationPro官网,点击即可进入。2.进入后向下滑动找到Workstation16ProforWindows,点击立即下载。3.下载完成,文件大小615MB,如下图:4.鼠标右击,以管理员身份运行。5.点击下一步6.勾选条款,点击下一步7.先勾选,再点击下一步8.去掉勾选,点击下一步9.点击下一步10.点击安装11.点击许可证12.在百度上搜索VM16许可证,复制填入,然后点击输入即可,亲测有效。13.点击完成14.重启系统,点击是15.双击VMwareWorkstationPro图标,进入虚拟机主

  3. Hive SQL 五大经典面试题 - 2

    目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类

  4. 100个python算法超详细讲解:画直线 - 2

    1.问题描述使用Python的turtle(海龟绘图)模块提供的函数绘制直线。2.问题分析一幅复杂的图形通常都可以由点、直线、三角形、矩形、平行四边形、圆、椭圆和圆弧等基本图形组成。其中的三角形、矩形、平行四边形又可以由直线组成,而直线又是由两个点确定的。我们使用Python的turtle模块所提供的函数来绘制直线。在使用之前我们先介绍一下turtle模块的相关知识点。turtle模块提供面向对象和面向过程两种形式的海龟绘图基本组件。面向对象的接口类如下:1)TurtleScreen类:定义图形窗口作为绘图海龟的运动场。它的构造器需要一个tkinter.Canvas或ScrolledCanva

  5. 西安华为OD面试体验 - 2

    西安华为OD面试体验开始投简历技术面试进展工作进展开始投简历去年一整年一直在考研和工作之间纠结,感觉自己的状态好像当时的疫情一样差劲。之前刚毕业的时候投了个大厂的简历,结果一面写算法的时候太拉跨了,虽然知道时dfs但是代码熟练度不够,放在平时给足时间自己可以调试通过,但是熟练度不够那面试当时就写不出来被刷了。说真的算法学到后期我感觉最重要的是熟练度和背板子(对于我这种普通玩家来说),面试题如果一上来短时间内想不出思路就完蛋了。然后由于当时找的工作不是很理想就又想考研了。但是考研是有风险的,我自我感觉自己可能冲不上那个学校,而找工作一个没成可以继续找嘛。本着抱着试试看的态度在boss上投了简历,

  6. H2数据库配置及相关使用方式一站式介绍(极为详细并整理官方文档) - 2

    目录H2数据库入门以及实际开发时的使用1.H2数据库的初识1.1H2数据库介绍1.2为什么要使用嵌入式数据库?1.3嵌入式数据库对比1.3.1性能对比1.4技术选型思考2.H2数据库实战2.1H2数据库下载搭建以及部署2.1.1H2数据库的下载2.1.2数据库启动2.1.2.1windows系统可以在bin目录下执行h2.bat2.1.2.2同理可以通过cmd直接使用命令进行启动:2.1.2.3启动后控制台页面:2.1.3spring整合H2数据库2.1.3.1引入依赖文件2.1.4数据库通过file模式实际保存数据的位置2.2H2数据库操作2.2.1Mysql兼容模式2.2.2Mysql模式

  7. ruby-on-rails - 在 Rails 应用程序的前端获取实时日志 - 2

    在Rails3.x应用程序中,我正在使用net::ssh并向远程pc运行一些命令。我想向用户的浏览器显示实时日志。比如,如果两个命令在net中运行::ssh执行即echo"Hello",echo"Bye"被传递然后"Hello"应该在执行后立即显示在浏览器中。这是代码我在ruby​​onrails应用程序中使用ssh连接和运行命令Net::SSH.start(@servers['local'],@machine_name,:password=>@machine_pwd,:timeout=>30)do|ssh|ssh.open_channeldo|channel|channel.requ

  8. ruby - 如何在转换器插件中访问页面属性(YAML 前端) - 2

    我正在为Jekyll编写一个转换器插件,需要访问一些页眉(YAML前端)属性。只有内容被传递给主要的转换器方法,似乎无法访问上下文。例子:moduleJekyllclassUpcaseConverter关于如何在转换器插件中访问页眉数据有什么想法吗? 最佳答案 基于Jekyll源代码,无法在转换器中检索YAML前端内容。根据您的情况,我看到了两种可行的解决方案。您的文件扩展名可以具有足够的描述性,以提供您本应包含在前言中的信息。看起来Converter插件的设计就是这么基本的。如果修改Jekyll是一个选项,您可以更改Convert

  9. 华为ensp详细安装包、安装教程及所遇问题 - 2

    目录一、安装包链接二、安装详细步骤1.安装Wireshark和WinPcap2.安装OracleVMVirtualBox3.安装ensp三、安装后注册四、启动路由器出现40错误怎么解决一、安装包链接二、安装详细步骤链接:https://pan.baidu.com/s/1QbUUYMOMIV2oeIKHWP1SpA?pwd=xftx提取码:xftx1.安装Wireshark和WinPcap找到Wireshark安装包所在文件夹,双击它,按照以下步骤安装。2.安装OracleVMVirtualBox找到OracleVMVirtualBox安装包所在文件夹,双击它,按照以下步骤安装。注:可自定义安装

  10. 蓝桥杯C/C++VIP试题每日一练之报时助手 - 2

    ?作者主页:静Yu?简介:CSDN全栈优质创作者、华为云享专家、阿里云社区博客专家,前端知识交流社区创建者?社区地址:前端知识交流社区?博主的个人博客:静Yu的个人博客?博主的个人笔记本:前端面试题个人笔记本只记录前端领域的面试题目,项目总结,面试技巧等等。接下来会更新蓝桥杯官方系统基础练习的VIP试题,依然包括解题思路,源代码等等。问题描述:给定当前的时间,请用英文的读法将它读出来。时间用时h和分m表示,在英文的读法中,读一个时间的方法是:  如果m为0,则将时读出来,然后加上“o’clock”,如3:00读作“threeo’clock”。  如果m不为0,则将时读出来,然后将分读出来,如5

随机推荐