发布订阅模式
我把发布订阅的实现类单独提出来,这样代码看起来简洁
/*
* 发布订阅
**/
class Pubsub {
static instance = null;
// 单例
static getInstance() {
if (Pubsub.instance == null) {
Pubsub.instance = new Pubsub;
}
return Pubsub.instance;
}
// 注册的事件和处理器关联集合
eventAndHandel = {};
// 触发
emit(eventName, params) {
if (this.eventAndHandel.hasOwnProperty(eventName)) {
this.eventAndHandel[eventName].forEach(hander => {
hander(params)
});
}
}
// 注册or订阅
on(eventName, cbc) {
if (!this.eventAndHandel.hasOwnProperty(eventName)) {
this.eventAndHandel[eventName] = [cbc]
} else {
this.eventAndHandel[eventName].push(cbc)
}
}
}
第一版本 es5
最普通的语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<h1 d-bind="name"></h1>
<span d-bind="name"></span>:<span d-bind="age"></span>
</div>
<div>
姓名:<input d-model="name" type="text"></br>
性别:<input d-model="age" type="text"></br>
</div>
<!-- <script src="./lodash.js"></script> -->
<script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
<script src="./helper.js"></script>
<script>
// ---------------------一个vue实例开始----------------------------
const data = {
name: '丁少华',
age: 20
};
// v-model的实现
const models = document.querySelectorAll(`[d-model]`);
models.forEach(item => {
const v = item.getAttribute('d-model');
item.oninput = function ({ target: { value } }) {
data[v] = value;
}
})
// 双向绑定的实现
const pubsub = Pubsub.getInstance();
const data_ = _.cloneDeep(data);
for (const key in data) {
Object.defineProperty(data, key, {
set(newValue) {
data_[key] = newValue;
pubsub.emit('vm', {
id: key,
value: newValue
})
},
get() {
return data_[key];
}
})
}
pubsub.on('vm', ({ id, value }) => {
// 给普通节点复制
const binds = document.querySelectorAll(`[d-bind=${id}]`);
binds.forEach(item => {
item.innerText = value;
})
// 给表单控件赋值
const models = document.querySelectorAll(`[d-model=${id}]`);
models.forEach(item => {
item.value = value;
})
});
// 初始化赋值
for (const key in data) {
data[key] = data[key];
}
</script>
</body>
</html>
第二版 es6
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>
<h1 d-bind="name"></h1>
<span d-bind="name"></span>:<span d-bind="age"></span>
</div>
<div>
姓名:<input d-model="name" type="text"></br>
性别:<input d-model="age" type="text"></br>
</div>
<script>
// ---------------------一个vue实例开始----------------------------
const data = {
name: '丁少华',
age: 20
};
// ------------双向绑定的实现---------------
const pubsub = Pubsub.getInstance();
const data_ = new Proxy(data,{
set(target, property, value, receiver){
Reflect.set(...arguments);
pubsub.emit('vm', {
id: property,
value: value
})
},
get(){
return Reflect.get;
}
})
pubsub.on('vm', ({ id, value }) => {
// 给普通节点复制
const binds = document.querySelectorAll(`[d-bind=${id}]`);
binds.forEach(item => {
item.innerText = value;
})
// 给表单控件赋值
const models = document.querySelectorAll(`[d-model=${id}]`);
models.forEach(item => {
item.value = value;
})
console.log(data_);
});
// 初始化赋值
for (const key in data) {
data_[key] = data[key];
}
// ---------v-model的实现-----------------
const models = document.querySelectorAll(`[d-model]`);
models.forEach(item => {
const v = item.getAttribute('d-model');
item.oninput = function ({ target: { value } }) {
data_[v] = value;
}
})
</script>
</body>
</html>
可以看到proxy很方便,我也不用使用lodash的深拷贝来进行隔离元数据了
vue编译模板
const compileHandel = (el, data) => {
const childNodes = el.childNodes;
const reg = /{{(.*)}}/; // 表达式文本
childNodes.forEach(node => {
const text = node.textContent;
const isElementNode = node.nodeType == 1; // 按元素节点方式编译
if (isElementNode) {
const nodeAttrs = node.attributes;
for (const iterator of nodeAttrs) { //argument都实现了iterator接口,所以可以for of
const { name: attrName, value: exp } = iterator;
if (attrName.indexOf('v-') == 0) {
const dir = attrName.substring(2);
if (dir.indexOf('on') === 0) { // 事件指令
compileHandelHelper.eventDirective(data,node, exp, dir);
} else { // 普通指令
if(dir === 'model'){
compileHandelHelper.modelDirective(data,node, exp);
}
}
}
}
} else {
reg.test(text) && compileHandelHelper.mastache(data, node, RegExp.$1.trim());
}
// 遍历编译子节点
if (node.childNodes && node.childNodes.length) {
compileHandel(node, data);
}
})
}
const compileHandelHelper = {
mastache(data, node, txt) { // 解析双大括号
node.textContent = '123';
},
eventDirective() { // 解析事件指令
},
modelDirective(data, node, exp) { // 解析v-model指令
node.oninput = function ({ target: { value } }) {
}
}
}
export default (el, data) => {
compileHandel(el, data);
}