本文深入探讨JavaScript中面向切面编程(AOP)的实际应用。通过定义before和after切面函数,实现无侵入式的代码增强。文章涵盖防止window.onload覆盖、无侵入统计、表单校验分离、Ajax动态参数注入、职责链模式及组合优于继承等六个核心场景,展示如何利用AOP提升代码的可维护性与灵活性。
AOP基础与核心概念
AOP即面向切面编程,熟悉Spring框架的开发者对其并不陌生。然而在JavaScript领域,AOP往往被忽视。本文将通过几个具体示例,展示AOP在JS中的独特价值。
本文主要探讨以下六个应用场景:1. 防止window.onload被二次覆盖;2. 实现无侵入式的统计代码;3. 分离表单请求与校验逻辑;4. 为Ajax请求动态添加参数;5. 应用职责链模式;6. 以组合方式替代继承。
首先定义before和after两个基础“切面”函数。顾名思义,它们分别用于在目标函数执行前或执行后介入。巧妙之处在于,before和after函数可以共享当前函数的this上下文和arguments参数,这极大地扩展了应用场景。

防止window.onload被覆盖
处理window.onload被二次覆盖的问题。
此前在技术交流群中有人提问:如何改写window.onload,同时避免覆盖原有函数?
最原始的做法是直接修改原有的window.onload函数,在其中追加新代码。

这种方法的弊端显而易见:必须修改原有函数,侵入性极强。
另一种稍好的方案是使用中间变量保存原有的window.onload引用。

这种方法引入了一个名为__onload的中间变量,管理该变量需要额外的成本。
设想一个场景:当人觉得冷出门时,自然会选择穿上一件貂皮大衣,而不是割掉自己的皮肤再换上。动态装饰的优势正在于此,它完全不会侵入原有函数。

无侵入式统计代码实现
实现无侵入式的统计代码。
与业务逻辑无关的统计代码往往需要硬插进函数中,这令许多负责数据上报的开发者感到困扰。例如,统计创建一个包含1000个节点的函数在用户电脑上执行所需的时间。

采用AOP方式,无需修改函数内部代码,只需定义一个通用的包装器。

仅需一行代码,即可为任何函数添加计时统计功能。

表单请求与校验分离
分离表单请求与校验逻辑。
在提交表单前,我们常需进行校验以确定是否允许提交。最糟糕的写法是将验证逻辑直接写在send函数内部。

更优的做法是将所有校验规则封装在策略模式中,返回false或true以决定验证结果。这样可以灵活选择和更换校验规则。

上述方法仍存在缺点:校验与发送请求耦合在同一函数中。利用AOP可将二者分离,将validata插件化,实现真正的即插即用。只需将send函数修改为:

回顾前文Function.prototype.before的代码可知,我们约定:若前一个函数返回false,则阻断后续函数执行。因此,当validata返回false时,send不再继续执行。由于before函数可与当前函数共享this和arguments,value参数也能顺利传递给validata函数。
Ajax请求动态参数注入
为Ajax请求动态添加参数。
第一个例子中window.onload使用的是after后置装饰,此处则使用before前置装饰,在Ajax请求发出前动态添加参数。
我们常遇到跨域请求场景,jsonp和iframe是常用方式。此前项目中,通过参数retype=jsonp标识jsonp请求,retype=iframe标识iframe请求。除该参数外,两者其他参数完全一致。利用before可将retype参数动态注入。
先定义一个Ajax请求的代理函数。
该函数不包含逻辑处理或分支语句,也不关心是jsonp还是iframe请求。它仅负责发送数据,是一个职责单一的优良函数。
接下来在发送请求前放置一个before装饰器。

开始发送请求:

职责链模式应用
应用职责链模式。
职责链模式在JS中的典型应用是事件冒泡。将所有子节点和父节点连接成一条链,沿链传递事件,直到某个节点处理为止。职责链模式是消除大量if-else语句的有效工具。
以近期一个文件上传需求为例,提供了控件、html5、flash、表单上传四种方式。根据优先级及浏览器支持情况判断当前使用哪种方式。改造前的伪代码如下:

实际代码远不止于此,还包括各种控件初始化、容错处理等。若需屏蔽flash,看似简单,实则如同在心脏旁拆除一根血管般困难。
尝试使用职责链模式,看看情况会简化多少:
第一步改写之前的after函数,使其在返回对象时阻断职责链传递,返回null时继续传递请求。

接下来将每种控件的创建方式包裹在各自函数中,确保逻辑无交叉且互不污染。

最后用职责链将它们串联起来:

预见未来某天需再次屏蔽flash,此时只需修改这一行代码。修改为:

组合优于继承
以组合方式替代继承。
程序设计时常面临组合与继承的选择。通常而言,组合更灵活轻巧。仍以之前的文件上传为例。
定义一个超类Upload,衍生出四个子类:Plugin_Upload、Html5_Upload、Flash_Upload以及Form_Upload。
Plugin_Upload继承父类,获得Upload的大部分功能,并对控件上传特性进行定制。例如,其他三种上传方式选择文件后直接上传,而控件上传在开始前需经过一轮文件扫描。
第一种做法是Plugin_Upload继承Upload,然后重写其start_upload方法。

采用更轻量的组合方式,可直接给原start_upload函数装饰上扫描功能,甚至无需衍生额外子类。
