vue2.0和3.0对比
一、vue3.0 新特性和原理
1、观察机制
3.0有一个基于 Proxy 的观察者,它会提供全语言覆盖的响应式跟踪。相比于 2.x 版本里基于 Object.defineProperty 的观察者,新的实现更加强大;可以检测属性的新增和删除 可以检测数组索引的变化和 length 的变化 支持 Map、Set、WeakMap 和 WeakSet。
2、其他的运行时增强
体积更小。新的代码库在设计的时候就考虑了 tree-shaking(rollup)。内置的组件(如 )和内置的指令(v-model)是按需引入的,支持 tree-shaking。
速度更快。包括虚拟 DOM 的挂载和更新、组件实例的初始化和观察者的创建。3.0 版本将让你的应用启动时间减少一半。
支持 fragment 和 portal。支持 Fragment(一个组件包含多个根节点)和 Portal(在 DOM 中渲染一个 subtree,而不需要在一个组件中)。
插槽机制增强。所有由编译产生的插槽现在都是函数,这些函数会在子组件的 render 调用时被调用(1、当插槽内容变化时,只有子组件重新渲染;2、当父组件重新渲染时,如果插槽内容没有变化,子组件就不需要重新渲染)----- 更精确的组件树层面上的变更检测,所以会减少很多无用的渲染。
自定义 render。提供一个 API 用来创建自定义的 render,因此你不需要为了自定义一些功能而 fork Vue 的代码。这个特性给 Weex 和 NativeScript Vue 这样的项目提供了很多便利。
3、编译器增强
编译出的内容对 tree-shaking 友好,更多 AOT 优化,更好的错误提示,对 source map 的支持更好。
二、vue2.0 和 vue3.0 对比
1. 默认进行懒观察(lazy observation)。在 2.x 版本里,不过数据多大,都会在一开始就为其创建观察者。当数据很大时,这可能会在页面载入时造成明显的性能压力。3.x 版本,只会对「被用于渲染初始可见部分的数据」创建观察者,而且 3.x 的观察者更高效。
2. 更精准的变更通知。比例来说:2.x 版本中,你使用 Vue.set 来给对象新增一个属性时,这个对象的所有 watcher 都会重新运行;3.x 版本中,只有依赖那个属性的 watcher 才会重新运行。
MVVM框架
model + view + viewmodel 框架,通过 viewmodel(Vue的一个实例) 连接数据模型model 和 view。
DOM Listeners和Data Bindings是实现双向绑定的关键。DOM Listeners监听页面所有View层DOM元素的变化,当发生变化,Model层的数据随之变化;Data Bindings监听Model层的数据,当数据发生变化,View层的DOM元素随之变化。
MVVM框架和普通框架的区别
vue 是数据驱动,通过数据来显示视图层而不是节点操用
Vue2.0中的MVVM实现
Vue2.0的MVVM实现中,对View-Model的实现本质利用的ES5的Object.defineProperty方法,当Object.defineProperty方法在给数据Model对象定义属性的时候先挂载一些方法,在这些方法里实现与界面的值绑定响应关系,当应用的属性被读取或者写入的时候便会触发这些方法,从而达到数据模型里的值发生变化时同步响应到页面上。
( vue.js 是采用 数据劫持 结合 发布者-订阅者 模式的方式,通过Object.defineProperty()来 劫持 各个属性的 setter,getter,在数据变动时 发布消息 给 订阅者,触发相应的 监听回调)。
当new Vue在实例化的时候,首先将data方法里返回的对象属性都挂载上setter方法,而setter方法里将页面上的属性进行绑定,当页面加载时,浏览器提供的DOMContentloaded事件触发后,调用mounted挂载函数,开始获取接口数据,获取完成后给data里属性赋值,赋值的时候触发前面挂载好的setter方法,从而引起页面的联动,达到响应式效果。
Vue实现数据双向绑定的原理:Object.defineProperty
vue实现数据双向绑定主要是:采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty() 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
vue的数据双向绑定 将MVVM作为数据绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听自己的model的数据变化,通过Compile来解析编译模板指令(vue中是用来解析 {{}}),最终利用watcher搭起observer和Compile之间的通信桥梁,达到数据变化 —›视图更新;视图交互变化(input)—›数据model变更双向绑定效果
VUE的数据绑定 ,通俗说:数据变,页面变
vue中 data中的数据都会经过Object.defineProperty进行处理,有了getter和setter
VUE 2.X 版本响应式原理的根本 Object.defineProperty
VUE 3.0 响应式原理是通过proxy原理,为什么要变革?
因为 Object.defineProperty监听不到对象属性的增删、数组元素和长度的变化
实现Vue3.0版本的MVVM
Vue3.0最新的实现方式,用 Proxy 和 Reflect 来替代Object.definePropertypry的方式。可以检测属性的新增和删除 可以检测数组索引的变化和 length 的变化 支持 Map、Set、WeakMap 和 WeakSet。
Proxy是ES6里的新构造函数,它的作用就是代理,简单理解为有一个对象,不想完全对外暴露出去,想做一层在原对象操作前的拦截、检查、代理。
Reflect是ES6里的新的对象,非构造函数,不能用new操作符。可以把它跟Math类比,Math是处理JS中数学问题的方法函数集合,Reflect是JS中对象操作方法函数集合,它暴露出来的方法与Object构造函数所带的静态方法大部分重合,实际功能也类似,Reflect的出现一部分原因是想让开发者不直接使用Object这一类语言层面上的方法,还有一部分原因也是为了完善一些功能。Reflect提供的方法还有一个特点,完全与Proxy构造函数里Hander参数对象中的钩子属性一一对应。
vue3
2:setup函数的生命周期
setup 这个函数是在beforeCreate和created之前运行的,所以你可以用它来代替这两个钩子函数。
VUE3.0中对VUE2.0的写法是完全兼容的,你也可以在setup之外写VUE2.0的生命周期函数。
容易的看出 vue3 的钩子函数基本是再 vue2 的基础上加了一个on,但也有两个钩子函数发生了变化。BeforeDestroy变成了onBeforeUnmount、destroyed变成了onUnmounted
vue2生命周期
Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期
第一次页面加载的时候会触发 beforeCreate, created, beforeMount, mounted 这几个钩子,并且DOM 渲染在 mounted 中就已经完成。
Vue2的生命周期适合那些场景
生命周期钩子的一些使用方法: beforecreate : 可以在这加个loading事件,在加载实例时触发; created : 初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用;mounted : 挂载元素,获取到DOM节点;updated : 如果对数据统一处理,在这里写上相应函数;beforeDestroy : 可以做一个确认停止事件的确认框;nextTick : 更新数据后立即操作domarguments是一个伪数组,没有遍历接口,不能遍历。
vue指令:内部指定指令 和 自定义指令
全局定义指令:在vue对象的directive方法里面有两个参数,一个是指令名称,另外一个是函数。组件内定义指令:directives
钩子函数:bind(绑定事件触发)、inserted(节点插入的时候触发)、update(组件内相关更新)
钩子函数参数:el、binding
Vue计算属性以及watch监听
监听对象的属性变化:
1、监听对象需要深度监听
watch: {
obj: {
handler(newName, oldName) {
console.log('obj.a changed');
},
immediate: true,
deep: true
}
}
2.监听对象里面某个属性的变化,通过computed做中间层实现
computed: {
channel () {
return this.msg.channel
}
},
watch:{
channel(newValue, oldValue) {
// 这里面可以执行一旦监听的值发生变化你想做的操作
console.log('new: %s, old: %s', newval, oldValue)
}
}
vue 中v-html 会导致得问题
- 可能会导致xss攻击
- V-html更新的是元素的 innerHTML 。内容按普通 HTML 插入, 不会作为 Vue 模板进行编译 。
- 但是有的时候我们需要渲染的html片段中有插值表达式,或者按照Vue模板语法给dom元素绑定了事件。
- 在单文件组件里,scoped 的样式不会应用在 v-html 内部,因为那部分 HTML 没有被 Vue 的模板编译器处理。如果你希望针对 v-html 的内容设置带作用域的 CSS,你可以替换为 CSS Modules 或用一个额外的全局 <style>元素手动设置类似 BEM 的作用域策略。
- 后台返回的html片段,以及css样式和js,但是返回的js是不执行的,因为浏览器在渲染的时候并没有将js渲染,这时要在$nextTick中动态创建script标签并插入
V-if和v-show的区别
v-if 条件判断,不满足条件的话则不会出现在dom中
v-show 是否显示,不管满不满足条件均会在dom中,若不满足条件,则会设置成隐藏 display:none
为什么v-for和v-if 不能一起使用
原因:v-for比v-if优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
v-for和v-if不应该一起使用,必要情况下应该替换成computed属性;
v-model 实现原理
v-model就是vue的双向绑定的指令,本质上不过是语法糖,可以用 v-model 指令在表单 <input>
、<textarea>
及 <select>
元素上创建双向数据绑定。
实现v-bind:绑定响应式数据,触发oninput 事件并传递数据。(v-model主要提供了两个功能,view层输入值影响data的属性值,data属性值发生改变会更新view层的数值变化)
详细过程
1、首先在页面初始化时候,vue的编译器会编译该html模板文件,将页面上的dom元素遍历生成一个虚拟的dom树。再递归遍历虚拟的dom的每一个节点。当匹配到其是一个元素而非纯文本,则继续遍历每一个属性。如果遍历到v-model这个属性,则会为这个节点添加一个input事件,当监听从页面输入值的时候,来更新vue实例中的data想对应的属性值
2、同样初始化vue实例时候,会递归遍历data的每一个属性,并且通过defineProperty来监听每一个属性的get,set方法,从而一旦某个属性重新赋值,则能监听到变化来操作相应的页面控制。
<input v-model="sth" /> // 等同于
<input :value="sth" @input="sth = $event.target.value" />
总结
其核心就是,一方面modal层通过defineProperty来劫持每个属性,一旦监听到变化通过相关的页面元素更新。另一方面通过编译模板文件,为控件的v-model绑定input事件,从而页面输入能实时更新相关data属性值。
Vue组件中为什么data必须是函数
在创建或注册模板的时候传入一个 data 属性作为用来绑定的数据。但是在组件中,data必须是一个函数,因为每一个 vue 组件都是一个 vue 实例,通过 new Vue() 实例化,引用同一个对象,如果 data 直接是一个对象的话,那么一旦修改其中一个组件的数据,其他组件相同数据就会被改变,而 data 是函数的话,每个 vue 组件的 data 都因为函数有了自己的作用域,互不干扰。(对象是引用数据类型)
vue 组件通信
方式一:(子父组件)父组件A通过 props 的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
方式二: (父子、兄弟、跨级)通过一个空的Vue实例作为中央事件总线(事件中心)
var Event = new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
方式三:vuex 状态管理器
注意:vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。(localStorage只支持字符串)
JSON.stringify(state.subscribeList); // array -> string
JSON.parse(window.localStorage.getItem("subscribeList")); // string -> array
方法四:$attrs
/$listeners
$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
方法五:provide/inject
父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。
方法六:$parent
/ $children
与 ref
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例$parent
/$children
:访问父 / 子实例
需要注意的是:这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据
以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
常见使用场景可以分为三类
- 父子通信:父向子传递数据是通过 props,子向父是通过 events(
$emit
);通过父链 / 子链也可以通信($parent
/$children
);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
- 兄弟通信:Bus;Vuex
- 跨级通信:Bus;Vuex;provide / inject API、
$attrs/$listeners
vue跨域
1、proxyTable
这里以vue脚手架生成的标准项目为准。一般在项目config目录下面有个index文件。
'use strict'
const path = require('path')
module.exports = {
dev: {
// Paths
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:7001',//后端接口地址
changeOrigin: true, // 是否允许跨越
pathRewrite: {
'^/api': '/api', // 重写,
}
}
},
host: '192.168.0.104',
port: 8081,
autoOpenBrowser: false,
errorOverlay: true,
notifyOnErrors: true,
poll: false,
useEslint: true,
showEslintErrorsInOverlay: false,
devtool: 'eval-source-map',
cacheBusting: true,
cssSourceMap: false,
},
}
2、cors
CORS即跨源资源共享,它定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。它是一个妥协,有更大的灵活性,但比起简单地允许所有这些的要求来说更加安全。但是CORS也具有一定的风险性,比如请求中只能说明来自于一个特定的域但不能验证是否可信,而且也容易被第三方入侵。 这里一般需要后端配合,开启cors。一般各种语言都有类似的包。比如NodeJS的koa2-cors
var koa = require('koa');
//npm install --save koa2-cors
var cors = require('koa2-cors');
var app = koa();
//开启
app.use(cors());
3.Nginx
4、后端程序代理
自己启一个后端程序做代理。然后把所有的请求转发到服务器。这里要用到node的一个包http-proxy-middleware。关键代码(express)如下
var proxy = require('http-proxy-middleware');
var app = express();
app.use('api', proxy({
target: "http://baidu.com:7001",
changeOrigin: true
}))
vuex 处理机制
- Vue Components:Vue组件。HTML页面上,负责接收用户操作等交互行为,执行dispatch方法触发对应action进行回应。
- dispatch:操作行为触发方法,是唯一能执行action的方法。
- actions:操作行为处理模块,由组件中的
$store.dispatch('action 名称', data1)
来触发。然后由commit()来触发mutation的调用 , 间接更新 state。负责处理Vue Components接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台API请求的操作就在这个模块中进行,包括触发其他action以及提交mutation的操作。该模块提供了Promise的封装,以支持action的链式触发。 - commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
- mutations:状态改变操作方法,由actions中的
commit('mutation 名称')
来触发。是Vuex修改state的唯一推荐方法。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些hook暴露出来,以进行state的监控等。 - state:页面状态管理容器对象。集中存储Vue components中data对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用Vue的细粒度数据响应机制来进行高效的状态更新。
- getters:state对象读取方法。图中没有单独列出该模块,应该被包含在了render中,Vue Components通过该方法读取全局state对象。
作用域插槽
父组件在用子组件来填充插槽的时候,有时候需要用到子组件里面插槽的数据 。子组件文件插槽上带的数据 在父组件的子组件标签里
让一个标签 带有slot-scope="xxx" 去接收 以便在下面进行调用
//这是子组件文件
<slot name="jinmao" uname="haomeili" age="250" gender="woman" hobby="eatFoots">
// 这里是父组件
// <com-haomeili> 是注册后的子组件标签
<com-haomeili>
<p slot-scope="recData">
// 这里的recData 就是接收到上面子组件文件里面name叫jinmao的插槽里面传递的数据
// 就可以进行如下使用
我的土金毛叫{{recData.uname}},她今年{{recData.age}}岁了,她的性别是{{recData.gender}},爱好是{{recData.hobby}}
</p>
用 vnode 来描述一个vue.js DOM结构
vnode虚拟节点就是用一个对象来描述真实的 dom 元素,在Vue.js 中 虚拟 DOM 的 JavaScript 对象就是 VNode
diff算法?
虚拟DOM
diff算法的基础是Virtual DOM,Virtual DOM是一棵以JavaScript对象作为基础的树,每一个节点称为VNode,用对象属性来描述节点,实际上它是一层对真实DOM的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说Virtual DOM就是一个Js对象,用以描述整个文档。
diff算法
当数据发生改变时,set
方法会让调用Dep.notify
通知所有订阅者Watcher
数据发生更新,订阅者就会调用patch
进行比较,然后将相应的部分渲染到真实DOM
结构。
patch函数接收两个参数oldVnode
和Vnode(
新的节点和之前的旧节点)
- 判断两节点是否值得比较,值得比较则执行
patchVnode
- 不值得比较则用
Vnode
替换oldVnode
diff算法的处理方法,对操作前后的dom树同一层的节点进行对比,一层一层对比。
时间复杂度
首先进行一次完整的diff需要O(n^3)的时间复杂度,这是一个最小编辑距离的问题,在比较字符串的最小编辑距离时使用动态规划的方案需要的时间复杂度是O(mn),但是对于DOM来说是一个树形结构,而树形结构的最小编辑距离问题的时间复杂度在30多年的演进中从O(m^3n^3)演进到了O(n^3),关于这个问题如果有兴趣的话可以研究一下论文https://grfia.dlsi.ua.es/ml/algorithms/references/editsurvey_bille.pdf。
对于原本想要提高效率而引入的diff算法使用O(n^3)的时间复杂度显然是不太合适的,如果有1000个节点元素将需要进行十亿次比较,这是一个昂贵的算法,所以必须有一些妥协来加快速度,对比较通过一些策略进行简化,将时间复杂度缩小到O(n),虽然并不是最小编辑距离,但是作为编辑距离与时间性能的折中是一个比较好的解决方案。
diff策略
上边提到的O(n)时间复杂度是通过一定策略进行的,React中提到了两个假设,在Vue中同样适用:
- 两个不同类型的元素将产生不同的树。
- 通过渲染器附带key属性,开发者可以示意哪些子元素可能是稳定的。
通俗点说就是:
- 只进行统一层级的比较,如果跨层级的移动则视为创建和删除操作。
- 如果是不同类型的元素,则认为是创建了新的元素,而不会递归比较他们的孩子。
- 如果是列表元素等比较相似的内容,可以通过key来唯一确定是移动还是创建或删除操作。
比较后会出现几种情况,然后进行相应的操作:
- 此节点被添加或移除->添加或移除新的节点。
- 属性被改变->旧属性改为新属性。
- 文本内容被改变->旧内容改为新内容。
- 节点tag或key是否改变->改变则移除后创建新元素。
对孩子是VNode
的三种情况:
有新孩子无旧孩子,直接创建新的。
有旧孩子无新孩子,直接删除旧的。
新旧孩子都有,那么调用
updateChildren
。
v-for为什么要用key?
vue中列表循环需加:key="唯一标识" 唯一标识可以是item里面id index等,因为vue组件高度复用增加Key可以标识组件的唯一性,为了更好地区别各个组件 key的作用主要是为了高效的更新虚拟DOM
描述组件渲染和更新过程
渲染组件时,会通过Vue.extend
方法构建子组件的构造函数,并进行实例化。最终手动调用$mount()
进行挂载。更新组件时会进行patchVnode
流程.核心就是diff算法
createElement是怎么渲染出一个虚拟节点的?
将对象传进去之后,如果是一个string类型,就会认为是一个普通的dom。如果传的是一个对象,会认为是一个组件了,就会调用createComponent方法创建组件,在创建组件的时候会调用一个Vue.extend方法(extend是传入一个对象,可以给你这个对象创建出一个构造函数,这个构造函数就是当时app的构造函数),创建完成之后会给组件加上一些Hooks(钩子函数),是组件的一些内部钩子函数:init,prepatch,insert,destroy。当我们组件初始化的时候会调用init钩子,当我们当时节点被插入的时候会调用insert钩子,当删除的时候会调destroy钩子,当进行比对的时候会调用prepatch钩子。最后返回组件vnode。
vue模板编译原理
Vue中我们有三种方式来创建HTML:
模板、手动写渲染函数、JSX
渲染函数是最原始的方法,而模板最终会通过编译转换陈渲染函数。渲染函数执行后,会得到一份vnode用来渲染真实DOM。所以,模板编译其实是配合虚拟DOM进行渲染,
模板———>模板编译——>渲染函数——>vnode——>用于界面
- 将模板解析为AST(抽象语法树)—— 解析器
- 遍历AST标记静态节点 —— 优化器
- 使用AST生成渲染函数 —— 代码生成器
**AST 和 Vnode 类似,都是使用JavaScript对象来描述节点。更准确的说,一个用对象来描述的节点树就是 AST
解析器分为几个子解析器
- HTML解析器
- 文本解析器 —— 解析带变量的文本
- 过滤器解析器
优化器
优化器的作用是在AST中找出静态子树并打标记。
标记静态节点的好处:
- 每次重新渲染,不需要为静态子树创建新节点
- 可以跳过虚拟DOM中patching过程
优化器主要干了两件事:
- 在AST中找出所有静态节点并打标记
- 在AST中找出所有静态根节点并打标记
AST中分别用 static 和 staticRoot 属性来标记节点是否为静态节点和静态根节点。
- 静态节点:不会发生变化的节点
- 静态根节点:它的子节点都是静态节点,它的父级是动态节点。
代码生成器
生成器的作用是将AST转换成渲染函数中的内容,这个内容也叫代码字符串。
代码字符串被包装进渲染函数,执行渲染函数后,可以得到一份vnode
vue中常见得性能优化
编码优化:
- 不要将所有的数据都放在data中,data中的数据都会增加getter和setter,会收集对应的 watcher
- vue 在 v-for 时给每项元素绑定事件需要用事件代理
- SPA 页面采用keep-alive缓存组件
- 拆分组件( 提高复用性、增加代码的可维护性,减少不必要的渲染 )
- v-if 当值为false时内部指令不会执行,具有阻断功能,很多情况下使用v-if替代v-show
- key 保证唯一性 ( 默认 vue 会采用就地复用策略 )
- Object.freeze 冻结数据
- 合理使用路由懒加载、异步组件
- 尽量采用runtime运行时版本
- 数据持久化的问题 (防抖、节流)
Vue 加载性能优化:
- 第三方模块按需导入 ( babel-plugin-component )
- 滚动到可视区域动态加载 ( vue-virtual-scroll-list ) 图片懒加载 (GitHub - hilongjw/vue-lazyload: A Vue.js plugin for lazyload your Image or Component in your application.)
用户体验:
- app-skeleton 骨架屏
- app-shell app壳 pwa serviceworker
SEO 优化:
- 预渲染插件 prerender-spa-plugin 服务端渲染 ssr
打包优化:
- 使用 cdn 的方式加载第三方模块 多线程打包 happypack
- splitChunks 抽离公共文件
- sourceMap 生成
缓存,压缩
- 客户端缓存
- 服务端缓存
- 服务端 gzip 压缩
vue 相同逻辑如何抽离
Vue.mixin 用法 给组件每个生命周期,函数等都混入一些公共逻辑
但有缺点:①变量来源不能明,不利于阅读
vue要使用异步组件?
1.异步组件可以减少打包的结果。会将异步组件分开打包,会采用异步的方式加载组件,可以有效的解决一个组件过大的问题。不使用异步组件,如果组件功能比较多打包出来的结果就会变大。
2.异步组件的核心可以给组件定义变成一个函数,函数里面可以用import语法,实现文件的分割加载,import语法是webpack提供的,采用的就是jsonp。
keep-alive
<keep-alive>
是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
- include: 字符串或正则表达式。只有匹配的组件会被缓存。
- exclude: 字符串或正则表达式。任何匹配的组件都不会被缓存。
hash 和 history 两种路由的实现
history:新增的两个API history.pushState()
和 history.replaceState()
三个参数:状态对象,标题(当前被忽略)和(可选)URL
区别:
history.replaceState()
操作完全一样history.pushState()
,只是replaceState()
修改当前的历史条目,而不是创建一个新的。请注意,这不会阻止在全局浏览器历史记录中创建新条目。replaceState()
当您想要更新当前历史记录条目的状态对象或URL以响应某些用户操作时,此功能特别有用。
不同之处在于,pushState()
会增加一条新的历史记录,而replaceState()
则会替换当前的历史记录。
注意pushState()
的url不支持跨域
hash:url 中看到 #;路由里的 # 不叫锚点,我们称之为 hash
监听哈希变化触发的事件 ——hashchange
事件;我们用 window.location
处理哈希的改变时不会重新渲染页面,而是当作新页面加到历史记录中,这样我们跳转页面就可以在 hashchange
事件中注册 ajax 从而改变页面内容。
vue-router 路由守卫
const router = new VueRouter({ ... })
// 全局前置守卫
router.beforeEach((to, from, next) => {
// ...
})
// 全局后置钩子
router.afterEach((to, from) => {
// ...
})
// 路由独享的守卫
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
]
})
// 全局解析守卫===2.5.0 新增
// router.beforeResolve 注册一个全局守卫。这和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。
-
to: Route
: 即将要进入的目标 路由对象 -
from: Route
: 当前导航正要离开的路由 -
next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖next
方法的调用参数。-
next()
: 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。 -
next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到from
路由对应的地址。 -
next('/')
或者next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向next
传递任意位置对象,且允许设置诸如replace: true
、name: 'home'
之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。 -
next(error)
: (2.4.0+) 如果传入next
的参数是一个Error
实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
-
组件内守卫:
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) { // (2.2 新增)
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}