扣丁学堂HTML5培训分享vue数据双向绑定
2019-04-08 14:55:22
1949浏览
本篇文章扣丁学堂HTML5培训小编给读者们分享一下vue 数据双向绑定,对vue数据双向绑定或者是对HTML5开发技术感兴趣的小伙伴就随小编来了解下吧。

已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,达到监听数据变动的目的。
为了便于理解,首先,来实现一个消息的储存中转的构造函数:
var uid = 0; // 通过全局的 uid 给 Dep 实例增加唯一 id,以区分不同实例
function Dep() {
this.id = uid++; // 给 Dep 实例添加 id,并将全局的 uid 加1
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) { // 增加 sub
this.subs.push(sub);
},
depend: function() {
Dep.target.addDep(this); // 将全局对象 Dep 的 target 属性指向的对象(这个函数的调用者 this)添加的 subs 里
},
removeSub: function(sub) { // 删处 sub
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
},
notify: function() { // 通知所有 subs 数据已更新
this.subs.forEach(function(sub) {
sub.update();
});
}
};
通过修改对象的属性,每一个绑定的属性都会有一个 Dep 实例。每一个 Dep 实例都会有一个 subs 属性,用来存储需要通知的对象,当对象属性改变时,通过 set 方法,调用这个属性的 Dep 实例的原型的 notify 方法,根据 subs 数组保存的内容,通知绑定了这个属性值的数据修改内容。
function Observer(data) {
this.data = data;
this.walk(data); // 调用原型的方法,处理对象
}
Observer.prototype = {
walk: function(data) {
var me = this;
Object.keys(data).forEach(function(key) { // 遍历 data 的属性,修改属性的 get / set
me.convert(key, data[key]);
});
},
convert: function(key, val) {
this.defineReactive(this.data, key, val);
},
defineReactive: function(data, key, val) { // 对属性进行修改
var dep = new Dep();
var childObj = observe(val);
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
if (Dep.target) {
dep.depend(); // 将全局的 Dep.target 添加到 dep 实例的 subs 数组里
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
// 新的值是object的话,进行监听
childObj = observe(newVal);
// 通知订阅者
dep.notify();
}
});
}
};
function observe(value, vm) {
if (!value || typeof value !== 'object') {
return;
}
return new Observer(value);
};
然后对 html 模板进行编译,根据每个节点及其的属性,判断是否包含 ‘{{}}’,’v-‘,’on’ 等特殊字符串,判断是否进行了绑定,将绑定了的属性个 get set 进行处理,
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 = {
node2Fragment: function(el) {
var fragment = document.createDocumentFragment(),
child;
// 将原生节点拷贝到fragment
while (child = el.firstChild) { // 如果 el 有资源素,就将其赋值给 child,返回 true
fragment.appendChild(child); // 将 child 从 el 转移到 fragment 下,el 会少一个资源素,进行下一轮循环
}
return fragment; // 返回 fragment
},
init: function() {
this.compileElement(this.$fragment); // 对 fragment 进行改造
},
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)) { // 处理文本节点
me.compileText(node, RegExp.$1);
}
if (node.childNodes && node.childNodes.length) {
me.compileElement(node); // 递归调用,处理子元素
}
});
},
compile: function(node) {
var nodeAttrs = node.attributes, // 获得 dom 节点在 html 代码里设置的属性
me = this;
[].slice.call(nodeAttrs).forEach(function(attr) { // 对属性进行遍历,设置
var attrName = attr.name;
if (me.isDirective(attrName)) { // 判断是普通属性还是绑定指令,如果是指令,对指令进行处理
var exp = attr.value;
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: function(node, exp) {
compileUtil.text(node, this.$vm, exp);
},
isDirective: function(attr) {
return attr.indexOf('v-') == 0;
},
isEventDirective: function(dir) {
return dir.indexOf('on') === 0;
},
标签:
HTML5培训
HTML5视频教程
HTML5学习视频
HTML5在线视频
HTML5培训班
前端H5架构师进阶
HTML5公开课