<!DOCTYPE html>
<html lang="en">
<body>
<div id="app">
<input v-model="name" />
<h1>{{ name }}</h1>
<h1>{{ age }}</h1>
<h1>{{ hello }}</h1>
<h1>{{ myname }}</h1>
<button v-on:click="addAge">过年了,大一岁</button>
<button v-on:click="changeName">我叫李四</button>
</div>
<script src="./myVue.js"></script>
<script>
var vm = new MyVue({
el: '#app',
data: {
name: '张三',
age: 20,
myname:'12312378'
},
computed: {
hello() {
return `你好我是${this.name}, 今年${this.age}岁。`;
}
},
watch: {
name(newVal, oldVal) {
console.log('新数据:'+newVal, '旧数据:'+oldVal)
}
},
methods: {
addAge() {
this.age++;
},
changeName() {
this.name = '李四';
}
}
});
</script>
</body>
</html>
class MyVue {
constructor({ el, data, methods, computed, watch }) {
let obs = new Observer(data,this);
this.$el = el;
this.$data = obs.data;
this.$watch = watch;
this.$computed = computed;
this.$methods = methods;
Object.keys(this.$data).forEach(i => {
this.proxyKeys(i);
});
new Compile(this);
}
proxyKeys(key) {
let _this = this;
Object.defineProperty(_this, key, {
enumerable: false,
configurable: true,
get() {
return _this.$data[key];
},
set(newVal) {
let oldVal = _this.$data[key];
_this.$data[key] = newVal;
}
})
}
}
class Observer {
constructor(data,_this) {
this.data = data;
Object.keys(data).forEach(key => {
let value = this.data[key];
let dep = new Dep();
Object.defineProperty(data, key, {
get() {
Dep.target && dep.add(Dep.target);
return value;
},
set(newVal) {
if (newVal !== value) {
value = newVal;
Object.keys(_this.$watch).forEach(key => {
new Watcher(_this.$data, key, () => {
_this.$watch[key].call(_this, value, newVal);
});
});
dep.notify(newVal);
}
}
})
})
}
}
class Dep {
constructor() {
this.subs = [];
}
add(sub) {
console.log(sub)
this.subs.push(sub)
}
notify(newVal) {
this.subs.forEach(sub => {
sub.update(newVal)
})
}
}
Dep.target = null;
class Watcher {
constructor(data, key, cb) {
this.data = data;
this.key = key;
this.cb = cb;
Dep.target = this;
this.value = data[key];
Dep.target = null;
}
update(newVal) {
let oldVal = this.value;
if (oldVal !== newVal) {
this.value = newVal;
this.cb(newVal);
}
}
}
class Compile {
constructor(vm) {
this.vm = vm;
let el = document.querySelector(this.vm.$el);
let fragment = document.createDocumentFragment();
if (el) {
while (el.firstChild) {
fragment.appendChild(el.firstChild);
}
this.compileElement(fragment);
el.appendChild(fragment);
}
else {
alert('挂载元素不存在');
}
}
compileElement(el) {
for (let node of el.childNodes) {
if (node.nodeType == 1) {
for (let attr of node.attributes) {
let { name: attrName, value: exp } = attr;
if (attrName.indexOf('v-') == 0) {
let [dir, value] = attrName.substring(2).split(':');
if (dir === 'on') {
let method = this.vm.$methods[exp];
method && node.addEventListener(value, method.bind(this.vm), false)
}
else if (dir === 'model') {
let value = this.vm.$data[exp];
node.value = typeof value != 'undefined' ? value : '';
node.addEventListener('input', e => {
if (e.target.value !== value) {
this.vm.$data[exp] = e.target.value;
}
})
new Watcher(this.vm.$data, exp, (newVal) => {
node.value = typeof newVal === 'undefined' ? '' : newVal;
})
}
}
}
}
else if (node.nodeType == 3) {
let reg = /\{\{(.*)\}\}/;
if(reg.test(node.textContent)){
let exp = reg.exec(node.textContent)[1].trim();
if(this.vm.$data[exp]){
let value = this.vm.$data[exp];
node.textContent = typeof value === 'undefined' ? '' : value;
new Watcher(this.vm.$data, exp, newVal => {
node.textContent = typeof newVal === 'undefined' ? '' : newVal;
});
}
else if(this.vm.$computed[exp]){
let computed = new ComputedWatcher(this.vm, exp, newVal => {
node.textContent = typeof newVal === 'undefined' ? '' : newVal;
});
node.textContent = computed.value;
Object.defineProperty(this.vm, exp, {
enumerable: false,
configurable: true,
get(){
return computed.value
}
})
}
}
}
if (node.childNodes && node.childNodes.length) {
this.compileElement(node);
}
}
}
}
class ComputedWatcher{
constructor(vm, key, cb){
this.vm = vm;
this.key = key;
this.cb = cb;
Dep.target = this;
this.value = this.vm.$computed[key].call(this.vm);
Dep.target = null;
}
update() {
let newVal = this.vm.$computed[this.key].call(this.vm);
let oldVal = this.value;
if (newVal !== oldVal) {
this.value = newVal;
this.cb(newVal);
}
}
}