双向绑定确实是目前主流框架比较常见的功能。backbone,angular以及vue三者在双向绑定的实现原理上有所不同。这篇文章主要就angular的双向绑定实现做深入分析。
angular使用dirty(脏值)检查机制来实现了双向绑定,大体思路为:通过watch一遍又一遍地监听脏值也就是所说的脏值检查,当某个dirty由false变为true时,触发view重新渲染。
怪异现象:有些model改变了,但是view并没有能体现出来,没有做到双向绑定,此时需要手动触发apply方法才能OK。
上述只是思路,不能知道具体需要做什么。watch如何工作的?apply又是怎样实现?双向分别如何实现?何时需要手动apply?技术细节如何体现优雅?下面我就这些问题从源码的层面一一深入,并最终真正理解angular双向绑定的原理以及angular的watch/apply/digest。
$scope下$watch方法介绍:
//$watch使用
$scope.value = 0; $scope.$watch( function( ) { return $scope.value; }, function( newValue, oldValue ) { console.log('newValue id updated!'); } );
代码-1
$watch的第一个参数实际就$scope.value,是被监听的对象,也可以写成字符串如'value'。
此处有个细节,第一个参数没有直接写$scope.value而是用的匿名函数return了该变量。通过一个直观的举例来说明这种写法目的:
<body> <a href="www.a.com" onclick="return newPage()"></a> <a href="www.a.com" onclick="newPage()"></a> <script> var newPage=function (){ return false; }; </script> </body>
代码-2
通过对两个<a>点击会发现,第一个没有跳转a.com而第二个正常跳转了。可以理解这里return的作用在于当事件中发生一些特定情况(例如此处可以理解为不满足跳转条件返回false)时,希望之后的事件方法停止执行。
继续看$watch。
$watch 的第二个参数是监听函数,当第一个参数值发生变化时,监听函数将会执行。其实$watch还有第三个参数是个boolean类型,作用是是否进行深度值检测。(需要注意的是【代码-1】中会看到我们没有改变$scope.value的值但是console里打印出“newValue is updated!”这是因为初始化时,watch认为$scope.value的值由undefined变成0,所以触发了监听事件)。
下面我们直接看angular中关于$watch的源码,已添加详细的注释辅助大家理解。
$watch: function(watchExp, listener, objectEquality) { var scope = this; //compileToFn可以判断watchExp并返回一个执行表达式的函数 var get = compileToFn(watchExp, 'watch'); var array = scope.$$watchers; //初始化一个监听对象 var watcher = { fn: listener,//监听事件 last: initWatchVal,//最后监听结果 get: get,//调用$parse来返回执行表达式的函数可以获得表达式对于的需要监听的值 exp: watchExp,//备份最初监听表达式 eq: !!objectEquality//是否深度对比//两个!来处理空值或者未定义的行为远比if判断优雅很多 }; lastDirtyWatch = null<