前端专项面试题含部分Vue原理

119 阅读9分钟

Vue2 PK Vue3

  • 速度更快
  • 体积更小
  • 更易维护 生命周期

Vue2Vue3
beforeCreate
creted
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

说下Vue的生命周期

  • beforeCreate:在实例创建之间执行,数据是未加载状态。
  • created:在实例创建、数据加载后,能初始化数据,DOM渲染之前执行。
  • beforeMount:虚拟DOM已创建完成,在数据渲染前最后一次更改数据。el未挂载。
  • mounted:页面、数据渲染完成。el挂载完毕。可以访问DOM节点。
  • beforeUpdate:重新渲染之前触发。不会造成重渲染。
  • Updated:数据已经更新完成,DOM也重新render完成,更改数据会陷入死循环。
  • beforeDestroy:实例销毁前执行,实例仍然完全可用。
  • destroyed:实例销毁后执行,这时候只剩下DOM空壳。

第一次页面加载会触发哪几个钩子?

beforeCreate, created, beforeMount, mounted

简述每个周期具体适合哪些场景

  • beforeCreate:其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法

  • create: data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作

  • beforeMount:执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的

  • mounted:Vue实例已经初始化完成了。此时组件进入到了运行阶段。如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行

  • beforeUpdate:当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步

  • updated:页面显示的数据和data中的数据已经保持同步了,都是最新的

  • beforeDestory:Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁

  • destroyed:这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。

vue获取数据在哪个周期函数?

一般 created/beforeMount/mounted皆可, 比如如果你要操作 DOM , 那肯定mounted时候才能操作

Fragment碎片化,支持对个根节点

Composition 组合式API

  • Vue2是选择式API(Options API),一个逻辑会散乱在不同的位置(data、peops、computed、watch、生命周期钩子函数),导致代码可读性变差,修改某个逻辑,需要在文件上跳来跳去
  • Vue3组合式API(Composition API)可以将同一逻辑的内容写到一起,增强了代码的可读性,内聚性

异步组件

Vue3提供Suspense组件,允许程序等待加载完成前呈现设置的内容,包括两个命名插槽:default和fallback,fallback作为插槽加载状态

Teleport

Vue3.0 新增了一个Teleport组件,开发者可以使用它将其所在组件模板的部分内容移动到特定的DOM位置,譬如body或者其他任意位置。

v-for和v-if哪个优先级高?

  • 在vue2中,v-for的优先级是高于v-if
  • vue3中相反,v-if的优先级高于v-for

谈谈你对Vuex的理解

Vuex是实现组件全局状态管理的一种机制,可以方便的实现组件之间数据的共享。

其中共有五大属性分别是,actions,state,getters,mutations,modules

state: 相当于是存储库,里面会对数据进行缓存

getters: 其实就是相当于vue里面的计算属性,可以通过getters访问store中的内存数据,通过this.$store.getters访问

actions: 是进行异步操作的,通过this.$store.dispatch访问

mutations: 是进行同步操作的,通过this.$store.commit访问

modules: 就是进行模块化,这样会使代码更加整洁

在项目中通常通过axios返回的后端数据使this.$store.commit 往state里面存值,之后通过

this.$store.getters 取值

弊端:因为vuex相当于是本地缓存,因此当页面强制F5刷新的时候会有数据丢失的现象,因此一般配合localStorage做成持久化存储或者使用sessionStorage来进行浏览器存储解决,但是localStorage存储过多数据会导致页面变卡,因为localStorage的本质是对字符串的读取,具体使用方案还是需要根据具体项目情况具体分析。

谈谈你对Vue依赖收集的理解

通过遍历所有data中的属性,使用Object.defineProperty为其添加getter和setter,在getter中每个属性会new Dep来被记录为一个依赖,每一个依赖都有若干个wachter进行监视,当data中数据改变时,通知给相应的dep,dep再通知给wachter,wachter就从对应的data中拿到值后渲染到页面。

谈谈你对虚拟DOM的理解

由于在浏览器中操作 DOM 是很昂贵的,如果直接使用真实DOM的话,会对性能造成比较大的浪费,因此引入了虚拟DOM这个概念,使用虚拟DOM的话,可以对比虚拟DOM和真实DOM的差异,从而进行局部渲染来达到优化性能的目的。

  • 优点:无需手动操作DOM
  • 缺点:页面初始化 DOM 的时候,由于多了一层虚拟 DOM 的计算,所以初始显示会慢一些

diff算法

  1. sameVnode方法判断是否为同一类型节点,不是就直接替换,如果是接着执行
  2. 进行patchVnode判断是否为同一节点,相同返回,不同按情况分支
  3. 如果新节点和旧节点都有文本节点,那么新文本替换旧文本
  4. 如果旧节点没子节点,则新增子节点
  5. 如果新节点没有子节点,旧节点有,那么删除子节点
  6. 如果两者都有子节点,执行updataChildren
  7. updateChildren方法用于新旧虚拟节点的子节点对比,主要采用首尾指针法进行对比,每次比较后start和end点向中间靠拢,当旧节点中有一个start跑到end点右侧时终止比较,以上均不满足则采用key做映射。

Vue2和Vue3的原理和区别

  • 2.0通过Object.defineProperty来劫持各个属性的setter和getter,在数据变动的时候发布消息给消息订阅者,触发相应的监听回调
  • 3.0实现基于ES6的Proxy

差异

  • 2.0基于Object.defineProerty,不及备监听数组的能力,需要重新定义数组原型来达到响应式
  • Object.defineProperty无法监听到数组的添加和删除(需使用Vue.set,Vue。delete),深度监听需要一次性递归,对性能影响大。
  • 3.0基于Proxy和Reflect,可以监听数组,可以监听对象属性的添加和删除
  • 不需要一次遍历data属性,可以显著提高性能。

为什么在vue3中采用了Proxy而抛弃了Object.defineProperty?

  1. Object.defineProperty只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。在Vue2里是通过递归+遍历data对象来实现对数据的监控的。
  2. Proxy可以劫持整个对象,并返回一个新的对象,Proxy还可以代理数组,还可以代理动态增加的属性。

Vue中的$nextTick原理?

  1. 把回调函数放入callbacks等待执行
  2. 将执行函数放到微任务或者宏任务中
  3. 事件循环到了微任务或者宏任务,执行函数依次执行callbacks中的回调

template渲染过程

  • 把模板编译为render函数
  • 实例进行挂载, 根据根节点render函数的调用,递归的生成虚拟dom
  • 通过diff算法对比虚拟dom,渲染到真实dom(类同react的虚拟DOM渲染过程)
  • 组件内部data发生变化,组件和子组件引用data作为props重新调用render函数,生成虚拟dom, 返回到步骤3

Pinia和Vuex的区别

接口更简单,代码更简洁

  • 舍弃了mutation,减少了很多不必要的代码
  • 可以直接对数据进行读写,直接调用action,不再需要commit,dispatch

更好的TypeScript支持

  • VueX中缺少很多类型支持,需要开发者自行模块类型声明
  • pinia中尽可能的利用了类型推断

你是如何结局跨域的

  • 发生原因是因为前台和后台得到服务器网址不一致产生的
  • 在根目录下的Vue.config.js文件中配置
  • 主要就是配置target,changeOrigin和pathRewrite这三个参数
    • target参数指向后台的真实接口
    • changeOrigin参数配置为true允许跨域
    • pathRewrite参数会重写路径

params和query的区别

用法:query要用path来引入,params要用name来引入,接收参数都是类似的,分别是 this.route.query.namethis.route.query.name 和 this.route.params.name 。url地址显示:query更加类似于我们ajax中get传参,params则类似于post,说的再简单一点,query在浏览器地址栏中显示参数,params不显示

父子组件通信

  • 父组件向子组件传值使用props属性
  • 子组件向父组件传值使用自定义方法通过$emit触发
  • 父组件访问子组件children(获取全部)、children(获取全部)、refs(获取指定的)
  • 子组件访问父组件parentparent、root(根组件)

兄弟组件通信

  • 事件总线 Vue.prototype.$bus = new Vue()
  • Vuex

单页面应用的优缺点

优点:

  1. 良好的交互体验。
  2. 良好的前后端工作分离模式。
  3. 减轻服务器压力。

缺点:

  1. SEO难度较高。
  2. 前进、后退管理。
  3. 初次加载耗时多。

$route$router的区别?

$router为VueRouter实例,$route为当前路由

语义化

  • 代码结构清晰,易于阅读
  • 利于开发和维护
  • 有利于搜索引擎优化(SEO)

SEO

  • Title:掘金 - 代码不止,掘金不停
  • Keywords:掘金,稀土,Vue.js,前端面试题
  • description: 描述内容

回流和重绘

  • 重绘:元素外观改变,改变布局
  • 重排/回流:重新计算元素,重新生成布局

避免回流或重构

定位\集中改变样式,不要一条一条地修改 DOM 的样式

Flex 布局

容器的属性:

  • flex-direction:决定主轴的方向 flex-direction: row|row-reverse|column|column-reverse;
  • flex-wrap:决定换行规则 flex-wrap: nowrap | wrap | wrap-reverse;
  • flex-flow:是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
  • justify-content:对其方式,水平主轴对齐方式(X轴)
  • align-items:对齐方式,竖直轴线方向(横向)
  • align-content(Y轴纵向)

项目的属性(元素的属性):

  • order 属性:定义项目的排列顺序,顺序越小,排列越靠前,默认为 0
  • flex-grow 属性:定义项目的放大比例,即使存在空间,也不会放大
  • flex-shrink 属性:定义了项目的缩小比例,当空间不足的情况下会等比例的缩小
  • flex-basis 属性:定义了在分配多余的空间,项目占据的空间。
  • flex:是 flex-grow 和 flex-shrink、flex-basis 的简写,默认值为 0 1 auto。
  • align-self:允许单个项目与其他项目不一样的对齐方式,可以覆盖

es6新增语法

let 和 const、解构赋值、展开运算符、模板字⾯量、箭头函数、Promises、Generators、Iterator、 for ... in、for ... of、Map 和 Set、Proxy、Class、es module

JS中的数据类型

  • 基本类型(值类型):Number、String、Boolean、Symbol,null,undefined   栈内存储
  • 引用类型(复杂数据类型):Object、Function、Array

作用域和作用域链

简单来说作用域就是变量与函数的可访问范围

  1. 全局作用域:代码在程序的任何地方都能被访问,window
  2. 函数作用域:在固定的代码片段才能被访问,function内
  3. 新增块级作用域,大括号内{}

一般情况下查找变量会在当前作用域先找。但是如果在当前作用域中没有查到,就会向上级作用域去查 直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

原型 && 原型链

  • 构造函数   通过new得到实例对象,内部prototype指向原型对象
  • 实例对象   __proto__指向原型对象
  • 原型对象   constructor指向构造函数

在对象上查找一个属性或者方法时,如果找不到就会去原型对象找,再找不到再去原型对象的原型对象, 最后找到object的原型对象为null时就表示没有

JavaScript对象是通过引用来传递的。当我们修改原型时,与之相关的对象也会改变

EventLoop

js: 先会执行栈中的内容 - 栈中的内容执行后执行微任务 - 微任务清空后再取出一个宏任务压入执行栈执行 - 再去执行微任务 - 然后在取宏任务清微任务这样不停的循环。 复制代码

宏任务:ajax、定时器、一些浏览器api、script... 微任务:promise.then、mutationObersve

双向数据绑定

Vue 使用 Object.defineProperty 遍历和递归对data中的所有属性做数据劫持,把这些 属性 全部转为 getter/setter,当获取的时候会触发getter,设置的时候会触发setter

发布者-订阅者模式

  • 监听(Observer): 遍历和递归对data中的所有属性做数据劫持,当获取的时候会触发get,设置的时候会触发set
  • 模版编译(Compile): 解析模板指令,将模板中变量替换成数据,然后渲染初始化的页面视图。并且给所有的 变量添加监听数据的 订阅者,一旦数据有变动,收到通知,更新试图
  • 依赖收集(Dep): 收集者,每个变量会有一个收集者,收集变量所对应的所有的订阅者,当数据发生变化的时候通知订阅者更新
  • 订阅者(Watcher):数据监听 和 模版编译之间通信的桥梁,当收到通知更新时更新页面