1118 Part IV ✦ JavaScript Core Language Reference Better ways exist to intercept and preprocess user input, but the watch() func- tion can be a helpful debugging tool when you want to monitor the hidden workings of scripts. Defining object property getters and setters A future version of the ECMA-262 language specification will likely include a pair of facilities called getter and setter. Until such time as the formal syntax is finalized, you can begin to experiment with this technique in NN6 using temporary syntax that adheres to the likely format (but intentionally uses different keywords until the standard is adopted). When the standard is adopted, a subsequent version of NN will include the standard keywords. I introduced the idea of creating a getter and setter for an object briefly in Chapter 14, where the NN6 syntax style extended properties of some W3C DOM objects to include some of the Microsoft-specific (and very convenient) DOM syn- tax. Most notably, you can define a getter for any container to return an array of nested elements just like the IE-only document.all collection. The purpose of a getter is to assign a new property to the prototype of an object and to define how the value returned by the property should be evaluated. A setter does the same, but it also defines how a new value assigned to the property should apply the value to the object. Both definitions are written in the form of anonymous functions, such that reading or writing an object’s property value can include sophisticated processing for either operation. Getters and setters are assigned to the prototype property of an object, thus enabling you to customize native and DOM objects. The NN6 syntax fashions get- ters, setters, and methods of an object’s prototype with the following syntax: object.prototype.__defineGetter__(“propName”, function) object.prototype.__defineSetter__(“propName”, function) Note that the underscores before and after the method names are actually pairs of underscore characters (that is, _, _, defineGetter, _, _). This double under- score was chosen as a syntax that the ECMA standard will not use, so it will not conflict with the eventual syntax for this facility. The first parameter of the method is the name of the property for which the get- ter or setter is defined. This can be an existing property name that you want to override. The second parameter can be a function reference; but more likely it will be an anonymous function defined in place. By using an anonymous function, you can take advantage of the context of the object for which the property is defined. For each property, define both a getter and setter — even if the property is meant to be read-only or write-only. To see how this mechanism works, let’s use the getter and setter shown in Chapter 14 to add an innerText property to HTML elements in NN6. This property is read/write, so functions are defined for both the getter and setter. The getter defi- nition is as follows: HTMLElement.prototype.__defineGetter__(“innerText”, function () { var rng = document.createRange() rng.selectNode(this) return rng.toString() }) 1119 Chapter 41 ✦ Functions and Custom Objects The modified object is the basic HTMLElement object — the object that NN6 uses to create instances of every HTML element for the page. After the statement above executes, every HTML element on the page inherits the new innerText property. Each time the innerText property is read for an element, the anonymous function in this getter executes. Thus, after a text range object is created, the range is set to the node that is the current element. This is an excellent example of how the con- text of the current object allows the use of the this keyword to refer to the very same object. Finally, the string version of the selected range is returned. It is essen- tial that a getter function include a return statement and that the returned value is of the desired data type. Also take notice of the closing of the function’s curly brace and the getter method’s parenthesis. By executing this function each time the property is read, the getter always returns the current state of the object. If content of the element has changed since the page loaded, you are still assured of getting the current text inside the element. This is far superior to simply running the statements inside this function once as the page loads to capture a static view of the element’s text. The corresponding setter definition is as follows: HTMLElement.prototype.__defineSetter__(“innerText”, function (txt) { var rng = document.createRange() rng.selectNodeContents(this) rng.deleteContents() var newText = document.createTextNode(txt) this.appendChild(newText) return txt }) To assign a value to an object’s property, the setter function requires that a parameter variable receive the assigned value. That parameter variable plays a role somewhere within the function definition. For this particular setter, the current object ( this) also manipulates the text range object. The contents of the current element are deleted, and a text node comprising the text passed as a parameter is inserted into the element. To completely simulate the IE behavior of setting the innerText property, the text is returned. While setters don’t always return values, this one does so that the expression that assigns a value to the innerText prop- erty evaluates to the new text. If you want to create a read-only property, you still define a setter for the prop- erty but you also assign an empty function, as in: Node.prototype.__defineSetter__(“all”, function() {}) This prevents assignment statements to a read-only property from generating errors. A write-only property should also have a getter that returns null or an empty string, as in: HTMLElement.prototype.__defineGetter__(“outerHTML”, function() {return “”}) Because the getter and setter syntax shown here is unique to NN6, you must obviously wrap such statements inside object detection or browser version detection statements. And, to reiterate, this syntax will change in future browser versions once ECMA adopts the formal syntax. 1120 Part IV ✦ JavaScript Core Language Reference Using custom objects There is no magic to knowing when to use a custom object instead of an array in your application. The more you work with and understand the way custom objects work, the more likely you will think about your data-carrying scripts in these terms — especially if an object can benefit from having one or more methods asso- ciated with it. This avenue is certainly not one for beginners, but I recommend that you give custom objects more than a casual perusal once you gain some JavaScripting experience. Object-Oriented Concepts As stated several times throughout this book, JavaScript is object-based rather than object-oriented. Instead of adhering to the class, subclass, and inheritance schemes of object-oriented languages such as Java, JavaScript uses what is called prototype inheritance. This scheme works not only for native and DOM objects but also for custom objects. Adding a prototype A custom object is frequently defined by a constructor function, which typically parcels out initial values to properties of the object, as in the following example: function car(plate, model, color) { this.plate = plate this.model = model this.color = color } var car1 = new car(“AB 123”, “Ford”, “blue”) NN4+ and IE4+ offer a handy shortcut, as well, to stuff default values into proper- ties if none are provided (the supplied value is null, 0, or an empty string). The OR operator (||) can let the property assignment statement apply the passed value, if present, or a default value you hard-wire into the constructor. Therefore, you can modify the preceding function to offer default values for the properties: function car(plate, model, color) { this.plate = plate || “missing” this.model = model || “unknown” this.color = color || “unknown” } var car1 = new car(“AB 123”, “Ford”, “”) After the preceding statements run, the car1 object has the following properties: car1.plate // value = “AB 123” car1.model // value = “Ford” car1.color // value = “unknown” If you then add a new property to the constructor’s prototype property, as in car.prototype.companyOwned = true 1121 Chapter 41 ✦ Functions and Custom Objects any car object you already created or are about to create automatically inherits the new companyOwned property and its value. You can still override the value of the companyOwned property for any individual car object. But if you don’t override the property for instances of the car object, the car objects whose companyOwned property is not overridden automatically inherit any change to the prototype. companyOwned value. This has to do with the way JavaScript looks for prototype property values. Prototype inheritance Each time your script attempts to read or write a property of an object, JavaScript follows a specific sequence in search of a match for the property name. The sequence is as follows: 1. If the property has a value assigned to the current (local) object, this is the value to use. 2. If there is no local value, check the value of the property’s prototype of the object’s constructor. 3. Continue up the prototype chain until either a match of the property is found (with a value assigned to it) or the search reaches the native Object object. Therefore, if you change the value of a constructor’s prototype property and you do not override the property value in an instance of that constructor, JavaScript returns the current value of the constructor’s prototype property. Nested objects and prototype inheritance When you begin nesting objects, especially when one object invokes the con- structor of another, there is an added wrinkle to the prototype inheritance chain. Let’s continue with the car object defined earlier. In this scenario, consider the car object to be akin to a root object that has properties shared among two other types of objects. One of the object types is a company fleet vehicle, which needs the properties of the root car object (plate, model, color) but also adds some prop- erties of its own. The other object that shares the car object is an object represent- ing a car parked in the company garage — an object that has additional properties regarding the parking of the vehicle. This explains why the car object is defined on its own. Now look at the constructor function for the parking record, along with the con- structor for the basic car object: function car(plate, model, color) { this.plate = plate || “missing” this.model = model || “unknown” this.color = color || “unknown” } function carInLot(plate, model, color, timeIn, spaceNum) { this.timeIn = timeIn this.spaceNum = spaceNum this.carInfo = car this.carInfo(plate, model, color) } 1122 Part IV ✦ JavaScript Core Language Reference The carInLot constructor not only assigns values to its unique properties ( timeIn and spaceNum), but it also includes a reference to the car constructor arbitrarily assigned to a property called carInfo. This property assignment is merely a conduit that allows property values intended for the car constructor to be passed within the carInLot constructor function. To create a carInLot object, use a statement like the following: var car1 = new carInLot(“AA 123”, “Ford”, “blue”, “10:02AM”, “31”) After this statement, the car1 object has the following properties and values: car1.timeIn // value = “10:02AM” car1.spaceNum // value = “31” car1.carInfo // value = reference to car object constructor function car1.plate // value = “AA 123” car1.model // value = “Ford” car1.color // value = “blue” Let’s say that five carInLot objects are created in the script (car1 through car5). The prototype wrinkle comes into play if, for example, you assign a new property to the car constructor prototype: car.prototype.companyOwned = true Even though the carInLot objects use the car constructor, the instances of carInLot objects do not have a prototype chain back to the car object. As the pre- ceding code stands, even though you’ve added a companyOwned property to the car constructor, no carInLot object inherits that property (even if you were to create a new carInLot object after defining the new prototype property for car). To get the carInLot instances to inherit the prototype.companyOwned property, you must explicitly connect the prototype of the carInLot constructor to the car constructor prior to creating instances of carInLot objects: carInLot.prototype = new car() The complete sequence, then, is as follows: function car(plate, model, color) { this.plate = plate || “missing” this.model = model || “unknown” this.color = color || “unknown” } function carsInLot(plate, model, color, timeIn, spaceNum) { this.timeIn = timeIn this.spaceNum = spaceNum this.carInfo = car this.carInfo(plate, model, color) } carsInLot.prototype = new car() var car1 = new carsInLot(“123ABC”, “Ford”,”blue”,”10:02AM”, “32”) car.prototype.companyOwned = true After this stretch of code runs, the car1 object has the following properties and values: 1123 Chapter 41 ✦ Functions and Custom Objects car1.timeIn // value = “10:02AM” car1.spaceNum // value = “31” car1.carInfo // value = reference to car object constructor function car1.plate // value = “AA 123” car1.model // value = “Ford” car1.color // value = “blue” car1.companyOwned // value = true NN4+ provides one extra, proprietary bit of syntax in this prototype world. The __proto__ property (that’s with double underscores before and after the word “proto”) returns a reference to the object that is next up the prototype chain. For example, if you inspect the properties of car1.__proto__ after the preceding code runs, you see that the properties of the object next up the prototype chain are as follows: car1.__proto__.plate // value = “AA 123” car1.__proto__.model // value = “Ford” car1.__proto__.color // value = “blue” car1.__proto__.companyOwned // value = true This property can be helpful in debugging custom objects and prototype inheri- tance chain challenges, but the property is not part of the ECMA standard. Therefore, I discourage you from using the property in your production scripts. Object Object Properties Methods constructor hasOwnProperty() prototype isPrototypeOf() propertyIsEnumerable() toLocaleString() toString() valueOf() Syntax Creating an object object: function constructorName([arg1, [,argN]]) { statement(s) } var objName = new constructorName([“argName1”, [,”argNameN”]) var objName = new Object() var objName = {propName1:propVal1[, propName2:propVal2[, N]}} objectObject 1124 Part IV ✦ JavaScript Core Language Reference Accessing an object object properties and methods: objectReference.property | method([parameters]) NN2 NN3 NN4 NN6 IE3/J1 IE3/J2 IE4 IE5 IE5.5 Compatibility (✓) ✓✓✓(✓) ✓✓✓✓ About this object While it might sound like doubletalk, the Object object is a vital native object in the JavaScript environment. It is the root object on which all other native objects — such as Date, Array, String, and the like — are based. This object also provides the foundation for creating custom objects, as described earlier in this chapter. By and large, your scripts do not access the properties of the native Object object. The same is true for many of its methods, such as toString() and valueOf(), which internally allow debugging alert dialog boxes (and The Evaluator) to display something when referring to an object or its constructor. You can use a trio of methods, described next, in IE5.5 and NN6 to perform some inspection of the prototype environment of an object instance. They are of interest primarily to advanced scripters who are building extensive, simulated object- oriented applications. Methods hasOwnProperty(“propName”) Returns: Boolean. NN2 NN3 NN4 NN6 IE3/J1 IE3/J2 IE4 IE5 IE5.5 Compatibility ✓✓ The hasOwnProperty() method returns true if the current object instance has the property defined in its constructor or in a related constructor function. But if this property is defined externally, as via assignment to the object’s prototype property, the method returns false. Using the example of the car and carInLot objects from earlier in this chapter, the following expressions evaluate to true: car1.hasOwnProperty(“spaceNum”) car1.hasOwnProperty(“model”) Even though the model property is defined in a constructor that is invoked by another constructor, the property belongs to the car1 object. The following state- ment, however, evaluates to false: car1.hasOwnProperty(“companyOwned”) This property is defined by way of the prototype of one of the constructor func- tions and is not a built-in property for the object instance. objectObject.hasOwnProperty() 1125 Chapter 41 ✦ Functions and Custom Objects isPrototypeOf(objRef) Returns: Boolean. NN2 NN3 NN4 NN6 IE3/J1 IE3/J2 IE4 IE5 IE5.5 Compatibility ✓✓ The isPrototypeOf() method is intended to reveal whether or not the current object has a prototype relation with an object passed as a parameter. In practice, the IE5.5 and NN6 versions of this method not only operate differently, but they also do not appear in either browser to report prototype relationships correctly between objects. If any updated information is available for this method within these browsers, I will post it to the JavaScript Bible Support Center at http://www.dannyg.com/update.html. propertyIsEnumerable(“propName”) Returns: Boolean. NN2 NN3 NN4 NN6 IE3/J1 IE3/J2 IE4 IE5 IE5.5 Compatibility ✓✓ In the terminology of the ECMA-262 language specification, a value is enumerable if constructions such as the for-in property inspection loop (Chapter 39) can inspect it. Enumerable properties include values such as arrays, strings, and virtu- ally every kind of object. According to the ECMA specification, this method is not supposed to work its way up the prototype chain. IE5.5 appears to adhere to this, whereas NN6 treats a property inherited from an object’s prototype as a valid parameter value. ✦✦✦ objectObject.propertyIsEnumerable() . 1118 Part IV ✦ JavaScript Core Language Reference Better ways exist to intercept and preprocess user input,. this syntax will change in future browser versions once ECMA adopts the formal syntax. 1120 Part IV ✦ JavaScript Core Language Reference Using custom objects There is no magic to knowing when to. objects more than a casual perusal once you gain some JavaScripting experience. Object-Oriented Concepts As stated several times throughout this book, JavaScript is object-based rather than object-oriented.