《Vue.js设计与实现》学习笔记 | 权衡的艺术

声明式与命令式

声明式编程

声明式编程与命令式不同,其 更加在意结果而非过程,强调声明而非命令。类似于领导(开发者)发话,只要说一句话就可以了,而底下的员工(程序)要考虑的事情就很多了。

例如,你调用一个名为 getUserName() 的方法,作为调用者,你并不需要关注其内部是如何实现的,只需要知道产生的结果即可,这就是声明式。

命令式编程

命令式编程强调对程序实现过程的 具体描述,即“命令”的过程。例如在 JavaScript 中你要修改某个元素内部的文本,如果你使用原生的DOM操作方法,你需要编写以下代码:

const element = document.getElementById('#id');	// 通过Id获取元素
element.innerHTML = '新的内容';	// 将新的内容赋予元素的innerHTML

你编写了详细的修改过程,每一步都是对程序的 精确控制,这就是命令式编程。

核心区别

声明式简洁易读,命令式灵活可控。

声明式编程具有容易实现高内聚、低耦合的特性,如以下代码:

const id = getUserId();
if (isValidId(id)) {
    return getUserData(id);
}
throwInvalidError();

其含义一目了然,甚至不需要添加任何注释。其中的逻辑分散在多个方法中,调用者无需关注其内部的具体实现流程。

但是,如果调用者需要对其中任何一个过程进行精确控制,由于代码逻辑被封装在方法中,除非修改对应方法的实现,否则都是不能达到目的。并且由于声明式编程的这一特性,程序往往会被迫承担多余的性能消耗。

这时候,命令式编程就体现出他的优势了,命令式允许开发者对实现流程的细节做任意程度修改,为程序开发提供了细粒度的控制。但是命令式编程的缺点也是显而易见的,命令式编程会极大增加代码的复杂度,并使得代码难以理解。

综上所述,我们可以得到两者的弊端:声明式编程可能会造成不必要的性能损耗,命令式编程则可能会使得程序变得复杂难懂。

对于性能的考量

声明式代码的性能并不优于命令式代码的性能

书中提到,对于框架而言,为了实现最优更新性能,其只需要找到更新前后变化的部分进行更新即可。

在这次更新中,如果我们使用了声明式编程的方式,那么框架并不知道实际上哪里发生了变化。因此,他需要使用一种算法来找出变化之处 —— Diff 算法。如果我们将 Diff算法 的性能损耗设为 B,更新 DOM 的性能设为 A,那么使用声明式编程的性能消耗为:A + B

而如果我们采用命令式编程(不使用框架)的方式进行更新,那么总性能消耗就只有 A,因为这个过程省去了框架帮我们找出差异的损耗。

从中我们也可以得出一个结论,框架的性能表现主要取决于 B 的大小,即找出差异的性能。


虚拟DOM的引入

为了最小化 B (找出差异的性能)的大小,Vue 引入了 虚拟DOM 这一技术,在这种技术普遍存在于多种前端框架中,例如 React。

创建HTML元素

在以往如果我们需要创建一系列的 HTML 元素,直接将 模板字符串 赋值给父元素的 innerHTML 属性是一种可行的方法,它看起来是这样的:

element.innerHTML = '<span><div>...</div></span>';

这种方法的性能损耗相当大。网页为了渲染出模板字符串的内容,必须首先将模板字符串解析为 DOM 树,而这是一个 DOM 层面的计算。

我们都知道,涉及 DOM 的计算远比 JavaScript 层面的计算性能差。

如果我们每次修改元素内容的时候都对 innerHTML 做操作,哪怕 innerHTML 字符串替换前后只差了一个字符,那么整个innerHTML对应的内容都要重新创建。注意此处用词是 创建 而非 更新 ,由于DOM层面的计算代价十分昂贵,过多的元素重新创建将会造成数量级的性能消耗。尤其是当 innerHTML 模板绑定的元素非常多的时候,性能更是差得离谱。

这时候虚拟DOM存在的意义就显而易见了,由于虚拟DOM是Javascript层面的产物,所以对其进行更改并不会产生在DOM层面进行修改那样巨大的代价。

心智负担 or 可维护性 or 性能

诚然,如果我们采用了命令式编程的方式进行DOM层面的修改,确实能够获得较高的性能。只不过我们必须明白: 写出绝对优化的代码是一件非常困难的事情 。命令式编程会严重加剧开发者的心智负担并且降低程序的可维护性,这与其产生的部分性能损耗相比也许更加严重。

现在我们已经意识到了一件事:完全可以用可接受的、较少的性能损耗去换取较低的心智负担和更高的维护性。

虚拟DOM存在的意义也许就在于此。


编译时和运行时

对于框架而言,有三种选择:纯编译时、运行时 + 编译时、纯运行时

运行时

假设你给框架提供了一个递归对象,每个对象中都包含本元素的属性和其子元素,类似于:

const obj = {
    tag: 'div',
    children: [
        { tag: 'span', children: 'hello world' }
    ]
}

然后在程序运行时,你使用一个名为 Render 的函数来将 obj 渲染为DOM元素。

Render(obk, document.body); // 渲染到body下

那么这就是一个运行时框架。

编译时

你自创了一种用于表示HTML结构的语法,但是框架得在用户写完后对这种语法进行转化,转化为一种能被利用的数据。例如,Vue中的模板语法:

<template>
    <div>
        <span>hello world</span>    
    </div>
</template>

于是你编写了一个叫 Compiler 的程序,这个程序将在用户保存时自动完成以上操作。那么这就是一个运行时框架。

运行时 + 编译时

框架将用户编写的模板语法转化为了类似于 obj 的数据,然后在运行时使用 Render 函数对其进行渲染。那么,这个框架就变成了一个运行时 + 编译时的框架。Vue.js 3.0 就是这样的一种框架。


总结

本章主要讲了声明式和命令式各自的特点和局限,框架开发者应当如何尽可能降低声明式带来的性能损耗,并降低开发者的心智负担,提高编写程序的可维护性。

接着,书中讨论了虚拟DOM的性能,以及其与传统操作模式的区别的优势。

最后,书中讨论了编译时和运行时这两种框架类型。并指出 Vue 3.0 术语运行时 + 编译时的框架。


本文同样也发布在我的个人博客网站:《Vue.js设计与实现》学习笔记 | 权衡的艺术 。欢迎来访噢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值