# Chapter 6. Objects ## 6.1 Introduction to Objects * An object is an unordererd collection of properties (each is a name/value pair) * JS object can inherit properties from another object (aka "prototype") * JS objects are dynamic; i.e. properties can be added/removed * Any value in JS is object except string, number, Symbol, `true`/`false`, `null`/`undefined` * Objects are mutable, and manupulated by reference rather than value. * e.g. `let y=x` means `y` holds a reference to the same obj, not a copy of that obj. * Common operations on obj: create, set, query, delete, test, and enumerate * Property has name & value, but no obj has two properties with the same name (that's why we use Symbol) * JS use *own property* to refer to non-inherited properties. * Each property has 3 property **attributes**: * **writable**: whether value of property can be set * **enumerable**: whether the property name is returned by for/in loop * **configurable**: whether the property can be deleted and its attributes can be altered ## 6.2 Creating Objects Creating obj, 4 methods: 1. using object literal 2. using keyword `new` 3. using `Object.create()` function ### 6.2.1 Object Literals **Object literal** in simplest form: * comma-separated list of colon-separated `name:value` pairs, enclosed within `{}`. * property *name*: JS identifier or string * property *value*: JS expression ```js let empty = {}; // An object with no properties let point = { x: 0, y: 0 }; // Two numeric properties let p2 = { x: point.x, y: point.y+1 }; // More complex values let book = { "main title": "JavaScript", // These property names include spaces, "sub-title": "The Definitive Guide", // and hyphens, so use string literals. for: "all audiences", // for is reserved, but no quotes. author: { // The value of this property is firstname: "David", // itself an object. surname: "Flanagan" } }; ``` When object literal works: * Object literal creates & initializes a new & distinct obj every time it's evaluated. * In loop body, a new obj can be created repeatedly. ### 6.2.2 Creating Objects with new * **`new` operator** * creates & initialize a new object * syntax: `new` followed by function invocation as **constructor** ```js let o = new Object(); // Create an empty object: same as {}. let a = new Array(); // Create an empty array: same as []. let d = new Date(); // Create a Date object representing the current time let r = new Map(); // Create a Map object for key/value mapping ``` ### 6.2.3 Prototypes Almost all JS obj has a prototype associate with it: * All objs created using **object literal** (shown in 6.2.1) are associated with the same prototype obj, referred by `Object.prototype` * objs created using `new` (invoking constructor) use *value of constructor function's `prototype` property* as prototype * `new Object()` inherits from `Object.prototype` * `new Array()` inherits from `Array.prototype` * Only few objects have `prototype` property, they are used to define `prototypes` for all other objs. ### 6.2.4 Object.create() 3 Methods below demonstrated ability to create a new obj with an arbitrary prototype: * Create new obj w/ defined prototype using **`Object.create()`** ```js let o1 = Object.create({x: 1, y: 2}); // o1 inherits properties x and y. o1.x + o1.y // => 3 ``` * Create new obj w/o prototype by parsing `null` * Created obj inherit no property or method (e.g. `toString()`) ```js let o2 = Object.create(null); // o2 inherits no props or methods. ``` * Create ordinary new empty obj using **`Object.prototype`** (like obj returned by `{}` or `Object()`) ```js let o3 = Object.create(Object.prototype); // o3 is like {} or new Object(). ``` #### Use created object to guard unintended modification * Q: How to guard against unintended modification of an obj by a function (from other library)? * A: Instead of passing the obj directly to the function, pass an obj that inherit from it. So writing property do not affect original value. (like passing a read-only) ```js let o = { x: "don't change this value" }; library.function(Object.create(o)); // Guard against accidental modifications ``` ## 6.3 Querying and Setting Properties Obtain value of property: * using dot (`.`): RHS of dot should be simple identifier (not string) of property * using square bracket (`[]`): value within `[]` should be an expression that evalutes to a string (or sth can convert to string) that contains property name ```js let author = book.author; // Get the "author" property of the book. let name = author.surname; // Get the "surname" property of the author. let title = book["main title"]; // Get the "main title" property of the book. ``` Create/Set a property: * Query property, and place it on LHS ```js book.edition = 7; // Create an "edition" property of book. book["main title"] = "ECMAScript"; // Change the "main title" property. ``` ### 6.3.1 Objects As Associative Arrays ```js object.property // C like structure access object["property"] // associative array ``` JS objects are **Associative Arrays** (e.g. hash or map or dictionary) * In strong typed language (e.g. C/C++), obj's property are defined. While, JS program can **create any number of properties in any object in runtime** * `.` operator requires name of the property as identifier, which may be unknown in code. * `[]` operator allow access properties dynamically Following code shows calculate portfolio value in runtime via associative arrays ```js function computeValue(portfolio) { let total = 0.0; for(let stock in portfolio) { // For each stock in the portfolio: let shares = portfolio[stock]; // get the number of shares let price = getQuote(stock); // look up share price total += shares * price; // add stock value to total value } return total; // Return total value. } ``` ### 6.3.2 Inheritance JS obj have a set of "own properties", and they also inherit properties from prototype chain. Read properties: * If a property cannot be found in a JS obj, it will search one by one (bottom to top, from child to parent) through prototype chain ```js let o = {}; // o inherits object methods from Object.prototype o.x = 1; // and it now has an own property x. let p = Object.create(o); // p inherits properties from o and Object.prototype p.y = 2; // and has an own property y. let q = Object.create(p); // q inherits properties from p, o, and... q.z = 3; // ...Object.prototype and has an own property z. let f = q.toString(); // toString is inherited from Object.prototype q.x + q.y // => 3; x and y are inherited from o and p ``` Write (assign) properties: * check prototype chain only to verify whether read-only. * If inherited property `x` is read-only, assignment is not allowed * If assignment is allowed, the property is created/set within the current object, and do not modify prototype chain * Only exception: if `o` inherits property `x`, and that property is an accessor property with a setter method, then the setter method is called rather than creating a new property `x` within `o`. ```js let unitcircle = { r: 1 }; // An object to inherit from let c = Object.create(unitcircle); // c inherits the property r c.x = 1; c.y = 1; // c defines two properties of its own c.r = 2; // c overrides its inherited property unitcircle.r // => 1: the prototype is not affected ``` ### 6.3.3 Property Access Errors Errors during accessing property: * It's not error to query a property/object that does not exist. Return `undefined` * It's error to query property of an non-existent object. Return `TypeError` Query non-exist object ```js book.subtitle // => undefined: property doesn't exist ``` Query property of an non-exist object ```js let len = book.subtitle.length; // !TypeError: undefined doesn't have length ``` Method to guard against this problem type: * Method 1: verbose and explicit ```js let surname = undefined; if (book) { if (book.author) { surname = book.author.surname; } } ``` * Method 2: A concise and idiomatic alternative to get surname or null or undefined * Check Chap4.10.1 for short-circuiting behavior of && operator ```js surname = book && book.author && book.author.surname; ``` * Method 3: Rewrite method 2 using `?.` ```js let surname = book?.author?.surname; ``` Lists of tips: * Attempting to set property on `null` or `undefined` causes a `TypeError` * Attempting to set property may fail due to * Some properties are read-only * Some objects don't allow adding new properties * Error from property assignment: * In strict mode (Chap 5.6.3), a TypeError is thown whenever an attempt to set a property fails. * Outsie strict mode, silent when fail 3 circumstances when failed to set a property `p` of obj `o`: 1. `o` has an own property `p` that is read-only 2. `o` has an inherited property `p` that is read-only: No way to overwrite this property 3. `o` does not have an own property `p`; `o` does not inherit a property `p` with a setter method, and `o`’s extensible attribute (see §14.2) is `false`. 1. Since `p` does not already exist in `o`, and if there is no setter method to call, then `p` must be added to `o`. 2. But if `o` is not extensible, then no new properties can be defined on it. ## 6.4 Deleting Properties `delte` operator removes a property from an obj. Its operand is *property access expression* `delete` & prototypes: * only delete own properties, cannot delete inherited ones (from prototypes). * deleting inherited properties need to it on prototype, which has affect all children obj. `delete` return true if: * delete succeeded * when delete had no effect (e.g. * on inherited properties * non-exist properties * when used with an expression that's not a property access expression ```js let o = {x: 1}; // o has own property x and inherits property toString delete o.x // => true: deletes property x delete o.x // => true: does nothing (x doesn't exist) but true anyway delete o.toString // => true: does nothing (toString isn't o's own property) delete 1 // => true: nonsense, but true anyway ``` `delete` fails if: * trying to rm properties that have a `configurable` attribute of `false`. * trying to delete non-configurable properties of built-in objects * properties of the global object created by variable declaration and function declaration `delete`'s failure in strict & non-strict mode: * In strict mode: causes TypeError * In non-strict mode: evaluate to `false` e.g. shown below ```js // In strict mode, all these deletions throw TypeError instead of returning false delete Object.prototype // => false: property is non-configurable var x = 1; // Declare a global variable delete globalThis.x // => false: can't delete this property function f() {} // Declare a global function delete globalThis.f // => false: can't delete this property either ``` `delete` configurable properties of global object: * In non-strict mode, via directly delete property name ```js globalThis.x = 1; // Create a configurable global property (no let or var) delete x // => true: this property can be deleted ``` * In strict mode, method above will create **SyntaxError**. Hence need to be explicit: ```js delete x; // SyntaxError in strict mode delete globalThis.x; // This works ``` ## 6.5 Testing Properties JS obj = sets of properties. We want to test membership in the set using: * `in` operator * `hasOwnProperty()` method * `propertyIsEnumerable()` method * querying property `in` operator return true if object has an own property or an inherited property **by that name**: ```js let o = { x: 1 }; "x" in o // => true: o has an own property "x" "y" in o // => false: o doesn't have a property "y" "toString" in o // => true: o inherits a toString property ``` `hasOwnProperty()` method of an obj return: * `true` if obj has an own property with the given name * `false` for inherited properties ```js let o = { x: 1 }; o.hasOwnProperty("x") // => true: o has an own property x o.hasOwnProperty("y") // => false: o doesn't have a property y o.hasOwnProperty("toString") // => false: toString is an inherited property ``` `propertyIsEnumerable()` refines `hasOwnProperty()`. It returns: * `true` if named property is not inherited and its *enumerable* attribute is `true` * Properties created by normal JS code are enumerable unless specified ```js let o = { x: 1 }; o.propertyIsEnumerable("x") // => true: o has an own enumerable property x o.propertyIsEnumerable("toString") // => false: not an own property Object.prototype.propertyIsEnumerable("toString") // => false: not enumerable ``` Qeurying method is simple by using `!=` to make sure whether property is undefined ```js let o = { x: 1 }; o.x !== undefined // => true: o has a property x o.y !== undefined // => false: o doesn't have a property y o.toString !== undefined // => true: o inherits a toString property ``` Simple querying method vs. `in` operator: * `in` operator can tell whether a property does not exit (`null`) or exit but not defined (`undefined`) ```js let o = { x: undefined }; // Property is explicitly set to undefined o.x !== undefined // => false: property exists but is undefined o.y !== undefined // => false: property doesn't even exist "x" in o // => true: the property exists "y" in o // => false: the property doesn't exist delete o.x; // Delete the property x "x" in o // => false: it doesn't exist anymore ``` ## 6.6 Enumerating Properties ### 6.6.1 Property Enumeration Order ## 6.7 Extending Objects ## 6.8 Serializing Objects ## 6.9 Object Methods ### 6.9.1 The toString() Method ### 6.9.2 The toLocaleString() Method ### 6.9.3 The valueOf() Method ### 6.9.4 The toJSON() Method ## 6.10 Extended Object Literal Syntax ### 6.10.1 Shorthand Properties ### 6.10.2 Computed Property Names ### 6.10.3 Symbols as Property Names ### 6.10.4 Spread Operator ### 6.10.5 Shorthand Methods ### 6.10.6 Property Getters and Setters