用 AOP 装饰函数
Function.prototype.before = function( beforefn ){var __self = this; // 保存原函数的引用return function(){ // 返回包含了原函数和新函数的"代理"函数beforefn.apply( this, arguments ); // 执行新函数,且保证 this 不被劫持,新函数接受的参数// 也会被原封不动地传入原函数,新函数在原函数之前执行return __self.apply( this, arguments ); // 执行原函数并返回原函数的执行结果,// 并且保证 this 不被劫持}}Function.prototype.after = function( afterfn ){var __self = this;return function(){var ret = __self.apply( this, arguments );afterfn.apply( this, arguments );return ret;}};
AOP 的应用实例
数据统计上报
分离业务代码和数据统计代码,无论在什么语言中,都是 AOP 的经典应用之一。在项目开发的结尾阶段难免要加上很多统计数据的代码,这些过程可能让我们被迫改动早已封装好的函数。
比如页面中有一个登录 button,点击这个 button 会弹出登录浮层,与此同时要进行数据上报, 来统计有多少用户点击了这个登录 button:
<html><button tag="login" id="button">点击打开登录浮层</button><script>var showLogin = function(){console.log( '打开登录浮层' );log( this.getAttribute( 'tag' ) );}var log = function( tag ){console.log( '上报标签为: ' + tag );// (new Image).src = 'http:// xxx.com/report?tag=' + tag; // 真正的上报代码略}document.getElementById( 'button' ).onclick = showLogin;</script></html>
我们看到在 showLogin 函数里,既要负责打开登录浮层,又要负责数据上报,这是两个层面 的功能,在此处却被耦合在一个函数里。使用 AOP 分离之后,代码如下:
<html><button tag="login" id="button">点击打开登录浮层</button><script>Function.prototype.after = function( afterfn ){var __self = this;return function(){var ret = __self.apply( this, arguments );afterfn.apply( this, arguments );return ret;}};var showLogin = function(){console.log( '打开登录浮层' );}var log = function(){console.log( '上报标签为: ' + this.getAttribute( 'tag' ) );}showLogin = showLogin.after( log ); // 打开登录浮层之后上报数据document.getElementById( 'button' ).onclick = showLogin;</script></html>
用AOP动态改变函数的参数
插件式的表单验证
<html><body>用户名:<input id="username" type="text"/>密码: <input id="password" type="password"/><input id="submitBtn" type="button" value="提交"></body><script>var username = document.getElementById( 'username' ),password = document.getElementById( 'password' ),submitBtn = document.getElementById( 'submitBtn' );var formSubmit = function(){if ( username.value === '' ){return alert ( '用户名不能为空' );}if ( password.value === '' ){return alert ( '密码不能为空' );}var param = {username: username.value,password: password.value}ajax( 'http:// xxx.com/login', param ); // ajax 具体实现略}submitBtn.onclick = function(){formSubmit();}</script></html>
formSubmit 函数在此处承担了两个职责,除了提交 ajax 请求之外,还要验证用户输入的合法 性。这种代码一来会造成函数臃肿,职责混乱,二来谈不上任何可复用性。
我们把校验输入的逻辑放到 validata 函数中,并且约定当 validata 函数返回 false 的时候,表示校验未通过,代码如下:
var validata = function(){if ( username.value === '' ){alert ( '用户名不能为空' );return false;}if ( password.value === '' ){alert ( '密码不能为空' );return false;}}
接下来进一步优化这段代码,使 validata 和 formSubmit 完全分离开来。首先要改写 Function. prototype.before,如果 beforefn 的执行结果返回 false,表示不再执行后面的原函数,代码如下:
Function.prototype.before = function( beforefn ){var __self = this;return function(){if ( beforefn.apply( this, arguments ) === false ){// beforefn 返回 false 的情况直接 return,不再执行后面的原函数return;}return __self.apply( this, arguments );}}var validata = function(){if ( username.value === '' ){alert ( '用户名不能为空' );return false;}if ( password.value === '' ){alert ( '密码不能为空' );return false;}}var formSubmit = function(){var param = {username: username.value,password: password.value}ajax( 'http:// xxx.com/login', param );}formSubmit = formSubmit.before( validata );submitBtn.onclick = function(){formSubmit();}
在这段代码中,校验输入和提交表单的代码完全分离开来,它们不再有任何耦合关系, formSubmit = formSubmit.before( validata )这句代码,如同把校验规则动态接在 formSubmit 函数 之前,validata 成为一个即插即用的函数,它甚至可以被写成配置文件的形式,这有利于我们分 开维护这两个函数。再利用策略模式稍加改造,我们就可以把这些校验规则都写成插件的形式, 用在不同的项目当中。
思考
typescript 或 es6 中的装饰器函数
