初识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实多级联动下拉菜单类,简单实现省市区联动菜单!
May 03 Javascript
js数组的基本用法及数组根据下标(数值或字符)移除元素
Oct 20 Javascript
javascript函数定义的几种区别小结
Jan 06 Javascript
angularjs创建弹出框实现拖动效果
Aug 25 Javascript
详解JavaScript数组和字符串中去除重复值的方法
Mar 07 Javascript
jQuery实现倒计时(倒计时年月日可自己输入)
Dec 02 Javascript
javascript中的面向对象
Mar 30 Javascript
关于React动态加载路由处理的相关问题
Jan 07 Javascript
简单了解TypeScript中如何继承 Error 类
Jun 21 Javascript
vue实现吸顶、锚点和滚动高亮按钮效果
Oct 21 Javascript
vuex存储token示例
Nov 11 Javascript
解决echarts数据二次渲染不成功的问题
Jul 20 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中的正则表达式实例详解
2017/04/25 PHP
PHP利用递归函数实现无限级分类的方法
2019/03/22 PHP
关于Yii中模型场景的一些简单介绍
2019/09/22 PHP
javascript动态改变img的src属性图片不显示的解决方法
2010/10/20 Javascript
javascript SpiderMonkey中的函数序列化如何进行
2012/12/05 Javascript
javascript打印html内容功能的方法示例
2013/11/28 Javascript
js简单实现表单中点击按钮动态增加输入框数量的方法
2015/08/18 Javascript
JS实现带圆弧背景渐变效果的导航菜单代码
2015/10/13 Javascript
JavaScript的this关键字的理解
2016/06/18 Javascript
手机端点击图片放大特效PhotoSwipe.js插件实现
2016/08/24 Javascript
js完整倒计时代码分享
2016/09/18 Javascript
jQuery实现的电子时钟效果完整示例
2018/04/28 jQuery
nodejs同步调用获取mysql数据时遇到的大坑
2019/03/02 NodeJs
JS+html5实现异步上传图片显示上传文件进度条功能示例
2019/11/09 Javascript
微信小程序通过websocket实时语音识别的实现代码
2020/08/19 Javascript
8个非常实用的Vue自定义指令
2020/12/15 Vue.js
JavaScript实现点击自制菜单效果
2021/02/02 Javascript
[01:06:26]全国守擂赛第二周 Team Coach vs DeMonsTer
2020/04/28 DOTA
Python中使用select模块实现非阻塞的IO
2015/02/03 Python
Python批量提取PDF文件中文本的脚本
2018/03/14 Python
Python3实现的字典遍历操作详解
2018/04/18 Python
Python实现读写INI配置文件的方法示例
2018/06/09 Python
对pyqt5中QTabWidget的相关操作详解
2019/06/21 Python
用Python写一个自动木马程序
2019/09/17 Python
python 实现检验33品种数据是否是正态分布
2019/12/09 Python
Keras 切换后端方式(Theano和TensorFlow)
2020/06/19 Python
使用python实现下载我们想听的歌曲,速度超快
2020/07/09 Python
Python 实现PS滤镜中的径向模糊特效
2020/12/03 Python
检测浏览器是否支持html5视频的代码
2013/03/28 HTML / CSS
伊莱克斯(Electrolux)俄罗斯网上商店:瑞典家用电器品牌
2021/01/23 全球购物
《尊严》教学反思
2014/02/11 职场文书
大学新生军训自我鉴定
2014/03/18 职场文书
领导干部民主生活会自我剖析材料范文
2014/09/20 职场文书
大学组织委员竞选稿
2015/11/21 职场文书
2016年感恩节活动总结大全
2016/04/01 职场文书
Java使用Unsafe类的示例详解
2021/09/25 Java/Android