在前面的8篇文章中,已经介绍了scope的几个方面,比如digest循环,继承机制等。
本篇文章以及后续的两篇文章打算把这块拼图完成,讨论一下scope的另外一项重要功能:事件机制。

主要分为以下6个方面进行讨论:
1. 发布-订阅模式(Publish-Subscribe Pattern)
2. 事件的生命周期-注册和注销
3. 事件与scope继承树-$emit以及$broadcast
4. 事件对象的组成
5. 事件的停止传播以及阻止默认行为
6. 事件在angular框架中的应用

本文首先讨论1-2。下一篇文章中讨论3-5。最后一篇文章讨论6。

发布-订阅模式(Publish-Subscribe Pattern)

这个模式算是设计模式中经典中的经典了。是消息中间件必然要实现的模式之一,也被众多框架以及API所支持。该模式在大规模分布式系统中是不可或缺的,它将系统中的消息产生者和消息消费者解耦开来,提高了系统的可扩展性。关于这个模式的概念和定义,这里就不再赘述了。对这个概念还不熟悉的同学们可以移步这里学习一下:英文Wiki。想看具体代码的同学网上搜索一下相关资料非常多。

那么当上下文变成了angular,发布订阅模式又是如何被实现和应用的呢?
就实现而言,angular的发布订阅模式和其它的各种实现并没有什么本质上的区别,但angular事件系统最重要的不同在于:它已经和scope继承树融为一体了。这也是为什么事件机制会被实现在scope中的原因,毕竟就第一印象而言,谁也不会将事件机制和scope这种数据载体以及继承机制混为一谈。

那么问题来了,事件机制怎么和scope继承树融为一体的呢?嗯,不要心急,待我一一道来。
让我们先看看angular中事件的生命周期,事件是如何注册以及注销的。

事件的生命周期-注册

实现其实不复杂,直接贴上代码一起分析一下:

$on: function(name,listener) {
  // listeners字典对象的初始化
  var namedListeners = this.$$listeners[name];
  if (!namedListeners) {
    this.$$listeners[name] = namedListeners = [];
  }
  namedListeners.push(listener);

  // 更新listenerCount计数器字典对象
  var current = this;
  do {
    if (!current.$$listenerCount[name]) {
      current.$$listenerCount[name] = 0;
    }
    current.$$listenerCount[name]++;
  } while ((current = current.$parent));

  // 提供注销该listener的函数
  var self = this;
  return function() {
    var indexOfListener = namedListeners.indexOf(listener);
    if (indexOfListener !== -1) {
      namedListeners[indexOfListener] = null;
      decrementListenerCount(self,1,name);
    }
  };
}

// 内部使用的用于减少计数器的方法
function decrementListenerCount(current,count,name) {
  do {
    current.$$listenerCount[name] -= count;

    if (current.$$listenerCount[name] === 0) {
      delete current.$$listenerCount[name];
    }
  } while ((current = current.$parent));
}

首先我们根据文档来看看这个 $on方法的签名:

/** /* @param {string} name 需要监听的事件名称。 * @param {function(event,...args)} 当事件发生时,需要执行的listener回调函数。 * @returns {function()} 返回一个用户注销事件的函数。 */ 
$on: function(name,listener){}

一目了然,这里在注册事件的时候就考虑到了注销,而采取的策略和在scope中定义watcher一致,都是返回一个注销函数,当需要注销此回调函数的时候调用一下即可。

$on的实现可以分为3个部分:
1. 初始化以及记录回调函数:初始化listeners字典对象并且将回调函数保存到其中。
2. 计数器更新:更新listenerCount计数器字典对象。
3. 返回注销函数:调用后更新listeners字典对象以及listenerCount计数器字典对象。

$on的实现基本没有什么比较晦涩难懂的地方,简洁明了。

至于一些细节部分,不懂的地方仔细体会一下即可。
而上面提到的两个字典对象:listeners和listenerCount,都是在创建子scope的时候就会创建的:

function createChildScopeClass(parent) {
  function ChildScope() {
    this.$$watchers = this.$$nextSibling =
        this.$$childHead = this.$$childTail = null;
    this.$$listeners = {};  // 用于保存事件对应的回调函数
    this.$$listenerCount = {};  // 用于保存事件对应回调函数的计数器
    this.$$watchersCount = 0;
    this.$id = nextUid();
    this.$$ChildScope = null;
  }
  ChildScope.prototype = parent;
  return ChildScope;
}

事件的生命周期-注销

对于事件的注销,过程上和注册非常相似,只不过正好相反:

return function() {
  var indexOfListener = namedListeners.indexOf(listener);
  if (indexOfListener !== -1) {
    namedListeners[indexOfListener] = null;
    decrementListenerCount(self,name);
  }
};

在调用注销函数后,首先得到注册的回调函数在数组中的位置,当存在时将其置为空并更新计数器。

总结一下,目前发现的angular中事件的几个特点:
1. 事件保存在scope上,也就是说和scope一样,事件也是拥有一个树形结构的。嗯,这里就可以初步看出angular的事件机制是和scope融为一体的。
2. 除了事件对应的回调函数外,还记录了回调函数的个数。
3. 提供了和watcher注销机制类似的注销机制。

angular事件和浏览器事件

一些题外话。
以上提到的angular事件和浏览器事件(比如,点击事件click,键盘输入事件keydown等)不是一回事。angular事件更多的应用内自定义事件。在使用时,事件的触发和事件的响应都需要由开发人员定义。而浏览器时间则不然,通常开发人员只需要定义响应事件即可,浏览器会帮你触发这些事件。

当需要在angular中定义浏览器事件的相应方法时,通常可以使用angular.element方法。这个方法也比较智能,当它判断jQuery可用时,会使用jQuery来初始化元素。否则会使用angular中自带的jqLite进行元素的初始化。

[AngularJS面面观] 9. scope事件机制 - 基本概念以及生命周期的更多相关文章

  1. 详解使用postMessage解决iframe跨域通信问题

    这篇文章主要介绍了详解使用postMessage解决iframe跨域通信问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  2. swift开发笔记9 - 正向和反向页面传参

    在storyboa里segue是这样的:首先看考勤页面(主页面)如何给备注页面传参:在考勤页面(主页面)的viewcontroller中找到prepareForSegue方法,这个方法由xcode自动生成,用于在使用segue跳转前,做一些处理动作:实际上是通过修改segue的目标页面的某个类属性,从而达到传参的目的。

  3. [快速学会Swift第三方库] Kingfisher篇

    [快速学会Swift第三方库]Kingfisher篇Kingfisher是一个轻量的下载和缓存网络图片库。也可以利用kf_setimageWithURL函数的返回值来进行更多的管理操作下载器自定义下载器参数缓存系统自定义缓存参数预取将一些图片在显示到屏幕上之前,先预取到缓存。动态图片加载动态图片只需要加上一行代码,设置imageView为AnimatedImageView,不设置也能加载,但是在动态图片较大的时候推荐进行该设置。深入学习这里列出了Kingfisher大多数操作,如果想要深入学习Kingfi

  4. swift - 回调

    回调函数B不由该函数的实现方A直接调用,而是在特定的事件或条件发生时由另外的一方C调用,用于对该事件或条件进行响应。在swift中有同步和异步两种形式的回调函数:同步回调函数异回调函数步C调用方法B,B可以是函数或者closureC调用函数B并使B在另一线程上运行B返回之前C处理block状态B和C运行在不同的线程,不会互相block。

  5. 6.3 Swift闭包表达式作为回调函数

    /**闭包表达式作为回调函数*//**上节课中呢,说了闭包表达式的语法,将闭包表达式赋给一个常量并不常用,那种调用方式还不如就写成函数的形式*/vararray=[20,2,3,70,8]showArrayprintbubbleSortletintCmp={->Intin//可以修改闭包表达式//letx=a%10//lety=b%10ifa>b{return-1}elseifaIntin//可以修改闭包表达式letx=a%10lety=b%10ifx>y{return-1}elseifxVoid{for

  6. [译] Alamofire Tutorial: Getting Started

    Alamofire提供了链式的request/response方法,JSON的传参和响应序列化,身份认证和其他特性。Alamofire的优雅之处在于它完完全全是由Swift写成的,并且没有从它的Objective-C版本-AFNetworking那继承任何特性。该token将会被包含在每个发往Imagga的请求的头部。Apple提供JSONSerialization类来帮助将内存中的对象转换为JSON,反之亦然。Alamofire有几个主要功能:.upload:以multipart,流,文件或数据方法上传

  7. 如何在Swift中传递回调函数

    我看过例子,这应该是我想的.我的类的定义是不正确的,因为我传递回调的方式吗?

  8. Android Studio是否支持用于Android UI设计的AngularJS?

    我对AndroidStudio有疑问:AS在设计XML文件时是否支持AngularJS代码,例如:对于小动画或效果?

  9. Android Facebook登录中不会调用回调函数

    解决方法你忘了把监听器放在onActivityResult()中将以下代码放在onActivityResult活动方法中:

  10. android – 如何使用ClientID和ClientSecret在Phonegap中使用Angularjs登录Google OAuth2

    我正尝试使用Angularjs(使用IonicFramework)通过GoogleOAuth2从我的Phonegap应用程序登录.目前我正在使用http://phonegap-tips.com/articles/google-api-oauth-with-phonegaps-inappbrowser.html进行登录.但是当我使用Angular-UI-RouterforIonic时,它正在创建非常

随机推荐

  1. Angular2 innerHtml删除样式

    我正在使用innerHtml并在我的cms中设置html,响应似乎没问题,如果我这样打印:{{poi.content}}它给了我正确的内容:``但是当我使用[innerHtml]=“poi.content”时,它会给我这个html:当我使用[innerHtml]时,有谁知道为什么它会剥离我的样式Angular2清理动态添加的HTML,样式,……

  2. 为Angular根组件/模块指定@Input()参数

    我有3个根组件,由根AppModule引导.你如何为其中一个组件指定@input()参数?也不由AppModalComponent获取:它是未定义的.据我所知,你不能将@input()传递给bootstraped组件.但您可以使用其他方法来做到这一点–将值作为属性传递.index.html:app.component.ts:

  3. angular-ui-bootstrap – 如何为angular ui-bootstrap tabs指令指定href参数

    我正在使用角度ui-bootstrap库,但我不知道如何为每个选项卡指定自定义href.在角度ui-bootstrap文档中,指定了一个可选参数select(),但我不知道如何使用它来自定义每个选项卡的链接另一种重新定义问题的方法是如何使用带有角度ui-bootstrap选项卡的路由我希望现在还不算太晚,但我今天遇到了同样的问题.你可以通过以下方式实现:1)在控制器中定义选项卡href:2)声明一个函数来改变控制器中的散列:3)使用以下标记:我不确定这是否是最好的方法,我很乐意听取别人的意见.

  4. 离子框架 – 标签内部的ng-click不起作用

    >为什么标签标签内的按钮不起作用?>但是标签外的按钮(登陆)工作正常,为什么?>请帮我解决这个问题.我需要在点击时做出回复按钮workingdemo解决方案就是不要为物品使用标签.而只是使用divHTML

  5. Angular 2:将值传递给路由数据解析

    我正在尝试编写一个DataResolver服务,允许Angular2路由器在初始化组件之前预加载数据.解析器需要调用不同的API端点来获取适合于正在加载的路由的数据.我正在构建一个通用解析器,而不是为我的许多组件中的每个组件设置一个解析器.因此,我想在路由定义中传递指向正确端点的自定义输入.例如,考虑以下路线:app.routes.ts在第一个实例中,解析器需要调用/path/to/resourc

  6. angularjs – 解释ngModel管道,解析器,格式化程序,viewChangeListeners和$watchers的顺序

    换句话说:如果在模型更新之前触发了“ng-change”,我可以理解,但是我很难理解在更新模型之后以及在完成填充更改之前触发函数绑定属性.如果您读到这里:祝贺并感谢您的耐心等待!

  7. 角度5模板形式检测形式有效性状态的变化

    为了拥有一个可以监听其包含的表单的有效性状态的变化的组件并执行某些组件的方法,是reactiveforms的方法吗?

  8. Angular 2 CSV文件下载

    我在springboot应用程序中有我的后端,从那里我返回一个.csv文件WheniamhittingtheURLinbrowsercsvfileisgettingdownloaded.现在我试图从我的角度2应用程序中点击此URL,代码是这样的:零件:服务:我正在下载文件,但它像ActuallyitshouldbeBook.csv请指导我缺少的东西.有一种解决方法,但您需要创建一个页面上的元

  9. angularjs – Angular UI-Grid:过滤后如何获取总项数

    提前致谢:)你应该避免使用jQuery并与API进行交互.首先需要在网格创建事件中保存对API的引用.您应该已经知道总行数.您可以使用以下命令获取可见/已过滤行数:要么您可以使用以下命令获取所选行的数量:

  10. angularjs – 迁移gulp进程以包含typescript

    或者我应该使用tsc作为我的主要构建工具,让它解决依赖关系,创建映射文件并制作捆绑包?

返回
顶部