aspect.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. define([], function(){
  2. // module:
  3. // dojo/aspect
  4. "use strict";
  5. var undefined, nextId = 0;
  6. function advise(dispatcher, type, advice, receiveArguments){
  7. var previous = dispatcher[type];
  8. var around = type == "around";
  9. var signal;
  10. if(around){
  11. var advised = advice(function(){
  12. return previous.advice(this, arguments);
  13. });
  14. signal = {
  15. remove: function(){
  16. if(advised){
  17. advised = dispatcher = advice = null;
  18. }
  19. },
  20. advice: function(target, args){
  21. return advised ?
  22. advised.apply(target, args) : // called the advised function
  23. previous.advice(target, args); // cancelled, skip to next one
  24. }
  25. };
  26. }else{
  27. // create the remove handler
  28. signal = {
  29. remove: function(){
  30. if(signal.advice){
  31. var previous = signal.previous;
  32. var next = signal.next;
  33. if(!next && !previous){
  34. delete dispatcher[type];
  35. }else{
  36. if(previous){
  37. previous.next = next;
  38. }else{
  39. dispatcher[type] = next;
  40. }
  41. if(next){
  42. next.previous = previous;
  43. }
  44. }
  45. // remove the advice to signal that this signal has been removed
  46. dispatcher = advice = signal.advice = null;
  47. }
  48. },
  49. id: nextId++,
  50. advice: advice,
  51. receiveArguments: receiveArguments
  52. };
  53. }
  54. if(previous && !around){
  55. if(type == "after"){
  56. // add the listener to the end of the list
  57. // note that we had to change this loop a little bit to workaround a bizarre IE10 JIT bug
  58. while(previous.next && (previous = previous.next)){}
  59. previous.next = signal;
  60. signal.previous = previous;
  61. }else if(type == "before"){
  62. // add to beginning
  63. dispatcher[type] = signal;
  64. signal.next = previous;
  65. previous.previous = signal;
  66. }
  67. }else{
  68. // around or first one just replaces
  69. dispatcher[type] = signal;
  70. }
  71. return signal;
  72. }
  73. function aspect(type){
  74. return function(target, methodName, advice, receiveArguments){
  75. var existing = target[methodName], dispatcher;
  76. if(!existing || existing.target != target){
  77. // no dispatcher in place
  78. target[methodName] = dispatcher = function(){
  79. var executionId = nextId;
  80. // before advice
  81. var args = arguments;
  82. var before = dispatcher.before;
  83. while(before){
  84. args = before.advice.apply(this, args) || args;
  85. before = before.next;
  86. }
  87. // around advice
  88. if(dispatcher.around){
  89. var results = dispatcher.around.advice(this, args);
  90. }
  91. // after advice
  92. var after = dispatcher.after;
  93. while(after && after.id < executionId){
  94. if(after.receiveArguments){
  95. var newResults = after.advice.apply(this, args);
  96. // change the return value only if a new value was returned
  97. results = newResults === undefined ? results : newResults;
  98. }else{
  99. results = after.advice.call(this, results, args);
  100. }
  101. after = after.next;
  102. }
  103. return results;
  104. };
  105. if(existing){
  106. dispatcher.around = {advice: function(target, args){
  107. return existing.apply(target, args);
  108. }};
  109. }
  110. dispatcher.target = target;
  111. }
  112. var results = advise((dispatcher || existing), type, advice, receiveArguments);
  113. advice = null;
  114. return results;
  115. };
  116. }
  117. // TODOC: after/before/around return object
  118. var after = aspect("after");
  119. /*=====
  120. after = function(target, methodName, advice, receiveArguments){
  121. // summary:
  122. // The "after" export of the aspect module is a function that can be used to attach
  123. // "after" advice to a method. This function will be executed after the original method
  124. // is executed. By default the function will be called with a single argument, the return
  125. // value of the original method, or the the return value of the last executed advice (if a previous one exists).
  126. // The fourth (optional) argument can be set to true to so the function receives the original
  127. // arguments (from when the original method was called) rather than the return value.
  128. // If there are multiple "after" advisors, they are executed in the order they were registered.
  129. // target: Object
  130. // This is the target object
  131. // methodName: String
  132. // This is the name of the method to attach to.
  133. // advice: Function
  134. // This is function to be called after the original method
  135. // receiveArguments: Boolean?
  136. // If this is set to true, the advice function receives the original arguments (from when the original mehtod
  137. // was called) rather than the return value of the original/previous method.
  138. // returns:
  139. // A signal object that can be used to cancel the advice. If remove() is called on this signal object, it will
  140. // stop the advice function from being executed.
  141. };
  142. =====*/
  143. var before = aspect("before");
  144. /*=====
  145. before = function(target, methodName, advice){
  146. // summary:
  147. // The "before" export of the aspect module is a function that can be used to attach
  148. // "before" advice to a method. This function will be executed before the original method
  149. // is executed. This function will be called with the arguments used to call the method.
  150. // This function may optionally return an array as the new arguments to use to call
  151. // the original method (or the previous, next-to-execute before advice, if one exists).
  152. // If the before method doesn't return anything (returns undefined) the original arguments
  153. // will be preserved.
  154. // If there are multiple "before" advisors, they are executed in the reverse order they were registered.
  155. // target: Object
  156. // This is the target object
  157. // methodName: String
  158. // This is the name of the method to attach to.
  159. // advice: Function
  160. // This is function to be called before the original method
  161. };
  162. =====*/
  163. var around = aspect("around");
  164. /*=====
  165. around = function(target, methodName, advice){
  166. // summary:
  167. // The "around" export of the aspect module is a function that can be used to attach
  168. // "around" advice to a method. The advisor function is immediately executed when
  169. // the around() is called, is passed a single argument that is a function that can be
  170. // called to continue execution of the original method (or the next around advisor).
  171. // The advisor function should return a function, and this function will be called whenever
  172. // the method is called. It will be called with the arguments used to call the method.
  173. // Whatever this function returns will be returned as the result of the method call (unless after advise changes it).
  174. // example:
  175. // If there are multiple "around" advisors, the most recent one is executed first,
  176. // which can then delegate to the next one and so on. For example:
  177. // | around(obj, "foo", function(originalFoo){
  178. // | return function(){
  179. // | var start = new Date().getTime();
  180. // | var results = originalFoo.apply(this, arguments); // call the original
  181. // | var end = new Date().getTime();
  182. // | console.log("foo execution took " + (end - start) + " ms");
  183. // | return results;
  184. // | };
  185. // | });
  186. // target: Object
  187. // This is the target object
  188. // methodName: String
  189. // This is the name of the method to attach to.
  190. // advice: Function
  191. // This is function to be called around the original method
  192. };
  193. =====*/
  194. return {
  195. // summary:
  196. // provides aspect oriented programming functionality, allowing for
  197. // one to add before, around, or after advice on existing methods.
  198. // example:
  199. // | define(["dojo/aspect"], function(aspect){
  200. // | var signal = aspect.after(targetObject, "methodName", function(someArgument){
  201. // | this will be called when targetObject.methodName() is called, after the original function is called
  202. // | });
  203. //
  204. // example:
  205. // The returned signal object can be used to cancel the advice.
  206. // | signal.remove(); // this will stop the advice from being executed anymore
  207. // | aspect.before(targetObject, "methodName", function(someArgument){
  208. // | // this will be called when targetObject.methodName() is called, before the original function is called
  209. // | });
  210. before: before,
  211. around: around,
  212. after: after
  213. };
  214. });