可链式对象设计和链接

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 时会产生可链接的要求。还有一个性能优势,因为它允许你避免必须创建变量来保存中间结果。最后一句话是可链接对象也可以以传统方式使用,因此你不会通过使对象可链接来强制链接。