JavaScript OOP Course 2: Objects Recap

From WikiMLT
Revision as of 09:33, 18 June 2022 by Spas (talk | contribs) (Стадий: 6 [Фаза:Утвърждаване, Статус:Утвърден]; Категория:JavaScript)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Ref­er­ences

See al­so:

Ob­ject Lit­er­als

const circle = {};

The Ob­jects are col­lec­tions of key: val­ue pears.

const circle = {
    radius: 1,                  // Property, Number
    location: {                 // Property, Object Literals      
        x: 1,                       // Property, Number
        y: 1                        // Property, Bumber
    },
    draw: function() {          // Method, a Function
        console.log('draw');
    }
};

The cir­cle ob­ject above has 3 mem­bers: 2 prop­er­ties (ra­dius and lo­ca­tion) and 1 method (draw). The prop­er­ties are used to hold val­ues. The meth­ods are used to hold log­ic. When an Ob­ject has one or more meth­ods we can say it has be­hav­ior. The syn­tax of method de­f­i­n­i­tion could be sim­pli­fied by re­mov­ing the func­tion key­word and the colon de­lim­iter.

const circle = {
    radius: 1,                  // Property, Number
    location: {                 // Property, Object Literals      
        x: 1,                       // Property, Number
        y: 1                        // Property, Bumber
    },
    draw() {          // Method, a Function
        console.log('draw');
    }
};
console.log(circle);
{radius: 1, location: {…}, draw: ƒ}

Fac­to­ries

The Fac­to­ry func­tions pro­duce Ob­jects. In the above ex­am­ple is used ES6 fea­ture where when the names of the key and the vari­able that will pro­duce the val­ue are iden­ti­cal, in­stead of prop­er­ty: prop­er­ty, we can write just prop­er­ty, with­in the ob­ject re­turned by the fac­to­ry func­tion.

function createCircle(radius, location) {
    return {
        radius,
        location,
        draw() {
            console.log('draw');
        }
    }
}
const circle1 = createCircle(1, {x: 1, y: 1});
const circle2 = createCircle(2, {x: 1, y: 1});
console.log(circle1, circle2);
{radius: 1, location: {…}, draw: ƒ} 
{radius: 2, location: {…}, draw: ƒ}

Con­struc­tors

Con­struc­tor func­tions are an­oth­er way to cre­ate an ob­ject. Un­like the Fac­to­ry func­tions which re­turn an ob­ject it­self, the Con­struc­tor func­tions are used with the new op­er­a­tor that cre­ates the new ob­ject.

function Circle(radius, location) {
    this.radius = radius;
    this.location = location;
    this.draw = function() {
        console.log('draw');
    }
}
const circle1 = new Circle(1, {x: 1, y: 1});
const circle2 = new Circle(1, {x: 1, y: 1});
console.log(circle1, circle2);
Circle {radius: 1, location: {…}, draw: ƒ}
Circle {radius: 2, location: {…}, draw: ƒ}

The Con­struc­tor func­tions are the base for prop­er un­der­stand­ing the ES6 Class­es. The ES6 Class­es will be dis­cussed in a sep­a­rate chap­ter, but let's see how the Class equiv­a­lent to the above Con­struc­tor func­tion looks like.

function Circle(radius, location) {
    this.radius = radius;
    this.location = location;
    this.draw = function() {
        console.log('draw');
    }
}
const circle1 = new Circle(1, {x: 1, y: 1});
const circle2 = new Circle(1, {x: 1, y: 1});
console.log(circle1, circle2);
Circle {radius: 1, location: {…}, draw: ƒ}
Circle {radius: 2, location: {…}, draw: ƒ}

Con­struc­tor Prop­er­ty

Every ob­ject in JavaScript has a prop­er­ty called Con­struc­tor. It ref­er­ences to the func­tion that was used to con­struct (or cre­ate) the ob­ject.

Ob­ject cre­at­ed by ES6 Class

class Circle {
    constructor(radius) {
        this.radius = radius;
    }
}
const circle = new Circle(1);
circle.constructor;
class Circle {
    constructor(radius) {
        this.radius = radius;
    }
}

Ob­ject cre­at­ed by Con­struc­tor func­tion

function Circle(radius) {
    this.radius = radius;
}
const circle = new Circle(1);
circle.constructor;
ƒ Circle(radius) {
    this.radius = radius;
}

Ob­ject cre­at­ed by Fac­to­ry func­tion

function createCircle(radius) {
    return { radius }
}
const circle = createCircle(1);
circle.constructor;
ƒ Object() { [native code] }

Ob­ject cre­at­ed by Ob­ject Lit­er­als

const circle = { radius: 1 };
circle.constructor;
ƒ Object() { [native code] }

Built-in Con­struc­tor func­tions in JavaScript

  • ƒ Ob­ject() => new Ob­ject(); {};
  • ƒ String() => new String('string'); 'string'; "string"; `string`;
  • ƒ Boolean() => new Boolean(true|false); true; false;
  • ƒ Num­ber() => new Number(1); 1; 2;
  • ƒ Func­tion() => new Function('arguments', `func­tion con­tent`) func­tion name(arguments) { func­tion con­tent };
  • ƒ Er­ror() => throw new Errot('the er­ror mes­sage');
  • etc.

Func­tions are Ob­jects

The Func­tions in JavaScript are ob­jects. And they have built-in meth­ods and prop­er­ties.

function Circle(radius) {
    this.radius = radius;
}
Circle.name;
'Circle'
Circle.length;  // return the number of the arguments
1
Circle.constructor
ƒ Function() { [native code] }

There is Con­struc­tor func­tion, called Func­tion(), that cre­ates the func­tions when we de­fine them in lit­er­als. So when we de­fine a func­tion in the fol­low­ing way.

function Circle(radius) {
    this.radius = radius;
}

In the back­ground the JavaScript en­gine does some­thing like this.

const Circle = new Function('radius', `this.radius = radius;`);
const circle = new Circle(1);
console.log(circle);
{radius: 1}

Function's Meth­ods – .call() and .ap­ply()

function Circle(radius) {
    this.radius = radius;
}
Circle.call({}, 1); // This expression is equal to 'new Circle(1)'
Circle.apply({}, [1]); // This expression is equal to 'new Circle(1)'

Val­ue vs Ref­er­ence Types

  • See al­so: JavaScript Ba­sics: Types
  • Val­ue types (Prim­i­tives) are copied by val­ue, Ref­er­ence types (Ob­jects) are copied by a ref­er­ence.

The Val­ue types (Prim­i­tives) in JavaScript are:

  1. Strings
  2. Num­bers
  3. Booleans
  4. un­de­fined
  5. null
  6. Sym­bol (ES6)

When we work with prim­i­tives (val­ue types) the val­ue is stored in­side the vari­able. This the prim­i­tives be­come in­de­pen­dent.

let x = 10;
let y = x;
x = 20;
console.log(x, y); // note the two variables will have different value
20 10

The Ref­er­ence types in JavaScript are – at all in JavaScript all Ref­er­ence types are Ob­jects:

  1. Ob­jects
  2. Ar­rays
  3. Func­tions

When we work with ob­jects (ref­er­ence types) the val­ue is stored in­to an in­ter­nal JavaScript ob­ject and the val­ue of the object's vari­able is just a ref­er­ence to that in­ter­nal ob­ject.

let x = {value: 10};
let y = x;
x.value = 20;
console.log(x.value, y.value); // note the two 'variables' will have the same value
20 20

When we copy x to y the ref­er­ence is copy not the val­ue, thus the two vari­ables (x and y) ref­er­enc­ing to the same JavaScript ob­ject in the mem­o­ry.

An­oth­er Val­ue vs Ref­er­ence Types ex­am­ple

let number = 10;
function increase(number) {
    number++;               // this 'number' Variable is complete independant of the number varible defined outside
}                           // So when we pass the 'number' variable as argument to the function its value
increase(number);           // is coppied to the internal variable. So the value of the global variable 'number'
console.log(number);        // is not touched by the function and we will see 10 on the console
10
let obj = { value: 10};
function increase(obj) {
    obj.value++;         // this 'obj' Object will have the same reference 
}                        // as the Object passed to the function as argument
increase(obj);           // So the value of the global Object 'obj'
console.log(obj);        // will be changed by the function and we will see 11 on the console
{value: 11}

Adding or Re­mov­ing Prop­er­ties – DOT and BRACK­ET No­ta­tion

The ob­jects in JavaScript are dy­nam­ic, that means af­ter cre­at­ing them we can add prop­er­ties (or meth­ods) to them or delete some prop­er­ties.

function Circle(radius) {
    this.radius = radius;
    this.draw = function() {
        console.log('draw');
    };
}
const circle = new Circle(1);

Add a prop­er­ty via DOT no­ta­tion

circle.location = {x: 1, y: 1};
consloe.log(circle);
Circle {radius: 1, location: {x: 1, y: 1}, draw: ƒ}

Add a prop­er­ty via BRACK­ET no­ta­tion

circle['visible'] = true;
consloe.log(circle);
Circle {radius: 1, location: {x: 1, y: 1}, visible: true, draw: ƒ}

The Brack­et no­ta­tion is use­ful in some cas­es, es­pe­cial­ly when we pass the name of the prop­er­ty as val­ue of a vari­able:

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

An­oth­er use­ful ap­pli­ca­tion of the Brack­et no­ta­tion is when we have spe­cial char­ac­ters in the prop­er­ty name.

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

Delete a prop­er­ty of an Ob­ject

delete circle['center-location'];
delete circle.location;

Enu­mer­at­ing Prop­er­ties

function Circle(radius) {
    this.radius = radius;
    this.draw = function() {
        console.log('draw');
    };
}
const circle = new Circle(10);

For it­er­at­ing over the prop­er­ties of an Ob­ject we can use the for.. in.. loop that will loop over the Prop­er­ties and the Meth­ods of the ob­ject.

for (let key in circle) {
    console.log(key, circle[key]);
    //          |    |-> Return the value of the key
    //          |-> Retyrn the key name
}
radius 10
draw ƒ () { console.log('draw'); }

In or­der to get on­ly the Prop­er­ties but not the Meth­ods we can use the type­of op­er­a­tor with­in an if state­ment.

for (let key in circle) {
    if (typeof circle[key] !== 'function')
        console.log(key, circle[key]);
}
radius 10

In or­der to get all the keys of an Ob­ject as an Ar­ray we can use the method Object.keys(obj). With this ap­proach we can­not sep­a­rate the Prop­er­ties and the Meth­ods.

const keys = Object.keys(circle);
console.log(keys);
(2) ['radius', 'draw']

In or­der to test whether an Ob­ject has a cer­tain Prop­er­ty we can use if.. in.. state­ment as fol­low.

if ('radius' in circle)
    console.log('Circle has a radius.');
Circle has a radius.

Ab­strac­tion – Pri­vate Prop­er­ties and Meth­ods

Ab­strac­tion means: Hide the de­tails and com­plex­i­ty and show (or ex­pose) on­ly the es­sen­tials.

function Circle(radius) {
    this.radius = radius;
    this.defaultLocation = {x: 0, y: 0};
    this.computeOptimumLocation = function(factor) {
      console.log('compute', factor);
    };
    this.draw = function() {
        this.computeOptimumLocation(0.1);
     // this.defaultLocation ...
     // this.radius ...
        console.log('draw');
    };
}
const circle = new Circle(10);

In or­der to ap­ply Ab­strac­tion and hide the de­fault­Lo­ca­tion prop­er­ty and com­puteOp­ti­mum­Lo­ca­tion method we must rewrite our Con­struc­tor func­tion in the fol­low­ing way where these mem­bers are de­fined as lo­cal vari­ables (with­in the con­stric­tor function's clo­sure).

function Circle(radius) {
    this.radius = radius;
    let defaultLocation = {x: 0, y: 0};
    let computeOptimumLocation = function(factor) {
      console.log('compute', factor);
    };
    this.draw = function() {
        computeOptimumLocation(0.1);
     // defaultLocation ...
     // this.radius ...
        console.log('draw');
    };
}
const circle = new Circle(10);

Get­ters and Set­ters


Here is how to get ac­cess to a pri­vate mem­bers of an ob­ject.

function Circle(radius) {
    this.radius = radius;
    let defaultLocation = {x: 0, y: 0};
    this.getDefaultLocation = function() {
        return defaultLocation;
        
    };
}
const circle = new Circle(10);
console.log(circle.defaultLocation());
{x: 0, y: 0}

In or­der to ac­cess the de­fault­Lo­ca­tion as prop­er­ty (not like a method as in the ex­am­ple above) we need to use Get­ter func­tion. Al­so we can us a Set­ter func­tion in or­der to set val­ues to a pri­vate prop­er­ties. Here is how this could be im­ple­ment­ed with­in our Con­struc­tor func­tion.

function Circle(radius) {
    this.radius = radius;
    let defaultLocation = {x: 0, y: 0};
    Object.defineProperty(this, 'defaultLocation', {
        get: function() {
            return defaultLocation;
        },
        set: function(value) {
            if (!value.x || !value.y)
                throw new Error('Invalid location property');
            defaultLocation = value;
        }
    });
}
const circle = new Circle(10);
console.log(circle.defaultLocation);
{x: 0, y: 0}
circle.defaultLocation = {x: 5, y: 6};
console.log(circle.defaultLocation);
{x: 5, y: 6}