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 ...
随机推荐
- SqlServer常用语句
首先,写这个的原因是我其实sql语句不太行,总觉得自己写得很乱,好像也没有系统学习过,借此复习和与大家探讨 No.1 关于查询时间区间是否重叠的sql语句 问题是这样:插入之前,想查询同User是否其 ...
- 怎么使用fiddler 测试post get 接口
直接上图 测试 post
- 自定义经纬度索引(非RTree、Morton Code[z order curve]、Geohash的方式)
自定义经纬度索引(非RTree.Morton Code[z order curve].Geohash的方式) Custom Indexing for Latitude-Longitude Data 网 ...
- FisherYates费雪耶兹随机置乱算法
public class FisherYates { public static void main(String[] args) { int[] arr = new int[10]; // 初始有序 ...
- 一个简单IOC与DI示例
1.通过bean工厂实现读取xml文件,并实例化对象,实现自动注入. package com.pri.test; import com.pri.factory.BeanFactory; import ...
- web第一章(html)
HTML介绍 HyperText(超文本) Markup(标记) Language(语音) 类似于XML都是由标签组成 xml:是可扩展标记语言,标签可以任意自定义 HTML:不可以使用任意标签,学习 ...
- 使用input做简单的上传图片
css 代码: .container{ width: 200px; height: 200px; border: 1px solid #666; } HTML 代码: <input type=& ...
- 微信小程序-scroll-view组件
<view class="section"> <view class="section__title">vertical scroll& ...
- javascript requestAnimationFrame vs. setTimeout
在做javascript动画时,我们常常使用的方法就是通过setTimeout调用告诉浏览器每隔20ms执行一段js代码来对dom对象执行操作,这个貌似没有什么问题,但是当深入理解计算机的fps以及浏 ...
- [翻译] MGConferenceDatePicker
MGConferenceDatePicker https://github.com/matteogobbi/MGConferenceDatePicker MGConferenceDatePicker ...