之前发不过一篇 Vue基于Object.defineProperty的双向绑定,今天又从新深入了解了一下
- 首先通过Object.defineProperty来劫持数据,
- 在编写的代码中封装了Observe函数来实现,可能数据有多层,所以会被多次调用,此函数主要是为了:观察对象,给对象增加Object.defindProperty
- 封装了Compile模板编译函数,来对数据进行编译。创建了文档碎片:document.createDocumentFragment()
createdocumentfragment()方法创建了一虚拟的节点对象,节点对象包含所有属性和方法。当你想提取文档的一部分,改变,增加,或删除某些内容及插入到文档末尾可以使用createDocumentFragment() 方法。
你也可以使用文档的文档对象来执行这些变化,但要防止文件结构被破坏,createDocumentFragment() 方法可以更安全改变文档的结构及节点。
在编译模板中通过正则/\{\{(.*)\}\}/
来查找{{}},根据文本节点,元素节点进行不同的编译模式,后面打算详细介绍一下, - 然后就是发布订阅模式,这里用了
Dep()
及Watcher(vm,exp,fn)
实现
-computed的实现主要是通过Object.keys()
它可以将{name:1,age:2}=>[name,age] 将对象转化为数组
具体的功能实现看具体的代码吧
js文件名为 mvvm.js,先进行导入
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<p>{{a.a}}</p>
<p>b的值是{{b}}</p>
<div>
<input type="text" v-mode='b'>
</div>
{{hello}}
</div>
<script src="./mvvm.js"></script>
<script>
let vue=new Vue({
el:'#app',
data:{a:{a:'是a'},b:'hhaha',c:'oK'} ,//Object.defineProperty
//computed: 可以缓存,只是把数据挂载在了vm上
computed: {
hello(){
return this.b+this.c
}
},
})
</script>
</body>
</html>
function Vue(options={}){
this.$options=options ; //将所有属性挂载在$options
// this._data
var data=this._data=this.$options.data;
observe(data);
//this 代理了 this._data
for(let key in data){ //使得通过this可直接访问
Object.defineProperty(this,key,{
enumerable:true,
get(){
return this._data[key] ;//this.a={a:1}
},
set(newval){
this._data[key]=newval
}
})
}
initComputed.call(this)
new Compile(options.el,this)
}
//computed的实现
function initComputed(){
let vm=this;
let computed= this.$options.computed; //Object.keys方法 {name:1,age:2}=>[name,age] 将对象转化为数组
Object.keys(computed).forEach(function(key){
Object.defineProperty(vm,key,{ //computed[key]
get:typeof computed[key] === "function"?computed[key]:computed[key].get,
set(){
}
})
})
}
function Compile(el,vm){
// el,表示替换的范围
vm.$el=document.querySelector(el);
//创建文档碎片
let fragment= document.createDocumentFragment();
while(child=vm.$el.firstChild){
//将app中的内容塞进内存之中
fragment.appendChild(child);
}
replace(fragment)
function replace(fragment){
Array.from(fragment.childNodes).forEach(function(node){
let text =node.textContent;
let reg=/\{\{(.*)\}\}/;
//如果是文本节点
if(node.nodeType===3 && reg.test(text)){
console.log(RegExp.$1); // a.a
// console.log(RegExp.$1,'哈哈哈');
let arr= RegExp.$1.split('.') //[a,a];
let val=vm;
arr.forEach(function(k){ //取this.a.a
val=val[k]
});
new Watcher(vm,RegExp.$1,function(newvla){ //函数里需要接受一个新的值
node.textContent=text.replace(/\{\{(.*)\}\}/,newvla)
})
node.textContent=text.replace(/\{\{(.*)\}\}/,val)
}
if(node.nodeType===1){
//元素节点
let nodeAttrs=node.attributes;//获取当前dom节点的属性
Array.from(nodeAttrs).forEach(function(attr){
let name=attr.name; //type="text"
let exp=attr.value; //v-mode="b"
if(name.indexOf('v-')==0){ //v-model
node.value=vm[exp]
}
new Watcher(vm,exp,function(newVal){
node.value=newVal; //当watcher触发时会自动将内容放入到输入框内
});
node.addEventListener('input',function(e){
let newVal=e.target.value;
vm[exp]=newVal;
})
})
}
if(node.childNodes){
replace(node)
}
})
}
// 拿到每一个节点
//转换为数组
vm.$el.appendChild(fragment)
}
function Observe(data){ //这里写主要逻辑
let dep=new Dep()
for(let key in data){ //把data属性通过Object.definePropertyd的方式 定义属性
let val=data[key];
observe(val)
Object.defineProperty(data,key,{
enumerable:true, //可枚举
get(){
Dep.target&&dep.addSub(Dep.target) //[watcher]
return val
},
set(newval){ //更改值得时候
if(newval===val){ //设置的值和以前的一样是
return
}
val=newval; //如果以后再获取值得时候,将刚才设置的值再丢回去
observe(newval)
dep.notify(); //让所有的watch 的update方法执行即可
}
})
}
}
//观察对象,给对象增加Object.defindProperty
function observe(data){
if(typeof data !=="object")return
return new Observe(data)
}
// Vue的特点不能新增不存在的属性 不存在的属性没有get和set
// 深度响应 因为每次赋予一个新对象时会给这个新对象增加数据劫持
//发布订阅
function Dep(){
this.subs=[];
}
Dep.prototype.addSub=function(sub){ //订阅
this.subs.push(sub)
}
Dep.prototype.notify=function(){ //通知
this.subs.forEach(sub=>sub.update())
}
//vm,当前实例
function Watcher(vm,exp,fn){ //Watcher是一个类,通过这个类创建的实例都拥有update方法
this.fn=fn;
this.vm=vm;
this.exp=exp; // 添加到订阅中
Dep.target=this;
let val=vm;
let arr=exp.split('.');
arr.forEach(function(k){ //this.a.a
val=val[k]
})
Dep.target=null;
}
Watcher.prototype.update=function(){
let val=this.vm;
let arr=this.exp.split('.');
arr.forEach(function(k){
val=val[k]
})
this.fn(val) //newval
}