12 KiB
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
meansy
holds a reference to the same obj, not a copy of that obj.
- e.g.
- 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:
- using object literal
- using keyword
new
- 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'sprototype
property as prototypenew Object()
inherits fromObject.prototype
new Array()
inherits fromArray.prototype
- Only few objects have
prototype
property, they are used to defineprototypes
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()
)
- Created obj inherit no property or method (e.g.
let o2 = Object.create(null); // o2 inherits no props or methods.
- Create ordinary new empty obj using
Object.prototype
(like obj returned by{}
orObject()
)
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 propertyx
, and that property is an accessor property with a setter method, then the setter method is called rather than creating a new propertyx
withino
.
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
orundefined
causes aTypeError
- 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
:
o
has an own propertyp
that is read-onlyo
has an inherited propertyp
that is read-only: No way to overwrite this propertyo
does not have an own propertyp
;o
does not inherit a propertyp
with a setter method, ando
’s extensible attribute (see §14.2) isfalse
.- Since
p
does not already exist ino
, and if there is no setter method to call, thenp
must be added too
. - But if
o
is not extensible, then no new properties can be defined on it.
- Since
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 offalse
. - 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