前端框架设计如何权衡

898 阅读3分钟

文章基于对《Vue.js 设计与实现》「权衡的艺术」章节学习,外加个人理解,并未完全照搬书中代码和文字。推荐购买正版《Vue.js 设计与实现》进行深度学习。

命令式和声明式

命令式编程关注过程,典型的命令式编程库 jQuery:

<div id="app"></div>

// 获取 id 为 app 的 div 标签
$('#app')
    // 设置文本类容为 hello world
    .text('hello world')
    // 点击时,内容修改为 hello jquery
    .on('click', function () {
        this.innerText = 'hello jquery'
    })

命令式编程更符合普通思维方式,直接操作 DOM,性能也更好。

声明式编程关注结果,如 Vue:

<template>
    <!-- 定义 UI -->
    <div id="app">{{ content }}</div>
</template>

<script setup>
import { ref } from 'vue'

// 声明状态
const content = ref('hello world')
const changeContent = () => {
    // 修改状态
    content.value = 'hello vue'
}
</script>

声明式编程符合 UI = f(x) 的函数思想,数据驱动视图,UI 修改过程交给框架,开发者只需要关注变量,减少维护时的心智负担。

总结:声明式编程的可维护性更好,但性能低于命令式,Vue 要做的就是保持可维护性的同时尽量减少性能损失。

虚拟 DOM 的性能

Vue 在初次渲染视图时,会生成一个 VNode,更新时又会生一个新的 VNode,将新旧两个 VNode 进行对比,找到最小修改差异,让进行修改,所以:

声明式代码的更新性能消耗 = 找出差异的性能消耗 + 修改 DOM 的性能消耗

理论上,直接原生 JavaScript 操作 DOM 性能更好,但这对开发者能力有较高的要求。在使用 jQuery 的时期,往往存在很多 innerHTML 暴力操作。

初次渲染时的性能对比

虚拟 DOM 耗时 = 创建 JavaScript 对象(VNode) + 全量 DOM 渲染

innerHTML 耗时 = 拼接 JavaScript 字符串 + 全量 DOM 渲染

更新时的性能对比

虚拟 DOM 耗时 = 创建新 JavaScript 对象(VNode) + Diff + 部分 DOM 渲染

innerHTML 耗时 = 拼接 JavaScript 字符串 + 全量 DOM 渲染

小结

操作虚拟 DOM 和操作原生 DOM 谁的性能更好,不能简单下结论。因为这跟如何操作 DOM,何时操作 DOM,修改量多少都有关。

虚拟 DOM 是保证运行性能、代码可维护性、降低心智负担的综合选择

运行时与编译时

纯运行时

手写 DOM 对象,运行时直接遍历对象进行渲染,但书写麻烦。

var vnode = {
    tag: 'div',
    children: [
        {
            tag: 'span',
            children: 'hello '
        },
        {
            tag: 'span',
            children: 'vue'
        }
    ]
}
function render(vnode, parent) {
    const el = document.createElement(vnode.tag)
    const children = vnode.children
    if (typeof children === 'string') {
        el.innerHTML = vnode.children
    }
    if (Array.isArray(children) && children.length !== 0) {
        children.forEach(child => {
            render(child, el)
        })
    }
    parent.appendChild(el)
}
render(vnode, document.body)

编译时 + 运行时

开发时写普通 HTML 模板:

<div>
    <span>hello </span>
    <span>vue</span>
</div>

构建时通过 compiler 进行编译:

const htmlString = '<div>...</div>'
const content = compile(htmlString)

运行时渲染:

render(content, document.body)

纯编译时

我们可以将模板编译成渲染函数或者 VNode,那同样也可以直接编译成原生 DOM 操作:

const htmlString = '<div>...</div>'
const content = compile(htmlString)

// 编译生成
const div = document.createElement('div')
const hello = document.createElement('span')
hello.innerHTML = 'hello '
div.appendChild(hello)
// ...

小结

纯运行时以及编译时 + 运行时的方式都可以通过不同的渲染器实现跨平台渲染,灵活性高。

但是纯运行时书写麻烦,如果想做静态优化需要手动标记,而编译时 + 运行时可以交给 compiler 去做。

纯编译时 理论上能获得最好的性能,但灵活性相对较弱。

综上

Vue 是一个编译时 + 运行时声明式 UI 框架,利用虚拟 DOM 进行更新渲染。保证其拥有良好的可维护性和灵活性,同时加上静态优化,尽可能接近原生的性能。