Skip to content

概述

命令式和声明式

命令式框架关注过程。

js
$('#app') // 获取 div
  .text('hello world') // 设置文本内容
  .on('click', () => alert('ok')) // 绑定点击事件

声明式框架更加关注结果。

vue
<div @click="() => alert('ok')">hello world</div>

注意

声明式代码的性能不优于命令式代码的性能。毕竟框架本身就是封装了命令式代码才实现了面向用户的声明式。对于框架来说,为了实现最优的更新性能,它需要找到前后的差异并只更新变化的地方。即便前后结果一致,也会执行比对函数。

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

虽然声明式代码的性能不优于命令式代码的性能,甚至会降低性能,但声明式代码的可维护性更强。

运行时和编译时

编译时:提供一个 Compiler 函数,将 HTML 字符串编译为树型结构的数据对象。 运行时:供一个 Render 函数,将树型结构的数据对象递归地渲染成 DOM 元素。 纯编译时:提供一个 Compiler 函数,将 HTML 字符串编译为命令式代码。 运行时编译:代码运行的时候才开始编译。

Vue 就是运行时 + 编译时的架构,打包时编译,做一些优化,上线后只需要渲染器。

MVC

MVC 通过分离 ModelViewController 的方式来组织代码结构。

  • View 负责页面的显示逻辑;
  • Model 负责数据增删改查;
  • Controller 负责业务处理;

当用户与页面产生交互时,触发 Controller 控制器,通过调用 Model 层,然后 Model 层再去通知 View 层更新。

MVVM

MVVM 分为 ModelViewViewModel

  • Model 代表数据模型,数据和业务逻辑都在 Model 层中定义;
  • View 代表 UI 视图,负责数据的展示;
  • ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;

ModelView 并无直接关联,而是通过 ViewModel 来进行联系的,实现数据双向数据绑定。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。

编译器 Compiler

编译器的作用其实就是将模板编译为渲染函数。

html
<div @click="handler">click me</div>

对于编译器来说,模板就是一个普通的字符串,它会分析该字符串并生成一个功能与之相同的渲染函数:

js
function render() {
  return h('div', { onClick: handler }, 'click me')
}

一个 .vue 文件 <template> 标签里的内容就是模板内容,编译器会把模板内容编译成渲染函数并添加到 <script> 标签块的组件对象上 render 函数。

渲染器 Renderer

渲染器的作用就是把虚拟 DOM 渲染为真实 DOM。渲染器还有一个重要的作用就是寻找并且只更新变化的内容(diff)。

  1. 根据虚拟 DOM 标签名创建 真实 DOM;
  2. 为真实 DOM 添加属性以及事件;
  3. 处理子节点,递归调用渲染器;
  4. 最后将真实 DOM 挂在的容器节点下;
js
function renderer(vnode, container) {
  // 1. 使用 vnode.tag 作为标签名称创建 DOM 元素
  const el = document.createElement(vnode.tag)
  // 2. 遍历 vnode.props,将属性、事件添加到 DOM 元素
  for (const key in vnode.props) {
    // ...
  }

  // 3. 处理 children
  if (typeof vnode.children === 'string') {
    // 如果 children 是字符串,说明它是元素的文本子节点
    el.appendChild(document.createTextNode(vnode.children))
  } else if (Array.isArray(vnode.children)) {
    // 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作为挂载点
    vnode.children.forEach(child => renderer(child, el))
  }

  // 4. 将元素添加到挂载点下
  container.appendChild(el)
}

编译器和渲染器配合工作:

  1. 编译时做静态标记 patchFlags,渲染时 diff 跳过静态节点;

响应式 Reactivity

响应式的作用是让数据和视图保持同步。

Vue.js 的响应式系统基于 JavaScript 对象的 getter 和 setter 实现的,在 getter 中收集依赖,setter 中,通知视图会自动更新。

Vue 3 中的响应式系统使用了 ES6 的 Proxy 对象来实现,相比于 Vue 2 中的 Object.defineProperty 实现具有更好的性能、更丰富的 API 和更少的限制。

以 vue3 为例

js
const target = { name: '张三' }

const proxy = new Proxy(target, {
  get(target, key) {
    // 依赖收集
    // ...

    return target[key]
  },
  set(target, key, newVal) {
    // 通知更新
    // ...

    target[key] = newVal
  }
})

除了编译器、渲染器、响应式这些核心功能外,Vue.js 还提供了许多其他的功能,如组件系统、路由、状态管理等。