Vue - 如何实现一个双向绑定
JS - 如何实现一个类似 vue 的双向绑定 Github JS 实现代码
先来看一张图:
这张图我做个简要的描述:
首先创建一个实例对象,分别触发了 compile 解析指令 和 observer 监听器,
compile 解析指令则循环递归 解析 类似 v-model 这样的指令,初始化 data 绑定数据,同时每个节点创建一个订阅者 watcher ,
observer 监听器 则利用了 Object.defineProperty() 方法的描述属性里边的 set,get方法,来监听数据变化,
get 方法是在创建实例对象,生成dom节点的时候都会触发,固:在compile 解析编译的时候,依次给每一个节点添加了一个订阅者到主题对象 Dep
set 方法则是数据发生改变了,通知Dep订阅器里的所有wachter,然后找到对应订阅者 wachter 触发对应 update 更新视图
简单的说明就是这样了。
双向绑定原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的。
具体点儿
Vue双向数据绑定的原理就是利用了Object.defineProperty() 这个方法重新定义了对象获取属性值(get)和设置属性值(set)的操作来实现的
2.实现一个监听器Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。
3.实现一个订阅者Watcher,每一个Watcher都绑定一个 update,watcher 可以收到属性的变化通知并执行相应的 update ,从而更新视图。
4.实现MVVM,双向绑定
以下实践里边的几个方法我就不做介绍了,感兴趣可查询
Object.defineProperty() createDocumentFragment() Object.keys()
话不多说:直接上代码:实现一个解析器Compile
/*
第一步
1,创建文档碎片,劫持所有dom节点,重绘dom节点
2,重绘dom节点,初始化文档碎片绑定数据 实现文档编译 compile
*/
function getDocumentFragment(node, vm) {
var flag = document.createDocumentFragment();
var child;
while (child = node.firstChild) {
/*
while (child = node.firstChild)
相当于
child = node.firstChild
while (child)
*/
compile(child, vm);
flag.appendChild(child);
}
node.appendChild(flag);
}
function compile(node, vm) {
/*
nodeType 返回数字,表示当前节点类型
1 Element 代表元素 Element, Text,
2 Attr 代表属性 Text, EntityReference
3 Text 代表元素或属性中的文本内容。
. . . 更多请查看文档
*/
if (node.nodeType === 1) {
// 获取当前元素的attr属性
var attr = node.attributes;
for (let i = 0; i < attr.length; i++) {
// nodeName 是attr属性 key 即名称 , 匹配自定义 v-m
if (attr[i].nodeName === 'v-m') {
// 获取当前值 即 v-m = "test" 里边的 test
let name = attr[i].nodeValue;
// 当前节点输入事件
node.addEventListener('keyup', function (e) {
vm[name] = e.target.value;
});
// 页面元素写值 vm.data[name] 即 vm.data['test'] 即 MVVM
node.value = vm.data[name];
//最后移除标签中的 v-m 属性
node.removeAttribute('v-m');
// 为每一个节点创建一个 watcher
new Watcher(vm, node, name, "input");
}
}
/*
继续递归调用 文档编译 实现 视图更新 ;
*/
if (child = node.firstChild) {
/*
if (child = node.firstChild)
相当于
child = node.firstChild
id(child)
*/
compile(child, vm);
}
}
if (node.nodeType === 3) {
let reg = /\{\{(.*)\}\}/;
if (reg.test(node.nodeValue)) {
let name = RegExp.$1.trim();
node.nodeValue = vm.data[name];
// 为每一个节点创建一个 watcher
new Watcher(vm, node, name, "text");
}
}
}
实现一个监听器Observer
/*
第二步
实现一个数据监听
1,获取当前实例对象的 data 属性 key
observer(当前实例对象 data ,当前实例对象)
2,使用 Object.defineProperty 方法 实现监听
*/
function observe(data, vm) {
Object.keys(data).forEach(function (key) {
defineReactive(vm, key, data[key]);
});
}
function defineReactive(vm, key, val) {
/*
Object.defineProperty
obj
要在其上定义属性的对象。
prop
要定义或修改的属性的名称。
descriptor
将被定义或修改的属性描述符。 描述符有很多,就包括我们要市用 set , get 方法
*/
var dep = new Dep();
Object.defineProperty(vm, key, {
get: function () {
/*
if (Dep.target) dep.addSub(Dep.target);
看到这段代码不要差异,生成每一个 dom节点,都会走 get 方法
这里为每一个节点 添加一个订阅者 到主题对象 Dep
*/
if (Dep.target) dep.addSub(Dep.target);
console.log(val)
return val;
},
set: function (newValue) {
if (newValue === val) return;
val = newValue;
console.log(val + "=>" + newValue)
// 通知所有订阅者
dep.notify();
}
});
}
实现一个订阅者Watcher
/*
第三步 1,实现一个 watcher 观察者/订阅者
订阅者原型上挂在两个方法 分别是
update 渲染视图 2,定义一个消息订阅器
很简单,维护一个数组,用来收集订阅者
消息订阅器原型挂载两个方法 分别是
addSub 添加一个订阅者
notify 数据变动 通知 这个订阅者的 update 方法
*/
function Watcher(vm, node, name, nodeType) {
Dep.target = this;
this.vm = vm;
this.node = node;
this.name = name;
this.nodeType = nodeType;
this.update();
console.log(Dep.target)
Dep.target = null;
}
Watcher.prototype = {
update: function () {
/*
this.node 指向当前修改的 dom 元素
this.vm 指向当前 dom 的实例对象
根据 nodeType 类型 赋值渲染页面
*/
if (this.nodeType === 'text') {
this.node.nodeValue = this.vm[this.name]
}
if (this.nodeType === 'input') {
this.node.value = this.vm[this.name]
}
}
}
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function (sub) {
this.subs.push(sub);
},
notify: function () {
this.subs.forEach(function (sub) {
sub.update();
});
}
}
实现类似Vue的MVVM
/*
创建一个构造函数,并生成实例化对象 vm
*/
function Vue(o) {
this.id = o.el;
this.data = o.data;
observe(this.data, this);
getDocumentFragment(document.getElementById(this.id), this);
}
var vm = new Vue({
el: 'app',
data: {
msg: 'HiSen',
test: 'Hello,MVVM'
}
});
也许看到最后大家也没有看出个所以然,曾几何时的我跟你们一样,看来看去,就是这么几段代码;建议:拿下我的源码,自己跑一跑,看一看,是骡子是马拉出来溜溜。
Vue - 如何实现一个双向绑定的更多相关文章
- 用ES6的class模仿Vue写一个双向绑定
原文地址:用ES6的class模仿Vue写一个双向绑定 点击在线尝试一下 最终效果如下: 构造器(constructor) 构造一个TinyVue对象,包含基本的el,data,methods cla ...
- vue中的数据双向绑定
学习的过程是漫长的,只有坚持不懈才能到达到自己的目标. 1.vue中数据的双向绑定采用的时候,数据劫持的模式.其实主要是用了Es5中的Object.defineProperty;来劫持每个属性的get ...
- 自己实现一个双向绑定的Vue
我们知道双向绑定是Vue的核心之一,接下来我们自己仿照Vue实现一个基本的功能. 项目代码在GitHub上: https://github.com/zhangKunUserGit/zk-vue
- Vue父子组件数据双向绑定,子组件可修改props
第一种,子组件通过监听父组件数据,子组件改变数据之后通知给父组件 原文链接:https://blog.csdn.net/m0_37728716/article/details/81776929 父组件 ...
- vue中v-model 数据双向绑定
表单输入绑定 v-model 数据双向绑定,只能应用在input /textare /select <div id="app"> <input type=&quo ...
- Vue.js 3.x 双向绑定原理
什么是双向绑定? 废话不多说,我们先来看一个 v-model 基本的示例: <input type="text" v-model="search"> ...
- vue 自定义组件 v-model双向绑定、 父子组件同步通信
父子组件通信,都是单项的,很多时候需要双向通信.方法如下: 1.父组件使用:msg.sync="aa" 子组件使用$emit('update:msg', 'msg改变后的值xxx ...
- vue 结合localStorage 来双向绑定数据
结合localStorage 来双向绑定数据(超级神奇) localStorage.js: const STORAGE_KEY = 'todos_vuejs' export default { fet ...
- vue 自定义组件 v-model双向绑定、 父子组件同步通信【转】
父子组件通信,都是单项的,很多时候需要双向通信.方法如下: 1.父组件使用:msg.sync="aa" 子组件使用$emit('update:msg', 'msg改变后的值xxx ...
随机推荐
- jQuery事件篇---事件对象
内容提纲: 1.事件对象 2.冒泡和默认行为 发文不易,转载请注明出处! JavaScript 在事件处理函数中默认传递了 event 对象,也就是事件对象.但由于浏览器的兼容性,开发者总是会做兼容方 ...
- MINI3内存分配算法
最差适应算法 #ifdef USING_WORST_FIT { //先找到第一个满足要求的空洞, //再以第一个为标准寻找最适合的空洞. //当最适合的空洞完全吻合 //就直接划给它,当空洞较大时就切 ...
- 关于JAVA是值传递还是引用传递的问题
1.概念 值传递:方法调用时,实际传入的是它的副本,在方法中对值的修改,不影响调用者的值. 引用传递:方法调用时,实际传入的是参数的实际内存地址,调用者和调用方法所操作的参数都指向同一内存地址,所以方 ...
- CentOS添加SSH登录提示
前言 使用阿里云服务器的应该都注意到每次ssh登录后都能看见类似下面这样的欢迎语: Last login:xxxxxxxxxxxxx Welcome to Alibaba Cloud Elastic ...
- 分布式微服务技术之 Spring Cloud Netflix
1 背景 Netflix 是全球十大视频网站中唯一收费站点,是美国互联网流媒体播放商,由于访问量巨大,转型为云计算公司. 由Netflix公司主持开发了一套代码框架和库Netflix OSS即open ...
- 找到链表中倒数第k个数
本文来源于翁舒航的博客,点击即可跳转原文观看!!!(被转载或者拷贝走的内容可能缺失图片.视频等原文的内容) 若网站将链接屏蔽,可直接拷贝原文链接到地址栏跳转观看,原文链接:https://www.cn ...
- 5.Resource注解解析
Resource有两种使用场景 1.Resource 当Resource后面没带参数的时候是根据它所注释的属性名称到applicationContext.xml文件中查找是否有bean的id与之匹配, ...
- python学习之老男孩python全栈第九期_day017知识点总结——初识递归、算法
一. 递归函数 如果一个函数在内部调用自身本身,这个函数就是递归函数. 最大递归深度默认是997 -- python从内存角度出发做得限制(而不是程序真的报错),最大深度可以修改 def func(n ...
- IPtables中SNAT和MASQUERADE的区别
问题 iptables中snat和MASQUERADE的区别 解决方案 iptables中可以灵活的做各种网络地址转换(NAT) 网络地址转换主要有两种:snat和DNAT snat是source n ...
- Android 退出app,后台推送的服务也停止了,怎么可以做到不停止后台服务呢?
service粘性等的那4种方式试了,三星的可以,小米老款手机可以,新款不行,华为新款也不行,还有魅族什么的,都不行,新款的手机上都有一个安全中心,只有在安全中心里面添加上允许app自启动才可以 怎么 ...