加载中...

一个让人安心的数字:26 讲那一版(v26)全部代码只有 593 行,其中核心的 dom.js 只有 109 行
不是那种读不完的大工程。

一 · 源码在哪:每一讲的代码放在自己章节下

每一讲的代码,就在那一讲的目录里。

26-实战(一)…/代码/
  ├── qpaint-mini.html          我改写的单文件版(带右侧面板,做实验用)
  ├── 事件盒子.py · 自己画自己.py · 工厂和实例.py · 递一支笔.py
  └── qpaint源码/v26/           ← 26 讲那一版(593 行)

27-实战(二)…/代码/qpaint源码/
  ├── v27/                      ← 加了选择工具(952 行)
  ├── diff-dom-v26-v27.txt      ← 27 讲给 Model 加了什么(314 行)
  └── diff-view-v26-v27.txt     ← 给 ViewModel 加了什么(75 行,适合练手)

28-实战(三)…/代码/
  ├── 两个ID.py                 localID / displayID 的可跑演示
  └── qpaint源码/
        ├── v28/                ← 加了离线持久化
        ├── diff-dom-v27-v28.txt
        └── diff-view-v27-v28.txt   (17 行)

29-实战(四)…/代码/qpaint源码/
  ├── v29/                      ← 浏览器端【与 v28 完全相同】
  ├── v29-服务端/                ← 【新增】paintdom 的 Go 代码
  │     shape.go · drawing.go · service.go · main.go + 两个测试
  └── diff-dom-v28-v29.txt      ← 【空文件】——浏览器端一个字没改

31-辅助界面元素…/代码/qpaint源码/
  ├── v31/                      ← 控件化(+ controls/ 四个文件)
  ├── diff-dom-v30-v31.txt      ← 【0 字节】——Model 层一行没改
  ├── diff-view-v30-v31.txt     ← 多例支持
  └── diff-menu-v30-v31.txt     ← 菜单【反而少了 36 行】

30-实战(五)…/代码/qpaint源码/
  ├── v30/                      ← dom.js 856 行
  ├── v30-服务端/                ← 多了一条 /sync
  ├── diff-dom-v29-v30.txt      ← 746 行,最大的一次变更
  ├── diff-view-v29-v30.txt     ← 【只有 22 行】
  └── diff-service-v29-v30.txt  ← 服务端就多了一条路由

五个版本的行数

文件 v26 v27 v28 v29 v30 倍数
dom.js Model 109 306 497 497 856 ×7.9
view.js ViewModel 112 125 131 131 133 ×1.2
三个原有 Creator Controller 269 256 256 256 256 ×0.95
服务端 *.go Model+Ctrl 657 725 新增

需求的重量几乎全压在 Model 上,上面两层几乎没被打扰。
展开见 带读-07 · 五

二 · ⚠️ 先说两处跟带读里不一样的地方

这两处会让你对不上,先讲清楚:

真实的 v26~v28 带读里 / qpaint-mini.html
View 怎么拿到 一个全局变量 qviewview.js 最后一行 var qview = new QPaintView())。
Controller 直接用它:qview.doc.addShape(...)
我把 view 当参数传进 Controllernew QRectCreator(qview, "rect")
Controller 的构造函数 constructor(shapeType)——不收 view constructor(view, shapeType)

为什么会有这个差异是 v31(31 讲)为了把画图程序做成控件才改的——
一个页面要能放多个画布,全局的 qview 就不够用了。详见
带读-09 · 五
我写 qpaint-mini.html 时照的是 v31 之后那版。

不影响理解qview 是全局变量还是参数,只是"Controller 怎么拿到 View"的写法差别,
机制完全一样。读真实源码时,看到光秃秃的 qview.xxx 就知道那是全局的那个 View。

另外,v26QPaintDoc 的字段叫 this.shapes(没有下划线)_shapes 是 v28 才改的。

三 · 推荐阅读顺序

先把最小的 v26 完整读一遍,再看 diff。 这样你就有了一个"完整的东西"打底。

顺序 文件 行数 读它是为了 对应带读
1 26 讲/…/v26/dom.js 109 Model 长什么样。一口气能读完 带读-01
2 26 讲/…/v26/index.htm 17 组装根。最短的文件,先看它有全局感 带读-03 · 2.2
3 26 讲/…/v26/view.js 112 ViewModel:五个盒子 + 花名册 + 重画 带读-02
4 26 讲/…/v26/creator/rect.js 108 一台 Controller 状态机 带读-02
5 26 讲/…/v26/accel/menu.js 86 菜单也是 Controller 带读-03 · 2.1
↑ 到这里 26 讲的代码你就全读完了(432 行)
6 27 讲/…/diff-dom-v26-v27.txt 27 讲给 Model 加了什么 带读-04
7 27 讲/…/v27/accel/select.js 91 新增的选择工具(第四台状态机) 同上
8 28 讲/…/diff-dom-v27-v28.txt 28 讲给 Model 加了什么 带读-05
9 29 讲/…/v29-服务端/service.go 247 服务端的路由表(只看开头那七行 带读-06
10 30 讲/…/diff-dom-v29-v30.txt 30 讲:同步器 + QSerializer 带读-07

四 · 结构地图(行号是真的,可以直接跳)

v26/dom.js —— 109 行,Model

  1  class QLineStyle          线宽 + 颜色。就两个字段
  8  class QLine               ┐
 26  class QRect               │ 四个图形类,结构一模一样:
 45  class QEllipse            │   constructor 记住几何数据
 64  class QPath               ┘   onpaint    拿别人递来的笔,自己画自己
 92  class QPaintDoc           根:一个数组 + addShape + onpaint
     93   constructor          this.shapes = []
     97   addShape             push 进数组
    103   onpaint              for 每个图形: 它自己画   ← 【整个 Model 的核心就这两行】

读法:先看 92~109(QPaintDoc,17 行),再回头看 8~25(QLine),
其余三个图形类扫一眼就行——换汤不换药

v26/index.htm —— 17 行,组装根

     <canvas id="drawing">     那块画布
     <div id="menu">           菜单要塞进来的地方
     <script src="dom.js">     ┐
     <script src="view.js">    │ 这份列表 = "这个程序由哪些零件构成"
     <script src="creator/…">  │ 删掉一行 = 删掉一个功能
     <script src="accel/menu.js"> ┘

v26/view.js —— 112 行,ViewModel

  1  class QPaintView
     2    constructor         ← 【最长的一段,2~51】做四件事:
                                 造空 Model、五个空盒子、空花名册、
                                 把 canvas 和 document 的事件接过来转交
    52    get currentKey      当前工具的名字
    55    get lineStyle       当前样式的【快照】(每次 new 一份)
    60    onpaint             ① doc.onpaint  ② 当前 Controller 的 onpaint(橡皮筋)
    67    getMousePos         屏幕坐标 → 画布坐标
    74    invalidateRect      取笔 → 全屏擦 → 全部重画
    81    registerController  往花名册里记一条(存的是【工厂】)
    88    invokeController    先 stop 旧的 → 查花名册 → 工厂() 造新的
    95    stopController      让当前 Controller 自己 stop()
   102    _setCurrent
   108  var qview = new QPaintView()      ← 全局的那个 View 在这儿诞生

v26/creator/rect.js —— 108 行,一台状态机

  2  function normalizeRect   往左上角拖时宽高会是负的,规整一下
 18  class QRectCreator
     19   constructor         填字段 + 【抢走四个事件盒子】
     32   stop                【把四个盒子还回去】(=null)
     39   reset               清空自己 + 要求重画
     43   buildShape          按 shapeType 造出 QLine/QRect/QEllipse/QPath
     64   onmousedown         记起点,started = true
     68   onmousemove         更新终点 → invalidateRect
     74   onmouseup           addShape ★ 图形诞生 → reset
     81   onkeydown           ESC → reset
     87   onpaint             buildShape().onpaint(ctx)   ← 【橡皮筋,造→画→扔】

这个文件是"Controller = 被翻过来的 workflow"最好的样本,109 行里状态机的五个动作齐全。

v27/dom.js —— 306 行(27 讲:加选择)

  4  hitLine / hitRect / normalizeRect / fill / deleteItem      几个工具函数(数学,可跳)
 61  class QShapeStyle        ← QLineStyle 改名而来,多了 fillColor + setProp + clone
 75  class QLine    ┐
112  class QRect    │ 每个图形【多了四个方法】:
149  class QEllipse │    bound()    我占哪块地方        ← 画选中框要用
195  class QPath    ┘    hitTest()  这一点在我身上吗    ← 点选要用
                        move()     把我挪一挪          ← 拖动要用
                        setProp()  改我的某个样式      ← 改颜色要用
275  class QPaintDoc
    280   addShape
    285   deleteShape        ← 新增
    288   hitTest            ← 新增,【倒着遍历】,命中就返回
    300   onpaint

读法:直接跳到 112~148 看 QRect(37 行),四个新方法一眼看完,其余三个图形同理。

v27/accel/select.js —— 91 行(新增的选择工具)

  1  class QShapeSelector
     2    constructor        抢四个盒子(跟 QRectCreator 一样)
    12    stop               还盒子 + 把鼠标光标恢复成默认
    20    reset
    25    onmousedown        doc.hitTest(pt) → 设 view.selection
    34    onmousemove        按住时:更新偏移;没按住时:悬停在图形上就把光标变"移动"手势
    48    onmouseup          坐标变了 → selection.move(dx,dy)   ★ 这一刻 Model 才真的变
    58    onkeydown          Delete → doc.deleteShape;ESC → reset
    70    onpaint            画那个【灰色虚线选中框】(用 selection.bound())

v28/dom.js —— 497 行(28 讲:加离线持久化)

新增的全是 70~161 这一段(存储层)和 QPaintDoc 里的几个 _ 方法,图形类只各加了三行。

  5  hitLine … deleteItem     和 v27 一样,没动
 70  _getNextID               "号发到几号了",从 10000 开始
 81  _makeLocalDrawingID      给新文档发一个 localID
 87  removeSomeCache          【localStorage 满了,淘汰最早的一篇文档】
105  localStorage_setItem     ★【全程序写盘唯一的口子】——满了就淘汰再重试
116  loadDrawing              读一篇文档的 JSON
121  documentChanged          ★ 文档级变更 → 重写 "dg:localID"
128  loadShape                ★【反序列化】:看 type 字段决定 new 哪个类(那个 switch)
151  shapeChanged             ★ 图形级变更 → 重写 "localID:shapeID"
163  class QShapeStyle        没动
177  class QLine …            四个图形类【各只加三行】:
                                 constructor 加 this.id = ""
                                 move / setProp 末尾加 shapeChanged(this)
391  class QPaintDoc
    396   _reset             清空 + localID/displayID 置空
    402   _load              从 localStorage 把整篇文档读回来
    422   _stringify         文档序列化——【只存 shapeID 列表,不存图形内容】
    434   _initShape         给新图形发一个 id
    445   init               ★【读 URL 里的 #t10001;没有就新发一个 ID 并改 URL】
    461   reload
    466   addShape           发 ID → shapeChanged → documentChanged
    473   deleteShape        只发 documentChanged

读法:先看 466~476(addShape/deleteShape,11 行),
再看 121~127 和 151~161(那两个 xxxChanged),28 讲的核心就这 20 行
其余(init_load_stringify)是围绕它们的配套。

五 · 怎么看那几个 diff 文件

-  这一行是【旧版】的                (v26/v27)
+  这一行是【新版】的                (v27/v28)
   前面没符号的是没变、用来定位的上下文
@@ -92,7 +275,12 @@   表示:旧文件第 92 行起 7 行,对应新文件第 275 行起 12 行

建议先看 diff-view-v26-v27.txt(很短,十几行) 练手,再看 diff-dom-*(长)。

六 · 能跑起来吗

v26 / v27 直接用浏览器打开 index.htm 就能跑(它们不依赖服务端)。

v28 打开也能跑,而且会自动跳到 #t10001,画的东西刷新后还在——
这正是 28 讲要的效果,可以直接验。

想看 localStorage 里存了什么:Cmd+Option+IApplication 面板 → 左边 Local Storage
你会看到 dg:1000110001:110001:2 这些 key,跟带读-05 讲的一模一样。

qpaint-mini.html 是我按 26 讲那一版改写的单文件版,加了右侧面板,用来做实验更方便;
想读"作者原本怎么写的",看这里的 v26/。)

公告栏
这是我的个人知识库。
记录技术,也记录生活 —— 读过的、试过的、想明白的,都堆在这儿。
最新文章
网站资讯
文章数目 :
5
已运行时间 :
本站总字数 :
15.7k
本站访客数 :
本站总访问量 :
最后更新时间 :
全局知识图谱
当前页面 已访问 文章 标签
ESC 关闭 · 滚轮缩放 · 拖拽移动 · Ctrl+G 开关