博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从零到一实现类vue2.*的简单MVVM框架
阅读量:7244 次
发布时间:2019-06-29

本文共 2993 字,大约阅读时间需要 9 分钟。

从一张图开始:

vue的整个内部实现原理都可以抽象成这样一张图,在这张图中,对于抽象Virtual DOM Tree暂不进行实现,以直接操作DOM代替抽象映射的过程。(该图来自掘金小册,侵删。https://juejin.im/book/5a36661851882538e2259c0f/section/5a37bbb35188257d167a4d64)

1.实现功能:

  1. 数据劫持
  • 模板编译(以v-model和{
    {}}实现为例

2.文件目录:

  1. mvvm.html(所谓视图文件,大家都懂)
  2. mvvm.js(整合数据劫持与模板编译部分)
  3. compile.js(模板编译)
  4. observer.js(数据劫持)
  5. watcher.js(处理依赖)

3.具体实现:

  1.  Observer类:
  • 实现数据劫持,为data中每一项属性设置存取器方法。
  • observer(data)递归遍历data对象的每一项属性。
  • defineReactive(obj,key,value)对每一项属性进行存取器设置,基于Object.defineProperty实现。
  • Object.defineProperty(obj,key,{    get(){};    set(){};})复制代码
Compile类:
  • 模板编译,对html文件中的'v-'指令以及{
    {}}进行编译,并实现相应的数据更新updater。
  • 先将html文档推入到fragment文档碎片中进行管理,此行为将清空页面,这也就是平时用vue进行开发时会出现也页面闪烁的原因。v-cloak即是用display:none将页面的初次渲染去掉,避免闪烁。
  • 递归将html节点推入fragment
  • let fragment=document.createDocumentFragment();let firstChild;  //首个子节点    while(firstChild=el.firstChild){//注意文本节点的坑    空格换行 #text        fragment.appendChild(firstChild); //此行为将直接转移页面中的节点}复制代码
    • compile对fragment中的内容进行编译
    • 对节点进行递归遍历,根据nodeType是否为1分为元素节点(可使用指令)和文字节点({
      {...}}),分别采用不同的编译方法。
    • compileElement用node.attributes取出元素的属性列表,对属性进行遍历,将属性为‘v-’开头的指令拿出来,寻找对应的数据更新方法CompileUtil[type](node,this.vm,expr);
    • compileText用node.textContent拿出文本节点的内容,用正则/\{\{([^}]+)\}\}/g进行匹配,是否满足{
      {}}语法,若匹配则执行文本节点的数据更新方法CompileUtil['text'](node,this.vm,expr);
    • compileUtil对象管理和执行数据更新。
    • 对于文本节点:这里我们传入表达式时传入的是类似{
      {message.a}}的形式,所以需要用正则换掉两边的括号

      return expr.replace(/\{\{([^}]+)\}\}/g,(...arguments)=>{ arguments[1]即是我们需要的值});复制代码

    • 同时从data中取出相应的值,这里我们需要处理常见的一种情况,比如message.a.b,所以不能直接data[message]进行取值,这里用reduce进行遍历,取出真实的值。

      expr.reduce((prev,next)=>{   return prev[next];},vm.$data)复制代码
    • 而对于元素节点,则可以直接用reduce取出真实的值。
    • 之后就是执行更新方法了,因为我们并没有实现virtual DOM,这里就是很简单操作DOM改变值就行。例如更新文本时

      updater && updater(node,this.getVal(vm,expr));textUpdater(node,value){    node.textContent=value;},复制代码

3.Watcher类

    • 数据监听,观察到数据变化时通知编译,影响视图,对视图上出现的表达式进行依赖收集,在第一次初始化渲染视图时就为每一个出现的数据确定依赖。
    • 依赖收集:我们这里的watcher指的就是视图上的节点对象,将他加入依赖,我们这里的做法是,在编译的时候即在compile中new一个Watcher对象出来,然后在watcher中这样处理:

      Dep.target=this;let value=Watcher.getVal(this.vm,this.expr);//触发getDep.target=null;复制代码

    • 将属于此节点的watcher,他长这样:

      this.vm=vm;this.expr=expr;this.cb=cb;this.value=this.get();//伪造数据读取,将Watcher对象存入订阅者数组中复制代码

    • 如上面那段代码中,我们将Dep.target指向watcher实例,然后主动触发数据的get方法,将Dep.target指向的watcher对象进行处理,加入到相应的数据的订阅者数组中。

      get(){       Dep.target && dep.addSub(Dep.target);        return value;}复制代码

    • 完成依赖处理。

4.Dep类

    • 就是我们所谓的发布订阅者。
    • 他很简单,长这样。

      class Dep{    constructor(){//订阅的数组                this.subs=[];        }        addSub(watcher){        this.subs.push(watcher);    }    notify(){        this.subs.forEach(watcher=>watcher.update());    }}复制代码

    • addSub用来将watcher加入到相应数据的和观察者数组中,subs及用来存储watcher。noyify即遍历数组中每一个观察者,并执行相应的更新方法update。而update方法则指向实例化Watcher对象时传入的cb回调函数。

      update(){        this.cb();//触发视图更新}复制代码

    • cb即是相应元素对应的更新方法。

      new Watcher(vm,expr,()=>{    updater && updater(node,this.getVal(vm,expr)); })复制代码

这样我们的框架就完成了,写这篇博客初衷是为整理自己的思路,如果有错误或不合适还希望指出。

这里是仓库地址:https://github.com/sugarhaining/Book_List/tree/master/javascript/MVVM

转载于:https://juejin.im/post/5c540b7c51882562973c0ddf

你可能感兴趣的文章
中合国创杯2017年创客中国互联网+创新创业大赛复赛成功举办 20各项目入围总决赛...
查看>>
UVAoj 11324 - The Largest Clique(tarjan + dp)
查看>>
使用Matplotlib绘制正余弦函数、抛物线
查看>>
四位辉光管时钟-学长毕设
查看>>
大话RAC介质恢复---联机日志损坏
查看>>
oracle 内存分配和调优 总结
查看>>
移植最新版libmemcached到VC++的艰苦历程和经验总结(上)
查看>>
诡异的bug: tcsh陷入死循环
查看>>
java-第一章-上机练习-04
查看>>
Active Directory 基础 (1)
查看>>
xml地图生成网址
查看>>
Python 练习1
查看>>
TCExam文件代码注释分析(后台首页admin/code/index.php)
查看>>
Finereport在企业级BI分析中的应用
查看>>
linux内核参数注释与优化
查看>>
linux 2.6x内核升级
查看>>
pxe
查看>>
NFS网络文件系统安装
查看>>
网页嵌入自动生成当前网页二维码图片代码
查看>>
Linux时间同步服务
查看>>