写作不易,未经作者容许禁止以任何模式转载!<br/>如果感觉文章不错,欢送关注、点赞和分享!
博客原文链接:Vue的前世今生
Vue的前世今生
- 2013 尤雨溪集体我的项目
- 2014.2 0.1版本公布
2015.10 1.0版本公布
- 模板语法改良
2016.9 2.0版本公布
- 跨端
- 新的渲染机制
2019.10 3.0 alpha公布
- 性能
- 架构
- 按需引入
- Composition API
- Proxy observer
- AOT优化
Vue 1 响应式原理
构建响应式对象流程
walk函数遍历data对象中的属性,调用defineReactive将其变成响应式对象
- 对于对象属性进行递归调用walk,以保障data整个对象树中的属性都是响应式对象。
- defineReactive中应用watchers数组贮存watcher,应用Object.defineProperty的get函数收集watcher和返回值,set函数用来设置值和对watchers中的watcher进行视图更新。
Walk函数实现
function walk(data){ Object.keys(data).foreach(key => { defineReactive(data, key, data[key]) //对象递归调用walk walk(data[key]) })}
defineReactive函数实现
function defineReactive(obj, key, value){ let oldValue = value; const watchers = [] Object.defineProperty(obj, key, { get(){ //收集watcher watchers.push(currentWatcher) return oldValue }, set(){ if(newValue === oldValue) return; oldValue = newValue; watchers.forEach(watcher => wathcer.update())//更新视图 } })}
看了这么久Watcher到底是什么?
Watcher用于获取数据和更新视图,并实现vue指令
- watcher从data中get数据render视图,同时data中的响应式对象劫持以后watcher并“贮存”起来
- data更新数据会触发响应式对象的set函数,把get数据时“贮存”的watchers取出遍历,“告诉”其更新视图。
- watcher“接到data中的数据更新告诉”,从新render视图。
- 视图发生变化会触发data的中响应式对象的set函数,循环造成数据流。
- 例:
// vm指向以后组件,el指向以后dom节点,第三个参数为标签类型,第四个为回调函数// currentWatcher为全局变量指针// 一般渲染的watcherWatcher(vm, el, 'text', () =>{ // 将currentWatcher对象指向以后watcher(vdom节点)供响应式对象的get函数获取 currentWatcher = this; // 读取显示的内容 el.textContext = eval('vm.data.text') // 解绑currentWatcher,避免产生谬误。 currentWatcher = null})//带v-if指令的watcherWatcher(vm, el, 'text', () =>{ // 将currentWatcher对象指向以后watcher(vdom节点)供响应式对象的get函数获取 currentWatcher = this; // 实现v-if指令,通过判断变量值决定是否显示该元素,v-show原理相似 el.style.display = eval('Boolean(vm.data.text)') ? 'block' : 'none' // 解绑currentWatcher,避免产生谬误。 currentWatcher = null})
Vue 1 中存在的几个显著问题
- 启动时拦挡所有组件的状态,进行递归响应式代理影响首次渲染速度
- 内存占用率高,一个“指令”,“computed计算属性”,“handlebar表达式”等等均须要创立一个watcher,watcher数量过多导致内存占用率高。
- 模板通过编译后间接操作dom,无奈跨端。
Vue中的优化
- 新的渲染引擎 - vdom
- Watcher依赖力度调整
其余
- API、语法糖从新设计与定义
- 生命周期调整
- 双向数据流 -> 单向数据流
- 反对了jsx语法
- 等等...
新的渲染引擎 - vdom
//template<template> <div v-if="text"> {{text}} </div></template>// vue-loader 编译后的 compile render// h函数用于生成Vdom节点,第一个参数为以后组件,第二个参数为属性,第三个属性为子节点render(){ return this.text ? h( 'div', null, h(this.text, null,[]) ) : vm.createEmptyVNode()}
Watcher依赖力度调整
watcher不再与单个dom节点、指令关联,一个component对应一个watcher,极大缩小了vue 1 中watcher数量过多导致的内存问题。同时以来vdom diff在渲染时能以最小的代价来更新dom。
Watch(compoent, vm, 'text', () =>{ const newVnode = component.render() const oldVnode = component.render() //通过diff算法返回新旧节点的差别 const patches = vm.diff(newVnode, oldVnode) // 通过patch函数对该组件利用差别 vm.patch(component, patches);})
vdom带来的劣势
- 框架屏蔽具体渲染细节,形象了渲染层,组建的形象能力得以晋升,不再依赖浏览器运行,进而能够跨段,如SSR、同构渲染一姐小程序、weex、uni-app等框架。
- 通过动态剖析进行更多的AOT(Ahead Of Time)编译优化。
- 附加能力:大量组件更新时以最小的代价去更新dom。
- vdom比照间接操作dom要慢,大部分状况下效率比vue 1 差,尽管就义了一点性能,然而使得vue取得更多个性及优化空间。
AOT编译优化
Cache static element
- 缓存动态节点、属性,防止反复创立
// 编译前<div> <span class="foo"> Static </span> <span> {{dynmic}} </span></div>// 编译后const __static1=h('span',{ class:'foo'}, 'static')render(){ return h('div', [ __static1, h('span', this.dynamic) ])}
Component fast path
- 编译后 直接判断是组件、原生标签还是文本节点,防止不必要的分支判断,晋升性能。
- 进步vdom diff时的效率
Vue2优化前
- 每次都要调用h函数去做分支判断
// 编译前<Comp></Comp><div></div><span></span>// Vue2render(){ return createFragment([ h(Comp, null, null), h('div', null, [ h('span', null, null) ]) ])}function h(type, attrs, children){ if(isComponent(type)){ //创立component vnode return createComponentVNode(type, attrs, children) } if(isDomElement(type)){ //创立原生dom vnode return createElementVNode(type, attrs, children) } //创立纯string节点 return createStringVNode(type, attrs, children)}
Vue3优化后
- 编译后间接调用不同的createVNode办法
// Vue3render(){ return createFragment([ createComponentVNode(Comp, null, null), createElmentVNode('div',null, [ createElmentVNode('span', null, null) ]) ])}
SSR optimize
- SSR时采纳字符串拼接,不创立vnode。
//编译前<template> <div> <p class="foo"> {{msg}} </p> <comp/ </div></template>//编译后render(){ return h('div', [ this.ssrString( `<p class="foo">` + this.msg + '</p>' ), h(comp) ])}
Inline handler
- 缓存dom上的event handler,防止反复创立。
// 编译前<div @click="count++"></div>// 编译后import {getBoundMethod} from 'vue'function __fn1(){ this.count++}render(){ return h('div',{ onClick:getBoundMethod(__fn1,this) })}
Vue3变更
Proxy Reactive State
- Vue3改用Proxy去生成响应式对象
- Vue1/2中遍历和递归所有data中的属性去生成响应式对象
- Vue3中改为仅在get获取这个属性的时候才去生成响应式对象,提早了响应式对象生成,放慢了首屏渲染速度。
// Vue1/2中的做法function walk(data){ Object.keys(data).foreach(key => { defineReactive(data, key, data[key]) //对象递归调用walk walk(data[key]) })}// Vue3中的做法function reactive(target){ let observerd = new Proxy(target, { get(target, key, receiver){ let result = Reflect.get(target, key, receiver) //只有在取对象子属性的时候才递归 reactive(result) return result }, set(target, key, value, receiver) { let oldValue = target[key] if (value == oldValue) return let result = Reflect.set(target, key, value, receiver) return result; } }) return observerd}
Composition API
Vue2中,代码依据数据、办法、计算属性等进行分块,导致可能同一个业务性能的代码须要重复高低地跳着去看。
- 尽管有Mixin,但业务和业务之间的关系,包含命名空间都会呈现肯定问题。
Vue3中引入Composition API使得开发者能够依据业务将代码分块,按需引入响应式对象、watch、生命周期钩子等各种属性,应用办法相似React Hooks,使得开发者更灵便地开发。
- 详情见Vue3中文文档 - vuejs (vue3js.cn)
Vue2
export default { data() { return { counter: 0 } }, watch: { counter(newValue, oldValue) { console.log('The new counter value is: ' + this.counter) } } }
Vue3
import { ref, watch } from 'vue' const counter = ref(0) watch(counter, (newValue, oldValue) => { console.log('The new counter value is: ' + counter.value) })
Why use Composition API
- mixin、hoc、composition api都是为了解决代码复用的问题。然而mixin、hoc过于灵便没有标准,导致开发人员容易写出零散、难以保护的逻辑。
- Compostion API躲避了mixin、hoc存在的缺点,提供固定的编程模式->函数组合,对各模块解耦使得更优雅、更容易地去组合复用。
- 以组件状态为例,传统写法所有state都在一个component,杂糅在一起,语义化不强,compostion api使得state依照不同的逻辑分离出来,形象出状态层组件。
const Foo = { template:'#modal', mixins:[Mixin1, Mixin2], methods:{ click(){ this.sendLog() } }, components:{ appChild:Child }}
看完以上代码会发现以下问题
- sendLog到底来自哪个Mixin
- mixin1,mixin2之间有没有逻辑关系
- mixin1,mixin2如果都注入了sendLog应用哪个
- 如果应用hoc的形式,hoc减少了两个组件实例耗费,多了两次diff。
- 再来多几个mixin,这个组件更难保护。
- 显著地体现了Composition API的益处
Time Slicing
- Vue3最开始实现了这个个性,不过前面移除了
起因总结为以下两条
- 基于响应式原理及AOT编译优化,相比react而言vue vdom diff具备很高的效率
- Time Slicing只在一些极其状况下有显著作用,引入会升高vdom diff效率,阻塞UI渲染,收益不大。
按需引入、反对treeshaking
- Vue各模块(响应式、SSR、runtime等)的解耦,可按需引入。
Vue vs React
相同点
- 基于MVVM思维:响应式数据驱动试图更新
- 提供组件化的解决方案
- 跨端:基于vdom的渲染引擎
外围差别
定位
- React是一个Library,只专一于state到view的映射,状态、路由、动画等解决方案均来自于社区。
- Vue是一个渐进式Framework,设计之初思考开发者可能面临的问题,官网提供路由、状态治理、动画、插件等比拟齐全的解决方案,不强制应用,譬如模块机制、依赖注入,能够通过插件机制很好和社区计划集成。
- Library,职责范畴小,开发效率低,需借助外力,然而易于扩大。对保护团队而言,放弃版本间兼容老本较低。更容易集中精力专一于外围变更。
- Framework,职责范畴大,开发效率高,内置一套解决方案,扩大水平低。对保护团队而言,放弃版本间兼容老本较高。
渲染引擎
- Vue进行数据拦挡/代理,它对侦测数据的变动更精确,扭转了多少数据,就触发多少更新多少。
- React setState触发部分整体刷新,没有追踪数据变更,做到准确更新,所以提供给开发者shouldComponentUpdate去除一些不必要的更新。
- 基于这个响应式设计,间接影响了外围架构的Composition API、React Hooks的实现。
模板DSL
- Vue template语法更靠近html,动态表达能力很强,基于申明式的能力,更不便做AOT编译优化。
- JSX语法能够认为是JS根底上又减少了对html的反对,实质还是命令式变成。动态表达能力偏弱,导致优化信息有余,无奈很好地做动态编译。
原文链接:Vue的前世今生
掘金:前端LeBron
知乎:前端LeBron
继续分享技术博文,关注微信公众号