Vue3
核心的Typescript
,Proxy
响应式,Composition
解决代码反复横跳都有很棒的文章剖析了, 我总结一下虚拟Dom
部分把,并对比一下React
, vdom
的重写也是vue3
性能如此优秀的重要缘由javascript
总体尤大直播的过程,几位大兄弟已经总结的贼棒了 ,直接移步html
先说结论,静态标记,upadte
性能提高1.3~2倍,ssr
提高2~3倍,怎么作到的呢前端
咱们来看一段很常见的代码vue
<div id="app"> <h1>技术摸鱼</h1> <p>今每天气真不错</p> <div>{{name}}</div> </div> 复制代码
vue2中会解析java
function render() { with(this) { return _c('div', { attrs: { "id": "app" } }, [_c('h1', [_v("技术摸鱼")]), _c('p', [_v("今每天气真不错")]), _c('div', [_v( _s(name))])]) } } 复制代码
其中前面两个标签是彻底静态的,后续的渲染中不会产生任何变化,Vue2
中依然使用_c
新建成vdom
,在diff
的时候须要对比,有一些额外的性能损耗react
咱们看下vue3中的解析结果git
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", { id: "app" }, [ _createVNode("h1", null, "技术摸鱼"), _createVNode("p", null, "今每天气真不错"), _createVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */) ])) } // Check the console for the AST 复制代码
最后一个_createVNode
第四个参数1,只有带这个参数的,才会被真正的追踪,静态节点不须要遍历,这个就是vue3优秀性能的主要来源,再看复杂一点的github
<div id="app"> <h1>技术摸鱼</h1> <p>今每天气真不错</p> <div>{{name}}</div> <div :class="{red:isRed}">摸鱼符</div> <button @click="handleClick">戳我</button> <input type="text" v-model="name"> </div> 复制代码
解析的结果 在线预览api
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", { id: "app" }, [ _createVNode("h1", null, "技术摸鱼"), _createVNode("p", null, "今每天气真不错"), _createVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */), _createVNode("div", { class: {red:_ctx.isRed} }, "摸鱼符", 2 /* CLASS */), _createVNode("button", { onClick: _ctx.handleClick }, "戳我", 8 /* PROPS */, ["onClick"]) ])) } // Check the console for the AST 复制代码
_createVNode出第四个参数出现了别的数字,根据后面注释也很容易猜出,根据text
,props
等不一样的标记,这样再diff的时候,只须要对比text
或者props
,不用再作无畏的props
遍历, 优秀! 借鉴一下劝退大兄弟的注释浏览器
export const enum PatchFlags { TEXT = 1,// 表示具备动态textContent的元素 CLASS = 1 << 1, // 表示有动态Class的元素 STYLE = 1 << 2, // 表示动态样式(静态如style="color: red",也会提高至动态) PROPS = 1 << 3, // 表示具备非类/样式动态道具的元素。 FULL_PROPS = 1 << 4, // 表示带有动态键的道具的元素,与上面三种相斥 HYDRATE_EVENTS = 1 << 5, // 表示带有事件监听器的元素 STABLE_FRAGMENT = 1 << 6, // 表示其子顺序不变的片断(没懂)。 KEYED_FRAGMENT = 1 << 7, // 表示带有键控或部分键控子元素的片断。 UNKEYED_FRAGMENT = 1 << 8, // 表示带有无key绑定的片断 NEED_PATCH = 1 << 9, // 表示只须要非属性补丁的元素,例如ref或hooks DYNAMIC_SLOTS = 1 << 10, // 表示具备动态插槽的元素 } 复制代码
若是同时有props
和text
的绑定呢, 位运算组合便可
<div id="app"> <h1>技术摸鱼</h1> <p>今每天气真不错</p> <div :id="userid"">{{name}}</div> </div> 复制代码
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue" export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", { id: "app" }, [ _createVNode("h1", null, "技术摸鱼"), _createVNode("p", null, "今每天气真不错"), _createVNode("div", { id: _ctx.userid, "\"": "" }, _toDisplayString(_ctx.name), 9 /* TEXT, PROPS */, ["id"]) ])) } // Check the console for the AST 复制代码
text
是1,props
是8,组合在一块儿就是9,咱们能够简单的经过位运算来断定须要作text
和props
的判断, 按位与便可,只要不是0就是须要比较
export const PLUGIN_EVENT_SYSTEM = 1; export const RESPONDER_EVENT_SYSTEM = 1 << 1; export const USE_EVENT_SYSTEM = 1 << 2; export const IS_TARGET_PHASE_ONLY = 1 << 3; export const IS_PASSIVE = 1 << 4; export const PASSIVE_NOT_SUPPORTED = 1 << 5; export const IS_REPLAYED = 1 << 6; export const IS_FIRST_ANCESTOR = 1 << 7; export const LEGACY_FB_SUPPORT = 1 << 8; 复制代码
绑定的@click
会存在缓存里 连接
<div id="app"> <button @click="handleClick">戳我</button> </div> 复制代码
export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", { id: "app" }, [ _createVNode("button", { onClick: _cache[1] || (_cache[1] = $event => (_ctx.handleClick($event))) }, "戳我") ])) } 复制代码
传入的事件会自动生成并缓存一个内联函数再cache里,变为一个静态节点。这样就算咱们本身写内联函数,也不会致使多余的重复渲染 真是优秀啊
<div id="app"> <h1>技术摸鱼</h1> <p>今每天气真不错</p> <div>{{name}}</div> <div :class="{red:isRed}">摸鱼符</div> </div> 复制代码
const _hoisted_1 = { id: "app" } const _hoisted_2 = _createVNode("h1", null, "技术摸鱼", -1 /* HOISTED */) const _hoisted_3 = _createVNode("p", null, "今每天气真不错", -1 /* HOISTED */) export function render(_ctx, _cache) { return (_openBlock(), _createBlock("div", _hoisted_1, [ _hoisted_2, _hoisted_3, _createVNode("div", null, _toDisplayString(_ctx.name), 1 /* TEXT */), _createVNode("div", { class: {red:_ctx.isRed} }, "摸鱼符", 2 /* CLASS */) ])) } 复制代码
不少人吐槽愈来愈像React
,其实愈来愈像的api
,表明着前端的两个方向
没有vdom
,彻底的响应式,每一个数据变化,都经过响应式通知机制来新建Watcher
干活,就像独立团规模小的时候,每一个战士入伍和升职,都主动通知咱老李,管理方便
项目规模变大后,过多的Watcher
,会致使性能的瓶颈
而React15
时代,没有响应式,数据变了,整个新数据和老的数据作diff
,算出差别 就知道怎么去修改dom
了,就像老李指挥室有一个模型,每次人事变动,经过对比全部人先后差别,就知道了变化, 看起来有不少计算量,可是这种immutable
的数据结构对大型项目比较友好,并且Vdom
抽象成功后,换成别的平台render成为了可能,不管是打鬼子仍是打国军,都用一个vdom
模式
碰到的问题同样,若是dom
节点持续变多,每次diff
的时间超过了16ms
,就可能会形成卡顿(60fps)
引入vdom,控制了颗粒度,组件层面走watcher通知, 组件内部走vdom作diff,既不会有太多watcher,也不会让vdom的规模过大,diff超过16ms,真是优秀啊 就像独立团大了之后,只有营长排长级别的变更,才会通知老李,内部的本身diff管理了
React
走了另一条路,既然主要问题是diff
致使卡顿,因而React
走了相似cpu
调度的逻辑,把vdom
这棵树,微观变成了链表,利用浏览器的空闲时间来作diff
,若是超过了16ms
,有动画或者用户交互的任务,就把主进程控制权还给浏览器,等空闲了继续,特别像等待女神的备胎
diff
的逻辑,变成了单向的链表,任什么时候候主线程女神有空了,咱们在继续蹭上去接盘作diff
,你们研究下requestIdleCallback
就知道,从浏览器角度看 是这样的
大概代码
requestIdelCallback(myNonEssentialWork); // 等待女神空闲 function myNonEssentialWork (deadline) { // deadline.timeRemaining()>0 主线程女神还有事件 // 还有diff任务没算玩 while (deadline.timeRemaining() > 0 && tasks.length > 0) { doWorkIfNeeded(); } // 女神没时间了,把女神还回去🤣 if (tasks.length > 0){ requestIdleCallback(myNonEssentialWork); } } 复制代码
这里的静态提高和事件缓存刚才说过了,就不说了,其实我也很纳闷,这些静态标记和事件缓存,React
自己也能够作,为啥就不实现了,连shouldComponentUpdate
都得本身定义,为啥不把默认的组件都变成pure
或者memo
呢,唉,也许这就是人生把
React
给你自由,Vue
让你持久,可能也是如今国内Vue和React都如此受欢迎的缘由把
Vue3
经过Proxy
响应式+组件内部vdom
+静态标记,把任务颗粒度控制的足够细致,因此也不太须要time-slice
了
人生啊,小孩才每天研究利弊, 成年人选择我都要,也期待React17的新特性
最后提问期间,强如尤大,也无法回避发量的问题,惋惜没推荐啥护发素,我仔细看了一下,好像vue3发布后,尤大发际线确实提高了 囧 祝你们技术提高的同时也能有乌黑的秀发
欢迎点赞关注,个人主要爱好就是摸鱼,推荐个摸鱼群把,这篇文章就是我上午摸鱼写出来的, 一块儿来技术摸鱼群把,最近准备摸鱼写个vue3源码全剖析系列