MVVM的设计模式是vue的核心思想,本文主要记录了实现思路。
index.html文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-demo</title>
</head>
<body>
<div id="vue-app">
<input type="text" k-model="word">{{word}}
<p><p>{{word}}</p></p>
</div>
<script src="./index.js"></script>
<script>
var vm = new Kue({
el: '#vue-app',
data: {
word: 'Hello World!'
}
});
</script>
</body>
</html>
index.js文件:
class Kue {
constructor(options){
this.$options = options
//数据绑定
this._data = options.data
this.observer(options.data)
this.compile(options.el)
}
//1,如何实现模板解析:找到{{}}中的属性,根据属性去data中找对应的属性值从而赋值给对应节点。
compile(el){
const nodeElement = document.querySelector(el)
this.compileFun(nodeElement)
}
compileFun(element){
const nodeChildren = element.childNodes
// console.log(nodeChildren)
Array.from(nodeChildren).forEach((node) => {
if(node.nodeType == 3){
//1,文本
//思路:文本,利用正则匹配{{}}
let nodeContent = node.textContent;
const reg = /\{\{\s*(\S*)\s*\}\}/;
if(reg.test(nodeContent)){
node.textContent = this._data[RegExp.$1]
new Watcher(this,RegExp.$1, newValue => {
node.textContent = newValue
});
}
}else if(node.nodeType == 1){
//2,标签。分为2种情况。
//对于标签中的{{}}值
let nodeContent = node.textContent;
const reg = /\{\{\s*(\S*)\s*\}\}/;
if(reg.test(nodeContent)){
node.textContent = this._data[RegExp.$1]
new Watcher(this,RegExp.$1, newValue => {
node.textContent = newValue
});
}
//对于标签中的指令值即属性值 以k-model指定为例
let attrs = node.attributes;
Array.from(attrs).forEach((attr) => {
let attrName = attr.name;
let attrValue = attr.value;
if(attrName.indexOf('k-') == 0){
node.value = this._data[attrValue]
attrName = attrName.substr(2)
if(attrName == 'model'){
node.value = this._data[attrValue]
}
//从v到m层
//3,如何实现页面文本发生变化,从而data中数据发生变化
node.addEventListener('input',e => {
this._data[attrValue] = e.target.value
})
new Watcher(this,attrValue,newValue => {
node.value = newValue
})
}
})
}
if (node.childNodes.length > 0) {
this.compileFun(node);
}
})
}
//2,如何实现m到v变化: 当data中的数据发生变化时,视图层对应也会随之变化。利用发布订阅模式。
//监听器:用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新
observer(data){
//思路:劫持data中的所有属性,使用Object.defineproperty
Object.keys(data).forEach((key) => {
let value = data[key];
let dep = new Dep();//创建dep对象,即消息订阅器
Object.defineProperty(data , key , {
configurable: true,
enumerable: true,
get: function () {
if (Dep.target) {
dep.addSub(Dep.target);
}
return value
},
set: function (newValue) {
//数据发生变化,告诉订阅者watcher去更新
dep.notify(newValue)
console.log("set", newValue);
}
})
})
}
}
//发布订阅模式。
//因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,
//然后在监听器Observer和订阅者Watcher之间进行统一管理的。
class Dep{
constructor(){
this.subs = []
}
addSub(sub){
this.subs.push(sub)
}
//通知去更新消息
notify(newValue){
this.subs.forEach(sub => {
sub.update(newValue);
})
}
}
//实现一个订阅者Watcher,连接Observer和Compile。
//可以订阅并收到每个属性的变化通知并执行指令绑定的相应函数,从而更新视图。
class Watcher{
constructor(vm,exp,cb){
Dep.target = this
vm._data[exp]
this.cb = cb
Dep.target = null
}
update(newValue){
console.log("更新了", newValue);
this.cb(newValue);
}
}