手动实现一个MVVM框架(下)
2019-01-25 本文已影响2人
不净莲华
实现双向绑定
HTML部分
<div id="app">
<form>
<input type="text" s-model="number">
<button type="button" s-click="increment">增 加</button>
</form>
<h3 s-bind="number"></h3>
</div>
使用方式
var app = new Svm({
el:'#app',
data: {
number: 0
},
methods: {
increment: function(){
this.number ++;
}
}
});
详解
构造函数
//构造函数
function Svm (options) {
this._init(options);
}
_init方法的定义
Svm.prototype._init = function(options){
this.$options = options;
this.$el = document.querySelector(options.el);
this.$data = options.data;
// 绑定的指令对象
this._binding = {};
// 监听数据变化 改变视图
this._observe(this.$data);
// 解析指令
this._compile(this.$el);
};
实现_observer函数
Svm.prototype._observer = function(obj){
//遍历数据对象
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
//指令合集
this._binding[key] = {
_directives: []
};
let value = obj[key];
}
//递归调用
if(typeof value === 'object') {
this._observer(value);
}
let binding = this._binding[key];
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get: function () {
return value
},
set: function (val) {
if(val !== value) {
// 修改数据
value = val;
binding._directives.forEach(function (item) {
item.update();
})
}
}
})
}
};
接下来实现_compile函数
Svm.prototype._compile = function(root){
var _this = this;
var nodes = root.children;
// 遍历传入的dom
for(var i = 0; i < nodes.length; i++) {
var node = nodes[i];
//递归遍历
if(node.children.length) {
this._compile(node)
}
//对元素上的属性做判断
//s-click
if(node.hasAttribute('s-click')) {
node.onclick = (function () {
let attrVal = node.getAttribute('s-click');
return _this.$methods[attrVal].bind(_this.$data)//返回对应方法绑定到对应的node上
})()
}
//s-model
if(node.hasAttribute('s-model') && node.tagName === 'INPUT' || node.tagName === 'TEXTAREA') {
node.addEventListener('input', (function (i) {
let attrVal = node.getAttribute('s-model');
_this._binding[attrVal]._directives.push(new Watcher(
'input',
node,
_this,
attrVal,
'value'
));
return function (e) {
//修改data数据
_this.$data[attrVal] = node[i].value;
}
})(i))
}
//s-bind
if(node.hasAttribute('s-bind')) {
let attrVal = node.getAttribute('s-bind');
_this._binding[attrVal]._directives.push(new Watcher(
'text',
node,
_this,
attrVal,
'innerHTML'
))
}
}
};
Watcher构造函数
/**
* [Watcher 监听数据更新DOM]
* @param {[type]} name [指令名称]
* @param {[type]} el [指令对应的DOM元素]
* @param {[type]} vm [所属的Svm实例]
* @param {[type]} exp [data中的属性]
* @param {[type]} attr [绑定的属性值]
*/
function Watcher (name, el, vm, exp, attr) {
this.name = name;
this.el = el;
this.vm = vm;
this.exp = exp;
this.attr = attr;
this.update();
}
//更新数据
Watcher.prototype.update = function(){
this.el[this.attr] = this.vm.$data[this.exp]
};
走一下其中的逻辑
执行_init方法把options各个选项挂载至实例上,_observer方法遍历data所有属性
并设置set存取器属性当data中的属性被修改时将执行 Watcher.update 方法设置视图中的数据与实例中的数据同步_compile函数遍历被挂载的dom并检测s-click,s-model,s-bind属性,若存在就往_binging中添加一个key为绑定数据的key的_directives数组中一watcher 若是s-model首先通过检测input事件执行改变data中的数据,然后添加一个watcher执行其update方法改变el上的数据而改变data中的数据则会执行update方法改变el数据,若是s-bind,则是直接添加一个watcher改变data触发update方法改变视图, s-click则是将methods中的方法挂到dom的onclick上并把data传入
结语
仔细理清其中的逻辑思路便会更加清晰vue背后的原理对于框架也有了更深的理解