JavaScript ECAMScript5 特性get/set访问器 实现json数据与dom对象的绑定
什么是数据绑定
数据绑定是将一个用户界面元素的属性绑定到一个类型/对象实例上的某个属性的方法,实现该元素属性和该对象实例属性在一方数值修改时另一方的数值也随之修改。
百度百科中的数据绑定的举例:如果一个开发者有一个Customer类型的实例,那么他就可以把Customer的“Name”属性绑定到一个TextBox的“Text”属性上。“绑定”了这2个属性之后,对TextBox的Text属性的更改将“传播”到Customer的Name属性,而对Customer的Name属性的更改同样会“传播”到TextBox的Text属性。Windows窗体的简单数据绑定支持绑定到任何public或者internal级别的·NET Framework属性,同样可以利用数据库来简单地绑定页面控件的单个属性。
在此之前做项目的时候做过C#编写的WPF应用程序,对其中的数据绑定有一些应用,感觉非常方便,开发效率很高。
C#WPF数据绑定举例:
先建模型类,告警类:
1 public class CAlarm //告警类 2 { 3 public int iID { get; set; } //告警对象编号 4 public String sTsName { get; set; } //告警对象名称 5 public String sAlarmLevel { get; set; } //告警级别 6 public String sDesc { get; set; } //告警描述 7 public DateTime dAlarmTime { get; set; } //告警时间 8 }
页面布局代码:
1 <Window x:Class="WPFBinding.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="告警列表" Height="350" Width="487"> 5 <Grid Height="311" Width="464"> 6 <ListView Name="listView1" Height="311" Width="464"> 7 <ListView.View> 8 <GridView> 9 <GridViewColumn Header="编号" DisplayMemberBinding="{Binding iID}" Width="50"></GridViewColumn> 10 <GridViewColumn Header="对象名称" DisplayMemberBinding="{Binding sTsName}" Width="100"></GridViewColumn> 11 <GridViewColumn Header="告警级别" DisplayMemberBinding="{Binding sAlarmLevel}" Width="60"></GridViewColumn> 12 <GridViewColumn Header="告警描述" DisplayMemberBinding="{Binding sDesc}" Width="100"></GridViewColumn> 13 <GridViewColumn Header="告警时间" DisplayMemberBinding="{Binding dAlarmTime}" Width="100"></GridViewColumn> 14 </GridView> 15 </ListView.View> 16 </ListView> 17 </Grid> 18 </Window>
然后是MainWindow的代码:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Windows; 6 using System.Windows.Controls; 7 using System.Windows.Data; 8 using System.Windows.Documents; 9 using System.Windows.Input; 10 using System.Windows.Media; 11 using System.Windows.Media.Imaging; 12 using System.Windows.Navigation; 13 using System.Windows.Shapes; 14 using System.Collections.ObjectModel; 15 16 namespace WPFBinding 17 { 18 /// <summary> 19 /// MainWindow.xaml 的交互逻辑 20 /// </summary> 21 public partial class MainWindow : Window 22 { 23 private IList<CAlarm> m_ilAlarms = new ObservableCollection<CAlarm>(); 24 public MainWindow() 25 { 26 DateTime dt = new DateTime(); 27 TimeSpan ts = new TimeSpan(0, 0, 2, 0, 0); 28 29 m_ilAlarms.Add(new CAlarm() { iID = 1, sTsName = "对象1", sAlarmLevel = "一级告警", sDesc = "水浸检测到漏水", dAlarmTime = dt }); 30 m_ilAlarms.Add(new CAlarm() { iID = 2, sTsName = "对象1", sAlarmLevel = "二级告警", sDesc = "水浸检测到漏水", dAlarmTime = dt = dt.Add(ts) }); 31 m_ilAlarms.Add(new CAlarm() { iID = 3, sTsName = "对象2", sAlarmLevel = "一级告警", sDesc = "水浸检测到漏水", dAlarmTime = dt = dt.Add(ts) }); 32 m_ilAlarms.Add(new CAlarm() { iID = 4, sTsName = "对象3", sAlarmLevel = "三级告警", sDesc = "水浸检测到漏水", dAlarmTime = dt = dt.Add(ts) }); 33 m_ilAlarms.Add(new CAlarm() { iID = 5, sTsName = "对象2", sAlarmLevel = "一级告警", sDesc = "水浸检测到漏水", dAlarmTime = dt = dt.Add(ts) }); 34 InitializeComponent(); 35 36 37 this.listView1.ItemsSource = m_ilAlarms; 38 } 39 } 40 41 public class CAlarm //告警类 42 { 43 public int iID { get; set; } //告警对象编号 44 public String sTsName { get; set; } //告警对象名称 45 public String sAlarmLevel { get; set; } //告警级别 46 public String sDesc { get; set; } //告警描述 47 public DateTime dAlarmTime { get; set; } //告警时间 48 } 49 }
运行结果查看绑定:
MainWindow后端代码使用ObservableCollection这个集合,可以双向绑定。
private IList<CAlarm> m_ilAlarms = new ObservableCollection<CAlarm>();是定义一个双向绑定的集合
以下是界面绑定内容
<GridViewColumn Header="编号" DisplayMemberBinding="{Binding iID}" Width="50"></GridViewColumn>
<GridViewColumn Header="对象名称" DisplayMemberBinding="{Binding sTsName}" Width="100"></GridViewColumn>
<GridViewColumn Header="告警级别" DisplayMemberBinding="{Binding sAlarmLevel}" Width="60"></GridViewColumn>
<GridViewColumn Header="告警描述" DisplayMemberBinding="{Binding sDesc}" Width="100"></GridViewColumn>
<GridViewColumn Header="告警时间" DisplayMemberBinding="{Binding dAlarmTime}" Width="100"></GridViewColumn>
使用DisplayMemberBinding="{Binding iID}"的方式绑定
当然WPF中还有其他绑定数据到界面元素的方法和绑定类型,在此不再一一赘述。我想说的是WPF的这种绑定的方式大大提高了开发的效率。
然后在最近的一个web应用的项目中前端和后端分离,使用ajax获取后端json数据,然后拿到数据后再通过js刷新到web页面的dom对象相应的属性中显示在页面上。
js代码写了一大片一大片的。好累人啊。
联想到WPF的开发高效,现在真的好痛苦啊。
我是一个很懒偷奸耍滑的人。怎么办?度娘。果然有现成的:
以下是度娘百科中的内容:
-
AngularJS
编辑
<script src="http://cdn.static.runoob.com/libs/angular.js/1.4.6/angular.min.js"></script>
<p>双向数据绑定:</p> <input type="text" [(ngModel)]="user.name"/> <div [ngStyle]="style1">{{user.name}}</div>
js代码:
//ts代码 user:any = { name:"12345" }
直接搞定。
好高效啊!
我能不能自己写个绑定代码?试试:
先做一个文本框和json对象绑定的试试。
查资料发现es5的特性get/set访问器有希望实现这个双向绑定的功能
上代码:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>setAndgetTest</title> 6 <style> 7 html, 8 body { 9 width: 100%; 10 height: 100%; 11 overflow: hidden; 12 } 13 </style> 14 <script type="text/javascript"> 15 16 //声明一个json对象用来和dom对象绑定 17 var theObj = { 18 student_id: 100001, 19 name: "Leao", 20 age: 31, 21 phoneNmb: "18618328433" 22 }; 23 24 var CBinding = function(obj) { 25 var vals = {}; 26 /* 27 private变量,用来保存对象属性值的集合,只保存基本类型,以json形式保存,比如vals最终可能是这样: 28 { 29 name:"Leao", 30 student_id: 100001, 31 age: 31, 32 phoneNmb: "18618328433" 33 } 34 */ 35 36 var doms = {}; 37 /* 38 private变量,用来保存dom对象的集合,也就是vals的内容跟dom对象的绑定,doms最终可能是这样 39 { 40 Obj:domObj, //domObj是name绑定的dom对象 41 protertyName:"value" //protertyName是name在dom对象domObj上绑定的属性名称 42 } 43 44 其实可以把上面的vals修改一下: 45 name: "Leao", 46 age: 31, 47 phoneNmb: "18618328433" 48 { 49 name:{ 50 name: "name1", 51 bindingObj:{ 52 Obj:domObj, 53 protertyName:"value" 54 } 55 }, 56 student_id: 100001,//没有绑定 57 age: 31,//没有绑定 58 phoneNmb: "18618328433"//没有绑定 59 } 60 */ 61 62 this.bInit = false;//是否被初始化 63 64 if (null != obj) { 65 var names = Object.getOwnPropertyNames(obj);//获取obj的所有属性名 66 var sFieldName = ""; 67 68 if (null != names && 0 < names.length) { 69 var iIndex = 0, iCount = names.length, val = null; 70 71 for (iIndex = 0; iIndex < iCount; iIndex++) { 72 sFieldName = names[iIndex]; 73 val = obj[sFieldName]; 74 vals[sFieldName] = val; 75 doms[sFieldName] = { 76 Obj: {}, //保存dom对象 77 bSet: false, //是否被绑定 78 protertyName: "" //dom对象的属性名称 79 }; 80 81 Object.defineProperty(this, sFieldName, { 82 value: val, 83 configurable: true, //能否使用delete、能否需改属性特性、或能否修改访问器属性、,false为不可重新定义,默认值为true 84 enumerable: true, //对象属性是否可通过for-in循环,flase为不可循环,默认值为true 85 writable: true, //对象属性是否可修改,flase为不可修改,默认值为true 86 }); 87 } 88 } 89 } 90 91 this.bInit = true;//被初始化 92 93 /* 94 绑定方法, 95 @domObj,绑定的dom对象 96 */ 97 this._binding = function(domObj) {// 98 if (null != domObj) { 99 var sName = domObj.name || domObj.id;//绑定按照name或id进行匹配 100 101 if (null != sName && 0 < sName.length) { 102 var theVal = vals[sName]; 103 var theDom = doms[sName]; 104 105 if (null != theVal && null != theDom) { //查看是存在sName的的值和绑定对象 106 var fieldName = "value"; //绑定的dom对象属性名称,此处只实现了value以后可以在本方法中添加一个参数用来表示属性名称 107 108 theDom.Obj = domObj; //保存绑定的dom对象引用/指针 109 theDom.bSet = true; //设置绑定标志 110 theDom.protertyName = fieldName; 111 theDom.Obj[fieldName] = theVal; //直接赋值 112 113 Object.defineProperty(this, sName, { 114 get: function() { //重写读取访问器 115 116 var theDom = doms[sName]; 117 var reValue = null; 118 119 console.log("get:"); 120 console.log(this); 121 if(theDom.bSet){ 122 reValue = theDom.Obj[fieldName];//直接读取dom对象的fieldName属性值 123 } 124 else{ 125 reValue = vals[sName]; //未绑定使用保存值 126 console.log(sName + "属性未绑定,使用保存值"); 127 } 128 return reValue; 129 }, 130 set: function(val) {//重写设置访问器 131 var theDom = doms[sName]; 132 133 vals[sName] = val;//保存值 134 console.log("set:"); 135 console.log(this); 136 if(theDom.bSet){ 137 theDom.Obj[fieldName] = val;//直接设置dom对象的fieldName属性值 138 } 139 else{ 140 console.log(sName + "属性未绑定,使用保存值"); 141 } 142 } 143 }); 144 } 145 } 146 } 147 } 148 } 149 150 CBinding.prototype.binding = function(domObj) { //定义绑定原型方法 151 this._binding(domObj);//调用定义的方法 152 }; 153 154 function bindingObj(domObj, dataObj) { //dom对象和json对象绑定,返回新的绑定对象 155 var rObj = null; 156 if (null != domObj && null != dataObj) { 157 if (null == dataObj.bInit) { 158 rObj = new CBinding(dataObj); 159 } else { 160 rObj = dataObj; 161 } 162 rObj.binding(domObj); 163 } 164 165 return rObj; 166 } 167 168 function bd() { 169 var domName = document.getElementById("name"); 170 var domAge = document.getElementById("age"); 171 172 console.log("原始json对象:"); 173 console.log(theObj); 174 175 theObj = bindingObj(domName, theObj); //绑定name,把原来的json替换为CBinding对象 176 bindingObj(domAge, theObj); //绑定age 177 178 console.log("绑定后json对象:"); 179 console.log(theObj); 180 181 console.log("获取name和age:"); 182 console.log(theObj.name); 183 console.log(theObj.age); 184 185 //开始更改name和age 186 console.log("开始更改name和age:"); 187 theObj.name = "testName"; 188 theObj.age = 35; 189 190 console.log("获取更改后的name和age:"); 191 console.log(theObj.name); 192 console.log(theObj.age); 193 194 console.log("测试是否能访问到CBinding的private变量vals:"); 195 console.log(theObj.vals);//测试是否能访问到CBinding的private变量vals 196 } 197 198 function setvalue() { 199 200 var domName = document.getElementById("name1"); 201 var domAge = document.getElementById("age1"); 202 203 theObj.name = domName.value; //测试setter 204 theObj.age = domAge.value; //测试setter 205 } 206 207 function getvalue(){ 208 209 var domName = document.getElementById("name2"); 210 var domAge = document.getElementById("age2"); 211 212 domName.value = theObj.name; //测试getter 213 domAge.value = theObj.age; //测试getter 214 } 215 </script> 216 </head> 217 218 <body onload="bd()"> 219 <table> 220 <tr> 221 <td>姓名</td> 222 <td><input id="name" /></td> 223 </tr> 224 <tr> 225 <td>年龄</td> 226 <td><input id="age" /></td> 227 </tr> 228 <tr> 229 <td>学号</td> 230 <td><input id="student_id" /></td> 231 </tr> 232 <tr> 233 <td>电话号码</td> 234 <td><input id="phoneNmb" /></td> 235 </tr> 236 <tr> 237 <td>设置姓名</td> 238 <td><input id="name1" /></td> 239 </tr> 240 <tr> 241 <td>设置年龄</td> 242 <td><input id="age1" /></td> 243 </tr> 244 <tr> 245 <td colspan="2"><button onclick="setvalue()">设置</button></td> 246 </tr> 247 <tr> 248 <td>读取姓名</td> 249 <td><input id="name2" readonly /></td> 250 </tr> 251 <tr> 252 <td>读取年龄</td> 253 <td><input id="age2" /></td> 254 </tr> 255 <tr> 256 <td colspan="2"><button onclick="getvalue()">读取</button></td> 257 </tr> 258 </table> 259 </body> 260 261 </html>
把上面的代码保存为htm文件在浏览器中打开画面:
浏览器控制台打印画面:
下面是json对象没有绑定时在控制台打印出来的内容:
能看到其每个成员的值。
绑定后发现绑定的值(只绑定了name和age)看不见了。
获取name和age控制台打印:
前端设置name和age被绑定的两个文本框内容也跟着修改:
点击读取按钮,能获取到name和age
以上是我使用ECAMScript5 特性get/set访问器实现的json数据绑定到dom对象一个实现。还有待于改进。
当然了还是使用AngularJS来的高效,毕竟那么多年了,AngularJS已经很成熟了,大家都在用。我在这里只是从另一个角度来思考一下能否实现json和dom绑定的解决思路。
还希望技术大牛看到后多提宝贵意见和建议。