0
点赞
收藏
分享

微信扫一扫

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

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) {
// 绑定事件监听
// 改变事件回调函数的this为vm 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;
},
};

 

举报

相关推荐

0 条评论