初识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 相关文章推荐
JavaScript中的bold()方法使用详解
Jun 08 Javascript
Bootstrap学习笔记之css样式设计(2)
Jun 07 Javascript
jquery.validate[.unobtrusive]和Bootstrap实现tooltip错误提示问题分析
Oct 30 Javascript
Javascript封装id、class与元素选择器方法示例
Mar 13 Javascript
JS闭包可被利用的常见场景小结
Apr 09 Javascript
react-native之ART绘图方法详解
Aug 08 Javascript
JS文件中加载jquery.js的实例代码
May 05 jQuery
详解处理bootstrap4不支持远程静态框问题
Jul 20 Javascript
JQuery通过后台获取数据遍历到前台的方法
Aug 13 jQuery
js数组去重的方法总结
Jan 18 Javascript
微信小程序全局变量GLOBALDATA的定义和调用过程解析
Sep 23 Javascript
手动实现vue2.0的双向数据绑定原理详解
Feb 06 Vue.js
复杂的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网络操作函数汇总
2015/05/18 PHP
yii2.0实现验证用户名与邮箱功能
2015/12/22 PHP
php版微信公众平台回复中文出现乱码问题的解决方法
2016/09/22 PHP
PHP实现创建微信自定义菜单的方法示例
2017/07/14 PHP
PHP实现找出链表中环的入口节点
2018/01/16 PHP
php工具型代码之印章抠图
2018/07/18 PHP
jquery 表单下所有元素的隐藏
2009/07/25 Javascript
Js-$.extend扩展方法使方法参数更灵活
2013/01/15 Javascript
JavaScript基础篇之变量作用域、传值、传址的简单介绍与实例
2013/06/29 Javascript
jquery获取对象的方法足以应付常见的各种类型的对象
2014/05/14 Javascript
深入理解JavaScript系列(37):设计模式之享元模式详解
2015/03/04 Javascript
JavaScript内存管理介绍
2015/03/13 Javascript
JavaScript代码实现禁止右键、禁选择、禁粘贴、禁shift、禁ctrl、禁alt
2015/11/17 Javascript
JavaScript中创建对象的模式汇总
2016/04/19 Javascript
JS实现刷新父页面不弹出提示框的方法
2016/06/22 Javascript
总结十个Angular.js由浅入深的面试问题
2016/08/26 Javascript
JS封装的三级联动菜单(使用时只需要一行js代码)
2016/10/24 Javascript
深入理解jquery中extend的实现
2016/12/22 Javascript
webpack的 rquire.context用法实现工程自动化的方法
2020/02/07 Javascript
js实现点击烟花特效
2020/10/14 Javascript
[08:29]DOTA2每周TOP10 精彩击杀集锦vol.7
2014/06/25 DOTA
跟老齐学Python之赋值,简单也不简单
2014/09/24 Python
Python实现的数据结构与算法之链表详解
2015/04/22 Python
Python的净值数据接口调用示例分享
2016/03/15 Python
python+selenium实现京东自动登录及秒杀功能
2017/11/18 Python
python实现转圈打印矩阵
2019/03/02 Python
Python程序包的构建和发布过程示例详解
2019/06/09 Python
python判断变量是否为int、字符串、列表、元组、字典的方法详解
2020/02/13 Python
Pycharm中import torch报错的快速解决方法
2020/03/05 Python
聚美优品陈欧广告词
2014/03/14 职场文书
歌颂祖国的演讲稿
2014/05/04 职场文书
求职教师自荐书
2014/06/19 职场文书
创先争优公开承诺书
2014/08/30 职场文书
学生夜不归宿检讨书
2014/09/23 职场文书
初中思想品德教学反思
2016/02/24 职场文书
mysql优化
2021/04/06 MySQL