1.Vue——渐进式JS框架、MVC/MVP/MVVM的介绍
参考答案
库和框架的区别
- 库(Library):本质是一些函数的集合。使用库,是由开发人员决定如何调用库中提供的方法。
- 框架(Framework):是一套完整的解决方案,使用时把代码放到框架合适的地方,框架会在合适的时机调用代码。 学习Vue要转化思想:不要在想着怎么操作DOM,而是想着如何操作数据!!!
MVC、MVP、MVVM的介绍
M: Model 数据模型,用于封装和应用程序的业务逻辑相关的数据以及对数据的处理方法;
V:View 视图视图层,主要负责数据的展示。
MVC
Controller控制器:处理业务逻辑
实线代表方法调用,虚线代表事件通知。
用户对View的操作交给了Controller处理,在Controller中响应View的事件调用Model的接口对数据进行操作,一旦Model发生变化便通知相关视图进行更新。
MVP
Presenter负责业务逻辑
在MVC里,View是可以直接访问Model的,但MVP中的View并不能直接使用Model,而是通过为Presenter提供接口,让Presenter去更新Model,再通过观察者模式更新View。
与MVC相比,MVP模式通过解耦View和Model,完全分离视图和模型使职责划分更加清晰;由于View不依赖Model,可以将View抽离出来做成组件,它只需要提供一系列接口提供给上层操作。
MVVM
"Model of View":视图的模型。
MVVM把View和Model的同步逻辑自动化了。以前Presenter负责的View和Model同步不再手动地进行操作,而是交给框架所提供的数据绑定功能进行负责,只需要告诉它View显示的数据对应的是Model哪一部分即可。
(MVVM中的View通过使用模板语法来声明式的将数据渲染进DOM,当ViewModel对Model进行更新的时候,会通过数据绑定更新到View)
2.Vue跟React的异同点?
参考答案
相同点:
- 1.都使用了虚拟dom
- 2.组件化开发
- 3.都是单向数据流(父子组件之间,不建议子修改父传下来的数据)
- 4.都支持服务端渲染
不同点:
- 1.
React的JSX ——Vue的template - 2.数据变化,
React手动(setState) ——Vue自动(初始化已响应式处理,Object.defineProperty) - 3.
React单向绑定 ——Vue双向绑定 - 4.
React的Redux ——Vue的Vuex
尤雨溪大大的回答:
- 1.vue使用的是web开发者更熟悉的模板与特性,vue的API跟传统web开发者熟悉的模板契合度更高,比如vue的单页面组件是以
模板+JS+CSS的组合模式呈现,它跟web现有的HTML、JavaScript、CSS能够更好地配合。React 的特色在于函数式编程的理念和丰富的技术选型。Vue 比起 React 更容易被前端工程师接受。 - 2.从使用习惯和思维模式上考虑,对于一个没有任何Vue和React基础的web开发者来说, Vue会更友好,更符合他的思维模式。Vue更加注重web开发者的习惯。
- 3.实现上,Vue跟React的最大区别在于数据的reactivity,就是反应式系统上。 Vue提供反应式的数据,当数据改动时,界面就会自动更新,而React里面需要调用方法SetState。我把两者分别称为Push-based和Pull-based。所谓Push-based就是说,改动数据之后,数据本身会把这个改动推送出去,告知渲染系统自动进行渲染。在React里面,它是一个Pull的形式,用户要给系统一个明确的信号说明现在需要重新渲染了,这个系统才会重新渲染。两者并没有绝对的优劣之分,更多的也是思维模式和开发习惯的不同。
3.Vue2.x和Vue3.x的区别?
参考答案
- 1.生命周期
- vue2.x的
beforeCreated和created替换成了setup - 生命周期钩子函数增加了on,增加了用于调试的钩子函数
- vue2.x的
- 2.双向绑定原理
- 3.组件通信方式
- vue3.x使用
mitt.js进行通信,mitt.js足够小,仅200kb,支持全部事件的监听和批量移除,不依赖vue实例,可以跨框架使用
- vue3.x使用
- 4.tree-shaking
- 引入摇树优化,最小化bundle体积
详细可以看这篇:Vue3对比Vue2
4.Vue和JS的区别?
参考答案
-
1.响应的数据绑定/响应式编程
- Vue.js是一个构建数据驱动的 web 界面的渐进式框架。Vue.js 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。核心是一个响应的数据绑定系统。
-
2.组件化开发/组件复用
-
3.虚拟DOM--Diff算法--性能
5.Vue和JQuery的区别?为什么放弃JQuery用Vue?
参考答案
-
jQuery是直接操作DOM,Vue不直接操作DOM,Vue的数据与视图是分开的,开发者只需要把大部分精力放在数据层面上 -
jQuery的操作DOM行为是频繁的,而Vue利用虚拟DOM的技术,大大提高了更新DOM时的性能 -
Vue集成的一些库,大大提高开发效率,比如Vuex,Router等
6.Vue的SSR是什么?有什么好处?
参考答案
SSR就是服务端渲染- 基于
nodejs serve服务环境开发,所有html代码在服务端渲染 - 数据返回给前端,然后前端进行“激活”,即可成为浏览器识别的html代码
SSR首次加载更快,有更好的用户体验,有更好的seo优化,因为爬虫能看到整个页面的内容,如果是vue项目,由于数据还要经过解析,这就造成爬虫并不会等待你的数据加载完成,所以其实Vue项目的seo体验并不是很好
7.vue生命周期
7.1 生命周期
参考答案
vue 2.x的生命周期
-
beforeCreate(初始化界面前)
数据还没有挂载,只是一个空壳,无法访问数据和dom,一般不做操作
-
created(初始化界面后)
绑定事件,挂载数据,并且在这里更新data,不会触发updata函数
-
beforeMount(渲染dom前)
将模板编译为虚拟dom,放到render中准备渲染,在这里更新data,不会触发updata函数
-
mounted(渲染dom后)
渲染出真实dom,可操作真实dom
-
beforeUpdate(更新数据前)
重新生成dom树,根据diff(本质是调用patch函数)算法,对比上一次dom树
-
updated(更新数据后)
数据更新完成render完成
-
beforeDestroy(卸载组件前)
一般在这里做一些善后工作,例如清除计时器、清除非指令绑定的事件等
-
destroyed(卸载组件后)
组件的数据绑定、监听...去掉后只剩下dom空壳
7.2 created 和 mounted 的区别
参考答案
- created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
- mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
注:vue异步请求适合放在created里面,如涉及到需要页面加载完成之后的操作就放在mounted里面。
8.vue路由
8.1路由原理
参考答案
路由的概念来源于服务端,在服务端中路由描述的是 URL 与处理函数之间的映射关系。
在 Web 前端单页应用 SPA(Single Page Application)中,路由描述的是 URL 与 UI 之间的映射关系,这种映射是单向的,即 URL 变化引起 UI 更新(无需刷新页面)。
可以看这篇:Vue Router 路由实现原理
可以看这篇:路由原理
8.2vue路由的模式和区别
参考答案
-
hash模式
hash模式背后的原理是浏览器的onhashchange()事件,可在window对象上进行监听。hash就是指url中#号以及后面的字符,不会包含在http请求中,hash值改变不会导致浏览器向服务端方发送请求,hash发生改变的url会被浏览器记录下来,浏览器前进后退就可以用了。这种页面状态和url一一关联起来,称为前端路由。 -
history模式
- 切换历史状态使用
back()、go()、forward(); - 修改路由状态是利用
HTML5的history中新增的pushState()和replaceState()方法。通过pushState()把页面的状态保存在state对象中,当页面的url再变回这个url时,可通过event.state获取到这个state对象,从而对页面状态进行还原。history模式下,前端的url必须和实际后端发起请求的url一致。
- 切换历史状态使用
8.3vue路由传参的基本方式
参考答案
路由传参方式就两种query和params,传参形式有以下三种:
router-link形式- 通过
path匹配路由的编程式导航形式 - 通过
name匹配路由的编程式导航形式
这两篇讲的很清楚,可以看看:
8.4 query和params传参的区别
参考答案
- query传递参数
query传递参数不会出现参数丢失的情况,不需要做其他配置,缺点就是参数会拼接到url后面:url?xx==yy这种方式来传递,会暴露参数,并且url也有字符长度的限制。
使用方式: this.$router.push({path: 'path', query: {id:1}})
获取参数: this.$route.query.id获取key为id的路由参数
- params传递参数
params传递参数是将参数放在
route对象中,没有放在url后面,但是在跳转之后的页面刷新时,会导致当前路由中保存的params的参数丢失。
使用方式:this.$router.push({name: 'name', params:{id:1}})
获取参数: this.$route.params.id 获取route对象中的Params的参数信息
解决使用params传递参数刷新页面参数丢失:
- 使用sessionStorage、localStorage
sessionStorage、localStorage根据具体的场景来选择,保存到里面的数据不会在刷新下的时候丢失,不过在移动端,使用微信悬浮窗的时候,部分安卓机型的sessionStorage中的数据会丢失。
- 使用params中的路由匹配
使用方式: 在router.js,即匹配路由规则的位置,加上占位符即可
{
path: '/index/:num/:name',
name: 'index',
component: Index
}
params中的参数名称需要和占位符的名称一致即可
获取参数,还是和获取params中参数一致:this.$route.params.name
这样的话,参数就会出现在url中,格式为:url/num/name,这种方式将参数放在url上,刷新的时候才不会丢失数据。
8.5路由按需加载
参考答案
路由懒加载的方式有两种,一种是require 另一种是 import 。当路由按需加载后,那么Vue服务在第一次加载时的压力就能够相应的小一些,不会出现 超长白屏P0问题 。下面是两种路由懒加载的写法:
// require法
component: resolve=>(require(['@/components/HelloWorld'],resolve))
// import
component: () => import('@/components/HelloWorld')
8.6路由守卫
参考答案
- 全局守卫
router.beforeEach对所有的路由都起作用,全局守卫有三个参数:
to:即将要进入的目标 路由对象
from:当前导航正要离开的路由对象
next:参数不同,做的事也不同
* next() 直接进入下一个钩子
* next(false) 停止当前导航
* next('/path') 跳转到path路由地址,也可写成对象形式next({path:'/path'})
* next(error) 如果传入参数是一个error实例,则导航会终止且该错误会被传递给router.onError()
router.beforeEach((to, from, next) => {
next();//使用时,千万不能漏写next!!!
}).catch(()=>{
//跳转失败页面
next({ path: '/error', replace: true, query: { back: false }}
)
})
- 路由独享的守卫
beforeEnter路由对象独享的守卫写在routes里面
const router = new VueRouter({
routes: [
{
path: '/goods',
component: goods,
beforeEnter: (to, from, next) => {
// 和全局守卫一样的用法
}
}
]
})
- 组件内的守卫
-
beforeRouteEnter进入路由前,组件还没有被实例化所以这里无法获取到this -
beforeRouteUpdate(2.2)这个阶段可以获取this,在路由复用同一个组件时触发 -
beforeRouteLeave这个阶段可以获取this,当离开组件对应的路由时,此时可以用来保存数据,或数据初始化,或关闭定时器等等
9.keep-alive介绍与应用
参考答案
keep-alive是一个抽象组件,自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁他们。
应用场景
用户在某个列表页面选择筛选条件过滤出一份数据列表,由列表页面进入数据详情页面,再返回该列表页面,我们希望:列表页面可以保留用户的筛选(选中)状态。keep-alive就是用来解决这种场景问题。当然keep-alive不仅仅是能够保存页面/组件的状态这么简单,它还可以避免组件反复创建和渲染,有效提升系统性能。 总的来说,keep-alive用于保存组件的渲染状态。
keep-alive原理
可以看这篇:keep-alive实现原理
keep-alive用法
- 在动态组件中
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<component :is="currentComponent"></component>
</keep-alive>
- 在vue-router中的应用
<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
<router-view></router-view>
</keep-alive>
include定义缓存白名单,keep-alive会缓存命中的组件;exclude定义缓存黑名单,被命中的组件将不会被缓存;max定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。
keep-alive不会生成真正的DOM节点,这是怎么做到的?
Vue在初始化生命周期的时候,为组件实例建立父子关系会根据abstract属性决定是否忽略某个组件。在keep-alive中,设置了abstract: true,那Vue就会跳过该组件实例。最后构建的组件树中就不会包含keep-alive组件,由组件树渲染成的DOM树也不会有keep-alive相关的节点。
keep-alive的生命周期- 初次进入时:
created>mounted>activated- 退出后触发
deactivated
- 再次进入:
- 只会触发
activated
- 只会触发
- 事件挂载的方法等,只执行一次的放在
mounted中;组件每次进去执行的方法放在activated中
- 初次进入时:
源码及更多可看参考文档:彻底揭秘keep-alive原理
10.vue组件中的data为什么必须是一个函数
参考答案
组件是可以被复用的,如果data是一个对象,那么注册一个组件本质是创建一个组件构造器的引用,当我们真正使用组件时才会将组件实例化。实例化对象component1,component2共享同样的对象data,当你修改实例属性时,data也会发生改变。当data是一个函数时,每一个实例的data属性都是独立的,不相互影响。
11.v-if和v-show的区别
参考答案
- 手段
v-if是动态向DOM树中增删元素;v-show是通过DOM元素的display样式属性控制显隐
- 编译过程
v-if有一个局部编译/卸载的过程,切换过程会销毁和重建;v-show只是基于CSS的切换
- 编译条件
v-if只有条件为真才会编译;v-show在任何条件下都被编译,只是显示还是隐藏罢了
- 性能消耗
v-if切换消耗高;v-show初次渲染消耗高
- 使用场景
v-if适用于不太可能改变的情况;v-show适用于频繁切换的情况
12.watch和computed的区别、computed和methods区别
参考答案
-
computed --get/set方式
- 支持缓存,只有一栏数据发生改变,才会重新计算
- 不支持异步,异步操作无效
- 计算属性是基于响应式依赖进行缓存的,也就是基于
data中声明过或父组件传递props中的数据通过计算得到的值 - 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是多对一或一对一,一般用
computed
-
watch--类似于监听机制+事件机制
- 不支持缓存,数据改变会直接触发响应操作
- 支持异步
- 监听的函数接收两个参数(oldVal, newVal)
- 当一个属性发生改变时,需要执行对应的操作,一对多
- 监听数据必须是
data中声明过或父组件传递props中的数据通过计算得到的值 - watch函数的属性:
immediate:组件加载立即触发回调函数执行deep:true深度监听,递归对对象内部进行监听sync:不将watch加入到nextTick队列而同步的更新
-
methods
- 挂载在对象上的函数,通常是 Vue实例本身 或 Vue组件 ,
methods里面是用来定义函数的,很显然,它需要主动调用才能执行(像"fuc()"这样去调用它),而不像watch和computed那样,“自动执行”预先定义的函数。
- 挂载在对象上的函数,通常是 Vue实例本身 或 Vue组件 ,
-
methods与computed区别
- 调用方式不同: computed直接以对象属性方式调用,不需要加括号,而methods必须要函数执行才可以得到结果
- 绑定方式不同: methods与compute纯get方式都是单向绑定,不可以更改输入框中的值。compute的get与set方式是真正的双向绑定。 是否存在缓存。methods没有缓存,调用相同的值计算还是会重新计算。competed有缓存,在值不变的情况下不会再次计算,而是直接使用缓存中的值。
13.vue组件中的slot作用
参考答案
插槽,其实就相当于占位符。它在组件中给你的HTML模板占了一个位置,让你来传入一些东西。插槽又分为匿名插槽、具名插槽以及作用域插槽。
14.Vue.set 和 Vue.delete( 对象新属性/删除属性无法更新视图 )
参考答案
vue声明在data中的属性都是响应式的,也就是说,我们修改data中的属性时,一般页面都能实时更新。但是由于ES5的限制,vue不能检测数组和对象的变化。比如我们对data中的对象属性和数组属性进行一些修改时,无法响应式更新渲染到页面,因此vue提供了$set和$delete的API来解决这个限制。
简单来说:set和delete的源码的话是对 数组 和 对象 分别进行了判定。底层源码本质:如果是数组,是利用splice方法进行增删修改,对象的话会判断key值是否有效,来进行设置和修改,然后通知dom更新。
Vue.set == $set ,Vue.delete == $delete
大致流程:
- 1.判断目标值是否为有效值,不是有效值直接停止
- 2.判断是否为数组,并且key值是否为有效的key值
- 对比数组的key值和数组长度,取较大值设置为数组的长度
- 用数组的splice方法替换目标值
- 3.判断目标值是否为响应式的__ob__
- 如果是vue实例,直接警告
- 如果不是响应式的数据,就是普通的修改对象操作
- 如果是响应式数据,那就通过Object.defineProperty进行数据劫持
- 4.通知dom更新 下面来看vue.set代码:
export function set (target: Array<any> | Object, key: any, val: any): any {
// 先判断目标值是否是有效值,不是直接停止,警告
if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target)) ) {
warn(`Cannot set reactive property on undefined,
null, or primitive value: ${(target: any)}`)
}
// 判断目标值是否为数组,并且key值是否为有效的数组索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
//对比数组的key值和数组长度,取较大值设置为数组的长度
target.length = Math.max(target.length, key)
// 目标值替换
target.splice(key, 1, val) return val
}
// 如果目标值是对象,并且key值是目标指存在的有效key值,且不是原型上的key值
if (key in target && !(key in Object.prototype)) {
target[key] = val return val // 目标值更改
}
const ob = (target: any).__ob__ // 判断目标值是否为响应式的,该属性为true标志着target是响应式对象
if (target._isVue || (ob && ob.vmCount)) { //如果是vue根实例,就警告
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.' )
return val }
if (!ob) { // 如果目标值不是响应式的,那么值需要给对应的key赋值
target[key] = val
return val
}
// 其他情况,目标值是响应式的,就通过Object.defineProperty进行数据监听
defineReactive(ob.value, key, val)
// 通知更新dom操作
ob.dep.notify()
return val
}
set 函数接收三个参数:第一个参数 target 是将要被添加属性的对象,第二个参数 key 以及第三个参数 val分别是要添加属性的键名和值。
错误写法:this.$set(key,value)(ps: 可能是vue1.0的写法)
mounted () {
this.$set(this.student.age, 24)
}
正确写法:this.$set(this.data,”key”,value') ---三个参数
mounted () {
this.$set(this.student,"age", 24)
}
下面来看vue.delete代码:
export function del (target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&(isUndef(target) || isPrimitive(target)) ) {
warn(`Cannot delete reactive property on undefined,
null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1) return // 数组的处理
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.' )
return
}
if (!hasOwn(target, key)) { // 对象的处理
return
}
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}
del 函数接收两个参数,分别是将要被删除属性的目标对象 target 以及要删除属性的键名 key.与set源码原理大致相同。
15.vue组件通信方式
参考答案:vue中8种组件通信方式
16.vue的Diff算法、key的作用
16.1.当数据发生改变时,vue是如何更新节点的?
参考答案
首先,根据真实DOM生成一颗虚拟DOM,当虚拟DOM的节点数据改变后会生成一个新的Vnode,然后Vnode和oldNode进行对比,发现不一样的地方直接修改在真实DOM上,然后使oldVNode的值为Vnode。节点比较的过程就是用的Diff算法。(注:VNode和oldVNode都是对象,是以对象的形式模拟树形结构)
16.2.Diff的比较方法
参考答案
Diff算法比较新旧节点的时候,比较只会在同层级进行,不会跨级比较。如下图:
16.3.Diff流程图
Diff流程图
当数据发生改变时,set方法会调用Dep.notify通知所有订阅者,订阅者会调用patch函数比较新旧节点,给真实DOM打补丁,更新相应的视图。
16.4.源码分析
看这篇很详细:详解vue的Diff算法
16.5.Vue中key的作用
参考答案
key的作用主要是为了更高效地更新虚拟DOM,给每个节点唯一的标识。在Diff算法中,key是为了在diff算法执行时更快的找到对应节点,提高diff速度。当页面发生变化时,diff算法只会比较同一层级的节点。
- 节点类型不同,直接干掉前面的节点,再创建插入新节点,不再比较之后的节点
- 节点类型相同,则会设置该节点的属性,从而实现节点的更新
16.5 Virtual Dom的效率一定比直接操作Dom高吗
可以看这篇:尤雨溪的回答
17.vue.nextTick作用和应用场景
18.Vue模板编译原理
参考答案
关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,这三个部分是有前后关系的:
- 第一步是将
模板字符串转换成element ASTs(解析器) - 第二步是对
AST进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器),静态节点指的是DOM不需要发生变化的节点。- 标记静态节点有两个好处:1.每次重新渲染的时候不需要为静态节点创建新节点; 2.在 Virtual DOM 中 patching 的过程可以被跳过
- 优化器的实现原理主要分两步:1.用递归的方式将所有节点添加
static属性,标识是不是静态节点; 2.标记所有静态根节点
- 第三步是 使用
element ASTs生成render函数代码字符串(代码生成器)
更多可以看这篇: Vue源码之模板编译原理
19.VueX
参考答案
1.什么是Vuex?
Vuex 是一个专为 Vue.js 应用程序开发的状态管理插件。它采用集中式存储管理应用的所有组件的状态,而更改状态的唯一方法是提交mutation。
2.解决了什么问题?应用场景?
- 多个组件依赖同一状态时,多层嵌套的组件传参很繁琐,用vuex能很好地管理状态。
- 来自不同组件的行为需要变更同一状态时,集中存储管理。
3.Vuex的五个核心属性
const store = new Vuex.Store({
state: {
count: 1
},
getters: {
},
mutations: {
increment (state) {
state.count++; // 变更状态
}
},
actions: {
increment (context) {
context.commit('increment')
}
},
})
- state:存放数据
- getters:获取
state数据的状态,可以对数据进行简单操作 - mutations: 更改store中修改状态,必须是同步函数,通过
this.$store.commit(increment', 10)来提交 - actions: 提交的是 mutation,而不是直接变更状态。可以包含任意异步操作,在组件中使用
this.$store.dispatch('xxx',data)分发action - module:将store分割成模块(module),每个模块拥有自己的state、mutation、action、getter,甚至是嵌套子模块。
const moduleA = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: () => ({ ... }),
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
4.在v-model上使用Vuex中state的值
需要通过computed计算属性来转换。
<input v-model="message">
// ...
computed: {
message: {
get () {
return this.$store.state.message
},
set (value) {
this.$store.commit('updateMessage', value)
}
}
}
20.vue2.x和vue3.x双向数据绑定原理区别
参考答案:vue2.x双向绑定原理