index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<title>Document</title>
</head>
<body>
<div id="app">{{msg}}
<h3>{{str}}</h3>
<button @click="changeMsg">changeMsg</button>
<input type="text" v-model="msg">
</div>
<script src="./vue.js"></script>
<script>
new Vue({
el: '#app',
data: {
msg: 'hello world',
str: 'hello',
},
methods: {
changeMsg(e) {
//this.msg = 'hello vue'
// console.log('fadsfadf')
// console.log(e)
// console.log(this.msg)
this.msg = 'hello vue'
}
},
// beforeCreate() {
// console.log('beforeCreate', this.$data)
// },
// created() {
// // 这里需要改变 this 的指向,否则获取不到 $data
// console.log('created', this.$data)
// },
// beforeMount() {
// console.log('beforeMount', this.$el)
// },
// mounted() {
// console.log('mounted', this)
// }
})
</script>
</body>
</html>
vue.js
class Vue {
constructor(options) {
this.$options = options;
this.$watchEvents = {};
if (typeof options.beforeCreate === "function") {
options.beforeCreate.bind(this)();
}
// 数据绑定
this.$data = options.data;
this.proxyData(options.data);
this.observe();
if (typeof options.created === "function") {
options.created.bind(this)();
}
if (typeof options.beforeMount === "function") {
options.beforeMount.bind(this)();
}
// 节点
this.$el = document.querySelector(options.el);
// 解析节点
this.compile(this.$el);
if (typeof options.mounted === "function") {
options.mounted.bind(this)();
}
}
proxyData(data) {
Object.keys(data).forEach((key) => {
// 将 data 中的所有数据直接放入到 this(Vue实例) 上
Object.defineProperty(this, key, {
enumerable: true,
configurable: true,
get() {
return data[key];
},
set(newValue) {
data[key] = newValue;
},
});
});
}
// data 中数据发生改变触发 watch 中的 update
observe() {
Object.keys(this.$data).forEach((key) => {
let value = this.$data[key];
let that = this;
Object.defineProperty(this.$data, key, {
enumerable: true,
configurable: true,
get() {
return value;
},
set(newValue) {
value = newValue;
// 通知视图更新
if (that.$watchEvents[key]) {
that.$watchEvents[key].forEach((watch) => {
watch.update();
});
}
},
});
});
}
compile(node) {
if (node.childNodes && node.childNodes.length) {
node.childNodes.forEach((child) => {
// nodeType 1: 元素节点 3: 文本节点
// 文本节点
if (child.nodeType === 3) {
let reg = /\{\{(.*)\}\}/;
let text = child.textContent;
// 给节点赋值
child.textContent = text.replace(reg, (matched, value) => {
// value 是模板中的数据(data 中的键)
value = value.trim();
if (this.hasOwnProperty(value)) {
let watch = new Watch(this, value, child, "textContent");
if (this.$watchEvents[value]) {
this.$watchEvents[value].push(watch);
} else {
this.$watchEvents[value] = [];
this.$watchEvents[value].push(watch);
}
}
return this.$data[value];
});
}
// 元素节点
if (child.nodeType === 1) {
if (child.hasAttribute("@click")) {
child.addEventListener("click", (e) => {
this.eventFn =
this.$options.methods[child.getAttribute("@click").trim()].bind(
this
);
this.eventFn(e);
});
}
if (child.hasAttribute("v-model")) {
let attr = child.getAttribute("v-model").trim();
if (this.hasOwnProperty(attr)) {
child.value = this[attr];
child.addEventListener("input", (e) => {
this[attr] = child.value;
});
}
}
if (child.childNodes && child.childNodes.length) {
this.compile(child);
}
}
});
}
}
}
class Watch {
constructor(vm, key, node, attr) {
// Vue实例
this.vm = vm;
// Vue实例的属性名称(data 中的键)
this.key = key;
// dom 节点
this.node = node;
// 节点的属性
this.attr = attr;
}
get() {
return this.vm.$data[this.key];
}
// 更新节点里面的数据内容(更新视图)
update() {
this.node[this.attr] = this.vm[this.key];
}
}