简单实现迷你Vue框架

2019-01-12 admin

本教程说明将采用es6语法来编写

创建MiniVue.js文件

//创建一个MVVM类
class MVVM {
  // 构造器
  constructor(option) {
    // 缓存重要属性
    this.$vm = this;
    this.$el = option.el;
    this.$data = option.data;
  }
}

MVVM类的作用: 解析视图模板,把对应的数据,渲染到视图

首先得判断视图是否存在,在视图存在的时候,创建模板编译器,来解析视图

class MVVM {
  // 构造器
  constructor(option) {
    // 缓存重要属性
    this.$vm = this;
    this.$el = option.el;
    this.$data = option.data;
    // 判断视图是否存在
    if (this.$el) {
      // 创建模板编译器, 来解析视图
      this.$compiler = new TemplateCompiler(this.$el, this.$vm)
    }
  }
}

下面我们来创建文件TemplateCompiler.js, 输入以下内容

// 创建一个模板编译工具
class TemplateCompiler {
  // el 视图
  // vm 全局vm对象
  constructor(el, vm) {
    // 缓存重要属性
    this.el = document.querySelector(el);
    this.vm = vm;
  }
} 

当缓存好重要的属性后,就要解析模板了

步骤分三步

  1. 把模板内容放进内存(内存片段)

  2. 解析模板

  3. 把内存的结果,放回到模板

    class TemplateCompiler {

    // el 视图
    // vm 全局vm对象
    constructor(el, vm) {
      // 缓存重要属性
      this.el = document.querySelector(el);
      this.vm = vm;
    
      // 1\. 把模板内容放进内存(内存片段)
      let fragment = this.node2fragment(this.el);
      // 2\. 解析模板
      this.compile(fragment);
      // 3\. 把内存的结果,放回到模板
      this.el.appendChild(fragment);
    }
    

    }

上面定义node2fragment()方法和compile()方法下面我们来实现


    class TemplateCompiler {
      // el 视图
      // vm 全局vm对象
      constructor(el, vm) {
        // 缓存重要属性
        this.el = document.querySelector(el);
        this.vm = vm;

        // 1\. 把模板内容放进内存(内存片段)
        let fragment = this.node2fragment(this.el)
        // 2\. 解析模板
        this.compile(fragment);
        // 3\. 把内存的结果,放回到模板
        this.el.appendChild(fragment);
       }
      }

  // 工具方法
  isElementNode(node) {
    // 1\. 元素节点  2\. 属性节点  3\. 文本节点
    return node.nodeType === 1;
  }
  isTextNode(node) {
    return node.nodeType === 3;
  }

  // 核心方法
  node2fragment(node) {
    // 1\. 创建内存片段
    let fragment = document.createDocumentFragment();
    // 2\. 把模板内容放进内存
    let child;
    while (child = node.firstChild) {
      fragment.appendChild(child);
    }
    // 3\. 返回
    return fragment;
  }
  compile(node) {
  }
}

关于createDocumentFragment的描述:

DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。

因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。

分析解析模板compile()方法实现方式

首先获取每一个子节点,然后遍历每一个节点,再判断节点类型 下面childNode是类数组没有数组方法

使用扩展运算符( spread )[…childNode]使其转化为数组便于操作

compile(parent) {
    // 1\. 获取子节点
    let childNode = parent.childNodes;
    // 2\. 遍历每一个节点
    [...childNode].forEach(node => {
      // 3\. 判断节点类型
      if (this.isElementNode(node)) {
        // 解析元素节点的指令
        this.compileElement(node);
      }
    })
  }

下面来实现compileElement()方法 当前调用传过来的是元素节点

接下来就要获取元素的所有属性,然后遍历所有属性,再判断属性是否是指令

v-text是vue的指令,像ng-xxx指令是不进行收集的

判断为指令时就要收集结果

compileElement(node) {
    // 1\. 获取当前节点的所有属性
    let attrs = node.attributes;
    // 2\. 遍历当前元素的所有属性
    [...attrs].forEach(attr => {
      let attrName = attr.name
      // 3\. 判断属性是否是指令
      if (this.isDirective(attrName)) {
        // 4\. 收集
        let type = attrName.substr(2); // v-text
        // 指令的值就是表达式
        let expr = attr.value;
        // 解析指令
        CompilerUtils.text(node, this.vm, expr);
      }
    })
  }

实现CompilerUtils类

CompilerUtils = {
  // 解析text指令
  text(node, vm, expr) {
    // 1\. 找到更新方法
    let updaterFn = this.updater['textUpdater'];
    // 执行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新规则对象
  updater: {
    // 文本更新方法
    textUpdater(node, value) {
      node.textContent = value;
    }
  }
}

在TemplateCompiler类工具方法下添加方法isDirective()判断属性是否是指令

isDirective(attrName) {
    // 判断属性是否是指令
    return attrName.indexOf('v-') >= 0;
  }

实现(v-text)完整代码

// 创建一个模板编译工具
class TemplateCompiler {
  // el 视图
  // vm 全局vm对象
  constructor(el, vm) {
    // 缓存重要属性
    this.el = document.querySelector(el);
    this.vm = vm;
    // 1\. 把模板内容放进内存(内存片段)
    let fragment = this.node2fragment(this.el)
    // 2\. 解析模板
    this.compile(fragment);
    // 3\. 把内存的结果,放回到模板
    this.el.appendChild(fragment);
  }
  // 工具方法
  isElementNode(node) {
    // 1\. 元素节点  2\. 属性节点  3\. 文本节点
    return node.nodeType === 1;
  }
  isTextNode(node) {
    return node.nodeType === 3;
  }
  isDirective(attrName) {
    // 判断属性是否是指令
    return attrName.indexOf('v-') >= 0;
  }
  // 核心方法
  node2fragment(node) {
    // 1\. 创建内存片段
    let fragment = document.createDocumentFragment();
    // 2\. 把模板内容放进内存
    let child;
    while (child = node.firstChild) {
      fragment.appendChild(child);
    }
    // 3\. 返回
    return fragment;
  }
  compile(parent) {
    // 1\. 获取子节点
    let childNode = parent.childNodes;
    // 2\. 遍历每一个节点
    [...childNode].forEach(node => {
      // 3\. 判断节点类型
      if (this.isElementNode(node)) {
        // 元素节点 (解析指令)
        this.compileElement(node);
      }
    })
  }
  // 解析元素节点的指令
  compileElement(node) {
    // 1\. 获取当前节点的所有属性
    let attrs = node.attributes;
    // 2\. 遍历当前元素的所有属性
    [...attrs].forEach(attr => {
      let attrName = attr.name
      // 3\. 判断属性是否是指令
      if (this.isDirective(attrName)) {
        // 4\. 收集
        let type = attrName.substr(2); // v-text
        // 指令的值就是表达式
        let expr = attr.value;
        CompilerUtils.text(node, this.vm, expr);
      }
    })
  }
  // 解析表达式
  compileText() {
  }
}
CompilerUtils = {
  // 解析text指令
  text(node, vm, expr) {
    // 1\. 找到更新方法
    let updaterFn = this.updater['textUpdater'];
    // 执行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新规则对象
  updater: {
    // 文本更新方法
    textUpdater(node, value) {
      node.textContent = value;
    }
  }
}

下面来实现(v-model)

修改如下代码

compileElement(node) {
  // 1\. 获取当前节点的所有属性
  let attrs = node.attributes;
  // 2\. 遍历当前元素的所有属性
  [...attrs].forEach(attr => {
    let attrName = attr.name
    // 3\. 判断属性是否是指令
    if (this.isDirective(attrName)) {
      // 4\. 收集
      let type = attrName.substr(2); // v-text
      // 指令的值就是表达式
      let expr = attr.value;
      //-----------------------修改代码start---------------------
      // CompilerUtils.text(node, this.vm, expr);
      CompilerUtils[type](node, this.vm, expr);
      //-----------------------修改代码end---------------------
    }
  })
}

在CompilerUtils类添加实现

CompilerUtils = {
  ...
  //----------------------新增方法---------------------
  // 解析model指令
  model(node, vm, expr) {
    // 1\. 找到更新方法
    let updaterFn = this.updater['modelUpdater'];
    // 执行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新规则对象
  updater: {
    ...
    //----------------------新增方法---------------------
    // 输入框更新方法
    modelUpdater(node, value) {
      node.value = value
    }
  }
}

实现(v-html)就由读者自行添加对应的方法,形式和(v-model)差不多

下面来实现解析表达式 ,在compile添加以下代码

重要的怎么用验证表达式

compile(parent) {
    // 1\. 获取子节点
    let childNode = parent.childNodes;
    // 2\. 遍历每一个节点
    [...childNode].forEach(node => {
      // 3\. 判断节点类型
      if (this.isElementNode(node)) {
        // 元素节点 (解析指令)
        this.compileElement(node);
      //-----------------新增代码--------------------
        // 文本节点
      } else if (this.isTextNode(node)) {
        // 表达式解析
        // 定义表达式正则验证规则
        let textReg = /\{\{(.+)\}\}/;
        let expr = node.textContent;
        // 按照规则验证内容
        if (textReg.test(expr)) {
          // 获取分组内容
          expr = RegExp.$1;
          // 调用方法编译
          this.compileText(node, expr);
        }
      }
    })
  }

下面来实现文本解析器,通过分析(v-text)和表达式解析差不多

// 解析表达式
  compileText(node, expr) {
    CompilerUtils.text(node, this.vm, expr);
  }

完整实现代码

// 创建一个模板编译工具
class TemplateCompiler {
  // el 视图
  // vm 全局vm对象
  constructor(el, vm) {
    // 缓存重要属性
    this.el = document.querySelector(el);
    this.vm = vm;
    // 1\. 把模板内容放进内存(内存片段)
    let fragment = this.node2fragment(this.el)
    // 2\. 解析模板
    this.compile(fragment);
    // 3\. 把内存的结果,放回到模板
    this.el.appendChild(fragment);
  }
  // 工具方法
  isElementNode(node) {
    // 1\. 元素节点  2\. 属性节点  3\. 文本节点
    return node.nodeType === 1;
  }
  isTextNode(node) {
    return node.nodeType === 3;
  }
  isDirective(attrName) {
    // 判断属性是否是指令
    return attrName.indexOf('v-') >= 0;
  }
  // 核心方法
  node2fragment(node) {
    // 1\. 创建内存片段
    let fragment = document.createDocumentFragment();
    // 2\. 把模板内容放进内存
    let child;
    while (child = node.firstChild) {
      fragment.appendChild(child);
    }
    // 3\. 返回
    return fragment;
  }
  compile(parent) {
    // 1\. 获取子节点
    let childNode = parent.childNodes;
    // 2\. 遍历每一个节点
    [...childNode].forEach(node => {
      // 3\. 判断节点类型
      if (this.isElementNode(node)) {
        // 元素节点 (解析指令)
        this.compileElement(node);
      } else if (this.isTextNode(node)) {
        // 表达式解析
        // 定义表达式正则验证规则
        let textReg = /\{\{(.+)\}\}/;
        let expr = node.textContent;
        // 按照规则验证内容
        if (textReg.test(expr)) {
          expr = RegExp.$1;
          // 调用方法编译
          this.compileText(node, expr);
        }
      }
    })
  }
  // 解析元素节点的指令
  compileElement(node) {
    // 1\. 获取当前节点的所有属性
    let attrs = node.attributes;
    // 2\. 遍历当前元素的所有属性
    [...attrs].forEach(attr => {
      let attrName = attr.name;
      // 3\. 判断属性是否是指令
      if (this.isDirective(attrName)) {
        // 4\. 收集
        let type = attrName.substr(2); // v-text
        // 指令的值就是表达式
        let expr = attr.value;
        // CompilerUtils.text(node, this.vm, expr);
        CompilerUtils[type](node, this.vm, expr);
      }
    })
  }
  // 解析表达式
  compileText(node, expr) {
    CompilerUtils.text(node, this.vm, expr);
  }
}
CompilerUtils = {
  // 解析text指令
  text(node, vm, expr) {
    // 1\. 找到更新方法
    let updaterFn = this.updater['textUpdater'];
    // 执行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 解析model指令
  model(node, vm, expr) {
    // 1\. 找到更新方法
    let updaterFn = this.updater['modelUpdater'];
    // 执行方法
    updaterFn && updaterFn(node, vm.$data[expr]);
  },
  // 更新规则对象
  updater: {
    // 文本更新方法
    textUpdater(node, value) {
      node.textContent = value;
    },
    // 输入框更新方法
    modelUpdater(node, value) {
      node.value = value;
    }
  }
}

数据双向绑定(完结篇)

后续内容更精彩 图片描述

[转载]原文链接:https://segmentfault.com/a/1190000017865248

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-50800.html

文章标题:简单实现迷你Vue框架

相关文章
v-charts | 饿了么团队开源的基于 Vue 和 ECharts 的图表工具
在使用echarts生成图表时,经常需要做繁琐的数据类型转化、修改复杂的配置项,v-charts的出现正是为了解决这个 痛点。基于Vue2.0和echarts封装的v-charts图表组件,只需要统一提供一种对前后端都友好的数据格式 设置简...
2018-05-24
React Native v0.13.3 发布,Facebook开源框架
React is a JavaScript library for building user interfaces. Just the UI: Lots of people use React as the V in MVC. Since...
2015-11-12
2014年最流行前端开发框架对比评测
如今,各种开发框架层出不穷,各有千秋。哪些是去年较受开发者关注的呢?前不久,云适配根据Github上的流行程度整理了2014年最受欢迎的6个前端开发框架,并进行对比说明,希望帮助有需要的朋友选择合适自己的前端框架。 1. Bootstrap...
2015-11-12
Riot.js:不足1KB的MVP客户端框架
Riot.js是一款MVP(模型-视图-呈现)开源客户端框架,其最大的特点就是体积非常小,不足1KB,虽然体积小,但它可以帮助用户构建大规模的Web应用程序。 Riot.js是由Moot公司开发,目前最新版本为v0.9.2,遵循MIT开源许...
2016-03-11
javaScript+turn.js实现图书翻页效果实例代码
为了实现图书翻页的效果我们在网上可以看到很多教程 在这里推荐turn.js 网上的turn.js 有api 不过是英文的  很多人看起来不方便 .关于代码也是奇形怪状在这里我将详细讲解如何使用turn.js实现翻页效果 ,本篇文章只是讲解 ...
2017-03-16
可以从CSS框架中借鉴到什么
现在很多人会使用 CSS 框架进行快速建站。   那 CSS 框架是什么呢,它通常是一些 CSS 文件的集合,这些文件包括基本布局、表单样式、网格、简单组件、以及样式重置。使用 CSS 框架大大降低工作成本进行快速建站。   当然对于一些大...
2016-03-11
js实现手机拍照上传功能
在前段时间的项目开发中,用到了拍照上传的地方,后来发现了最为简单的一种方法,现总结如下: <form id="form" method="post" action="http:&#x2...
2017-03-06
纯JS实现旋转图片3D展示效果
CSS: <style type="text/css"> #show{position:relative;margin:20px auto;width:800px;} .item{position:...
2017-03-22
vue.js实现请求数据的方法示例
vue2.0示例代码如下: var vm = new Vue({ el:"#list", data:{ gridData: "", }, ...
2017-03-20
js实现鼠标左右移动,图片也跟着移动效果
效果:鼠标往左移,图片对应右移,鼠标往右移,图片就左移动。图片距离越远偏移距离越大。 思路:首先获取图片原先的距离。设置一个变化值,图片的最终距离等于原先的距离加上变化值 布局:大盒子里面是图片,大盒子position:relative;图...
2017-02-17
回到顶部