因为工作中实际开发需要,才开始接触angular框架。从当初的比葫芦画瓢,被各种问题、概念折磨摧残,到现在有一定的了解认识,觉得有必要将自己的认识进行简单的总结。不到位的地方还望多多包涵。
1.双向数据绑定
目前业内盛行各种MV**框架,相关的框架不断涌现,而angular就是其中的一种(MVVM)。MV**框架其实最核心的问题就是将view层和model分离开来,降低代码的耦合性,做到数据和表现的分离,MVC、MVP、MVVM均有相同的目标,而他们之间的不同就在于如何把model层和view关联起来。
数据在model、view层如何流动就成了问题的关键,angular通过dirty-check实现了数据的双向绑定。所谓的双向绑定,就是view的变化可以反映到model层,而model数据的变化可以在view体现出来。那么angular是如何做到双向绑定的?为何成为dirty-check呢?还是前端的一个原始问题出发吧:
html:
<input type="button" value="increase 1" /> <span></span>
js:
<script> var bindDate = { count: 1, appy: function () { document.querySelector('#J-count').innerHTML = this.count; }, increase: function () { var _this = this; document.querySelector('#J-increase').addEventListener('click', function () { _this.count++; appy(); }, true); }, initialize: function () { // 初始化 this.appy(); // this.increase(); } }; bindDate.initialize(); </script>
在上面的例子中,存在两个过程:
view层影响model层: 页面上点击button,造成数据count的数量增加1
model层反应view层: count发生完变化以后,通过apply函数来反映到view层上
这是以前使用jquery、YUI等类库实现的数据处理,这里面存在的问题很明显:
涉及到了大量的DOM操作;
过程繁琐;
代码耦合性太高,不便于写单元测试。
下面来看看angular是如何进行数据处理的:
第一步. 添加watcher:就是当数据发生变化的时候,需要检测哪些对象,需要先进行注册
// 对angular里面的源码进行了精简 $watch: function(watchExp, listener, objectEquality) { var scope = this, array = scope.$$watchers, watcher = { fn: listener, last: initWatchVal, get: get, exp: watchExp, eq: !!objectEquality }; if (!array) { array = scope.$$watchers = []; } array.unshift(watcher); }
第二步. dirty-check:就是当有某个scope作用域下的数据发生变化后,需要遍历检测注册的$$watchers = [...]
$digest: function() { while (length--) { watch = watchers[length]; watch.fn(value, lastValue, scope); } }
这样就实现了数据的双向绑定,上面的实现是否跟自定义事件很像呢?可以看到使用了观察者设计模式或(publisher-subscriber)。
2.依赖注入
使用过spring框架的同学都知道,Ioc、AOP是spring里面最重要的两个概念,而Ioc就可以里面为注入依赖(DI),很明显angular带有非常浓厚的后端色彩。
同样,首先来看下不使用DI,是如何解决对象相互依赖的:
function Car() { ... } Car.prototype = { run: function () {...} } function Benz() { var cat = new Car(); } Benz.prototype = { ... }
在上面的例子中,类Benz依赖于类Car,直接通过内部New来解决这种依赖关系。这样做的弊端非常明显,代码耦合性变高,不利于维护。后端框架很早就意识到了这个问题,spring早期通过在xml文件中注册对象之间的依赖关系,后来有通过anotation的方式更加方便地解决DI问题,COS端的同学可以看看后端的代码。
js语言本身是不具有注解(annotation)机制的,那angular是如何实现的呢?
1.模拟注解
// 注解的模拟 function annotate(fn, strictDi, name) { var $inject; if (!($inject = fn.$inject)) { $inject = []; $inject.push(name); }else if (isArray(fn)) { $inject = fn.slice(0, last); } return $inject; } createInjector.$$annotate = annotate;
2. 注入对象的创建
function createInjector(modulesToLoad, strictDi) { //通过singleton模式创建对象 var providerCache = { $provide: { provider: supportObject(provider), factory: supportObject(factory), service: supportObject(service), value: supportObject(value), constant: supportObject(constant), decorator: decorator } }, instanceCache = {}, instanceInjector = (instanceCache.$injector = createInternalInjector(instanceCache, function(serviceName, caller) { var provider = providerInjector.get(serviceName + providerSuffix, caller); return instanceInjector.invoke(provider.$get, provider, undefined, serviceName); })); return instanceInjector; }
3. 获取注入对象