1.简介
熟悉vue的小伙伴应该熟悉vue中的v-model的使用方法,他的作用就是来实心双数据绑定的,那么先在来说明一下双数据绑定的原理
它的底层原理是由Object.defineProperty实现的
2.Object.defineProperty用法
作用:
给一个对象添加或者修改属性,返回一个对象
参数:
参数一:目标对象
参数二:需要修改或添加的属性
参数三:给当前属性的一些特征
接下来我们来简单实现以下
var obj = {age:18} Object.defineProperty(obj,'name',{ value:'val' }) console.log(obj)
这样我们就实现了给对象添加name这个属性值为val
这样我们再次学他的其他一些简单的应用
var obj = {age:18} Object.defineProperty(obj,'name',{ value:'val', writable:false, //设置当前属性是否能被修改 configurable:false, //设置当前属性是否能被删除 enumerable:false, //设置当前属性是否可以被枚举 get(){}, //当属性被获取时触发 set(){} //当属性被修改时触发 })
注意:
当使用set,get方法时候,不能使用value,writable这两个属性
3.如何实现双数据绑定
原理:
我们通过监听一个数据的变化,再将数据变化时的值设置另一个数据上
现在我们依照原理来简单实现以下
<body> <input type="text" id="txt"> <p id='msg'></p> <body>
<script> var obj = {massage:'随便写点'} var oTxt = document.getElementById('txt') var oMsg = document.getElementById('msg') //监听input框的变化 oTxt.addEventListener('input',function(){ obj.massage = oTxt.value }) Object.defineProperty(obj,'massage',{ configurable:true, enumerable:true, set(newText){ //将监听到的值赋给msg oMsg.innerText = newText } }) </script>
我们这样就完成了一个简单的双数据绑定
但是我们还没有看见vue的影子,接下来我们写一个稍微复杂一点的双数据绑定
<div id="app"> <input type="text" id="txt"> <p id="msg"></p> </div>
var oTxt = document.getElementById('txt') var oMsg = document.getElementById('msg') function Vue(options){ this.el = document.querySelector(options.el) this.data = options.data; this.init(this.data) } Vue.prototype = { constructor:Vue, init:function(obj){ Object.keys(obj).forEach((key)=>{ var value = obj[key] Object.defineProperty(obj,key,{ configurable:true, enumerable:true, set(newText){ if(value != newText){ console.log(newText) value = newText; oMsg.innerText = newText } } }) }) } } var vm = new Vue({ el:'#app', data:{ massage:'hello' } }) oTxt.addEventListener('input',function(){ vm.data.massage = this.value })
这样看起来会比较像,但是还是感觉差了点什么
所以我们再写一个最终版的vue双数据绑定
<div id="app"> <input type="text" v-model="message"> <p>{{message}}</p> <p>{{message}}</p> <p>{{message}}</p> <p>{{message}}</p> <p>{{message}}</p> <p>{{message}}</p> <p>{{message}}</p> <input type="text" v-model="name"> <p>{{name}}</p> <p>{{name}}</p> <p>{{name}}</p> <p>{{name}}</p> </div>
function Vue(options){ this.el = document.querySelector(options.el); this.data = options.data; //是数据层和view之间的一个映射关系这里面存放这个需要双数据绑定的元素和当前元素的一些特征 this.viewModel = {}; this.init(this.data); this.eventType(this.el); } Vue.prototype = { constructor:Vue, init:function(obj){ var _this = this; Object.keys(obj).forEach(function(key){ var value = obj[key]; //将当前key值作用的元素一些特征保存在这个数组里面 _this.viewModel[key] = { _directive:[] } console.log(_this.viewModel) Object.defineProperty(obj,key,{ configurable:true, enumerable:true, get:function(){ return value; }, set:function(newValue){ if(newValue!=value){ value = newValue; //数据更新 _this.viewModel[key]._directive.forEach(function(item){ item.update(); }) } } }) }) }, eventType:function(root){ var childs = root.children; console.log(childs) var _this = this; for(var i=0;i<childs.length;i++){ if(childs[i].hasAttribute("v-model") && childs[i].tagName == "INPUT"){ childs[i].addEventListener("input",(function(i){ var attr = childs[i].getAttribute("v-model"); _this.viewModel[attr]._directive.push(new watch( childs[i].tagName, "value", childs[i], _this, attr )) return function(){ //vm.data.message = _this.data[attr] = childs[i].value; } })(i)) } if(childs[i].innerText.replace(/{{|}}/g,"")){ var dataAttr = childs[i].innerText.replace(/{{|}}/g,""); _this.viewModel[dataAttr]._directive.push(new watch( childs[i].tagName, "innerText", childs[i], _this, dataAttr )) } } } } //更新数据的方法 标签的名称 当前元素 当前元素的属性 vue的实例 data属性 function watch(name,exp,el,vm,attr){ this.name = name; this.exp = exp; this.el = el; this.vm = vm; this.attr = attr; this.update(); } watch.prototype.update = function(){ this.el[this.exp] = this.vm.data[this.attr]; } var vm = new Vue({ el:"#app", data:{ message:"123", name:"456" } })