2012-05-14 30 views
7

Tôi đang viết một ứng dụng webapp JS. Người dùng có thể chỉnh sửa danh sách/cây của các mục văn bản (ví dụ: danh sách việc cần làm hoặc ghi chú). Tôi thao tác DOM với jQuery rất nhiều.Làm thế nào để tránh mã trùng lặp khi hoạt động trên DOM theo hướng "lên" và "xuống"?

Người dùng có thể điều hướng danh sách lên và xuống bằng bàn phím (tương tự như phím J/K trong Gmail) và thực hiện một số thao tác khác. Nhiều hoạt động trong số này có chức năng phản chiếu "up"/"down", ví dụ:

$.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); 
    // .... 
} 

Bây giờ mô hình này có hai chức năng gần như giống hệt được lặp lại ở một số nơi trong mã của tôi bởi vì có rất nhiều hoạt động thuộc danh mục/cây các mặt hàng được khá nhiều đối xứng.

CÂU HỎI: Làm cách nào để refactor thanh lịch này để tránh trùng lặp mã?

Cách tầm thường tôi đã đưa ra cho đến nay là sử dụng .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 ]); 

     // ... 
    }; 

Lợi ích là dễ dàng bảo trì khi cấu trúc của các yếu tố khác của mã đòi hỏi phải thay đổi moveUp/moveDown. Tôi đã cần phải thay đổi một chút mã nhiều lần, và tôi luôn luôn cần phải nhớ rằng tôi cần phải làm điều đó ở hai nơi ...

Nhưng tôi không hài lòng với phiên bản "thống nhất", bởi vì:

  • Triển khai phức tạp hơn di chuyển đơn giản/di chuyểnDown, do đó trong tương lai nó có thể KHÔNG dễ dàng để duy trì sau đó. Tôi thích mã đơn giản. Tất cả các tham số này, các phép toán ba giây, các thủ thuật .apply() trong mọi hàm được cho là đi "lên" và "xuống" ...
  • Không chắc chắn nếu nó an toàn để tham khảo các hàm jQuery.fn. * Một cách rõ ràng (sau khi tất cả chúng được cho là được sử dụng bằng cách áp dụng chúng cho đối tượng jQuery $ ('...'). *)

Làm thế nào để bạn giải quyết các tình huống "hầu như giống hệt mã" khi làm việc với DOM hoặc cấu trúc tương tự?

+1

BTW hugomg

Trả lời

4

Một điều mà bạn có thể làm là sử dụng đóng cửa để ẩn các thông số cấu hình đi từ người sử dụng:

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

var move_up = mk_mover("up"); 
var move_down = mk_mover("down"); 

Nếu bạn muốn tiếp tục theo hướng này, nó trở thành cơ bản là một vấn đề quyết định cách tốt nhất để chuyển các tham số cho việc triển khai chung là gì và không có giải pháp tốt nhất cho việc này.


Một hướng có thể thực hiện là sử dụng cách tiếp cận kiểu OO, chuyển đối tượng "chiến lược" cấu hình đến chức năng triển khai.

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

Tôi thích làm điều này khi giao diện chiến lược (phương pháp khác nhau giữa hướng) nhỏ và tương đối ổn định và khi bạn muốn mở thêm khả năng thêm hướng mới trong tương lai.

Tôi cũng có xu hướng sử dụng điều này khi tôi biết rằng không phải tập hợp chỉ đường cũng như các loại phương thức sẽ cần phải thay đổi nhiều, vì JS hỗ trợ tốt hơn cho OO sau đó cho báo cáo chuyển đổi.


Các hướng khác có thể đi là cách tiếp cận enum mà bạn sử dụng:

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

     //... 
    }; 
} 

Đôi khi bạn có thể sử dụng đối tượng gọn gàng hoặc mảng thay vì tuyên bố chuyển sang làm cho mọi việc đẹp:

var something = ({ 
    up : 17, 
    down: 42 
}[direction]); 

Trong khi tôi phải thú nhận rằng đây là loại vụng về, nó có lợi thế là nếu tập hợp các chỉ đường của bạn được khắc phục sớm, bạn hiện có rất nhiều tính linh hoạt để thêm một if-else hoặc switc mới tuyên bố h bất cứ khi nào bạn cần, một cách khép kín (mà không cần phải đi thêm một số phương pháp để đối tượng chiến lược ở một nơi khác ...)


BTW, tôi nghĩ rằng cách tiếp cận bạn đề nghị nằm trong một vùng đất giữa khó chịu giữa hai phiên bản tôi đề nghị.

Nếu tất cả những gì bạn đang làm là chuyển đổi bên trong chức năng của bạn tùy thuộc vào thẻ định hướng giá trị, tốt hơn là chỉ thực hiện các công tắc trực tiếp tại các điểm bạn cần mà không cần phải cấu trúc lại các chức năng riêng biệt và sau đó phải thực hiện nhiều thao tác công cụ gây phiền nhiễu .call và .apply.Mặt khác, nếu bạn gặp khó khăn trong việc xác định và tách các chức năng bạn có thể thực hiện để bạn chỉ nhận được chúng trực tiếp từ người gọi (dưới dạng một đối tượng chiến lược) thay vì thực hiện việc gửi đi tự tay.

+0

Thx cho các đề xuất tốt, đặc biệt là ý tưởng về phương pháp "nhà máy". –

2

Tôi đã gặp phải các vấn đề tương tự trước đây và không thực sự là một mẫu tuyệt vời mà tôi đã tìm thấy để xử lý các tác vụ có vẻ tương tự ngược lại.

Trong khi nó có ý nghĩa với bạn như một con người mà họ đang tương tự, bạn thực sự chỉ cần gọi phương pháp tùy ý như xa như trình biên dịch là có liên quan. Việc sao chép rõ ràng chỉ là thứ tự mà điều này được gọi là:

  1. tìm một mục mà chúng tôi muốn để chèn trước hoặc sau
  2. kiểm tra rằng mục tồn tại
  3. chèn mục hiện hành trước hoặc sau it

Bạn đã giải quyết được vấn đề theo một cách đã thực hiện những kiểm tra này. Tuy nhiên, giải pháp của bạn không thể đọc được. Dưới đây là cách tôi sẽ giải quyết vấn đề đó:

$.fn.moveItem = function(dir){ 
    var index = dir == 'up' ? 0:1, 
     item = this['getPreviousItem','getNextItem'][index](); 
    if(item.length){ 
    this['insertBefore','insertAfter'][index].call(this,item); 
    } 
} 

Bằng cách sử dụng chỉ mục mảng, chúng tôi có thể nhìn thấy những gì được gọi dễ dàng hơn trong ví dụ của bạn. Ngoài ra, việc sử dụng các ứng dụng là không cần thiết, như bạn biết có bao nhiêu đối số bạn đang đi qua, do đó, cuộc gọi là phù hợp hơn với trường hợp sử dụng đó.

Không chắc chắn nếu điều đó tốt hơn, nhưng ngắn hơn một chút (là trả về false cần thiết?) Và IMO dễ đọc hơn một chút.

Theo tôi, các hành động tập trung quan trọng hơn khả năng đọc - nếu bạn muốn tối ưu hóa, thay đổi hoặc gắn thêm vào hành động di chuyển thứ gì đó, bạn chỉ phải thực hiện nó ở một nơi. Bạn luôn có thể thêm nhận xét để giải quyết vấn đề về khả năng đọc.

+0

áp dụng vs call - g2k (thêm tại: http://stackoverflow.com/questions/ 1986896/cái gì-là-sự khác biệt-giữa-gọi-và-áp dụng). –

1

Tôi nghĩ rằng cách tiếp cận một chức năng là tốt nhất, nhưng tôi sẽ tách nó ra khỏi điều đó lên/xuống. Thực hiện một chức năng để di chuyển một mục vào một chỉ mục hoặc trước một mục khác (tôi thích mục sau) và xử lý tất cả các sự kiện "di chuyển" một cách tổng quát ở đó, không phụ thuộc vào một hướng.

function moveBefore(node) { 
    this.parentNode.insertBefore(this, node); // node may be null, then it equals append() 

    // do everything else you need to handle when moving items 
} 
function moveUp() { 
    var prev = this.previousSibling; 
    return !!prev && moveBefore.call(this, prev); 
} 
function moveDown() { 
    var next = this.nextSibling; 
    return !!next && moveBefore.call(this, next.nextSibling); 
} 
+0

Cảm ơn bạn đã gợi ý tốt để tách biệt điểm chung với nhau! –

Các vấn đề liên quan