WEB前端程序员的主要工作是实现数据到HTML5视图的绑定和控制视图和数据之间的切换,DOM在其中扮演了重要的角色,不过DOM代码也是程序员的噩梦,大量的DOM繁琐而丑陋,能不能把视图的状态信息保存在数据里,并且使用数据驱动的方式来操作视图呢?下面就是一个关于这方面的实现,特点:延迟响应,数据驱动,简而言之就是通过模型的改变控制视图的改变,从模型里获取数据就是从视图获取最新的人机交互数据。看代码:
1 "use strict"; 2 3 //定义变量 4 var JXR; 5 //闭包 6 (function () { 7 Function.prototype.f = function (name, fun) { 8 this.prototype[name] = fun; 9 }; 10 11 //定义只读属性 12 Object.prototype.readonly = function (property, value) { 13 Object.defineProperty(this, property, { 14 configurable: false, 15 enumeratable: false, 16 writable: false, 17 value: value 18 }); 19 }; 20 21 //定义读写属性 22 Object.prototype.readwrite = function (property, value) { 23 Object.defineProperty(this, property, { 24 configurable: false, 25 enumeratable: false, 26 writable: true, 27 value: value 28 }); 29 }; 30 31 //定义读写器属性 32 Object.prototype.setget = function (property, set, get) { 33 Object.defineProperty(this, property, { 34 configurable: false, 35 enumeratable: true, 36 set: set, 37 get: get 38 }); 39 }; 40 41 //定义读写器属性 42 Object.prototype.getset = function (property, get, set) { 43 Object.defineProperty(this, property, { 44 configurable: false, 45 enumeratable: true, 46 set: set, 47 get: get 48 }); 49 }; 50 51 //bind函数 52 Function.prototype.bind = Function.prototype.bind || function (context) { 53 //外部参数 54 var outterArgs = Array.prototype.slice.call(arguments, 1); 55 var self = this; 56 return function () { 57 //内部参数:在Html事件就是event对象 58 var innerArgs = Array.prototype.slice.call(arguments); 59 //最终参数:最终的事件事件函数的参数 60 var finalArgs = outterArgs.concat(innerArgs); 61 return self.apply(context, finalArgs); 62 } 63 }; 64 65 //对象浅复制 66 Object.create = Object.create || function (object, properties) { 67 var F = function () { 68 }; 69 F.prototype = object; 70 var newObj = new F(); 71 //定义新属性 72 this.defineProperties(newObj, properties); 73 return newObj; 74 }; 75 76 //在函数原型里绑定一个extend方法,用于类式继承 77 Function.prototype.extend = function (parent) { 78 //超类的原型拷贝 79 var clone = Object.create(parent.prototype); 80 this.prototype = clone; 81 //修改构造器 82 this.prototype.constructor = this; 83 84 }; 85 /*判断对象的类型*/ 86 Object.prototype.is = function (constructor) { 87 return this.constructor === constructor; 88 }; 89 90 /*是否原生类型*/ 91 Object.primitive = function (val) { 92 return typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || typeof val === 'undefined'; 93 }; 94 95 /*对象是否拥有属性*/ 96 Object.prototype.has = function (property) { 97 return property in this; 98 }; 99 100 /*判空*/ 101 Object.empty = function (val) { 102 return val === undefined || val === null; 103 }; 104 105 /*字符串到原生类型的转换*/ 106 String.prototype.to = function (type) { 107 switch (type) { 108 case 'number': 109 return Number(this); 110 case 'boolean': 111 return Boolean(this); 112 default: 113 return this; 114 } 115 }; 116 117 })(); 118 119 /* 120 DOM的扩展 121 * */ 122 (function () { 123 /*判断是否空白文本节点*/ 124 Node.f('blank', function () { 125 return this.nodeType === Node.TEXT_NODE && (Object.empty(this.data) || this.data.trim() === ''); 126 }); 127 128 /*属性结点的数据绑定 129 绑定一个简单数据类型 130 * */ 131 function AttrNode(el, attr, data) { 132 var ret = parse(attr.value, data); 133 if (Object.empty(ret.obj) || Object.empty(ret.name)) 134 return; 135 if (Object.empty(ret.value)) 136 ret.value = ''; 137 //绑定到属性值 138 attr.value = String(ret.value); 139 attr[JXR.TYPE_PRIMITIVE] = typeof ret.value; 140 ret.obj.setget(ret.name, function (v) { 141 //保存类型 142 attr[JXR.TYPE_PRIMITIVE] = typeof v; 143 attr.value = String(v); 144 }, function () { 145 var type = attr[JXR.TYPE_PRIMITIVE]; 146 //原生属性 147 if (el[attr.name]) 148 return el[attr.name].to(type); 149 else 150 //自定义属性 151 return attr.value.to(type); 152 153 }); 154 } 155 156 /*文本结点的数据绑定 157 绑定一个简单数据类型 158 * */ 159 function TextNode(node, data) { 160 var text=node.data.trim(); 161 //脚本节点 162 if(/\$(\S+)\$/gi.test(text)) 163 { 164 node.data=eval(JXR.DATA+'.'+/\$(\S+)\$/gi.exec(text)[1]); 165 } 166 else if(isExp(text)) 167 { 168 var ret = parse(text, data); 169 if (Object.empty(ret.obj) || Object.empty(ret.name)) 170 return; 171 if (Object.empty(ret.value)) 172 ret.value = ''; 173 //绑定到文本 174 node.data = String(ret.value); 175 node[JXR.TYPE_PRIMITIVE] = typeof ret.value; 176 177 ret.obj.setget(ret.name, function (v) { 178 //保存类型 179 node[JXR.TYPE_PRIMITIVE] = typeof v; 180 node.data = String(v); 181 }, function () { 182 var type = node[JXR.TYPE_PRIMITIVE]; 183 return node.data.trim().to(type); 184 }); 185 186 } 187 188 } 189 190 /* 191 *主类:参数obj={ 192 * el:HTMLElement || string(css selector), 193 * data:Object (数据), 194 * on:Object (事件函数) 195 * } 196 * 197 * */ 198 JXR = function () { 199 200 if (arguments.length == 1 && arguments[0].constructor == Object) { 201 //参数 202 var obj = arguments[0]; 203 this.readwrite('el',obj.el instanceof HTMLElement ? obj.el : document.querySelector(obj.el)); 204 this.el.normalize(); 205 //保存父节点 206 this.readonly('parent',this.el.parentElement); 207 //保存模板 208 this.readonly('_el',this.el.cloneNode(true)); 209 //数据 210 this._data = obj.data; 211 //复制事件对象 212 this.on=obj.on; 213 bind(this.el, this._data, this); 214 var self=this; 215 this.setget(JXR.DATA,function (v) { 216 217 if (Object.empty(v)) { 218 this.parent.removeChild(this.el); 219 } 220 /*else if(v===this._data) 221 { 222 alert('set'); 223 set(this._data,v); 224 225 }*/ 226 else { 227 self.on=clearEvents(self.on); 228 var newChild = this._el.cloneNode(true); 229 bind(newChild, v, self); 230 this.parent.replaceChild(newChild, this.el); 231 //清除事件句柄 232 clearEvents(this.el); 233 this.el = newChild; 234 } 235 this._data = v; 236 },function () { 237 alert('get'); 238 return this._data; 239 }); 240 241 } 242 else { 243 throw new Error('参数必须是对象.'); 244 } 245 }; 246 247 /* 248 清除事件 249 */ 250 function clearEvents(el) 251 { 252 //所有属性 253 var attrs=el.attributes; 254 for(var i=0;i<attrs.length;i++) 255 { 256 var attr = attrs[i]; 257 var name = attr.name; 258 var value = attr.value; 259 if(/on:(\S+)/gi.test(name)) 260 { 261 //获取事件类型 262 var type=/on:(\S+)/gi.exec(name)[1]; 263 el[JXR.EVENT_ATTR_PREFIX+type]=null 264 } 265 } 266 //所有子元素 267 var children=el.children; 268 for(var i=0;i<children.length;i++) 269 { 270 clearEvents(children[i]) 271 } 272 } 273 274 /*实例方法 275 更新UI 276 * */ 277 JXR.f('update', function (data) { 278 this.data = data || this._data; 279 }); 280 281 /* 282 实例方法,获取最新的界面数据 283 */ 284 JXR.f('getValue', function (data) { 285 return get(data || this.data); 286 }); 287 288 /* 289 工具函数,绑定数据到视图 290 */ 291 JXR.bind = function (view, obj) { 292 var domParser = new DOMParser(); 293 var el = domParser.parseFromString(view, 'text/html').body.firstElementChild; 294 return new JXR({ 295 el:el, 296 data:obj.data, 297 on:obj.on 298 }); 299 }; 300 301 /* 302 *工具函数:获取对象的拷贝,里面是最新的从界面收集到的数据 303 * */ 304 function get(obj) { 305 var ret = null; 306 if (typeof obj === 'object') { 307 if (Array.isArray(obj)) 308 ret = []; 309 else 310 ret = {}; 311 for (var i in obj) { 312 var val = obj[i]; 313 if (!obj.hasOwnProperty(i) || Object.prototype.has(i) || typeof val === 'function' || i === JXR.EVENT_ATTR_PREFIX) { 314 continue; 315 } 316 else if (typeof val === 'object') { 317 ret[i] = get(val); 318 } 319 else if (Object.primitive(val)) { 320 ret[i] = val; 321 } 322 } 323 } 324 return ret; 325 326 }; 327 328 function set(obj, data) { 329 if (typeof obj === 'object') { 330 for (var i in data) { 331 var val = data[i]; 332 //没有i属性 333 if(!obj.has(i)) 334 { 335 throw new Error('"+i+"属性不存在!'); 336 } 337 else if (typeof val === 'object') { 338 set(obj[i], val); 339 } 340 else if (Object.primitive(val)) { 341 obj[i] = val; 342 } 343 } 344 } 345 } 346 347 /*工具函数*/ 348 JXR.toString = function d(value) { 349 var ret = ''; 350 if (typeof value === 'object') { 351 ret += Array.isArray(value) ? '[' : '{'; 352 for (var i in value) { 353 if (!value.hasOwnProperty(i)) 354 continue; 355 var val = value[i]; 356 if (typeof val === 'object') { 357 if (Array.isArray(value)) 358 ret += d(val) + ','; 359 else 360 ret += i + ':' + d(val) + ','; 361 } 362 else if (Object.primitive(val)) { 363 if (typeof val === 'string') 364 ret += i + ':"' + val + '",'; 365 else 366 ret += i + ':' + val + ','; 367 } 368 369 } 370 if (ret.lastIndexOf(',') > 0) 371 ret = ret.substring(0, ret.length - 1); 372 ret += Array.isArray(value) ? ']' : '}'; 373 } 374 return ret; 375 376 }; 377 378 /*常量的定义*/ 379 JXR.EXP = '$'; 380 JXR.DATA = 'data'; 381 JXR.FOR_ATTR = 'jxr:for'; 382 JXR.IF_ATTR = 'jxr:if'; 383 JXR.DATA_BIND_ATTR = 'jxr:bind'; 384 JXR.EVENT_ATTR_PREFIX = 'on'; 385 JXR.TYPE_PRIMITIVE = 'primitive-type'; 386 JXR.SCRIPT='jxr:script'; 387 388 389 /*工具函数,处理data:bind属性*/ 390 function DataBind(el, attr, data, self) { 391 var ret = parse(attr.value, data); 392 if (Object.empty(ret.obj) || Object.empty(ret.name)) 393 return null; 394 if (Object.empty(ret.value)) { 395 //移除该元素节点 396 el.parentElement.removeChild(el); 397 398 } 399 return ret.value; 400 401 } 402 403 /*工具函数,处理for控制*/ 404 function For(el, attr, data, self) { 405 var ret = parse(attr.value, data); 406 if (Object.empty(ret.obj) || Object.empty(ret.name)) 407 return null; 408 else if (!Object.empty(ret.value)) { 409 if(typeof ret.value==='number') 410 { 411 var loop=ret.value; 412 } 413 else if(!Array.isArray(ret.value)) 414 { 415 throw new Error('for循环必须绑定一个数组或者一个数字.'); 416 } 417 418 } 419 420 //数组长度 421 var size=0; 422 //清空 423 if(Object.empty(ret.value)) 424 { 425 size=0; 426 } 427 //简单循环 428 else if(loop) 429 { 430 size=loop; 431 } 432 //集合循环 433 else 434 { 435 size=ret.value.length; 436 } 437 //要循环的子元素,目前只支持一个子元素 438 var child = el.firstElementChild; 439 el.removeChild(child); 440 //循环 441 for (var i = 0; i < size; i++) { 442 var clone = child.cloneNode(true); 443 //是个简单循环 444 if(loop) 445 { 446 var val={index:i,count:i+1}; 447 } 448 else 449 { 450 var val=ret.value[i]; 451 } 452 //追加循环节点 453 el.appendChild(clone); 454 bind(clone,val,self); 455 } 456 return ret.value; 457 } 458 459 /*工具函数,处理if控制*/ 460 function If(el, attr, data) { 461 var ret = parse(attr.value, data); 462 if (Object.empty(ret.obj) || Object.empty(ret.value) || Object.empty(ret.name)) 463 return null; 464 else if(typeof ret.value!=='boolean') 465 { 466 throw new Error('if:test值必须是boolean.'); 467 } 468 //绑定到属性 469 attr.value = ret.value; 470 //重新定义成读写器属性 471 if (!ret.obj.has(ret.name)) { 472 ret.obj.setget(ret.name,function (v) { 473 attr.value = String(v); 474 //控制元素的隐藏和显示 475 if (!v) { 476 //元素隐藏 477 el.style.display = 'none'; 478 } 479 },function () { 480 return Boolean(attr.value); 481 }); 482 483 } 484 485 if (!ret.value) { 486 //元素隐藏 487 el.style.display = 'none'; 488 } 489 return true; 490 } 491 492 /*工具函数:判断是否是一个带$的数据绑定*/ 493 function isExp(text) { 494 return text.trim().charAt(0) === JXR.EXP; 495 } 496 497 /* 498 工具函数用来解析$a.b.c.d的数据绑定,返回:{obj:对象,value:d的值,name:'d'} 499 args:$a.b.c.d 500 _data:真实数据 501 data:影子数据 502 * */ 503 function parse(args, data) { 504 if (Object.empty(data)) { 505 throw new Error('data:(null|undefined)'); 506 } 507 else { 508 try { 509 //去掉空格和$ 510 var d = args.trim().replace(JXR.EXP, ''); 511 //处理形如:a.b.c.d 512 var s = d.split('.'); 513 var o = data; 514 for (var i = 0; i < s.length - 1; i++) { 515 o = o[s[i]]; 516 if (Object.empty(o)) { 517 throw new Error('data:(null|undefined)'); 518 } 519 } 520 var n = s[s.length - 1]; 521 //属性d的值 522 var v = o[n]; 523 return {obj: o, value: v, name: n}; 524 } 525 catch (e) { 526 console.error(e); 527 return null; 528 } 529 530 } 531 } 532 533 /* 534 处理事件属性 535 */ 536 function handleEvent(el,data, name,value, self) { 537 //获取事件类型 538 var type=/on:(\S+)/gi.exec(name)[1]; 539 //获得事件方法名 540 var method=value.replace(/\(\S*\)/gi,''); 541 //获取方法参数 542 var args=/(?<=\()\S*(?=\))/gi.exec(value)[0]; 543 //收集参数 544 var array=[]; 545 if(!Object.empty(args) && args!=='') 546 { 547 548 //处理方法参数 549 args=args.split(','); 550 for(var i=0;i<args.length;i++) 551 { 552 var arg=args[i]; 553 if(isExp(arg)) 554 { 555 arg=parse(arg, data).value; 556 557 } 558 array.push(arg); 559 } 560 561 } 562 //清除事件 563 el[JXR.EVENT_ATTR_PREFIX + type] = null; 564 //绑定事件 565 el[JXR.EVENT_ATTR_PREFIX + type] = function(ev){ 566 array.push(ev); 567 self[JXR.EVENT_ATTR_PREFIX][method].apply(self,array); 568 }; 569 } 570 571 /*处理元素的普通属性*/ 572 function handleAttrs(el, data, self) { 573 var attrs = el.attributes; 574 for (var i = 0; i < attrs.length; i++) { 575 var attr = attrs[i]; 576 var name = attr.name; 577 if (name === JXR.IF_ATTR || name === JXR.FOR_ATTR || name === JXR.DATA_BIND_ATTR) { 578 continue; 579 } 580 var value = attr.value; 581 if(!Object.empty(value)) 582 { 583 value=value.trim(); 584 } 585 if(value==='') 586 { 587 continue; 588 } 589 //事件属性 590 if(/on:(\S+)/gi.test(name)) 591 { 592 handleEvent(el,data,name,value,self); 593 } 594 //脚本属性 595 else if(/\$(\S+)\$/gi.test(value)) 596 { 597 attr.value=eval(JXR.DATA+'.'+/\$(\S+)\$/gi.exec(value)[1]); 598 } 599 //普通属性绑定 600 else if (isExp(value)) { 601 AttrNode(el, attr, data); 602 } 603 604 } 605 } 606 607 /*处理元素的所有子节点*/ 608 function handleChildNodes(el, data, self) { 609 610 //处理子节点 611 var childNodes = el.childNodes; 612 for (var j = 0; j < childNodes.length; j++) { 613 var node = childNodes[j]; 614 if (node.blank()) 615 continue; 616 //是元素 617 if (node.nodeType === Node.ELEMENT_NODE) { 618 //递归 619 bind(node, data, self); 620 } 621 //是文本 622 else if (node.nodeType === Node.TEXT_NODE) { 623 TextNode(node, data); 624 } 625 } 626 } 627 628 629 /*工具函数,绑定数据到元素*/ 630 function bind(el, data, self) { 631 //处理属性 632 var attrs = el.attributes; 633 //条件控制 634 if (attrs[JXR.IF_ATTR]) { 635 636 if (!If(el, attrs[JXR.IF_ATTR], data)) { 637 return; 638 } 639 } 640 //循环控制 641 if (attrs[JXR.FOR_ATTR]) { 642 var forData = For(el, attrs[JXR.FOR_ATTR], data, self); 643 if (!forData) { 644 return; 645 } 646 } 647 //数据绑定 648 else if (attrs[JXR.DATA_BIND_ATTR]) { 649 var data = DataBind(el, attrs[JXR.DATA_BIND_ATTR], data, self); 650 if (!data) 651 return; 652 } 653 //处理其他属性 654 handleAttrs(el, data, self); 655 //循环已经处理 656 if(forData) 657 { 658 return; 659 } 660 //处理子节点 661 handleChildNodes(el, data, self); 662 } 663 664 Object.freeze(JXR); 665 Object.freeze(JXR.prototype); 666 })(); 667 668 //阻止往对象里面追加属性 669 //Object.preventExtensions($); 670 //delete $.n;允许 671 //console.info($.n); 672 // $.a='123';报错 673 //既阻止追加,也阻止删除属性 674 //Object.seal($); 675 //$.n=890;允许 676 //delete $.n;//不允许 677 //既阻止追加,也阻止删除,并且还不允许修改属性
测试页面如下:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>用户管理案例</title> 6 <style> 7 table,tr,th,td{ 8 border: 1px solid green; /*边框*/ 9 } 10 #main{ 11 margin: 10% 20% auto; 12 } 13 caption{ 14 font-size: large; 15 font-weight: bold; 16 } 17 table{ 18 border-collapse:collapse; 19 table-layout:fixed; 20 width: 600px; 21 height: 400px; 22 } 23 a{ 24 text-decoration: none; 25 } 26 tr{ 27 background-color: lightgray; 28 29 } 30 td{ 31 width:100px; /*单元格宽度*/ 32 height:50px; /*单元格高度*/ 33 text-align:center; /*单元格文字居中对齐*/ 34 } 35 .currentPage{ 36 background-color: red; 37 } 38 39 </style> 40 </head> 41 <body> 42 <div id="main"> 43 <table> 44 <caption>用户管理</caption> 45 <thead> 46 <tr> 47 <th colspan="3" class="toolbar"> 48 <button on:click="addUser($add)">$add</button> <button on:click="delUser()">$del</button> <button on:click="updateUser()">$update</button> <button on:click="saveUser()">$save</button> 49 </th> 50 </tr> 51 <tr> 52 <th>用户名</th> 53 <th>联系方式</th> 54 <th>性别</th> 55 </tr> 56 </thead> 57 <tbody class="content" jxr:for="$rows" on:click="onclick()"> 58 <tr data-userid="$user.userid" data-checked="$checked"> 59 <td><input type="text" value="$user.name"></td> 60 <td><input type="text" value="$user.mobile"></td> 61 <td>$user.gender?'男':'女'$</td> 62 </tr> 63 </tbody> 64 <tfoot> 65 <tr> 66 <th colspan="3" class="pagination"> 67 总共<span>$pages</span>页 第<span class="pages" jxr:for="$pages"><a href="javascript:void(0)" on:click="search()">$count </a></span>页 68 </th> 69 </tr> 70 </tfoot> 71 </table> 72 </div> 73 <script type="text/javascript" src="lib.js"></script> 74 <script type="text/javascript"> 75 //处理工具栏 76 var Toolbar=new JXR({ 77 el:'.toolbar', 78 data:{ 79 add:'追加用户', 80 update:'修改用户', 81 del:'删除用户', 82 save:'保存用户' 83 }, 84 on:{ 85 addUser:function (a) { 86 alert('add:'+a); 87 //this.data.add='add'; 88 }, 89 delUser:function (ev) { 90 alert('del'); 91 92 }, 93 updateUser:function (ev) { 94 alert('update'); 95 }, 96 saveUser:function (ev) { 97 alert('save'); 98 }, 99 } 100 }); 101 //内容 102 var Content=new JXR({ 103 el:'.content', 104 data:{ 105 rows:[{ 106 checked:false, 107 editable:true, 108 user:{ 109 userid:'1', 110 name:'张三', 111 mobile:'1234', 112 gender:false 113 } 114 }], 115 }, 116 on:{ 117 onclick:function(ev){ 118 //alert('click'); 119 this.data=null; 120 } 121 } 122 }); 123 //分页 124 var Pagination=new JXR({ 125 el:'.pagination', 126 data:{ 127 pages:5 128 }, 129 on:{ 130 search:function (ev) { 131 alert('search'); 132 } 133 } 134 }); 135 var data={ 136 data:{ 137 hello:'你好' 138 }, 139 on:{ 140 hit:function (ev) { 141 //this.hello='不好'; 142 alert(this.data.hello); 143 } 144 } 145 }; 146 147 var obj=JXR.bind('<div on:click="hit()"><input type="radio" checked="$hello$" value="$hello"></div>',data); 148 document.getElementById('main').appendChild(obj.el); 149 </script> 150 </body> 151 </html>
大家可以看看效果,有不清楚代码的可以加我qq讨论:412383550