共计 6321 个字符,预计需要花费 16 分钟才能阅读完成。
写作不易,未经作者容许禁止以任何模式转载!<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 为全局变量指针
// 一般渲染的 watcher
Watcher(vm, el, 'text', () =>{
// 将 currentWatcher 对象指向以后 watcher(vdom 节点)供响应式对象的 get 函数获取
currentWatcher = this;
// 读取显示的内容
el.textContext = eval('vm.data.text')
// 解绑 currentWatcher,避免产生谬误。currentWatcher = null
})
// 带 v -if 指令的 watcher
Watcher(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>
// Vue2
render(){
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 办法
// Vue3
render(){
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
继续分享技术博文,关注微信公众号👇🏻
正文完