惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Recent Announcements
Recent Announcements
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
B
Blog
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
腾讯CDC
D
Docker
G
Google Developers Blog
D
DataBreaches.Net
雷峰网
雷峰网
Blog — PlanetScale
Blog — PlanetScale
S
SegmentFault 最新的问题
The Cloudflare Blog
有赞技术团队
有赞技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Stack Overflow Blog
Stack Overflow Blog
大猫的无限游戏
大猫的无限游戏
量子位
美团技术团队
aimingoo的专栏
aimingoo的专栏
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Engineering at Meta
Engineering at Meta
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园_首页

Plist 二进制格式 Milvus 和 PGVector,哪个更好? OpenClaw 已过时?在 VS Code 中运行 Hermes Agent! 第30篇文章:一个大三计科生的自白 Manim如何在数学公式中完美显示中文? Docker 部署 RocketMQ 5 并发编程核心概念辨析 C#事务处理最佳实践:别再让“主表存了、明细丢了”的破事发生 CLI 是什么?为什么大厂突然集体卷命令行? 【从0到1构建一个ClaudeAgent】协作-自主Agent UIImageView 设置图片不生效的原因排查 最小二乘问题详解20:无先验约束下的增量式SFM自由网平差 痞子衡嵌入式:大话双核i.MXRT1180之XIP应用里借助MU实现可靠Flash IAP的方法 AI Chat 封装, SemanticKerne.AiProvider.Unified 已发布 Windows下右键编辑js文件无法打开记事本——在注册表中使用环境变量 在后台服务中使用 Scoped 服务,为什么总是报错? H200 安装驱动并使用sglang启动模型 wireshark 抓包Trap上报告警内容 我用 AI 辅助开发了一系列小工具(2):图片压缩工具 [A Primer On MC and CC] 2.1 Memory Consistency 1 - 指令重排序和 SC 模型 Oracle数据库SCN推进技术详解与实践指南 玩转控件:封装个带图片的Label控件 Claude Code 4.7 真正该升级的不是模型,而是你的工作流 前端小白一句话,AI 帮我做了个颜值拉满的桌面媒体播放器。当代码不再是门槛,一句话编程就是现实。 5. WorkBuddy: 小龙虾的灵魂三件套,让你的小龙虾不只是工具 SQLite 分片方案实战:三种分片策略的深度对比 告别简陋 UI!一款基于 Fluent Design 和基于 WinUI 的开源免费、现代化的 Avalonia UI 控件库 关于二进制排列组合枚举的总结 AI开发-python-LangGraph框架(3-27-LangGraph从零实现大模型智能决策工作流) ElasticSearch主分片和副本分片概念详解
网页为什么需要框架?
微山湖上静悄悄 · 2026-06-05 · via 博客园_首页

从一个计算器说起

想象你面前有一个简单的网页计算器:

┌─────────────────────┐
│  [         42     ] │  ← 显示区
├─────────────────────┤
│  7  │  8  │  9  │ ÷ │
│  4  │  5  │  6  │ × │
│  1  │  2  │  3  │ - │
│  0  │  .  │  =  │ + │
└─────────────────────┘

用纯 JavaScript 你怎么做?

// 获取元素
const display = document.getElementById('display')
const buttons = document.querySelectorAll('button')

let currentInput = ''
let previousInput = ''
let operator = null

// 每个按钮都要手动绑定事件
buttons.forEach(btn => {
  btn.addEventListener('click', () => {
    const value = btn.textContent

    if (value === '=') {
      // 计算结果
      const result = calculate(previousInput, currentInput, operator)
      display.value = result           // ← 手动更新 DOM
      currentInput = result
      previousInput = ''
    } else if (['+', '-', '×', '÷'].includes(value)) {
      operator = value
      previousInput = currentInput
      currentInput = ''
    } else {
      currentInput += value
      display.value = currentInput     // ← 手动更新 DOM
    }
  })
})

这个计算器只有一个需要更新的地方(显示屏),代码已经有点乱了。

现在想象一个真实的应用

  • 用户资料卡片:头像、名字、邮箱、个人简介
  • 待办事项列表:可以添加、删除、标记完成
  • 消息通知:右上角的红点数字
  • 购物车:商品列表 + 总价 + 数量角标

你能想象纯手写代码来维护这些吗?


现代网页的复杂度爆炸

让我们做一个简单的思想实验。假设你有一个待办事项应用:

┌──────────────────────────────┐
│  我的待办          📋 3 项   │  ← 标题栏,显示总数
├──────────────────────────────┤
│  ☑ 买牛奶       [删除]      │
│  ☐ 写报告       [删除]      │
│  ☐ 健身         [删除]      │
├──────────────────────────────┤
│  已完成: 1 / 总计: 3        │  ← 底部状态栏
└──────────────────────────────┘

纯 JS 的做法:

// 添加一个待办项
function addTodo(text) {
  // 1. 更新数据
  todos.push({ text, completed: false })

  // 2. 手动重绘列表
  renderTodoList()

  // 3. 更新标题栏的计数
  updateTitleCount()

  // 4. 更新底部状态栏
  updateFooter()

  // 5. 如果列表为空,还要显示"暂无待办"
  updateEmptyState()
}

// 删除一个待办项
function deleteTodo(index) {
  todos.splice(index, 1)
  renderTodoList()
  updateTitleCount()
  updateFooter()
  updateEmptyState()
}

// 标记完成
function toggleTodo(index) {
  todos[index].completed = !todos[index].completed
  renderTodoList()
  updateTitleCount()  // ← 又忘了?万一哪次忘记调就出 bug
  updateFooter()
}

问题很明显:

  1. 每个操作都要手动更新所有相关的 DOM。忘了更新任何一处,画面就对不上了。
  2. 数据变了,视图不会自动变。数据和视图之间有一条巨大的鸿沟。
  3. 代码散落在各处。同一个 DOM 节点在不同函数中被更新,追踪起来极其痛苦。

数据与视图的鸿沟

用一张图来表示传统开发的困境:

   数据层                    视图层
┌──────────┐            ┌──────────┐
│  todos   │            │  <div>   │
│  user    │ ═══?═══》  │  <ul>    │
│  cart    │   (手动)    │  <span>  │
└──────────┘            └──────────┘

  每次数据变化,你都要手动找到所有相关的 DOM 节点,
  然后写代码去更新它们。
  → 容易遗漏
  → 代码膨胀
  → 难以维护

而理想的状态是:

   数据层                    视图层
┌──────────┐            ┌──────────┐
│  data    │ ═══自═══》  │  自动    │
│  变了!  │   动同步    │  更新!  │
└──────────┘            └──────────┘

  你只管改数据,视图自动跟着变。

这就是 "数据驱动视图" 的思想——Vue 等现代框架的核心哲学。


一个故事:老板和秘书

让我讲个故事来帮你建立直觉。

从前有一个老板(数据),他每天要处理很多事情。公司有一块公告板(视图/页面),上面写着公司的各种信息。

没有秘书的时候(纯 JS 开发):

老板每次做一件事,都要自己跑到公告板前修改:

  • "销售额涨了,我去改一下数字"
  • "新员工入职,我去加一行信息"
  • "有个项目黄了,我去把那一行删掉"

老板累得半死,还经常忘记改某个地方,导致公告板上的信息和实际对不上。

有了秘书之后(用 Vue):

老板雇了一个聪明的秘书(Vue 的响应式系统)。秘书对老板说:

"您只管做决策就好。您桌上有一沓文件(响应式数据),您在上面写什么,公告板(视图)上就会自动更新。我负责盯着这些文件,一旦有变化,立刻就更新公告板上对应的位置。"

于是老板只需要:

// 老板只管改数据
this.sales = 1000000      // 销售额变了 → 秘书自动更新公告板
this.employees.push(张三)  // 新员工 → 秘书自动更新公告板
this.projects.pop()        // 项目取消 → 秘书自动更新公告板

这就是 Vue 做的事情:它在你和数据之间放了一个"会盯着数据变化"的秘书,一旦数据变了,它知道该更新哪里。


Vue 的整体架构:一个总览

在深入细节之前,我们先看一眼 Vue 的全貌。这样你不会在后面的章节中"迷路"。

                    ┌──────────────────────────┐
                    │     new Vue({...})       │
                    │     你的代码从这里开始     │
                    └────────────┬─────────────┘
                                 │
                ┌────────────────┼────────────────┐
                ▼                ▼                 ▼
        ┌───────────┐   ┌─────────────┐   ┌─────────────┐
        │  模板编译  │   │  响应式系统  │   │  虚拟 DOM   │
        │           │   │             │   │             │
        │ HTML 模板  │   │ 数据变 →    │   │ JS对象描述  │
        │    ↓      │   │ 通知所有    │   │ 视图结构    │
        │ 渲染函数   │   │ 依赖方      │   │    ↓       │
        │           │   │             │   │ Diff + Patch│
        └─────┬─────┘   └──────┬──────┘   └──────┬──────┘
              │                │                  │
              │         ┌──────▼──────┐           │
              │         │  依赖收集    │           │
              │         │ (Dep/Watcher)│          │
              │         └──────┬──────┘           │
              │                │                  │
              └────────────────┼──────────────────┘
                               ▼
                    ┌──────────────────┐
                    │   更新真实 DOM    │
                    │   页面变了!      │
                    └──────────────────┘

用一句话描述这个流程:

模板(template)被编译成渲染函数(render function),渲染函数读取响应式数据,在读取过程中 Vue 收集"谁依赖了这个数据",然后生成虚拟 DOM(VNode),最后通过 diff 算法对比新旧 VNode,把差异更新到真实 DOM 上。当响应式数据变化时,Vue 通知所有依赖方重新执行渲染函数,周而复始。

这个循环就是 Vue 的核心工作流。后面每一个章节,都是在拆解这个流程中的一个环节。


让我们先体验一下"响应式"的魔法

写一段最简单的 Vue 代码,感受一下:

<div id="app">
  <p>{{ message }}</p>
  <button @click="changeMessage">点我</button>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    message: '你好,世界'
  },
  methods: {
    changeMessage() {
      this.message = '你好,Vue!'  // 改数据,视图自动变
    }
  }
})
</script>

你只改了 this.message 这一个变量,页面上的 <p> 标签就自动更新了。

你不用写 document.querySelector('p').textContent = ...
你不用管什么时候更新、更新哪个节点。
你只管改数据。

这就是框架存在的意义。


接下来的旅程

现在你已经理解了"为什么需要框架"。接下来的每一篇,我们都会深入一个具体的技术点,用同样的方式——先理解问题,再看解决方案,最后动手实现一个迷你版本

让我们从核心中的核心开始:响应式系统