mvc,mvp与mvvm
MVC(Model-View-Controller):
视图(view):用户视图
控制器(Controller):业务逻辑
模型(Model):数据保存
view传送指令到controller,controller 完成业务逻辑后,要求model 改变状态,model 将新的数据发送到 view,用户得到反馈。
MVP(Model-View-Presenter):
各部分之间的通信都是双向的,view和model不发生联系,都是通过presenter传递。view非常轻量,不部署任何业务逻辑,所有逻辑都是通过presenter处理。
MVVM(Model-View-ViewModel):
将mvp的presenter替换为ViewModel,增加了双向绑定,view的改变会自动反应到ViewModel上。
vue组件的生命周期
1、单组件生命周期
创建阶段:
beforeCreate:实例初始化后,不能获取dom。
created:vue实例已经创建,但是只是存在于js中的变量,还没开始渲染,仍不能获取dom。
挂载阶段:
beforeMount:根节点已经创建,但还是不能获取到具体的dom。
mounted:组件绘制完成,数据和dom已经渲染完成,一般数据请求放在这个阶段。
更新阶段:
beforeUpdate:vue遵循数据驱动DOM 的原则,beforeUpdate 函数在数据更新后没有立即更新数据,但是DOM 数据会改变,这是双向数据绑定的作用。
updated:这一阶段,DOM 会和更改过的内容同步。
销毁阶段:
beforeDestory:当我们不再需要 vue 操纵 DOM 时,就要销毁 vue,也就是清除vue 实例与 DOM 的关联,调用destroy方法可以销毁当前组件。在销毁前,会触发 beforeDestroy 钩子函数。
destroyed:销毁完成。
2、父子组件生命周期
挂载阶段:
父 beforeCreate --> 父 created --> 父 beforeMount --> 子 beforeCreate --> 子 created --> 子 beforeMount --> 子 mounted --> 父 mounted
更新阶段:
父 beforeUpdate --> 子 beforeUpdate --> 子 updated --> 父 updated
销毁阶段:
父 beforeDestroy --> 子 beforeDestroy --> 子 destroyed --> 父 destroyed
3、在beforeDestory阶段需要做的事
- 自定义事件解除绑定(eventBus)
- 销毁定时任务(setTimeout、setInterval等)
- 销毁绑定的window和document事件
vue组件间如何通信
-
props/$emit
①在父组件里引用子组件时用v-bind绑定要传递的属性,在子组件中用props接收。(父传子)父组件:
<template> <div id="app"> <A v-bind:text="text"></A> </div> </template> <script> import A from "./components/A" export default { name: 'App', data(){ return { text:"这是传给A的消息", } }, components: { A, } } </script> <style> </style>
子组件:
<template> <div> {{text}} </div> </template> <script> export default { name: 'A', props: { text:{ type:String } } } </script> <style scoped> </style>
②在父组件里引用子组件时用v-on绑定事件调用父组件的方法,在子组件中使用this. $emit触发事件传递参数。(子传父)
父组件:
<template> <div id="app"> {{title}} <B v-on:sendMessage="setTitle"></B> </div> </template> <script> import B from "./components/B" export default { name: 'App', data(){ return { title:"", } }, methods:{ setTitle(msg){ this.title = msg; } }, components: { B, } } </script> <style>
子组件:
<template> <button @click="message">给父元素发消息</button> </template> <script> export default { name: "B", methods:{ message(){ this.$emit("sendMessage","这是b传递的标题"); } } } </script> <style scoped> </style>
适用场景:父子组件通信
-
eventBus
eventBus = new vue()创建import Vue from 'vue' export const eventBus = new Vue();
需要接收参数的地方通过eventBus.$on(“事件名”,prams=>{})定义事件
<template> <div id="app"> {{title}} <A></A> </div> </template> <script> import A from "./components/A" import {eventBus} from "./assets/eventBus"; export default { name: 'App', data(){ return { title:"aaa", } }, mounted() { eventBus.$on("setTitle", (msg) => { //这里必须使用箭头函数,不然this不指向当前组件 alert(this.title); this.title = msg; }) }, components: { A, } } </script> <style> </style>
需要传递参数的地方通过event.$emit(“事件名”,参数)触发对应的事件
<template> <div> <button @click="sendMessage">发送消息</button> </div> </template> <script> import {eventBus} from "@/assets/eventBus"; export default { name: 'A', props: { text:{ type:String } }, methods:{ sendMessage() { eventBus.$emit("setTitle","这是A组件通过事件总线传递的title"); } } } </script> <style scoped> </style>
适用场景:父子组件通信、兄弟组件通信、跨级通信
-
$attrs / $listeners
$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。
$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。所以我们可以通过 $attrs获取上级传递的参数,也可以通过 $listeners触发上级的事件方法给上级传递参数。
父组件:
<template> <div id="app"> <A :name="name" :age = age @setName = "setName" @setAge = "setAge"></A> </div> </template> <script> import A from "./components/A" export default { name: 'App', data(){ return { name:"张三", age:24 } }, methods:{ setName(name){ this.name = name; }, setAge(age){ this.age = age; } }, components: { A, } } </script> <style> </style>
子组件:
<template> <div> 姓名:{{$attrs.name}} 年龄:{{$attrs.age}} <button @click="changeName">修改名字</button> <button @click="changeAge">修改年龄</button> </div> </template> <script> export default { name: 'A', methods:{ changeName() { this.$listeners.setName("李四"); }, changeAge() { this.$listeners.setAge(30); } }, } </script> <style scoped> </style>
适用场景:父子组件通信,跨级通信
-
$parent / $children
子组件可以通过 $parent访问父组件实例获取父组件属性,也可以通过 $parent访问父组件方法给父组件传递参数。父组件也可以通过 $children访问所有子组件的属性或方法。
父组件:<template> <div id="app"> {{name}}--{{age}} <br> <A></A> </div> </template> <script> import A from "./components/A" export default { name: 'App', data(){ return { name:"张三", age:24 } }, mounted() { this.name = this.$children[0].name }, methods:{ setAge(age) { this.age = age; } }, components: { A, } } </script> <style> </style>
子组件:
<template> <div> 姓名:{{$parent.name}} 年龄:{{$parent.age}} <button @click="setAge">设置年龄</button> </div> </template> <script> export default { name: 'A', data(){ return { name:"王五" } }, methods:{ setAge() { this.$parent.setAge(30); } } } </script> <style scoped> </style>
适用场景:父子组件通信
-
ref
如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
父组件:<template> <div id="app"> <A ref="comA"></A> </div> </template> <script> import A from "./components/A" export default { name: 'App', mounted() { this.$refs.comA.sayHello(); }, components: { A, } } </script> <style> </style>
子组件:
<template> <div> </div> </template> <script> export default { name: 'A', methods:{ sayHello() { window.alert("hello") } } } </script> <style scoped> </style>
注意:ref只是子到父单向的,通常用于父组件调用子组件中的方法。
-
provide/inject API
祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。父组件:
<template> <div id="app"> <A ref="comA"></A> </div> </template> <script> import A from "./components/A" export default { name: 'App', provide:{ name:"张三" }, components: { A, } } </script> <style> </style>
子组件:
<template> <div> {{name}} </div> </template> <script> export default { name: 'A', inject:["name"], mounted() { alert(this.name); } } </script> <style scoped> </style>
注意:provide注入的属性是非响应式的,也就是说父组件属性修改了不会自动更新到子组件。
provide/inject实现响应式的方法:
----provide祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
----使用2.6最新API Vue.observable 优化响应式 provide(推荐)
适用场景:父子组件通信、跨级通信 -
vuex
vuex文档
适用场景:父子组件通信、跨级通信、兄弟组件通信 -
v-model
借助v-model的原理也可以实现父子组件通信。
v-bind和v-on:input的结合,即监听了表单的input事件,然后修改value属性对应的值
父组件:<template> <div id="app"> <A v-model="name"></A> </div> </template> <script> import A from "./components/A" export default { name: 'App', data(){ return { name:"张三" } }, components: { A, } } </script> <style> </style>
子组件:
<template> <div> {{value}} </div> </template> <script> export default { name: 'A', props: { value:{ type:String } }, mounted() { this.$emit("input","王麻子") } } </script> <style scoped> </style>
所以v-model实现通信其实是一种语法糖,其本质还是props和$emit
v-show 和 v-if的区别
v-show是css display控制显示和隐藏,而v-if是dom真正的渲染和销毁。需要频繁切换显示状态的就用v-show,否则用v-if。
v-show 和 keep-alive的区别
keep-alive 是在vue 框架层级进行的JS 对象渲染,一般简单的可用 v-show,复杂一点的一般用 keep-alive,keep-alive 通常用于 tab 的切换,性能优化。
为何 v-for 要用 key
必须要用 key, 而且不能用 index 和 random,key是vue中vnode的唯一标记,通过这个key,我们的diff操作可以更准确,更快速;在 diff 算法中用 tag 和 key来判断,是否是sameNode;可以减少渲染次数,提高渲染性能。
computed 和 watch,methods 的区别
computed是计算属性,具有缓存,当依赖的数据发生变化时才会重新计算,一般是对应多个属性。
watch是监听器,一般用于数据变化后需要执行异步或开销较大的操作时。
methods是组件的方法,每当触发重新渲染时,调用方法总会再次执行函数。
computed为什么需要缓存?
可以提高性能。假设我们有一个性能开销比较大的计算属性A,它需要遍历一个巨大的数组做大量的计算,然后我们可能有其他的属性依赖于A,如果没有缓存,每次使用时都需要做大量重复的计算。
computed原理
1、初始化 data和computed,分别代理其set以及get方法, 对data中的所有属性生成唯一的dep实例。
2、对computed中的每个计算属性生成唯一watcher,并保存在vm._computedWatchers中
3、访问计算属性时,设置Dep.target指向该计算属性的watcher,调用该属性具体方法。
4、方法中会访问到data中的属性,调用该属性代理的get方法。将该属性的dep加入计算属性的watcher,同时在该dep的subs这添加这个watcher。
5、当我们改变属性的值时,set方法会调用dep.notify。
6、计算属性的watcher收到通知后,将自己的dirty设置为true。
7、当我们使用计算属性时,访问计算数学的get方法,发现dirty是true,,调用watcher.evaluate()方法获取新的值。
路由的常用的模式及其原理
- hash模式:onhashchange事件监听hash变化,参数加在#后面。
- history模式:根据history api中的pushState,replaceState两个方法改变路径,需要后端配合做一些配置。
$ router和$ route的区别
$ route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数。
而$router是“路由实例”对象包括了路由的跳转方法,钩子函数等。
路由有哪些钩子?
-
全局钩子
beforeEach:全局前置守卫。
beforeResolve:全局解析守卫。
afterEach:全局后置守卫。 -
路由独享守卫
beforeEnter -
组件内守卫
beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
解析流程:
触发导航 - > beforeRouteLeave - > beforeEach - > beforeRouteUpdate (如果重用) - > beforeEnter - >解析异步路由 - > beforeRouteEnter - > beforeResolve - >导航被确认 - > afterEach - > 触发DOM更新 - > beforeRouteEnter 中的next
如何配置 vue-router 异步加载?
异步加载性能会优化很多,配置使用函数式:component: () => import(path)
为何组件 data 必须是一个函数?
防止组件重用的时候导致数据相互影响。根本上 .vue 文件编译出来是一个类,这个组件是一个class,我们在使用这个组件的时候相当于对class 实现实例化,在实例化的时候执行data,如果 data不是函数的话那每个组件的实例结果都一样了,共享了,如果 data不是函数的话在一个地方改了,另一个地方也改了。如果data是函数在左边实例化一个右边实例化一个都会执行这个函数,这两个data都在闭包中,两个不会相互影响。
vue 如何监听数组变化
借用原型式继承生成一个拦截器(新的数组原型),重写了原型上push,pop等方法。
vuex 中 action 和 mutation有什么区别?
action 中处理异步,mutation 不可以,action 不能改变state,需要在action中借助mutation 来改变state。
Vue 常见性能优化方式
1、合理使用v-show 和 v-if
2、合理使用compute和watch
3、v-for 时要加key,避免和 v-if 同时使用
4、自定义事件、DOM 事件及时销毁
5、合理的使用异步组件
6、合理使用keep-alive
7、data层级不要太深(因为深度监听一次性监听到底)
8、使用 vue-loader 在开发环境做模板编译(预编译)
9、ssr
loader 和 plugin 的区别
loader :模块转换器
css-loader,babel-loader,less-loader,url-loader,file-loader,vue-loader,style-loader
plugin:扩展插件
HtmlWebpackPlugin:自动生成html文件
ExtractTextWebpackPlugin:把css样式提取成单独的文件
UglifyjsWebpackPlugin:代码压缩
HotModuleReplacementWebpackPlugin:热更新
CommonsChunkPlugin:提取公共模块
DllPlugin& DllReferencePlugin:解决第三方库代码无变化仍然要打包增加打包时间问题,也能解决通过外链引用但可能第三方库还是被打包进来的问题
clean-webpack-plugin:清理上一次项目的bundle文件