index.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<body>
<div id="app">
<h1> {{ str }} </h1>
{{ str }}
<span> {{ val }} </span>
<button @click="btn">按钮</button>
<input v-model="str" />
</div>
<script type="text/javascript" src="vue.js"></script>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
str: '123',
val: 'abc'
},
methods: {
btn(event) {
this.str = '456';
}
},
beforeCreate() {
console.log('beforeCreated', this.$el, this.$data);
},
created() {
console.log('created', this.$el, this.$data);
},
beforeMount() {
console.log('beforeMounted', this.$el, this.$data);
},
mounted() {
console.log('mounted', this.$el, this.$data);
}
})
</script>
</body>
</html>
vue.js
constructor(options) {
console.log(this);
this.$options = options;
this.$watchEvent = {};
options.beforeCreate.bind(this)();
this.$data = options.data;
this.proxyData();
options.created.bind(this)();
options.beforeMount.bind(this)();
this.$el = document.querySelector(options.el);
this.compile(this.$el);
options.mounted.bind(this)();
}
// 数据代理,给new Vue对象实例添加属性
proxyData() {
for (let key in this.$data) {
Object.defineProperty(this, key, {
get() {
return this.$data[key];
},
set(val) {
this.$data[key] = val;
// 更新视图
this.observe(key);
}
})
}
}
// 视图更新
observe(key) {
if (this.$watchEvent[key]) {
this.$watchEvent[key].forEach((item, index) => {
item.update();
})
}
}
// 节点监听
addWatch(key, item, attr) {
let watch = new Watcher(this, key, item, attr)
if(this.$watchEvent[key]) {
this.$watchEvent[key].push(watch);
} else {
this.$watchEvent[key] = [];
this.$watchEvent[key].push(watch);
}
}
// 模板解析
compile(node) {
node.childNodes.forEach((item, index) => {
if (item.nodeType === 1) {
if (item.hasAttribute('@click')) {
let key = item.getAttribute('@click');
item.addEventListener('click', (event) => {
let fn = this.$options.methods[key].bind(this);
fn(event);
});
}
if (item.hasAttribute('v-model')) {
let key = item.getAttribute('v-model').trim();
if (this.hasOwnProperty(key)) {
item.value = this[key];
this.addWatch(key, item, 'value')
}
item.addEventListener('input', (event) => {
this[key] = item.value;
});
}
if (item.childNodes.length > 0) {
this.compile(item);
}
}
if (item.nodeType === 3) {
let reg = /\{\{(.*?)\}\}/g;
let text = item.textContent;
item.textContent = text.replace(reg, (match, key) => {
key = key.trim();
if (this.hasOwnProperty(key)) {
this.addWatch(key, item, 'textContent')
}
return this.$data[key.trim()];
})
}
});
}
}
class Watcher {
constructor(vm, key, node, attr) {
this.vm = vm;
this.key = key;
this.node = node;
this.attr = attr;
}
update() {
this.node[this.attr] = this.vm[this.key];
}
}