1)vue 数据双向绑定原理-observer 2)vue 数据双向绑定原理-wather 3)vue 数据双向绑定原理-解析器 Complie
vue
数据双向绑定原理, 和简单的实现, 本文将实现 mvvm
的模板指令解析器
上一步实现了简单数据绑定, 最后实现解析器, 来解析 v-model
, v-on:click
等指令, 和 {{}}
模板数据. 解析器 Compile
实现步骤:
-
- 解析模板指令,并替换模板数据,初始化视图
-
- 将模板指令对应的节点绑定对应的更新函数,初始化相应的订阅器
为了解析模板,首先需要获取到 dom
元素,然后对含有 dom
元素上含有指令的节点进行处理,因此这个环节需要对 dom
操作比较频繁,所有可以先建一个 fragment
片段,将需要解析的 dom
节点存入 fragment
片段里再进行处理:
function node2Fragment(el) {
var fragment = document.createDocumentFragment(),
child;
// 将原生节点拷贝到fragment
while ((child = el.firstChild)) {
fragment.appendChild(child);
}
return fragment;
}
接下来 渲染 '{{}}'
模板
//Compile
function Compile(el, vm) {
this.$vm = vm;
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
this.$fragment = this.node2Fragment(this.$el);
this.init();
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
init: function () {
this.compileElement(this.$fragment);
},
node2Fragment: function (el) {
//...
},
//编译模板
compileElement: function (el) {
var childNodes = el.childNodes,
self = this;
[].slice.call(childNodes).forEach(function (node) {
var text = node.textContent;
var reg = /{{(.*)}}/; //表达式文本
//按元素节点方式编译
if (self.isElementNode(node)) {
self.compile(node);
} else if (self.isTextNode(node) && reg.test(text)) {
self.compileText(node, RegExp.$1);
}
//遍历编译子节点
if (node.childNodes && node.childNodes.length) {
self.compileElement(node);
}
});
},
isElementNode: function (node) {
return node.nodeType == 1;
},
isTextNode: function (node) {
return node.nodeType == 3;
},
compileText: function (node, exp) {
var self = this;
var initText = this.$vm[exp];
this.updateText(node, initText);
new Watcher(this.$vm, exp, function (value) {
self.updateText(node, value);
});
},
updateText: function (node, value) {
node.textContent = typeof value == "undefined" ? "" : value;
},
};
处理解析指令对相关指令进行函数绑定,
代码语言:javascript复制Compile.prototype = {
......
isDirective: function(attr) {
return attr.indexOf('v-') == 0;
},
isEventDirective: function(dir) {
return dir.indexOf('on:') === 0;
},
//处理v-指令
compile: function(node) {
var nodeAttrs = node.attributes,
self = this;
[].slice.call(nodeAttrs).forEach(function(attr) {
// 规定:指令以 v-xxx 命名
// 如 <span v-text="content"></span> 中指令为 v-text
var attrName = attr.name; // v-text
if (self.isDirective(attrName)) {
var exp = attr.value; // content
var dir = attrName.substring(2); // text
if (self.isEventDirective(dir)) {
// 事件指令, 如 v-on:click
self.compileEvent(node, self.$vm, exp, dir);
} else {
// 普通指令如:v-model, v-html, 当前只处理v-model
self.compileModel(node, self.$vm, exp, dir);
}
//处理完毕要干掉 v-on:, v-model 等元素属性
node.removeAttribute(attrName)
}
});
},
compileEvent: function(node, vm, exp, dir) {
var eventType = dir.split(':')[1];
var cb = vm.$options.methods && vm.$options.methods[exp];
if (eventType && cb) {
node.addEventListener(eventType, cb.bind(vm), false);
}
},
compileModel: function(node, vm, exp, dir) {
var self = this;
var val = this.$vm[exp];
this.updaterModel(node, val);
new Watcher(this.$vm, exp, function(value) {
self.updaterModel(node, value);
});
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
self.$vm[exp] = newValue;
val = newValue;
});
},
updaterModel: function(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
},
}
最后再关联起来
代码语言:javascript复制function Vue(options) {
.....
observe(this.data, this);
this.$compile = new Compile(options.el || document.body, this)
return this;
}
来尝试下效果
代码语言:javascript复制<!--html-->
<div id="app">
<h2>{{name}}</h2>
<input v-model="name" />
<h1>{{name}}</h1>
<button v-on:click="test">click here!</button>
</div>
<script>
new Vue({
el: "#app",
data: {
name: "chuchur",
age: 29,
},
methods: {
test() {
this.name = "My name is chuchur";
},
},
});
</script>
OK. 基本完善了