可鏈式物件設計和連結

Chaining 和 Chainable 是一種用於設計物件行為的設計方法,以便對物件函式的呼叫返回對 self 或其他物件的引用,提供對其他函式呼叫的訪問,允許呼叫語句將多個呼叫連結在一起而無需引用變數持有物件。

可以連結的物件被認為是可連結的。如果呼叫物件可連結,則應確保所有返回的物件/基元的型別正確。你的可連結物件只需要一次不返回正確的引用(很容易忘記新增 return this),使用你的 API 的人將失去信任並避免連結。可鏈式物件應該是全部或全部(即使是部件也不是可連結物件)。如果只有一些函式,則不應將物件稱為可連結物件。

物件設計為可連結

function Vec(x = 0, y = 0){
    this.x = x;
    this.y = y;
    // the new keyword implicitly implies the return type 
    // as this and thus is chainable by default.
}
Vec.prototype = {
    add : function(vec){
        this.x += vec.x;
        this.y += vec.y;
        return this; // return reference to self to allow chaining of function calls
    },
    scale : function(val){
        this.x *= val;
        this.y *= val;
        return this; //  return reference to self to allow chaining of function calls
    },
    log :function(val){
        console.log(this.x + ' : ' + this.y);
        return this;
    },
    clone : function(){
        return new Vec(this.x,this.y);
    }
}

連結的例子

var vec = new Vec();
vec.add({x:10,y:10})
    .add({x:10,y:10})
    .log()             // console output "20 : 20"
    .add({x:10,y:10})
    .scale(1/30)
    .log()             // console output "1 : 1"
    .clone()           // returns a new instance of the object
    .scale(2)          // from which you can continue chaining
    .log()

不要在返回型別中建立歧義

並非所有函式呼叫都返回有用的可連結型別,它們也不會始終返回對 self 的引用。這是常識使用命名很重要的地方。在上面的例子中,函式呼叫 .clone() 是明確的。其他示例是 .toString() 意味著返回一個字串。

可鏈式物件中不明確的函式名稱的示例。

 // line object represents a line
 line.rotate(1)
    .vec();  // ambiguous you don't need to be looking up docs while writing.

 line.rotate(1)
    .asVec()    // unambiguous implies the return type is the line as a vec (vector)
    .add({x:10,y:10)
 // toVec is just as good as long as the programmer can use the naming 
 // to infer the return type

語法約定

連結時沒有正式的用法語法。慣例是將單個行上的呼叫連結起來,或者在新行上鍊接從引用的物件中縮排一個製表符,並在新行上新增點。分號的使用是可選的,但通過清楚地表示鏈的末端確實有幫助。

  vec.scale(2).add({x:2,y:2}).log();  // for short chains

  vec.scale(2)     // or alternate syntax
      .add({x:2,y:2})
      .log();  // semicolon makes it clear the chain ends here

  // and sometimes though not necessary
  vec.scale(2)     
      .add({x:2,y:2})
      .clone()    // clone adds a new reference to the chain
           .log(); // indenting to signify the new reference

  // for chains in chains
  vec.scale(2)     
      .add({x:2,y:2})
      .add(vec1.add({x:2,y:2})  // a chain as an argument 
           .add({x:2,y:2})      // is indented
           .scale(2))
      .log();

  // or sometimes 
  vec.scale(2)     
      .add({x:2,y:2})
      .add(vec1.add({x:2,y:2})  // a chain as an argument 
           .add({x:2,y:2})      // is indented
           .scale(2)
      ).log();   // the argument list is closed on the new line

語法錯誤

   vec          // new line before the first function call
      .scale()  // can make it unclear what the intention is
      .log();

   vec.          // the dot on the end of the line
      scale(2).  // is very difficult to see in a mass of code
      scale(1/2); // and will likely frustrate as can easily be missed
                  // when trying to locate bugs

任務的左手邊

分配鏈的結果時,將分配最後一個返回的呼叫或物件引用。

 var vec2 = vec.scale(2)
                .add(x:1,y:10)
                .clone();   // the last returned result is assigned
                                // vec2 is a clone of vec after the scale and add

在上面的例子中,vec2 被賦予從鏈中最後一次呼叫返回的值。在這種情況下,這將是縮放和新增後的 vec 的副本。

摘要

更改的優點是更清晰,更易於維護的程式碼。有些人更喜歡它,並且在選擇 API 時會產生可連結的要求。還有一個效能優勢,因為它允許你避免必須建立變數來儲存中間結果。最後一句話是可連結物件也可以以傳統方式使用,因此你不會通過使物件可連結來強制連結。