vue双向数据绑定原理图(简易)
双向数据绑定的概念,相信大家都耳熟能详,简单来说,数据变化更新视图,视图变化更新数据。为了实现这一效果,在 Vue 中,采用了 数据劫持结合发布订阅者模式
的方式来实现。
通过 Object.defineProperty()
实现数据劫持,监听数据的变化。
通过 发布者Dep()
订阅者Watcher
实现发布订阅者模式,达到视图与数据之间相互更新的解耦。
关于如何实现一个简单的数据双向绑定,网上有很多例子,我就不列举了。我这里将我理解的双向绑定原理图画了一下:
然后,我分模块贴下代码(代码不是我写的,我也是找了别人的学习)
observer()
function observer(data) {
if(!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(key => {
var dep = new Dep();
var value = data[key];
observer(value);
Object.defineProperty(data, key, {
configurable: true,
enumerable: true,
get() {
if(Dep.target) {
dep.addSub(Dep.target)
}
return value;
},
set(newValue) {
if (value === newValue) {
return;
}
value = newValue;
dep.notify();
},
})
})
}
Dep()
function Dep() {
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
notify: function() {
this.subs.forEach(sub => {
sub.update();
});
}
}
Dep.target = null;
Watcher
function Watcher(vm, exp, cb) {
this.vm = vm;
this.exp = exp;
this.cb = cb;
this.value = this.get();
}
Watcher.prototype = {
get: function() {
Dep.target = this;
var value = this.vm._data;
this.exp.split('.').forEach(key => {
value = value[key]
})
Dep.target = null;
return value;
},
update: function() {
this.run();
},
run: function() {
var value = this.vm._data;
this.exp.split('.').forEach(key => {
value = value[key]
})
if (value !== this.value) {
this.value = value;
this.cb.call(this.vm, value);
}
}
}
Compile
function Compile(el, vm) {
this.el = document.querySelector(el);
this.vm = vm;
this.init();
}
Compile.prototype = {
init: function() {
this.fragment = this.node2Fragment(this.el);
this.compile(this.fragment);
this.el.appendChild(this.fragment);
},
node2Fragment: function(el) {
var fragment = document.createDocumentFragment();
var child = el.firstChild;
while(child) {
fragment.appendChild(child);
child = el.firstChild;
}
return fragment;
},
compile: function(el) {
var childNodes = el.childNodes;
var that = this;
Array.prototype.slice.call(childNodes).forEach(node => {
// if(that.isElementNode(node)) {
// that.compileElement(node)
// }
if (that.isTextNode(node)) {
that.compileText(node)
}
if(node.childNodes && node.childNodes.length) {
that.compile(node)
}
})
},
compileElement: function(node) {
var attributes = node.attributes;
var that = this;
Array.prototype.forEach.call(attributes, function(attr) {
if(that.isDirective(attr)) {
if(that.isModelDirective) {
that.compileModel()
}
if(that.isHtmlDirective) {
that.compileHtml()
}
if(that.isEventDirective) {
that.compileEvnet()
}
}
// if(that.isShortEventDirective(attr)) {
// that.compileEvnet()
// }
});
},
compileText: function(node) {
var that = this;
var reg = /\{\{(.*)\}\}/;
if(reg.test(node.textContent)) {
var exp = reg.exec(node.textContent)[1].trim()
var val = that.vm._data;
exp.split('.').forEach(key => {
val = val[key]
})
that.updateText(node, val);
new Watcher(that.vm, exp, function(value) {
that.updateText(node, value)
})
}
},
compileModel: function() {},
compileHtml: function() {},
compileEvnet: function() {},
updateText: function(node, value) {
node.textContent = value
},
isDirective: function(attr) {
return attr.indexof('v-') === 0;
},
isEventDirective: function(attr) {
return attr.indexof('on:') === 0;
},
isShortEventDirective: function(attr) {
return attr.indexof('@') === 0;
},
isHtmlDirective: function(dir) {
return dir.indexof('html') === 0;
},
isModelDirective: function(dir) {
return dir.indexof('model') === 0;
},
isElementNode: function(node) {
return node.nodeType === 1;
},
isTextNode: function(node) {
return node.nodeType === 3;
}
}
Vue
function Vue(options = {}) {
this.$options = options;
this.$el = document.getElementById(options.el);
this._data = options.data;
let data = this._data;
this._proxyData(options.data);
observer(data);
this.methods = options.methods;
new Compile(options.el, this)
}
Vue.prototype = {
_proxyData: function(data) {
var that = this;
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
configurable: true,
enumerable: false,
get() {
return that._data[key];
},
set(newValue) {
that._data[key] = newValue;
},
})
})
}
}
测试
<div id="app">
<div>
<span>{{ msg }}</span>
<br>
<span>{{ f.name }}</span>
</div>
</div>
var vm = new Vue({
el: '#app',
data: {
msg: '11',
f: {
name: 1
},
dom: '<strong></strong>'
},
methods: {
clickMe() {
console.log(123);
}
}
})
在 浏览器控制台 输入
vm.msg = 333;
vm.f.name = 'xxxxx'
可以看到数据变化了
推荐阅读
webpack 独立打包与缓存
通过 webpack 打包文件,并根据不同环境独立出相应的配置文件,这是我们都会的。但,我们不能满足于能打包出文件就可以了,还应该思考怎么打包出更优的文件。比如,我们都知道浏览器有强缓存与协商缓存,如果能够...
zhangjinpei赞 2阅读 3.4k
Vue中的diff算法
diff算法是一种通过同层的树节点进行比较的高效算法,避免了对树进行逐层搜索遍历,所以时间复杂度只有 O(n)。diff算法的在很多场景下都有应用,例如在 vue 虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较更新时...
款冬赞 27阅读 13.4k评论 7
给我实现一个前端的 Excel 导入和导出功能
前言【负责人 A】:现在报表部分基于接口的 Excel 的导入和导出功能有点慢,前端这边能不能实现一下这个功能,然后我们在比对看看效果!【切图仔 B】: 接口这边不能优化一下吗?比如排查下慢的原因什么的。【负...
熊的猫赞 19阅读 2.6k
vue-property-decorator使用手册
@Component 装饰器可以接收一个对象作为参数,可以在对象中声明 components ,filters,directives等未提供装饰器的选项,也可以声明computed,watch等
似曾相识赞 17阅读 29.1k评论 7
一个开源vue网站博客,nuxt开源网站,前后端分离项目
开媛笔记,基于nuxt ssr首屏服务器端渲染 。用于分享、记录、交流和学习,希望可以帮助到小伙伴们。同时网站在不断更新,创造属于猿(媛)的世界 -$Bao Yalong ..Let's Go! [链接]
jigsaw赞 16阅读 8.4k评论 3
你知道前端水印功能是怎么实现的吗?
前一段时间由于项目需要实现水印功能,于是去了解了相关的内容后,基于 Vue 的实现了一个 v-watermark 指令完成了对应的功能,其实整体内容并不复杂!
熊的猫赞 14阅读 1.7k
2022 你还不会微前端吗 (上) — 从巨石应用到微应用
微前端系列分为 上/下 两篇,本文为 上篇 主要还是了解微前端的由来、概念、作用等,以及基于已有的微前端框架进行实践,并了解微前端的核心功能所在,而在下篇 2022 你还不会微前端吗 (下) — 揭秘微前端核心原理...
熊的猫赞 14阅读 1.6k
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。