前提: 因为大量使用vue,趁项目比较闲的时候整理一下vue最核心的双向绑定的原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的
项目演示地址: 请点击
首先需要明白 Object.defineProperty() :对js中的对象进行属性添加和属性修改
例
// 定义一个对象
var O={}
// 为对象添加attr属性
Object.defineProperty(O,'attr',{
set:function(value){
attr=value
},
get:function(){
return attr
}
})
//通过set方法设置attr属性
O.attr='我的名字叫做安'
console.log(O.attr)
首先:我们需要将vue data里面的数据的key全部设置成data对象的属性然后为这些属性设置Set ,Get方法
遍历vue.data
//将data中的数据绑定为vue的属性,并为其附加get,set方法
Object.keys(this.data).forEach(function (key) {
that.observe(that.data,key,that.data[key])
})
newVue.prototype={
observe:function(d,k,v){
var that = this
Object.defineProperty(d,k,{
get:function(){
return v
},
set:function(newValue){
if (newValue === v) {
return;
}
v=newValue
that.map.update(k,v)
}
})
},
这个时候data中所有数据的key都注册成vue.data的属性,可以通过get方法获取属性值,并通过修改其属性来触发set函数中的逻辑
然后遍历所有的节点解析指令
nodeMap:function(el){
var childNodes= el.childNodes;
var that = this;
[].slice.call(childNodes).forEach(function(node) {
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;
//遍历节点获取所有节点属性
if(typeof node.attributes !='undefined'){
let attributes=node.attributes;
//遍历节点属性
[].slice.call(attributes).forEach(function (attr) {
console.log(attr.name)
if(attr.name==='v-model'){
node.addEventListener('input', function(e) {
var newValue = e.target.value;
var val=that.data[attr.value]
if (val === newValue) {
return;
}
that.data[attr.value] = newValue;
val = newValue;
});
}
})
}
//如果这个阶段内有{{}},将data内的值与{{}}内的值关联起来
if(reg.test(text)){
//方法用于检索字符串中的正则表达式的匹配
var value= reg.exec(text)[1]
//将data中的数据更新到页面
node.textContent = typeof that.data[value]== 'undefined' ? '' : that.data[value];
//将model对象保存
that.map.add({value,node})
console.log(that.map)
}
})
}
最后创建一个容器,收集所有的订阅器,当data中的数据发生改变时遍历订阅器即可
function modelMap() {
this.list = []
}
modelMap.prototype={
add:function(s){
this.list.push(s)
},
//遍历model对象
update:function(key,value){
this.list.forEach(function (s) {
let val= s.node.textContent
if(key === s.value&&value !==val){
s.node.textContent=value
}
})
}
}
最后将以上组装到vue对象中,即可通过vue对象管理dom和数据
var el = document.querySelector("#vm")
function newVue(s) {
this.data=s.data
this.map=new modelMap()
this.nodeMap(s.el)
var that = this
//将data中的数据绑定为vue的属性,并为其附加get,set方法
Object.keys(this.data).forEach(function (key) {
that.observe(that.data,key,that.data[key])
})
}
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="text/javascript">
</script>
</head>
<body>
<div id="vm">
<div style="height: 50px;width: 200px;border: dashed 1px gray">
{{test}}
</div>
<input v-model="test">
</div>
</body>
<script type="text/javascript">
var el = document.querySelector("#vm")
function newVue(s) {
this.data=s.data
this.map=new modelMap()
this.nodeMap(s.el)
var that = this
//将data中的数据绑定为vue的属性,并为其附加get,set方法
Object.keys(this.data).forEach(function (key) {
that.observe(that.data,key,that.data[key])
})
}
newVue.prototype={
observe:function(d,k,v){
var that = this
Object.defineProperty(d,k,{
get:function(){
return v
},
set:function(newValue){
if (newValue === v) {
return;
}
v=newValue
that.map.update(k,v)
}
})
},
nodeMap:function(el){
var childNodes= el.childNodes;
var that = this;
[].slice.call(childNodes).forEach(function(node) {
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;
//遍历节点获取所有节点属性
if(typeof node.attributes !='undefined'){
let attributes=node.attributes;
//遍历节点属性
[].slice.call(attributes).forEach(function (attr) {
console.log(attr.name)
if(attr.name==='v-model'){
node.addEventListener('input', function(e) {
var newValue = e.target.value;
var val=that.data[attr.value]
if (val === newValue) {
return;
}
that.data[attr.value] = newValue;
val = newValue;
});
}
})
}
//如果这个阶段内有{{}},将data内的值与{{}}内的值关联起来
if(reg.test(text)){
//方法用于检索字符串中的正则表达式的匹配
var value= reg.exec(text)[1]
//将data中的数据更新到页面
node.textContent = typeof that.data[value]== 'undefined' ? '' : that.data[value];
//将model对象保存
that.map.add({value,node})
console.log(that.map)
}
})
}
}
function modelMap() {
this.list = []
}
modelMap.prototype={
add:function(s){
this.list.push(s)
},
//遍历model对象
update:function(key,value){
this.list.forEach(function (s) {
let val= s.node.textContent
if(key === s.value&&value !==val){
s.node.textContent=value
}
})
}
}
var vue = new newVue({
data:{
test:''
},
el:el
})
console.log(vue)
// vue.test=321
/* function nodeMap(el,vue){
this.vue=vue
this.map(el)
}
nodeMap.prototype={
map:function (el) {
var childNodes = el.childNodes
var that = this
Array.prototype.slice.call(childNodes).forEach(function (node) {
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;
if(reg.test(text)){
//方法用于检索字符串中的正则表达式的匹配
console.log( reg.exec(text)[1])
}
})
}
}*//* function nodeMap(el,vue){
this.vue=vue
this.map(el)
}
nodeMap.prototype={
map:function (el) {
var childNodes = el.childNodes
var that = this
Array.prototype.slice.call(childNodes).forEach(function (node) {
var reg = /\{\{(.*)\}\}/;
var text = node.textContent;
if(reg.test(text)){
//方法用于检索字符串中的正则表达式的匹配
console.log( reg.exec(text)[1])
}
})
}
}*/
// var n = new nodeMap(el)
</script>
</html>