共计 12658 个字符,预计需要花费 32 分钟才能阅读完成。
一、简介
vue 几个核心思想:
- 数据驱动
- 组件化
- 虚构 dom、diff 部分最优更新
源码目录介绍
Vue.js 的源码在 src 目录下,其目录构造如下。
src
├── compiler # 编译相干
├── core # 外围代码
├── platforms # 不同平台的反对
├── server # 服务端渲染
├── sfc # .vue 文件解析
├── shared # 共享代码
- compiler:编译相干的代码。它包含把模板解析成 ast 语法树,ast 语法树优化,代码生成等性能。
- core:外围代码,包含内置组件、全局 API 封装,Vue 实例化、观察者、虚构 DOM、工具函数等等。
- platform:不同平台的反对,是 Vue.js 的入口,2 个目录代表 2 个次要入口,别离打包成运行在 web 上和 weex 上的 Vue.js。
- server:服务端渲染,把组件渲染为服务器端的 HTML 字符串,将它们间接发送到浏览器,最初将动态标记 ” 混合 ” 为客户端上齐全交互的应用程序。
- sfc:.vue 文件内容解析成一个 JavaScript 的对象。
- shared:浏览器端和服务端所共享的工具办法。
源码构建
基于 Rollup 构建,相干配置在 scripts 目录下。
构建时通过不同的命令执行不同的脚本,去读取不同用途的配置,而后生成适宜各种场景的 Vue 源码。
vue2.0 有以下几种场景:
- 浏览器端
- 服务端渲染
- 配合 weex 平台在客户端应用
类型查看
在 vue2.x 版本中应用 Flow 作为 js 动态类型查看工具,3.x 版本应用 typescript 实现,自带类型查看。
二、数据驱动
vue 核心思想之一就是 数据驱动
,指 数据
驱动生成 视图
,通过批改数据主动实现对视图的批改。这里次要剖析 模板和数据是如何渲染成最终的 DOM的。
1. new Vue 的过程
Vue 初始化次要就干了几件事件,
- 合并配置
- 初始化生命周期
- 初始化事件核心
- 初始化渲染
- 初始化 data、props、computed、watcher 等等。
2. 实例挂载
$mount 办法
- Vue 不能挂载在 body、html 这样的根节点上;
- 如果没有定义 render 办法,则会把 el 或者 template 字符串转换成 render 办法
- 在 Vue 2.0 版本中所有 Vue 的组件的渲染最终都须要 render 办法,是一个“在线编译”的过程;
挂载组件: mountComponent
外围就是先实例化一个 渲染 Watcher
,在它的回调函数中会调用 updateComponent
办法,在此办法中调用 vm._render
办法学生成虚构 Node
,最终调用 vm._update
更新 DOM。
Watcher 在这里起到两个作用:
- 初始化的时候会执行回调函数;
- 当 vm 实例中的监测的数据发生变化的时候执行回调函数
3. render 渲染
- 把 template 编译成 render 办法【编译过程前面专门介绍】
- createElement 创立 dom 节点
4. 虚构 dom
- 因为 dom 操作耗时非常长,且 dom 对象的体积很大,单个 div 的 dom 属性就有
294
个之多; - Virtual DOM 就是用一个原生的 JS 对象去形容一个 DOM 节点,所以它比创立一个 DOM 的代价要小很多。
- VNode 是对实在 DOM 的一种形象形容,它的外围定义无非就几个要害属性,标签名、数据、子节点、键值等,其它属性都是用来扩大 VNode 的灵活性以及实现一些非凡 feature 的。因为 VNode 只是用来映射到实在 DOM 的渲染,不须要蕴含操作 DOM 的办法,因而它是十分轻量和简略的。
- Virtual DOM 到实在的 dom 须要通过以下过程:VNode 的 create、diff、patch
5. createElement
判断第一个参数 tag 的类型,分为一般 html 标签、组件和其余类型,将子节点标准成 VNode 类型,递归整个树实现虚构 dom 树的构建。
此办法是 render 函数的参数。
const app = new Vue({
el: '#app',
render: createElement => createElement(App)
})
6. update
调用的机会:一个是首次渲染,一个是数据更新的时候;
首次渲染会将虚构 dom 树整个渲染为 dom 节点,数据更新的时候会通过 diff
过程,只选取批改的虚构 dom 节点进行部分更新。
update 的外围就是调用 vm.__patch__
办法,不同的平台实现不一样,web 平台生成 dom 节点,ssr 服务端渲染生成 html 字符串。
dom 树节点的 插入
程序是 先子后父
,
- vue 初始渲染的工作流程:
new Vue
➜init
➜$mount
➜compile
➜render
➜vnode
➜patch
➜dom
三、组件化
- 组件化,就是把页面拆分成多个组件 (component),每个组件依赖的 CSS、JavaScript、模板、图片等资源放在一起开发和保护。
- 组件是资源独立的,组件在零碎外部可复用,组件和组件之间能够嵌套。
1. createComponent
在 createElement 外面调用,判断 tag 类型为组件时调用,用来 将组件转换成虚构 dom。
外围步骤:
- 结构子类构造函数
- 装置组件钩子函数
- 实例化 vnode
Vue.extend
- 作用就是结构一个 Vue 的子类,这个子类就是组件自身,应用原型继承把纯对象转换一个继承于 Vue 的结构器 Sub 并返回
- Sub 扩大了属性,如扩大 options、增加全局 API 等;并且对配置中的 props 和 computed 做了初始化工作;
- 最初对于这个 Sub 构造函数做了缓存,防止屡次执行 Vue.extend 的时候对同一个子组件反复结构。参考 前端 vue 面试题具体解答
2. patch
patch 次要实现组件的 渲染
工作。
createComponent 过程把组件转换成了 VNode,patch 过程会调用 createElm 把 VNode 转换成真正的 DOM 节点。
- createComponent:
递归实现深度遍历
整个 VNode 树,用先子后父
的形式插入 dom 树 - 最终根节点 VNode 转化为 dom 后挂载到
#app
的节点上,且挂载元素不能是html
或body
- patch 整体流程:createComponent ➜ 自组件初始化 ➜ 子组件 render ➜ 自组件 patch
3. 合并配置
vue 本身定义了一些默认配置,同时又能够在初始化阶段传入一些定义配置,而后去 merge 默认配置,来达到定制化不同需要的目标。
vue 组件其实是一个 js 对象,咱们写组件其实就是在写各种配置,这个配置在构建组件的时候会调用 Vue.extent
办法构建成一个组件类 (因而咱们组件外部拜访到的this
才是 Vue 的实例),那么在组件类实例化 new Vue()
的过程中,就会做 合并配置
这件事。
合并配置分为两种形式:
- 内部初始化调用
new Vue
(例如挂载 #app 的时候) - 组件场景
次要合并以下几方面的配置:
- mixin 的配置
- extends 继承的配置
- 编写的对象组件配置
4. 生命周期
生命周期
是 vue 在运行期间的各个要害节点运行的 钩子函数
,以便能够在特定场景做特定的事。
- 生命周期顺次有:
beforeCreate ➜ created ➜ beforeMount ➜ mounted ➜ beforeUpdate ➜ updated ➜ beforeDestroy ➜ destroyed - 除此之外还有两个 keep-alive 中应用的生命周期:
activated ➜ deactivated - 生命周期是一个数组,可能有多个钩子函数(合并配置中自带的和用户写的?)
- 父子组件创立挂载执行程序
父 beforeCreate ➜ 父 create ➜ 父 beforeMount ➜ 子 beforeCreate ➜ 子 created ➜ 子 mounted ➜ 父 mounted - 更新
父 beforeUpdate ➜ 子 beforeUpdate ➜ 子 updated ➜ 父 updated - 销毁
父 beforeDestroy ➜ 子 beforeDestroy ➜ 子 destroyed ➜ 父 destroyed
beforeCreate & created
- 调用在实例化 & 初始化 state(props、data、methods、watch、computed 等属性)前后
- 故在 beforeCreate 办法中无奈应用
this
拜访以后实例,也无法访问 data、props 等; - 应用场景:这两个办法可用来和后端交互,created 做渲染前的数据初始化操作;不可拜访组件 dom;
- 父子组建执行程序
先父后子
beforeMount & mounted
- 调用在 DOM 挂载前后
- beforeMount 钩子函数执行程序
先父后子
,mounted 钩子函数执行程序先子后父
- 应用场景:这两个办法可用来和后端交互;mounted 中可操作 dom;拜访
ref
beforeUpdate & updated
- beforeUpdate 数据更新后更新 dom 前,updated 调用在更新 dom 前后
beforeDestroy & destroyed
- 调用在实例销毁前后
- 销毁操作:parent 的 $children 中删掉本身,删除 watcher
- 应用场景:beforeDestroy 可解除本人绑定的事件监听等
activated & deactivated
- 调用在 keep-alive 激活后和行将来到激活后
生命周期示意图
5. 组件注册
- 全局注册
Vue.component(tagName, options)
, 挂载到Vue.options.components
上,所有组件均可拜访; - 部分注册
components:{componentName: component}
, 挂载到vm.$options.components
上,仅父组件可拜访; - 解析 template 模版遇到组件时,优先寻找 vm.$options.components 上的部分组件,找不着再寻找 Vue.options.components 上的全局组件;
- 注册组件的时候,id 能够是连字符、驼峰或首字母大写的模式
6. 异步组件
Vue 有 3
种异步组件,实现了 loading、resolve、reject、timeout 4 种状态。异步组件实现的实质是 2 次渲染,除了 0 delay 的高级异步组件第一次间接渲染成 loading 组件外,其它都是第一次渲染生成一个正文节点,当异步获取组件胜利后,再通过 forceRender 强制从新渲染,这样就能正确渲染出咱们异步加载的组件了。
- 一般函数异步组件: 通过 resove 回调解决
- Promise 异步组件:promise.then 解决
- 高级异步组件: 减少 loading、error、delay、timeout 等,解决了加载占位、错误处理、超时工夫等;
四、深刻响应式原理
1. 响应式对象
响应式对象,外围就是利用 Object.defineProperty
给数据 递归 增加了 getter
和 setter
,目标就是为了在咱们拜访数据以及写数据的时候能主动执行一些逻辑:getter 做的事件是 依赖收集
,setter 做的事件是 派发更新
。实质上是 公布订阅模式(观察者模式)
。
2. 依赖收集
所以在 vm._render() 过程中,会触发所有数据的 getter,这样实际上曾经实现了一个依赖收集的过程。
在定义相应式对象的的 getter 函数里,触发 dep.depend
做依赖收集,将获取属性的中央全副退出订阅者列表中,当数据发生变化时,通过遍历订阅者列表实现变更公布。
再次 render 时会先做依赖革除,再次进行新的依赖收集,这样做是为了解决 v -if 条件渲染的数据不必再派发更新了。
3. 派发更新
实际上就是当数据发生变化的时候,触发 setter 逻辑,把在依赖过程中订阅的的所有观察者,也就是 watcher,都触发它们的 update 过程,这个过程又利用了队列做了进一步优化,在 nextTick 后执行所有 watcher 的 run,最初执行它们的回调函数。
通过 setter 来触发变量的更新,这里引入了一个队列的概念,这也是 Vue 在做派发更新的时候的一个优化的点,它并不会每次数据扭转都触发更新,而是先增加到一个队列里,而后在 nextTick 后执行更新,能够了解为等一段时间一起更新。
队列排序 queue.sort((a, b) => a.id – b.id) 对队列做了从小到大的排序,这么做次要有以下要确保以下几点:
- 组件的更新由父到子;因为父组件的创立过程是先于子的,所以 watcher 的创立也是先父后子,执行程序也应该放弃先父后子。
- 用户的自定义 watcher 要优先于渲染 watcher 执行;因为用户自定义 watcher 是在渲染 watcher 之前创立的。
- 如果一个组件在父组件的 watcher 执行期间被销毁,那么它对应的 watcher 执行都能够被跳过,所以父组件的 watcher 应该先执行。
4. nextTick
此办法能够在数据批改触发 dom 更新实现之后调用。
在浏览器环境中,常见的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate;常见的 micro task 有 MutationObsever 和 Promise.then。
6. 检测变动的注意事项
- 给对象增加属性不能被
Object.defineProperty
监测到,须要通过Vue.set
办法手动通知 vue 收集这个依赖并且派发更新。 -
vue 不能间接检测到
数组项的赋值
和间接批改长度
的,然而能够监测到splice
等办法的批改,起因在于- 赋值:无奈给数组设置
defineProperty
,可通过Vue.set
实现对数组项的批改; - 通过数组办法批改:vue 在
Observer
类中独自对数组做了解决,对数组对能减少数组长度的 3 个办法重写push
、unshift
、sueplice
,现将办法原有逻辑执行完,再手动把新增加的值变成一个响应式对象,并且派发更新。
- 赋值:无奈给数组设置
- 对象属性的删除可通过
Vue.del
办法,确保触发更新视图。
7. 计算属性 VS 侦听属性
计算属性
计算属性的触发有以下两种状况:
- 被动拜访:当计算属性被拜访时触发 getter 函数,执行用户返回的计算结果,如果返回值发生变化才触发渲染更新(有缓存,依赖发生变化才执行)。
- 依赖属性更新:计算属性会成为依赖变量的订阅者,依赖变量产生扭转则触发计算属性从新计算。
- 计算属性有
lazy
和active
两种模式,active 模式依赖更新立刻计算,lazy 模式依赖变动仅设置this.dirty = true
,等拜访计算属性时再从新计算,并退出缓存。 - 计算属性的和侦听属性初始化都产生在 Vue 实例初始化阶段的 initState 函数中,先初始化计算属性后监听属性。
延时计算
计算属性不会立即求值(除非设置immediate: true
)- 计算属性能够通过闭包来实现传参:
computed: {value () {return function (a, b, c) {
/** do something */
return data
}
}
}
监听属性
- 监听属性相当于被动订阅了属性的变动,属性发生变化时执行回调函数
- 监听属性的 watcher 执行优先级高于渲染 watcher;
- deep 设置为 true 用于监听对象外部值的变动
- immediate 设置为 true 将立刻以表达式的以后值触发回调
实质上侦听属性也是基于 Watcher 实现的,它是一个 user watcher。
计算属性 vs 监听属性 从利用场景看
计算属性
适宜用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;侦听属性
实用于观测某个值的变动去实现一段简单的业务逻辑(例如执行异步或开销较大的操作)。
watcher 的 4 种类型:deep
、user
、computed
、sync
- deep:能够监听到对象属性的属性的批改变动,递归遍历对象所有属性,订阅所有子孙属性的变动(设置办法
deep: true
); - user:watch 外面增加的侦听属性
- computed:computed 外面增加的监听
- sync:属性变动后同步执行更新,不会退出缓冲队列在 nextTick 后执行。(设置办法
immediate: true
)
计算属性 vs 办法
- 缓存: computed 是能够缓 ` 的,methods 不能缓存;只有相干依赖没有扭转,屡次拜访计算属性失去的值是之前缓存的计算结果,不会屡次执行;调用办法将总会再次执行函数;
- 延时计算: 计算属性次要对响应式属性的依赖,没有响应式依赖不会自动更新:如这样只会计算一次
computed: {now: ()=>Date.now()}
- 值得注意的是计算属性能够通过返回一个函数造成闭包来实现
传参
;
8. 组件更新(diff)
组件更新外围是响应式数据监控到数据的扭转,从新生成了虚构 dom 树,而后通过 diff 算法计算出前后虚构 dom 树的差别点,更新 dom 时只更新变动的局部。
快问快答:
- 为什么要 diff? 答: O(n^3) 意味着如果要展现 1000 个节点,就要顺次执行上十亿次的比拟,无奈接受大数据量的比照。
- 间接比拟和批改两个树的复杂度为什么是
n^3
? 答: 老树的每一个节点都去遍历新树的节点,直到找到新树对应的节点。那么这个流程就是 O(n^2),再紧接着找到不同之后,再计算最短批改间隔而后批改节点,这里是 O(n^3)。 - diff 的策略是什么?有什么依据? 答:
1、Web UI 中 DOM 节点跨层级的挪动操作特地少,能够忽略不计,因而仅进行同层比拟。
2、如果父节点不同,放弃对子节点的比拟,间接删除旧节点而后增加新的节点从新渲染;
3、如果子节点有变动,Virtual DOM 不会计算变动的是什么,而是从新渲染。
4、同级多个节点可通过惟一的 key 比照异同; - diff 流程是什么? 答:
新旧节点不同:创立新节点 ➜ 更新父占位符节点 ➜ 删除旧节点;
新旧节点雷同且没有子节点:不变;
新旧节点雷同且都有子节点:遍历子节点同级比拟,做挪动、增加、删除三个操作,具体见下图;
9. Props 属性
用作父组件给自组件传参,
- 规范化:将 props 属性都变成对象(原来可能是数组);
- 初始化:校验数据类型、响应式解决、代理(this.name 变成 this._props.name);
- 更新:一种是 prop 值被批改触发 this._prop 的 setter;另一种是对象属性批改走对象响应式;
五、编译
编译的外围是把 template
模板编译成 render
函数。
vue 有两种编译模式:
- 运行时编译:在运行时才做模版编译(Runtime + Compiler)
- 打包编译:借助 webpack 的
vue-loader
当时把模板编译成 render 函数(Runtime only)
1. 入口
运行时编译:入口 compileToFunctions
// 解析模板字符串生成 AST
const ast = parse(template.trim(), options)
// 优化语法树
optimize(ast, options)
// 生成代码
const code = generate(ast, options)
2. parse
AST
:种形象语法树,是对源代码的形象语法结构的树状表现形式。
次要采纳 标记化算法
的思路,解析器外部保护一个 状态机
;
- 解析流程会遍历 html 字符串,随着索引 index 的后移,状态机 status 会更新当初所处的解析状态;
- 依据不同的解析状态应用不同的解析办法,以后解析状态实现后,再进入下一个解析状态;
- 如此周而复始解析残缺个树。
parse 的指标是把 template 模板字符串转换成 AST 树,它是一种用 JavaScript 对象的模式来形容整个模板。那么整个 parse 的过程是利用正则表达式程序解析模板,当解析到开始标签、闭合标签、文本的时候都会别离执行对应的回调函数,来达到结构 AST 树的目标。
AST 元素节点总共有 3 种类型,type 为 1 示意是一般元素,为 2 示意是表达式,为 3 示意是纯文本。【换成常量更好】
- 解析 template 模版用到的正则
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`) // 开始标签关上
const startTagClose = /^\s*(\/?)>/ // 开始标签敞开
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`) // 标签完结
const doctype = /^<!DOCTYPE [^>]+>/i // 文档类型节点
const comment = /^<!\--/ // 正文节点
const conditionalComment = /^<!\[/
3. optimize
优化 AST 树的起因:解决响应式、标记动态节点、解决指令等
动态节点的判断办法:
- 如果是表达式,就是非动态;如果是纯文本,就是动态;
- 一般元素用了
v-pre
指令,是动态; -
一般元素满足上面所有条件则是动态:
- 没有应用 v-if、v-for
- 没有应用其它指令(不包含 v-once)
- 非内置组件,是平台保留的标签
- 非带有 v-for 的 template 标签的间接子节点,节点的所有属性的 key 都满足动态 key;
如果是一般元素非动态节点,则遍历它的所有 children,递归执行动态节点的标记,子节点有不是动态的状况,则它的父节点也为非动态。
标记动态根:缓存节点,优化 diff 过程,来缩小操作 dom
4. codegen
把 AST 语法树转换成可执行的 render
函数,
次要解决 AST 的以下属性,将其变成 render 函数的写法:
- static 动态节点
- once 只渲染一次的节点
- for 解决循环
- if 解决条件渲染
- children 递归解决树状构造
- slot 解决插槽
- 解决组件或者原生 dom 标签
六、扩大
次要介绍 event、v-model、slot、keep-alive、transition 等。
1. event 事件
次要从上面三个角度剖析:
- 编译解析
- dom 原生事件
- 组件自定义事件
编译解析
在编译过程中解析 template 模版,辨认其中 v-on
、@
等指令,记录下事件的 名称
和回调函数
,其中回调函数可能使函数名称或者一个函数。
dom 原生事件
绑定办法:在组件上应用原生事件须要加 .native
修饰符(例如 @click.native)
增加移除:DOM 事件调用原生 addEventListener
和removeEventListener
;
组件自定义事件
通过事件核心实现,思维相似公布订阅模式:
- 把所有的事件用 vm._events 存储起来,当执行 vm.$on(event,fn) 的时候,按事件的名称 event 把回调函数 fn 存储起来 vm._events[event].push(fn)。
- 当执行 vm.$emit(event) 的时候,依据事件名 event 找到所有的回调函数 let cbs = vm._events[event],而后遍历执行所有的回调函数。
- 当执行 vm.$off(event,fn) 的时候会移除指定事件名 event 和指定的 fn
- 当执行 vm.once(event,fn)的时候,外部就是执行 vm.on,并且当回调函数执行一次后再通过 vm.$off 移除事件的回调,这样就确保了回调函数只执行一次。
留神
- 原生 DOM 事件和自定义事件次要的
区别
:增加和删除事件的形式不一样;DOM 事件调用原生addEventListener
和removeEventListener
增加和删除;自定义事件调用vm.$off
办法删除回调函数即可; - 只有组件节点才能够增加自定义事件,并且增加原生 DOM 事件须要应用 native 修饰符;而一般元素应用 .native 修饰符是没有作用的,也只能增加原生 DOM 事件。
2. v-model 双向数据绑定
数据响应:data ➜ view
v-model 双向数据绑定: data ↔ view
v-model 是一种 语法糖
,即能够作用在 一般表单元素
上,又能够作用在 组件
上。
- 表单元素实现 v-model 的办法:
通过批改 AST 元素,给 el 增加一个 prop,相当于咱们在 input 上动静绑定了 value,又给 el 增加了事件处理,相当于在 input 上绑定了 input 事件。<input v-model="message" />
相当于:<input v-bind:value="message" v-on:input="message=$event.target.value" />
- 对组件来说就是:
<child :value="message" @input="e => message = e"/>
组件能够配置子组件接管的 prop 名称和派发的事件名称
{props: ['msg'],
model: {
prop: 'msg',
event: 'change'
},
methods: {updateValue(e) {this.$emit('change', e.target.value)
}
}
}
3. slot 插槽
插槽就像是子组件中的一个个空抽屉,父组件能够在调用子组件的时候本人决定放什么内容到不同的抽屉里。
编译
- 编译父组件时,当解析到标签上有 slot 属性的时候,将元素节点上标记为 data.slot = slotName || ‘default’
- 编译自组件时,当解析到 slot 标签的时候,在此 AST 元素节点上标记 slotName,而后在渲染阶段从父组件的 children 中遍历匹配 data.slot 获取对应名称渲染好的插槽 vnode
作用域插槽
作用域插槽作用:子组件给父组件传递数据。
读取 scoped-slot 属性并赋值给以后元素节点的 slotScope 属性,接下来在结构 AST 树的时候,不会作为 children 增加到以后 AST 树中,而是存到父 AST 元素节点的 scopedSlots 属性上,它是一个对象,以插槽名称 name 为 key,以渲染函数为 value。
而后在子组件渲染的时候,取到父组件的 scopedSlots 外面的渲染函数,执行生成 vnode。
一般插槽和作用域插槽的区别:
- 一般插槽是在父组件编译和渲染阶段生成 vnodes,所以数据的作用域是
父组件实例
,子组件渲染的时候间接拿到这些渲染好的 vnodes - 对于作用域插槽,父组件在编译和渲染阶段并不会间接生成 vnodes,而是在父节点 vnode 的 data 中保留一个 scopedSlots 对象,存储着不同名称的插槽以及它们对应的渲染函数,只有在编译和渲染子组件阶段才会执行这个渲染函数生成 vnodes,因为是在子组件环境执行的,所以对应的数据作用域是
子组件实例
。 - 简略地说,两种插槽的目标都是让子组件 slot 占位符生成的内容由父组件来决定,但数据的作用域会依据它们 vnodes 渲染机会不同而不同。
4. keep-alive
keep-alive
是一个内置形象
组件,在组件实例建设父子关系的时候会被疏忽;- created 钩子里定义了 this.cache 和 this.keys 用来缓存曾经创立过的 vnode;在组件从新被激活时把缓存的 dom
vnode.elm
插入 dom 树即可; - keep-alive 组件只解决第一个子元素(将子节点当成树的根节点故只有一个),个别和它搭配应用的有 component 动静组件或者是 router-view 组件;
- include 和 exclude 做匹配过滤,匹配胜利的退出缓存,参数反对数组、字符串、正则表达式等;max 管制缓存的组件个数
- 生命周期:activated(激活)、deacitvated(解冻)
5. transition、transition-group
keep-alive
也是一个内置形象组件,是web 平台独有
的,同样也只解决一个子节点(多了会正告);- 提早 dom 操作的执行机会,用来执行动画,执行结束进行 dom 操作。
- 通过监听 dom 上 css3 过渡动画的执行完结的事件得悉执行结束
在下列情景中增加过渡成果
- 条件渲染 (应用 v-if)
- 条件展现 (应用 v-show)
- 动静组件
- 组件根节点
Vue 的过渡实现分为以下几个步骤:
- 主动嗅探指标元素是否利用了 CSS 过渡或动画,如果是,在失当的机会增加 / 删除 CSS 类名。
- 如果过渡组件提供了 JavaScript 钩子函数,这些钩子函数将在失当的机会被调用。
- 如果没有找到 JavaScript 钩子并且也没有检测到 CSS 过渡 / 动画,DOM 操作 (插入 / 删除) 在下一帧(requestAnimationFrame 实现)中立刻执行。
真正执行动画的是咱们写的 CSS 或者是 JavaScript 钩子函数,而 Vue 的 transition 组件只是帮咱们很好地治理了这些 CSS 的增加 / 删除,以及钩子函数的执行机会。
transition-group
- transiiton 只能针对繁多元素实现过渡成果,transition-group 对列表实现适度成果,当批改列表的数据的时候,如果是增加或者删除数据,则会触发相应元素自身的过渡动画;
七、vue-router
路由的性能是
兼顾散发
,通知什么人应该干什么事件,对前端来说就是将不同的门路映射到不同的性能(视图)下来。
vue-router 反对 hash
、history
、abstract
3 种路由形式,提供了 <router-link>
和 <router-view>
2 种组件
1. 路由注册
- Vue 从它的设计上就是一个渐进式 JavaScript 框架,它自身的外围是解决视图渲染的问题,其它的能力就通过插件的形式来解决。Vue-Router 就是官网保护的路由插件。
- 插件通过 Vue.use 办法来实现注册,实际上是运行插件的 install 办法
- Vue-Router 装置最重要的一步就是利用 Vue.mixin 去把 beforeCreate 和 destroyed 钩子函数注入到每一个组件中
-
通过在 Vue.prototype 原型上增加办法的形式来让用户拜访到办法,应用 defineProperty 设置只读可防止被用户手动篡改。
Object.defineProperty(Vue.prototype, '$router', {get () {return this._routerRoot._router} })
- 注册全局组件
<router-link>
和<router-view>
2. VueRouter 对象
蕴含以下办法:
- init
- beforeEach
- beforeResolve
- afterEach
- onReady
- onError
- push
- replace
- go
- back
- forward
- getMatchedComponents
- resolve
- addRoutes
3. matcher
匹配过程次要做的事件:
- 初始化依据用户传递的路由配置,生成映射表
- 解析 url 参数,url 与用户设置的路由门路匹配
4. 门路切换
路由始终会保护以后的线路,路由切换的时候会把以后线路切换到指标线路,切换过程中会执行一系列的导航守卫钩子函数,会更改 url,同样也会渲染对应的组件,切换结束后会把指标线路更新替换以后线路,这样就会作为下一次的门路切换的根据。
- router-link 点击相当于调用 $router.push 办法去批改 url
<router-link>
比起写死的 <a href="...">
会好一些,理由如下:
- 无论是 HTML5 history 模式还是 hash 模式,它的体现行为统一,所以,当你要切换路由模式,或者在 IE9 降级应用 hash 模式,毋庸作任何变动。
- 在 HTML5 history 模式下,router-link 会守卫点击事件,让浏览器不再从新加载页面。
- 当你在 HTML5 history 模式下应用 base 选项之后,所有的 to 属性都不须要写(基门路)了。