重编译,轻运行
思考1. 哪些是 vue2.x 的性能提升点?
- diff 如果你想到了 diff,那怎么更快呢?
diff 放在 worker 里执行,不会阻塞当前
- proxy 当让比 Object.defineProperty 好,为什么?
vue2 总是挂载在实例this 上,项目大的话都使用Object.defineProperty 开销大;proxy 返回对象,挂载在this 上面就会少,内存上会有提升。
- SSR 更多的利用静态字符串拼接
vue2 项目复杂度高的话,服务端压力大 ...
思考2. 纯静态
<div>
<h1>hello world!</h1>
</div>
纯静态的问题:
不论页面渲染多少次,都不会边,但是每一次重新渲染都会走一遍 diff,只是不会patch,开销diff
思考3. 动静结合
<div>
<h1>hello world!</h1>
<p>{{ text }}</p>
</div>
如果都把静态的部分提取出来,只处理动态部分(节点)Vue3 标记静态节点
业务中绝大部分情况都是动静结合的,所以在处理这种类型的页面上做优化,可以获取到最大的收益
思考4. 分治思想
分而治之,将大的,复杂的问题拆解成若干类似的小问题
思考5. 分!
- 先怎么分?是不是把静态的和动态的要先分开?怎么体现?
分开动静,某个动态无法继续的时候还可以退回,走全diff; 各自声明两个数据结构存储
- 存在哪儿?
还是存在寻你 dom 中,多一个dynamicChildren
- 还可以继续分吗?怎么甄别呢?
可以通过字符串匹配得知动态,静态 - {{}} : 之类 通过 patchFlag 给每个节点做标记,在虚拟 dom 中挂一个标记,判断什么具体什么类型
export const enum PatchFlags {
TEXT = 1,// 动态的文本节点
CLASS = 1 << 1, // 2 动态的 class
STYLE = 1 << 2, // 4 动态的 style
PROPS = 1 << 3, // 8 动态属性,不包括类名和样式
FULL_PROPS = 1 << 4, // 16 动态 key,当 key 变化时需要完整的 diff 算法做比较
HYDRATE_EVENTS = 1 << 5, // 32 表示带有事件监听器的节点
STABLE_FRAGMENT = 1 << 6, // 64 一个不会改变子节点顺序的 Fragment
KEYED_FRAGMENT = 1 << 7, // 128 带有 key 属性的 Fragment
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512
DYNAMIC_SLOTS = 1 << 10, // 动态 solt
HOISTED = -1, // 特殊标志是负整数表示永远不会用作 diff
BAIL = -2 // 一个特殊的标志,指代差异算法
}
// like fiber!
// Block - Block Tree
{
tag: 'div',
children: [
{ tag: 'h1', children: 'hello world!' },
{ tag: 'p', children: ctx.text, patchFlag: 1 /* TEXT */ },
],
dynamicChildren: [
// 所有子元素中动态的部分哦
{ tag: 'p', children: ctx.text, patchFlag: 1 /* TEXT */ },
]
}
思考7. 导致问题的原因找到了吗?如何解决?
dynamicChildren可能会和原始的有冲突
p 节点相同,但是外层一个是div 另一个是 section;正常 diff 先走 div,然后再section 完全不同的节点,直接把 div 干掉重新添加;但是现在不会再重新渲染
// demo1
<div>
<h1>hello world!</h1>
<div v-if="someCondition">
<p>{{ text }}</p>
</div>
<section v-else>
<p>{{ text }}</p>
</section>
</div>
// demo2
<div>
<p v-for="i in list">{{ i }}</p>
</div>
思考7. 导致问题的原因找到了吗?如何解决?
原因只是把动态的节点提出来,但是没有考虑外层结构
// 尽量保持一样的结构
{
tag: 'div',
children: [
{ tag: 'h1', children: 'hello world!' },
// ...
],
dynamicChildren: [
// v-if 也作为一个 Block
// 现在,我们有了 Block Tree!
{ tag: 'div', dynamicChildren: []},
{ tag: 'section', dynamicChildren: []},
]
}
整个v-if 形成一个block,新增了block tree, block - 新的数据结构,额外挂了几个属性
思考8. 并不能彻底解决可能的边界情况,怎么兜底?
<p v-for='i in 100'>{{ i }}</p> // i 只可能是静态的
<p v-for='i in list'>{{ i }}</p> // i 是动态的,也可能是动态的
分治的好处,随时可以退一步海阔天空; i in list 这种情况,就会使用传统的 diff
思考9. 新的数据结构一定对应着新的渲染函数
// 感受一下
// openBlock
// disableTracking ????
function openBlock(disableTracking = false) {
blockStack.push((currentBlock = disableTracking ? null : []));
}
function createBlock(type, props, children, patchFlag, dynamicProps) {
const vnode = createVNode(type, props, children, patchFlag, dynamicProps, true);
// 构造 `Block Tree`
vnode.dynamicChildren = currentBlock || EMPTY_ARR;
closeBlock();
// 注意是需要条件的
if (someCondition === true) {
currentBlock.push(vnode);
}
return vnode;
}
// 不是所有的都可以是 Block 的
if (
isBlockTreeEnabled > 0 &&
// avoid a block node from tracking itself
!isBlockNode &&
// has current parent block
currentBlock &&
// presence of a patch flag indicates this node needs patching on updates.
// component nodes also should always be patched, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
patchFlag !== PatchFlags.HYDRATE_EVENTS
) {
currentBlock.push(vnode)
}
openBlock 和 createBlock 成对出现
现在可以这么写渲染函数了
<div>
<h1>hello world!</h1>
</div>
const render = ()
=> (openBlock(), createBlock('div', null, [createVNode('h1', null, 'hello world!')]))
思考10. 上面的渲染函数可以优化吗?
// hoists
let __h1__ = createVNode('h1', null, 'hello world!')
const render = ()
=> (openBlock(), createBlock('div', null, [__h1__]))
将函数的结果提在外面,返回就是一个结果,相当于做了一个缓存
思考11. 节点提升的有效范围?
可以理解为,思考哪些是不可以被提升的
- 动态 props
- ref
- 自定义指令
- ...
思考12. 节点不能提升了,我们就眼巴巴望着?
节点虽然是动态的,有些地方可以提升,比如属性
<div>
<h1 class='foo'>{{ text }}</h1>
</div>
// props hoists
let __cls__ = { class: 'foo' }
const render = () => (openBlock(), createBlock('div', null, [
createVNode('p', __cls__, ctx.text, PatchFlags.TEXT)
]))
最大程度提升静态内容
思考13. 还可以继续?
// 预先字符串化!!!
export function createStaticVNode(
content: string,
numberOfNodes: number
): VNode {
// A static vnode can contain multiple stringified elements, and the number
// of elements is necessary for hydration.
const vnode = createVNode(Static, null, content)
vnode.staticCount = numberOfNodes
return vnode
}
// 当然是满足一定量的情况,比如超过 20 个满足条件的节点,才能被预先字符串化
思考14. 还有啥可以优化的?
还知道 React 的一个坑点吗?
import React from 'react'
import A from './components/A'
function App() {
return (
<>
<A onClick={() => handleSomeClick()} />
</>
)
}
优化:缓存事件
const handler = () => handleSomeClick()
(openBlock(), createBlock(A, { onClick: handler }))
// v-once
此外还有很多优化和值得思考的地方,比如代码层面
编译原理
编译流程:词法&语法分析 -> Parsing -> Transformation 词法分析(Tokenlize) - 打成一个个的字符串
For the following syntax:
*
* (add 2 (subtract 4 2))
*
* Tokens might look something like this:
*
* [
* { type: 'paren', value: '(' },
* { type: 'name', value: 'add' },
* { type: 'number', value: '2' },
* { type: 'paren', value: '(' },
* { type: 'name', value: 'subtract' },
* { type: 'number', value: '4' },
* { type: 'number', value: '2' },
* { type: 'paren', value: ')' },
* { type: 'paren', value: ')' },
* ]
*
Parsing - 将token 合成 AST
* And an Abstract Syntax Tree (AST) might look like this:
*
* {
* type: 'Program',
* body: [{
* type: 'CallExpression',
* name: 'add',
* params: [{
* type: 'NumberLiteral',
* value: '2',
* }, {
* type: 'CallExpression',
* name: 'subtract',
* params: [{
* type: 'NumberLiteral',
* value: '4',
* }, {
* type: 'NumberLiteral',
* value: '2',
* }]
* }]
* }]
* }
Transformation - 增删改 AST 对象,变成新的 AST
编译器:
function compiler(input) {
let tokens = tokenizer(input);
let ast = parser(tokens);
let newAst = transformer(ast);
let output = codeGenerator(newAst);
// and simply return the output!
return output;
}
[super-tiny-compiler]github.com/jamiebuilds…