aspect.js 7.7 KB

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