初识angular框架后的所思所想


Posted in Javascript onFebruary 19, 2016

因为工作中实际开发需要,才开始接触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" id="J-increase" />
 <span id="J-count"></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. 获取注入对象

 

function invoke(fn, self, locals, serviceName) {
 var args = [],
  $inject = annotate(fn, strictDi, serviceName);

 for (...) {
  key = $inject[i];
   // 替换成依赖的对象
   args.push(
   locals && locals.hasOwnProperty(key)
     ? locals[key]
    : getService(key, serviceName)
   );
 }
  if (isArray(fn)) {
  fn = fn[length];
  }   
  return fn.apply(self, args);
}

       到这里,是否是看到很多后端框架设计的思路,没有anotation就模拟一个,难怪PPK要说angular是" a front-end framework by non-front-enders for non-front-enders"

3.controller通信
    在实际开发中,应用系统会非常庞大,一个应用app不可能只存在一个controller,那么不同controller之间就存在通信的可能,如何解决这个常见问题,主要有两种方法:

1.事件机制: 把事件注册在$rootScope上,这样做的问题就是会在$rootScope上注册太大事件,会引起一些列后续问题

//controller1
app.controller('controller1', function ($rootScope) {
 $rootScope.$on('eventType', function (arg) {
    ......
  })
 })

// controller2
app.controller('controller2', function ($rootScope) {
   $rootScope.$emit('eventType',arg);
 or
  $rootScope.$broadcast('eventType',arg);
 })

 2.通过service: 充分利用angular的DI特性,利用service是单例的特点,在不同controller之间起到桥梁作用

// 注册service
app.service('Message', function () {
 return {
  count: void(0);
 }
 })
 // controller1,修改service的count值
app.controller('controller1', function ($scope, Message) {
  $scope.count = 1;
 Message.count = $scope.count;
});
 // controller2, 获取service的count值
app.controller('controller2', function ($scope, Message) {
$scope.num = Message.count;
 });

4.service的特点

1. 单例(singleton): angular里面只有service才可以进行DI诸如,controller、directive这些均不具有这些功能,service字面上就是提供一些基本的服务,跟具体的业务没有关联,而controller、directive则与具体业务紧密相关联,所以需要保证service的唯一性。

 2. lazy new:angular首先会生成service的provider,但是并没有立即生成对应的service,只有到需要这些服务的时候才会进行实例化操作。

3. provider)的分类: provider()、factory、service、value、constant,其中provider是最底层的实现,其他方式都是在其基础上的语法糖(sugar),需要注意的是这些服务最终均要添加$get方法,因为具体service是通过执行$get方法生成的。 

5. directive的实现
     directive的编译(compiler)包括两个阶段: compile、link。简单来讲compile阶段主要处理template DOM,此时并不涉及作用域问题,也就是没有进行数据渲染,例如ngRepeate指令就是通过compile进行template修改的,执行compile后会返回link函数,覆盖后面定义的link函数;而link主要是进行数据渲染,分为pre-link和post-link两个环节,这两个环节解析的顺序是相反,post-link是先解析内部,然后才是外部,这样对directive的解析就是安全的,因为directive内部还可以包括directive,同时link是对真正DOM的处理,会涉及DOM操作的性能问题。

初识angular框架后的所思所想

本文涉及的内容还不是很全民,之后还会有相应补充,希望大家也可以对angular框架进行学习探讨。

Javascript 相关文章推荐
漂亮的提示信息(带箭头)
Mar 21 Javascript
JS 分号引起的一段调试问题
Jun 18 Javascript
JS中prototype关键字的功能介绍及使用示例
Jul 21 Javascript
javascript实现类似超链接的效果
Dec 26 Javascript
JS中的THIS和WINDOW.EVENT.SRCELEMENT详解
May 25 Javascript
JavaScript中的bold()方法使用详解
Jun 08 Javascript
jQuery实现页面顶部显示的进度条效果完整实例
Dec 09 Javascript
JS使用eval()动态创建变量的方法
Jun 03 Javascript
JS 组件系列之 bootstrap treegrid 组件封装过程
Apr 28 Javascript
js Date()日期函数浏览器兼容问题解决方法
Sep 12 Javascript
详解Vue.js 作用域、slot用法(单个slot、具名slot)
Oct 15 Javascript
微信小程序语音同步智能识别的实现案例代码解析
May 29 Javascript
复杂的javascript窗口分帧解析
Feb 19 #Javascript
javascript轻量级库createjs使用Easel实现拖拽效果
Feb 19 #Javascript
jQuery fancybox在ie浏览器下无法显示关闭按钮的解决办法
Feb 19 #Javascript
谈一谈javascript中继承的多种方式
Feb 19 #Javascript
多种js图片预加载实现方式分享
Feb 19 #Javascript
JS实现1000以内被3或5整除的数字之和
Feb 18 #Javascript
ECharts仪表盘实例代码(附源码下载)
Feb 18 #Javascript
You might like
Memcache 在PHP中的使用技巧
2010/02/08 PHP
php selectradio和checkbox默认选择的实现方法详解
2013/06/29 PHP
PHP使用正则表达式获取微博中的话题和对象名
2015/07/18 PHP
PHP 中 var_export、print_r、var_dump 调试中的区别
2018/06/19 PHP
详解new function(){}和function(){}() 区别分析
2008/03/22 Javascript
FLASH 广告之外的链接
2008/12/16 Javascript
用Javascript数组处理多个字符串的连接问题
2009/08/20 Javascript
JS小游戏之象棋暗棋源码详解
2014/09/25 Javascript
JS烟花背景效果实现方法
2015/03/03 Javascript
JavaScript实现页面跳转的方式汇总
2016/05/16 Javascript
Vue.js动态组件解析
2016/09/09 Javascript
bootstrapValidator bootstrap-select验证不可用的解决办法
2017/01/11 Javascript
Bootstrap表格制作代码
2017/03/17 Javascript
js实现图片轮播效果学习笔记
2017/07/26 Javascript
vue2.0开发入门笔记之.vue文件的生成和使用
2017/09/19 Javascript
node.js基于fs模块对系统文件及目录进行读写操作的方法详解
2017/11/10 Javascript
[原创]js实现保存文本框内容为本地文件兼容IE,chrome,火狐浏览器
2018/02/14 Javascript
angular4自定义表单控件[(ngModel)]的实现
2018/11/23 Javascript
Element input树型下拉框的实现代码
2018/12/21 Javascript
javascript实现计算指定范围内的质数示例
2018/12/29 Javascript
[00:11]战神迅矛
2019/03/06 DOTA
Python中的tuple元组详细介绍
2015/02/02 Python
详细介绍Python的鸭子类型
2016/09/12 Python
Python3安装Pymongo详细步骤
2017/05/26 Python
python远程连接服务器MySQL数据库
2018/07/02 Python
python3使用腾讯企业邮箱发送邮件的实例
2019/06/28 Python
python的re模块使用方法详解
2019/07/26 Python
python中的&amp;&amp;及||的实现示例
2019/08/07 Python
CSS3 制作旋转的大风车(充满童年回忆)
2013/01/30 HTML / CSS
科颜氏印度官网:Kiehl’s印度
2021/02/20 全球购物
人事行政主管岗位职责
2013/12/22 职场文书
初三政治教学反思
2014/01/30 职场文书
求职个人评价范文
2014/04/09 职场文书
五一促销活动总结
2014/07/01 职场文书
国际贸易毕业生求职信
2014/07/20 职场文书
小学数学教师研修日志
2015/11/13 职场文书