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