初识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 相关文章推荐
JS 无法通过W3C验证的处理方法
Mar 09 Javascript
JavaScript转换农历类实现及调用方法
Jan 27 Javascript
JavaScript数据库TaffyDB用法实例分析
Jul 27 Javascript
基于MVC方式实现三级联动(JavaScript)
Jan 23 Javascript
原生js封装添加class,删除class的实例
Nov 06 Javascript
微信小程序导航栏滑动定位功能示例(实现CSS3的positionsticky效果)
Jan 24 Javascript
vue实现form表单与table表格的数据关联功能示例
Jan 29 Javascript
谈谈JavaScript中super(props)的重要性
Feb 12 Javascript
微信小程序使用车牌号输入法的示例代码
Aug 20 Javascript
jquery实现穿梭框功能
Jan 19 jQuery
Vue常用API、高级API的相关总结
Feb 02 Vue.js
ElementUI实现el-form表单重置功能按钮
Jul 21 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
利用php获取服务器时间的实现代码
2013/06/07 PHP
thinkphp实现面包屑导航(当前位置)例子分享
2014/05/10 PHP
php生成Android客户端扫描可登录的二维码
2016/05/13 PHP
表单的焦点顺序tabindex和对应enter键提交
2013/01/04 Javascript
JS获取url链接字符串 location.href
2013/12/23 Javascript
使用jQuery实现图片遮罩半透明坠落遮挡
2015/03/16 Javascript
jQuery实现在下拉列表选择时获取json数据的方法
2015/04/16 Javascript
js实现鼠标移到链接文字弹出一个提示层的方法
2015/05/11 Javascript
websocket+node.js实现实时聊天系统问题咨询
2017/05/17 Javascript
Vue.js表单标签中的单选按钮、复选按钮和下拉列表的取值问题
2017/11/22 Javascript
使用Electron构建React+Webpack桌面应用的方法
2017/12/15 Javascript
JavaScript复制内容到剪贴板的两种常用方法
2018/02/27 Javascript
微信小程序中使用echarts的实现方法
2019/04/24 Javascript
vue App.vue中的公共组件改变值触发其他组件或.vue页面监听
2019/05/31 Javascript
通过实例了解JS 连续赋值
2019/09/24 Javascript
Node.js API详解之 V8模块用法实例分析
2020/06/05 Javascript
解决vue与node模版引擎的渲染标记{{}}(双花括号)冲突问题
2020/09/11 Javascript
[00:58]2016年国际邀请赛勇士令状宣传片
2016/06/01 DOTA
[02:38]DOTA2 夜魇暗潮2020活动介绍官方视频
2020/11/04 DOTA
python发送邮件示例(支持中文邮件标题)
2014/02/16 Python
Python requests发送post请求的一些疑点
2018/05/20 Python
Python面向对象程序设计之私有属性及私有方法示例
2019/04/08 Python
python爬虫爬取某网站视频的示例代码
2021/02/20 Python
移动端rem布局的两种实现方法
2018/01/03 HTML / CSS
用React加CSS3实现微信拆红包动画效果
2017/03/13 HTML / CSS
匡威意大利官方商店 :Converse意大利
2018/11/27 全球购物
传播学毕业生求职信
2013/10/11 职场文书
高级护理专业毕业生推荐信
2013/12/25 职场文书
春季运动会广播稿大全
2014/02/19 职场文书
亲子读书活动方案
2014/02/22 职场文书
手机银行营销方案
2014/03/14 职场文书
商铺消防安全责任书
2014/07/29 职场文书
员工工作自我评价
2014/09/26 职场文书
Go语言基础切片的创建及初始化示例详解
2021/11/17 Golang
MySQL中B树索引和B+树索引的区别详解
2022/03/03 MySQL
Go gorilla securecookie库的安装使用详解
2022/08/14 Golang