Dark Dwarf Blog background

JavaScript 视图

JavaScript 视图

1. 视图的引入

在前端开发中,视图 (View) 是用户在浏览器中看到和交互的一切——也就是用户界面(UI)。JavaScript 的核心任务之一就是动态地操纵这个视图,以响应用户操作或数据变化。

最原始的视图操作方式是直接使用 DOM API。

// 1. 获取一个 DOM 元素
const container = document.getElementById('app');

// 2. 创建一个新的 h1 元素
const title = document.createElement('h1');

// 3. 设置其内容和样式
title.textContent = 'Hello, World!';
title.style.color = 'blue';

// 4. 将新元素添加到视图中
container.appendChild(title);

这种方式虽然可行,但当应用变得复杂时,会迅速暴露出一系列问题:

  1. 过程式代码: 代码是命令式的,一步步告诉浏览器“做什么”(创建元素、设置属性、插入),而不是声明式地“想要什么”(一个标题)。这使得代码难以阅读和维护。
  2. 状态管理混乱: 当数据(例如,一个列表)发生变化时,你需要手动编写复杂的逻辑来找出视图中哪些部分需要更新、添加或删除。这非常容易出错。
  3. 性能问题: 频繁地、不加选择地直接操作 DOM 会导致大量的“重排”(Reflow)和“重绘”(Repaint),严重影响页面性能

为了解决这些问题,现代前端开发引入了各种视图库和框架。

2. 视图库与框架

a.a. 核心思想

现代视图库(如 React, Vue, Svelte)的核心思想是数据驱动视图。开发者不再需要手动操作 DOM,而是遵循一个简单的模式:

  • UI = f(state)
  • UI (视图): 用户看到的界面。
  • f (函数): 视图库或框架提供的渲染机制。
  • state (状态): 驱动应用运行的 JavaScript 数据(对象、数组等)。

开发者的主要任务是管理状态 (state)。当状态发生变化时,框架会自动、高效地计算出 DOM 的最小变化,并更新视图。

这就像在玩一个模拟经营游戏。你只需要调整预算、政策(状态),游戏引擎(框架)就会自动更新城市的面貌(视图),而不需要你亲自去一砖一瓦地建造或拆除建筑。

b.b. 虚拟 DOM

i.i. 相关概念

为了实现高效的视图更新,React 和 Vue 等主流框架引入了**虚拟 DOM (Virtual DOM)**的概念。它是真实 DOM 在内存中的一个轻量级 JavaScript 对象表示。它描述了真实 DOM 的结构和属性。

ii.ii. 工作流程

虚拟 DOM 的工作流程如下:

  1. 当状态发生变化时,框架会根据新的状态生成一个新的虚拟 DOM 树。
  2. 框架会将这个新的虚拟 DOM 树与上一次渲染时保存的旧虚拟 DOM 树进行比较(这个过程称为 Diffing)。
  3. Diffing 算法会找出两棵树之间的最小差异(例如,只有一个 p 标签的文本变了)。
  4. 框架将这些差异一次性地批量地应用到真实的 DOM 上(这个过程称为 Patching)。
// 这是一个简化的虚拟 DOM 节点
const virtualNode = {
  tag: 'h1',
  props: { id: 'title' },
  children: ['Hello, Virtual DOM!']
};

// 框架会把这个对象转换成真实的 DOM
// document.createElement('h1')...

通过在内存中进行计算和比较,虚拟 DOM 最大限度地减少了对昂贵的真实 DOM 的操作次数,从而极大地提升了性能。

3. 数据绑定

数据绑定是实现“数据驱动视图”的关键机制,它负责将状态和视图“绑定”在一起。

a.a. 单向数据绑定

单向数据绑定是许多现代框架(如 React)推崇的模式。

  • 数据流是单向的: 状态 (State) 的变化会引起视图 (View) 的更新。
  • 视图是只读的: 你不能直接在视图中修改状态。要改变状态,必须触发一个动作 (Action),由动作来更新状态,从而再引起视图更新。

State

View

Action

这种模式使得数据流非常清晰、可预测,更容易调试和追踪 bug。下面是一个 React的例子:

function Counter() {
  // 1. 定义状态
  const [count, setCount] = useState(0);

  // 2. 视图从状态派生
  return (
    <div>
      <p>You clicked {count} times</p>
      {/* 3. 触发 Action 来更新状态 */}
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

b.b. 双向数据绑定

双向数据绑定在一些框架(如早期 AngularJS 和 Vue 的 v-model)中很流行。在双向数据绑定中,状态的变化会更新视图,同时,在视图中的用户输入(如表单)也会直接更新绑定的状态。下面是 Vue 的一个例子:

<div id="app">
  <!-- input 的值与 message 状态双向绑定 -->
  <input v-model="message">
  <p>{{ message }}</p>
</div>

<script>
  new Vue({
    el: '#app',
    data: {
      // 状态
      message: 'Hello Vue!'
    }
  });
</script>

在这个例子中,当你在输入框里打字时,message 状态会实时改变,p 标签里的内容也随之更新。反之,如果通过代码改变 message 的值,输入框的内容也会变。

双向绑定在处理表单时非常方便,可以减少大量模板代码。但当应用逻辑复杂时,可能会因为数据来源不清晰而导致难以追踪的问题。

特性传统 DOM 操作现代视图框架 (React/Vue)
编程范式命令式 (Imperative)声明式 (Declarative)
核心任务手动操作 DOM管理状态 (State)
数据流手动维护,容易混乱清晰可控 (单向或双向)
性能依赖开发者优化,容易产生瓶颈内置优化 (如虚拟 DOM),性能较高
开发效率低,代码冗长高,代码简洁,组件化