ptg 8.2 Updates to the Object Model 163 ES3 objects are basically mutable collections of properties. The Object.seal method can be used to seal an entire object; all of the object’s own properties will have their configurable attribute set to false, and subsequently the object’s [[Extensible]] property is set to false. Using a browser that supports Object.getOwnPropertyDescriptor and Object.defineProperty, the seal method could be implemented as in Listing 8.4. Listing 8.4 Possible Object.seal implementation if (!Object.seal && Object.getOwnPropertyNames && Object.getOwnPropertyDescriptor && Object.defineProperty && Object.preventExtensions) { Object.seal = function (object) { var properties = Object.getOwnPropertyNames(object); var desc, prop; for (var i = 0, l = properties.length; i < l; i++) { prop = properties[i]; desc = Object.getOwnPropertyDescriptor(object, prop); if (desc.configurable) { desc.configurable = false; Object.defineProperty(object, prop, desc); } } Object.preventExtensions(object); return object; }; } We can check whether or not an object is sealed using Object.isSealed. Notice how this example also uses Object.getOwnPropertyNames, which returns the names of all the object’s own properties, including those whose enumerable attribute is false. The similar method Object.keys returns the property names of all enumerable properties of an object, exactly like the method in Prototype.js does today. To easily make an entire object immutable, we canusethe related, but even more restrictive function Object.freeze. freeze works like seal, and additionally sets all the properties writable attributes to false, thus completely locking the object down for modification. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 164 ECMAScript 5th Edition 8.2.2 Prototypal Inheritance ECMAScript 5 makes prototypal inheritance in JavaScript more obvious, and avoids the clumsy constructor convention. In ES3, the only native way to create an object sphere that inherits from another object circle is by proxying via a constructor, as Listing 8.5 shows. Listing 8.5 Create an object that inherits from another object in ES3 "test es3 inheritance via constructors": function () { var circle = { /* */ }; function CircleProxy() {} CircleProxy.prototype = circle; var sphere = new CircleProxy(); assert(circle.isPrototypeOf(sphere)); } Additionally, there is no direct way of retrieving the prototype property in ES3. Mozilla added a proprietary __ proto __ property that fixes both of these cases, as in Listing 8.6. Listing 8.6 Proprietary shortcut to accessing and setting prototype "test inheritance via proprietary __ proto __ ": function () { var circle = { /* */ }; var sphere = {}; sphere. __ proto __ = circle; assert(circle.isPrototypeOf(sphere)); } The __ proto __ property is not codified by ES5. Instead, two methods were added to work easily with prototypes. Listing 8.7 shows how we will be doing this in the future. Listing 8.7 Creating an object that inherits from another object in ES5 "test inheritance, es5 style": function () { var circle = { /* */ }; var sphere = Object.create(circle); From the Library of WoweBook.Com Download from www.eBookTM.com ptg 8.2 Updates to the Object Model 165 assert(circle.isPrototypeOf(sphere)); assertEquals(circle, Object.getPrototypeOf(sphere)); } You might recognize Object.create from Section 7.5.1, The Object. create Method, in Chapter 7, Objects and Prototypal Inheritance, in which we did in fact implement exactly such a method. The ES5 Object.create does us one better—it can also add properties to the newly created object, as seen in Listing 8.8. Listing 8.8 Create an object with properties "test Object.create with properties": function () { var circle = { /* */ }; var sphere = Object.create(circle, { radius: { value: 3, writable: false, configurable: false, enumerable: true } }); assertEquals(3, sphere.radius); } As you might have guessed, Object.create sets the properties using Ob- ject.defineProperties (which in turn uses Object.defineProperty). Its implementation could possibly look like Listing 8.9. Listing 8.9 Possible Object.create implementation if (!Object.create && Object.defineProperties) { Object.create = function (object, properties) { function F () {} F.prototype = object; var obj = new F(); if (typeof properties != "undefined") { Object.defineProperties(obj, properties); } return obj; }; } From the Library of WoweBook.Com Download from www.eBookTM.com ptg 166 ECMAScript 5th Edition Because Object.defineProperties and, by extension, Object. defineProperty cannot be fully simulated in ES3 environments, this is not usable, but it shows how Object.create works. Also note that ES5 allows the prototype to be null, which is not possible to emulate across browsers in ES3. An interesting side-effect of using Object.create is that the instanceof operator may no longer provide meaningful information, as the native Object. create does not use a proxy constructor function to create the new object. The only function of which the newly created object will be an instance is Object. This may sound strange, but instanceof really isn’t helpful in a world in which objects inherit objects. Object.isPrototypeOf helps determine relationships between objects, and in a language with duck typing such as JavaScript, an object’s capabilities are much more interesting than its heritage. 8.2.3 Getters and Setters As stated in the previous section, Object.defineProperty cannot be reliably emulated in ES3 implementations, because they do not expose property attributes. Even so, Firefox, Safari, Chrome, and Opera all implement getters and setters, which can be used to solve part of the defineProperty puzzle in ES3. Given that it won’t work in Internet Explorer until version 9 1 , getters and setters won’t be applicable to the general web for still some time. Getters and setters make it possible to addlogic to getting and setting properties, without requiring change in client code. Listing 8.10 shows an example in which our circle uses getters and setters to add a virtual diameter property. Listing 8.10 Using getters and setters "test property accessors": function () { Object.defineProperty(circle, "diameter", { get: function () { return this.radius * 2; }, set: function (diameter) { if (isNaN(diameter)) { throw new TypeError("Diameter should be a number"); } this.radius = diameter / 2; 1. Internet Explorer 8 implements Object.defineProperty, but for some reason not for client objects. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 8.2 Updates to the Object Model 167 } }); circle.radius = 4; assertEquals(8, circle.diameter); circle.diameter = 3; assertEquals(3, circle.diameter); assertEquals(1.5, circle.radius); assertException(function () { circle.diameter = {}; }); } 8.2.4 Making Use of Property Attributes Using the new property attributes makes it possible to create much more sophis- ticated programs with JavaScript. As we already saw, we can now create properly immutable objects. Additionally, we can now also emulate how the DOM works, by providing property accessors that have logic behind them. Previously, I also argued that Object.create (backed by Object. defineProperty) will obliterate the need for object constructors along with the instanceof operator. In particular, the example given in Listing 8.7 cre- ates an object with which the instanceof operator will only make sense with Object. However,using Object.create does notmean we cannot have a usable instanceof operator. Listing 8.11 shows an example in which Object.create is used inside a constructor to provide a meld between ES3 and ES5 style prototypal inheritance. Listing 8.11 Constructor using Object.create function Circle(radius) { var _radius; var circle = Object.create(Circle.prototype, { radius: { configurable: false, enumerable: true, set: function (r) { From the Library of WoweBook.Com Download from www.eBookTM.com ptg 168 ECMAScript 5th Edition if (typeof r != "number" || r <= 0) { throw new TypeError("radius should be > 0"); } _radius = r; }, get: function () { return _radius; } } }); circle.radius = radius; return circle; } Circle.prototype = Object.create(Circle.prototype, { diameter: { get: function () { return this.radius * 2; }, configurable: false, enumberable: true }, circumference: { /* */ }, area: { /* */ } }); This constructor can be used like any other, and even makes sense with instanceof. Listing 8.12 shows a few uses of this constructor. Listing 8.12 Using the hybrid Circle TestCase("CircleTest", { "test Object.create backed constructor": function () { var circle = new Circle(3); assert(circle instanceof Circle); assertEquals(6, circle.diameter); circle.radius = 6; assertEquals(12, circle.diameter); From the Library of WoweBook.Com Download from www.eBookTM.com ptg 8.2 Updates to the Object Model 169 delete circle.radius; assertEquals(6, circle.radius); } }); Defining the object and constructor this way works because of the way con- structors work. If a constructor returns an object rather than a primitive value, it will not create a new object as this. In those cases, the new keyword is just syntactical fluff. As the example in Listing 8.13 shows, simply calling the function works just as well. Listing 8.13 Using Circle without new "test omitting new when creating circle": function () { var circle = Circle(3); assert(circle instanceof Circle); assertEquals(6, circle.diameter); } The prototype property is a convention used with constructors in order for the new keyword to work predictably. When we are creating our own objects and setting up the prototype chain ourselves, we don’t really need it. Listing 8.14 shows an example in which we leave constructors, new and instanceof behind. Listing 8.14 Using Object.create and a function "test using a custom create method": function () { var circle = Object.create({}, { diameter: { get: function () { return this.radius * 2; } }, circumference: { /* */ }, area: { /* */ }, create: { value: function (radius) { var circ = Object.create(this, { radius: { value: radius } }); return circ; From the Library of WoweBook.Com Download from www.eBookTM.com ptg 170 ECMAScript 5th Edition } } }); var myCircle = circle.create(3); assertEquals(6, myCircle.diameter); assert(circle.isPrototypeOf(myCircle)); // circle is not a function assertException(function () { assertFalse(myCircle instanceof circle); }); } This example creates a single object that exposes a create method to construct the new object. Thus there is no need for new or prototype, and prototypal inheritance works as expected. An interesting side effect of this style is that you can call myCircle.create(radius) to create circles that inherit from myCircle and so on. This is just one possible way to implement inheritance without constructors in JavaScript. Regardless of what you think of this particular implementation, I think the example clearly shows why constructors and the new keyword are unneeded in JavaScript, particularly in ES5, which provides better tools for working with objects and prototypal inheritance. 8.2.5 Reserved Keywords as Property Identifiers In ES5, reserved keywords can be used as object property identifiers. This is par- ticularly important in the light of the added native JSON support, because it means that JSON is not restricted in available property names. Reserved keywords as im- plemented in ES3 caused trouble, for instance when implementing the DOM, as Listing 8.15 shows an example of. Listing 8.15 Reserved keywords and property identifiers // ES3 element.className; // HTML attribute is "class" element.htmlFor; // HTML attribute is "for" // Is valid ES5 element.class; element.for; From the Library of WoweBook.Com Download from www.eBookTM.com ptg 8.3 Strict Mode 171 This does not imply that the DOM API will change with ES5, but it does mean that new APIs do not need to suffer the inconsistency of the DOM API. 8.3 Strict Mode ECMAScript 5 allows a unit—a script or a function—to operate in a strict mode syntax. Thissyntax doesnot allowsome of ES3’s less stellar features, is less permissive of potentially bad patterns, throws more errors, and ultimately aspires to reduce confusion and provide developers with an easier to work with environment. Because ES5 is supposed to be backwards compatible with ES3, or at least implementations of it, strict mode is opt-in, an elegant way to deprecate features scheduled for removal in future updates. 8.3.1 Enabling Strict Mode The example in Listing 8.16 shows how strict mode can be enabled by a single string literal directive. Listing 8.16 Enable strict mode globally "use strict"; // Following code is considered strict ES5 code This simple construct may look a little silly, but it is extremely unlikely to collide with existing semantics and is completely backwards compatible—it’s just a no-op string literal in ES3. Because it may not be possible to port all ES3 code to strict mode from the get-go, ES5 offers a way to enable strict mode locally. When placed inside a function, the directive will enable strict mode inside the function only. Listing 8.17 shows an example of strict and non-strict code side-by-side in the same script. Listing 8.17 Local strict mode function haphazardMethod(obj) { // Function is not evaluated as strict code with (obj) { // Not allowed in strict } } function es5FriendlyMethod() { From the Library of WoweBook.Com Download from www.eBookTM.com ptg 172 ECMAScript 5th Edition "use strict"; // Local scope is evaluated as strict code } Strict mode can be enabled for evaled code as well, either by making a direct call to eval from within other strict code, or if the code to be evaled itself begins with the strict directive. The same rules apply to a string of code passed to the Function constructor. 8.3.2 Strict Mode Changes The following are changes to the language in strict mode. 8.3.2.1 No Implicit Globals Implicit globals is likely JavaScript’s least useful and certainly least appreciated feature. In ES3, assigning to an undeclared variable does not result in an error, or even a warning. Rather, it creates a property of the global object, paving the way for some truly obscure bugs. In strict mode, assigning to undeclared variables results in a ReferenceError. Listing 8.18 shows an example. Listing 8.18 Implicit globals function sum(numbers) { "use strict"; var sum = 0; for (i = 0; i < numbers.length; i++) { sum += numbers[i]; } return sum; } // ES3: Property i is created on global object // ES5 strict mode: ReferenceError 8.3.2.2 Functions Strict mode offers some help when dealing with functions. For instance, an error will now be thrown if two formal function parameters use the same identifier. In ES3 implementations, using the same identifier for more than one formal parameter From the Library of WoweBook.Com Download from www.eBookTM.com . constructors in JavaScript. Regardless of what you think of this particular implementation, I think the example clearly shows why constructors and the new keyword are unneeded in JavaScript, particularly. strict mode inside the function only. Listing 8.17 shows an example of strict and non-strict code side-by-side in the same script. Listing 8.17 Local strict mode function haphazardMethod(obj). Attributes Using the new property attributes makes it possible to create much more sophis- ticated programs with JavaScript. As we already saw, we can now create properly immutable objects. Additionally,