深入浅出MV*框架源码(一):从一个高仿库Moon看起

前言

彻底摆脱秋招之后,我也来到了公司实习,我们主要使用的框架是Vue.js,如果自然而然地,我需要学习这个框架。平心而论,vue的api数量可以说是angular的1/10不到,虽然简洁,但内部实现并不简单。

随便下个断点冲进vue源码之处,都能感受到里面结构的复杂,对于我等菜鸟来说实在是无法多待一会儿的。

所以,本着面对复杂问题先将其简单化的思维方式,我和另一位朋友决定先从一个将Vue核心api实现的高仿库摸透,之后再来啃Vue源码。

为什么选择Moon

市面上有很多MVVM库,Vue也有早期版本在github上供人研究,为什么我们选择了Moon呢?

原因有三:

  1. Moon源码只有2000多行,却实现了实例属性(data、computed、methods、template)、render、指令、组件以及组件通信、生命钩子、slot等大部分Vue核心功能,在各大MVVM库中算是研究性价比最高的(目前为止我能找到的)。
  2. Moon的作者异常活跃,我们一旦有啥问题就可以和他邮件交流。
  3. Moon相比Vue的早期版本也有它独到的思想,作者本身实力也很厉害。比如他针对静态HTML元素渲染做的优化

研究源码一般而言有两种方法:自顶向下和自底向上。

自顶向下就是先从最抽象的层面上观察源码,从它的结构、设计着手,再一步步深入到各个代码模块->子程序->具体语句。

自底向上就是特意选择一个地方打一个断点,然后运行代码,通过一步步调试观察它所经过的调用栈和所有的中间变量。

我选择综合两种方法来研究源码:先自顶向下把它的代码结构摸清楚,再自底向上打断点一步步照亮黑暗区域。和我们平时打游戏需要一个大地图提供全局观,然后自己探索其中的黑暗处道理相似。

本系列文章于2017.12.1开始写,作者打包的最新版本是v0.11.0

Moon源码整体结构

为了方便起见,我绘制了一张图(建议大家下载下来看):


moon源码结构.png

这里我简单把各个文件夹(标了颜色的方块)看成是类,然后实现了看成接口的各个代码文件的功能。

每个代码文件里有若干个子程序,没有写成函数的我就假装它们是一个函数的内容,并且在这个我造的函数名前面打了星号,虚线所指向的方块是它们实际做的事情。

打开源码的package.json,可以看出作者是使用gulp打包构建他的代码的。而整块代码被他分割成了六个文件夹:

  1. compiler---编译模板到dom树的各个函数。
  2. util---通用工具、dom工具、vdom工具函数。
  3. observer---观察者实例和相关的函数。
  4. directives---处理指令的函数。
  5. instance---Moon实例上存在的函数。
  6. global---Moon对象上的静态属性和方法。

最后通过在index.js里逐个引入,交给wrapper.js(还有gulp)打包成最终版本。

//index.js
"use strict";

/* ======= Global Variables ======= */
let directives = {};
let specialDirectives = {};
let components = {};
let eventModifiersCode = {
  stop: 'event.stopPropagation();',
  prevent: 'event.preventDefault();',
  ctrl: 'if(event.ctrlKey === false) {return null;};',
  shift: 'if(event.shiftKey === false) {return null;};',
  alt: 'if(event.altKey === false) {return null;};',
  enter: 'if(event.keyCode !== 13) {return null;};'
};
let eventModifiers = {};

/* ======= Observer ======= */
//=require observer/methods.js
//=require observer/computed.js
//=require observer/observer.js

//=require util/util.js
//=require util/dom.js
//=require util/vdom.js

/* ======= Compiler ======= */
//=require compiler/template.js
//=require compiler/lexer.js
//=require compiler/parser.js
//=require compiler/generator.js
//=require compiler/compiler.js

function Moon(options) {
//省略,这里不是这篇文章的重点
}
//=require instance/methods.js

//=require global/api.js

//=require directives/default.js
//wrapper.js
(function(root, factory) {
  /* ======= Global Moon ======= */
  (typeof module === "object" && module.exports) ? module.exports = factory() : root.Moon = factory();
}(this, function() {
    //=require ../dist/moon.js
    return Moon;
}));

一个Moon实例的一生

有了全局观,我们就从探究一个new一个的Moon过程开始,看看它究竟经历了些什么?:

<div id="app">
    {{yf}}
</div>
<script src="./moon.js"></script>
<script>
  debugger
  const app = new Moon({
    el: "#app",
    data: {
        yf: "云峰"
    }
  })
</script>
  1. 很好,我们进来了:


    preview-1.jpg
  2. 嗯,不出所料,首先进的是Moon构造函数,毕竟new了一下。


    preview-2.jpg
  3. 我们没有定义methods,所以跳过了initMethods,不过没关系,以后会有机会进去的~


    preview-3.jpg
  4. 嘿!我们new了一个Observer对象,它肯定是观察当前这个实例变化的!


    preview-4.jpg
  5. initComputed同样被我们跳过去了,不过没关系,我们要进init了!!


    preview-5.jpg
  6. init里貌似做了一些事情,不过紧接着就进入了mount。


    preview-6.jpg
  7. mount貌似也做了一些事情,我感觉快要迷路了,不过这时候一个compile让我好奇心大增,它竟然把模板字符串转成了一个匿名函数!做完这些它就进了build。


    preview-7.jpg
  8. build一上来就把上一步生成的函数执行了!并且按作者的注释来看的话还得到了virtual node,我们发现光得到还不行,后续还要和node进行patch一下。


    preview-8.jpg
  9. 在patch里我们因为virtual node不是dom node类型跳转到了一个hydrate的子程序里


    preview-9.jpg
  1. hydrate的子程序在作者注释中被解释为Hydrates Node and a VNode,也就是把人为生成的vnode混入到实际存在于dom树中的node里去。

在混入完成后我们便一步步出栈,回到了起点。

也就是说,我们的Moon实例经历了从init初始化->mount挂载->build建立的过程,期间实例化了一个Observer观察者,对模板字符串进行了编译,并执行得到vnode,最后作用于dom树中的node来改变最终渲染结果。(template->vnode->node)

那这些过程中具体细节是什么样的呢?我准备在之后的文章中从下面几个角度分析:

  1. render函数从一段code->html的过程
  2. compiler从html->code的过程
  3. 数据变更检测
  4. 指令
  5. 组件
    ...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容