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 ...
随机推荐
- C#三大特性之 封装、继承、多态
一.封装: 封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类).被封装的对象通常被称为抽象数据类型. 封装的意义: 封装的意义在于保护或者防止代码(数据) ...
- spring mongodb增删改查操作
添加数据 School @Id @GeneratedValue private long id; @Indexed(unique = true) private String name; studen ...
- anaconda使用,jupyter notebook的使用方法
1. 通过anaconda安装不同的python环境 1) conda create -n python36 python=3.5 2) 激活虚拟环境: activate python36 # 进 ...
- Docker学习(六): 网络使用与配置
特别声明: 博文主要是学习过程中的知识整理,以便之后的查阅回顾.部分内容来源于网络(如有摘录未标注请指出).内容如有差错,也欢迎指正! =============系列文章============= 1 ...
- 面向对象(基础oop)之属性与构造函数
大家好,我叫李京阳,,很高兴认识大家,之所以我想开一个自己的博客,就是来把自己所了解的知识点通过自己的话写一下,希望被博客园的朋友们点评和一起讨论一下,也希望从博客园中多认识一些软件开发人员!现在我开 ...
- POJ P2777 Count Color——线段树状态压缩
Description Chosen Problem Solving and Program design as an optional course, you are required to sol ...
- Spring Data MongoDB 级联操作
DBRef 方式关联 DBRef 就是在两个Collection之间定义的一个关联关系,暂不支持级联的保存功能 例子:一个Person对象有多个Book对象,一对多关系 实体Person public ...
- docker pull提示x509错误的对应方法
在一台虚拟机上使用docker pull时出现了x509错误,相关原因与对应方法简单memo如下. 错误现象 在使用docker pull从dockerhub上下载镜像时提示如下错误 docker: ...
- _tcsrchr
原文:http://www.cnblogs.com/diyunpeng/archive/2012/01/18/2325289.html _tcsrchr #include <afx.h> ...
- Centos 7配置docker-阿里云镜像加速
阿里云加速网址:https://cr.console.aliyun.com/cn-hangzhou/mirrors(自行注册账密码) sudo mkdir -p /etc/docker sudo vi ...