近段时间研究了下angular.js 觉得它内部实现的view和model层之间存在很微妙的关系,如下图
如上图说的,view的改变会update 数据层model, 数据层会update视图层view,这双方之所以能实现互相的监听,就是通过中间层(理解为监听层),代码初始化的时候就会将view和model的相关状态都保存在监听层里面(可以理解为保存一个handler的函数到监听层里面),
view和model的改变都触发监听器里面的绑定的handler,实现状态的共享;
个人简单实现了下,可能实现得不太好,望拍砖(没有实现啥兼容性的哈,只在chrome下运行),希望各位大神给点意见!!
(function(win){ var doc = win.document, _bind_key = "data-bind-" , _event_prefix = "message-", _data = {};//存放数据的空间 /*生成唯一的gid*/ var gid = (function(){ var id =1; return function(){ return "ng-"+(id++); } })(); /*事件存放的容器*/ var publisher = { callbacks : {}, on : function( type , callback ){ this.callbacks[type] = this.callbacks[type]||[]; this.callbacks[type].push(callback); }, fire : function( type ){ var callback = this.callbacks[type]||[]; for(var i=0,len= callback.length;i<len;i++){ callback[i].apply( this , arguments ); } } }; var user = { set : function( id, key , value){ if(arguments<3){ return _data[object_id]; } this.setData( id,key,value ); publisher.fire(type,key, value); }, setData : function( id, key , value ){ _data[id][key] = value; } } /** * @param {Node} : elem it can be a nodeList or one item * @param {Function} callback : //数据层,view层改变后会触发此函数 */ win.twoWayBind = function( elem ,callback){ var id = gid(); this.attr(elem ,_bind_key+id , true); this.propoties = { "id" : id, "data-attr" : _bind_key+id, "type" : "message-"+id } this.init( callback ); } win.twoWayBind.prototype = { set : function(key , value){ this.propoties[key] = value; }, get : function(key){ return this.propoties[key]; }, attr : function( elems , key, value ){ //为elem元素设置相关属性 if(!elems){return;} if(elems.nodeName || elems.nodeType===1){ elems = [elems]; } for(var i =0 , len = elems.length;i<len;i++){ if( value ){ elems[i].setAttribute(key,value); }else{ return elems[i].getAttribute(key); } } }, /** * @description 次函数用于外层调用,以实现改变数据的时候能监听器里面的函数 */ setData : function( key , value ){ if(arguments.length<2){ return; } user.set( this.get('id'),key, value ); }, /*初始化*/ init : function( callback ){ var id = this.get('id'); this.domBinder(); this.dataBinder(); if(callback){ publisher.on(this.get('type'), callback); } }, domBinder : function(){ var object_id = this.get('id'), data_attr = this.get('data-attr'); /*view监听的事件*/ var handler = function( evt ){ var target = evt.target || evt.srcElement, prop_name = target.getAttribute(data_attr); if( prop_name ){ publisher.fire(type,prop_name,target.value); } } if(doc.addEventListener){ doc.addEventListener('change' , handler , false ); }else{ doc.attachEvent('change' , handler); } /*dom触发事件*/ publisher.on(this.get('type') , function( evt , prop_name ,newValue , current ){ if(!evt){ return; }//如果触发此事件的目标不是一个元素//则不执行下面的代码 var elems = doc.querySelectorAll("["+data_attr+"]"); for(var i=0,len= elems.length;i<len;i++){ elem = elems[i]; if(elem.nodeName.toLowerCase()==="input" || elem.nodeName.toLowerCase()=="textarea"){ elem.value = newValue; }else{ elem.innerHTML = newValue; } } }); }, dataBinder : function(){ var object_id = this.get('id'); _data[object_id] = _data[object_id] || {}; var data_attr = _bind_key+object_id; type = "message-"+object_id; /*dom触发事件*/ publisher.on(this.get('type') , function( evt , key ,value , isDataChange ){ user.setData(object_id,key,value); console.log(evt); }); } } })(window)
完整代码如下:
<!DOCTYPE HTML> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <input placeholder="按enter提交代码" type="text" id="text" /> <div> <button value="1" onclick="setData(this)">设置字段value的值为1</button> <button value="2" onclick="setData(this)">设置字段value的值为2</button> </div> <ul id='list'></ul> <script type="text/javascript"> (function(win){ var doc = win.document, _bind_key = "data-bind-" , _event_prefix = "message-", _data = {};//存放数据的空间 /*生成唯一的gid*/ var gid = (function(){ var id =1; return function(){ return "ng-"+(id++); } })(); /*事件存放的容器*/ var publisher = { callbacks : {}, on : function( type , callback ){ this.callbacks[type] = this.callbacks[type]||[]; this.callbacks[type].push(callback); }, fire : function( type ){ var callback = this.callbacks[type]||[]; for(var i=0,len= callback.length;i<len;i++){ callback[i].apply( this , arguments ); } } }; var user = { set : function( id, key , value){ if(arguments<3){ return _data[object_id]; } this.setData( id,key,value ); publisher.fire(type,key, value); }, setData : function( id, key , value ){ _data[id][key] = value; } } /** * @param {Node} : elem it can be a nodeList or one item * @param {Function} callback : //数据层,view层改变后会触发此函数 */ win.twoWayBind = function( elem ,callback){ var id = gid(); this.attr(elem ,_bind_key+id , true); this.propoties = { "id" : id, "data-attr" : _bind_key+id, "type" : "message-"+id } this.init( callback ); } win.twoWayBind.prototype = { set : function(key , value){ this.propoties[key] = value; }, get : function(key){ return this.propoties[key]; }, attr : function( elems , key, value ){ //为elem元素设置相关属性 if(!elems){return;} if(elems.nodeName || elems.nodeType===1){ elems = [elems]; } for(var i =0 , len = elems.length;i<len;i++){ if( value ){ elems[i].setAttribute(key,value); }else{ return elems[i].getAttribute(key); } } }, /** * @description 次函数用于外层调用,以实现改变数据的时候能监听器里面的函数 */ setData : function( key , value ){ if(arguments.length<2){ return; } user.set( this.get('id'),key, value ); }, /*初始化*/ init : function( callback ){ var id = this.get('id'); this.domBinder(); this.dataBinder(); if(callback){ publisher.on(this.get('type'), callback); } }, domBinder : function(){ var object_id = this.get('id'), data_attr = this.get('data-attr'); /*view监听的事件*/ var handler = function( evt ){ var target = evt.target || evt.srcElement, prop_name = target.getAttribute(data_attr); if( prop_name ){ publisher.fire(type,prop_name,target.value); } } if(doc.addEventListener){ doc.addEventListener('change' , handler , false ); }else{ doc.attachEvent('change' , handler); } /*dom触发事件*/ publisher.on(this.get('type') , function( evt , prop_name ,newValue , current ){ if(!evt){ return; }//如果触发此事件的目标不是一个元素//则不执行下面的代码 var elems = doc.querySelectorAll("["+data_attr+"]"); for(var i=0,len= elems.length;i<len;i++){ elem = elems[i]; if(elem.nodeName.toLowerCase()==="input" || elem.nodeName.toLowerCase()=="textarea"){ elem.value = newValue; }else{ elem.innerHTML = newValue; } } }); }, dataBinder : function(){ var object_id = this.get('id'); _data[object_id] = _data[object_id] || {}; var data_attr = _bind_key+object_id; type = "message-"+object_id; /*dom触发事件*/ publisher.on(this.get('type') , function( evt , key ,value , isDataChange ){ user.setData(object_id,key,value); console.log(evt); }); } } })(window) </script> <script type="text/javascript"> var tipsContainer = document.getElementById('list'); var binder = new twoWayBind( document.getElementById('text') ,function( e, key ,value ){ var li = document.createElement('li'); li.innerHTML = "当前input的值是:"+ value; tipsContainer.appendChild(li); }); function setData(elem){ var value = elem.value; binder.setData("key" , value); } </script> </body> </html>