Declaration Object constructor syntax
const person = new Object();
person.firstName = "John";
person.lastName = "Doe";
let person1 = new Object({
name: 'Chris',
age: 38,
greeting: function() {
alert('Hi! I\'m ' + this.name + '.');
}
});
create() method
let person2 = Object.create(person1);
person2.name; // 'Chris'
person2.greeting(); // Hi! I'm Chris.
Object literal syntax
let user = { // "user" object has 3 properties: 1st property has the name "name" and the value "John"
name: "John", // by key/name/identifier "name" the value "John" is stored
age: 30, // by key "age" the value 30 is stored
"likes birds": true, // multi word property name must be quoted // can use trailing comma
a: {b: 666},
};
Constructor function purpose is to implement reusable object creation can be done using constructor functions and the "new" operator function named starts from the capital letter executed with "new" operator constructors usually do not have a return statement. their task is to write all necessary stuff into 'this', and it automatically becomes the result.
function User(name) {
this.name = name
this.isAdmin = false
this.sayHi = function() { alert( "My name is: " + this.name ) }
}
user1 = new User // {name: undefined, isAdmin: false, sayHi: ƒ}
user2 = new User() // {name: undefined, isAdmin: false, sayHi: ƒ}
user3 = new User('John') // {name: 'John', isAdmin: false, sayHi: ƒ}
// same as
function User(name) {
const obj = {}
obj.name = name
obj.isAdmin = false
obj.sayHi = function() { alert( "My name is: " + obj.name ) }
return obj
}
Set value
user.name = "John";
user["likes birds"] = true;
Get property values
user.name; // John
user.age; // 30
user["likes birds"]; // true
user['a']['b']; // 666
Optional chaining ?. safe way to access nested object properties, even if a property doesn’t exist safe reading and deleting, but not writing no error if key does not exist ?. stops the evaluation & returns undefined if the value before ?. is undefined or null variable before ?. must be declared works for .key , ?.() , ?.[]
let user = {car: "volvo"}
user?.car // volvo
user?.address // undefined
user?.address?.street // undefined
user?.address.street // error
// same as
user.address ? user.address.street : undefined
// no error if method does not exist
let userGuest = {};
let userAdmin = {
admin() {
alert("I am admin");
}
};
userAdmin.admin?.(); // I am admin
userGuest.admin?.(); // nothing (no such method)
// ?.()
let userAdmin = {
admin() {
alert("admin");
}
}
let userGuest = {};
userAdmin.admin(); // admin
userAdmin.admin?.(); // admin
userGuest.admin(); // error
userGuest.admin?.(); // nothing (no such method)
// ?.[]
let key = "firstName";
let user1 = {
firstName: "John"
};
let user2 = null;
user1?.[key] // John
user2?.[key] // undefined
// read, delete, but not write
let user = {}
delete user?.name; // delete user.name if user exists
user?.name = "John"; // Error, doesn't w ause it evaluates to undefined = "John"
Remove property
delete user.age;
delete user["likes birds"];
Dynamic key with square brackets
let key = "likes birds";
user[key] = true;
let fruit = "apple";
let bag = {
[fruit]: 5, // the name of the property is taken from the var "fruit"
};
let fruit = 'apple';
let bag = {
[fruit + 'Computers']: 5 // bag.appleComputers = 5
};
Property value shorthand
function makeUser(name, age) {
return {
name, // same as name: name
age, // same as age: age
// ...
};
}
let user = makeUser("John", 30); // {name: "John", age: 30}
Property existence, “in” operator
let user = { name: "John", age: 30 };
"age" in user // true
"blabla" in user // false
let key = 'age';
key in user // true
For…in loop
let obj = {
name: "John",
age: 30,
isAdmin: true
};
// iterates over properties of an object
for (let key in obj) {
console.log( key, obj[key] ); // name John, age 30, isAdmin true
}
Order Integer properties are sorted, others appear in creation order
let obj = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
"1": "USA"
};
for (let code in obj) {
alert(code); // 1, 41, 44, 49
}
Object references Objects are stored and copied “by reference”. On object copy, the reference is copied, but object itself is not duplicated.
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // changed by the "admin" reference
user.name; // 'Pete', changes are seen from the "user" reference
user === admin; // true
Two objects are not equal, even though they look alike.
let a = {};
let b = {};
a == b ; // false
Object declared as const can be modified.
const user = { name: "John" };
user.name = "Pete";
user.name; // Pete
Cloning Shallow copy
let obj = { name: "John", age: 30 };
let clone = Object.assign({}, user);
// or
clone = { ...obj }
Nested cloning use existing library _.cloneDeep(obj) from the library lodash
// npm i lodash.clonedeep
const obj = [{ 'a': 1 }, { 'b': 2 }];
const deep = _.cloneDeep(obj);
Object.keys, values, entries
let user = { name: "John", age: 30 };
Object.keys(user) // ["name", "age"] // real array, not an iterator
Object.values(user) // ["John", 30]
Object.entries(user) // [ ["name","John"], ["age",30] ]
for (let value of Object.values(user)) {
alert(value); // John, then 30
}
// loop over keys-and-values
for (let [key, value] of Object.entries(obj)) {
alert(`${key}:${value}`); // name:John, then age:30
}
Array into Object Object.fromEntries()
let arr = [
['banana', 1],
['orange', 2],
['meat', 4]
]
let obj = Object.fromEntries(arr); // {banana: 1, orange: 2, meat: 4}
Map over object
let prices = {
banana: 1,
orange: 2,
meat: 4,
};
// convert to array, map, and then fromEntries gives back the object
let doublePrices = Object.fromEntries(
Object.entries(prices).map(([key, value]) => [key, value * 2])
)
doublePrices // {banana: 2, orange: 4, meat: 8}
Object & 'this' this is an object means "current object"
// example 1
{
let user = {
name: "John",
age: 30,
showThisName() { alert(this.name) },
};
user.showThisName(); // "John"
// same as alert(user.name);
}
// example 2
{
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() { alert( this.name ); }
// use the same function in two objects
user.f = sayHi; // assign method to object
admin.f = sayHi;
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
}
// "this" in function
function x() { alert( this ); }
x() // [object Window]
// arrow function has no “this”, it is taken from the outer “normal” function
{
let user = {
name: "John",
sayHi() {
let arrow = () => alert(this.name);
arrow();
}
};
user.sayHi(); // John
}
Chainable methods Just return this
let ladder = {
step: 0,
up() {
this.step++;
return this;
},
down() {
this.step--;
return this;
},
showStep: function() {
// shows the current step
alert( this.step );
}
}
ladder.up().up().down().showStep(); // 1
Global object The global object holds variables available everywhere In a browser it is named window In Node.js it is named global Can be accessed by globalThis or window
var gVar = 5
window.gVar // 5 (became a property of the global object)
window.currentUser = { name: "John"};
window.currentUser.name // John
Primitive as an object Primitives also have methods, but how? they are not objects A special object is created that has useful methods, like toUpperCase() , runs and destroyed null & undefined have no methods "Hello".toUpperCase(); // HELLO Property flags and descriptors Object properties, have 3 attributes (flags): "writable" – if true, the value can be changed, otherwise it’s read-only "enumerable" – if true, then listed in loops, otherwise not listed "configurable" – if true, the property can be deleted and flags can be modified, otherwise not by default they are all true Object.getOwnPropertyDescriptor() Object.defineProperty(obj, prop, descriptor) Can work with flags using descriptors methods: Object.getOwnPropertyDescriptor(obj, propertyName) - returns “property descriptor” object: it contains the value and all the flags. Object.defineProperty(obj, prop, descriptor) - defines a new property on object or modifies an existing ones & returns the object Object.defineProperties(obj, props) - defines new or modifies existing properties directly on an object, returning the object. Sets many properties at once. Object.getOwnPropertyDescriptors(obj) - returns object containing all own property descriptors of an object Object.preventExtensions(obj) - forbids the addition of new properties to the object Object.seal(obj) - forbids adding/removing of properties. Sets 'configurable' to false for all existing properties. Object.freeze(obj) - forbids adding/removing/changing of properties. Sets 'configurable' to false , 'writable' to false for all existing properties. Object.isExtensible(obj) - returns false if adding properties is forbidden, otherwise true . Object.isSealed(obj) - returns true if adding/removing properties is forbidden, and all existing properties have configurable: false . Object.isFrozen(obj) - returns true if adding/removing/changing properties is forbidden, and all current properties are configurable: false , writable: false .
// getOwnPropertyDescriptor
let user = { name: "John" }
let descriptor = Object.getOwnPropertyDescriptor(user, 'name')
descriptor // {value: 'John', writable: true, enumerable: true, configurable: true}
// defineProperty
let user = {
name: "John",
toString() { return this.name }
}
for (let key in user) alert(key) // name, toString
Object.defineProperty(user, "name", {
value: "John",
writable: false, // won't be able to change user.name or its flags
configurable: false, // delete user.name; // Error // can not change even flags //
enumerable: false, // for (let key in user) alert(key); // toString ONLY!!!
// + there are many more property settings
})
user.name = "Anton"
user.name // "John"
delete user.name // false
// non-enumerable properties are also excluded from Object.keys
// making a property non-configurable is a one-way road, cannot change it back with defineProperty
// “configurable: false” prevents change of flags and its deletion, while allowing to change its value
// defineProperties
// method defines new or modifies existing properties directly on an object, returning the object
// we can set many properties at once.
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
});
// getOwnPropertyDescriptors
// method defines new or modifies existing properties directly on an object, returning the object
// we can set many properties at once.
Object.getOwnPropertyDescriptors(user)
/*
{
name: {value: 'John', writable: false, enumerable: false, configurable: false}
surname: {value: 'Smith', writable: false, enumerable: false, configurable: false}
toString: {writable: true, enumerable: true, configurable: true, value: ƒ}
}
*/
Getters & setters In object literal they are denoted by get and set keywords
let user = {
name: "John",
surname: "Smith",
get fullName() {
return `${this.name} ${this.surname}`;
},
set fullName(value) {
[this.name, this.surname] = value.split(" ");
}
}
user.fullName // John Smith
user.fullName = "Jane Musk" // not a method with parenthesis, but a property
user.name // Jane
user.surname // Musk
Widely known convention is to keep value in a separate property, which starts with underscore & it is accessed via getter and setter. _propName
let user = {
get name() {
return this._name
},
set name(value) {
if (value.length < 4) {
alert("Name is too short, need at least 4 characters")
return
}
this._name = value
}
}
user.name = "Pete"; // "Pete"
user.name = ""; // Name is too short...
In accessor descriptors they are denoted by get() and set() methods
let user = { name: "John", surname: "Smith" };
Object.defineProperty(user, 'fullName', {
get() { return `${this.name} ${this.surname}`; },
set(value) { [this.name, this.surname] = value.split(" "); }
});
user.fullName; // "John Smith"
// property can be either an accessor (has get/set methods) or a data property (has a value), not both
// If we try to supply both get and value in the same descriptor, there will be an error
Can be used in constructor functions
function User(name, birthday) {
this.name = name
this.birthday = birthday
// age is calculated from the current date and stored birthday
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear()
return todayYear - this.birthday.getFullYear()
}
})
}
let john = new User("John", new Date(1992, 6, 1))
john.birthday // Wed Jul 01 1992 00:00:00 GMT+0300 (Eastern European Summer Time)
john.age // 29
Iterable object Iterable has for..of loop functionality
// strings are iterables
for (let char of "test") {
console.log(char) // t, e, s, t
}
To make an iterable we need to add Symbol.iterator & next() method.
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) alert(num); // 1, then 2, 3, 4, 5
Array - like object Array-likes are objects that have indexes and length, so they look like arrays
// strings are array-like objects
"test".length // 4
"test"[2] // 's'
// here’s the object that is array-like, but not iterable:
let arrayLike = { // has indexes and length => array-like
0: "Hello",
1: "World",
length: 2
};
for (let item of arrayLike) {} // Error (no Symbol.iterator)
From iterables & array-like object into array iterables & array-likes are not arrays, they don’t have push, pop etc. methods Array.from() makes a “real” array
let arrayLike = {
0: "Hello",
1: "World",
length: 2
};
let arr = Array.from(arrayLike);
arr.pop(); // World (method works)
// optional args
Array.from(obj[, mapFn, thisArg])
let arr = Array.from(arrayLike, str => " - " + str); // [" - Hello", " - World"]
// string into array
let str = "Hello"
let strArr1 = Array.from(str)
let strArr2 = str.split("")
console.log(strArr1, strArr2) // ["H", "e", "l", "l", "o"] ["H", "e", "l", "l", "o"]
// we can convert jQuery collection into array with such method
Objects are not equal From MDN In JavaScript, objects are a reference type. Two distinct objects are never equal, even if they have the same properties. Only comparing the same object reference with itself yields true. Primitives
'abc' === 'abc' // true
123 === 123 // true
false === false // true
null === null // true
undefined === undefined // true
Symbol("Sym") === Symbol("Sym") // false
Objects
{} === {} // false
[] === [] // false
(() => 0) === (() => 0) // false
Conditionally add a property into an object
const didIPassExam = true
const study = {
monday : 'writing',
tuesday : 'reading',
...(didIPassExam && {wednesday : 'sleep happily'})
}
Without parentheses also works (but looks weird imho)
const obj = {
key1: 'value 1',
key2: 'value 2',
...true && { key3: 'value 3' }
}