这篇文章给大家分享的是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.如何根据数据变动来更改视图?
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;
}
});
})
}
观察者模式实现数据响应
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;
}
});
})
}
大型站长资讯类网站! https://www.0833zz.com