javascript-definitive-guide/notes/chap6_objects.md

12 KiB
Raw Blame History

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
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
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()
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())
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())
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)
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
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
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

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

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
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.
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

book.subtitle    // => undefined: property doesn't exist

Query property of an non-exist object

let len = book.subtitle.length; // !TypeError: undefined doesn't have length

Method to guard against this problem type:

  • Method 1: verbose and explicit
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
surname = book && book.author && book.author.surname;
  • Method 3: Rewrite method 2 using ?.
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 os 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
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
// 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
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:
delete x;               // SyntaxError in strict mode
delete globalThis.x;    // This works

6.5 Testing Properties

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