vue中数据响应式怎样实现?一文带你看懂过程
Admin 2021-10-30 群英技术资讯 917 次浏览
这篇文章给大家分享的是vue中数据响应式实现的内容,下文将给大家介绍为何要实现数据响应式及vue中的数据响应,文中示例代码介绍的非常详细,感兴趣的朋友接下来一起跟随小编看看吧。
为什么实现数据响应式
当前vue、react等框架流行。无论是vue、还是react框架大家最初的设计思路都是类似的。都是以数据驱动视图,数据优先。希望能够通过框架减少开发人员直接操作节点,让开发人员能够把更多的精力放在业务上而不是过多的放在操作节点上。另一方面,框架会通过虚拟dom及diff算法提高页面性能。这其中需要数据优先最根本的思路就是实现数据响应式。so,本次来看下如何基于原生实现数据响应式。
vue中的数据响应
vue中会根据数据将数据通过大胡子语法及指令渲染到视图上,这里我们以大胡子语法为例。如下:
<div id="app">
{{message}}
</div>
let vm = new Vue({
el:"#app",
data:{
message:"测试数据"
}
})
setTimeout(()=>{
vm.message = "修改的数据";
},1000)
如上代码,很简单 。vue做了两件事情。一、把message数据初次渲染到视图。二、当message数据改变的时候视图上渲染的message数据同时也会做出响应。以最简单的案例。带着问题来看,通过原生js如何实现??这里为了简化操作便于理解,这里就不去使用虚拟dom。直接操作dom结构。
实现数据初次渲染
根据vue调用方式。定义Vue类来实现各种功能。将初次渲染过程定义成编译compile函数渲染视图。通过传入的配置以及操作dom来实现渲染。大概思路是通过正则查找html 里 #app 作用域内的表达式,然后查找数据做对应的替换即可。具体实现如下:
class Vue {
constructor(options) {
this.opts = options;
this.compile();
}
compile() {
let ele = document.querySelector(this.opts.el);
// 获取所有子节点
let childNodes = ele.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 3) {
// 找到所有的文本节点
let nodeContent = node.textContent;
// 匹配“{{}}”
let reg = /\{\{\s*([^\{\}\s]+)\s*\}\}/g;
if (reg.test(nodeContent)) {
let $1 = RegExp.$1;
// 查找数据替换 “{{}}”
node.textContent = node.textContent.replace(reg, this.opts.data[$1]);
}
}
})
}
}
如上完成了初次渲染,将message数据渲染到了视图上。但是会返现并没对深层次的dom结构做处理也就是如下情况:
<div id="app">
1{{ message }}2
<div>
hello , {{ message }}
</div>
</div>

渲染结果如上
发现结果并没有达到预期。so,需要改下代码,让节点可以深层次查找就可以了。代码如下:
compile() {
let ele = document.querySelector(this.opts.el);
this.compileNodes(ele);
}
compileNodes(ele) {
// 获取所有子节点
let childNodes = ele.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 3) {
// 找到所有的文本节点
let nodeContent = node.textContent;
// 匹配“{{}}”
let reg = /\{\{\s*([^\{\}\s]+)\s*\}\}/g;
if (reg.test(nodeContent)) {
let $1 = RegExp.$1;
// 查找数据替换 “{{}}”
node.textContent = node.textContent.replace(reg, this.opts.data[$1]);
}
} else if (node.nodeType === 1) {
if (node.childNodes.length > 0) {
this.compileNodes(node);
}
}
})
}
上述代码通过递归查找节点 实现深层次节点的渲染工作。如此,就实现了视图的初次渲染。
数据劫持
回过头来看下上面说的第二个问题:当message数据改变的时候视图上渲染的message数据同时也会做出响应。如何实现数据响应式?简而言之就是数据变动影响视图变动?再将问题拆分下 1. 如何知道数据变动了? 2.如何根据数据变动来更改视图?
let obj = {
myname:"张三"
}
Object.defineProperty(obj,'myname',{
configurable:true,
enumerable:true,
get(){
console.log("get.")
return "张三";
},
set(newValue){
console.log("set")
console.log(newValue);
}
})
console.log(obj);
上述代码会发现,通过defineProperty劫持的对象属性下都会有get及set方法。那么当我们获取或者设置数据的时候就能出发对应的get及set 。这样就能拦截数据做后续操作。

还有没有其他方式达到数据劫持的效果呢?ES6中出现了Proxy 代理对象同样也可以达到类似劫持数据的功能。如下代码:
let obj = {
myname:"张三"
}
let newObj = new Proxy(obj,{
get(target,key){
console.log("get...")
return "张三"
},
set(target,name,newValue){
console.log("set...");
return Reflect.set(target,name,newValue);
}
})
两种方式都可以实现数据劫持。proxy功能更加强大,很多方法是defineProperty所不具备的。且proxy直接拦截的是对象而defineProperty拦截的是对象属性。so,可以利用上述方式将data数据做劫持,代码如下:
observe(data){
let keys = Object.keys(data);
keys.forEach(key=>{
let value = data[key];
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
return value;
},
set(newValue){
value = newValue;
}
});
})
}
观察者模式实现数据响应
有了劫持数据方式后,接下来需要实现的就是当修改数据的时候将新数据渲染到视图。如何办到呢?会发现,需要在data设置的时候触发视图的compile编译。二者之间互相影响,此时可以想到利用观察者模式,通过观察者模式让二者产生关联,如下:

图略小,代码也贴上吧。
class Vue extends EventTarget {
constructor(options) {
super();
this.opts = options;
this.observe(this.opts.data);
this.compile();
}
observe(data){
let keys = Object.keys(data);
let _this = this;
keys.forEach(key=>{
let value = data[key];
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
return value;
},
set(newValue){
_this.dispatchEvent(new CustomEvent(key,{
detail:newValue
}));
value = newValue;
}
});
})
}
compile() {
let ele = document.querySelector(this.opts.el);
this.compileNodes(ele);
}
compileNodes(ele) {
// 获取所有子节点
let childNodes = ele.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 3) {
// 找到所有的文本节点
let nodeContent = node.textContent;
// 匹配“{{}}”
let reg = /\{\{\s*([^\{\}\s]+)\s*\}\}/g;
if (reg.test(nodeContent)) {
let $1 = RegExp.$1;
// 查找数据替换 “{{}}”
node.textContent = node.textContent.replace(reg, this.opts.data[$1]);
this.addEventListener($1,e=>{
let oldValue = this.opts.data[$1];
let newValue = e.detail;
let reg = new RegExp(oldValue);
node.textContent = node.textContent.replace(reg,newValue);
})
}
} else if (node.nodeType === 1) {
if (node.childNodes.length > 0) {
this.compileNodes(node);
}
}
})
}
}
如上,成功的通过观察者模式实现了数据的响应。但是会发现data与compile之间需要通过键名来进行关联。如果data数据结构嵌套关系复杂后面会比较难处理。有没有一种方式让二者松解耦呢?这时候可以用发布订阅模式来进行改造。
发布订阅模式改造响应式

还是略小,也还是贴上代码:
class Vue {
constructor(options) {
this.opts = options;
this.observe(this.opts.data);
this.compile();
}
observe(data){
let keys = Object.keys(data);
let _this = this;
keys.forEach(key=>{
let value = data[key];
let dep = new Dep();
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){
if(Dep.target){
dep.addSub(Dep.target);
}
return value;
},
set(newValue){
dep.notify(newValue);
value = newValue;
}
});
})
}
compile() {
let ele = document.querySelector(this.opts.el);
this.compileNodes(ele);
}
compileNodes(ele) {
// 获取所有子节点
let childNodes = ele.childNodes;
childNodes.forEach(node => {
if (node.nodeType === 3) {
// 找到所有的文本节点
let nodeContent = node.textContent;
// 匹配“{{}}”
let reg = /\{\{\s*([^\{\}\s]+)\s*\}\}/g;
if (reg.test(nodeContent)) {
let $1 = RegExp.$1;
// 查找数据替换 “{{}}”
node.textContent = node.textContent.replace(reg, this.opts.data[$1]);
new Watcher(this.opts.data,$1,(newValue)=>{
let oldValue = this.opts.data[$1];
let reg = new RegExp(oldValue);
node.textContent = node.textContent.replace(reg,newValue);
})
}
} else if (node.nodeType === 1) {
if (node.childNodes.length > 0) {
this.compileNodes(node);
}
}
})
}
}
class Dep{
constructor(){
this.subs = [];
}
addSub(sub){
this.subs.push(sub);
}
notify(newValue){
this.subs.forEach(sub=>{
sub.update(newValue);
})
}
}
class Watcher{
constructor(data,key,cb){
Dep.target = this;
data[key];
this.cb = cb;
Dep.target = null;
}
update(newValue){
this.cb(newValue);
}
}
如上代码思路是 针对每个数据会生成一个dep(依赖收集器)在数据get的时候收集watcher,将watcher 添加到dep里保存。数据一旦有改变触发notify发布消息从而影响compile编译更新视图。这个流程也可以参看下图:

如上就完成了视图响应。通过上述代码,我们可以看出实现数据响应两个核心点1.数据劫持。2.观察者和发布订阅。在这我们可以思考一个问题,2个设计模式都是可以实现的但是有什么区别呢?
观察者与发布订阅
这里需要从概念来看

两者之间关系,发布订阅是三者之间关系。发布订阅会多了一个关系器来组织主题和观察者之间的关系。这样做的好处就是松解耦。看上面响应式例子可以看出观察者需要通过事件名称来进行关联。发布订阅定义dep管理器之后data和compile彻底解耦,让二者松散解耦。在处理多层数据结构上发布订阅会更清晰。松解耦能够应对更多变化,把模块之间依赖降到最低。发布订阅广义上是观察者模式。
关于vue中数据响应式实现就介绍到这,上述示例具有一定的借鉴价值,有需要的朋友可以参考学习,希望对大家学习vue框架有帮助,想要了解更多可以继续浏览群英网络其他相关的文章。
文本转载自脚本之家
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍了Vue3.0 自己实现放大镜效果案例讲解,本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
jquery判断当前元素是第几个的方法:1、创建一个HTML示例文件;2、通过jQuery代码“$("ul li").click(function () {var index = $("ul li").index...})”进行判断即可。
怎样用JS写一个加减乘除的计算器?加减乘除的计算器是JavaScript学习中一个比较常见的练习,想要实现这个效果并不困难,接下来我们就一起来了解一下,感兴趣的朋友可以参考下文的代码。
我们经常能在一些网站页面上看到圆圈序号列表,也就是带圆圈的需要,这样的序号列表能增加网页的美观性,那么圆圈序号列表怎样用js实现呢?下面就给大家分享一个JS实现彩色圆圈序号列表的实例。
本文给大家介绍的是函数防抖和函数节流,本文会给大家详细的介绍函数防抖和节流是什么,以及如何实现和使用场景等等,下文还有示例供大家参考,感兴趣的朋友就继续往下看吧。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008