JavaScript Objects

JavaScript Objects
GregDeamons Profile Pic
GregDeamons,New Zealand,Professional
Published Date:03-08-2017
Your Website URL(Optional)
Comment CHAPTER 16 JavaScript Objects 16.0 Introduction Your JavaScript applications can consist entirely of functions and variables—both local and global—but if you want to ensure ease of reuse, compactness of code, and effi- ciency, as well as code that plays well with other libraries, you’re going to need to consider opportunities to encapsulate your code into objects. Luckily, working with objects in JavaScript isn’t much more complicated than working with functions. After all, a JavaScript function is an object, and all objects are, techni- cally, just functions. Confused yet? Unlike languages such as Java or C++, which are based on classes and class instances, JavaScript is based on prototypal inheritance. What prototypal inheritance means is that reuse occurs through creating new instances of existing objects, rather than in- stances of a class. Instead of extensibility occurring through class inheritance, proto- typal extensibility happens by enhancing an existing object with new properties and methods. Prototype-based languages have an advantage in that you don’t have to worry about creating the classes first, and then the applications. You can focus on creating appli- cations, and then deriving the object framework via the effort. It sounds like a mishmash concept, but hopefully as you walk through the recipes you’ll get a better feel for JavaScript’s prototype-based, object-oriented capabilities. ECMAScript and all its variations, including JavaScript, isn’t the only prototype-based language. The Wikipedia page on prototype-based lan guages, lists several. 359 Download from Wow eBook See Also Several of the recipes in this book are based on new functionality that was introduced in ECMAScript 5. You can access the complete ECMAScript 5 specification (a PDF) at Note that the implementation of the new functionality is sketchy. I’ll point out browser support as I go. 16.1 Defining a Basic JavaScript Object Problem You want to create a custom, reusable JavaScript object. Solution Define a new object explicitly using functional syntax, and then create new instances, passing in the data the object constructor is expecting: function Tune (song, artist) this.title = song; this.artist = artist; this.concat=function() return this.title + "-" + this.artist; window.onload=function() var happySong = new Array(); happySong0 = new Tune("Putting on the Ritz", "Ella Fitzgerald"); // print out title and artist alert(happySong0.concat()); Discussion As you can see from the solution, there is no class description, as you might expect if you’ve used other languages. A new object is created as a function, with three members: two properties, title and artist, and one method, concat. You could even use it like a function: Tune("test","artist"); However, using the object like a function, as compared to using it as an object con- structor, has an odd and definitely unexpected consequence. The new keyword is used to create a new Tune instance. Values are passed into the object constructor, which are then assigned to the Tune properties, title and artist, via the this keyword. In the solution, this is a reference to the object instance. When you 360 Chapter 16: JavaScript Objects Download from Wow eBook assign the property values to the object instance, you can then access them at a later time, using syntax like happySong0.title. In addition, the Tune object’s concat meth- od also has access to these properties. However, when you use Tune like a regular function, this doesn’t represent an object instance, because there isn’t any. Instead, this references the owner of the Tune func- tion, which in this case, is the global window object: // treating Tune like a function Tune("the title", "the singer"); alert(window.concat()); // lo and behold, // "the title the singer" prints out Completely unexpected and unwelcome behavior. To summarize: to create a new object type, you can create a function with both prop- erties and methods. To ensure the properties are assigned to the correct object, treat the object like a constructor using the new operator, rather than as a function. The this keyword establishes ownership of the properties, which is the Tune object instance, if the function is used as an object constructor and not a regular function. See Also See Recipe 16.2 for more information about the role this plays with JavaScript objects. 16.2 Keeping Object Members Private Problem You want to keep one or more object properties private, so they can’t be accessed outside the object instance. Solution When creating the private data members, do not use the this keyword with the member: function Tune(song,artist) var title = song; var artist = artist; this.concat = function() return title + " " + artist; window.onload=function() var happySongs = new Array(); happySongs0 = new Tune("Putting on the Ritz", "Ella Fitzgerald"); try 16.2 Keeping Object Members Private 361 Download from Wow eBook // error alert(happySongs0.title); catch(e) alert(e); // prints out correct title and artist alert(happySongs0.concat()); Discussion Members in the object constructor (the function body), are not accessible outside the object unless they’re assigned to that object using this. If they’re attached to the object using the var keyword, only the Tune’s inner function, the concat method, can access these now-private data members. This type of method—one that can access the private data members, but is, itself, exposed to public access via this—has been termed a privileged method by Douglas Crockford, the father of JSON (JavaScript Object Notation). As he himself explains (at This pattern of public, private, and privileged members is possible because JavaScript has closures. What this means is that an inner function always has access to the vars and parameters of its outer function, even after the outer function has returned. This is an extremely powerful property of the language . . . . Private and privileged members can only be made when an object is constructed. Public members can be added at any time. See Also See Recipe 6.5 for more on function closures. See Recipe 16.3 for more on adding public members after the object has been defined. 16.3 Expanding Objects with prototype You want to extend an existing object with a new method. Solution Use the Object prototype to extend the object: Tune.prototype.addCategory = function(categoryName) this.category = categoryName; Discussion Every object in JavaScript inherits from Object, and all methods and other properties are inherited via the prototype object. It’s through the prototype object that we can 362 Chapter 16: JavaScript Objects Download from Wow eBook extend any object, and that includes the built-in objects, such as String and Number. Once an object has been extended via the prototype property, all instances of the object within the scope of the application have this functionality. In Example 16-1, the new object, Tune, is defined using function syntax. It has two private data members, a title and an artist. A publicly accessible method, concat, takes these two private data members, concatenates them, and returns the result. After a new instance of the object is created, and the object is extended with a new method and data member, the new method is used to update the existing object instance. Example 16-1. Instantiating a new object, adding values, and extending the object DOCTYPE html head titleTune Object/title script function Tune(song,artist) var title = song; var artist = artist; this.concat = function() return title + " " + artist; window.onload=function() // create instance, print out values var happySong = new Tune("Putting on the Ritz", "Ella Fitzgerald"); // extend the object Tune.prototype.addCategory = function(categoryName) this.category = categoryName; // add category happySong.addCategory("Swing"); // print song out to new paragraph var song = "Title and artist: " + happySong.concat() + " Category: " + happySong.category; var p = document.createElement("p"); var txt = document.createTextNode(song); p.appendChild(txt); document.getElementById("song").appendChild(p); /script /head body h1Tune/h1 16.3 Expanding Objects with prototype 363 Download from Wow eBook div id="song" /div /body /html Figure 16-1 shows the page after the new element and data have been appended. Figure 16-1. Demonstration of custom JavaScript object after values printed out The prototype property can also be used to override or extend external or global objects. Before ECMAScript 5 added trim as a default method for the String object, applications used to extend the String object by adding a trim method through the prototype object: String.prototype.trim = function() return (this.replace(/\s\xA0+/, "").replace(/\s\xA0+/, "")); Needless to say, you’d want to use extreme caution when using this functionality with global objects. Applications that have extended the String object with a homegrown trim method may end up behaving differently than applications using the new standard trim method. Using prototype with your own objects is usually safe. The only time you may run into problems is if you provide your objects as an external library, and others build on them. 16.4 Adding Getter/Setter to Objects Problem You want to provide property access to protected data. Solution Use the getter/setter functionality introduced with ECMAScript 3.1 with your (or out- side) objects: 364 Chapter 16: JavaScript Objects Download from Wow eBook function Tune() var artist; var song; this.__defineGetter__("artist",function() return artist); this.__defineSetter__("artist",function(val) artist = "By: " + val); this.__defineGetter__("song",function() return "Song: " + song); this.__defineSetter__("song",function(val) song=val); window.onload=function() var happySong = new Tune(); happySong.artist="Ella Fitzgerald";"Putting on the Ritz"; alert( + " " + happySong.artist); Discussion We can add functions to our objects to get private data, as demonstrated in Rec- ipe 16.2. However, when we use the functions, they are obvious functions. The getter/ setter functionality is a special syntax that provides property-like access to private data members. The getter and setter functions provide an extra layer of protection for the private data members. The functions also allow us to prepare the data, as demonstrated with the solution, where the song and artist strings are concatenated to labels. You can define the getter and setter functions within the object constructor, as shown in the solution: this.__defineGetter__("song",function() return "Song: " + song); this.__defineSetter__("song",function(val) song=val); You can also add a getter/setter with other objects, including DOM objects. To add a getter/setter outside of the object constructor, you first need to access the object’s prototype object, and then add the getter/setter functions to the prototype: var p = Tune.prototype; p.__defineGetter__("song",function() return "Song: " + title); 16.4 Adding Getter/Setter to Objects 365 Download from Wow eBook p.__defineSetter__("song",function(val) title=val); You can also use getter/setters with “one-off” objects, used to provide JavaScript name- spacing (covered later in the chapter): var Book = title: "The JavaScript Cookbook", get booktitle() return this.title; , set booktitle(val) this.title = val; ; Book.booktitle = "Learning JavaScript"; This approach can’t be used to hide the data, but can be used to control the display of the data, or to provide special processing of the incoming data before it’s stored in the data member. The getter/setter functionality does not work in IE8 or earlier. See Also See Recipe 16.6 for demonstrations of the new ECMAScript 5 property methods. Rec- ipe 16.11 covers JavaScript namespacing. 16.5 Inheriting an Object’s Functionality Problem When creating a new object type, you want to inherit the functionality of an existing JavaScript object. Solution Use the concept of constructor chaining and the Function.apply method to emulate traditional class inheritance behavior in JavaScript: function oldObject(param1) this.param1 = param1; this.getParam=function() return this.param1; 366 Chapter 16: JavaScript Objects Download from Wow eBook function newObject(param1,param2) this.param2 = param2; this.getParam2 = function() return this.param2; oldObject.apply(this,arguments); this.getAllParameters=function() return this.getParam() + " " + this.getParam2(); window.onload=function() newObject.prototype = new oldObject(); var obj = new newObject("value1","value2"); // prints out both parameters alert(obj.getAllParameters()); Discussion In the solution, we have two objects: the original, oldObject, and newObject that inherits functionality from the older object. For this to work, a couple of things are happening. First, in the constructor for newObject, an apply method is called on oldObject, passing in a reference to the new object, and the argument array. The apply method is inherited from the Function object, and takes a reference to the calling object, or the window object if the value is null, and an optional argument array. The second part of the inheritance is the line: newObject.prototype=new oldObject(); This is an example of constructor chaining in JavaScript. What happens is when you create a new instance of newObject, you’re also creating a new instance of oldObject, in such a way that newObject inherits both the older object’s methods and property. It is this combination of constructor chaining, which chains the constructors of the new objects together, and the apply method, which passes both object context and data to the inherited object, that implements inheritance behavior in JavaScript. Because of this inheritance, the new object has access not only to its own property, param2, and method, getParam2, but also has access to the old object’s param1 and getParam property and method. To see another example of JavaScript inheritance, Example 16-2 shows it working with another couple of objects: a Book object and a TechBook object, which inherits from Book. The lines that implement the inheritance are bolded. 16.5 Inheriting an Object’s Functionality 367 Download from Wow eBook Example 16-2. Demonstrating object inheritance in JavaScript DOCTYPE html head titleConstructor Chaining/title script type="text/javascript" function Book (title, author) var title = title; var author = author; this.getTitle=function() return "Title: " + title; this.getAuthor=function() return "Author: " + author; function TechBook (title, author, category) var category = category; this.getCategory = function() return "Technical Category: " + category; Book.apply(this,arguments); this.getBook=function() return this.getTitle() + " " + author + " " + this.getCategory(); window.onload=function() // chain the object constructors TechBook.prototype = new Book(); // get all values var newBook = new TechBook("The JavaScript Cookbook", "Shelley Powers", "Programming"); alert(newBook.getBook()); // now, individually alert(newBook.getTitle()); alert(newBook.getAuthor()); alert(newBook.getCategory()); /script /head body psome content/p /body 368 Chapter 16: JavaScript Objects Download from Wow eBook Unlike the objects in the solution, all of the data members in both objects in Exam- ple 16-2 are protected, which means the data can’t be directly accessed outside the objects. Yet notice when all three Book properties—title, author, and category—are printed out via the getBook method in the TechBook object, that TechBook has access to the Book author and title properties, in addition to its own category. That’s because when the new TechBook object was created, a new Book object was also created and inherited by the new TechBook object. To complete the inheritance, the data used in the constructor for TechBook is passed through the Book object using the apply method. Not only can the TechBook object directly access the Book instance data, you can access both of the Book object’s methods, getAuthor and getCategory, directly on the instan- tiated TechBook object instance. 16.6 Extending an Object by Defining a New Property Problem You want to extend an existing object by adding a new property, but without changing the object’s constructor function. Solution Use the new ECMAScript Object.defineProperty method to define one property: Object.defineProperty(newBook, "publisher", value: "O'Reilly", writable: false, enumerable: true, configurable: true); Use the Object.defineProperties method to define more than one property: Object.defineProperties(newBook, "stock": value: true, writable: true, enumerable: true, , "age": value: "13 and up", writable: false ); Discussion Properties are handled differently in ECMAScript 5. Where before all you could do was assign a value, now you have greater control over how an object’s properties are man- aged. This greater control comes about through the provision for several new attributes 16.6 Extending an Object by Defining a New Property 369 Download from Wow eBook that can be assigned to a property when it’s created. These new attributes make up what is known as the property descriptor object, and include: writable If true, property can be changed; otherwise, not. configurable If true, property can be deleted, or changed; otherwise, not. enumerable If true, property can be iterated. The type of property descriptor can also vary. If the descriptor is a data descriptor, another attribute is value, demonstrated in the solution and equivalent to the following: someObject.newProperty = "somevalue"; If the descriptor is an accessor descriptor, the property has a getter/setter, similar to what was covered in Recipe 16.4. A restriction when defining an accessor property is that you can’t set the writable attribute: Object.defineProperty(TechBook, "category", get: function () return category; , set: function (value) category = value; , enumerable: true, configurable: true); var newBook = new TechBook(...); newBook.publisher="O'Reilly"; You can also discover information about the property descriptor for a property with the Object.getOwnPropertyDescription method. To use, pass in the object and the property name whose property descriptor you wish to review: var propDesc = Object.getOwnPropertyDescriptor(newBook,"category"); alert(propDesc.writable); // true if writable, otherwise false The property has to be publicly accessible (not a private member). Easily view all of the property attributes using the JSON object’s stringify method: var val = Object.getOwnPropertyDescriptor(TechBook,"category"); alert(JSON.stringify(val)); // "enumerable":true,"configurable":true If the property descriptor configurable attribute is set to true, you can change descrip- tor attributes. For instance, to change the writable attribute from false to true, use the following: Object.defineProperty(newBook, "publisher", writable: true); The previously set attributes retain their existing values. Example 16-3 demonstrates the new property descriptors, first on a DOM object, then a custom object. 370 Chapter 16: JavaScript Objects Download from Wow eBook Example 16-3. Trying out new object property methods DOCTYPE html head titleObject Properties/title script type="text/javascript" // Book custom object function Book (title, author) var title = title; var author = author; this.getTitle=function() return "Title: " + title; this.getAuthor=function() return "Author: " + author; // TechBook, inheriting from Book function TechBook (title, author, category) var category = category; this.getCategory = function() return "Technical Category: " + category; Book.apply(this,arguments); this.getBook=function() return this.getTitle() + " " + author + " " + this.getCategory(); window.onload=function() try // DOM test, WebKit bites the dust on this one var img = new Image(); // add new property and descriptor Object.defineProperty(img, "geolatitude", get: function() return geolatitude; , set: function(val) geolatitude = val;, enumerable: true, configurable: true); // test configurable and enumerable attrs var props = "Image has "; for (var prop in img) props+=prop + " "; alert(props); catch(e) alert(e); 16.6 Extending an Object by Defining a New Property 371 Download from Wow eBook try // now we lose IE8 // chain the object constructors TechBook.prototype = new Book(); // add new property and property descriptor Object.defineProperty(TechBook, "experience", get: function () return category; , set: function (value) category = value; , enumerable: false, configurable: true); // get property descriptor and print var val = Object.getOwnPropertyDescriptor(TechBook,"experience"); alert(JSON.stringify(val)); // test configurable and enumerable props = "TechBook has "; for (var prop in TechBook) props+=prop + " "; alert(props); Object.defineProperty(TechBook, "experience", enumerable: true); props = "TechBook now has "; for (var prop in TechBook) props+=prop + " "; alert(props); // create TechBook instance var newBook = new TechBook("The JavaScript Cookbook", "Shelley Powers", "Programming"); // test new setter newBook.experience="intermediate"; // test data descriptor Object.defineProperty(newBook, "publisher", value: "O'Reilly", writable: false, enumerable: true, configurable: true); // test writable newBook.publisher="Some Other"; alert(newBook.publisher); catch(e) alert(e); 372 Chapter 16: JavaScript Objects Download from Wow eBook /script /head body psome content/p /body These methods are very new. At the time I wrote this recipe, they only work in nightly builds for WebKit and Firefox (Minefield), and in a very limited sense with IE8. The IE8 limitation is that the new property methods only work with DOM elements. The Object.defineProperty method works with the Image element, but not with the custom objects. However, using defineProperty on DOM elements causes an exception in WebKit. None of the new property methods work with Opera. The Firefox Minefield nightly and the Chrome beta were the only browsers that currently work with both types of objects, as shown in Figure 16-2, which displays the Image object properties in Firefox. Figure 16-2. Displaying Image properties after adding a new property with defineProperty 16.6 Extending an Object by Defining a New Property 373 Download from Wow eBook After printing out the Image properties, a new property (experience) and property de- scriptor are added to the TechBook custom object. The Object.getOwnPropertyDescrip tor is called, passing in the TechBook object and the property name, experience. The property descriptor object is returned, and the JSON.stringify method is used on the object to print out the values: "enumerable":false,"configurable:true Next, the property descriptor values are tested. Currently, because the experience property is not enumerable, the use of the loop can’t enumerate through the properties, and the result is: Techbook has prototype The new experience property’s enumerable attribute is then changed to true, because the property descriptor for experience allows modification on descriptor values. Enu- merating over the experience property now yields the following string for Firefox: Techbook has prototype experience However, Chrome does not pick up the prototype property. The next two lines of code create a new instance of the TechBook object and adds an experience, which is then printed out to demonstrate the success of the property. The last code in the example adds another new property (publisher) and property descriptor. This is a data property descriptor, which can also take a default value: “O’Reilly”. The writable property descriptor is set to false, and configurable and enu- merable descriptors are set to true. The code then tries to change the publisher value. However, the original publisher value of O'Reilly prints out because the publisher property writable attribute is set to false. See Also See Recipe 16.7 for more on property enumeration. Though not all browsers support defineProperty and defineProperties yet, there are workarounds, as detailed by John Resig in a nice article describing the new object property capabilities. 16.7 Enumerating an Object’s Properties Problem You want to see what properties an object has. Solution Use a specialized variation of the for loop to iterate through the properties: for (var prop in obj) alert(prop); // prints out the property name 374 Chapter 16: JavaScript Objects Download from Wow eBook or use the new ECMAScript 5 Object.keys method to return the names for all properties that can be enumerated: alert(Object.keys(obj).join(", ")); or use another new ECMAScript 5 method, Object.getOwnPropertyNames(obj), to get the names of all properties, whether they can be enumerated or not: var props = Object.getOwnPropertyNames(obj); Discussion For an object instance, newBook, based on the following object definitions: function Book (title, author) var title = title; = author; this.getTitle=function() return "Title: " + title; this.getAuthor=function() return "Author: " + author; function TechBook (title, author, category) var category = category; this.getCategory = function() return "Technical Category: " + category; Book.apply(this,arguments); this.getBook=function() return this.getTitle() + " " + author + " " + this.getCategory(); // chain the object constructors TechBook.prototype = new Book(); // create new tech book var newBook = new TechBook("The JavaScript Cookbook", "Shelley Powers", "Programming"); using the loop: var str = ""; for (var prop in newBook) str = str + prop + " "; alert(str); a message pops up with the following values: getCategory author getTitle getAuthor getBook 16.7 Enumerating an Object’s Properties 375 Download from Wow eBook Neither the category property in TechBook nor the title property in Book are returned, as these are private data members. When using WebKit nightly or Firefox Minefield, the same result is returned when using the new Object.keys method: alert(Object.keys(newBook).join(" ")); The same result is also returned, again with WebKit nightly or Firefox Minefield, when using the new Object.getOwnPropertyNames method: var props = Object.getOwnPropertyNames(newBook); alert(props.join(" ")); However, if I add a property descriptor for the title property, making it enumerable: // test data descriptor Object.defineProperty(newBook, "title", writable: true, enumerable: true, configurable: true); When I enumerate over the properties again, this time the title displays among the properties, even though it still can’t be accessed or reset directly on the object. We can also use these same property enumerators over the object constructors (Book or TechBook) or any of the built-in objects. However, the values we get back when we enumerate over Book, as compared to the instance, newBook, do vary among the methods. The loop returns just one property, prototype, as does the Object.keys meth- od. That’s because prototype is the only enumerable property for the Function object. While newBook is an instance of Book, Book, itself is an instance of the Function object. The Object.getOwnPropertyNames, however, returns the following set of properties for Book: arguments callee caller length name prototype Unlike Object.keys and the loop, Object.getOwnPropertyNames returns a list of all properties, not just those that are enumerable. This leads to a new question: why did Object.getOwnPropertyNames not return all of the properties for the newBook in- stance? It should have picked up title before it was made enumerable, as well as TechBook’s private member, category. I added another property descriptor to newBook, this time for category, and this time with enumerable set to false: Object.defineProperty(newBook,"category", writable: true, enumerable: false, configurable: true); The category property isn’t listed when I use or Object.keys, but this time it is picked up by Object.getOwnPropertyNames. It would seem that a property must either be publicly accessible, or have a property descriptor for it to be picked up by Object.getOwnPropertyNames, at least for these earlier 376 Chapter 16: JavaScript Objects Download from Wow eBook implementations of these new Object methods. I imagine the reason is to ensure con- sistent results between older ECMAScript implementations and newer ones: defining a property in older versions of ECMAScript is not the same as defining a property in the newer version. Speaking of ECMAScript 5 and new Object methods, support for the older loop is broad, but support for Object.keys and Object.getOwnPropertyNames, in addi- tion to support for property descriptors, is sparse at this time. Opera does not support defineProperty and the associated new ECMAScript 5 functionality. WebKit nightly and the Chrome beta support all of the new functionality, while the Firefox nightly (Minefield), supports Object.keys, but not getOwnPropertyNames. IE8’s coverage is limi- ted because it only supports the new methods on DOM elements, such as Image, and not on custom objects. See Also For more on property descriptors, see Recipe 16.6. 16.8 Preventing Object Extensibility Problem You want to prevent others from extending an object. Solution Use the new ECMAScript 5 Object.preventExtensions method to lock an object against future property additions: "use strict"; var Test = value1 : "one", value2 : function() return this.value1; ; try Object.preventExtensions(Test); // the following fails, and throws an exception in Strict mode Test.value3 = "test"; catch(e) alert(e); 16.8 Preventing Object Extensibility 377 Download from Wow eBook Discussion Covering Object.preventExtensions is a leap of faith on my part, as no browser has implemented this new ECMAScript 5 functionality. However, by the time this book hits the streets, I expect (hope) at least a couple of browsers will have implemented this new feature. The Object.preventExtensions method prevents developers from extending the object with new properties, though property values themselves are still writable. It sets an internal property, Extensible, to false. You can check to see if an object is extensible using Object.isExtensible: if (Object.isExtensible(obj)) // extend the object Though you can’t extend the object, you can edit existing property values, as well as modify the object’s property descriptor. See Also Recipe 16.6 covers property descriptors. Strict Mode Another new ECMAScript 5 addition is demonstrated in the solution for Recipe 16.8: the new Strict mode. You can now put an application within a strict operating mode, which means that older, deprecated features are disabled or generate errors, and actions that may not throw errors but are not advisable will throw errors. It’s a way of inherently ensuring production-quality code. The ECMAScript 5 specification details what happens in Strict mode throughout the document. For instance, if you use the deprecated with statement, the application will throw an error; the use of the delete operator with a variable, function argument, or function will also generate an error; most uses of eval will generate errors, and so on. Annex C of the ECMAScript 5 specification lists all of the Strict mode restrictions and exceptions. To turn Strict mode on, use the following at the top level of your code: "use strict"; This includes quotes. Or, you can turn on Strict mode only within a function: function somename() "use strict"; ; When creating custom JavaScript objects, you can ensure their quality by using Strict mode. Currently, no browsers support Strict mode, but all plan to eventually. 378 Chapter 16: JavaScript Objects Download from Wow eBook

Advise: Why You Wasting Money in Costly SEO Tools, Use World's Best Free SEO Tool Ubersuggest.