JavaScript装饰者模式

栏目: JavaScript · 发布时间: 6年前

内容简介:本文是《JavaScript设计模式与开发实践》的学习笔记,例子来源于书中,对于设计模式的看法,推荐看看本书作者的装饰者模式能够在不改变对象自身的基础上,在运行程序期间给对象动态地添加职责。这是一种轻便灵活的做法,装饰者是一种“即付即用”的方式,比如天冷了就多穿一件外套。想要为函数添加一些功能,最简单粗暴的方式就是直接改写该函数,但是这是最差的办法,直接违反了开放——封闭原则。

本文是《JavaScript设计模式与开发实践》的学习笔记,例子来源于书中,对于 设计模式 的看法,推荐看看本书作者的 建议

什么是装饰者模式?

给对象动态增加职责的方式成为装饰者模式。

装饰者模式能够在不改变对象自身的基础上,在运行程序期间给对象动态地添加职责。这是一种轻便灵活的做法,装饰者是一种“即付即用”的方式,比如天冷了就多穿一件外套。

装饰函数

想要为函数添加一些功能,最简单粗暴的方式就是直接改写该函数,但是这是最差的办法,直接违反了开放——封闭原则。

var a = function(){
    alert(1)
}
// 改成
var a = function(){
    alert(1)
    alert(2)
}

很多时候我们不想碰原函数,也许原函数是其他同事编写的,甚至在一个古老的项目中,这个函数的源代码被隐藏在一个我们不愿触碰的阴暗角落里。现在需要不改变源代码的情况下,给函数增加功能。

我们通过保存原引用的方式改写某个函数。

var a = function(){
    alert(1)
}
var _a = a
a = function(){
    _a()
    alert(2)
}
a()

这是实际开发中很常见的一个做法,比如我们想给 window 绑定 onload 事件,但是又不确定这个事件是不是已经被其他人绑定过,为了避免覆盖掉之前的 window.onload 函数中的行为,先保存 window.onload,把它放入新的 window.onload。

window.onload = function(){
    alert(1)
}

var _onload = window.onload || function(){}

window.onload = funtion(){
    _onload()
    alert(2)
}

这样的代码是符合封闭——开放原则,我们在增加新功能的时候确实没有修改原来的代码,但是这种方式存在两个问题:

  1. 必须维护 _onload 这个中间变量,虽然看起来不起眼,但是如果函数装饰链较长,或者需要装饰的函数变多,这些中间变量的数量也会越来越多。
  2. 其实还遇到了 this 被劫持的问题,在 window.onload 的例子中没有这个烦恼,因为调用 _onload 的时候 this 也指向 window,跟调用 window.onload 的时候一样。

用 AOP 装饰函数

AOP(Aspect Oriented Programming)面向切面编程的主要作用是:把一些跟核心业务逻辑无关的功能抽离出来,这些跟业务逻辑无关的功能通常包括日志统计、安全控制、异常处理等。把这些功能抽离出来以后,再通过“动态织入”的方式掺入业务逻辑模块中。这样的好处首先是可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用日志统计等功能模块。

首先给出 Function.prototype.before 和 Function.prototype.after 方法:

Function.prototype.before = function(beforefn){
    // 保存原函数的引用
    var _self = this
    // 返回包含原函数和新函数的“代理函数”
    return function(){
        // 执行新函数,保证this不被劫持,
        // 新函数接受的参数也会被原封不动地传入原函数,
        // 新函数在原函数之前执行
        beforefn.apply(this,arguments)
        return _self.apply(this.arguments)
    }
}

Function.prototype.after = function(afterfn){
    var _self = this
    return function(){
        var ret = _self.apply(this,arguments)
        afterfn.apply(this,arguments)
        return ret
    }
}

“代理函数”只是结构上像代理而已,并不承担代理的职责(比如控制对象的访问),它的工作是把请求分别转发给新添加的函数和原函数,且负责保证它们的执行顺序。

再回到 window.onload 的例子中,用 Function.prototype.after 来增加新事件:

window.onload = function(){
    alert(1)
}

window.onload = (window.onload || function(){}).after(function(){
    alert(2)
}).after(function(){
    alert(3)
})

AOP 的应用实例

(1)数据统计上报

<html>
    <button tag="login" id="button">点击打开登录浮层</button>
    <script>
        var showLogin = function(){
        console.log( '打开登录浮层' )
        log( this.getAttribute( 'tag' ) )
    }
        var log = function( tag ){
            console.log( '上报标签为: ' + 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>

(2) 插件式表单验证

<html>
<body>
    用户名:<input id="username" type="text"/>
    密码: <input id="password" type="password"/>
    <input id="submitBtn" type="button" value="提交"></button>
</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>

formatSubmit 函数承担了两个职责,除了提交ajax请求,还要验证用户输入的合法性。我们把校验输入的逻辑放到validata函数中,并约定当validata函数返回false的时候表示校验未通过。

var validata = function(){
    if ( username.value === '' ){
        alert ( '用户名不能为空' );
        return false;
    }
    if ( password.value === '' ){
        alert ( '密码不能为空' );
        return false;
    }
}

var formSubmit = function(){
    if ( validata() === false ){ // 校验未通过
    return;
    }
    var param = {
        username: username.value,
        password: password.value
    }
    ajax( 'http:// xxx.com/login', param );
}

submitBtn.onclick = function(){
    formSubmit();
}

使用AOP优化代码

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();
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

An Introduction to the Analysis of Algorithms

An Introduction to the Analysis of Algorithms

Robert Sedgewick、Philippe Flajolet / Addison-Wesley Professional / 1995-12-10 / CAD 67.99

This book is a thorough overview of the primary techniques and models used in the mathematical analysis of algorithms. The first half of the book draws upon classical mathematical material from discre......一起来看看 《An Introduction to the Analysis of Algorithms》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具