在这个前端大爆发的年头,追着这些框架跑,真的好无力。angular、vue、react三个框架在领跑着,angular要出2.0了,vue也要2.0了,各种框架蓄势待发,好像潮流又要改一改的节奏。。。
 
 在这个对新手不友好的前端发展阶段,掌握一些核心才是关键。。。好的,我在扯淡,这是本人对mvvm的数据绑定的一些见解,请轻拍 
angular1.x的检脏机制
先来看一些简单的例子
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
 | <body> <span ng-bind = "count"></span> <button ng-click = "add">+</button> <button ng-click = "minus">-</button> <script type="text/javascript"> var Scope = function(){     var scope = this          var bind = function(){         var click = document.querySelectorAll('[ng-click]')         click.forEach(function(item){             console.log(item.getAttribute('ng-click'))             item.onclick = (function(item){                 return function(){                     scope[item.getAttribute('ng-click')]()                     scope.$apply()                 }             })(item)         })     }     bind()          scope.$apply = function () {         var span = document.querySelectorAll('[ng-bind]')         span.forEach(function(item){             if (item.innerHTML!==scope[item.getAttribute('ng-bind')]) {                 item.innerHTML = scope[item.getAttribute('ng-bind')]             }         })     }          scope.add = function () {         scope.count++      }          scope.minus = function () {         scope.count--      }          scope.count = 0          scope.$apply() } Scope() </script> </body>
 | 
以上就是简单的实现
利用bind函数来将ng-click这个指令分装,在为点击事件绑定相应的方法然后调用apply进行脏检测,而apply方法检测视图的值与js变量值是否一致,不一致则对其进行视图值进行刷新。基本的实现就完成了。
在Scope函数的最后加上一个settimeout函数试试
| 1 2 3 4 5 6
 | scope.$apply()     setTimeout(function(){      scope.count++      console.log(scope.count)  },1000)
 | 
显然上面代码是不行的,虽然值改变了(控制台输出1),但是页面值没有改变,为什么这样呢?嗯,是没有去检脏导致的,所以我们需要手动得去检脏
| 1 2 3 4 5 6 7
 | scope.$apply()  setTimeout(function(){     scope.count++     console.log(scope.count)     scope.$apply() },1000)
 | 
嗯,一切按我们所预想的实现了,页面值也在延时后变化了,这就是angular1.x的检脏机制。
原则上就是当变量改变了,就要去调用一次apply,来进行检脏。angular也为此封装了很多指令和服务,为我们自动检脏,例如ng-click,$timeout等
现在试着编写一个双向数据绑定的例子
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
 | <body>  <span ng-bind = "count"></span>  <button ng-click = "add">+</button>  <button ng-click = "minus">-</button>  <input ng-model = "count"/>  <script type="text/javascript">  var Scope = function(){      var scope = this            var bind = function(){          var click = document.querySelectorAll('[ng-click]')          click.forEach(function(item){              console.log(item.getAttribute('ng-click'))              item.onclick = (function(item){                  return function(){                      scope[item.getAttribute('ng-click')]()                      scope.$apply()                  }              })(item)          })          var input = document.querySelectorAll('[ng-model]')          input.forEach(function(item){              item.onkeyup = function(){                  scope[item.getAttribute('ng-model')] = item.value                  scope.$apply()              }              item.select = function(){                  scope[item.getAttribute('ng-model')] = item.value                  scope.$apply()              }          })      }      bind()            scope.$apply = function () {          var span = document.querySelectorAll('[ng-bind]')          var input = document.querySelectorAll('[ng-model]')          span.forEach(function(item){              if (item.innerHTML!==scope[item.getAttribute('ng-bind')]) {                  item.innerHTML = scope[item.getAttribute('ng-bind')]              }          })          input.forEach(function(item){              if (item.value!==scope[item.getAttribute('ng-model')]) {                  item.value = scope[item.getAttribute('ng-model')]              }          })      }            scope.add = function () {          scope.count++       }            scope.minus = function () {          scope.count--       }            scope.count = 0            scope.$apply()      setTimeout(function(){          scope.count++          scope.$apply()          console.log(scope.count)      },1000)  }  Scope()  </script>  </body>
 | 
完美实现,这就是简单的双向数据绑定,利用input绑定事件来改变js的值,然后在检脏。
其实angular1.x里面的主要实现不一定和本文相符,它的双向绑定的事件也不是本文这样简单的keyup,select;
它主要是通过$compile来编译文本,把那些指令都给缓存起来,  这个模板引擎也会被编译成指令,而缓存的对象有视图值,变量值等属性,十分复杂,本文仅仅简单实现一下原理。。。
vue的劫持get和set的方法
先来说说Object.defineProperty
Object.defineProperty是ES5的一个特性,这也是为什么vue不支持IE8以下的理由。
Object.defineProperty(object, propertyname, descriptor)
object
必需。  要在其上添加或修改属性的对象。  这可能是一个本机 JavaScript 对象(即用户定义的对象或内置对象)或 DOM 对象。
propertyname
必需。  一个包含属性名称的字符串。
descriptor
必需。  属性描述符。  它可以针对数据属性或访问器属性。
来个栗子
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
 | var test = { key:0 }; Object.defineProperty(test, 'key', {     get: function() {         console.log('get:' + key);                  return key;     },     set: function(value) {         key = value;         console.log('set:' + key);              } }); test.key = 2;  console.log(test.key); 
 | 
从上面可以看出这个,每次访问或者操作一个对象的内容时都可以触发相应的回调函数(set,get),这样实时监测对对象的操作,然后在do something执行设置好的监听函数之类的,进行数据的刷新。这样和检脏对比来说,性能可就好了很多,修改一个值,就刷新这个值所绑定的视图,而脏检测就只能遍历所有的新旧值。。。    但这仅限于对象,那在数组里面是怎样的呢,vue是直接把数组的原型给改了,对几个方法进行了封装。
请看下面这个栗子
| 1 2 3 4 5 6 7 8 9 10 11 12 13
 | Array.prototype.push_old = Array.prototype.push delete Array.prototype['push'] Array.prototype.push = function  (data) {     this.push_old(data)     console.log('do somthing') } var arr = [] arr.push(2);   console.log(arr)   arr.push([3])   console.log(arr)   arr[1].push(3)   console.log(arr)  
 | 
这样就简单地把它封装好了,每次push之后,都可以执行相关的函数,把相应的视图进行刷新,很赞。但是vue可能并不是这么简单的玩起来。。下面是在别人blog抄来的一个vue源码节选
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
 | const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto)  - Intercept mutating methods and emit events  */ ;[   'push',   'pop',   'shift',   'unshift',   'splice',   'sort',   'reverse' ] .forEach(function (method) {      var original = arrayProto[method]   def(arrayMethods, method, function mutator () {               var i = arguments.length     var args = new Array(i)     while (i--) {       args[i] = arguments[i]     }     var result = original.apply(this, args)     var ob = this.__ob__     var inserted     switch (method) {       case 'push':         inserted = args         break       case 'unshift':         inserted = args         break       case 'splice':         inserted = args.slice(2)         break     }     if (inserted) ob.observeArray(inserted)          ob.dep.notify()     return result   }) })
 | 
vue把数组的几个方法改掉,用来可以触发监听的视图改变事件。但是这样子,还有一个问题,就是数组arr[1] = 0;这样的操作vue根本没办法检测到这样的操作发生。。为此 Vue.js 在文档中明确提示不建议直接角标修改数据,但也还是提供了一个$set方法来解决这个问题;其本质就是Array原型设置一个$set方法,类似angular的apply检脏;而且需要手动触发,也就是如果写了arr[1] = 0;然后又想要视图改变的话,手动执行$set方法。。  
写在最后
前端真的很有趣。。。