1 Introduction to Objects
2 Creating Objects
3 Objects Prototype
4 Querying, Setting and Removing Object Properties
5 Testing Object Properties
6 Enumerating Object Properties
7 Copying object properties with Object.assign()
8 Extensions to Object Literal Syntax
1. Introduction to Objects
object ⇔ {prop1, prop2, prop3, ... , propN}, (property ⇔ name:value)
A JavaScript object is a set of properties, each of which has a name and a value.
- since property names are strings, we say that an object maps string to values. In computer science, a data structure mapping string to values is named hash map, hash table, dictionary or associative array.
- JavaScript objects are dynamic, as properties can be added and deleted after the object creation.
- in contrast to primitive value, JavaScript objects are mutable and manipulated by reference.
Object properties
An object property associates a name to a value. A property name may be any string. The property value may be any JavaScript value: a primitive value or an object or the two getter and setter functions.
Object properties attributes
Each property has associated data called attributes:
| attribute name | attribute meaning | attribute values |
|---|---|---|
| writable | may I set the property's value | true|false |
| enumerable | may I iterate with for/in the property's name | true|false |
| configurable | may I delete property or modify its attributes | true|false |
- JavaScript built-in objects' properties are non-writable, non-enumerable and non-configurable, by default.
- User-defined objects' properties are writable, enumerable and configurable, by default.
Prototypal Inheritance
In addition to keeping its own properties, a JavaScript object inherits the properties of another object, known as its "prototype", this is called prototypal inheritance.
Terminology for objects
Here are some terms used to distinguish three categories of JavaScript objects:
- native or built-in objects
- A native object is an object or class of objects defined by the ECMAScript specification.
For example, arrays, functions, dates and regular expressions are native objects - host objects
- A host object is an object defined by the host environment (such as a web browser) within which the JavaScript interpreter
is embedded
For exampleHTMLElementobjects are host objects. Host objects may also be native objects. - user-defined objects
- A user-defined object is an object created during execution of JavaScript code
Terminology for object properties
- own property
- An own property is a property defined directly on an object
- inherited property
- An inherited property is a property defined by an object’s prototype object
2. JavaScript Object Creation
Three ways to create objects:
- with object literals notation
- with the
newkeyword - with the
Object.create()function
Creating objects with literal notation
An object literal is a comma-separated list of properties enclosed within curly braces. Each property is a colon separated 'name:value' pair.
An object literal expression creates a new object each time it is evaluated.
// object with no (own) properties
let emptyObj = {};
// object with two (own) properties
let location = { latitude: '53.349805', longitude: '-6.260310' };
// object with four (own) properties
let actress = {
firstName: 'Uma',
middleName: 'Karuna',
lastName: 'Thurman',
birth: {place: 'Boston', date: 'April 29, 1970'}
};
Creating objects with new keyword
The new keyword followed by a constructor function invocation creates and initialize a new object.
You can use language built-in constructor function or your own defined constructor function.let object = new Object(); // create an empty object: same as {}
let array = new Array(); // create an empty array: same as []
let date = new Date(); // create a Date object with current date
let regexp = new RegExp("\\d"); // create a RegExp object
Creating objects with Object.create() static function
The static function Object.create() creates a new object, using its first argument as the prototype of that object.
Object.create() takes an optional second argument for adding properties to the new object.
var object = Object.create(Object.prototype); // create an empty object: like {} or new Object().
var actress = {fullName: 'Uma Thurman'};
var heir = Object.create(actress, {fullName: {value: 'Maya Thurman-Hawke', configurable: false}});
3. Object prototype
The prototype object
- every JavaScript object references another object (or null) known as its prototype
- the object inherits the proprieties of its prototype
The object's prototype is set at creation time
- the way an object is created affects the object's prototype
- all objects created with literals have as prototype the
Object.prototypeobject - objects created with
newuse the value of theprototypeproperty of the constructor function to set their prototype: objects created withnew Object()inherit fromObject.prototype; objects created bynew Array()usesArray.prototypeas their prototype; objects created bynew Date()usesDate.prototypeas their prototype - objects created with
Object.create()use the first argument to the function
The [[Prototype]] attribute
- an object
[[Prototype]]attribute is a reference to its prototype object
Accessing the prototype of an object
- the
Object.getPrototypeOf(obj)static function returns the prototype object of the obj argument - the
Object.setPrototypeOf(obj, prototype)static function sets the prototype object of the obj argument - note: accessing an object's
[[Prototype]]attribute is equivalent to accessing the object's__proto__property
Example: getting the prototype of an object
let father = { fullName: 'Kirk Douglas' };
// create an heir object
let son = Object.create(father);
// get the prototype
console.log(Object.getPrototypeOf(son)===father); // true
Note: the Object's
Prototype]] property vs the <functionName>.prototype property
- do not confuse an object's
[[Prototype]]attribute with a<functionName>.prototypeproperty. - the
<functionName>.prototypeproperty specifies the prototype object to be assigned to all the objects created by the<functionName>when used as a constructor
The prototype chain
- every prototype object (but not
Object.prototype) have a prototype from which it inherits properties - for example,
Array.prototype,Date.prototype, etc. inherit properties fromObject.prototype - this means that a date object inherits properties from both
Date.prototypeandObject.prototype - this series of prototype objects is known as a prototype chain
Checking the prototype chain
- the
Object.prototype.isPrototypeOf()method allows to determine whether one object is the prototype of or is part of the prototype chain of another object
Example:
let father = { fullName: 'Kirk Douglas' };
// create an heir object
let son = Object.create(father);
// check the prototype
console.log(father.isPrototypeOf(son)); // true: son inherits from father
console.log(Object.prototype.isPrototypeOf(son)); // true: son inherits from Object.prototype
4. Querying, Setting and Deleting Object Properties
To obtain the value of a property, use the dot operator or the square brakets.
object.propName // propName is identifier // or object[expression] // expression evaluates to string
- if you use the dot operator, on the right there must be an identifier that gives name to the property.
- if you use the square brackets, the value within the brackets must an expression that evaluates to a string containing the property name.
Examples
// actress object
const actress = {
firstName: 'Uma',
lastName: 'Thurman',
'Social Security number': 'UT3 BS 3476'
};
// access with identifier
console.log(actress.firstName); // Uma
// access with variable expression
let pName = "Social Security number";
console.log(actress[pName]); // UT3 BS 3476
// accessing a nonexistent property, it yields undefined
console.log(actress.middleName); // undefined
To create or set a property, use a dot or square brackets on the left-hand side of an assignment expression:
- when using the square brackets, the value in the brackets must be any expression that evaluates or that can be converted to a string, for example a number is used to access array elements.
// setting properties
let actress = {
firstName: 'Uma',
lastName: 'Thurman',
};
actress.middleName = 'Karuna';
actress["TAX ID number"] = '8759825UT';
console.log(actress); // {firstName: "Uma", lastName: "Thurman", middleName: "Karuna", TAX ID number: "8759825UT"}
To remove a property from an object use the delete operator
- the delete operator deletes own properties, not inherited properties
- a delete expression always evaluates to true, whether the property exists or does not exist
- the delete operator does not remove a property that has a configurable attribute of false
// deleting properties
let actress = {
firstName: 'Uma',
middleName: 'Karuna',
lastName: 'Thurman',
'Social Security number': 'UT3 BS 3476'
};
delete actress.middleName; // the actress has no middleName property
delete actress['Social Security number']; // the actress has no Social Security number property either
console.log(actress); // {firstName: "Uma", lastName: "Thurman"}
Objects As Associative Arrays
The following two JavaScript expression have the same value:
object.property object["property"]
The first syntax is similar to accessing a static field of a struct in C or an object in Java, the second syntax is similar to accessing an array indexed by strings. In fact, JavaScript objects are associative arrays.
JavaScript objects are associative arrays, as a consequence object properties must not be defined in advance and a program can create any number of properties in any object at runtime. Since strings are datatype and programs can evaluate strings at runtime, programs which want to define new properties on an object have to use the array notation, not the dot notation.
You can use an object to store (x,y) pairs data from user's input leveraging the array notation.
Property Access and Inheritance
How does the prototype chain affect variable resolution, when querying an object's propriety?
JavaScript objects have a set of own properties and a set of inherited properties:
when you query the property x in the object o, the property is searched on that object and, if not found,
it is searched on his prototype and if not found on the prototype of the prototype, until the
Object.prototype object is reached.
Example: a chain of prototypes
let erectus = {} // erectus inherits object methods from Object.prototype
erectus.erectusBirth = 'erectus born 800,000 years ago'; // and has an own property erectusBirth.
// neanderthalensis inherits properties from erectus and Object.prototype
let neanderthalensis = Object.create(erectus);
// and has an own property neanderthalensisBirth.
neanderthalensis.neanderthalensisBirth = 'neanderthalensis born 200,000 years ago';
// sapiens inherits properties from neanderthalensis, erectus, and Object.prototype
let sapiens = Object.create(neanderthalensis);
sapiens.sapiensBirth = 'sapiens born 70,000 years ago'; // and has an own property sapiensBirth.
console.log(sapiens.sapiensBirth); // ... "sapiensBirth" is own property
console.log(sapiens.neanderthalensisBirth); // ... "neanderthalensisBirth" property is in sapiens's prototype
console.log(sapiens.erectusBirth); // ... "erectusBirth" property is in prototype's prototype
How does the prototype chain affect variable assignment, when setting an object's propriety?
When you assign a property pName to an object, you creates or reassign an own property to the object.
If the object has an inherited property holding the same name pName, the newly created own property hides
that inherited property. If the property of the prototype object is read-only, then the assignment is not allowed.
let father = {fullName: 'Kirk Douglas'};
let son = Object.create(father); // it creates a son object
console.log(son.fullName); // "Kirk Douglas", it returns father's property
son.fullName = 'Michael Douglas'; // this hides father's property
console.log(son.fullName); // 'Michael Douglas'
5. Testing Object Properties
A JavaScript object is set of properties. If you want to check whether an object has a property with a given name, you can do this in four ways:
- with the
inoperator - with the
Object.prototype.hasOwnProperty()method - with the
Object.prototype.propertyIsEnumerable()method - by querying the property name
The in operator check whether an object has an own or an inherited property
let obj = { firstname: 'Uma', lastname: 'Thurman' };
"firstname" in obj; // true: obj has an own property "firstname"
"middlename" in obj; // false: obj doesn't have a property "middlename"
"toString" in obj; // true: obj has an inherited propery toString
The hasOwnProperty method check whether object has own property with the given name
let obj = { firstname: 'Uma', lastname: 'Thurman' };
obj.hasOwnProperty("firstname"); // true: obj has an own property firstname
obj.hasOwnProperty("middlename"); // false: obj doesn't have a property middlename
obj.hasOwnProperty("toString"); // false: toString is an inherited property
The propertyIsEnumerable method checks whether object has own property and is enumerable
let obj = Object.create({ firstname: 'Uma', lastname: 'Thurman' });
obj.fullname = 'Uma Karuna Thurman';
obj.propertyIsEnumerable("fullname"); // true: obj has an own enumerable property fullname
obj.propertyIsEnumerable("firstname"); // false: firstname is inherited, not own
Object.prototype.propertyIsEnumerable("toString"); // false: not enumerable
The property!==undefined query checks whether it has own or inherited property
- Note:
property!==undefinedquery is not useful to detect existing property set to undefined, use theinoperator instead
let obj = { firstname: 'Uma', lastname: 'Thurman' };
obj.firstname !== undefined; // true: obj has a property 'firstname'
obj.toString !== undefined; // true: obj inherits a toString property
obj.middlename !== undefined; // false: obj doesn't have a property 'middlename'
How to check for existence of null, undefined and falsy values
- Use the query if(prop != null) instead of if(prop !== undefined) to test for undefined and null at one time.
- Use the query if(obj.property) to distinguish between truthy and falsy values.
// check whether the object has a property whose value is not null or undefined
if (obj.firstname != null)
console.log("property", obj.firstname);
// check whether the object has a property whose value is not null, undefined, false, "", 0 or NaN
if (obj.firstname)
console.log("property", obj.firstname);
6. Enumerating Object Properties
If you want to go through all the properties of an object, iterate though the properties using
the for/in loop. If you only want to get a list of the properties of an object,
use certain built-in static functions. The for/in loop works with inherited and own properties,
the static functions only work with own properties.
Iterating through all the enumerable properties using the for/in loop
- the
for/inloop executes the body of the loop once for each property of the given object:- the name of the property is assigned to the loop variable
- the
for/inloop iterates through every enumerable property: both own and inherited properties. - the own properties, including methods, are enumerable by default
- the properties inherited from
Object.prototypeare not enumerable
Example - The for/in loop iterate through all the object properties
let object = {
firstName: 'Kirk',
lastName: 'Douglas',
job: 'actor'
};
for(propName in object) // loop through the properties
console.log(propName); // prints firstName, lastName and job, but not toString which is not enumerable
The for/in loop enumerates own and inherited object properties and object's methods as well.
To skip the inherited properties, use the Object.prototype.hasOwnProperty() method.
Example - Using for/in with Object.prototype.hasOwnProperty() method
let father = {
firstName: 'Kirk',
lastName: 'Douglas',
job: 'actor'
};
let son = Object.create(father); // creating son object inheriting from father object
son.firstName = 'Michael'; // updating son's firstName property
for (var key in son) {
// skip inherited properties
if(son.hasOwnProperty(key))
console.log(key); // firstName
}
Retrieving the names of the properties of an object using certain static functions
Sometimes you may want to get an array of property names for an object and then loop through that array with a for/of loop.
| function | returns an array of |
|---|---|
Object.keys() |
own property names, only enumerable ones |
Object.getOwnPropertyNames() |
own property names, whether or not they are enumerable |
Object.getOwnPropertySymbols() |
own property names that are Symbols, whether or not they are enumerable |
Reflect.ownKeys() |
own property names, both enumerable and non-enumerable and both string and Symbol |
Example - listing own property names
let actress = {
firstName: 'Uma',
lastName: 'Thurman',
middleName: 'Karuna',
birth: {place: 'Boston', date: 'April 29, 1970'}
};
let keys = Object.keys(actress); // get an array of enumerable own property names
let allKeys = Reflect.ownKeys(actress); // get an array of all the own property names
console.log(keys); // ["firstName", "lastName", "middleName", "birth"]
console.log(allKeys); // ["firstName", "lastName", "middleName", "birth"]
Example - enumerating with Object.keys, Object.values and Object.entries static functions
// iterate over keys with Object.keys and array.forEach
Object.keys(actress).forEach( key => console.log(key) );
// iterate over keys with for/of and Object.keys
for (const key of Object.keys(actress))
console.log(key);
// iterate over values with Object.values and array.forEach
Object.values(actress).forEach( value => console.log(value) );
// iterate over values with for/of and Object.values
for (const value of Object.values(actress))
console.log(value);
// iterate over entries with Object.entries and array.forEach
Object.entries(actress).forEach( ([key, value]) => console.log(key, value) );
// iterate over entries with for/of and Object.entries
for ([key, value] of Object.entries(actress))
console.log(key, value);
7. Copying object properties with Object.assign()
The Object.assign() static function copies the properties from source objects into a target object
target_obj = Object.assign(target_obj, source_obj1 [, source_obj2 ... [, source_objn] ] );
the Object.assign() function takes two or more objects as arguments
- the first argument is the target object
- the target object is modified and returned
- the second and the other optional arguments are the source objects
- source objects enumerable own properties, with both string typed names and symbolic typed names, are copied
Example: merging three object - copying the properties of two source objects into a target object
// a target object
let actress = {firstName: 'Uma', lastName: 'Thurman'};
// two source objects
let middleName = {middleName: 'Karuna'};
let SSN = {SSN: 'UT3 BS 3476'};
// merge the three objects
Object.assign(actress, middleName, SSN);
console.log(actress); // {firstName: "Uma", lastName: "Thurman", middleName: "Karuna", SSN: "UT3 BS 3476"}
// clone the actress object
let clone = Object.assign({}, actress);
console.log(clone); // {firstName: "Uma", lastName: "Thurman", middleName: "Karuna", SSN: "UT3 BS 3476"}
Example: copying whole source objects into property values of a target object
// three source objects
let pub_name = {
name: 'The Foggy Dew'
};
let pub_address = {
street: '1, Fownes Street Upper',
locality: 'Dublin',
county: 'County Dublin',
eircode: 'D02 WP21',
country: 'Ireland'
};
let pub_contacts = {
phone: [{bar: '01 677 9328'}, {office: '01 677 9659'}],
email: 'info@thefoggydew.ie'
};
// copy pub_address and pub_contacts source objects into property values of the target empty object
let pub = Object.assign( {}, pub_name,
{ address: JSON.parse(JSON.stringify(pub_address)) },
{ contacts: JSON.parse(JSON.stringify(pub_contacts)) });
// pub only has three properties
console.log('pub', pub); // {name: "The Foggy Dew", address: {…}, contacts: {…}}
// deep clone the pub object
let clone = Object.assign( {}, JSON.parse(JSON.stringify(pub)) );
console.log('clone', clone); // {name: "The Foggy Dew", address: {…}, contacts: {…}}
// test deep cloning effectiveness
console.log(pub.address === clone.address); // false
the Object.assign() function copies properties invoking property getter and setter methods
- the source object's getter method and the target object's setter method are invoked
- the source object's getter and setter methods are not copied
8. Extensions to Object Literal Syntax
Modern JavaScript add new notations to object literals syntax
Shorthand Properties
The shorthand property notation allows to create an object from a set a variables without the need to repeat the variable identifiers twice
let firstName = 'Uma';
let middleName = 'Karuna';
let lastName = 'Thurman';
// traditional syntax
var actress = {
firstName: firstName,
middleName: middleName,
lastName: lastName,
};
console.log(actress); // {firstName: "Uma", middleName: "Karuna", lastName: "Thurman"}
// modern javaScript syntax
var actress = {
firstName,
middleName,
lastName,
};
console.log(actress); // {firstName: "Uma", middleName: "Karuna", lastName: "Thurman"}
Computed Property Names
In computed property notation, square brackets encapsulate a JavaScript expression that is evaluated at runtime and the resulting value is used as name for the property
You compute property names when creating an object:
- if property names are variables containing strings
- if property names are any expressions that can be evaluated as strings
- if the property names are only known at run time, for example, because they come from user's input
In ECMAScript 5, you can computes property names only on object instances
// ES5 syntax for computed property names
let actress = {};
let firstName = 'First Name';
actress[firstName] = 'Uma'; // property name is a variable
actress['Last Name'] = 'Thurman'; // property name is a string with space
console.log(actress); // {First Name: "Uma", Last Name: "Thurman"}
ECMAScript 6 introduces computed property names on the object literal syntax
// ES6 syntax to compute property names from variables
let firstName = 'First Name', lastName = 'Last Name';
let actress = {
[firstName]: 'Uma',
[lastName]: 'Thurman'
};
console.log(actress); // {First Name: "Uma", Last Name: "Thurman"}
// ES6 syntax to compute property names from expressions
const first = 'FIRSTNAME', last = 'LASTNAME';
let actress = {
[first.toLowerCase()]: 'Uma',
[last.toLowerCase()]: 'Thurman'
};
console.log(actress); // {firstname: "Uma", lastname: "Thurman"}
Symbols as Property Names
Symbol type values are good for creating unique property names:
- you create a Symbol by calling the Symbol() constructor function, a string may be passed as argument
const symbol = Symbol('label'); // declare a symbol called 'label'
Computed property names allow to use Symbols as property names:
- use the brackets notation:
let obj = {[symbol]: value}; // declare a property having a symbol as key
obj[symbol] = value; // access the property having symbolic typed key
Example: attach extra properties to a DOM node
const symbol = Symbol('extra'); // create a symbol
const widget = document.querySelector('.mywidget'); // get a DOM node
// add a property whose name does not conflict with existing ones
widget[symbol]: { position: 'absolute', top: 10, left: 20 };
// add more properties without conflict
widget[symbol].width = 400;
widget[symbol].height = 200;
Properties with symbol keys are not enumerated, so they do not show up in for loops
The point of bringing in symbols as property names is providing objects with a safe extension mechanism
- if you are using an object coming from a source you do not have control over and you want to add new properties to external objects, you can use Symbol as property names to be sure that your properties do not conflict with properties that already exist on the external objects
- Symbols were not introduced for security reasons: if you are using symbolic names, third party code
might use the use
Object.getOwnPropertySymbols()function to read the symbols and alter your properties.
Spread Operator
The spread notation in the object literals syntax copy the own enumerable properties from another objects
Example: spread properties to clone object
// an objects with properties to spread
let name = { firstName: 'Uma', lastName: 'Thurman' };
// clone the name object into actress object
var actress = {...name};
console.log(actress); // {firstName: "Uma", lastName: "Thurman"}
Example: spread properties to merge objects
// two objects with properties to spread
let fullname = { firstName: 'Uma', middleName: 'Karuna', lastName: 'Thurman' };
let birth = { birthDate: 'April 29, 1970', birthPlace: 'Boston' };
// merge the names and birth objects into actress object
var actress = {...fullname, ...birth};
console.log(actress); // {firstName: "Uma", middleName: "Karuna", lastName: "Thurman", birthDate: "April 29, 1970", birthPlace: "Boston"}
In the code above, the properties of the objects are spread out in the object literal syntax
- if the first object and the spread objects have properties with the same name, then the first values are overwritten.
Object.assign() vs spread noation [see MDN]
- they both do shallow-cloning or merging of objects: they do not copy prototypes
-
Object.assign()triggers setters, whereas spread notation doesn't
Shorthand Methods
In defining object methods, traditional javaScript use a function expression, whereas modern JavaScript use a shortcut notation that omits the function keyword.
var actress = {
firstName: 'Uma',
lastName: 'Thurman',
birthdate: 'April 29, 1970',
// traditional JS notation
age: function() {
let millisecs = ( new Date().getTime()) - (new Date(this.birthdate).getTime() );
return Math.floor( millisecs / (1000*60*60*24*365) );
}
};
console.log(actress.age()); // 49
var actress = {
firstName: 'Uma',
lastName: 'Thurman',
birthdate: 'April 29, 1970',
// modern JS notation
age() {
let millisecs = ( new Date().getTime()) - (new Date(this.birthdate).getTime() );
return Math.floor( millisecs / (1000*60*60*24*365) );
}
};
console.log(actress.age()); // 49
Property Getters and Setters
Objects properties can be categorized in two kinds: data properties and accessor properties.
- a data property is a name, a value and a set of attributes
- an accessor property is a name and a method or a pair of methods, known as getter and a setter
How does JavaScript programs interact with accessor properties?
- if a program queries an accessor property the getter method is invoked
- if a program sets an accessor property the setter method is invoked
How does an accessor property happen to be one or two methods?
- accessor properties do not have a writable attribute
- if an accessor property only has the getter method the property is read-only
- if an accessor property only has the setter method the property is write-only
- if an accessor property has both methods the property is read-write
Notation for accessor properties in object literals
let obj = {
// a data property handled as private
_data: value,
// a read-write accessor property named propName
get propName() { return this._data; },
set propName(value) { this._data = value; }
};
To define accessor properties with object literal syntax:
- two functions declaration where the
functionkeyword is replaced withgetorset - the function names are the same as the property name
- no colon is used, but a comma is still required after the function body
Example: accessor property to generate serial numbers
The object serial generates strictly increasing serial numbers.
- the data property
_number, handled as private, holds the current number. It is only manipulated within getter and setter. - the accessor property
numberimplements the serial number and is read and written by the JavaScript program
let serial = {
// data property
_number: 0,
// the getter method returns the current $number and increment it
get number() { return this._number++; },
// the setter method sets the _number data property
set number(n) {
if (n >= this._number)
this._number = n;
else
console.log("number can only be set to values bigger than "+this._number);
}
};
// get the number
console.log(serial.number); // 0
console.log(serial.number); // 1
// set the number
serial.number=50;
console.log(serial.number); // 50
// try to set the number but argument is wrong
serial.number=10; // "number can only be set to values bigger than 51"
Example: definition of age property as a read-only accessor property
var actress = {
// data property
firstName: 'Uma',
lastName: 'Thurman',
birthDate: "April 29, 1970",
// age accessor property
get age() {
let millisecs = ( new Date().getTime()) - (new Date(this.birthDate).getTime() );
return Math.floor( millisecs / (1000*60*60*24*365) );
}
};
// query the accessor property
console.log(actress.age); // 50
No comments:
Post a Comment