console
function proxys(data, observebal) {
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach((key, index) => {
let val = data[key];
proxys(val, observebal);
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get: function() {
console.log('get~~~!');
Observebal.observer && observebal.addObserver(Observebal.observer);
return val;
},
set: function(newValue) {
console.log('set~~~!');
proxys(newValue, observebal);
val = newValue;
observebal.notify();
},
});
});
}
class Observebal {
constructor(data) {
proxys(data, this);
this.observerIds = {};
this.observers = [];
}
addObserver(observer) {
if (!this.observerIds.hasOwnProperty(observer.id)) {
this.observers.push(observer);
this.observerIds[observer.id] = observer;
}
}
notify() {
this.observers.forEach((observer, index) => {
observer.update();
});
}
}
Observebal.observer = null;
var globalObserverId = 0;
class Observer {
constructor(vm, expr, cb) {
this.$vm = vm;
this.$expr = expr;
this.$cb = cb;
this.id = globalObserverId++;
this.getter(this.$vm, this.$expr);
}
update() {
this.$cb(this.getter(this.$vm, this.$expr));
}
getter(vm, expr) {
var exps = expr.split('.');
let obj = vm;
for (var i = 0, len = exps.length; i < len; i++) {
if (!obj) return;
obj = obj[exps[i]];
}
return obj;
}
}
let vm2 = { a: 1, b: 2 };
let observal = new Observebal(vm2);
let observer1 = new Observer(vm2, 'a', value => {
console.log('observer1', value);
});
let observer2 = new Observer(vm2, 'b', value => {
console.log('observer2', value);
});
let value;
Observebal.observer = observer1;
value = vm2.a;
Observebal.observer = observer2;
value = vm2.a;
vm2.a = 22222;
class Compile {
constructor(vm) {
this.$vm = vm;
let ele = this.$vm.$options.ele;
let fragement = this.parseNode(ele);
this.walkNodes(fragement);
ele.appendChild(fragement);
}
parseNode(node) {
let fragement = document.createDocumentFragment();
let child;
while ((child = node.firstChild)) {
fragement.appendChild(child);
}
return fragement;
}
walkNodes(node) {
let isElementNode = function(node) {
return node.nodeType == 1;
};
let isTextNode = function(node) {
return node.nodeType == 3;
};
if (isTextNode(node)) {
let isMatch = /\{\{(.*)\}\}/.test(node.textContent || node.value);
isMatch && this.bindCallBack(node, 'txt', this.$vm, RegExp.$1);
}
if (isElementNode(node)) {
this.parseAttributes(node);
}
let childNodes = node.childNodes;
for (let i = 0; i < childNodes.length; i++) {
this.walkNodes(childNodes[i]);
}
}
eventHandler(node, vm, exp, dir) {
var eventType = dir.split(':')[1],
fn = vm.$options.methods && vm.$options.methods[exp];
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false);
}
}
getVmVal(vm, exp) {
var val = vm;
exp = exp.split('.');
exp.forEach(function(k) {
val = val[k];
});
return val;
}
setVmVal(vm, exp, newValue) {
var val = vm;
exp = exp.split('.');
exp.forEach(function(k, i) {
if (i < exp.length - 1) {
val = val[k];
} else {
val[k] = newValue;
}
});
}
bindCallBack(node, type, vm, exp) {
let updater = updator[type + 'Updater'];
updater(node, this.getVmVal(vm, exp));
Observebal.observer = new Observer(vm, exp, newValue => {
updater(node, newValue);
});
if (type == 'model') {
node.addEventListener(
'input',
e => {
this.setVmVal(vm, exp, e.target.value);
},
false
);
}
return this.getVmVal(vm, exp);
}
parseAttributes(node) {
var nodeAttrs = node.attributes;
Array.prototype.slice.call(nodeAttrs).forEach(attr => {
var attrName = attr.name;
if (attrName.indexOf('v-') == 0) {
var exp = attr.value;
var type = attrName.substring(2);
if (type.indexOf('on') === 0) {
this.eventHandler(node, this.$vm, exp, type);
} else {
this.bindCallBack(node, type, this.$vm, exp);
}
node.removeAttribute(attrName);
}
});
}
}
var updator = {
txtUpdater: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
htmlUpdater: function(node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
classUpdater: function(node, value, oldValue) {
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
var space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
modelUpdater: function(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
},
};
function MVVM(options) {
this.$options = options || {};
var data = (this._data = this.$options.data);
var me = this;
Object.keys(data).forEach(function(key) {
me._proxyData(key);
});
this._initComputed();
new Observebal(data);
this.$compile = new Compile(this);
}
MVVM.prototype = {
$watch: function(key, cb, options) {
Observebal.observer = new Observer(this, key, cb);
},
_proxyData: function(key, setter, getter) {
var me = this;
setter =
setter ||
Object.defineProperty(me, key, {
configurable: false,
enumerable: true,
get: function proxyGetter() {
return me._data[key];
},
set: function proxySetter(newVal) {
me._data[key] = newVal;
},
});
},
_initComputed: function() {
var me = this;
var computed = this.$options.computed;
if (typeof computed === 'object') {
Object.keys(computed).forEach(function(key) {
Object.defineProperty(me, key, {
get: typeof computed[key] === 'function' ? computed[key] : computed[key].get,
set: function() {},
});
});
}
},
};
var vm = new MVVM({
ele: document.getElementById('mvvm-app'),
data: {
someStr: 'hello ',
className: 'btn',
htmlStr: '<span style="color: #f00;">red</span>',
child: {
htmlStr: 'few',
someStr: 'World !'
}
},
computed: {
getHelloWord: function() {
return this.someStr + this.child.someStr;
}
},
methods: {
clickBtn: function(e) {
var randomStrArr = ['childOne', 'childTwo', 'childThree'];
this.child.someStr = randomStrArr[parseInt(Math.random() * 3)];
this.child.htmlStr = randomStrArr[parseInt(Math.random() * 3)];
}
}
});
vm.$watch('child.someStr', function() {
console.log('few', arguments);
});
vm.$watch('child.someStr', function() {
console.log('fewfewfww');
});
<div id="mvvm-app">
<input type="text" v-model="someStr">
<input type="text" v-model="child.someStr">
<p>{{getHelloWord}}</p>
<p v-html="child.htmlStr"></p>
<button v-on:click="clickBtn">change model</button>
</div>