用 AOP 装饰函数

  1. Function.prototype.before = function( beforefn ){
  2. var __self = this; // 保存原函数的引用
  3. return function(){ // 返回包含了原函数和新函数的"代理"函数
  4. beforefn.apply( this, arguments ); // 执行新函数,且保证 this 不被劫持,新函数接受的参数
  5. // 也会被原封不动地传入原函数,新函数在原函数之前执行
  6. return __self.apply( this, arguments ); // 执行原函数并返回原函数的执行结果,
  7. // 并且保证 this 不被劫持
  8. }
  9. }
  10. Function.prototype.after = function( afterfn ){
  11. var __self = this;
  12. return function(){
  13. var ret = __self.apply( this, arguments );
  14. afterfn.apply( this, arguments );
  15. return ret;
  16. }
  17. };

AOP 的应用实例

数据统计上报

分离业务代码和数据统计代码,无论在什么语言中,都是 AOP 的经典应用之一。在项目开发的结尾阶段难免要加上很多统计数据的代码,这些过程可能让我们被迫改动早已封装好的函数。
比如页面中有一个登录 button,点击这个 button 会弹出登录浮层,与此同时要进行数据上报, 来统计有多少用户点击了这个登录 button:

  1. <html>
  2. <button tag="login" id="button">点击打开登录浮层</button>
  3. <script>
  4. var showLogin = function(){
  5. console.log( '打开登录浮层' );
  6. log( this.getAttribute( 'tag' ) );
  7. }
  8. var log = function( tag ){
  9. console.log( '上报标签为: ' + tag );
  10. // (new Image).src = 'http:// xxx.com/report?tag=' + tag; // 真正的上报代码略
  11. }
  12. document.getElementById( 'button' ).onclick = showLogin;
  13. </script>
  14. </html>

我们看到在 showLogin 函数里,既要负责打开登录浮层,又要负责数据上报,这是两个层面 的功能,在此处却被耦合在一个函数里。使用 AOP 分离之后,代码如下:

  1. <html>
  2. <button tag="login" id="button">点击打开登录浮层</button>
  3. <script>
  4. Function.prototype.after = function( afterfn ){
  5. var __self = this;
  6. return function(){
  7. var ret = __self.apply( this, arguments );
  8. afterfn.apply( this, arguments );
  9. return ret;
  10. }
  11. };
  12. var showLogin = function(){
  13. console.log( '打开登录浮层' );
  14. }
  15. var log = function(){
  16. console.log( '上报标签为: ' + this.getAttribute( 'tag' ) );
  17. }
  18. showLogin = showLogin.after( log ); // 打开登录浮层之后上报数据
  19. document.getElementById( 'button' ).onclick = showLogin;
  20. </script>
  21. </html>

用AOP动态改变函数的参数

插件式的表单验证

  1. <html>
  2. <body>
  3. 用户名:<input id="username" type="text"/>
  4. 密码: <input id="password" type="password"/>
  5. <input id="submitBtn" type="button" value="提交">
  6. </body>
  7. <script>
  8. var username = document.getElementById( 'username' ),
  9. password = document.getElementById( 'password' ),
  10. submitBtn = document.getElementById( 'submitBtn' );
  11. var formSubmit = function(){
  12. if ( username.value === '' ){
  13. return alert ( '用户名不能为空' );
  14. }
  15. if ( password.value === '' ){
  16. return alert ( '密码不能为空' );
  17. }
  18. var param = {
  19. username: username.value,
  20. password: password.value
  21. }
  22. ajax( 'http:// xxx.com/login', param ); // ajax 具体实现略
  23. }
  24. submitBtn.onclick = function(){
  25. formSubmit();
  26. }
  27. </script>
  28. </html>

formSubmit 函数在此处承担了两个职责,除了提交 ajax 请求之外,还要验证用户输入的合法 性。这种代码一来会造成函数臃肿,职责混乱,二来谈不上任何可复用性。
我们把校验输入的逻辑放到 validata 函数中,并且约定当 validata 函数返回 false 的时候,表示校验未通过,代码如下:

  1. var validata = function(){
  2. if ( username.value === '' ){
  3. alert ( '用户名不能为空' );
  4. return false;
  5. }
  6. if ( password.value === '' ){
  7. alert ( '密码不能为空' );
  8. return false;
  9. }
  10. }

接下来进一步优化这段代码,使 validata 和 formSubmit 完全分离开来。首先要改写 Function. prototype.before,如果 beforefn 的执行结果返回 false,表示不再执行后面的原函数,代码如下:

  1. Function.prototype.before = function( beforefn ){
  2. var __self = this;
  3. return function(){
  4. if ( beforefn.apply( this, arguments ) === false ){
  5. // beforefn 返回 false 的情况直接 return,不再执行后面的原函数
  6. return;
  7. }
  8. return __self.apply( this, arguments );
  9. }
  10. }
  11. var validata = function(){
  12. if ( username.value === '' ){
  13. alert ( '用户名不能为空' );
  14. return false;
  15. }
  16. if ( password.value === '' ){
  17. alert ( '密码不能为空' );
  18. return false;
  19. }
  20. }
  21. var formSubmit = function(){
  22. var param = {
  23. username: username.value,
  24. password: password.value
  25. }
  26. ajax( 'http:// xxx.com/login', param );
  27. }
  28. formSubmit = formSubmit.before( validata );
  29. submitBtn.onclick = function(){
  30. formSubmit();
  31. }

在这段代码中,校验输入和提交表单的代码完全分离开来,它们不再有任何耦合关系, formSubmit = formSubmit.before( validata )这句代码,如同把校验规则动态接在 formSubmit 函数 之前,validata 成为一个即插即用的函数,它甚至可以被写成配置文件的形式,这有利于我们分 开维护这两个函数。再利用策略模式稍加改造,我们就可以把这些校验规则都写成插件的形式, 用在不同的项目当中。

思考

typescript 或 es6 中的装饰器函数