1、Vue的基本原理
【】当一个Vue实例创建时,Vue会遍历data
中的属性,用 Object.defineProperty
(vue3.0使用proxy )将它们转为 getter/setter
,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。 每个组件实例都有相应的 watcher
程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter
被调用时,会通知watcher重新计算,从而致使它关联的组件得以更新。
2、双向绑定数据的原理
【】Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。主要分为以下几个步骤:
- 需要
observe
的数据对象进行递归遍历,包括子属性对象的属性,都加上setter和getter这样的话,给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化 compile
解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图Watcher
订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: ①在自身实例化时往属性订阅器(dep)里面添加自己 ②自身必须有一个update()方法 ③待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调,则功成身退- MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,达到数据变化 -> 视图更新;视图交互变化(input) -> 数据model变更的双向绑定效果
3、使用 Object.defineProperty() 来进行数据劫持有什么缺点
【】通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty 不能拦截到这些操作
4、MVVM 理解
【】MVVM 分为 Model、View、ViewModel
:
- Model 代表数据模型,数据和业务逻辑都在Model层中定义
- View 代表UI视图,负责数据的展示
- ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作
5、Computed、Watch、Methods、Fliters
【】对于 Computed:
- 支持缓存,只有依赖的数据发生了变化,才会重新计算
- 不支持异步,当Computed中有异步操作时,无法监听数据的变化
【】对于 Watch:
- 不支持缓存,数据变化时,它就会触发相应的操作
- 支持异步监听
【】对于 Methods:
- 同一函数可以定义为一个 method 或者一个计算属性,但是 method 不支持缓存,每次都会调用
【】对于 Fliters:
- 对数据进行格式化。过滤器用在插值表达式
{{ }}
和v-bind
表达式 中,然后放在操作符“ | ”
后面进行指示
6、slot是什么?有什么作用?原理是什么?
【】slot又名插槽,是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口
【】分为三类:默认插槽,具名插槽和作用域插槽
【】实现原理:当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在vm.$slot
中,默认插槽为vm.$slot.default
,具名插槽为vm.$slot.xxx
,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 $slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽
7、常见的事件修饰符
【】.stop
:等同于 JavaScript 中的 event.stopPropagation() ,防止事件冒泡
【】.prevent
:等同于 JavaScript 中的 event.preventDefault() ,防止执行预设的行为
【】.capture
:与事件冒泡的方向相反,事件捕获由外到内
【】.self
:只会触发自己范围内的事件,不包含子元素
【】.once
:只会触发一次
8、v-if 和 v-show 的区别
【】手段:v-if 是动态的向DOM树内添加或者删除DOM元素;v-show 是通过设置DOM元素的display样式属性控制显隐
【】编译过程:v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show 只是简单的基于css切换
【】编译条件:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show 是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留
9、data 为什么是一个函数而不是对象
【】首先当多个实例引用同一个对象时,只要一个实例对这个对象进行操作,其他实例中的数据也会发生变化
【】在Vue中,更多的是想要复用组件,那就需要每个组件都有自己的数据,这样组件之间才不会相互干扰。数据以函数返回值的形式定义,这样当每次复用组件的时候,就会返回一个新的data,也就是说每个组件都有自己的私有数据空间,它们各自维护自己的数据,不会干扰其他组件的正常运行
10、nextTick() 的作用
【】在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的DOM结构的时候,这个操作就需要方法在nextTick()的回调函数中
【】在vue生命周期中,如果在created()钩子进行DOM操作,也一定要放在nextTick()的回调函数中。因为在created()钩子函数中,页面的DOM还未渲染,这时候也没办法操作DOM
【】$nextTick 本质是对事件循环原理的一种应用,在更新DOM的微任务队列后追加了我们自己的回调函数,具体实现见:添加链接描述Vue中的nextTick是怎么实现的
11、自定义指令
【】一般需要对DOM元素进行底层操作时使用,尽量只用来操作 DOM展示,不修改内部的值。当使用自定义指令直接修改 value 值时绑定v-model的值也不会同步更新;如必须修改可以在自定义指令中使用keydown事件,在vue组件中使用 change事件,回调中修改vue数据
【】全局定义:Vue.directive("focus",{})
。 局部定义:directives:{focus:{}}
【】钩子函数:bind inSerted update componentupdate unbind
. 钩子函数参数:el. bing. name. value. oldValue. expression. arg. modifers. vnode. oldVnode
12、assets 和 static 的区别
【】assets
和 static
两个都是存放静态资源文件
【】在项目打包时,assets 中放置的静态资源文件会进行打包上传,static 中放置的静态资源文件就不会要走打包压缩格式化等流程
【】将项目中 template需要的样式文件js文件等都可以放置在 assets 中,走打包这一流程。减少体积。而项目中引入的第三方的资源文件如iconfoont.css 等文件可以放置在 static 中,因为这些引入的第三方文件已经经过处理,不再需要处理,直接上传
13、Vue 模版编译原理
【】解析阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树AST
【】优化阶段:遍历AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行diff比较时,直接跳过这一些静态节点,优化runtime的性能
【】生成阶段:将最终的AST转化为render函数字符串
14、对 SSR 的理解
【】SSR也就是服务端渲染,也就是将Vue在客户端把标签渲染成HTML的工作放在服务端完成,然后再把html直接返回给客户端
【】优势:更好的SEO;首屏加载速度更快
【】缺点:服务器端渲染只支持beforeCreate和created两个钩子;更多的服务端负载
15、对 SPA 单页面的理解
【】SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载
【】优点:
- 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染
- 基于上面一点,SPA 相对对服务器压力小
- 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理
【】缺点:
- 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载
- 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理
- SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势
16、Vue 子组件和父组件的执行顺序
【】加载渲染过程:父组件 beforeCreate. >. 父组件 created. >. 父组件 beforeMount. >. 子组件 beforeCreate. >. 子组件 created. >. 子组件 beforeMount. >. 子组件 mounted. >. 父组件 mounted
【】更新过程:父组件 beforeUpdate. >. 子组件 beforeUpdate. >. 子组件 updated. >. 父组件 updated
【】销魂过程:父组件 beforeDestroy. >. 子组件 beforeDestroy. >. 子组件 destroyed. >. 父组件 destoryed
【】vue 的生命周期钩子核心实现式利用发布订阅模式
先把用户传入的生命周期钩子订阅好(内部采用数组的方式存储),然后在创建组件实例的过程中会依次执行对应的钩子方法(发布)
17、组件通信方式
【】props / $emit
【】eventBus 事件总线
【】依赖注入(provide / inject):依赖注入所提供的属性是非响应式的
【】ref / $refs
【】$parent / $children
【】$attrs / $listeners
18、对虚拟 DOM 的理解
【】在浏览器上频繁的操作DOM,会产生一定的性能问题。使用虚拟DOM可以减少直接操作DOM的次数,减少浏览器的重绘及回流
【】Virtual DOM 本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象
【】Virtual DOM 映射到真实DOM要经历VNode的create、diff、patch等阶段
【】虚拟 DOM 的作用:
- 将真实元素节点抽象成 VNode,有效减少直接操作 dom 次数,从而提高程序性能
- 方便实现跨平台:可以使用虚拟DOM去针对不同平台进行渲染
19、虚拟 DOM 的解析过程
【】首先对将要插入到文档中的 DOM 树结构进行分析,使用 js 对象将其表示出来,比如一个元素对象,包含 TagName、props 和 Children 这些属性。然后将这个 js 对象树给保存下来,最后再将 DOM 片段插入到文档中
【】当页面的状态发生改变,需要对页面的 DOM 的结构进行调整的时候,首先根据变更的状态,重新构建起一棵对象树,然后将这棵新的对象树和旧的对象树进行比较,记录下两棵树的的差异
【】最后将记录的有差异的地方应用到真正的 DOM 树中去,这样视图就更新了
20、说一下前端登录的流程
【】初次登录的时候,前端调后端的登录接口,发送用户名和密码,后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token,和一个用户信息的值,前端拿到token,将token储存到Vuex中,然后从Vuex中把token的值存入浏览器Cookies中。把用户信息存到Vuex然后再存储到LocalStroage中,然后跳转到下一个页面,根据后端接口的要求,只要不登录就不能访问的页面需要在前端每次跳转页面师判断Cookies中是否有token,没有就跳转到登录页,有就跳转到相应的页面,我们应该再每次发送post/get请求的时候应该加入token,常用方法再项目utils/service.js中添加全局拦截器,将token的值放入请求头中 后端判断请求头中有无token,有token,就拿到token并验证token是否过期,在这里过期会返回无效的token然后有个跳回登录页面重新登录并且清除本地用户的信息
21、DIFF算法的原理
【】在新老虚拟DOM对比时
- 首先,对比节点本身,判断是否为同一节点,如果不为相同节点,则删除该节点重新创建节点进行替换
- 如果为相同节点,进行patchVnode,判断如何对该节点的子节点进行处理,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
- 比较如果都有子节点,则进行updateChildren,判断如何对这些新老节点的子节点进行操作(diff核心)
- 匹配时,找到相同的子节点,递归比较子节点
22、首页加载白屏问题处理
【】白屏产生的原因
- FP(全称“First Paint”,翻译为“首次绘制”) 是时间线上的第一个“时间点”,它代表浏览器第一次向屏幕传输像素的时间,也就是页面在屏幕上首次发生视觉变化的时间
- FCP(全称“First Contentful Paint”,翻译为“首次内容绘制”) ,它代表浏览器第一次向屏幕绘制 “内容” (文本、图片(包含背景图)、非白色的 canvas 或SVG)
- FMP(全称“First Meaningful Paint”,翻译为“首次有效绘制”) 表示页面的“主要内容”开始出现在屏幕上的时间点,ajax请求数据之后,首次有效绘制,就是页面加载差不多了,但是可能图片还没加载出来
- 从FP到FMP这个过程全是白屏
【】解决方法
- 预渲染、同构、SSR、路由懒加载、quicklink、使用Gzip压缩减少文件体积、外链CSS/JS文件、webpack entry、骨架屏、loading
23、vue模板(template)里为什么不能使用多个头结点
【】只能有一个节点是因为当前 构建 和 diff virutalDOM 的算法还未支撑这样的结构,也很难在保证性能的情况下支撑
【】react 里新增了 Fragment 语法以支持类似多节点的组件,而 Vue3 也提出了类似的语法支持,实际上需要在多节点外包裹一层特殊节点
24、Vue.use 插件机制
【】如果插件是一个对象,必须提供install
方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入;该方法需要在调用 new Vue()
之前被调用;当 install 方法被同一个插件多次调用,插件将只会被安装一次
【】原理:功能主要就是两点:安装Vue插件、已安装插件不会重复安装:
- 先声明一个数组,用来存放安装过的插件,如果已安装就不重复安装
- 然后判断plugin是不是对象,如果是对象就判断对象的install是不是一个方法,如果是就将参数传入并执行install方法,完成插件的安装
- 如果plugin是一个方法,就直接执行
- 最后将plugin推入上述声明的数组中,表示插件已经安装
- 最后返回Vue实例
25、Vue.extend 是什么
【】
26、new Vue() 做了什么
【】合并配置
【】初始化生命周期
【】初始化事件
【】初始化render函数
【】调用 beforecreate钩子函数
【】初始化state,包括data、props、computed
【】调用 created 钩子函数
【】然后按照生命周期,调用 vm.$mount 挂载渲染
27、vue 项目性能优化
【】v-if 和 v-show 区分使用场景
【】v-for 遍历添加 key 值
【】事件的销毁
【】图片资源懒加载
【】路由懒加载
【】第三方插件的懒加载
【】服务端渲染 or 预渲染
28、Vue2.x 与 Vue3 区别
【】生命周期变化
【】使用 proxy 代替 defineProperty
【】diff 算法提升
【】typeScript 支持
【】打包体积变化
【】其他 API 和功能的改动
29、Vue2 与 Vue3 双向数据绑定的区别
【】vue2 双向数据绑定存在的问题:
- 对于对象,无法检测 property 的添加和删除
- 对于数组,无法利用索引直接设置一个数组项,无法直接修改数组长度
- 解决方法是使用 set 方法
【】set () 的原理
- 目标是对象,就用
defineReactive
给新增的属性去添加 getter 和 setter - 目标是数组,就直接调用数组本身的
splice
方法去出发响应式
【】vue2 使用 Object.defineProperty 对象以及对象属性劫持 + 发布订阅模式,只要数据发生变化直接通知变化,并驱动视图的更新
<input type="text" id="in"/> 输入的值为:<span id="out"></span>
var int = document.getElementById('in');
var out = document.getElementById('out');
// 定义一个对象
let data ={name:'tcy',age:'20'}
function observe(data){
//获取所有的data数据对象中的所有属性进行遍历
const keys = Object.keys(data)
for (let i = 0; i < keys.length; i++) {
let val = data[keys[i]];
defineReactive(data, keys[i],val)//为每个属性增加监听
}
}
function defineReactive(obj,key,val){
Object.defineProperty(obj, key, {
enumerable: true,//可枚举
configurable: true,//可配置
get: function reactiveGetter () {
//模拟get劫持
console.log("get劫持");
return val;
},
set: function reactiveSetter (newVal) {
//模拟set劫持
console.log("set劫持,新值:"+newVal);
val = newVal;
}
})
}
observe(data);
int.addEventListener('input', function(e) {
data.name = e.target.value;
})
data.age=25 // 触发set方法
【】Vue3.0 中的响应式采用了ES6中的 Proxy 方法,Proxy 对象用于定义基本操作的自定义行为
const p = new Proxy(target, handler);
- 参数target表示要使用Proxy包装的对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理)
- 参数handler是一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
let obj = {age:18}
obj = new Proxy(obj, {
get(target, property){
return target[property]
},
set(target, property, value){
if(target[property] === value) return
target[property] = value
observer()
}
})