Example
Imagine that there are two different classes of APIs.
The first is a bit vector: an ordered set of bits
var bits=new BitVector();bits.enable(4);bits.enable([1,3,8,17]);bits.bitAt(4);//1bits.bitAt(8);//1bits.bitAt(9);//0
The Enable method is overloaded and can pass in an array of indexes or indexes.
The second class's API is a collection of strings: unordered collection of strings
var set=new StringSet();set.add(‘Hamlet‘);set.add([‘Rosencrantz‘,‘Guildenstern‘]);set.add({‘Ophelia‘:1,‘Polonius‘:1,‘Horatio‘:1});set.contains(‘Polonius‘);//trueset.contains(‘Guildenstern‘);//trueset.contains(‘Falstaff‘);//false
Where the Add method is overloaded, you can receive a Dictionary object in addition to receiving strings and string arrays. function overloading
To implement the BitVerctor.prototype.enable method, you can test other situations to avoid the problem of how to determine whether an object is an array.
BitVector.prototype.enable=function(x){ if(typeof x === ‘number‘){ this.enableBit(x); }else{ for(var i=0,n=x.length;i< n;i++){ this.enableBit(x[i]); } }};
This only implements the index and index array of judgments, it is easy to implement. How does the StringSet.prototype.add method come true? Here you need to differentiate between arrays and objects. In JS, an array is an object. What you really want to do here is detach the array object and the non-array object.
Such a distinction and the concept of JS's flexible class array object are disputed. Any object can be treated as an array, as long as it follows the correct interface. There is also no clear way to test whether an object satisfies an interface. You can try to treat an object with the length property as an array, but there will also be errors, such as the chance that a Dictionary object has a length attribute?
dimensions.add({ ‘length‘:1, ‘height‘:1, ‘width‘:1});
Using imprecise heuristics to determine an interface is an easy way to be misunderstood and abused. Guessing whether an object implements a struct type is sometimes called a duck test, which is bad practice. Because objects do not have explicit information tokens to represent the types of structures they implement, there is no reliable programmatic way to detect information.
Overloading two types means there must be a way to differentiate between the two cases. It is not possible to detect whether a value implements a structural interface.
Rule-api should never overload types that overlap with other types instanceof
For Stringset, do not start with a structured class array interface. Instead, we should choose a type that has a clearly defined "label" that indicates that the user really wants to use it as an array. An obvious but imperfect option is to use the instanceof operator to test whether an object inherits from Array.prototype.
StringSet.prototype.add=function(x){ if(typeof x === ‘string‘){ this.addString(x); }else if(x instaceof Array){ x.forEach(function(s){ this.addString(s); },this); }};
Any time an instance of an array, it behaves like an array. Sometimes, however, in environments where multiple global objects can be allowed, there may be multiple copies of the standard array constructor and prototype objects. In this case in the browser, each frame will have a separate copy of the standard library. When you communicate across a frame, the array in one frame does not inherit from the Array.prototype of another frame. Array.isarray
In this case, ES5 introduces the Array.isarray function, which is used to test whether a value is an array, regardless of the stereotype inheritance. In the ES standard, the function tests whether an object's internal [[Class]] property value is an array. The Array.isarray method is better than the instanceof operator when it is necessary to test whether an object is a true array, rather than just a class array object.
StringSet.prototype.add=function(x){ if(typeof x === ‘undefined‘){ this.addString(x); }else if(Array.isArray(x)){ x.forEach(function(s){ this.addString(s); },this); }else{ for(var key in x){ this.addString(key); } }};
Object.prototype.toString
In an environment that does not support ES5, you can use the standard Object.prototype.toString method to test whether an object is an array.
var toString=Object.prototype.toString;functin isArray(x){ return toString.call(x) === ‘[object Array]‘;}
The Object.prototype.toString function creates a result string using the [[Class]] property inside the object, so it is more accurate than the instanceof operator when testing whether an object is an array.
Note: This version of the Add method has different behavior that affects the user of the API. The array version of the overloaded API does not receive arbitrary class array objects. For example, you cannot pass in an arguments object and expect it to be treated as an array.
function MyClass(){ this.keys=new StringSet(); //...}MyClass.prototype.update=function(){ this.keys.add(arguments);};
Array.prototype.slice
Arguments will be treated as a dictionary here. You can convert a arguments object into a real array.
MyClass.prototype.update=function(){ this.keys.add([].slice.call(arguments));};
When the caller passes in multiple parameters, they are converted to a real array, and then the add operation is used. This conversion is required when the caller wants to pass a class array object to an API that expects to receive a real array. You can indicate which types of parameters each API receives in the API's documentation. As in the above example, the Enable method receives the numbers and class array objects. The Add method receives strings, true arrays, and non-array objects. Tips
Never overload a struct type that has overlapping other types
When you overload a struct type with another type, test other types first
Receive true arrays instead of class array objects when other object types are overloaded
Document callout Whether your API receives a true array or an array of classes
Test a true array using the Array.isarray method provided by ES5
[Effective JavaScript note] 58th: Distinguish between array objects and class array objects