JavaScript: The Definitive Guide, Sixth Editio javaScript权威指南(第6版) pdf 文字版-文字版, javascript电子书, 和javascript 有关的电子书:

9.5.4 Duck-Typing

// This constructor has no name var Complex = function(x,y) { this.r = x; this.i = y; } // This constructor does have a name var Range = function Range(f,t) { this.from = f; this.to = t; }


9.5.4 Duck-Typing

None of the techniques described above for determining the class of an object are problem-free, at least in client-side JavaScript. An alternative is to sidestep the issue: instead of asking “what is the class of this object?” we ask instead, “what can this object do?” This approach to programming is common in languages like Python and Ruby and is called duck-typing after this expression (often attributed to poet James Whitcomb Riley):

When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.

For JavaScript programmers, this aphorism can be understood to mean “if an object can walk and swim and quack like a Duck, then we can treat it as a Duck, even if it does not inherit from the prototype object of the Duck class.”

The Range class of Example 9-2 serves as an example. This class was designed with numeric ranges in mind. Notice, however, that the Range() constructor does not check its arguments to ensure that they are numbers. It does use the > operator on them, however, so it assumes that they are comparable. Similarly, the includes()method uses the <= operator but makes no other assumptions about the endpoints of the range. Because the class does not enforce a particular type, its includes() method works for any kind of endpoint that can be compared with the relational operators:

var lowercase = new Range("a", "z"); var thisYear = new Range(new Date(2009, 0, 1), new Date(2010, 0, 1));

The foreach() method of our Range class doesn’t explicitly test the type of the range endpoints either, but its use of Math.ceil()and the ++operator means that it only works with numeric endpoints.

As another example, recall the discussion of array-like objects from §7.11 . In many circumstances, we don’t need to know whether an object is a true instance of the Array class: it is enough to know that it has a nonnegative integer length property. The existence of an integer-valued length is how arrays walk, we might say, and any object that can walk in this way can (in many circumstances) be treated as an array.

Keep in mind, however, that the length property of true arrays has special behavior: when new elements are added, the length is automatically updated, and when the length is set to a smaller value, the array is automatically truncated. We might say that this is how arrays swim and quack. If you are writing code that requires swimming and quacking, you can’t use an object that only walks like an array.

9.5 Classes and Types | 213

The examples of duck-typing presented above involve the response of objects to the < operator and the special behavior of the length property. More typically, however, when we talk about duck-typing, we’re talking about testing whether an object implements one or more methods. A strongly-typed triathlon() function might require its argument to be an TriAthlete object. A duck-typed alternative could be designed to accept any object that has walk(), swim(), and bike() methods. Less frivolously, we might redesign our Range class so that instead of using the < and ++ operators, it uses the compareTo() and succ() (successor) methods of its endpoint objects.

One approach to duck-typing is laissez-faire: we simply assume that our input objects implement the necessary methods and perform no checking at all. If the assumption is invalid, an error will occur when our code attempts to invoke a nonexistent method. Another approach does check the input objects. Rather than check their class, however, it checks that they implement methods with the appropriate names. This allows us to reject bad input earlier and can result in more informative error messages.

Example 9-5 defines a quacks() function (“implements” would be a better name, but implements is a reserved word) that can be useful when duck-typing. quacks() tests whether an object (the first argument) implements the methods specified by the remaining arguments. For each remaining argument, if the argument is a string, it checks for a method by that name. If the argument is an object, it checks whether the first object implements methods with the same names as the methods of that object. If the argument is a function, it is assumed to be a constructor, and the function checks whether the first object implements methods with the same names as the prototype object.

Example 9-5. A function for duck-type checking

// Return true if o implements the methods specified by the remaining args. function quacks(o /*, ... */) {

for(var i = 1; i < arguments.length; i++) { // for each argument after o

var arg = arguments[i];

switch(typeof arg) { // If arg is a:

case 'string': // string: check for a method with that name

if (typeof o[arg] !== "function") return false;

continue;

case 'function': // function: use the prototype object instead

// If the argument is a function, we use its prototype object

arg = arg.prototype;

// fall through to the next case

case 'object': // object: check for matching methods

for(var m in arg) { // For each property of the object

if (typeof arg[m] !== "function") continue; // skip non-methods

if (typeof o[m] !== "function") return false;

}

}

}

// If we're still here, then o implements everything

return true; }

There are a couple of important things to keep in mind about this quacks() function. First, it only tests that an object has one or more function-valued properties with specified names. The existence of these properties doesn’t tell us anything about what those functions do or how many and what kind of arguments they expect. This, however, is the nature of duck-typing. If you define an API that uses duck-typing rather than a stronger version of type checking, you are creating a more flexible API but also entrusting the user of your API with the responsibility to use the API correctly. The second important point to note about the quacks() function is that it doesn’t work with built-in classes. For example, you can’t write quacks(o, Array) to test that o has methods with the same names as all Array methods. This is because the methods of the built-in classes are nonenumerable and the for/in loop in quacks() does not see them. (Note that this can be remedied in ECMAScript 5 with the use of Object.getOwnProperty Names().)

欢迎转载,转载请注明来自一手册:http://yishouce.com/book/1/31379.html
友情链接It题库(ittiku.com)| 版权归yishouce.com所有| 友链等可联系 admin#yishouce.com|粤ICP备16001685号-1