Vue双向数据绑定简易实现
一、vue中的双向数据绑定主要使用到了Object.defineProperty(新版的使用Proxy实现的)对Model层的数据进行getter和setter进行劫持,修改Model层数据的时候,在setter中可以知道对那个属性进行修改了,然后修改View的数据。
二、简易版双向数据绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Proxy双向数据绑定大概原理</title>
</head>
<body>
<div id="app">
<input type="text" id="inpt"/>
<span id="txt"></span>
</div>
<script>
var inputDom = document.getElementById("inpt"),
spanDom = document.getElementById("txt"),
data = {} // 更新DOM
function notifyToUpdateDOM (newVal) {
inputDom.value = newVal
spanDom.innerHTML = newVal
} var proxyHandler = {
get: function(target, property){
return target[property]
},
set: function(target, property, value){
target[property] = value
notifyToUpdateDOM(value)
}
} // 创建代理
var dataProxy = new Proxy(data, proxyHandler) // 监听input的input事件
inputDom.addEventListener("input", function(e){
// 设置data中的inputModel属性,会触发set方法的调用
dataProxy.inputModel = e.target.value
})
</script>
</body>
</html>
以上简易代码比较适合Model层没有默认数据的时候,如果Model层的inputModel默认有值为:“双向数绑定”;那么如何在页面初始化完成的时候就把Model层的数据显示到View上呢?因此在进行数据绑定之前,需要把View模板进行编译,和Model层的数据进行关联。
三、实现View数据变化映射到Model数据上,初始化的Model数据映射到View上
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Proxy双向数据绑定大概原理</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text"/>{{text}}
</div> <!-- 此次完成了UI到Model的数据绑定,还没有实现Model到UI的绑定({{xxx}}处理没有实现) -->
<script> // 参数el:最外层的dom元素,此处案例是app
// 参数vm:表示Vue的实例
// 主要处理v-model指令和{{xxx}},把里面的变量和Vue中的进行映射
function CompileTemplate(el, vm){
this.$ele = el
this.$vm = vm
this.$fragment = null // 保存重新编译之后的dom结构
} CompileTemplate.prototype = {
// 修正构造函数的指向
constructor: CompileTemplate,
// 返回fragment
getDocumentFragment: function(){
if (this.$fragment) {
return this.$fragment
} else {
this.$fragment = this.nodeToFragment()
return this.$fragment
}
},
nodeToFragment: function(){
var node = document.getElementById(this.$ele),
fragment = document.createDocumentFragment(),
child = null while(child = node.firstChild){
this.compileElement(child)
/*如果被插入的节点已经存在于当前文档的文档树中,则那个节点会首先从原先的位置移除,
然后再插入到新的位置;如果你需要保留这个子节点在原先位置的显示,则你需要先用Node.cloneNode
方法复制出一个节点的副本,然后在插入到新位置.
*/
fragment.appendChild(child)
}
return fragment
},
// 处理节点信息以及绑定事件
compileElement: function(node){
// 匹配{{}}
var reg = /\{\{(.*)\}\}/g,
_this = this // 元素
if (node.nodeType === 1) {
var attributes = node.attributes
for (var len = attributes.length - 1; len > 0; len--) {
// 获取v-model绑定的变量
if (attributes[len].nodeName === 'v-model') {
// 获取v-model="txt"中的txt
var name = attributes[len].nodeValue
// 为input元素绑定input事件,当事件发生时,设置Vue对象中的$data的值
node.addEventListener("input", function(e){
// 设置vue对象中data的text中值
_this.$vm.$data[name] = e.target.value
})
// 初始化的时候,需要把Vue对象中的数据赋值给input元素的value
node.value = _this.$vm.$data[name];
node.removeAttribute('v-model')
}
}
} // text
if (node.nodeType === 3) {
if(reg.test(node.nodeValue)) {// 获取v-model绑定的属性名 {{text}}
var name = RegExp.$1.trim(); // 获取匹配到的字符串
// 初始化的时候,把vue对象data的数据赋值给{{text}}
node.nodeValue = _this.$vm.$data[name];
}
}
}
} function Vue(options){
this.$el = options.el
this.$data = options.data
// 解析DOM模板,如v-model指令改为input事件,{{xxx}}改为对象中的数据
var fragmentDOM = new CompileTemplate(this.$el, this).getDocumentFragment()
// 更新DOM
document.getElementById(this.$el).appendChild(fragmentDOM)
} var vm = new Vue({
el: "app",
data: {
text: '双向数据绑定'
}
})
</script> </body>
</html>
四、在案例三的基础上,使用订阅发布模式实现{{xxx}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Proxy双向数据绑定大概原理(最终版)</title>
</head>
<body>
<div id="app">
<input type="text" v-model="text"/>{{text}}
</div> <!-- 此次实现Model到UI的绑定 -->
<script> // 参数el:最外层的dom元素,此处案例是app
// 参数vm:表示Vue的实例
// 主要处理v-model指令和{{xxx}},把里面的变量和Vue中的进行映射
function CompileTemplate(el, vm){
this.$ele = el
this.$vm = vm
this.$fragment = null // 保存重新编译之后的dom结构
} CompileTemplate.prototype = {
// 修正构造函数的指向
constructor: CompileTemplate,
// 返回fragment
getDocumentFragment: function(){
if (this.$fragment) {
return this.$fragment
} else {
this.$fragment = this.nodeToFragment()
return this.$fragment
}
},
nodeToFragment: function(){
var node = document.getElementById(this.$ele),
fragment = document.createDocumentFragment(),
child = null while(child = node.firstChild){
this.compileElement(child)
/*如果被插入的节点已经存在于当前文档的文档树中,则那个节点会首先从原先的位置移除,
然后再插入到新的位置;如果你需要保留这个子节点在原先位置的显示,则你需要先用Node.cloneNode
方法复制出一个节点的副本,然后在插入到新位置.
*/
fragment.appendChild(child)
}
return fragment
},
// 处理节点信息以及绑定事件
compileElement: function(node){
// 匹配{{}}
var reg = /\{\{(.*)\}\}/g,
_this = this // 元素
if (node.nodeType === 1) {
var attributes = node.attributes
for (var len = attributes.length - 1; len > 0; len--) {
// 获取v-model绑定的变量
if (attributes[len].nodeName === 'v-model') {
// 获取v-model="txt"中的txt
var name = attributes[len].nodeValue
// 为input元素绑定input事件,当事件发生时,设置Vue对象中的$data的值
node.addEventListener("input", function(e){
// 设置vue对象中data的text中值
_this.$vm.$data[name] = e.target.value
})
// 初始化的时候,需要把Vue对象中的数据赋值给input元素的value
// node.value = _this.$vm.$data[name];
new Watcher(_this.$vm, node, name, "value")
node.removeAttribute('v-model')
}
}
} // text
if (node.nodeType === 3) {
if(reg.test(node.nodeValue)) {// 获取v-model绑定的属性名 {{text}}
var name = RegExp.$1.trim(); // 获取匹配到的字符串
// 初始化的时候,把vue对象data的数据赋值给{{text}}
// node.nodeValue = _this.$vm.$data[name];
new Watcher(_this.$vm, node, name, "nodeValue")
}
}
}
} /* 对model层的数据进行劫持,model改变时,需要通知修改UI的数据,此处使用Proxy处理(兼容性不好),
也可以使用Object.defineProperty处理;Proxy虽然可以动态对被代理的对象进行属性劫持,但是对Model到
UI这条路径还是无法进行双向数据绑定,因为模板先编译了;所以在真正的Vue.js中需要通过this.$set()设置
动态属性,这样才能做到响应式
*/
function observe (obj) {
var publish = new Publish()
var dataProxy = new Proxy(obj, {
// 在首次编译模板的时候,创建观察者时,触发vm.$data中属性的get方法
get: function(target, property){
// 把观察者放入发布者中,Publish类的属性
if (Publish.target) {
publish.addSubscribe(Publish.target)
}
return target[property]
},
set: function(target, property, value){
if (target[property] === value) {
return
}
target[property] = value
// 通知更新UI
publish.notify()
}
})
return dataProxy;
} // 发布者
function Publish () {
// 保存观察者
this.subscribes = []
}
Publish.prototype = {
constructor: Publish,
// 保存观察者
addSubscribe: function (sub){
// 不存在
if (this.subscribes.indexOf(sub) === -1) {
this.subscribes.push(sub)
}
},
// Model改变了需要更新UI
notify: function () {
this.subscribes.forEach(function(sub) {
sub.update()
})
}
} // 观察者:绑定Model的数据到UI中对应的DOM节点属性中
// 参数vm:Vue对象的实例
// 参数node:需要进行数据绑定的DOM对象
// 参数name:Vue对象$data的属性名称
// 参数type:DOM元素需要设置数据的属性。如:value(input元素),nodeValue(text元素的内容)
function Watcher (vm, node, name, type) {
// 在发布者身上绑定一个当前唯一的观察者对象(类似class中的static,属于类属性)
Publish.target = this
this.vm = vm
this.node = node
this.name = name
this.type = type
this.update() // 关键点
Publish.target = null
} Watcher.prototype = {
constructor: Watcher,
// 把Model中的数据绑定到UI中
update: function(){
// 此处会触发vm对象中的get方法
this.node[this.type] = this.vm.$data[this.name]
}
} function Vue(options){
this.$el = options.el
this.$data = observe(options.data)
// 解析DOM模板,如v-model指令改为input事件,{{xxx}}改为对象中的数据
var fragmentDOM = new CompileTemplate(this.$el, this).getDocumentFragment()
// 更新DOM
document.getElementById(this.$el).appendChild(fragmentDOM)
} var vm = new Vue({
el: "app",
data: {
text: '双向数据绑定'
}
})
</script> </body>
</html>
五、参考的文章:https://blog.csdn.net/tangxiujiang/article/details/79594860
Vue双向数据绑定简易实现的更多相关文章
- vue 双向数据绑定的实现学习(二)- 监听器的实现
废话:上一篇https://www.cnblogs.com/adouwt/p/9928278.html 提到了vue实现的基本实现原理:Object.defineProperty() -数据劫持 和 ...
- vue 双向数据绑定的实现学习(一)
前言:本系列学习笔记从以下几个点展开 什么是双向数据绑定 双向数据绑定的好处 怎么实现双向数据绑定 实现双向数据数据绑定需要哪些知识点 数据劫持 发布订阅模式 先看看我们要实现的目标是什么,如下动图: ...
- vue双向数据绑定的简单实现
vue双向数据绑定的简单实现 参考教程:链接 <!DOCTYPE html> <html lang="en"> <head> <meta ...
- Vue双向数据绑定原理分析(转)
add by zhj: 目前组里使用的是前端技术是jQuery + Bootstrap,后端使用的Django,Flask等,模板是在后端渲染的.前后端没有分离,这种做法有几个缺点 1. 模板一般是由 ...
- angular和vue双向数据绑定
angular和vue双向数据绑定的原理(重点是vue的双向绑定) 我在整理javascript高级程序设计的笔记的时候看到面向对象设计那章,讲到对象属性分为数据属性和访问器属性,我们平时用的js对象 ...
- vue双向数据绑定最最最最最简单直观的例子
vue双向数据绑定最最最最最简单直观的例子 一.总结 一句话总结:双向绑定既不仅model可以影响view的数据,view也可以影响model的数据 view model 数据 1.vue双向数据绑定 ...
- React 事件对象、键盘事件、表单事件、ref获取dom节点、react实现类似Vue双向数据绑定
1.案例实现代码 import React, { Component } from 'react'; /** * 事件对象.键盘事件.表单事件.ref获取dom节点.react实现类似Vue双向数据绑 ...
- 详解 vue 双向数据绑定的原理,并实现一组双向数据绑定
1:vue 双向数据绑定的原理: Object.defineProperty是ES5新增的一个API,其作用是给对象的属性增加更多的控制Object.defineProperty(obj, prop, ...
- vue双向数据绑定原理探究(附demo)
昨天被导师叫去研究了一下vue的双向数据绑定原理...本来以为原理的东西都非常高深,没想到vue的双向绑定真的很好理解啊...自己动手写了一个. 传送门 双向绑定的思想 双向数据绑定的思想就是数据层与 ...
随机推荐
- sql server 的临时表和表变量
临时表 本地临时表 适合开销昂贵 结果集是个非常小的集合 -- Local Temporary Tables IF OBJECT_ID('tempdb.dbo.#MyOrderTotalsByYe ...
- 18 | 为什么这些SQL语句逻辑相同,性能却差异巨大?
在MySQL中,有很多看上去逻辑相同,但性能却差异巨大的SQL语句.对这些语句使用不当的话,就会不经意间导致整个数据库的压力变大. 我今天挑选了三个这样的案例和你分享.希望再遇到相似的问题时,你可以做 ...
- Debian 9.x "stretch" 解决 /etc/rc.local 开机启动问题
由于某些软件并没有增加开启启动的服务,很多时候需要手工添加,一般我们都是推荐添加命令到 /etc/rc.local 文件,但是 Debian 9 默认不带 /etc/rc.local 文件,而 rc. ...
- 四十五.加密与解密 AIDE入侵检测系统 扫描与抓包
一.加密与解密 1.1 常见的加密算法 对称加密:怎么加密,就怎么解密 DES Date Encryption Standard AES Advance Encryption Standard 非对称 ...
- 使用webuploader实现分片上传
这里只写后端的代码,基本的思想就是,前端将文件分片,然后每次访问上传接口的时候,向后端传入参数:当前为第几块文件,和分片总数 下面直接贴代码吧,一些难懂的我大部分都加上注释了: 上传文件实体类: 看得 ...
- php利用webuploader实现超大文件分片上传、断点续传
PHP用超级全局变量数组$_FILES来记录文件上传相关信息的. 1.file_uploads=on/off 是否允许通过http方式上传文件 2.max_execution_time=30 允许脚本 ...
- Android工程的合并
http://www.xuanyusong.com/archives/3395 1.游戏包名( 类似 com.xx.xxx ) Android应用程序只能有一个包名,如果两个游戏包名一样,那么后者安装 ...
- python 监视和控制鼠标键盘的输入(使用pynput 而非pyhook)
百度上搜到的文章大多基于pyhook, pip不能直接安装,托管在sourceForge上的代码仓库也找不到. google上发现可以使用pynput,貌似控制更为简单,而且可以直接使用pip安装 示 ...
- Pytest从测试类外为测试用例动态注入数据
今天Nelly问我Pytest能不能支持从TestClass类外传入参数?从类外批量传入各个test方法需要的参数.因为数据文件可能有很多情况,不方便依次匹配. 然而又必须用类对用例进行归类及复用,数 ...
- linux 查询cpu版本、核心、线程脚本
#!/bin/bash #1.查看物理cpu个数 physical=`cat /proc/cpuinfo |grep 'physical id'|sort -u|wc -l` echo 物理cpu个数 ...