let user = { // an object
name: "John", // by key "name" store value "John"
age: 30 // by key "age" store value 30
};
":" and a value to the right of it.let user = {
name: "John",
age: 30,
"likes birds": true // multiword property name must be quoted
};
delete user.age; // Delete
function makeUser(name, age) {
return {
name, // same as name: name
age, // same as age: age
// ...
};
}
0 becomes a string "0" when used as a property key:// these properties are all right
let obj = {
for: 1,
let: 2,
return: 3
};
alert( obj.for + obj.let + obj.return ); // 6
Check property existence:
in operator
"key" in objectlet user = { name: "John", age: 30 };
alert( "age" in user ); // true, user.age exists
alert( "blabla" in user ); // false, user.blabla doesn't exist
Please note that on the left side of in there must be a property name. That’s usually a quoted string.
If we omit quotes, that means a variable should contain the actual name to be tested.
let user = { age: 30 };
let key = "age";
alert( key in user ); // true, property "age" exists
For instance, let’s output all properties of user:
let user = {
name: "John",
age: 30,
isAdmin: true
};
for (let key in user) {
// keys
alert( key ); // name, age, isAdmin
// values for the keys
alert( user[key] ); // John, 30, true
}
sorted, others appear in creation order. The details follow.As an example, let’s consider an object with the phone codes:
let codes = {
"49": "Germany",
"41": "Switzerland",
"44": "Great Britain",
// ..,
"1": "USA"
};
for (let code in codes) {
alert(code); // 1, 41, 44, 49
}
Above all objects are called a “plain object”, or just Object.
There are many other kinds of objects in JavaScript:
Array to store ordered data collections,Date to store the information about the date and time,Error to store the information about an error.objects are stored and copied “by reference”, whereas primitive values: strings, numbers, booleans, etc – are always copied “as a whole value”.
Here we put a copy of message into phrase:
let message = "Hello!";
let phrase = message;
As a result we have two independent variables, each one storing the string "Hello!".
![[IMG_6675.png]]
A variable assigned to an object stores not the object itself, but its “address in memory” – in other words “a reference” to it.
let user = {name: "salman"}
The object is stored somewhere in memory (at the right of the picture), while the user variable (at the left) has a “reference” to it.
![[IMG_6676.png]]
let user = { name: "John" };
let admin = user; // copy the reference
Now we have two variables, each storing a reference to the same object, there’s still one object, but now with two variables that reference it.
![[IMG_6674 1.png]]
We can use either variable to access the object and modify its content.
let user = { name: 'John' };
let admin = user;
admin.name = 'Pete'; // changed by the "admin" reference
alert(user.name); // 'Pete', changes are seen from the "user" reference
let a = {};
let b = a;
console.log(a === b); // true (same reference)
let a = {};
let b = {};
console.log(a === b); // false (different objects)
Because JavaScript objects are stored by reference. So:
const user = {
name: "John"
};
user.name = "Pete"; // (*)
alert(user.name); // Pete
The value of user is constant, it must always reference the same object, but properties of that object are free to change.
In other words, the const user gives an error only if we try to set user=... as a whole.
We can create a new object and replicate the structure of the existing one, by iterating over its properties and copying them on the primitive level.
let user = {
name: "John",
age: 30
};
let clone = {}; // the new empty object
// let's copy all user properties into it
for (let key in user) {
clone[key] = user[key];
}
// now clone is a fully independent object with the same content
clone.name = "Pete"; // changed the data in it
alert( user.name ); // still John in the original object
The syntax is:
Object.assign(dest, ...sources)
dest is a target object.It copies the properties of all source objects into the target dest, and then returns it as the result.
Eg:
let user = {
name: "John",
age: 30
};
let clone = {}; // the new empty object
Object.assign(clone, user);
// now clone is a fully independent object with the same content
clone.name = "Pete"; // changed the data in it
alert( user.name ); // still John in the original object
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
let clone = Object.assign({}, user);
alert( user.sizes === clone.sizes ); // true, same object
// user and clone share sizes
user.sizes.width = 60; // change a property from one place
alert(clone.sizes.width); // 60, get the result from the other one
Now it’s not enough to copy clone.sizes = user.sizes, because user.sizes is an object, and will be copied by reference, so clone and user will share the same sizes:
we should use a cloning loop that examines each value of user[key] and, if it’s an object, then replicate its structure as well.
That is called a “deep cloning” or “structured cloning”. There’s structuredClone method that implements deep cloning.
The call structuredClone(object) clones the objectwith all nested properties.
let user = {
name: "John",
sizes: {
height: 182,
width: 50
}
};
let clone = structuredClone(user);
alert( user.sizes === clone.sizes ); // false, different objects
// user and clone are totally unrelated now
user.sizes.width = 60; // change a property from one place
alert(clone.sizes.width); // 50, not related
structuredClone method can clone most data types, such as objects, arrays, primitive values. (Except function)A function that is a property of an object is called its method.
So, here we’ve got a method sayHi of the object user.
let user = {
name: "John",
age: 30
};
user.sayHi = function() {
alert("Hello!");
};
user.sayHi(); // Hello!
we could use a pre-declared function as a method, like this:
let user = {
// ...
};
// first, declare
function sayHi() {
alert("Hello!");
}
// then add as a method
user.sayHi = sayHi;
user.sayHi(); // Hello!
// these objects do the same
user = {
sayHi: function() {
alert("Hello");
}
};
// method shorthand looks better, right?
user = {
sayHi() { // same as "sayHi: function(){...}"
alert("Hello");
}
};
this keyword.this do not look at object definition. Only the moment of call matters.this inside foo() is undefined, because it is called as a function, not as a method with “dot” syntax.let user = {
name: "John",
age: 30,
sayHi() {
// "this" is the "current object"
alert(this.name);
}
};
user.sayHi(); // John
The value of this is evaluated during the run-time, depending on the context.
let user = { name: "John" };
let admin = { name: "Admin" };
function sayHi() {
alert( this.name );
}
// use the same function in two objects
user.f = sayHi;
admin.f = sayHi;
// these calls have different this
// "this" inside the function is the object "before the dot"
user.f(); // John (this == user)
admin.f(); // Admin (this == admin)
admin['f'](); // Admin (dot or square brackets access the method – doesn't matter)
Calling without an object:
this == undefined
We can even call the function without an object at all:
function sayHi() {
alert(this);
}
sayHi(); // undefined
In this case this is undefined in strict mode. If we try to access this.name, there will be an error.
In non-strict mode the value of this in such case will be the global object (window in a browser, we’ll get to it later in the chapter Global object). This is a historical behavior that "use strict" fixes.
Usually such call is a programming error. If there’s thisinside a function, it expects to be called in an object context.
this. If we reference this from such a function, it’s taken from the outer “normal” function.For instance, here arrow() uses this from the outer user.sayHi() method:
let user = {
firstName: "Ilya",
sayHi() {
let arrow = () => alert(this.firstName);
arrow();
}
};
user.sayHi(); // Ilya
That’s a special feature of arrow functions, it’s useful when we actually do not want to have a separate this, but rather to take it from the outer context.
Try this Question, you’ll get better understanding on it.
Constructor functions technically are regular functions. There are two conventions though:
"new" operator.function Person(name) {
this.name = name;
this.sayHi = function () {
console.log(`Hi, I am ${this.name}`);
};
}
let user = new Person("Salman");
user.sayHi(); // Hi, I am Salman
When a function is executed with new, it does the following steps:
this.this, adds new properties to it.this is returned.In other words, new Person(...) does something like:
function Person(name) {
// this = {}; (implicitly)
// add properties to this
this.name = name;
this.sayHi(){
console.log(`Hi, I am ${this.name}`);
}
// return this; (implicitly)
}
Of course, we can add to this not only properties, but methods as well.
So let person = new Person("Salman") gives the same result as:
let person = {
name: "Salman",
sayHi(){
console.log(`Hi, I am ${this.name}`);
}
};
If we have many lines of code all about creation of a single complex object, we can wrap them in an immediately called constructor function, like this:
// create a function and immediately call it with new
let user = new function() {
this.name = "John";
this.isAdmin = false;
// ...other code for user creation
// maybe complex logic and statements
// local variables etc
};
This constructor can’t be called again, because it is not saved anywhere, just created and called.
Usually, constructors do not have a return statement.
But if there is a return statement, then the rule is simple:
return is called with an object, then the object is returned instead of this.return is called with a primitive, it’s ignored and return thisfunction BigUser() {
this.name = "John";
return { name: "Godzilla" }; // <-- returns this object
// return; <-- returns this
}
alert( new BigUser().name ); // Godzilla, got that object
By the way, we can omit parentheses after new:
let user = new User; // <-- no parentheses
// same as
let user = new User();
Omitting parentheses here is not considered a “good style”, but the syntax is permitted by specification.
Optional chaining lets you safely access nested properties or methods without throwing errors if something is null or undefined.
let user = {};
console.log(user.address?.street); // undefined (no error)
let user = {};
console.log(user.address.city); // ❌ Error: Cannot read property 'city' of undefined
console.log(user.address?.city); // ✅ undefined (no error)
You can’t use optional chaining on the left-hand side of an assignment:
user.address?.city = "Kurnool"; // ❌ SyntaxError
A Symbol is a primitive data type used to create unique identifiers for object properties.
let id = Symbol("id");
Even if two symbols have the same description, they are never equal:
Symbol("id") === Symbol("id") // false
Symbols can be used as hidden or non-enumerable keys in objects:
let user = {
name: "Salman"
};
let id = Symbol("id");
user[id] = 123;
console.log(user[id]); // 123
Symbol-keyed properties are not visible in:
- for...in
- Object.keys()
- JSON.stringify()
But they are accessible via:
Object.getOwnPropertySymbols(user)
Creates or retrieves a shared symbol from the global registry:
let id1 = Symbol.for("id");
let id2 = Symbol.for("id");
console.log(id1 === id2); // true
Use Symbol.keyFor() to get the key for a global symbol:
Symbol.keyFor(id1); // "id"
Key Points:
objects are auto-converted to primitives, and then the operation is carried out over these primitives and results in a primitive value.
Type Conversions the rules for numeric, string and boolean conversions of primitives. Now for objects,
true in a boolean context.Dateobjects (to be covered in the chapter Date and time) can be subtracted, and the result of date1 - date2 is the time difference between two dates.alert(obj) and in similar contexts.When an object is used where a primitive value is expected, JavaScript automatically converts it to a primitive. This happens for operators like +, -, comparisons, alert(), and other built-in operations that need a primitive value first.
Objects do not behave like primitives (e.g., numbers or strings). But many operations require primitives, so JavaScript tries to coerce objects into primitive values before performing those operations.
For example:
{} + 2 // "[object Object]2" (string concatenation)
{} * 2 // NaN (number conversion fails)
alert({}); // "[object Object]" (string conversion)
JavaScript uses a hint to decide what type of primitive is wanted:
"string" — for string contexts such as alert(obj) or template literals."number" — for maths like obj * 2 or unary +obj."default" — for binary + and == comparisons.
Most built-in objects treat "default" the same way as "number".When converting an object to a primitive, JavaScript tries:
obj[Symbol.toPrimitive](hint)
If this exists, it is used first. It must return a primitive or a TypeError is thrown.
If hint == "string"
toString()valueOf().If hint == "number" or "default"
valueOf()toString().If none returns a primitive, a TypeError is thrown.
Symbol.toPrimitiveModern, most flexible method—it handles all hints:
obj[Symbol.toPrimitive] = function(hint) {
if (hint === "string") return "...";
if (hint === "number") return 123;
return "..."; // for default
};
This tells JavaScript exactly what to return for each context.
toString()Older technique, prioritized for string conversion:
.toString() returns "[object Object]".valueOf()Older technique, prioritized for number/default conversion:
.valueOf() returns the object itself (ignored).alert(obj) → attempts string conversion.+obj or obj - 1 → numeric conversion.obj + "foo" → default (toPrimitive or numeric), then string concatenation if a string is produced.Example with defined methods:
let user = {
name: "John",
money: 1000,
[Symbol.toPrimitive](hint) {
return hint == "string" ? `{name: "${this.name}"}` : this.money;
}
};
alert(user); // "{name: "John"}"
alert(+user); // 1000
alert(user + 0); // 1000
Or with old-style methods:
let user = {
name: "John",
toString() {
return this.name;
},
valueOf() {
return this.name.length;
}
};
alert(user); // toString → "John"
alert(+user); // valueOf → 4
Note: toString() may be used even for + if valueOf doesn’t return a primitive.
"string", "number", "default".Symbol.toPrimitive → toString/valueOf depending on hint..valueOf() returns the object itself and is ignored. (MDN Web Docs)If you want, I can format this as ready-to-copy Markdown for your GitHub notes.