前端面试题总结----

1.基础

1.1 性能优化

  1. 减少HTTP请求:

    • 合并和压缩文件:减少页面所需的CSS、JavaScript和图像文件数量,并通过合并和压缩这些文件来减小它们的大小。
    • 使用CSS精灵:将页面上的多个小图标合并为一个精灵图,以减少HTTP请求。
  2. 优化图像:

    • 选择适当的图像格式:根据具体需求选择合适的图像格式,如JPEG、PNG或WebP。
    • 图像压缩:使用工具压缩图像,减小文件大小而不损失太多质量。
  3. 延迟加载(懒加载):

    • 图片懒加载:仅在用户滚动到图片可见区域时加载图片,而不是一开始就加载所有图片。
    • 延迟加载JavaScript:将不必要的JavaScript代码推迟到页面加载后再执行。
  4. 浏览器缓存:

    • 利用浏览器缓存:通过设置适当的缓存头信息,允许浏览器缓存页面资源,减少重复加载。
  5. 服务端优化:

    • 启用Gzip压缩:服务器端开启Gzip压缩,减小传输的数据量。
    • 启用CDN(内容分发网络):通过在全球分布的服务器上存储资源副本,加速用户访问速度。
  6. 使用字体图标:

    • 使用字体图标代替图像:字体图标通常比图像更轻量,且可以通过CSS进行调整和样式更改。
  7. 异步加载JavaScript:

    • 异步加载外部JavaScript:通过async或defer属性异步加载JavaScript,防止它们阻塞页面的渲染。
  8. 代码优化:

    • 精简和优化代码:删除不必要的代码、注释和空格,以及使用更有效的算法。

React性能优化

  1. 使用 memo、PureComponent 缓存组件
  2. 使用 useMemo 缓存数据、useCallback 缓存函数
  3. 使用 lazy 实现组件懒加载、使用 react-visibility-observer 实现懒渲染
  4. 批量更新
  5. 利用debounce、throttle 避免重复回调
  6. 发布者订阅者跳过中间组件 Render 过程
  7. 状态下放,缩小状态影响范围

小程序优化
围绕 加载性能 跟 渲染性能

  1. 分包加载
  2. 去除无用代码,过大的图片应尽量采用网络图片
  3. 减少启动过程的同步调用
  4. 精简首屏数据,与视图层无关的数据尽量不要放在 data 中
  5. onPageScroll 使用 throttle

1.2 浏览器是如何渲染页面的

  1. 解析HTML文件,创建DOM树
  2. 解析CSS 文件,创建CSSOM树
  3. 将CSSOM与DOM合并,构建渲染树
  4. 布局计算每个对象的精确位置和大小
  5. 最后一步是绘制,使用最终渲染树将像素渲染到屏幕上

1.3 cookie,localStorage,sessionStorage三者区别

  1. cookie 数据可在浏览器和服务器间来回传递,而sessionStorage 和 localStorage 不会自动把数据发给服务器,仅在本地保存
  2. cookie大小不超过 4k,sessionStorage 和 localStorage可达到5M或更大
  3. cookie在设置的过期时间之前一直有效,即使窗口或浏览器关闭;sessionStorage在当前窗口关闭后自动删除,localStorage永不过期,除非主动删除数据

1.4 cookie、session、token

cookie和session

cookie 是网站用于标记用户的身份的一段数据(加密的字符串),session是另一种记录客户状态的机制

  1. 客户端发送一个http请求到服务器
  2. 服务器接受客户端请求后,建立一个Session和一个Session ID用来标识这个唯一 Session,并发送一个http响应到客户端,这个响应头,其中就包含 Set-Cookie 头部(Session ID)
  3. 客户端再次访问时会将 Cookie 中的 Session ID 放在请求头中一并发送到服务器上
  4. 服务器从请求中提取出Session ID,并和保存的所有Session ID进行对比,找到这个用户对应的 Session

cookie和session的区别

  1. cookie 数据存储在客户端(只能存储String对象),session 数据存储在服务端(可以存储任意数据类型)
  2. cookie 可以伪造,并不是很安全;session 存储在服务器,过多会占用服务器的性能
  3. cookie 数据不能超过 4k,很多浏览器限制了一个站点最多保存20个cookie;session 没有限制
  4. 一般重要信息存储在session,其他信息可以放在cookie

cookie 被禁用了怎么办

保持登录的关键不是cookie,而是cookie保存的session id,所以还常用 HTTP 请求头来传输,但需要手动添加

session 弊端

  1. session 过多时会过度消耗服务器资源
  2. 某一时间段服务器访问量大时,会导致 session Id 失效

token

token 的认证流程与 session 很相似,无本质区别,使用token的目的是为了减轻服务器的压力

  1. 用户登录,成功后服务器返回Token给客户端
  2. 客户端收到数据后保存在客户端
  3. 客户端再次访问服务器,将token放入headers中,服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码

token与cookie的区别

  1. token 比 cookie 更安全,浏览器不会自动添加到headers里,需要开发者手动添加

  2. token 支持跨域访问,cookie 不支持

  3. token 在服务器不需要存储 session 信息,本身就包含用户信息,只需要在客户端存储

  4. 不依赖cookie,不需要防范CSRF

1.5 闭包的理解

含义:

  1. 函数声明的时候,会生成一个独立的作用域
  2. 同一作用域的对象可以互相访问
  3. 作用域呈层级包含状态,形成作用域链,子作用域的对象可以访问父作用域的对象,反之不能;另外子作用域会使用最近的父作用域的对象

作用:

  1. 延伸局部变量的作用范围
  2. 提供有限的访问权限

GC回收机制

基本思路:确定确定哪个变量不会再使用,然后释放它占用的内存。这个过程是周期性的,即垃圾回收程序每隔一定时间就会自动运行。

主要使用两种方式标记未使用的变量:标记清理和引用计数。

  1. 标记清理
    当变量进入上下文(比如在函数内部声明一个变量),这个变量会被加上存在于上下文的标记。在上下文的变量,逻辑上讲永远不应该释放它的内存,因为只要代码在运行,就有可能用到。当变量离开上下文时,也会被加上离开标记

    GC回收运行的时候,会标记内存中存储的所有变量(标记方式有很多种,标记过程并不重要,关键是策略)。然后,它会将所有在上下文中的变量的标记去掉。在此之后再被标记的就是待删除的,原因是任何在上下文中的变量都访问不到它们了。随后GC做一次内存清理,销毁带标记的所有值并收回它们的内存。

  2. 引用计数
    思路是对每个值都记录它的引用次数。声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1。如果对该值得引用得变量被其他值给覆盖了,那么引用数减1。当一个值得引用数为0时,就说明没办法再访问到这个值了,就可以安全的收回其内存了。

1.6 GET与POST的区别

GET和POST是HTTP协议中的两种发送请求的方法, HTTP的底层是TCP/IP( 数据如何在万维网中通信的协议),所以GET和POST的底层也是TCP/IP 。 GET和POST能做的事情是一样的。给GET加上request body,给POST带上url参数,技术上是完全行的通的

  1. 请求区别
    • GET在浏览器回退时是无害的,而POST会再次提交请求
    • GET产生的URL地址可以被Bookmark,而POST不可以
    • GET请求会被浏览器主动缓存,而POST不会,除非手动设置
    • GET请求在URL中传送的参数是有长度限制(2kb),而POST没有
    • GET只接受ASCII字符,而POST没有限制
    • GET参数通过URL传递,POST放在Request body中
  2. 表单提交区别
    • GET是从服务器上获取数据,POST是向服务器传送数据
    • GET提交不安全,数据会附在URL后面,POST则不会
  3. 重大区别
    • GET产生一个TCP数据包;POST产生两个TCP数据包
      • GET方式的请求,浏览器会把http header和data一并发送出去
      • 对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data

总结: GET 与 POST 都有自己的语义,不能随便混用 。POST 请求比 GET 慢,因为 POST 要发两次包,GET 在 request body 中带数据有些服务器可能会忽略

1.7 JavaScript 的 “事件冒泡”?为什么会有"事件冒泡"?

答: 多个元素嵌套,有层次关系,这些元素都注册了相同的事件,如果里边的元素的事件触发了,外面元素的改事件也自动触发;

如果事件涉及到更新HTML节点或者添加HTML节点的时候,就会出现这样的一种情况,新更新的或者新添加的节点无法绑定事件,表现的行为是无法触发事件

比如:有一个需求,需要点击 ul 列表下的 li 标签触发事件,如果给每个 li 都绑定事件,会产生下面 2 个问题

  1. li 数量非常大的话就会产生性能问题,甚至造成页面卡顿崩溃
  2. 动态新增的 li 不能绑定事件

如果用事件委托,则会很好的解决这两个问题, 用注册一个事件则能监听子节点的所有事件,所应用的就是事件的冒泡

1.8 url、href 和 src 的区别

  1. url代表唯一的网上资源链接或者是服务器资源链接的地址,引用资源
  2. href 目的不是为了引用资源,而是为了建立联系,让当前标签能够链接到目标地址
  3. src 指向外部资源的位置,指向的内容将会替换当前标签内容

1.9 link 标签引入和 @import 引入的区别

一、相同点
两者都是外部引用CSS的方式

二、区别

  1. link除了引用样式文件,还可以引用图标等资源文件,而@import只引用样式文件
  2. link引用样式时,在页面载入时同时加载;@import需要页面网页完全载入以后加载
  3. link无兼容问题;@import低版本的浏览器不支持
  4. link支持使用JavaScript控制DOM去改变样式;而@import不支持

1.10 BFC规范(块级格式化上下文:block formatting context)是什么?

BFC规定了内部的Block Box如何布局

定位方案:

  1. 内部的Box会在垂直方向上一个接一个放置
  2. Box垂直方向的距离由margin决定,属于同一个BFC的两个相邻Box的margin会发生重叠
  3. 每个元素的margin box 的左边,与包含块border box的左边相接触
  4. BFC的区域不会与float box重叠
  5. BFC是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素
  6. 计算BFC的高度时,浮动元素也会参与计算

满足下列条件之一就可触发BFC

  1. float的值不为none(默认)
  2. overflow的值不为visible(默认)
  3. display的值为inline-block、table-cell、table-caption
  4. position的值为absolute或fixed

1.11 从输入URL到页面展示发生了什么

  1. URL地址解析,判断输入的是一个合法的URL还是一个待搜索的关键词,接着发起真正的URL请求。如果浏览器本地缓存了这个资源,则直接将数据转发给浏览器进程,如果没有缓存,则进行DNS域名解析。
  2. 查找浏览器有没有DNS缓存(之前有访问记录),如果有则返回IP,没有则寻找本地的host文件,看有没有域名记录,如果有则返回IP,没有则直接向本地DNS服务器请求,直至返回IP。
  3. 建立TCP连接(三次握手)
    • 第一次:客户端发送 ‘SYN’ 数据包给服务端
    • 第二次:服务端收到客户端的数据包,返回 ‘SYN/ACK’ 数据包给客户端
    • 第三次:客户端收到服务端的返回后,发送 ‘ACK’ 数据包给服务端
  4. 连接成功,发送http请求,调用后台接口,服务器开始运作起来,准备数据返回
  5. 服务器处理请求,返回响应结果
  6. 关闭TCP连接(四次挥手)
    • 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
    • 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
    • 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
    • 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1, Server进入CLOSED状态,完成四次挥手。
  7. 浏览器渲染页面

1.12 cookie 属性有哪些?

Cookie一共十个属性

  1. Name: Cookie名
  2. Value: Cookie值
  3. Domain: 指定了可以访问该 Cookie 的 Web 站点或域。如果设成.test.com,那么子域名a.test.com和b.test.com,都可以使用.test.com的Cookie。
  4. Path: 定义了Web站点上可以访问该Cookie的目录。如果设成/path/,则只有路径为/path/的页面可以访问该Cookie。如果设为/,则本域名下的所有页面都可以访问该Cookie。
  5. Expires / Max-Age:
    Cookie的超时时间。若设置其值为一个时间,那么当到达此时间后,此Cookie失效。不设置的话默认值是Session,意思是Cookie会和Session一起失效。当浏览器关闭(不是浏览器标签页,而是整个浏览器)后,此Cookie失效。
  6. Size: Cookie大小。
  7. HttpOnly: 若此属性为true,则只有在http请求头中会带有此Cookie的信息,而不能通过document.cookie来访问此Cookie。
  8. Secure: 设置是否只能通过https来传递此条Cookie。
  9. SameSite:
    用来防止 CSRF 攻击和用户追踪。
    可以设置三个值:Strict、Lax 和 None。
    Strict: Strict最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
    Lax: Lax规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
    None: 关闭SameSite属性,提是必须同时设置Secure属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
  10. Priority: 优先级。定义了三种优先级,Low/Medium/High,当Cookie数量超出时,低优先级的Cookie会被优先清除。

1.13 new 实例化对象过程

  1. 创建一个新的空对象
  2. 将新对象的 __proto__ 指向构造函数的prototype
  3. 将构造函数中this指向新对象(借助 call/apply)
  4. 判断构造函数的返回值
    • 设置了返回值:
      若返回值为引用值,则返回引用值
      若返回值为原始数据,则返回新对象
    • 未设置返回值:返回新对象
function newFn (Fn, params) {
    // 创建一个新的空对象 instance
    // const instance = {}

    // 将 instance 的 __proto__ 属性指向构造函数的原型(Fn.prototype)
    // instance.__proto__ = Fn.prototype
    
    const instance = Object.create(Fn.prototype)
    // 以 instance 来调用执行构造函数(借助 call/apply)
    const result = Fn.apply(instance, params)

    // 判断构造函数的返回值,返回 instance 或函数返回值(当构造函数返回值为 object 时)
    return (result && (typeof result === 'object' || typeof result === 'function')) ? result : instance
}

1.14 面向对象与面向过程的区别

  1. 面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了
  2. 面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为

优缺点

面向过程:

优点是性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源。而Linux\Unix等一般采用面向过程开发,性能是最重要的因素。缺点是没有面向对象易维护,易复用,易扩展。可维护性差,不易修改。

面向对象:

优点是易维护,易复用,易扩展。由于面向对象由封装,继承,多态性的特性,可以设计出耦合度低的系统,使系统更加灵活,更加易于维护。 缺点是性能比面向过程低

1.15 JS 继承方式有哪些?

  1. 组合继承
    通过原型链和盗用构造函数实现

  2. 原型式继承

  3. 寄生式继承

  4. 寄生式组合继承

  5. 类继承

1.16 小程序的双线程模式

渲染层和逻辑层

  • WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层
  • 渲染层的界面使用了WebView 进行渲染
  • 逻辑层采用JsCore线程运行JS脚本

一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发

为什么小程序不采用浏览器的设计模式?

  1. 小程序不需要那么多原生dom的标签
  2. 小程序不需要JS直接操作dom,用目前比较先进的数据驱动,虚拟dom diff更新完全可以
  3. Web Worker由于它是子线程,在执行JS上性能远不如主线程

知道了原理我们能干什么?

  1. 在保证功能的前提下尽量使用结构简单的 UI(减少渲染层的工作量)
  2. 尽量降低 JavaScript 逻辑的复杂度(减少逻辑层的工作量)
  3. 尽量减少 setData 的调用频次和携带的数据体量(减少数据与事件传递时携带的载荷)

1.17 什么是原型链

原型定义:每个对象都有一个名为__proto__的属性,该属性指向另一个对象(构造函数的prototype属性),这个另一个对象就是该对象的原型
属性查找:当访问一个对象的属性时,首先会在该对象上查找这个属性。如果没有找到,它会沿着原型向上查找,直到找到该属性或者到达顶端,这种呈链式查找称为原型链

1.18

2.HTTP 与 HTTPS

2.1 HTTP 状态码

  • 100 – 允许客户端继续在后续的请求中发送附件
  • 200 – 服务端接收请求,并返回数据成功
  • 201 – 请求已经被实现(创建)
  • 202 – 服务端接收请求,但还未处理
  • 206 – 在客户端请求的资源是部分范围内的内容时返回,例如一个大文件被分割成多个部分,而客户端只请求其中的某一部分
  • 301 – 永久性重定向。请求的资源已经永久分配了新的URI ,服务端会自动将该请求重定向到新的位置
  • 302 – 临时重定向,希望用户本次使用的新分配的URI
  • 304 – 自从上次请求后,请求的网页未修改过
  • 400 – 客户端错误,一般是参数错误
  • 403 – 拒绝访问
  • 404 – 请求资源不存在
  • 500 – 服务端错误
  • 501 – 服务器无法识别请求
  • 503 – 服务器暂时无法使用

2.2 浏览器缓存机制

浏览器缓存机制有两种:

  1. 强缓存(默认)
    浏览器访问网站后会强缓存资源,第二次访问就不会请求服务器(一般会定个时间再去请求服务器)
  2. 协商缓存

强缓存:
通过响应头中的Cache-Control属性判断 (优先级最高)

  • private:客户端可以缓存
  • public: 客户端和代理服务器都可缓存
  • max-age=xxx : 缓存的内容将在 xxx 秒后失效
  • no-cache:需要使用对比缓存来验证缓存数据
  • no-store:所有内容都不会缓存,基本不用

协商缓存:

  1. 第一次请求返回给客户端数据和缓存信息,也就是一个特定的缓存标识
  2. 客户端把这个缓存标识放到缓存数据库
  3. 再次请求时,客户端把缓存标识也一起发给服务端,进行对比,判断成功后,返回304状态码,通知客户端比较成功,可以使用缓存数据

两种缓存标识

  1. Etag:(唯一标识)优先级更高
  2. Last-Modified/If-Modified-Since:返回给客户端最后这个资源的修改时间,优先级没有Etag高

协商缓存标识不生效时,状态码200,服务端返回body和header

在对比缓存标识生效时,状态码为304,并且报文大小和请求时间大大减少。原因是缓存标识生效只返回header部分,通过状态码通知客户端使用缓存,不再需要将报文主体部分返回给客户端

总结

  1. 强制缓存的优先级更高,如果没失效,就直接用缓存数据库里的东西
  2. 如果时间已经失效了,就看用的是哪种标识(Etag服务端生成的唯一标识,还是last-modified资源最后修改时间标识)返回304就用缓存里的,返回200就返回body和新的header

2.3 事件循环

所有任务可以分成两种,一种是同步任务, 另一种是异步任务

  1. 所有同步任务都在主线程上执行,形成一个执行栈
  2. 主线程之外,还存在一个"任务队列" , 只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",结束等待状态,进入执行栈,开始执行
  4. 只要主线程空了,就会去读取”任务队列” , 这个过程会不断重复

宏观任务和微观任务
先执行微观任务,再执行宏观任务

宏观任务主要包含:setTimeout、setInterval、script(整体代码)
微观任务主要包括:Promise、MutaionObserver、process.nextTick(Node.js 环境)

Node 事件循环

当Node.js启动时会初始化event loop, 每一个event loop都会包含按如下六个循环阶段,nodejs事件循环和浏览器的事件循环完全不一样

阶段概览

  1. timers(定时器) : 此阶段执行那些由 setTimeout() 和 setInterval() 调度的回调函数.
  2. I/O callbacks(I/O回调) : 此阶段会执行几乎所有的回调函数, 除了 close callbacks(关闭回调) 和 那些由 timers 与 setImmediate()调度的回调.
  3. idle(空转), prepare : 此阶段只在内部使用
  4. poll(轮询) : 检索新的I/O事件; 在恰当的时候Node会阻塞在这个阶段
  5. check(检查) : setImmediate() 设置的回调会在此阶段被调用
  6. close callbacks(关闭事件的回调): 诸如 socket.on(‘close’, …) 此类的回调在此阶段被调用

如果event loop进入了 poll 阶段,且代码未设定timer,将会发生下面情况:

  • 如果poll queue不为空,event loop将同步的执行queue里的callback,直至queue为空,或执行的callback到达系统上限;
  • 如果poll queue为空,将会发生下面情况:
    • 如果代码已经被setImmediate()设定了callback, event loop将结束poll阶段进入check阶段,并执行check阶段的queue (check阶段的queue是 setImmediate设定的)
    • 如果代码没有设定setImmediate(callback),event loop将阻塞在该阶段等待callbacks加入poll queue,一旦到达就立即执行

如果event loop进入了 poll阶段,且代码设定了timer:

  • event loop将检查timers,如果有1个或多个timers时间时间已经到达,event loop将按循环顺序进入 timers 阶段,并执行timer queue

2.4 http1.0、http1.1及http2.0的区别

http1.0:每次请求都需要重新建立tcp连接,请求完后立即断开与服务器连接,这很大程度造成了性能上的缺陷,http1.0被抱怨最多的就是连接无法复用。

http1.1 对比 1.0

  1. 缓存处理。1.0 中主要使用 header 里的 if-Modified-Since,Expires来做为缓存判断的标准;1.1 则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略
  2. Host头处理。1.0中认为每台服务器都绑定一个唯一的IP地址,因此,请求消息中的URL并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP地址。HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中如果没有Host头域会报告一个错误(400 Bad Request)
  3. 长连接(Connection: keep-alive)。相较于1.0减少了连接和关闭的延迟,提高了效率,但是若干个请求还是需要串行排队处理,如果一旦某个请求超时,后面的就会被阻塞,也就是常说的线头阻塞。

http2 对比 1.1

  1. 新的二进制格式传输:二进制即0和1的组合,实现方便健壮,而1.x版本是基于文本,解析存在缺陷
  2. 多路复用:一个连接可以有多个请求,且可以混杂在一起根据requestid来区分不同的请求,提高了连接的利用率,降低了延迟
  3. header头部压缩:通讯两方各自缓存了一份 header请求头表,避免了重复的header传输,且缩小了包的体积大小
  4. 服务端推送功能:可以服务端主动向客户端push消息

2.5 npm run 发生了什么

以 vue 项目 npm run serve 为例

  1. 实际上就是执行了vue-cli-service serve 这条命令,因为操作系统中没有存在 vue-cli-service 这一条指令,所以直接执行会报错
  2. 在通过 npm i XXX 安装依赖的时候,例如 npm i @vue/cli-service,就会在 node_modules/.bin/ 目录中创建 好 vue-cli-service 为名的几个可执行文件
  3. .bin 目录下的文件,表示一个个软链接,打开文件可以看到文件顶部写着 #!/bin/sh ,表示这是一个脚本,相当于执行了 ./node_modules/.bin/vue-cli-service serve(最后的 serve 作为参数传入)
  4. 执行npm run xxx 的时候,就会到 node_modules/bin中找对应的映射文件,然后再找到相应的js文件来执行

3.Vue

3.1 vue的响应式原理

  1. 通过Object.defineProperty劫持所有data属性,一个属性创建一个 Dep 对象

  2. 解析器(Compile)解析模板中的 Directive(指令),获取到哪里用到了属性(订阅者),比如{{name}} {{message}},创建一个观察者watcher添加到对应的Dep 对象中,同时初始化view,在界面上显示

  3. Watcher属于Observer和Compile桥梁,将接收到的Observer产生的数据变化,并根据Compile提供的指令进行视图渲染,使得数据变化促使视图变化

3.2 toast 模块封装

import Toast from './Toast'

export default {
  install(Vue) {
    // 1. 创建组件构造器
    const toastConstructor = Vue.extend(Toast)

    // 2. new 一个组件对象
    const toast = new toastConstructor()

    // 3. 手动挂载某一盒子上
    toast.$mount(document.createElement('div'))

    // 4. toast.$el 对应的就是 div
    document.body.appendChild(toast.$el)

    // 5. 原型上添加属性
    Vue.prototype.$toast = toast
  }
}

3.3 vuex 跟 pinia 的区别

  1. pinia【同步、异步】统一使用 action 来修改 state 数据
  2. pinia 没有 modules 配置,每一个独立的仓库都是 definStore 生成出来的
  3. pinia 有完整的 TypeScript 支持

缺点:pinia 不支持调试

3.4 MVC 跟 MVVM 的区别

一、MVVM

在MVVM下视图和模型是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的双向绑定。

优点:

  • 低耦合: View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
  • 可重用性: 可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
  • 独立开发: 开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计。

二、MVC

分为:Model(模型)、View(视图)、Controller(控制器)。View和Model不直接联系,必须通过Controller来承上启下。

优点:

  • 低耦合
  • 重用性高
  • 可维护性高

区别:

  • MVVM通过数据来显示视图层而不是节点操作
  • MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验

3.5 vue和react区别

  1. 响应式原理不同
    vue:会遍历data数据对象,使用Object.definedProperty()监听每个属性
    react:通过setState()方法来更新状态,状态更新之后,组件也会重新渲染

  2. 监听数据变化的实现原理不同
    vue:使用的是可变数据,通过 getter/setter以及一些函数的劫持,能精确知道数据变化
    react:强调数据的不可变,通过比较引用的方式(diff)进行的,如果不优化可能导致大量不必要的VDOM的重新渲染

  3. Diff算法不同
    vue对比节点,如果节点元素类型相同,但是className不同,认为是不同类型的元素,会进行删除重建,但是react则会认为是同类型的节点,只会修改节点属性。
    vue的列表比对采用的是首尾指针法,而react采用的是从左到右依次比对的方式,当一个集合只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到最后一个,从这点上来说vue的对比方式更加高效。

  4. 数据流不同
    vue:组件与DOM之间可以通过v-model双向绑定
    react:一直不支持双向绑定,提倡的是单向数据流

  5. 组合不同功能的方式不同
    vue:通过mixin(侵入太强)
    react:通过HoC(高阶组件)

  6. 模板渲染方式不同
    vue:在和组件JS代码分离的单独的模板中,通过指令来实现的
    react:在组件JS代码中,通过原生JS实现模板中的常见语法,比如插值,条件,循环等,都是通过JS语法实现的

  7. 渲染过程不同
    vue:可以更快地计算出Virtual DOM的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树
    react:应用的状态被改变时,全部子组件都会重新渲染

3.6 computed 缓存原理

  1. 响应式
    读取 computed 时会触发 get, 设置时会触发 set

  2. 如何控制
    某个计算属性C依赖 data 中的 A,每次读取C,C就会去读取A,从而触发A的 get,如果没有缓存多次触发是很消耗性能的;
    脏数据标记:dirty,是 watcher 的属性

    • dirty 是 true 时,读取 computed 时重新计算
    • dirty 是 fasle 时,读取 computed 时使用缓存
  3. 依赖的data发生改变,computed 如何更新

    • C 依赖 A,所以A可以收集到C的watcher
    • A 发生改变,会把 watcher 的 dirty 设置为 true
    • A 发生改变,会通知页面进行更新,页面重新读取计算属性C,此时 dirty 是 true,所以重新计算
    • computed 更新完后,会把 dirty 设置为 false

3.7 Vue 3 对比 Vue 2

  1. diff 算法优化
    • Vue2 中的虚拟dom是进行全量的对比,即数据更新后在虚拟 DOM 中每个标签内容都会对比有没有发生变化
    • Vue3 新增了静态标记(PatchFlag)。在创建虚拟 DOM 的时候会根据 DOM 的内容会不会变化添加 PatchFlag,数据更新后,只对比带有PatchFlag的节点
      <p>标签内容p>
      <p>{{ msg }}p> 
      
  2. hoistStatic 静态提升
    • Vue2 中无论元素是否参与更新, 每次都会重新创建, 然后再渲染
    • Vue3 中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可
  3. 事件监听缓存
    • 默认情况下 onClick 会被视为动态绑定, 所以每次都会去追踪它的变化,但是因为是同一个函数,所以没有追踪变化, 直接缓存起来复用即可

4.React

4.1 setState 为什么是异步的

  1. 提升性能
    1. 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的
    2. 最好的办法应该是获取到多个更新,之后进行批量更新
  2. 如果同步更新了state,但是还没有执行 render 函数,那么 state 和 props 不能保持同步

setState 在组件生命周期或React合成事件中更新数据是异步的,在setTimeout或者原生dom事件中更新数据是同步的。原因是返回了不同的值做更新判断,同步返回 Sync,批量处理返回 Batch

4.2 setState 的数据为什么要保证原数据不可变性

跟 shouldComponentUpdate 更新界面有关,内部进行的是浅层比较,如果直接在原数据上修改引用型数据类型,比较的时候内存地址是一样的导致不会更新视图,但实际上数据又发生了变化

4.3 React的更新流程

  1. 同层节点之间相互比较,不会垮节点比较;
  2. 不同类型的节点,产生不同的树结构;
  3. 开发中,可以通过key来指定哪些节点在不同的渲染下保持稳定;

情况一:对比不同类型的元素

当一个元素从

变成

时,

树下面的子节点会全部销毁,重新渲染

会调用

树的 componentWillUnmount(),

树的 componentDidMount() 方法

情况二:对比同一类型的元素

会保留 DOM 节点,仅比对及更新有改变的属性

  • 会更新该组件的props
  • 下一步,调用 render() 方法,diff 算法将在之前的结果以及新的结果中进行递归

情况三:对子节点进行递归

当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差异时,生成一个mutation

4.4 受控组件与非受控组件

在 HTML 中,表单元素(如