Vue源码分析-模板解析

模板解析

模板解析流程:

  1. 将el的所有节点取出,放在文档碎片fragment对象中
  2. 将fragment对象的所有子节点进行递归解析处理
  3.  

插值语法解析

    1. 对插值语法(表达式)进行解析(与v-text指令解析一致),使用compileText方法
      1. 根据正则对象获取匹配到的表达式字符串
      2. 从vm实例上找到表达式对应的值
      3. 将属性值设置为文本节点的textContent

指令解析

    1. 一般指令解析
      1. 从标签节点中获取到指令名和表达式
      2. 从vm实例上拿到表达式的对应的值
      3. 根据指令操作标签节点的属性

v-text----textContent属性

v-html----innerHtml属性

v-class----className属性

v-model----value属性

      1. 将表达式的值设置到对应的属性上
      2. 指令解析后,移除元素此属性
    1. 事件指令解析
      1. 从标签节点找到获取到对应的事件名和表达式
      2. 根据表达式从vm实例methods中获取到函数
      3. 给当前元素节点绑定指定的事件名和回调函数
      4. 指令解析后,移除元素此属性
  1. 将解析后的文档对象fragment添加到el中显示

相关代码

mvvm.js

//编译模板

this.$compile = new Compile(options.el || document.body, this);

 

compile.js

扫描二维码关注公众号,回复: 11621346 查看本文章

function Compile(el, vm) {

  // 保存vm

  this.$vm = vm;  //为了能在其他函数内部获取到vm实例身上的数据

  // 获取dom元素保存起来

  this.$el = this.isElementNode(el) ? el : document.querySelector(el);

 

  if (this.$el) {

    // 1. 将元素所有子节点添加到新的创建文档碎片节点中

    this.$fragment = this.node2Fragment(this.$el);

    // 2. 递归编译文档碎片节点所有子节点

    this.init();

    // 3. 将编译好文档碎片节点添加到页面中生效

    this.$el.appendChild(this.$fragment);

  }

}

node2Fragment—得到文档随便对象

  node2Fragment: function (el) {

    // 创建文档碎片节点

    var fragment = document.createDocumentFragment(),

      child;

    // 将原生节点拷贝到fragment

    while ((child = el.firstChild)) {

      fragment.appendChild(child);

    }

    return fragment;

  },

Init—开始编译

  init: function () {

    // 递归编译所有子节点方法

    this.compileElement(this.$fragment);

  },

compileElement—接收文档碎片进行编译

  compileElement: function (el) {

    // 取出当前元素所有子节点

    var childNodes = el.childNodes,

      me = this;

    // 转换成数组进行遍历

    [].slice.call(childNodes).forEach(function (node) {

      // 获取节点的文本内容

      var text = node.textContent;

      // 定义一个用于匹配插值语法的正则

      var reg = /\{\{(.*)\}\}/;

 

      // 判断当前元素是否是元素节点

      if (me.isElementNode(node)) {

        // 编译指令语法

        me.compile(node);

        // 判断当前元素是否文本节点

        // 并且里面是否有插值语法

      } else if (me.isTextNode(node) && reg.test(text)) {

        // 编译插值语法

        // RegExp.$1.trim() 取出插值语法中的表达式

        me.compileText(node, RegExp.$1.trim());

      }

 

      // 判断当前节点是否还有子节点

      if (node.childNodes && node.childNodes.length) {

        // 递归编译子节点

        me.compileElement(node);

      }

    });

  },

 

compile—解析标签节点

  compile: function (node) {

    // 获取当前元素所有属性

    var nodeAttrs = node.attributes,

      me = this;

 

    [].slice.call(nodeAttrs).forEach(function (attr) {

      // 获取当个属性名 v-on:click

      var attrName = attr.name;

      // 判断属性是否是指令属性

      if (me.isDirective(attrName)) {

        // 获取指令属性对应表达式

        var exp = attr.value;

        // 截取指令属性 on:click

        var dir = attrName.substring(2);

        // 事件指令

        if (me.isEventDirective(dir)) {

          // 给元素绑定事件

          compileUtil.eventHandler(node, me.$vm, exp, dir);

          // 普通指令

        } else {

          compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);

        }

 

        // 将编译好的属性给删除掉

        // 只会删除指令属性,其他属性进不来

        node.removeAttribute(attrName);

      }

    });

  },

 

compileText—解析文件节点插值语法

  compileText: function (node, exp) {

//node节点   exp插值语法:例如:name

    compileUtil.text(node, this.$vm, exp);

  },

 

// CompileUtil指令处理集合  this指向compileUtile对象

compileUtil = {

  text: function (node, vm, exp) {

    this.bind(node, vm, exp, "text");

  },

 

  html: function (node, vm, exp) {

    this.bind(node, vm, exp, "html");

  },

 

  model: function (node, vm, exp) {

    // 给数据进行初始化显示

    // node.value = xxx

    this.bind(node, vm, exp, "model");

 

    var me = this,

      // 读取当前属性的值 

      val = this._getVMVal(vm, exp);

    // 绑定input事件,监听元素的value的变化

    node.addEventListener("input", function (e) {

      // 获取当前元素最新的值

      var newValue = e.target.value;

      // 如果相等就不更新

      if (val === newValue) {

        return;

      }

      // 更新data数据 --> 触发setter方法,从而更新用户界面

      me._setVMVal(vm, exp, newValue);

      // 将当前值存起来,方便进行下一次比较~

      val = newValue;

    });

  },

 

  class: function (node, vm, exp) {

    this.bind(node, vm, exp, "class");

  },

 

  /**

   *

   * @param {*} node 节点

   * @param {*} vm 实例对象

   * @param {*} exp expression表达式 'wife.name'

   * @param {*} dir directive指令 'text'

   */

  bind: function (node, vm, exp, dir) {

    // 取出要处理的函数 textUpdater

    var updaterFn = updater[dir + "Updater"];

    // 判断函数是否存在并调用

    // this._getVMVal(vm, exp) --> 用来通过vm找到表达式(属性名)对应的值

    updaterFn && updaterFn(node, this._getVMVal(vm, exp));//给节点设置渲染的值

    

    /*

      凡是 普通指令语法和插值语法 的元素会有watcher

      watcher都会有一个更新用户界面回调函数

    */

    new Watcher(vm, exp, function (value, oldValue) {

      updaterFn && updaterFn(node, value, oldValue);

    });

  },

 

  /**

   * 事件处理

   * @param {*} node 元素节点

   * @param {*} vm 实例对象

   * @param {*} exp 指令表达式 show

   * @param {*} dir 指令 on:click

   */

  eventHandler: function (node, vm, exp, dir) {

    // 获取事件类型 ['on', 'click']

    var eventType = dir.split(":")[1],

      // 获取事件回调函数

      fn = vm.$options.methods && vm.$options.methods[exp];

 

    if (eventType && fn) {

      // 绑定事件监听

      // 改变事件回调函数的thisvm fn.bind(vm)

      node.addEventListener(eventType, fn.bind(vm), false);

    }

  },

 

  /**

   * 获取vm上对应表达式的值

   * @param {*} vm

   * @param {*} exp 表达式 'wife.name'

   */

  _getVMVal: function (vm, exp) {

    var val = vm;

exp = exp.split("."); // ['wife', 'name']

//表达式可能是xxx.xxx.xxx

    // 第一次遍历 val = vm['wife'] = {xxx}

    // 第二次遍历 val = {xxx}['name'] = 'rose'

    exp.forEach(function (k) {

      val = val[k];

    });

    return val; // 'rose'

  },

 

  // 设置vm上对应表达式的值

  _setVMVal: function (vm, exp, value) {

    var val = vm;

    exp = exp.split(".");

    exp.forEach(function (k, i) {

      // 非最后一个key,更新val的值

      if (i < exp.length - 1) {

        val = val[k];

      } else {

        val[k] = value;

      }

    });

  },

};

 

var updater = {

  textUpdater: function (node, value) {

    // node节点设置文本内容

    node.textContent = typeof value == "undefined" ? "" : value;

  },

 

  htmlUpdater: function (node, value) {

    // node节点设置html内容

    node.innerHTML = typeof value == "undefined" ? "" : value;

  },

  /**

   * 处理class方法

   * @param {*} node

   * @param {*} value 表达式的值。font

   */

  classUpdater: function (node, value) {

    // 获取元素上的class属性的值 red

    var className = node.className;

    // 给元素设置新的className

    // 新的className=原来的class + ' ' + v-class设置的class

    node.className = className + " " + value;

  },

 

  modelUpdater: function (node, value, oldValue) {

    node.value = typeof value == "undefined" ? "" : value;

  },

};

 

 

猜你喜欢

转载自blog.csdn.net/shkstart/article/details/108466869