加载中...

一 · 八个文件,各是什么

文件 干什么
index.htm 组装根 决定加载哪些文件 + 页面布局(一个 menu div + 一个 canvas)
dom.js Model 图形的数据 + 自己画自己
view.js ViewModel 五个事件盒子、花名册、selection、当前样式、重画
creator/rect.js Controller 画 Line / Rect / Ellipse / Circle
creator/path.js Controller 画折线
creator/freepath.js Controller 自由笔
accel/select.js Controller 选择工具(27 讲新增)
accel/menu.js Controller 菜单按钮 + 属性面板 + 鼠标位置显示

最容易忘的一条:menu.js 也是 Controller。
它不是"框架",它跟那些画图工具是平级的——只不过它的辅助 View 是按钮,别人的是橡皮筋。

二 · 三条链路

链路一 · 画一个矩形

你在画布上按下鼠标
  ↓  浏览器:查 document 树 → 命中 <canvas>
  ↓ 【view.js】     canvas.onmousedown 的转交函数
  ↓ 【view.js】     view.onmousedown 盒子
  ↓ 【rect.js】     QRectCreator.onmousedown → 记起点(Model 里什么都没发生)
拖动…松手
  ↓ 【rect.js】     onmouseup → buildShape()
  ↓ 【dom.js】      doc.addShape(矩形)          ★ 图形诞生
  ↓ 【view.js】     invalidateRect → 取笔、擦干净
  ↓ 【dom.js】      doc.onpaint → 每个图形自己画

链路二 · 换工具

你点菜单上的「Rect」按钮
  ↓  浏览器:查 document 树 → 命中 <button id="RectCreator">
  ↓ 【menu.js】     按钮的 onclick → onClickCtrl("RectCreator")
  ↓ 【view.js】     invokeController("RectCreator")
                      ├─ stopController()  → 上一个把盒子还回来
                      └─ 查花名册 → 工厂() → new QRectCreator(...)
  ↓ 【rect.js】     构造函数 → 抢走四个盒子

链路三 · 画完自动切回选择工具(27 讲新增)

(接链路一,松手之后)
  ↓ 【rect.js】     reset() → qview.fireControllerReset()      「我画完了」
  ↓ 【view.js】     看 onControllerReset 槽 → 取出函数调用
  ↓ 【menu.js】     启动时挂的那个函数 → qview.invokeController("ShapeSelector")
  ↓ 【view.js】     走一遍链路二的后半段
  ↓ 【select.js】   QShapeSelector 上岗,抢走盒子

链路四 · 选中一个图形并拖动(27 讲新增)

你在画布上点一下(当前是选择工具)
  ↓ 【view.js】     转交 → view.onmousedown 盒子
  ↓ 【select.js】   QShapeSelector.onmousedown
  ↓ 【dom.js】      doc.hitTest(pt) → 倒着遍历,问每个图形"这点在你身上吗"
  ↓ 【view.js】     view.selection = 命中的那个   →  setter 发 onSelectionChanged
  ↓ 【menu.js】     听到了,把调色板更新成这个图形的颜色
拖动…松手
  ↓ 【select.js】   onmouseup → selection.move(dx, dy)   ★ 这一刻 Model 才真的变

三 · 谁认识谁

menu.js       ──▶ view.js ──▶ dom.js
creator/*.js  ──▶ view.js ──▶ dom.js
creator/*.js  ──▶ dom.js          (直接调 addShape)
select.js     ──▶ view.js ──▶ dom.js

✗ menu.js      不认识任何 creator      (只念字符串 "RectCreator")
✗ creator 之间  互不认识
✗ view.js      不认识任何具体 Controller(只认识字符串 + 花名册)
✗ dom.js       谁都不认识

箭头全都朝下,没有回头的,没有横着的。

四 · View 上一共有几个「槽」

这是最容易混的地方。它们机制一样(都是装函数的字段),但用法完全不同

谁放进去 什么时候放 会换人吗 谁取出来调
onmousedown onmousemove
onmouseup ondblclick onkeydown
当前 Controller(构造时抢) 每次换工具换一次 会,一直在换 View(浏览器送事件来时)
onControllerReset 菜单 启动时挂一次 不会 View(Creator 调 fire... 时)
onSelectionChanged 菜单 启动时挂一次 不会 View(selection setter 里)
方向一:外部世界 → Controller
    浏览器 ─▶ canvas 盒子 ─▶ View 的五个盒子 ─▶ 当前 Controller

方向二:Controller 之间横向,View 当中转站
    Creator ─喊 fire─▶ View 的 onControllerReset 槽 ─▶ 菜单
    View 自己 ───────▶ View 的 onSelectionChanged 槽 ─▶ 菜单

View 在所有方向上干的都是同一件事:端着盒子,按盒子转交,不关心内容。

五 · 两个入口

事件其实从两个地方进来,在 View 汇合:

canvas.onmousedown  ┐
canvas.onmousemove  │
canvas.onmouseup    ├──▶  View 的五个盒子  ──▶  当前 Controller
canvas.ondblclick   │
document.onkeydown  ┘
     ↑ 只有这一个挂在 document 上(键盘没有坐标,canvas 又拿不到焦点)

六 · 相关篇目

想细看什么 去哪
完整源码 + 每个文件的结构地图(标行号) 带读-源码导航
前端底子(盒子、lambda、canvas、document) 带读-00
dom.js 逐类读 带读-01
一次画矩形的完整链条(含浏览器一侧) 带读-02
ViewModel 的责任 · 菜单与组装 · 架构思维 带读-03
27 讲:加选择工具 带读-04
28 讲:离线持久化与对象 ID 带读-05
29 讲:网络协议与服务端(含异步 带读-06
30 讲:对接、同步、Model 层的厚度 带读-07
31 讲:辅助界面元素与控件化 带读-09
32 讲:系统的概要设计(附 AI coding 对照 [带读-10](…/32-架构:系统的概要设计/带读-10-32讲:系统的概要设计(附 AI coding 对照).md)
收尾:五讲带走了什么(六个手法 · 16 条判断 · 诚实清单) 带读-08

七 · 服务端开发篇的带读(11~19)

这一页(一~六节)讲的是浏览器端的 qpaint(v26~v31)。
服务端那条线(34~46 讲)的带读接在下面,四讲实战 41~44 用的是同一个 qpaint,只是换到了 paintdom/

7.1 基础篇:存储与业务架构

想细看什么 去哪
37 讲:事务、主从与分布式(KV 是一切的基础) 带读-11
38 讲:文件系统与对象存储(去目录 / 去关系 / 去可变那三刀) 带读-12
39 讲:存储与缓存(缓存就是 @lru_cache · 那条永久不一致) 带读-13
40 讲:服务端的业务架构建议(四条建议背后的机制) 带读-14

7.2 实战四讲:把 mock 服务端做成产品级

想细看什么 去哪
41 讲:RPC 框架、分层与单元测试(★ 业务层零改动的那份 diff) 带读-15
42 讲:使用界面、数据结构与算法(★ 最厚的一讲:多租户 · mongodb · 索引 · 顺序与事务) 带读-16
43 讲:帐号与授权(★ OAuth 的零点 · 双 token · 授权码模式为什么那么绕) 带读-17
44 讲:dex、ID Token 与「不造轮子」(★ 网关翻译授权 · JWT 不是加密的) 带读-18
收尾:后端四讲带走了什么(六个手法 · 16 条判断 · 诚实清单 · 与前端五讲的对照 带读-19

7.3 服务端那边的源码在哪

前端那条线的源码在 26-…/代码/qpaint源码/v2631-…/v31;服务端这条线是:

分支 是什么 放在哪
v29-服务端 · v30-服务端 第一版 mock 服务端(全内存) 29 讲 · 30 讲
v31-服务端 mock 版定稿(41 讲的起点,286 行 service.go 41 讲
v41-服务端 换成 restrpc(169 行;业务层与 v31 字节相同 41 讲
v42-服务端 换成 mongodb + 多租户(含那份 README_IMPL.md 42 讲
v44-bear-网关 帐号授权(paintdom 零改动,全部在网关 44 讲

⚠ 一个必须记下来的事实:原文 44 讲让你看的 compare/v42...v44 是空的(0 commit,v44 = v42)。
接 dex 的代码不存在;真正提交了代码的是 v44-bear。详见 带读-19 · 6.1

7.4 服务端这条线的一句话

前端五讲问:什么该放进 Model?        → 需求在长,Model 就该跟着长(dom.js ×7.9)
后端四讲问:什么不该自己写?          → 环境在换,业务层就一行都不该动(×1.09)
★ 两个答案是同一件事:把业务从它所处的环境里剥出来。
公告栏
这是我的个人知识库。
记录技术,也记录生活 —— 读过的、试过的、想明白的,都堆在这儿。
最新文章
网站资讯
文章数目 :
5
已运行时间 :
本站总字数 :
15.7k
本站访客数 :
本站总访问量 :
最后更新时间 :
全局知识图谱
当前页面 已访问 文章 标签
ESC 关闭 · 滚轮缩放 · 拖拽移动 · Ctrl+G 开关