JavaScript Course 4: Objects

From WikiMLT
Revision as of 08:29, 26 September 2022 by Spas (talk | contribs) (Text replacement - "mlw-continue" to "code-continue")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Ref­er­ences

See al­so:

Ba­sics

Ob­ject ori­ent­ed pro­gram­ming (OOP) is a style of pro­gram­ming where we see a pro­gram as a col­lec­tion of Ob­jects, that talk to each oth­er to per­form some func­tion­al­i­ty. The Ob­jects has:

  • Prop­er­ties – prim­i­tives de­fined by "keys": and "values",
  • Meth­ods – in­ter­nal functions() that op­er­ates with the prop­er­ties of the ob­ject.

We can ac­cess the Prop­er­ties of an Ob­ject via:

  • Dot no­ta­tion: object.proprtyName;
  • Brack­et no­ta­tion: object['proprtyName'];

Us­ing Dot no­ta­tion is clean­er and eas­i­er, but the Brack­et no­ta­tion could be used when we want to pass vari­able in the place of the key – i.e. when when us­ing a loop or so.

We can ac­cess the Meth­ods of an Ob­ject via:

  • Dot no­ta­tion (on­ly):
    • object.methodName();
    • object.methodName = function() {…};

In JavaScript Ob­jects are dy­nam­ic, so we can add ad­di­tion­al prop­er­ties and meth­ods to them, de­spite of they are de­fined by key­words var, let or con­st. When we de­fine an Ob­ject by con­st that means the type of the vari­able that holds the ob­ject is con­st and we can't change that type, but we can mod­i­fy the cre­at­ed Ob­ject it­self – chang­ing the prop­er­ties and meth­ods of the Ob­ject .

In or­der to delete a mem­ber (prop­er­ty or method) of an Ob­ject we can use the delete op­er­a­tor in the fol­low­ing way.

delete circle.name;

Ways to cre­ate an Ob­ject in JavaScript

With JavaScript, you can de­fine and cre­ate your own ob­jects. There are dif­fer­ent ways to cre­ate new ob­jects:

  1. Cre­ate a sin­gle ob­ject, us­ing an Ob­ject lit­er­al.
  2. Cre­ate a sin­gle ob­ject, with the key­word new.
  3. De­fine Ob­ject Fac­to­ry func­tion, and then gen­er­ate a new ob­ject by this func­tion.
  4. De­fine an Ob­ject Con­struc­tor func­tion, and then cre­ate ob­jects of the con­struct­ed type.
  5. Cre­ate an ob­ject us­ing Object.create().
  6. ES6 Class­es.

Lit­er­al syn­tax of Ob­ject de­f­i­n­i­tion

const circle = {
    "name": "Circle 1",         // Property of type String
    "radius": 1,                // Property of type Number
    "location": {             // Property of type Object
        "x": 1,
        "y": 1
   },
    "isVisible": true,          // Property of type Boolean
    "draw": function() {       // Method: a property of type Function
        console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
   }
};
console.log(circle);
{name: 'Circle 1', radius: 1, location: {…}, isVisible: true, draw: ƒ}

Ac­cess Prop­er­ties and Meth­ods

Dot no­ta­tion

Call and as­sign a Prop­er­ty with Dot no­ta­tion.

console.log(circle.name);
Circle 1
circle.isVisible = false;
console.log(circle.isVisible);
false

Brack­et no­ta­tion

Call and as­sign a Prop­er­ty with Brack­et no­ta­tion.

console.log(circle['location']);
{x: 1, y: 1}
circle.location['x'] = 2;
circle.location.y = 3;
console.log(circle.location.x, circle.location['y']);
2 3

Meth­ods and Dot no­ta­tion

When a Func­tion is a part of an Ob­ject in the terms of OOP we call­ing this Func­tion a Method. Meth­ods could be called and de­fined on­ly with Dot no­ta­tion.

circle.draw();
Draw a circle with: 2 3 1
circle.radius = 2;
circle.area = function() {
    return this.radius * this.radius * 3.14;
};
console.log(circle.area());
12.56

Fac­to­ry Func­tions for Ob­jects de­f­i­n­i­tion

Just like a fac­to­ry pro­duce prod­ucts, the Fac­to­ry func­tions pro­duce Ob­jects. Here is how to trans­form the Ob­ject lit­er­al de­f­i­n­i­tion in­to a fac­to­ry func­tion.

The nam­ing con­ven­tion for the Fac­to­ry func­tions is: kamel­No­ta­tion

Step 1, move the ob­ject de­f­i­n­i­tion in­side :the Fac­to­ry func­tion and re­turn it as out­put of that func­tion:

#Step 1
function createCircle() {
    const circle = {
        "name": "Circle 1",         // Property of type String
        "radius": 1,                // Property of type Number
        "location": {             // Property of type Object
            "x": 1,
            "y": 1
       },
        "isVisible": true,          // Property of type Boolean
        "draw": function() {       // Method: a property of type Function
            console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
       }
   };
    return circle;
}

Step 2, sim­pli­fy the above and just re­turn the ob­ject de­f­i­n­i­tion.

#Step 2
function createCircle() {
    return {
        "name": "Circle 1",         // Property of type String
        "radius": 1,                // Property of type Number
        "location": {             // Property of type Object
            "x": 1,
            "y": 1
       },
        "isVisible": true,          // Property of type Boolean
        "draw": function() {       // Method: a property of type Function
            console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
       }
   };
}

Step 3, con­vert hard cod­ded val­ues as pa­ra­me­ters, which val­ues will be sup­ply as ar­gu­ments when we call­ing the Fac­to­ry func­tion.

#Step 3
function createCircle(name, radius, location, isVisible) {
    return {
        "name": name,    
        "radius": radius,
        "location":  location,
        "isVisible": visible,
        "draw": function() {
            console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
       }
   };
}

Step 4, in mod­ern JS if the key and the val­ue are the same we can make our code short­er, and re­mov­ing the val­ue and sim­ply adding the key.

#Step 4
function createCircle(name, radius, location, isVisible) {
    return {
        name,    
        radius,
        location,
        visible,
        "draw": function() {
            console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
       }
   };
}

Step 5, sim­pli­fy the Meth­ods de­f­i­n­i­tions, like we de­fine a func­tion out­side of an ob­ject, but we can drop­ping the func­tion key­word.

#The fi­nal re­sult, Step 5
function createCircle(name, radius, location, isVisible) {
    return {
        name,    
        radius,
        location,
        visible,
        draw() {
            console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
       }
   };
}

Use the Fac­to­ry func­tion.

const circle1 = createCircle("Circle 1", 2, {x: 1, y: 2}, true);
console.log(circle1);
{name: 'Circle 1', radius: 2, location: {…}, visible: true, draw: ƒ}
const circle2 = createCircle("Circle 2", 4, {x: 2, y: 2}, true);
console.log(circle2);
{name: 'Circle 2', radius: 4, location: {…}, visible: true, draw: ƒ}

Con­struc­tor Func­tions for Ob­jects de­f­i­n­i­tion

The pur­pose of the Con­struc­tor func­tions is the same as the Fac­to­ry func­tions – they pro­duce Ob­jects, it's just an­oth­er ap­proach.

The nam­ing con­ven­tion for the Fac­to­ry func­tions is: Pas­cal­No­ta­tion

function Circle(name, radius, location, isVisible) {
    this.name = name;
    this.radius = radius;
    this.location = location;
    this.isVisible = isVisible;
    this.draw = function() {
        console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
   };
}

Use the Con­struc­tor func­tion.

const circle3 = new Circle("Circle 3", 6, {x: 2, y: 4}, true);
console.log(circle3);
Circle {name: 'Circle 3', radius: 6, location: {…}, isVisible: true, draw: ƒ}
circle3.draw();
Draw a circle with: 2 4 6

When we use the new op­er­a­tor three things hap­pen:

  1. The new op­er­a­tor cre­ates an emp­ty JavaScript Ob­ject, some­thing like this: con­stant x = {};
  2. Then new set this of the Con­struc­tor func­tion to point to the new emp­ty Ob­ject.
  3. Fi­nal­ly new re­turns the new Ob­ject from the con­struc­tor func­tion, but we don't need ex­plic­it­ly add a re­turn state­ment.

Class­es for Ob­jects de­f­i­n­i­tion

The pur­pose of the Class­es is the same as the Fac­to­ry and Con­struc­tor func­tions – they pro­duce Ob­jects. It's just an­oth­er mod­ern ap­proach de­fined in ES6 (ES2015).

Class­es are a tem­plate for cre­at­ing ob­jects. They en­cap­su­late da­ta with code to work on that da­ta. Class­es in JS are built on pro­to­types but al­so have some syn­tax and se­man­tics that are not shared with ES5 class-like se­man­tics.

Class­es are in fact "spe­cial func­tions", and just as you can de­fine func­tion ex­pres­sions and func­tion de­c­la­ra­tions, the class syn­tax has two com­po­nents: class ex­pres­sions and class de­c­la­ra­tions.

An im­por­tant dif­fer­ence be­tween func­tion de­c­la­ra­tions and class de­c­la­ra­tions is that while func­tions can be called in code that ap­pears be­fore they are de­fined, class­es must be de­fined be­fore they can be con­struct­ed. Code like the fol­low­ing will throw a Ref­er­enceEr­ror:

The nam­ing con­ven­tion for the Class­es (as the Fac­to­ry func­tions) is: Pas­cal­No­ta­tion

class Circle {
    constructor(name, radius, location, isVisible) {
        this.name = name;
        this.radius = radius;
        this.location = location;
        this.isVisible = isVisible;
    }
    draw() {
        console.log("Draw a circle with:", this.location.x, this.location.y, this.radius);
    }
}

Use the Con­struc­tor func­tion. The us­age is the same as the Con­struc­tor func­tions by us­ing the new op­er­a­tor.

const circle4 = new Circle("Circle 4", 8, {x: 6, y: 6}, false);
console.log(circle4);
Circle {name: 'Circle 4', radius: 8, location: {…}, isVisible: false, draw: ƒ}

Con­struc­tor Prop­er­ty

Every Ob­ject has a con­struc­tor prop­er­ty that show the func­tion that we use to cre­ate the ob­ject.

let x = {};
console.log(x.constructor);
ƒ Object() {[native code]}
let x = new String('ABC');
console.log(x.constructor);
ƒ String() {[native code]}
let x = 'ABC';
console.log(x.constructor);
ƒ String() {[native code]}

Func­tions and Ar­rays are Ob­jects

In JavaScript the Func­tions (and Ar­rays) are al­so Ob­jects. There is a built-in con­struc­tor func­tion called Func­tion that cre­ates the ob­jects of the func­tions. So the Func­tions al­so have meth­ods and prop­er­ties.

function Circle(radius) {
    this.radius = radius;
    this.draw = function() {
        console.log("Draw circle radius:", this.radius);
   }
}
console.log(Circle.name);
'Circle'
console.log(Circle.length);  // will return the number of arguments
1
console.log(Circle.constructor);  // will return the constructor function
ƒ Function() {[native code]}

See al­so Call and Ap­ply meth­ods:

Val­ue vs Ref­er­ence types

The Val­ue types, al­so called Prim­i­tive types – Strings, Num­bers, Booleans, un­de­fined, null, Sym­bol – holds the val­ue in­side the vari­able of the prim­i­tive. In oth­er hand the Ref­er­ence types – Ob­jects, Ar­rays, Func­tions – holds a rev­er­ence to an in­ter­nal variable(s) where the ac­tu­al val­ue is stored.

Prim­i­tives are copied by their val­ue. Ob­jects (ref­er­ence types) are copied by their ref­er­ence.

1.A. Ex­am­ple with prim­i­tives – when we copy the val­ue of a Prim­i­tive type we cre­ate an in­de­pen­dent vari­able:

let x = 10;
let y = x;
x = 20;
console.log(x, y); // We can see 'x' and 'y' are two indepentant variables
20 10

1.B. Ex­am­ple with ref­er­ences – when we copy the val­ue or Ref­er­ence type we copy the ref­er­ence, not the val­ue:

let x = {value: 10};
let y = x;
x.value = 20;
console.log(x);
{value: 20}
console.log(y);
{value: 20}

2.A. An­oth­er ex­am­ple with Prim­i­tive types:

let number = 10;

function increase(num) {
    num++;
}

increase(number);
console.log(number);
10

When we pass the vari­able num­ber as an ar­gu­ment of the func­tion increase(number), there is cre­at­ed a lo­cal vari­able (in­side the func­tion) that is com­plete­ly dif­fer­ent of the glob­al vari­able num­ber, so the val­ue of the glob­al vari­able is not changed.

2.B. An­oth­er ex­am­ple with Ref­er­ence types:

let object = {value: 10};

function increase(obj) {
    obj.value++;
}

increase(object);
console.log(object);
{value: 11}

In this ex­am­ple wen we pass the ob­ject as ar­gu­ment to the func­tion it is passed as its ref­er­ence. So we have two vari­ables ob­ject and obj that point­ing to the same (in­ter­nal JS) ob­ject.

Enu­mer­at­ing Prop­er­ties of an Ob­ject

const circle = {
    "radius": 1,
    draw() {console.log("Draw...");}
};
for (let key in circle)
    console.log(`${key} : ${circle[key]}`);
radius : 1
draw : draw() {console.log("Draw...");}
// Object.keys(circle) returns an Array of keys of the circle 'object', which array is iterative
for (let key of Object.keys(circle))
    console.log(`${key} : ${circle[key]}`);
radius : 1
draw : draw() {console.log("Draw...");}
// Object.keys(circle) returns an Array of keys of the circle 'object', which array is iterative
for (let entry of Object.entries(circle))
    console.log(entry);
(2) ['radius', 1]
(2) ['draw', ƒ]

If giv­en prop­er­ty or method ex­ists ex­am­ple:

if ('radius' in circle) console.log('YES');
YES

Clone an Ob­ject

const circle = {
    "radius": 1,
    draw() {console.log("Draw...");}
};

The old ap­proach – it­er­ate over the keys of an ex­it­ing ob­ject and copy their val­ues to the new ob­ject.

const another = {};
for (let key in circle)
    another[key] = circle[key];
console.log(another);
{radius: 1, draw: ƒ}

The mod­ern ap­proach – use Object.assign() which copies all prop­er­ties and meth­ods from one or more ob­jects to a new ob­ject.

const another = Object.assign({}, circle);
console.log(another);
{radius: 1, draw: ƒ}

Clone an Ob­ject and add an ad­di­tion­al prop­er­ty or method to the new ob­ject.

const another = Object.assign({"color": "yellow"}, circle);
console.log(another);
{color: 'yellow', radius: 1, draw: ƒ}

A sim­i­le and el­e­gant way to clone an ob­ject – by us­ing the Spread op­er­a­tor: ...

const another = {...circle};
console.log(another);
{radius: 1, draw: ƒ}