如何在"向上"和"向下"方向上操作DOM时避免重复代码?

Jak*_* P. 7 javascript jquery refactoring code-duplication

我正在写一个JS webapp客户端.用户可以编辑文本项的列表/树(例如,待办事项列表或备注).我用jQuery操作DOM很多.

用户可以使用键盘上下导航列表(类似于GMail中的J/K键),并执行其他几项操作.这些操作中的许多操作具有镜像"向上"/"向下"功能,例如

$.fn.moveItemUp = function() {
    var prev = this.getPreviousItem();
    prev && this.insertBefore(prev);
    // there's a bit more code in here, but the idea is pretty simple,
    // i.e. move the item up if there's a previous item in the list
}

$.fn.moveItemDown = function() {
    var next = this.getNextItem();
    next && this.insertAfter(next);
    // ....
}
Run Code Online (Sandbox Code Playgroud)

现在这个具有两个几乎相同的函数的模式在我的代码中的几个地方重复,因为在列表/树上有许多操作非常对称.

问题:如何优雅地重构这一点以避免代码重复?

到目前为止我提出的琐碎方法是使用.apply()......

$.fn.moveItem = function(direction) {
        var up = direction === 'up',
            sibling = up ? this.getPreviousItem() : this.getNextItem(), 
            func = up ? $.fn.insertBefore : $.fn.insertAfter;

        // ...

        if (! sibling) { return false; }

        func.apply(this, [ sibling ]);

        // ...
    };
Run Code Online (Sandbox Code Playgroud)

当代码的其他元素的结构需要更改moveUp/moveDown时,更容易维护.我已经需要多次稍微改变代码了,我总是需要记住,我需要在两个地方做这个...

但我对"统一"版本不满意,因为:

  • 实现比简单的moveUp/moveDown更复杂,因此将来可能不会那么容易维护.我喜欢简单的代码.所有这些参数,三元操作,.apply()都会在每个应该"向上"和"向下"的函数中诡计......
  • 不确定是否可以显式地引用jQuery.fn.*函数(毕竟它们应该通过将它们应用于jQuery对象$('...')来使用.*)

在使用DOM或类似结构时,如何解决那些"几乎相同的代码"情况?

hug*_*omg 4

您可以做的一件事是使用闭包来隐藏从用户传递的配置参数:

var mk_mover = function(direction){
     return function(){
         //stuff
     };
};

var move_up = mk_mover("up");
var move_down = mk_mover("down");
Run Code Online (Sandbox Code Playgroud)

如果您想继续朝这个方向发展,那么基本上就变成了决定将参数传递给通用实现的最佳方法是什么的问题,并且对此没有单一的最佳解决方案。


一种可能的方向是使用 OO 风格的方法,将配置“策略”对象传递给实现函数。

var mk_mover = function(args){
    var get_item = args.getItem,
        ins_next = args.insNext;
    return function(){
        var next = get_item.call(this);
        next && ins_next.call(this, next);
    };
 };

 var move_up = mk_mover({
     get_item : function(){ return this.getPrevious(); },
     get_next : function(x){ return this.insertBefore(x); }
 });

 var move_down = mk_mover({/**/});
Run Code Online (Sandbox Code Playgroud)

当策略接口(方向之间不同的方法)很小并且相对恒定时,并且当您想要在未来添加新类型的方向的可能性时,我更喜欢这样做。

当我知道方向集和方法类型都不需要做太多改变时,我也倾向于使用它,因为 JS 对 OO 的支持比对 switch 语句的支持更好。


另一个可能的方向是您使用的枚举方法:

var mk_mover = function(direction){
    return function(){
        var next = (
            direction === 'up' ? this.getNextItem() :
            direction === 'down' ? this.getPreviousItem() :
            null );

        if(next){
            if(direction === 'up'){
                this.insertAfter(next);
            }else if(direction === 'down'){
                this.insertBefore(next);
            }
        }

        //...
    };
}
Run Code Online (Sandbox Code Playgroud)

有时您可以使用简洁的对象或数组而不是 switch 语句来使事情变得更漂亮:

var something = ({
    up : 17,
    down: 42
}[direction]);
Run Code Online (Sandbox Code Playgroud)

虽然我不得不承认这有点笨拙,但它的优点是,如果您的一组方向在早期就已固定,那么您现在可以非常灵活地在需要时添加新的 if-else 或 switch 语句,以自包含的方式(不需要在其他地方的策略对象中添加一些方法......)


顺便说一句,我认为您建议的方法处于我建议的两个版本之间令人不舒服的中间地带。

如果您所做的只是根据传递的值方向标记在函数内部进行切换,那么最好直接在需要的位置进行切换,而不必将事物重构为单独的函数,然后进行大量烦人的 .call并.应用东西。

另一方面,如果您经历了定义和分离函数的麻烦,您可以直接从调用者那里接收它们(以策略对象的形式),而不是自己手动进行分派。