/** * A Range class using a constructor */ // range2.js: Another class representing a range of values. // This is a constructor function that initializes new Range objects. // Note that it does not create or return the object. It just initializes this. functionRange(from, to) { // Store the start and end points (state) of this new range object. // These are noninherited properties that are unique to this object. this.from = from; this.to = to; } // All Range objects inherit from this object. // Note that the property name must be "prototype" for this to work. Range.prototype = { // Return true if x is in the range, false otherwise // This method works for textual and Date ranges as well as numeric. includes: function(x) { returnthis.from <= x && x <= this.to; }, // Invoke f once for each integer in the range. // This method works only for numeric ranges. foreach: function(f) { for (var x = Math.ceil(this.from); x <= this.to; x++) f(x); }, // Return a string representation of the range toString: function() { return"(" + this.from + "..." + this.to + ")"; } }; // Here are example uses of a range object var r = newRange(1, 3); // Create a range object r.includes(2); // => true: 2 is in the range r.foreach(console.log); // Prints 1 2 3 console.log(r); // Prints (1...3)
// Define the ES5 String.trim() method if one does not already exist. // This method returns a string with whitespace removed from the start and end. String.prototype.trim = String.prototype.trim || function() { if (!this) returnthis; // Don't alter the empty string returnthis.replace(/^\s+|\s+$/g, ""); // Regular expression magic }; // Return a function's name. If it has a (nonstandard) name property, use it. // Otherwise, convert the function to a string and extract the name from that. // Returns an empty string for unnamed functions like itself. Function.prototype.getName = function() { returnthis.name || this.toString().match(/function\s*([^(]*)\(/)[1]; };
/** * A type() function to determine the type of a value * Return the type of o as a string: * -If o is null, return "null", if o is NaN, return "nan". * -If typeof returns a value other than "object" return that value. * (Note that some implementations identify regexps as functions.) * -If the class of o is anything other than "Object", return that. * -If o has a constructor and that constructor has a name, return it. * -Otherwise, just return "Object". **/ functiontype(o) { var t, c, n; // type, class, name // Special case for the null value: if (o === null) return"null"; // Another special case: NaN is the only value not equal to itself: if (o !== o) return"nan"; // Use typeof for any value other than "object". // This identifies any primitive value and also functions. if ((t = typeof o) !== "object") return t; // Return the class of the object unless it is "Object". // This will identify most native objects. if ((c = classof(o)) !== "Object") return c; // Return the object's constructor name, if it has one if (o.constructor && typeof o.constructor === "function" && (n = o.constructor.getName())) return n; // We can't determine a more specific type, so return "Object" return"Object"; } // Return the class of an object. functionclassof(o) { returnObject.prototype.toString.call(o).slice(8, -1); }; // Return the name of a function (may be "") or null for nonfunctions Function.prototype.getName = function() { if ("name"inthis) returnthis.name; returnthis.name = this.toString().match(/function\s*([^(]*)\(/)[1]; };
9.5.4 鸭式辩型
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.
/** * A function for duck-type checking */ // Return true if o implements the methods specified by the remaining args. functionquacks(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") returnfalse; 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") returnfalse; } } } // If we're still here, then o implements everything returntrue; }
/** * An arbitrary set of values */ functionSet() { // This is the constructor this.values = {}; // The properties of this object hold the set this.n = 0; // How many values are in the set this.add.apply(this, arguments); // All arguments are values to add } // Add each of the arguments to the set. Set.prototype.add = function() { for (var i = 0; i < arguments.length; i++) { // For each argument var val = arguments[i]; // The value to add to the set var str = Set._v2s(val); // Transform it to a string if (!this.values.hasOwnProperty(str)) { // If not already in the set this.values[str] = val; // Map string to value this.n++; // Increase set size } } returnthis; // Support chained method calls }; // Remove each of the arguments from the set. Set.prototype.remove = function() { for (var i = 0; i < arguments.length; i++) { // For each argument var str = Set._v2s(arguments[i]); // Map to a string if (this.values.hasOwnProperty(str)) { // If it is in the set deletethis.values[str]; // Delete it this.n--; // Decrease set size } } returnthis; // For method chaining }; // Return true if the set contains value; false otherwise. Set.prototype.contains = function(value) { returnthis.values.hasOwnProperty(Set._v2s(value)); }; // Return the size of the set. Set.prototype.size = function() { returnthis.n; }; // Call function f on the specified context for each element of the set. Set.prototype.foreach = function(f, context) { for (var s inthis.values) // For each string in the set if (this.values.hasOwnProperty(s)) // Ignore inherited properties f.call(context, this.values[s]); // Call f on the value }; // This internal function maps any JavaScript value to a unique string. Set._v2s = function(val) { switch (val) { caseundefined: return'u'; // Special primitive casenull: return'n'; // values get single-letter casetrue: return't'; // codes. casefalse: return'f'; default: switch (typeof val) { case'number': return'#' + val; // Numbers get # prefix. case'string': return'"' + val; // Strings get " prefix. default: return'@' + objectId(val); // Objs and funcs get @ } } // For any object, return a string. This function will return a different // string for different objects, and will always return the same string // if called multiple times for the same object. To do this it creates a // property on o. In ES5 the property would be nonenumerable and read-only. // function objectId(o) { var prop = "|**objectid**|"; // Private property name for storing ids if (!o.hasOwnProperty(prop)) // If the object has no id o[prop] = Set._v2s.next++; // Assign it the next available return o[prop]; // Return the id } }; Set._v2s.next = 100; // Start assigning object ids at this value.
// Wrap our code in a function so we can define variables in the function scope (function() { // Define objectId as a nonenumerable property inherited by all objects. // When this property is read, the getter function is invoked. // It has no setter, so it is read-only. // It is nonconfigurable, so it can't be deleted. Object.defineProperty(Object.prototype, "objectId", { get: idGetter, // Method to get value enumerable: false, // Nonenumerable configurable: false// Can't delete it }); // This is the getter function called when objectId is read functionidGetter() { // A getter function to return the id if (!(idprop inthis)) { // If object doesn't already have an id if (!Object.isExtensible(this)) // And if we can add a property throwError("Can't define id for nonextensible objects"); Object.defineProperty(this, idprop, { // Give it one now. value: nextid++, // This is the value writable: false, // Read-only enumerable: false, // Nonenumerable configurable: false// Nondeletable }); } returnthis[idprop]; // Now return the existing or new value }; // These variables are used by idGetter() and are private to this function var idprop = "|**objectId**|"; // Assume this property isn't in use var nextid = 1; // Start assigning ids at this # }()); // Invoke the wrapper function to run the code right away
/** * Property descriptor utilities */ // Make the named (or all) properties of o nonwritable and nonconfigurable. functionfreezeProps(o) { var props = (arguments.length == 1) // If 1 arg ? Object.getOwnPropertyNames(o) // use all props : Array.prototype.splice.call(arguments, 1); // else named props props.forEach(function(n) { // Make each one read-only and permanent // Ignore nonconfigurable properties if (!Object.getOwnPropertyDescriptor(o, n).configurable) return; Object.defineProperty(o, n, { writable: false, configurable: false }); }); return o; // So we can keep using it } // Make the named (or all) properties of o nonenumerable, if configurable. functionhideProps(o) { var props = (arguments.length == 1) // If 1 arg ? Object.getOwnPropertyNames(o) // use all props : Array.prototype.splice.call(arguments, 1); // else named props props.forEach(function(n) { // Hide each one from the for/in loop // Ignore nonconfigurable properties if (!Object.getOwnPropertyDescriptor(o, n).configurable) return; Object.defineProperty(o, n, { enumerable: false }); }); return o; }
/** * A Range class with strongly encapsulated endpoints */ // This version of the Range class is mutable but encapsulates its endpoint // variables to maintain the invariant that from <= to. functionRange(from, to) { // Verify that the invariant holds when we're created if (from > to) thrownewError("Range: from must be <= to"); // Define the accessor methods that maintain the invariant functiongetFrom() { returnfrom; }
functiongetTo() { return to; }
functionsetFrom(f) { // Don't allow from to be set > to if (f <= to) from = f; elsethrownewError("Range: from must be <= to"); }
functionsetTo(t) { // Don't allow to to be set < from if (t >= from) to = t; elsethrownewError("Range: to must be >= from"); } // Create enumerable, nonconfigurable properties that use the accessors Object.defineProperties(this, { from: { get: getFrom, set: setFrom, enumerable: true, configurable: false }, to: { get: getTo, set: setTo, enumerable: true, configurable: false } }); } // The prototype object is unchanged from previous examples. // The instance methods read from and to as if they were ordinary properties. Range.prototype = hideProps({ constructor: Range, includes: function(x) { returnthis.from <= x && x <= this.to; }, foreach: function(f) { for (var x = Math.ceil(this.from); x <= this.to; x++) f(x); }, toString: function() { return"(" + this.from + "..." + this.to + ")"; } });
/** * 声明全局变量Set,使用一个函数的返回值给它赋值 * 函数结束时紧跟的一对圆括号说明这个函数定义后立即执行 * 它的返回值将赋值给Set,而不是将这个函数赋值给Set * 注意它是一个函数表达式,不是一条语句,因此函数"Invocation"并没有创建全局变量 */ varSet = (functioninvocation() { functionSet() { // This constructor function is a local variable. this.values = {}; // The properties of this object hold the set this.n = 0; // How many values are in the set this.add.apply(this, arguments); // All arguments are values to add } // Now define instance methods on Set.prototype. // For brevity, code has been omitted here Set.prototype.contains = function(value) { // Note that we call v2s(), not the heavily prefixed Set._v2s() returnthis.values.hasOwnProperty(v2s(value)); }; Set.prototype.size = function() { returnthis.n; }; Set.prototype.add = function() { /* ... */ }; Set.prototype.remove = function() { /* ... */ }; Set.prototype.foreach = function(f, context) { /* ... */ }; // These are helper functions and variables used by the methods above // They're not part of the public API of the module, but they're hidden // within this function scope so we don't have to define them as a // property of Set or prefix them with underscores. functionv2s(val) { /* ... */ }
functionobjectId(o) { /* ... */ } var nextId = 1; // The public API for this module is the Set() constructor function. // We need to export that function from this private namespace so that // it can be used on the outside. In this case, we export the constructor // by returning it. It becomes the value of the assignment expression // on the first line above. returnSet; }()); // Invoke the function immediately after defining it.