美文网首页moonjs
深入浅出MV*框架源码(一):从一个高仿库Moon看起

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

作者: 云峰yf | 来源:发表于2017-12-01 23:29 被阅读0次

前言

彻底摆脱秋招之后,我也来到了公司实习,我们主要使用的框架是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. 组件
    ...

相关文章

网友评论

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

    本文链接:https://www.haomeiwen.com/subject/ukiovxtx.html