一、defineproperty和proxy
1、相同
都是基于数据劫持的双向绑定
2、不同
Object.defineProperty等方法对 对象属性 的 "劫持"
Proxy是对 对象 的 "劫持"
Object.defineProperty需要结合发布订阅模式实现数据双向绑定
(1) Object.defineProperty的缺点
- 无法监听数组变化,只有这7种办法可以监听数组变化
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
- 只能劫持对象的属性,如果属性值也是对象那么需要深度遍历
(2) proxy的优点
1.Proxy可以直接监听数组的变化
2.Proxy直接可以劫持整个对象,并返回一个新对象
@@@@Vue的响应式数据原理
其实就是Vue 内部有一个机制能监听到数据变化然后触发更新
- 在new Vue()的时候,我们会把options传进去
- 然后会调用init()进行vue初始化的过程,对状态进行初始化initState()
- 初始化的顺序依次是 prop>methods>data>computed>watch,
- initData()的时候会将data挂载到vm实例上,这样我们以就可使用this.data代替this.$options.data
- 同时通过observe(data)对数据进行观测(),在observe中对data的所有属性进行遍历,并通过object.defineProperty()对属性进行数据劫持,如果属性的值还是一个对象,就会再递归遍历,进行数据劫持
- 对数组,预先设置7个方法,如果是这几种方法,会调用Observer实例的observeArray对数组每一项进行观测
注意:
对象新增或者删除属性无法被 set 监听到 只有对象本身存在的属性修改才会被劫持
二、发布订阅模式和观察者模式的区别
1、发布订阅模式
发布订阅模式中,包含发布者,,订阅者三个角色。
发布者和订阅者是松散耦合的,互不关心对方是否存在,他们关注的是事件本身
发布者借用事件调度中心提供的方法发布事件,而订阅者则通过进行订阅。
2、观察者模式
观察者模式只有两个主体,分别是目标对象Subject,观察者Observer。
目标对象只管理一种事件,需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。
参考
三、v-for的key有什么作用
key主要作用是 高效地更新DOM,patch的时候会执行patchVnode,然后执行updateChildren方法,更新新旧元素,通过key可以精准的判断新旧节点是否为同一个节点,如果没有key就会认为是同一个节点,这样就能避免很多的节点更新操作,结合通过判断首尾的一致性的方法更高效地更新dom。
四、diff算法
深度优先,同级比较,左右根
节点patch的策略,首先看双方是否都有孩子,都有、一方有,都没有分别对应不同的操作
总结:
1、diff算法通过 高效比较 新旧虚拟DOM,将变化的地方更新到真实DOM
2、通过diff算法可以 精确找到 变化了的地方
3、Vue中diff执行的时刻是组件实例执行其更新函数时,他会比对上一次渲染结果oldVnode和新渲染结果newVnode,此过程称为patch
4、diff过程整体遵循深度优先、同层比较的策略;两个节点之间比较会根据他们是否拥有子节点或文本节点做不同的操作;比较子节点是算法的重点,首先假设头尾节点可能相同做4次对比,如果没有找到相同才会按照通用的方式遍历查找,查找结束再按情况处理剩下的节点,借助key可以非常精确的找到相同的节点,使整体patch过程非常高效
五、组件化
- 将独立的功能和模块单独提出来,把他切分为更小的块,具有独立的逻辑,更好的复用性
- 定义:
Vue.component('comp',{})
- 提高开发效率、复用性、可维护性
- 组件分类有:页面组件、业务组件、通用组件
- 常见的组件化技术:prop、自定义时间、插槽等
- 单向数据流
六、对Vue设计原则的理解
- 渐进式javascript框架
- 易用、灵活和高效
灵活:应用足够小,我们只需要vue核心就i可以,随着应用变大,我们才可能引入路由、状态管理、vue-cli等工具
高效:超快的虚拟DOM和diff算法
七、谈谈对MVC、MVP、MVVM的理解
讲历史
web1.0时代:jsp,每个文件中同事包含了html、css,js、java等,前后端代码混在一起,难以维护,SSH等框架
web2.0时代,ajax开始出现,前后端开始分离
MVC:
View(将model数据展示)
Controller(根据业务逻辑,修改model数据)
Model(拿到后端数据)
在 Controller 里面把 Model 的数据赋值给 View
MVVM
Model(拿到后端数据)
View(将model数据展示)
ViewModel(通过一套机制自动响应Model中数据的变化),减少了大量操作DOM的代码
总结:
MVVM 与 MVC 最大的区别就是:它实现了 View 和 Model 的自动同步,也就是当 Model 的属性改变时,我们不用再自己手动操作 Dom 元素,来改变 View 的显示,而是改变属性后该属性对应 View 层显示会自动改变(对应Vue数据驱动的思想)
- 三者都是框架模式,目的是解决Model和View的耦合问题
- MVC出现在后端,优点是分层清晰,缺点是数据流混乱,不灵活,维护困难
- MVVM,解决耦合问题,减少了DOM操作代码,提高了开发效率,容易维护
Vue 并没有完全遵循 MVVM
严格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 这个属性,让 Model 可以直接操作 View,违反了这一规定,所以说 Vue 没有完全遵循 MVVM。
八、VUE优化
<1> 代码层面
- 多用v-show代替v-if,大组件,v-show总是会被渲染,只是通过css的display进行切换
- v-for和v-if不同时使用,同时使用会:渲染每一项都会先遍历,再判断if,这样影响渲染效率,如果只需要渲染一小部分,就可以通过computed进行处理
-
computed:使用场景: 需要数值计算,并且依赖其他数据时。
优点:有缓存,只有当它依赖的其他数据有变化,才会重新计算
watch:数据变化的时候需要进行异步操作或者开销较大的操作的时候使用 -
长列表优化:
- 情景一:如果有大量数据要进行展示,只是单纯进行展示,可以通过Object.freeze()禁止数据劫持
- 情景二:优化无限滚动列表的性能,利用窗口化的技术,只渲染少部分区域的内容,减少重新渲染dom的时间,使用vue-virtual-scroll-list
- 图片懒加载,通过vue-lazyload插件,标签中使用v-lazy
- 路由懒加载,将不同的路由对应的组件分割成不同的代码块,然后当路由被访问的时候才去加载对应的组件,加载效率会更快
- 第三方插件按需引入,利用babel-plugin-component
- SSR:
- 优点:
a> 更好的SEO,单页应用的页面内容是空的,SSR返回的是渲染之后返回的是拥有真实DOM节点的html文件,更容易搜索到
b> 首屏更快,Vue要等到所有Vue编译后的js文件都下载完成后,才开始进行页面的渲染,文件下载和dom节点的渲染都需要时间;但是,SSR直接返回的就是已经渲染好的html页面,更快 - 缺点:
a> 开发条件限制,只有beforeCreate和create两个钩子函数
b> 运行环境是node Server
c> 服务器负载更大,占用cpu资源
d> 如果只需要改善少数几个页面,则不必使用SSR,使用预渲染,通过prerender-spa-plugin添加预渲染
- keep-alive缓存页面减少dom渲染
- 定时器销毁、事件监听器解绑
<2> webpack层面的优化
1、图片优化:通过url-loader的limit,小于limit的图片转化为base64格式,对较大的图片资源,使用image-webpack-loader来压缩图片
2、提取公共代码:通过CommonsChunkPlugin提取多个页面的公共代码
- 如果没有将第三方库和公共资源提取出来,会有2个问题
1、相同的资源被重复加载,浪费用户流量和服务器成本
2、每个页面需要加载的资源太大,导致网页首屏加载缓慢
九、Vue3.0新特性
更快
- 虚拟DOM重写
- 优化slots的生成
- 静态树的提升
- 静态属性提升
5.基于proxy的响应式系统
更小
优化核心库体积
更易维护
ts+模块化
更友好
跨平台
更易用
更好的ts支持
更好的调试支持
独立的相应化模块
十、父子组件传值
1. prop和$emit
- 父传子:prop
- 子传父:$emit
2. $refs、$parent和$children
- $refs,为子组件指定一个名字通过$refs拿到子组件的data和methods
- $children,拿到所有的子组件(数组),但是不保证顺序
- $parent,拿到上一级父组件的data和methods
- $root,当有多级父组件时,$root拿到根父组件
3. $attrs 和 $listeners
适用的场景是A>B>C这种,A和C组件进行交流可以使用这两个属性
- C组件中能直接触发A组件中的方法test的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性
- 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props中声明的)
4. provide和inject
父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量
5. eventBus
- 初始化
方式一:
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
方式二
// main.js
//这种方式初始化的EventBus是一个全局的事件总线
Vue.prototype.$EventBus = new Vue()
- 发送事件
//A页面
import { EventBus } from "../event-bus.js";
EventBus.$emit("aMsg", '来自A页面的消息');
- 接收事件
//B页面
import { EventBus } from "../event-bus.js";
EventBus.$on("aMsg", (msg) => {
// A发送来的消息
this.msg = msg;
});
- 缺点:
如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。还要就是如果业务有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。
- 解决方法:
vue页面销毁时,同时移除EventBus事件监听
EventBus.$off()
全局eventBus(基于发布订阅模式)
- 创建
var EventBus = new Vue();
Object.defineProperties(Vue.prototype, {
$bus: {
get: function () {
return EventBus
}
}
})
- 传递数据
this.$bus.$emit('nameOfEvent', { ... pass some event data ...});
this.$bus.$on('nameOfEvent',($event) => {
// ...
})
//移除监听
this.$bus.$off('nameOfEvent')
十一、虚拟 DOM 是什么 有什么优缺点
Virtual DOM 本质就是用一个原生的 JS 对象去描述一个 DOM 节点,是对真实 DOM 的一层抽象。
优点:
- 保证性能下限:直接操作DOM非常影响性能,虚拟 DOM可以保证比直接操作DOM更好的性能
- 无需手动操作 DOM:只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图
- 跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关
缺点:
- 更深层次的优化困难
- 首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入慢
十二、路由守卫
3种:全局的、组件内的、单个路由独享三种
全局的:beforeEach、afterEach
单个路由独享:beforeEnter,
- 与全局的beforeEach完全相同,如果都设置则在beforeEach之后紧随执行,参数to、from、next
组件内的路由钩子函数: beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
- beforeRouteEnter:路由进入组件之前调用,this不可访问
- beforeRouteUpdate:在当前路由改变时,并且该组件被复用时调用,this可以使用
- beforeRouteLeave:离开该组件时调用,this可以使用
十三、路由传参
<router-link :to="{name:'first',params:{name:'张三'}}">
<router-link :to="{name:'second',query:{name:'李四'}}">
params动态路由传参和query动态路由传参的区别
- query : 在url地址栏上以?拼接了参数路径
- params: 没有拼接
params传参刷新页面会丢失参数,解决办法?
- path: '/first/:name'
{
path: '/first/:name',
name:'first',
component:()=>import( '../components/first.vue' )
}
//跳转方式 1
<router-link to="/first/张三">
//跳转方式 2
<a @click="$router.push({name:'first',params:{name:'张三'}})">
- 手动通过?拼接
<router-link to="/second?name=张三">
<a @click="$router.push('/second?name=张三')">
十四、keep-alive
缓存页面减少反复渲染
使用LRU算法(最近最少使用淘汰)
- 新数据插入到队列尾部,
- 缓存命中的数据也放到队列尾部
- 当数据达到一定量时,从队列头部开始删除访问几率最低的数据
十五、vue-router的实现原理
两种模式:hash和history
hash模式:监听hashchange事件实现更新页面部分内容的操作
history模式:3个事件:pushState、replaceState、popState
十六、路由传参
两种方式: query、params+动态路由
1. query
写法:path/name+query
url展现方式:/detail?id=1&user=123&identity=1&更多参数
数据接收:this.$route.query.id
刷新页面,数据不会丢失
2. params+动态路由
写法: 首先,路由定义 path: '/detail/:id',然后router-link,name+params
数据接收:this.$route.params.id
如果不写/:id,也能进行传参,但是页面刷新后数据就丢失了
十七、Vue的优点
- 轻量级 大小只有几十kb
- 简单易学,国人开发
- 双向数据绑定,数据操作更简单
- 组件化,html页面的封装和服用
- 虚拟DOM操作,减少了操作DOM
十八、computed和watch的适用场景
computed适用于多个数据影响一个属性的场景
watch适用于一个数据影响多个数据的场景
十九、v-if和v-for
v-for的优先级更高
二十、static和assets的区别
相同点
assets和static文件夹中都用来存放静态资源,比如:文件、图片、字体图标、css等
不同点
在打包时(npm run build),assets文件夹中的资源会进行压缩之后才会放入dist中的static中,static文件夹中的资源不会压缩,直接放入dist中的static文件夹中
所以
引入的第三方的文件一般放到static文件夹中,因为第三方的文件已经经过压缩处理,无需再处理,而项目中自己开发的css、js等文件还是放到assets中进行压缩处理
二十一、Vue和jQuery的区别
jquery是使用选择器$方便地获取DOM元素,然后操作DOM对象,数据和视图并没有分离
Vue做到了数据和视图分离,MVVM框架,修改数据无需再操作DOM节点
二十二、Vue页面初始化闪动的问题
会先显示{{message}}这种
- 在CSS中加上
[v-clock]{
display:none
}
2.如果还是没有解决,则在根元素加
style="display=none;" :style="{display:'block'}"
二十三、钩子相关问题
1. 第一次页面加载会触发哪些钩子函数
beforeCreate、created、beforeMount、mounted
2. ajax请求最好放在created里面,因为此时已经可以访问this了,请求到数据就可以直接放在data里面。
这里也碰到过几次,面试官问:ajax请求应该放在哪个生命周期。
3. 关于dom的操作要放在mounted里面,在mounted前面访问dom会是undefined。
4. 每次进入/离开组件都要做一些事情,用什么钩子:
不缓存:
进入的时候可以用created和mounted钩子,离开的时候用beforeDestory和destroyed钩子,beforeDestory可以访问this,destroyed不可以访问this。
缓存了组件:
缓存了组件之后,再次进入组件不会触发beforeCreate、created 、beforeMount、 mounted,如果你想每次进入组件都做一些事情的话,你可以放在activated进入缓存组件的钩子中。
同理:离开缓存组件的时候,beforeDestroy和destroyed并不会触发,可以使用deactivated离开缓存组件的钩子来代替
5. 触发钩子的完整顺序:
将路由导航、keep-alive
、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件:
-
beforeRouteLeave
:路由组件的组件离开路由前钩子,可取消路由离开。 -
beforeEach
: 路由全局前置守卫,可用于登录验证、全局路由loading等。 -
beforeEnter
: 路由独享守卫 -
beforeRouteEnter
: 路由组件的组件进入路由前钩子。 -
beforeResolve
:路由全局解析守卫 -
afterEach
:路由全局后置钩子 -
beforeCreate
:组件生命周期,不能访问this
。 -
created
:组件生命周期,可以访问this
,不能访问dom。 -
beforeMount
:组件生命周期 -
deactivated
: 离开缓存组件a,或者触发a的beforeDestroy
和destroyed
组件销毁钩子。 -
mounted
:访问/操作dom。 -
activated
:进入缓存组件,进入a的嵌套子组件(如果有的话)。 - 执行beforeRouteEnter回调函数next。
二十四、router-link和a标签有什么区别
- 有click事件就先执行click事件
- click的时候,阻止a标签的默认事件(跳转和刷新页面)
- 在取得href,通过history模式跳转,只是链接变了,并不会刷新页面