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 ...
随机推荐
- vertical-align属性baseline(转)
图7-34 文字和图片内容默认垂直对齐方式为基线对齐 上一小节讲解了行高与单行纯文字的垂直居中,而如果行内含有图片和文字,在浏览器内浏览时,读者可以发现文字和图片在垂直方向并不是沿中线居中,而是沿基线 ...
- c 语言 随机生成数函数
int theRandom = 0; theRandom = arc4random_uniform(3)//随机生成一个从0到2的数 或者 #include "stdio.h" # ...
- 使用gunzip、tar、rar、(zip压缩和unzip解压缩)
---------------------20171119------------------------------ 解压gz后缀 使用gunzip filename.gz ------------ ...
- git远程易错点
git pull下来用git branch -r查看远程分支才有数据 解决方案:指定当前工作目录工作分支,跟远程的仓库,分支之间的链接关系. 比如我们设置master对应远程仓库的master分支 g ...
- CodeForces 598B(循环数组)
对于循环数组的问题,就是找偏移K后位置 偏移后位置=起始位置+(相对位置+K)%(长度+1) #include <iostream> #include <string> #in ...
- MyEclipse中设置开发项目时使用的默认JDK
安装好MyEclipse之后,在MyEclipse中开发项目时,默认使用的是MyEclipse是自带的JDK,如下图所示: 如果我们需要使用自己安装好的JDK,那么就需要在MyEclipse中重新设置 ...
- HTML5 Canvas中绘制椭圆的几种方法
1.canvas自带的绘制椭圆的方法 ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)是后来 ...
- LeetCode赛题----Find Left Most Element
问题描述 Given a binary tree, find the left most element in the last row of the tree. Example 1: Input: ...
- Android BitmapFactory.Options
public Bitmap inBitmap 如果设置,解码选项“对象的方法,采取将尝试重用这个位图加载内容时. public int inDensity 使用的位图的象素密度. public boo ...
- org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): cn.gaiay.business.helper.dao.LiveRegenrationRecordMapper.insert
原因分析: 字段名称.报名.类名 对应不上 ,比如colomn和property属性 反了.. 按以下步骤一一执行: 1:检查xml文件所在的package名称是否和interface对应的packa ...