实现一个双向绑定和v-model
发布订阅模式
我把发布订阅的实现类单独提出来,这样代码看起来简洁
/*
* 发布订阅
**/
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);
}
实现一个双向绑定和v-model的更多相关文章
- 用ES6的class模仿Vue写一个双向绑定
原文地址:用ES6的class模仿Vue写一个双向绑定 点击在线尝试一下 最终效果如下: 构造器(constructor) 构造一个TinyVue对象,包含基本的el,data,methods cla ...
- 自己实现一个双向绑定的Vue
我们知道双向绑定是Vue的核心之一,接下来我们自己仿照Vue实现一个基本的功能. 项目代码在GitHub上: https://github.com/zhangKunUserGit/zk-vue
- Vue - 如何实现一个双向绑定
JS - 如何实现一个类似 vue 的双向绑定 Github JS 实现代码 先来看一张图: 这张图我做个简要的描述: 首先创建一个实例对象,分别触发了 compile 解析指令 和 observe ...
- Vue双向绑定原理,教你一步一步实现双向绑定
当今前端天下以 Angular.React.vue 三足鼎立的局面,你不选择一个阵营基本上无法立足于前端,甚至是两个或者三个阵营都要选择,大势所趋. 所以我们要时刻保持好奇心,拥抱变化,只有在不断的变 ...
- 前端MVVM框架avalon揭秘 - 双向绑定原理
avalon大家可能不熟悉,但是Knockout估计或多或少听过用过,那么说说KO的几个概念 监控属性(Observables)和依赖跟踪(Dependency tracking) 声明式绑定(Dec ...
- Angular系列----AngularJS入门教程05:双向绑定(转载)
在这一步你会增加一个让用户控制手机列表显示顺序的特性.动态排序可以这样实现,添加一个新的模型属性,把它和迭代器集成起来,然后让数据绑定完成剩下的事情. 请重置工作目录: git checkout -f ...
- Angular数据双向绑定
Angular数据双向绑定 AngularJS诞生于2009年,由Misko Hevery 等人创建,后为Google所收购.是一款优秀的前端JS框架,已经被用于Google的多款产品当中.Angul ...
- AngularJS入门讲解2:过滤器和双向绑定
我们在上一课做了很多基础性的训练,接下来,我们讲一些难点的知识点,首先,讲一下如何实现一个全文检索功能: <html ng-app> <head> ... <script ...
- 剖析Vue原理&实现双向绑定MVVM-1
本文能帮你做什么?1.了解vue的双向数据绑定原理以及核心代码模块2.缓解好奇心的同时了解如何实现双向绑定为了便于说明原理与实现,本文相关代码主要摘自vue源码, 并进行了简化改造,相对较简陋,并未考 ...
- 很easy的js双向绑定框架(二):控制器继承
初衷 上一篇已经实现了数据的双向绑定,但model的控制范围是整个文档.在实际project中必需要有作用范围,以便做ui模块的拆分. 这一篇,我们希望实现像angularjs一样的控制器继承: 1. ...
随机推荐
- 阿里云域名+Github配置Pages
最近用Flutter开发了一个新的应用,准备上架AppStore的时候遇到一个问题,就是通过什么途经能把自己的隐私html和官网html通过链接都显示出来呢? 经过一番的查找和研究后就准备用Githu ...
- Mybatis的*Dao.XML中的配置与其对应的接口、resultMap的运用
例子. <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC &quo ...
- 使用CAMEL创建第一个Agent Society
CAMEL介绍 CAMEL 是一个开源社区,致力于探索代理的扩展规律.相信,在大规模研究这些代理可以提供对其行为.能力和潜在风险的宝贵见解.为了促进这一领域的研究,实现了并支持各种类型的代理.任务.提 ...
- MySQL 中的数据排序是怎么实现的?
MySQL 中的数据排序实现 在 MySQL 中,数据排序是通过 ORDER BY 子句实现的,主要涉及 文件排序 和 索引排序 两种方式. 1. 排序的基本原理 MySQL 的排序实现分为以下两种情 ...
- Java 中的字符串常量池和运行时常量池
Java 中的字符串常量池和运行时常量池 1. 字符串常量池(String Constant Pool) 定义 字符串常量池是 JVM 内存中专门用于存储字符串字面量和通过 intern() 方法加入 ...
- 题解:P10862 [HBCPC2024] Spicy or Grilled?
题目翻译: 有 nnn 个人,我们为他们买了香辣鸡肉汉堡套餐,每份是 aaa 元,可是里面有 xxx 人不能吃辣,于是还准备了 xxx 份烤鸡汉堡套餐,每份是 bbb 元,求总共要多少元. 解题思路 ...
- Kreuzberg:本地OCR+多格式解析!Kreuzberg如何用Python暴力提取30+文档格式?程序员看完直呼内行!
嗨,大家好,我是小华同学,关注我们获得"最新.最全.最优质"开源项目和高效工作学习方法 我们经常需要从各种不同类型的文档中提取文本内容,无论是办公文档.图像还是PDF文件.而Kre ...
- JavaScript编程实践:打造优雅健壮的代码
@charset "UTF-8"; .markdown-body { line-height: 1.75; font-weight: 400; font-size: 15px; o ...
- C#之使用任务并行库
.NET Framework4.0引入了一个新的关于异步操作的API,它叫做任务并行库(Task Parallel Library,简称TPL).TPL的核心是任务,一个任务代表一个异步操作,该操作可 ...
- 安装Scikit-learn 0.24.2版本
安装Scikit-learn 0.24.2版本 Anaconda 安装scikit-learn是0.19版本,目前最新版本是0.24.2,之前的版本与现在的版本之间差距还是比较大,因此有必要对scik ...