编程范式和程序语言特征
说明
声明式和命令式,函数式、面向过程和面向对象,运行时和编译时笔记总结
# 视图层
视图层框架通常分为命令式和声明式
# 命令式编程
命令式编程最大的特点,也是有别于其它编程范式的特征就是:命令式编程关注的是过程,
例:
01 - 获取 id 为 app 的 div 标签
02 - 它的文本内容为 hello world
03 - 为其绑定点击事件
04 - 当点击时弹出提示:ok
---------------------------------------
01 const div = document.querySelector('#app') // 获取 div
02 div.innerText = 'hello world' // 设置文本内容
03 div.addEventListener('click', () => { alert('ok') }) // 绑定点击事件
2
3
4
5
6
7
8
9
10
11
12
13
自然语言描述能够与代码产生一一对应的关系,代码 本身描述的是“做事的过程”,这符合我们的逻辑直觉
# 声明式编程
与命令式编程关注过程不同,声明式编程更多的是关注结果。它只表达计算的逻辑而不去描述其中的控制流程,即告诉计算机需要计算什么,而不是教计算机如何去计算
01 <div @click="() => alert('ok')">hello world</div>
# 函数式编程
深入理解函数式编程(上) - 美团技术团队 (meituan.com) (opens new window)
是声明式的子集。
函数式代码的特征:避免副作用。它不会依赖也不会改变当前函数以外的数据,无副作用的函数和高阶函数
函数式编程,就是将函数作为参数传递到另一个函数里。因为js里函数也是对象,所以在js 里会经常看到这种写法。
# 过程式编程
是命令式的子集
面向过程是一种以事件为中心的编程思想,编程的时候把解决问题的步骤分析出来,然后用函数把这些步骤实现,在一步一步的具体步骤中再按顺序调用函数。
# 命令式与声明式的权衡
声明式代码的性能 不优于命令式代码的性能
如果我们把直接修改的性能消耗定义为 A,把找出差异的性能消 耗定义为 B,那么有
命令式代码的更新性能消耗 = A
声明式代码的更新性能消耗 = B + A
命令式在理论上可 以做到极致优化,但是用户要承受巨大的心智负担;而声明式能够有 效减轻用户的心智负担,但是性能上有一定的牺牲,要想 办法尽量使性能损耗最小化。
# 面向对象编程
每一个都可以看做一个对象,而每个对象都有自己的属性和行为,对象与对象之间通过方法来交互。面向对象是一种以“对象”为中心的编程思想,把要解决的问题分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个对象在整个解决问题的步骤中的属性和行为。
面向对象编程是将事物对象化,通过对象通信来解决问题。面向对象编程,数据和对数据的操作是绑定在一起的。 面向对象的三大基本特征:
封装:把客观事物封装成抽象的类,隐藏属性和方法的实现细节,仅对外公开接口。
继承:子类可以使用父类的所有功能,并且对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。
多态:接口的多种不同的实现方式即为多态。同一操作作用于不同的对象,产生不同的执行结果。在运行时,通过指向基类的指针或引用来调用派生类中的虚函数来实现多态。
封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的类。它们的目的都是为了---代码重用。
而多态则是为了实现另一个目的---接口重用。
# 框架特征
纯运行时、运行时 + 编译时、纯编译时
# 纯运行时
纯运行时的框架。由于它没有编译的过程,因此我们没办 法分析用户提供的内容
假设我们设计了一个框架,它提供 一个 Render 函数,用户可以为该函数提供一个树型结构的数据对 象,然后 Render 函数会根据该对象递归地将数据渲染成 DOM 元 素。我们规定树型结构的数据对象如下
//tag 代表标签名称,children 既可以是一个数组(代表子节点),也可以直接是一段文本(代表文本子节点)
const obj = {
tag: 'div',
children: [
{ tag: 'span', children: 'hello world' }
]
}
function Render(obj, root) {
const el = document.createElement(obj.tag)
if (typeof obj.children === 'string') {
const text = document.createTextNode(obj.children)
el.appendChild(text)
} else if (obj.children) {
// 数组,递归调用 Render,使用 el 作为 root 参数
obj.children.forEach((child) => Render(child, el))
}
// 将元素添加到 root
root.appendChild(el)
}
Render(obj, document.body)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 运行时+编译时
const html = `
<div>
<span>hello world</span>
</div>
`
// 编写 Compiler 的程序
// 调用 Compiler 编译得到树型结构的数据对象
const obj = Compiler(html)
// 再调用 Render 进行渲染
Render(obj, document.body)
2
3
4
5
6
7
8
9
10
上面这段代码能够很好地工作,这时我们的框架就变成了一个运 行时 + 编译时的框架。它既支持运行时,用户可以直接提供数据对象 从而无须编译;又支持编译时,用户可以提供 HTML 字符串,我们将 其编译为数据对象后再交给运行时处理。准确地说,上面的代码其实 是运行时编译,意思是代码运行的时候才开始编译
# 纯编译时
纯编译时的框架,因为我们不支持任何 运行时内容,用户的代码通过编译器编译后才能运行
`<div>
<span>hello world</span>
</div>`
// 编写 Compiler 的程序 ... 得到
const div = document.createElenent('div ');
const span = docunent.createElenent('span ');
span.innerText = 'hello world';
div.appendChild(span);
document.body.appendChild(div);
2
3
4
5
6
7
8
9
10
11