Angular作用域


前言

之前我们探讨过Angular的执行流程,在一切准备工作就绪后(我是指所有directive和service都装载完毕),接下来其实就是编译dom(从指定的根节点开始遍历dom树),通过dom节点的元素名(E),属性名(A),class值(C)甚至注释(M)匹配指令,进而完成指令的compile,preLink,postLink,这期间就有可能伴随着作用域的创建和继承(有些指令通过scope字段要求创建自己的(孤立)作用域),从而形成一个作用域(scope)的继承关系。

下面的代码:

  1. 调用compile(element)(scope);开始编译dom树,传递的element是应用的根节点(有ng-app属性的节点或者手动bootstrap(element,...)的节点),而传递的scope则是唯一的根作用域(实质上是$RootScopeProvider服务返回的一个单例),与根节点对应。
  2. 最后通过scope.$apply(..)进行digest进行脏检查,开始一些初始化工作。
  1. injector.invoke(['$rootScope', '$rootElement', '$compile', '$injector',
  2. function bootstrapApply(scope, element, compile, injector) {
  3. scope.$apply(function() {
  4. element.data('$injector', injector);
  5. compile(element)(scope);
  6. });
  7. }]
  8. );

Scope

接下来我们讲的内容都是围绕rootScope.js,对于Scope的实现和一些概念,大家可以先参考这篇文章构建自己的AngularJS,第一部分:Scope和Digest,建议看原文。

Scope类

前面提到了根作用域$rootScope,其实就是Scope类的一个实例,我们通过简单的依赖注入的方式就可以获取到它,像这样:

  1. var injector = angular.injector(['ng']);
  2. injector.invoke(['$rootScope', function (scope) {
  3. console.log(scope);
  4. }]);

从控制台中可以很清晰地看到$rootScope对象的全部属性和方法,所以我们直接看下Scope类的定义来进行下对照:

  1. function Scope() {
  2. // 省略属性定义
  3. }
  4. Scope.prototype = {
  5. constructor: Scope,
  6. $new: function(isolate) {...},
  7. $watch: function(watchExp, listener, objectEquality) {...},
  8. $watchGroup: function(watchExpressions, listener) {...},
  9. $watchCollection: function(obj, listener) {...},
  10. $digest: function() {...},
  11. $destroy: function() {...},
  12. $eval: function(expr, locals) {...},
  13. $evalAsync: function(expr) {...},
  14. $apply: function(expr) {...},
  15. $applyAsync: function(expr) {...},
  16. $on: function(name, listener) {...},
  17. $emit: function(name, args) {...},
  18. $broadcast: function(name, args) {...}
  19. };
  1. 从原型方法中,可以看到我们熟悉的$watch$apply$digest方法,以及处理自定义事件(消息传递)的$on, $emit$broadcaset方法,这些我们稍后会讲到。
  2. 而由Scope new出来的实例就是一个简单的object,没有任何的getter和setter,我们可以很方便的直接向里面添加修改任何自定义属性,像这样:scope.hello='world';
scope作用域树

为什么会说成作用域树?我们其实知道作用域之间是通过原型链继承的,又或者是没有任何继承关系的孤立作用域单独存在的。

带着这样的疑问,首先我们假设有以下的dom结构:

  1. 节点A为根节点
  2. 每个节点都有指令,且指令都会创建自己的(孤立)作用域
  3. 节点E和节点F创建的是孤立作用域
  1. <A>
  2. <B>
  3. <F></F>
  4. </B>
  5. <C>
  6. <D></D>
  7. </C>
  8. <E></E>
  9. </A>

对照这样的dom结构和假设,我们可以画出这样的一张图(原图):

从这张图里面我们可以看出的不仅是作用域的继承关系还有作用域之间及父子兄弟关系:

  1. 普通的作用域通过原型链实现了继承关系,孤立作用域没有任何继承关系。

  2. 所有的作用域之间(也包括孤立作用域)根据自身所处的位置都存在以下这些关系:

    • $root来访问跟作用域
    • $parent来访问父作用域
    • $childHead$childTail)访问头(尾)子作用域
    • prevSibling$nextSibling)访问前(后)一个兄弟作用域

    这样的关系便形成了一个作用域树,通过它便可以完成作用域的向上(下)的遍历,从而实现后面的消息传递,$emit(向上冒泡),broadcast(向下广播)

  3. 所有的作用域都引用同一个$$asyncQueue$$postDigestQueue

$new方法构建作用域

上面这张图能够画出来都归功于自$new这个方法的实现。

代码其实很简单,就是返回一个(child)Scope的实例:

  1. $new: function(isolate) {
  2. var child;
  3. // isolate参数用来作为是否创建孤立作用域的标志
  4. if (isolate) {
  5. child = new Scope();
  6. child.$root = this.$root;
  7. // 保持$$asyncQueue和$$postDigestQueue的唯一性
  8. child.$$asyncQueue = this.$$asyncQueue;
  9. child.$$postDigestQueue = this.$$postDigestQueue;
  10. } else {
  11. // 实现原型继承
  12. // $ChildScope构造器只在第一次调用$new方法时才会被创建
  13. if (!this.$$ChildScope) {
  14. this.$$ChildScope = function ChildScope() {
  15. this.$$watchers = this.$$nextSibling =
  16. this.$$childHead = this.$$childTail = null;
  17. this.$$listeners = {};
  18. this.$$listenerCount = {};
  19. this.$id = nextUid();
  20. this.$$ChildScope = null;
  21. };
  22. this.$$ChildScope.prototype = this;
  23. }
  24. child = new this.$$ChildScope();
  25. }
  26. // 维护作用域之间的父子兄弟关系
  27. child['this'] = child;
  28. child.$parent = this;
  29. child.$$prevSibling = this.$$childTail;
  30. if (this.$$childHead) {
  31. this.$$childTail.$$nextSibling = child;
  32. this.$$childTail = child;
  33. } else {
  34. this.$$childHead = this.$$childTail = child;
  35. }
  36. return child;
  37. }
$watch方法监听作用域变化

我们在controller或者directive的link方法中经常会使用$watch方法,来监听当作用域的某个值发生变化时,采取什么样的操作。

我们可以在控制台里写一个例子(利用$rootScope),像这样:

  1. var injector = angular.injector(['ng']);
  2. injector.invoke(['$rootScope', function (scope) {
  3. // 获取scope对象到全局
  4. window.rootScope = scope;
  5. }]);
  6. rootScope.a = 'hello';
  7. // 监听scope.a的值
  8. rootScope.$watch('a', function (newVal, oldVal) {
  9. console.log(arguments)
  10. });
  11. // 程序初始化时digest
  12. rootScope.$digest();
  13. //修改scope.a的值,并进行digest脏检查
  14. rootScope.$apply(function (scope) {
  15. scope.a = 'world';
  16. });

看到控制台下面的日志信息如下:

  1. ["hello", "hello", Scope] // 初始化digest,触发回调,newVal和oldVal一样
  2. ["world", "hello", Scope] // 修改scope.a的值后,触发回调,newVal和oldVal不一样

所以我们经常会有这样的代码来区别第一次初始化和值改变:

  1. rootScope.$watch('a', function (newVal, oldVal) {
  2. if (newVal !== oldVal) {
  3. console.log('change');
  4. }
  5. });

从上面的代码,便可以看出我们使用$watch方法注册监听函数来响应当作用域中某个变量发生变化时的操作,利用$apply或者$digest方法来触发监听函数的执行。

所以$watch函数所做的工作其实就是作用域中变量和关联的监听函数的存储,

看看代码:

  1. $watch: function(watchExp, listener, objectEquality) {
  2. // 参数objectEquality进行严格比较,像object,array这种进行非引用比较而是递归值比较
  3. // 利用$parse服务转换成函数,用于获取作用域里的变量值
  4. var get = $parse(watchExp);
  5. if (get.$$watchDelegate) {
  6. return get.$$watchDelegate(this, listener, objectEquality, get);
  7. }
  8. // watcher对象是存储的元单位
  9. // watch.fn 存储监听函数
  10. // watch.last 记录变量改变之前的值
  11. // watch.eq 是否进行严格匹配
  12. var scope = this,
  13. array = scope.$$watchers,
  14. watcher = {
  15. fn: listener,
  16. last: initWatchVal,
  17. get: get,
  18. exp: watchExp,
  19. eq: !!objectEquality
  20. };
  21. lastDirtyWatch = null;
  22. if (!isFunction(listener)) {
  23. watcher.fn = noop;
  24. }
  25. // 第一次初始化$$watchers为数组
  26. if (!array) {
  27. array = scope.$$watchers = [];
  28. }
  29. // 存储数据
  30. array.unshift(watcher);
  31. // 返回函数,可用于取解除该监听
  32. return function deregisterWatch() {
  33. arrayRemove(array, watcher);
  34. lastDirtyWatch = null;
  35. };
  36. }
$digest方法进行脏检查

之前我们用$watch方法,存储了监听函数,当作用域里的变量发生变化时,调用$digest方法便会执行该作用域以及它的所有子作用域上的相关的监听函数,从而做一些操作(如:改变view)

不过一般情况下,我们不需要手动调用$digest或者$apply(如果一定需要手动调用的话,我们通常使用$apply,因为它里面除了调用$digest还做了异常处理),因为内置的directive和controller内部(即Angular Context之内)都已经做了$apply操作,只有在Angular Context之外的情况需要手动触发$digest,如: 使用setTimout修改scope(这种情况我们除了手动调用$digest,更推荐使用$timeout服务,因为它内部会帮我们调用$apply)。

举个controller的例子:

  1. angular.module('myApp',[])
  2. .controller('MessageController', function($scope) {
  3. setTimeout(function() {
  4. $scope.message = 'Fetched after 2 seconds';
  5. //$scope.$apply(function() {
  6. // $scope.message = 'Fetched after 2 seconds';
  7. //});
  8. }, 2000);
  9. });

正确的方式是注释掉的那一段(用$apply包裹),否则视图(如:{{message}})将不会得到更新。

看一下源代码(这里精简成最核心的代码片段):

  1. $digest: function() {
  2. // ...省略若干代码
  3. // 外层循环至少执行一次
  4. // 如果scope中被监听的变量一直有改变(dirty为true),那么外层循环会一直下去(TTL减1),这是为了防止监听函数有可能改变scope的情况,
  5. // 另外考虑到性能问题,如果TTL从默认值10减为0时,则会抛出异常
  6. do {
  7. dirty = false;
  8. current = target;
  9. /// 执行异步操作evalAsync
  10. while (asyncQueue.length) {
  11. try {
  12. asyncTask = asyncQueue.shift();
  13. asyncTask.scope.$eval(asyncTask.expression);
  14. } catch (e) {
  15. $exceptionHandler(e);
  16. }
  17. lastDirtyWatch = null;
  18. }
  19. // 标签语句。用于随时跳出该循环
  20. // 该循环遍历当前作用域以及它的子作用域,并执行监听函数
  21. traverseScopesLoop: do {
  22. if ((watchers = current.$$watchers)) {
  23. length = watchers.length;
  24. // 遍历监听函数
  25. while (length--) {
  26. try {
  27. watch = watchers[length];
  28. if (watch) {
  29. // 进行值比较或者严格的递归比较,这里考虑到一个特殊情况NaN不等于自身的情况
  30. if ((value = watch.get(current)) !== (last = watch.last) &&
  31. !(watch.eq ? equals(value, last) : (typeof value === 'number' && typeof last === 'number' && isNaN(value) && isNaN(last)))) {
  32. dirty = true; // 标记为dirty
  33. lastDirtyWatch = watch; // 保存最后一个dirty的watch,用于下面的判断watch === lastDirtyWatch
  34. watch.last = watch.eq ? copy(value, null) : value; // 保存被监听变量上一次的值
  35. watch.fn(value, ((last === initWatchVal) ? value : last), current); // 执行监听函数
  36. if (ttl < 5) {
  37. logIdx = 4 - ttl;
  38. if (!watchLog[logIdx]) watchLog[logIdx] = [];
  39. logMsg = (isFunction(watch.exp)) ? 'fn: ' + (watch.exp.name || watch.exp.toString()) : watch.exp;
  40. logMsg += '; newVal: ' + toJson(value) + '; oldVal: ' + toJson(last);
  41. watchLog[logIdx].push(logMsg);
  42. }
  43. } else if (watch === lastDirtyWatch) {
  44. // 这里是一个性能优化的地方,利用上一次循环记录的lastDirtyWatch,如果当前watch与它相等,表示后面的watch所监听的变量
  45. // 都不会再变化了,所以直接标记dirty为false,并跳出循环
  46. dirty = false;
  47. break traverseScopesLoop;
  48. }
  49. }
  50. } catch (e) {
  51. $exceptionHandler(e);
  52. }
  53. }
  54. }
  55. // 遍历该作用域下面的所有子作用域(貌似是按照深度优先)
  56. if (!(next = (current.$$childHead ||
  57. (current !== target && current.$$nextSibling)))) {
  58. while (current !== target && !(next = current.$$nextSibling)) {
  59. current = current.$parent;
  60. }
  61. }
  62. } while ((current = next));
  63. // traverseScopesLoop循环被跳出的位置
  64. // 检测是否循环次数超过了TTL的限制
  65. if ((dirty || asyncQueue.length) && !(ttl--)) {
  66. clearPhase();
  67. throw $rootScopeMinErr('infdig',
  68. '{0} $digest() iterations reached. Aborting!\n' +
  69. 'Watchers fired in the last 5 iterations: {1}',
  70. TTL, toJson(watchLog));
  71. }
  72. } while (dirty || asyncQueue.length);
  73. clearPhase();
  74. // ...省略若干代码
  75. }

另外:$RootScopeProvider中提供了digestTtl方法,用于修改TTL的值(默认是10),可以这样修改:

  1. angular.module('ng').config(['$rootScopeProvider', function ($RootScopeProvider) {
  2. $RootScopeProvider.digestTtl(20);
  3. }]);

作用域事件(消息)机制

Angular在scope上通过$on$emit$broadcast方法实现了自定义事件(消息)机制,这是代码解耦,实现数据共享的神器。

区别于Backbone.Events,这里的事件(消息)传递和接收针对的不是单一对象,而是多个对象(作用域树)

$emit 传递消息是从当前scope对象开始,通过scope.$parent 将消息向上冒泡一直传递到rootScope对象

broadcast 传递消息也是从当前scope对象开始,通过复杂的作用域之间的关系,将消息向下广播到所有的childScope对象(貌似是深度优先的顺序)

另外,在消息传递并执行监听函数时,会有一个event对象会被作为参数传递给监听函数,里面有我们关心的几个字段:

  1. {
  2. name: 'xxx', // 消息名
  3. targetScope: scope, // 触发改事件的目标(起始)作用域
  4. currentScope: scope, // 正在执行监听函数的当前作用域
  5. stopPropagation: fn // 阻止冒泡(只在emit时存在)
  6. ...
  7. }

一个简单的例子:

html

  1. <div ng-controller="ParentCtrl as parent" class="ng-scope">
  2. ParentCtrl
  3. <div ng-controller="SiblingOneCtrl as sib1" class="ng-scope">
  4. SiblingOneCtrl
  5. </div>
  6. <div ng-controller="SiblingTwoCtrl as sib2" class="ng-scope">
  7. SiblingTwoCtrl
  8. <div ng-controller="ChildCtrl as child" class="ng-scope">
  9. ChildCtrl
  10. </div>
  11. </div>
  12. </div>

js

  1. app.controller('ParentCtrl', function ($scope) {
  2. $scope.$on('ChildCtrl:emit', function () {
  3. console.log('ParentCtrl: ', arguments);
  4. });
  5. $scope.$on('ParentCtrl:broadcast', function () {
  6. console.log('ParentCtrl:', arguments);
  7. });
  8. // 延迟执行
  9. $scope.$evalAsync(function () {
  10. // 向下广播传递消息
  11. $scope.$broadcast('ParentCtrl:broadcast', 'Broadcast!');
  12. });
  13. });
  14. app.controller('SiblingOneCtrl', function ($scope) {
  15. $scope.$on('ChildCtrl:emit', function () {
  16. console.log('SiblingOneCtrl:', arguments);
  17. });
  18. $scope.$on('ParentCtrl:broadcast', function () {
  19. console.log('SiblingOneCtrl:', arguments);
  20. });
  21. });
  22. app.controller('SiblingTwoCtrl', function ($scope) {
  23. $scope.$on('ChildCtrl:emit', function () {
  24. console.log('SiblingTwoCtrl:', arguments);
  25. });
  26. $scope.$on('ParentCtrl:broadcast', function () {
  27. console.log('SiblingTwoCtrl:', arguments);
  28. });
  29. });
  30. app.controller('ChildCtrl', function ($scope) {
  31. $scope.$on('ChildCtrl:emit', function () {
  32. console.log('ChildCtrl:', arguments);
  33. });
  34. $scope.$on('ParentCtrl:broadcast', function () {
  35. console.log('ChildCtrl:', arguments);
  36. });
  37. // 向上冒泡传递消息
  38. $scope.$emit('ChildCtrl:emit', 'Emit!');
  39. });

控制台里我们可以看到以下日志(Object为event对象):

  1. ChildCtrl: [Object, "Emit!"]
  2. SiblingTwoCtrl: [Object, "Emit!"]
  3. ParentCtrl: [Object, "Emit!"]
  4. ParentCtrl: [Object, "Broadcast!"]
  5. SiblingOneCtrl: [Object, "Broadcast!"]
  6. SiblingTwoCtrl: [Object, "Broadcast!"]
  7. ChildCtrl: [Object, "Broadcast!"]

消息传递路径:

  1. emit: ChildCtrl -> SiblingTwoCtrl -> ParentCtrl
  2. broadcast: ParentCtrl -> SiblingOneCtrl -> SiblingTwoCtrl -> ChildCtrl
$on方法注册自定义事件(消息)

实现很易懂,就是将消息名和监听函数一一对应地存储在scope.$$listeners对象里,这里唯一的亮点在于scope.$$listenerCount的维护和用途。

当一个子作用域注册新的自定义事件时,它自身和它所有祖先作用域的scope.$$listenerCount都会加1,而当事件被取消时,该作用域和它所有祖先作用域的scope.$$listenerCount也会减1,这是一个性能优化点,当进行scope.broadcast传递消息(深度优先遍历)时,就无需遍历到每一个叶子作用域(即叶子节点),所以说scope.$$listenerCount不是指该作用域上该事件(消息)名有多少个监听函数。

  1. $on: function(name, listener) {
  2. var namedListeners = this.$$listeners[name];
  3. if (!namedListeners) {
  4. this.$$listeners[name] = namedListeners = [];
  5. }
  6. // 存储监听函数
  7. namedListeners.push(listener);
  8. var current = this;
  9. // 维护$$listenerCount,用于提高broadcast的性能
  10. do {
  11. if (!current.$$listenerCount[name]) {
  12. current.$$listenerCount[name] = 0;
  13. }
  14. current.$$listenerCount[name]++;
  15. } while ((current = current.$parent));
  16. var self = this;
  17. // 返回函数,用来取消监听函数
  18. return function() {
  19. namedListeners[namedListeners.indexOf(listener)] = null;
  20. decrementListenerCount(self, 1, name);
  21. };
  22. }
$emit向上冒泡传递事件(消息)

通过作用域关系scope.$parent不断向父作用域传递消息,达到冒泡的效果,既然是冒泡,当然就有阻止冒泡的方法,angular在会传递给监听函数一个event对象,可以通过event.stopPropagation方法来做到这一点。

  1. $emit: function(name, args) {
  2. var empty = [],
  3. namedListeners,
  4. scope = this,
  5. stopPropagation = false,
  6. event = { // 传递给监听函数的event对象
  7. name: name,
  8. targetScope: scope, // 目标作用域,类似于jquery中的event.target
  9. stopPropagation: function() {
  10. stopPropagation = true;
  11. },
  12. preventDefault: function() {
  13. event.defaultPrevented = true;
  14. },
  15. defaultPrevented: false
  16. },
  17. listenerArgs = concat([event], arguments, 1),// 传递给监听函数的参数
  18. i, length;
  19. do { // 循环处理作用域上的监听函数
  20. namedListeners = scope.$$listeners[name] || empty;
  21. event.currentScope = scope; // 当前作用域,类似于jquery中的event.currentTarget
  22. for (i = 0, length = namedListeners.length; i < length; i++) {
  23. // 事件的取消,之前只是置为null,这里借此次循环做下清除处理工作
  24. if (!namedListeners[i]) {
  25. namedListeners.splice(i, 1);
  26. i--;
  27. length--;
  28. continue;
  29. }
  30. try {
  31. // 执行回调
  32. namedListeners[i].apply(null, listenerArgs);
  33. } catch (e) {
  34. $exceptionHandler(e);
  35. }
  36. }
  37. // 阻止冒泡
  38. if (stopPropagation) {
  39. event.currentScope = null;
  40. return event;
  41. }
  42. // 向上访问父作用域
  43. scope = scope.$parent;
  44. } while (scope);
  45. // 处理完监听函数后,去除作用域引用
  46. event.currentScope = null;
  47. return event;
  48. }
$broadcast向下广播传递事件(消息)

$emit一样需要向其他作用域传递消息,这里的传递的目标作用域不再是$parent,而是所有的子作用域,避免深层次的循环嵌套,采用深度优先算法遍历作用域树,从而达到广播的效果,这里只看下核心代码:

  1. $broadcast: function(name, args) {
  2. // ... 省略定义代码
  3. // 循环遍历所有子作用域
  4. while ((current = next)) {
  5. event.currentScope = current;
  6. listeners = current.$$listeners[name] || [];
  7. for (i = 0, length = listeners.length; i < length; i++) {
  8. // 依旧是清理工作
  9. if (!listeners[i]) {
  10. listeners.splice(i, 1);
  11. i--;
  12. length--;
  13. continue;
  14. }
  15. // 监听函数调用
  16. try {
  17. listeners[i].apply(null, listenerArgs);
  18. } catch (e) {
  19. $exceptionHandler(e);
  20. }
  21. }
  22. // 核心代码
  23. // 这里实现了深度优先遍历,利用了作用域树的父子兄弟关系,其中利用$$listenerCount做了性能优化(前面说到)
  24. if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
  25. (current !== target && current.$$nextSibling)))) {
  26. while (current !== target && !(next = current.$$nextSibling)) {
  27. current = current.$parent;
  28. }
  29. }
  30. }
  31. event.currentScope = null;
  32. return event;
  33. }

最后

差不多以上就是我对angular scope源码的一些理解,如果不对的地方,麻烦指出来。(__)

之后会对指令的解析流程做个分析,也就是complile.js

【原创】angularjs1.3.0源码解析之scope的更多相关文章

  1. 【原创】angularjs1.3.0源码解析之directive

    # Angular指令编译原理 前言 angular之所以使用起来很方便,是因为通常我们只需要在html里面引入一个或多个(自定义或内置的)指令就可以完成一个特定的功能(这也是angular推荐的方式 ...

  2. 【原创】angularjs1.3.0源码解析之执行流程

    Angular执行流程 前言 发现最近angularjs在我厂的应用变得很广泛,下周刚好也有个angular项目要着手开始做,所以先做了下功课,从源代码开始入手会更深刻点,可能讲的没那么细,侧重点在于 ...

  3. 【原创】angularjs1.3.0源码解析之service

    Angular服务 在angular中,服务(service)是以提供特定的功能的形式而存在的. angular本身提供了很多内置服务,比如: $q: 提供了对promise的支持. $http: 提 ...

  4. solr&lucene3.6.0源码解析(四)

    本文要描述的是solr的查询插件,该查询插件目的用于生成Lucene的查询Query,类似于查询条件表达式,与solr查询插件相关UML类图如下: 如果我们强行将上面的类图纳入某种设计模式语言的话,本 ...

  5. solr&lucene3.6.0源码解析(三)

    solr索引操作(包括新增 更新 删除 提交 合并等)相关UML图如下 从上面的类图我们可以发现,其中体现了工厂方法模式及责任链模式的运用 UpdateRequestProcessor相当于责任链模式 ...

  6. Heritrix 3.1.0 源码解析(三十七)

    今天有兴趣重新看了一下heritrix3.1.0系统里面的线程池源码,heritrix系统没有采用java的cocurrency包里面的并发框架,而是采用了线程组ThreadGroup类来实现线程池的 ...

  7. solr&lucene3.6.0源码解析(二)

    上文描述了solr3.6.0怎么采用maven管理的方式在eclipse中搭建开发环境,在solr中,为了提高搜索性能,采用了缓存机制,这里描述的是LRU缓存,这里用到了 LinkedHashMap类 ...

  8. solr&lucene3.6.0源码解析(一)

      本文作为系列的第一篇,主要描述的是solr3.6.0开发环境的搭建   首先我们需要从官方网站下载solr的相关文件,下载地址为http://archive.apache.org/dist/luc ...

  9. apache mina2.0源码解析(一)

    apache mina是一个基于java nio的网络通信框架,为TCP UDP ARP等协议提供了一致的编程模型:其源码结构展示了优秀的设计案例,可以为我们的编程事业提供参考. 依照惯例,首先搭建a ...

随机推荐

  1. java加解密操作过程中的中文乱码问题

    import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import ...

  2. Arduino小车学习与研究

    信安系统设计基础实践模块 Arduino小车学习与研究 ================== 陈都(20135328) 余佳源(20135321) 莫凡(20135225) ---------- 索引 ...

  3. UIAccessibilityElement

    UIaccessibilityElement类封装的项目信息对一些特殊的人可见,默认情况下是不可访问的.例如,一个图标或文字图像不会自动访问,因为它没有继承的UIView(或UIControl).一个 ...

  4. Android四大布局及其主要属性

    布局: <LinearLayout></LinearLayout> <RelativeLayout></RelativeLayout> <Fram ...

  5. Sea.js & Require.js

    Sea.js 追求简单.自然的代码书写和组织方式,具有以下核心特性: 简单友好的模块定义规范:Sea.js 遵循 CMD 规范,可以像 Node.js 一般书写模块代码. 自然直观的代码组织方式:依赖 ...

  6. Android中图片大小和屏幕密度的关系讲解

    Android手机适配是非常让人头疼的一件事,尤其是图片,android为了做到是适配提供了很多文件夹来存放不同大小的图片,比如:drawable-ldpi.drawable-mdpi.drawabl ...

  7. Jsp语法、指令及动作元素

    一.JSP的语法 1.JSP的模板元素:(先写HTML) 就是JSP中的那些HTML标记 作用:页面布局和美化 2.JSP的Java脚本表达式: 作用:输出数据到页面上 语法:<%=表达式%&g ...

  8. 微信小程序「官方示例代码」浅析【上】

    从某个微信群里,拿到了这个IDE的下载地址,然后就有了这个: 根本登不上去,怎么办,怎么办呢? 看代码啊... 反正我又没有保密协议,解压缩一看NodeWebkit + React: 好啦 ,逛逛呗, ...

  9. Bootstrap系列 -- 14. 表单控件输入框input

    每一个表单都是由表单控件组成.离开了控件,表单就失去了意义.接下来的我们简单的来了解Bootstrap框架中表单控件的相关知识. 单行输入框,常见的文本输入框,也就是input的type属性值为tex ...

  10. redmine邮件发送功能配置详解

    redmine的邮件发送功能还是很有用的.像项目有更新啦,任务分配啦,都能邮件发送的相关责任人.我自己在linux服务器上安装并启动了redmine后,邮件一直发送了不了.查了网上的资料,都是讲修改下 ...