JavaScript OOP Course 6: ES6 Classes: Difference between revisions

From WikiMLT
m (Стадий: 6 [Фаза:Утвърждаване, Статус:Утвърден]; Категория:JavaScript)
 
 
(4 intermediate revisions by the same user not shown)
Line 21: Line 21:
*Creating new objects by ES6 Classes '''enforce the use of the <code>new</code>''' operator.
*Creating new objects by ES6 Classes '''enforce the use of the <code>new</code>''' operator.
*JavaScript engine executes the body of the Classes in Strict Mode, no matter <code class="noTypo">'use strict';</code> is engaged or not.
*JavaScript engine executes the body of the Classes in Strict Mode, no matter <code class="noTypo">'use strict';</code> is engaged or not.
----Let's begin with the following Constructor function that will be converted to ES6 Class.<syntaxhighlight lang="javascript" class="mlw-continue">
----Let's begin with the following Constructor function that will be converted to ES6 Class.<syntaxhighlight lang="javascript" class="code-continue">
function Circle(radius) {
function Circle(radius) {
     this.radius = radius;
     this.radius = radius;
Line 33: Line 33:
     return this.radius * this.radius * Math.PI;
     return this.radius * this.radius * Math.PI;
};
};
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
const circle1 = new Circle(1);
const circle1 = new Circle(1);
console.log(circle1);
console.log(circle1);
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
Circle {radius: 1, draw: ƒ}
Circle {radius: 1, draw: ƒ}
     radius: 1
     radius: 1
Line 46: Line 46:
</syntaxhighlight>We can rewrite this code using ES6 Classes in the following way.
</syntaxhighlight>We can rewrite this code using ES6 Classes in the following way.


'''Step 1.''' Define the '''body of the class'''. In this body we can define properties and methods.<syntaxhighlight lang="javascript" class="mlw-continue">
'''Step 1.''' Define the '''body of the class'''. In this body we can define properties and methods.<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
}
}
</syntaxhighlight>'''Step 2.''' There is a special method that is called <code>'''constructor()'''</code> and it is used to initialize the objects. The <code>constructor()</code> method is like the Constructor function shown above. When we define a method inside the special <code>constructor()</code> method this new method will be part of the <code>new</code> object instance.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>'''Step 2.''' There is a special method that is called <code>'''constructor()'''</code> and it is used to initialize the objects. The <code>constructor()</code> method is like the Constructor function shown above. When we define a method inside the special <code>constructor()</code> method this new method will be part of the <code>new</code> object instance.<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 59: Line 59:
     }
     }
}
}
</syntaxhighlight>'''Step 3.''' Define the prototype members - in this case the <code>.area()</code> method. When we define a method outside the special <code>constructor()</code> method this new method will be part of the prototype of <code>new</code> object. When we defining methods outside the <code>constructor()</code> we can use the simplified syntax shown below.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>'''Step 3.''' Define the prototype members - in this case the <code>.area()</code> method. When we define a method outside the special <code>constructor()</code> method this new method will be part of the prototype of <code>new</code> object. When we defining methods outside the <code>constructor()</code> we can use the simplified syntax shown below.<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 73: Line 73:
     }
     }
}
}
</syntaxhighlight>'''Step 4.''' Create a new object and inspect it.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>'''Step 4.''' Create a new object and inspect it.<syntaxhighlight lang="javascript" class="code-continue">
const circle2 = new Circle(1);
const circle2 = new Circle(1);
console.log(circle2);
console.log(circle2);
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
Circle {radius: 1, draw: ƒ}
Circle {radius: 1, draw: ƒ}
     radius: 1
     radius: 1
Line 84: Line 84:
         constructor: class Circle
         constructor: class Circle
         [[Prototype]]: Object
         [[Prototype]]: Object
</syntaxhighlight>Let's look for the <code>typeof Circle</code> class. Tit is a function - the ES6 Classes are essentially functions!<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>Let's look for the <code>typeof Circle</code> class. Tit is a function - the ES6 Classes are essentially functions!<syntaxhighlight lang="javascript" class="code-continue">
typeof Circle
typeof Circle
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
'function'
'function'
</syntaxhighlight>
</syntaxhighlight>
Line 94: Line 94:


* '''Function Declaration Syntax.''' These functions '''are hoisted''' - which means the JavaScript engine raise them to the top of the program when executing it. So we can use a function defined by Function Declaration Syntax before its definition.
* '''Function Declaration Syntax.''' These functions '''are hoisted''' - which means the JavaScript engine raise them to the top of the program when executing it. So we can use a function defined by Function Declaration Syntax before its definition.
: <syntaxhighlight lang="javascript" class="mlw-continue">
: <syntaxhighlight lang="javascript" class="code-continue">
sayHello();
sayHello();


Line 100: Line 100:
     console.log('Hello');  
     console.log('Hello');  
}
}
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
Hello
Hello
</syntaxhighlight>
</syntaxhighlight>
* '''Function Expression Syntax.''' These functions '''are not hoisted'''. It is like when we defining a variable that contains a primitive - number, string, etc.
* '''Function Expression Syntax.''' These functions '''are not hoisted'''. It is like when we defining a variable that contains a primitive - number, string, etc.
: <syntaxhighlight lang="javascript" class="mlw-continue">
: <syntaxhighlight lang="javascript" class="code-continue">
sayHello();
sayHello();


Line 116: Line 116:
We can define '''ES6 Classes''' by also using a Declaration or Expression Syntax - but note in both syntaxes the '''Classes are not hoisted!'''
We can define '''ES6 Classes''' by also using a Declaration or Expression Syntax - but note in both syntaxes the '''Classes are not hoisted!'''


* '''Class Declaration Syntax.''' This is the simpler and cleaner syntax.<syntaxhighlight lang="javascript" class="mlw-continue">
* '''Class Declaration Syntax.''' This is the simpler and cleaner syntax.<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     // Class definition
     // Class definition
}
}
</syntaxhighlight>
</syntaxhighlight>
* '''Class Expression Syntax.''' This is rarely used syntax - probably you won't see it in the practice.<syntaxhighlight lang="javascript" class="mlw-continue">
* '''Class Expression Syntax.''' This is rarely used syntax - probably you won't see it in the practice.<syntaxhighlight lang="javascript" class="code-continue">
const Circle = class {
const Circle = class {
     // Class definition
     // Class definition
Line 133: Line 133:
* '''Instance methods''' - in JavaScript they can be at the Object level or at the Prototype level - these methods are '''available at the instance of a Class which is an Object'''.
* '''Instance methods''' - in JavaScript they can be at the Object level or at the Prototype level - these methods are '''available at the instance of a Class which is an Object'''.
* '''Static methods'''. - these methods are '''available on the Class itself''' (not at the Object instance). We have often used them to create utility functions that are not specific to a given object.
* '''Static methods'''. - these methods are '''available on the Class itself''' (not at the Object instance). We have often used them to create utility functions that are not specific to a given object.
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 151: Line 151:


* In the above example the expression <code>JSON.parse(str).radius</code> tests whether the input argument <code>str</code> is valid JSON format and extract the <code>radius</code> property if it is available.
* In the above example the expression <code>JSON.parse(str).radius</code> tests whether the input argument <code>str</code> is valid JSON format and extract the <code>radius</code> property if it is available.
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
const circle = new Circle(1);
const circle = new Circle(1);
console.log(circle);
console.log(circle);
Line 162: Line 162:
         [[Prototype]]: Object
         [[Prototype]]: Object
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
circle.draw();
circle.draw();
</syntaxhighlight>
</syntaxhighlight>
Line 171: Line 171:


We can see the <code>.create()</code> method is not available at the class instance - the <code>circle</code> object.
We can see the <code>.create()</code> method is not available at the class instance - the <code>circle</code> object.
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
circle.create('{"radius": 1}');
circle.create('{"radius": 1}');
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-code-error">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-code-error">
Uncaught TypeError: circle.parse is not a function
Uncaught TypeError: circle.parse is not a function
     at index.js:18
     at index.js:18
</syntaxhighlight>But the <code>.create()</code> method is available at the class <code>Circle</code> itself.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>But the <code>.create()</code> method is available at the class <code>Circle</code> itself.<syntaxhighlight lang="javascript" class="code-continue">
Circle.create('{"radius": 1}');
Circle.create('{"radius": 1}');
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 187: Line 187:
</syntaxhighlight>
</syntaxhighlight>


As it can be seen above, with this implementation we can create new instances (objects) of the class <code>Circle</code> in the following way.<syntaxhighlight lang="javascript" class="mlw-continue">
As it can be seen above, with this implementation we can create new instances (objects) of the class <code>Circle</code> in the following way.<syntaxhighlight lang="javascript" class="code-continue">
const c = Circle.create('{"radius": 1}');
const c = Circle.create('{"radius": 1}');
</syntaxhighlight>
</syntaxhighlight>
Line 195: Line 195:


== The <code>'''this'''</code> Keyword ==
== The <code>'''this'''</code> Keyword ==
'''The <code>this</code> keyword references the Object that is executing the current Function.''' For example if a function is method of an Object <code>this</code> references to that Object itself. Otherwise if that function is a regular <code>function</code> (which means it is not part of certain Object) it references to the global object which is the Window Object in the bowsers and Global Object in Nod.js.
'''The <code>this</code> keyword references the Object that is executing the current Function.''' For example if a function is method of an Object <code>this</code> references to that Object itself. Otherwise if that function is a regular <code>function</code> (which means it is not part of certain Object) it references to the global object which is the Window Object in the browsers and Global Object in Node.js.


=== <code>'''this'''</code> within Functions and Methods ===
=== <code>'''this'''</code> within Functions and Methods ===
Let's declare a constructor function (by using Function Expression Syntax) and create an object instance of Circle.<syntaxhighlight lang="javascript" class="mlw-continue">
Let's declare a constructor function (by using Function Expression Syntax) and create an object instance of Circle.<syntaxhighlight lang="javascript" class="code-continue">
const Circle = function(radius) {
const Circle = function(radius) {
     this.radius = radius;
     this.radius = radius;
Line 205: Line 205:
const c = new Circle(1);
const c = new Circle(1);
</syntaxhighlight>
</syntaxhighlight>
If we use the '''Method Call Syntax''' for the <code>.logThis()</code> method, we are going to see the new Circle object in the console, because <code>this</code> of the <code>.logThis()</code> method points to that object - remember the <code>new</code> operator which creates a new object and set <code>this</code> from the Constructor to points to that new object.<syntaxhighlight lang="javascript" class="mlw-continue">
If we use the '''Method Call Syntax''' for the <code>.logThis()</code> method, we are going to see the new Circle object in the console, because <code>this</code> of the <code>.logThis()</code> method points to that object - remember the <code>new</code> operator which creates a new object and set <code>this</code> from the Constructor to points to that new object.<syntaxhighlight lang="javascript" class="code-continue">
c.logThis();                  // this is 'method call syntax'
c.logThis();                  // this is 'method call syntax'
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 215: Line 215:
         [[Prototype]]: Object
         [[Prototype]]: Object
</syntaxhighlight>
</syntaxhighlight>
Now let's get a reference to the method in a variable.<syntaxhighlight lang="javascript" class="mlw-continue">
Now let's get a reference to the method in a variable.<syntaxhighlight lang="javascript" class="code-continue">
const logThis = c.logThis;        // by omitting () we just creating a reference to a function and not calling it
const logThis = c.logThis;        // by omitting () we just creating a reference to a function and not calling it
</syntaxhighlight>'''Note:''' '''By omitting <code>()</code> at the end of a function (or method) we just creating a reference to that function''' and not calling it. To prove that let's log the variable to the console - we will see the function itself not the output of its execution.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>'''Note:''' '''By omitting <code>()</code> at the end of a function (or method) we just creating a reference to that function''' and not calling it. To prove that let's log the variable to the console - we will see the function itself not the output of its execution.<syntaxhighlight lang="javascript" class="code-continue">
console.log(logThis);         
console.log(logThis);         
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
ƒ () { console.log(this); }
ƒ () { console.log(this); }
</syntaxhighlight>Now if we use '''Function Call Syntax''' for the variable containing the reference - run it just as regular function, we will see the Window Object, that is the default context of the regular functions.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>Now if we use '''Function Call Syntax''' for the variable containing the reference - run it just as regular function, we will see the Window Object, that is the default context of the regular functions.<syntaxhighlight lang="javascript" class="code-continue">
logThis();                    // this is 'function call syntax'
logThis();                    // this is 'function call syntax'
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 230: Line 230:


=== Strict Mode changing behavior of <code>'''this'''</code> within Functions ===
=== Strict Mode changing behavior of <code>'''this'''</code> within Functions ===
When we enable the Strict Mode, by placing <code>'use strict';</code> in the beginning of our program, the JavaScript engine will be more sensitive, it will do more error checking and also it '''will change the behavior of of the <code>this</code> keyword within functions'''. So if enabling the Strict Mode the output of the above function will be ''<code>undefined</code>'' instead of the Window Object.<syntaxhighlight lang="javascript" class="mlw-continue">
When we enable the Strict Mode, by placing <code>'use strict';</code> in the beginning of our program, the JavaScript engine will be more sensitive, it will do more error checking and also it '''will change the behavior of of the <code>this</code> keyword within functions'''. So if enabling the Strict Mode the output of the above function will be ''<code>undefined</code>'' instead of the Window Object.<syntaxhighlight lang="javascript" class="code-continue">
'use strict';
'use strict';
// the rest code of the program...
// the rest code of the program...
Line 242: Line 242:


=== <code>'''this'''</code> Keyword and ES6 Classes ===
=== <code>'''this'''</code> Keyword and ES6 Classes ===
'''JavaScript engine executes the body of the Classes in Strict Mode''', no matter <code class="noTypo">'use strict';</code> is engaged or not in the beginning of our program. Let's define a <code>Circle</code> class with <code>.logThis()</code> method and do repeat the above steps - we will see <code>''undefined''</code> in the console.<syntaxhighlight lang="javascript" class="mlw-continue">
'''JavaScript engine executes the body of the Classes in Strict Mode''', no matter <code class="noTypo">'use strict';</code> is engaged or not in the beginning of our program. Let's define a <code>Circle</code> class with <code>.logThis()</code> method and do repeat the above steps - we will see <code>''undefined''</code> in the console.<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 258: Line 258:


== Abstraction and Private Members ==
== Abstraction and Private Members ==
'''Abstraction means hiding the details and complexity and showing only the essential parts.''' This is one of the core principle in OOP. '''In order to implement abstraction we use Private Properties and Methods.''' So certain members of an object wont be accessible from the outside. <syntaxhighlight lang="javascript" class="mlw-continue">
'''Abstraction means hiding the details and complexity and showing only the essential parts.''' This is one of the core principle in OOP. '''In order to implement abstraction we use Private Properties and Methods.''' So certain members of an object wont be accessible from the outside. <syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 266: Line 266:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c.radius;
c.radius;
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 272: Line 272:
</syntaxhighlight>
</syntaxhighlight>


 
Let's imagine we want to '''make the <code>radius</code> property''' from the above class '''private''', so it can't be accessible from outside. Within ES6 Classes there are three approaches to do that.
Let's imagine we want to '''make the <code>radius</code> property''' from the below class '''private''', so it can't be accessible from outside. Within ES6 Classes there are three approaches to do that.


=== Using _Underscore Naming Convention ===
=== Using _Underscore Naming Convention ===
Using underscore in the beginning of the name of a property is not actually a way to make  the property private (it will be accessible from the outside) '''it is just naming convention''' used by some programmers. This is not abstraction this is a convention for developers. It doesn't prevent another developer from writing code against these underscored properties. <syntaxhighlight lang="javascript" class="mlw-continue">
Using underscore in the beginning of the name of a property is not actually a way to make  the property private (it will be accessible from the outside) '''it is just naming convention''' used by some programmers. This is not abstraction this is a convention for developers. It doesn't prevent another developer from writing code against these underscored properties. <syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 284: Line 283:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c._radius;
c._radius;
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 292: Line 291:
=== Private Members Using ES6 Symbols ===
=== Private Members Using ES6 Symbols ===


'''In ES6 we have a new primitive type called Symbol. <code>Symbol()</code>''' is a ES6 function we called to generate a '''Symbol''' primitive value which represents an '''an unique identifier'''. Note it is not a constructor (or class) so we '''don't need to use the <code>new</code>''' operator, otherwise we will get an error. Every time we call the <code>Symbol()</code> function it generates an unique value. If we do a comparison like <code>Symbol() === Symbol()</code> we will get <code>false</code>.
'''In ES6 we have a new primitive type called Symbol. <code>Symbol()</code>''' is a ES6 function we called to generate a '''Symbol''' primitive value which represents '''an unique identifier'''. Note it is not a constructor (or class) so we '''don't need to use the <code>new</code>''' operator, otherwise we will get an error. Every time we call the <code>Symbol()</code> function it generates an unique value. If we do a comparison like <code>Symbol() === Symbol()</code> we will get <code>false</code>.


So let's define a new variable of this type (by using the underscore naming convention) and the unique value generated by the <code>Symbol()</code> function to name a property of an object by using a Bracket notation.<syntaxhighlight lang="javascript" class="mlw-continue">
So let's define a new variable of this type (by using the underscore naming convention) and the unique value generated by the <code>Symbol()</code> function to name a property of an object by using a Bracket notation.<syntaxhighlight lang="javascript" class="code-continue">
const _radius = Symbol();  
const _radius = Symbol();  
</syntaxhighlight>
</syntaxhighlight>
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 305: Line 304:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
console.log(c);
console.log(c);
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 313: Line 312:
         constructor: class Circle
         constructor: class Circle
         [[Prototype]]: Object
         [[Prototype]]: Object
</syntaxhighlight>We can see our Circle object has one property named <code>Symbol()</code>. If we set multiple properties using Symbols, the property names all will show up a <code>Symbol()</code> but internally they are unique. '''This property is semi private -''' '''we cannot access this property directly in our code'''.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>We can see our Circle object has one property named <code>Symbol()</code>. If we set multiple properties using Symbols, the property names all will show up a <code>Symbol()</code> but internally they are unique. '''This property is semi private -''' '''we cannot access this property directly in our code'''.<syntaxhighlight lang="javascript" class="code-continue">
console.log(Object.getOwnPropertyNames(c));
console.log(Object.getOwnPropertyNames(c));
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
[]
[]
</syntaxhighlight>From the above command we see our object <code>c</code> doesn't have any regular property.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>From the above command we see our object <code>c</code> doesn't have any regular property.<syntaxhighlight lang="javascript" class="code-continue">
console.log(Object.getOwnPropertySymbols(c));
console.log(Object.getOwnPropertySymbols(c));
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 324: Line 323:
     length: 1
     length: 1
     [[Prototype]]: Array(0)
     [[Prototype]]: Array(0)
</syntaxhighlight>The above command returns an array of Symbols() so if we get the first element of this array we can get the radius value by using this as property name of our Circle object <code>c</code>. <syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>The above command returns an array of Symbols() so if we get the first element of this array we can get the radius value by using this as property name of our Circle object <code>c</code>. <syntaxhighlight lang="javascript" class="code-continue">
const key = Object.getOwnPropertySymbols(c)[0];
const key = Object.getOwnPropertySymbols(c)[0];
const radius = c[key];
const radius = c[key];
Line 331: Line 330:
10
10
</syntaxhighlight>
</syntaxhighlight>
----'''To make a''' '''Method private''' by using Symbol, we need to define another Symbol and use ES6 feature called '''Computed Property Names'''.<syntaxhighlight lang="javascript" class="mlw-continue">
----'''To make a''' '''Method private''' by using Symbol, we need to define another Symbol and use ES6 feature called '''Computed Property Names'''.<syntaxhighlight lang="javascript" class="code-continue">
const _radius = Symbol();  
const _radius = Symbol();  
const _draw = Symbol();
const _draw = Symbol();
Line 346: Line 345:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
console.log(c);
console.log(c);
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 360: Line 359:


'''WeakMap is a new type in ES6 to implement Private Properties and Methods in an Object.''' A WeakMap is essentially a dictionary where keys are objects and values can be anything, and the reason we call them weak maps is because the keys are weak. So if there are no references to these keys, there will be garbage collector.
'''WeakMap is a new type in ES6 to implement Private Properties and Methods in an Object.''' A WeakMap is essentially a dictionary where keys are objects and values can be anything, and the reason we call them weak maps is because the keys are weak. So if there are no references to these keys, there will be garbage collector.
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
const _radius = new WeakMap();
const _radius = new WeakMap();
</syntaxhighlight>
</syntaxhighlight>


<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 372: Line 371:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
console.log(c);
console.log(c);
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
Line 386: Line 385:
* <code>radius</code> is the property we set to that ''key''.
* <code>radius</code> is the property we set to that ''key''.


We can access a Property defined by a WeakMap in the following way.<syntaxhighlight lang="javascript" class="mlw-continue">
We can access a Property defined by a WeakMap in the following way.<syntaxhighlight lang="javascript" class="code-continue">
const _radius = new WeakMap();
const _radius = new WeakMap();


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 401: Line 400:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
console.log(c.getRadius());
console.log(c.getRadius());
</syntaxhighlight><syntaxhighlight lang="shell-session">
</syntaxhighlight><syntaxhighlight lang="shell-session">
10
10
</syntaxhighlight>
</syntaxhighlight>
----'''In order to define a Private Method''' with this approach we need another WeakMap.<syntaxhighlight lang="javascript" class="mlw-continue">
----'''In order to define a Private Method''' with this approach we need another WeakMap.<syntaxhighlight lang="javascript" class="code-continue">
const _radius = new WeakMap();
const _radius = new WeakMap();
const _move = new WeakMap();
const _move = new WeakMap();


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 430: Line 429:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c.getMove();
c.getMove();
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
move undefined
move undefined
</syntaxhighlight>We can see the private <code>._move()</code> method returns <code>''undefined''</code> for <code>this</code>. This is because the body of the class is executed in Strict Mode and the callback function is just a regular function - read the section above . We can solve this problem by using Arrow Function (as it is shown below) or we could [[JavaScript Course 6: Functions#Changing this|change <code>this</code> of the callback function]] in some other way.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>We can see the private <code>._move()</code> method returns <code>''undefined''</code> for <code>this</code>. This is because the body of the class is executed in Strict Mode and the callback function is just a regular function - read the section above . We can solve this problem by using Arrow Function (as it is shown below) or we could [[JavaScript Course 6: Functions#Changing this|change <code>this</code> of the callback function]] in some other way.<syntaxhighlight lang="javascript" class="code-continue">
const _radius = new WeakMap();
const _radius = new WeakMap();
const _move = new WeakMap();
const _move = new WeakMap();


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 458: Line 457:
const c = new Circle(10);
const c = new Circle(10);


</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c.getMove();
c.getMove();
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
move Circle {}
move Circle {}
</syntaxhighlight>
</syntaxhighlight>


== Getter and Setters with ES6 Classes ==
== Getter and Setters with ES6 Classes ==
Let's remind you, in order to define [[JavaScript Course 6: Functions#Getter and Setters|Getter and Setters when we use Constructor functions]], we've used the <code>Object.defineProperty()</code> method like is shown below. Also we've made some ''properties'' private by defining variables inside the Constructor.<syntaxhighlight lang="javascript" class="mlw-continue">
Let's remind you, in order to define [[JavaScript Course 6: Functions#Getter and Setters|Getter and Setters when we use Constructor functions]], we've used the <code>Object.defineProperty()</code> method like is shown below. Also we've made some ''properties'' private by defining variables inside the Constructor.<syntaxhighlight lang="javascript" class="code-continue">
function Circle(radius) {
function Circle(radius) {
     let _radius = radius;
     let _radius = radius;
Line 480: Line 479:
     });
     });
}
}
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
const c1 = new Circle(10);
const c1 = new Circle(10);
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c1.radius;
c1.radius;
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
10
10
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c1.radius = 15;
c1.radius = 15;
c1.radius;
c1.radius;
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
15
15
</syntaxhighlight>'''Defining Getters and Setters within ES6 Classes''' is much easier. Note in the following example we have one property called <code>radius</code>, made private by WeakMap, so it is not accessible from the outside.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>'''Defining Getters and Setters within ES6 Classes''' is much easier. Note in the following example we have one property called <code>radius</code>, made private by WeakMap, so it is not accessible from the outside.<syntaxhighlight lang="javascript" class="code-continue">
const _radius = new WeakMap();
const _radius = new WeakMap();
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
class Circle {
class Circle {
     constructor(radius) {
     constructor(radius) {
Line 508: Line 507:
     }
     }
}
}
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
const c2 = new Circle(20);
const c2 = new Circle(20);
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c2.radius;
c2.radius;
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
20
20
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c2.radius = 25;
c2.radius = 25;
c2.radius;
c2.radius;
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
25
25
</syntaxhighlight>
</syntaxhighlight>


== Inheritance and ES6 Classes ==
== Inheritance and ES6 Classes ==
Let's start with one class called <code>Shape</code>, which has one method called <code>.move()</code>.<syntaxhighlight lang="javascript" class="mlw-continue">
Let's start with one class called <code>Shape</code>, which has one method called <code>.move()</code>.<syntaxhighlight lang="javascript" class="code-continue">
class Shape {
class Shape {
     move() {
     move() {
Line 528: Line 527:
     }
     }
}
}
</syntaxhighlight>Now let's create another class called <code>Circle</code> which has its own method called <code>.draw()</code>, but '''we want to inherit also the <code>.move()</code> method''' from the <code>Shape</code> class. Within ES6 Classes this operation is much easier and cleaner than of the [[JavaScript OOP Course 4: Prototypical Inheritance#Resetting the Constructor|approach]] used with Constructor functions - we do not need to change the Prototype and reset the Constructor.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>Now let's create another class called <code>Circle</code> which has its own method called <code>.draw()</code>, but '''we want to inherit also the <code>.move()</code> method''' from the <code>Shape</code> class. Within ES6 Classes this operation is much easier and cleaner than of the [[JavaScript OOP Course 4: Prototypical Inheritance#Resetting the Constructor|approach]] used with Constructor functions - we do not need to change the Prototype and reset the Constructor.<syntaxhighlight lang="javascript" class="code-continue">
class Circle extends Shape {
class Circle extends Shape {
     draw() {
     draw() {
Line 534: Line 533:
     }
     }
}
}
</syntaxhighlight>Now let's create a Circle object and inspect it.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>Now let's create a Circle object and inspect it.<syntaxhighlight lang="javascript" class="code-continue">
const c1 = new Circle();
const c1 = new Circle();
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
console.log(c1);
console.log(c1);
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
Circle {}
Circle {}
     [[Prototype]]: Shape
     [[Prototype]]: Shape
Line 547: Line 546:
             constructor: class Shape
             constructor: class Shape
             [[Prototype]]: Object
             [[Prototype]]: Object
</syntaxhighlight>Let's imagine all of our shapes need a color - so we nee to ad such property at the <code>Shape</code> class.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>Let's imagine all of our shapes need a color - so we nee to ad such property at the <code>Shape</code> class.<syntaxhighlight lang="javascript" class="code-continue">
class Shape {
class Shape {
     constructor(color) {
     constructor(color) {
Line 558: Line 557:
}
}
</syntaxhighlight>
</syntaxhighlight>
At this point, if we have a constructor at the base class (in this example <code>Shape</code>), when we creating derived class (in this example <code>Circle</code>) which have its own constructor we mist '''initialize the base class constructor''' '''by using the <code>super</code> keyword''', otherwise we will get an error.<syntaxhighlight lang="javascript" class="mlw-continue">
At this point, if we have a constructor at the base class (in this example <code>Shape</code>), when we creating derived class (in this example <code>Circle</code>) which have its own constructor we mist '''initialize the base class constructor''' '''by using the <code>super</code> keyword''', otherwise we will get an error.<syntaxhighlight lang="javascript" class="code-continue">
class Circle extends Shape {
class Circle extends Shape {
     constructor(color, radius) {
     constructor(color, radius) {
Line 569: Line 568:
     }
     }
}
}
</syntaxhighlight>Now let's create a new Circle object, set values for the <code>color</code> and <code>radius</code> properties and inspect it.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>Now let's create a new Circle object, set values for the <code>color</code> and <code>radius</code> properties and inspect it.<syntaxhighlight lang="javascript" class="code-continue">
const c2 = new Circle('red', 10);
const c2 = new Circle('red', 10);
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
console.log(c2);
console.log(c2);
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
Circle {color: 'red', radius: 10}
Circle {color: 'red', radius: 10}
     color: "red"
     color: "red"
Line 587: Line 586:


== Method Overriding and ES6 Classes ==
== Method Overriding and ES6 Classes ==
<syntaxhighlight lang="javascript" class="mlw-continue">
<syntaxhighlight lang="javascript" class="code-continue">
class Shape {
class Shape {
     move() {
     move() {
Line 593: Line 592:
     }
     }
}
}
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
class Circle extends Shape {
class Circle extends Shape {
     move() {
     move() {
Line 599: Line 598:
     }
     }
}
}
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
const c1 = new Circle();
const c1 = new Circle();
</syntaxhighlight>Now if we call <code>c1.move()</code> we will see the child implementation of the method is used.<syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight>Now if we call <code>c1.move()</code> we will see the child implementation of the method is used.<syntaxhighlight lang="javascript" class="code-continue">
c1.move();
c1.move();
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
Circle move
Circle move
</syntaxhighlight>The reason for that goes back to the Prototypical Inheritance - so when accessing a property or method the JavaScript engine walks on the Prototypical Inheritance Tree from the child all the way to the parent and evaluate the first accessible property.
</syntaxhighlight>The reason for that goes back to the Prototypical Inheritance - so when accessing a property or method the JavaScript engine walks on the Prototypical Inheritance Tree from the child all the way to the parent and evaluate the first accessible property.


Let's imagine we have scenario where we want reuse some of the code that have been implemented at the parent <code>move()</code> method - in that case we can call that by using the <code>super</code> keyword.<syntaxhighlight lang="javascript" class="mlw-continue">
Let's imagine we have scenario where we want reuse some of the code that have been implemented at the parent <code>move()</code> method - in that case we can call that by using the <code>super</code> keyword.<syntaxhighlight lang="javascript" class="code-continue">
class Circle extends Shape {
class Circle extends Shape {
     move() {
     move() {
Line 614: Line 613:
     }
     }
}
}
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
const c2 = new Circle();
const c2 = new Circle();
</syntaxhighlight><syntaxhighlight lang="javascript" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue">
c2.move();
c2.move();
</syntaxhighlight><syntaxhighlight lang="shell-session" class="mlw-continue">
</syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue">
Shape move
Shape move
Circle move
Circle move

Latest revision as of 09:23, 20 June 2024

Ref­er­ences

See al­so:

ES6 Class­es

  • Class­es are a new way to cre­ate Ob­jects and Pro­to­typ­i­cal In­her­i­tance.
  • Class­es in JavaScript are not like class­es in oth­er lan­guages like C#, Ja­va and so on.
  • Class­es in JavaScript are syn­tac­tic sug­ar over Pro­to­typ­i­cal In­her­i­tance. It is im­por­tant to know how the Pro­to­typ­i­cal In­her­i­tance works be­fore learn­ing this new syn­tax which is clean­er and sim­pler.
  • Cre­at­ing new ob­jects by ES6 Class­es en­force the use of the new op­er­a­tor.
  • JavaScript en­gine ex­e­cutes the body of the Class­es in Strict Mode, no mat­ter 'use strict'; is en­gaged or not.

Let's be­gin with the fol­low­ing Con­struc­tor func­tion that will be con­vert­ed to ES6 Class.

function Circle(radius) {
    this.radius = radius;
    
    this.draw = function() {
        console.log('draw');
    };
}

Circle.prototype.area = function() {
    return this.radius * this.radius * Math.PI;
};
const circle1 = new Circle(1);
console.log(circle1);
Circle {radius: 1, draw: ƒ}
    radius: 1
    draw: ƒ ()
    [[Prototype]]: Object
        area: ƒ ()
        constructor: ƒ Circle(radius)
        [[Prototype]]: Object

We can rewrite this code us­ing ES6 Class­es in the fol­low­ing way. Step 1. De­fine the body of the class. In this body we can de­fine prop­er­ties and meth­ods.

class Circle {
}

Step 2. There is a spe­cial method that is called con­struc­tor() and it is used to ini­tial­ize the ob­jects. The con­struc­tor() method is like the Con­struc­tor func­tion shown above. When we de­fine a method in­side the spe­cial con­struc­tor() method this new method will be part of the new ob­ject in­stance.

class Circle {
    constructor(radius) {
        this.radius = radius;
        
        this.draw = function() {
            console.log('draw');
        };
    }
}

Step 3. De­fine the pro­to­type mem­bers – in this case the .area() method. When we de­fine a method out­side the spe­cial con­struc­tor() method this new method will be part of the pro­to­type of new ob­ject. When we defin­ing meth­ods out­side the con­struc­tor() we can use the sim­pli­fied syn­tax shown be­low.

class Circle {
    constructor(radius) {
        this.radius = radius;
        
        this.draw = function() {
            console.log('draw');
        };
    }
    
    area() {
        return this.radius * this.radius * Math.PI;
    }
}

Step 4. Cre­ate a new ob­ject and in­spect it.

const circle2 = new Circle(1);
console.log(circle2);
Circle {radius: 1, draw: ƒ}
    radius: 1
    draw: ƒ ()
    [[Prototype]]: Object
        area: ƒ area()
        constructor: class Circle
        [[Prototype]]: Object

Let's look for the type­of Cir­cle class. Tit is a func­tion – the ES6 Class­es are es­sen­tial­ly func­tions!

typeof Circle
'function'

Hoist­ing

Let's dis­cuss what we have learned about the Func­tions. In JavaScript are avail­able two way to de­fine a func­tion:

  • Func­tion De­c­la­ra­tion Syn­tax. These func­tions are hoist­ed – which means the JavaScript en­gine raise them to the top of the pro­gram when ex­e­cut­ing it. So we can use a func­tion de­fined by Func­tion De­c­la­ra­tion Syn­tax be­fore its de­f­i­n­i­tion.
sayHello();

function sayHello() {
    console.log('Hello'); 
}
Hello
  • Func­tion Ex­pres­sion Syn­tax. These func­tions are not hoist­ed. It is like when we defin­ing a vari­able that con­tains a prim­i­tive – num­ber, string, etc.
sayHello();

const sayHello = function() { 
    console.log('Hello'); 
};  // Should be determined by semicolon
Uncaught ReferenceError: Cannot access 'sayHello' before initialization
    at index.js:1

We can de­fine ES6 Class­es by al­so us­ing a De­c­la­ra­tion or Ex­pres­sion Syn­tax – but note in both syn­tax­es the Class­es are not hoist­ed!

  • Class De­c­la­ra­tion Syn­tax. This is the sim­pler and clean­er syn­tax.
    class Circle {
        // Class definition
    }
    
  • Class Ex­pres­sion Syn­tax. This is rarely used syn­tax – prob­a­bly you won't see it in the prac­tice.
    const Circle = class {
        // Class definition
    };  // Should be determined by semicolon
    

Sta­t­ic Meth­ods

In clas­si­cal ob­ject ori­ent­ed lan­guages we have two tips of meth­ods:

  • In­stance meth­ods – in JavaScript they can be at the Ob­ject lev­el or at the Pro­to­type lev­el – these meth­ods are avail­able at the in­stance of a Class which is an Ob­ject.
  • Sta­t­ic meth­ods. – these meth­ods are avail­able on the Class it­self (not at the Ob­ject in­stance). We have of­ten used them to cre­ate util­i­ty func­tions that are not spe­cif­ic to a giv­en ob­ject.
class Circle {
    constructor(radius) {
        this.radius = radius;
    }
    
    draw() {                    // Instance method, it will be available at new Circle objects
        console.log('draw');
    }
    
    static create(str) {         // Static method, it will not be available at new Circle objects        
        const radius = JSON.parse(str).radius;
        return new Circle(radius);
    }
}
  • In the above ex­am­ple the ex­pres­sion JSON.parse(str).radius tests whether the in­put ar­gu­ment str is valid JSON for­mat and ex­tract the ra­dius prop­er­ty if it is avail­able.
const circle = new Circle(1);
console.log(circle);
Circle {radius: 1}
    radius: 1
    [[Prototype]]: Object
        draw: ƒ draw()
        constructor: class Circle
        [[Prototype]]: Object
circle.draw();
draw

We can see the .cre­ate() method is not avail­able at the class in­stance – the cir­cle ob­ject.

circle.create('{"radius": 1}');
Uncaught TypeError: circle.parse is not a function
    at index.js:18

But the .cre­ate() method is avail­able at the class Cir­cle it­self.

Circle.create('{"radius": 1}');
Circle {radius: 1}
    radius: 1
    [[Prototype]]: Object
        draw: ƒ draw()
        constructor: class Circle
        [[Prototype]]: Object

As it can be seen above, with this im­ple­men­ta­tion we can cre­ate new in­stances (ob­jects) of the class Cir­cle in the fol­low­ing way.

const c = Circle.create('{"radius": 1}');

One more time:

  • We use sta­t­ic meth­ods to cre­ate util­i­ty func­tions that are not part of par­tic­u­lar ob­ject.

The this Key­word

The this key­word ref­er­ences the Ob­ject that is ex­e­cut­ing the cur­rent Func­tion. For ex­am­ple if a func­tion is method of an Ob­ject this ref­er­ences to that Ob­ject it­self. Oth­er­wise if that func­tion is a reg­u­lar func­tion (which means it is not part of cer­tain Ob­ject) it ref­er­ences to the glob­al ob­ject which is the Win­dow Ob­ject in the browsers and Glob­al Ob­ject in Node.js.

this with­in Func­tions and Meth­ods

Let's de­clare a con­struc­tor func­tion (by us­ing Func­tion Ex­pres­sion Syn­tax) and cre­ate an ob­ject in­stance of Cir­cle.

const Circle = function(radius) {
    this.radius = radius;
    this.logThis = function() { console.log(this); };
};
const c = new Circle(1);

If we use the Method Call Syn­tax for the .logTh­is() method, we are go­ing to see the new Cir­cle ob­ject in the con­sole, be­cause this of the .logTh­is() method points to that ob­ject – re­mem­ber the new op­er­a­tor which cre­ates a new ob­ject and set this from the Con­struc­tor to points to that new ob­ject.

c.logThis();                   // this is 'method call syntax'
Circle {radius: 1, draw: ƒ}
    radius: 1
    logThis: ƒ ()
    [[Prototype]]: Object
        constructor: ƒ (radius)
        [[Prototype]]: Object

Now let's get a ref­er­ence to the method in a vari­able.

const logThis = c.logThis;        // by omitting () we just creating a reference to a function and not calling it

Note: By omit­ting () at the end of a func­tion (or method) we just cre­at­ing a ref­er­ence to that func­tion and not call­ing it. To prove that let's log the vari­able to the con­sole – we will see the func­tion it­self not the out­put of its ex­e­cu­tion.

console.log(logThis);
ƒ () { console.log(this); }

Now if we use Func­tion Call Syn­tax for the vari­able con­tain­ing the ref­er­ence – run it just as reg­u­lar func­tion, we will see the Win­dow Ob­ject, that is the de­fault con­text of the reg­u­lar func­tions.

logThis();                     // this is 'function call syntax'
Window {window: Window, self: Window, document: document, name: '', location: Location, …}
    ...
    ...

Strict Mode chang­ing be­hav­ior of this with­in Func­tions

When we en­able the Strict Mode, by plac­ing 'use strict'; in the be­gin­ning of our pro­gram, the JavaScript en­gine will be more sen­si­tive, it will do more er­ror check­ing and al­so it will change the be­hav­ior of of the this key­word with­in func­tions. So if en­abling the Strict Mode the out­put of the above func­tion will be un­de­fined in­stead of the Win­dow Ob­ject.

'use strict';
// the rest code of the program...
logThis();
undefined

Note:

  • 'use strict'; can­not be used with­in the con­sole, it should be part of our script file.
  • When we en­able 'use strict'; op­tion this of the func­tions not longer point to the glob­al ob­ject which is the Win­dow Ob­ject in the bowsers and Glob­al Ob­ject in Nod.js The rea­son for this is to pre­vent us from ac­ci­dent­ly mod­i­fy­ing the glob­al ob­ject.

this Key­word and ES6 Class­es

JavaScript en­gine ex­e­cutes the body of the Class­es in Strict Mode, no mat­ter 'use strict'; is en­gaged or not in the be­gin­ning of our pro­gram. Let's de­fine a Cir­cle class with .logTh­is() method and do re­peat the above steps – we will see un­de­fined in the con­sole.

class Circle {
    constructor(radius) {
        this.radius = radius;
    }
    
    logThis() { console.log(this); }
}
const c = new Circle(1);
const logThis = c.logThis;
logThis();
undefined

Ab­strac­tion and Pri­vate Mem­bers

Ab­strac­tion means hid­ing the de­tails and com­plex­i­ty and show­ing on­ly the es­sen­tial parts. This is one of the core prin­ci­ple in OOP. In or­der to im­ple­ment ab­strac­tion we use Pri­vate Prop­er­ties and Meth­ods. So cer­tain mem­bers of an ob­ject wont be ac­ces­si­ble from the out­side.

class Circle {
    constructor(radius) {
        this.radius = radius;
    }
}
const c = new Circle(10);
c.radius;
10

Let's imag­ine we want to make the ra­dius prop­er­ty from the above class pri­vate, so it can't be ac­ces­si­ble from out­side. With­in ES6 Class­es there are three ap­proach­es to do that.

Us­ing _​​​Underscore Nam­ing Con­ven­tion

Us­ing un­der­score in the be­gin­ning of the name of a prop­er­ty is not ac­tu­al­ly a way to make the prop­er­ty pri­vate (it will be ac­ces­si­ble from the out­side) it is just nam­ing con­ven­tion used by some pro­gram­mers. This is not ab­strac­tion this is a con­ven­tion for de­vel­op­ers. It doesn't pre­vent an­oth­er de­vel­op­er from writ­ing code against these un­der­scored prop­er­ties.

class Circle {
    constructor(radius) {
        this._radius = radius;
    }
}
const c = new Circle(10);
c._radius;
10

Pri­vate Mem­bers Us­ing ES6 Sym­bols

In ES6 we have a new prim­i­tive type called Sym­bol. Sym­bol() is a ES6 func­tion we called to gen­er­ate a Sym­bol prim­i­tive val­ue which rep­re­sents an unique iden­ti­fi­er. Note it is not a con­struc­tor (or class) so we don't need to use the new op­er­a­tor, oth­er­wise we will get an er­ror. Every time we call the Sym­bol() func­tion it gen­er­ates an unique val­ue. If we do a com­par­i­son like Sym­bol() === Sym­bol() we will get false.

So let's de­fine a new vari­able of this type (by us­ing the un­der­score nam­ing con­ven­tion) and the unique val­ue gen­er­at­ed by the Sym­bol() func­tion to name a prop­er­ty of an ob­ject by us­ing a Brack­et no­ta­tion.

const _radius = Symbol();
class Circle {
    constructor(radius) {
        this[_radius] = radius;
    }
}
const c = new Circle(10);
console.log(c);
Circle {Symbol(): 10}
    Symbol(): 10
    [[Prototype]]: Object
        constructor: class Circle
        [[Prototype]]: Object

We can see our Cir­cle ob­ject has one prop­er­ty named Sym­bol(). If we set mul­ti­ple prop­er­ties us­ing Sym­bols, the prop­er­ty names all will show up a Sym­bol() but in­ter­nal­ly they are unique. This prop­er­ty is se­mi pri­vate - we can­not ac­cess this prop­er­ty di­rect­ly in our code.

console.log(Object.getOwnPropertyNames(c));
[]

From the above com­mand we see our ob­ject c doesn't have any reg­u­lar prop­er­ty.

console.log(Object.getOwnPropertySymbols(c));
[Symbol()]
    0: Symbol()
    length: 1
    [[Prototype]]: Array(0)

The above com­mand re­turns an ar­ray of Sym­bols() so if we get the first el­e­ment of this ar­ray we can get the ra­dius val­ue by us­ing this as prop­er­ty name of our Cir­cle ob­ject c.

const key = Object.getOwnPropertySymbols(c)[0];
const radius = c[key];
console.log(radius);
10

To make a Method pri­vate by us­ing Sym­bol, we need to de­fine an­oth­er Sym­bol and use ES6 fea­ture called Com­put­ed Prop­er­ty Names.

const _radius = Symbol(); 
const _draw = Symbol();

class Circle {
    constructor(radius) {
        this[_radius] = radius;
    }
    
    [_draw]() {
        console.log('draw');
    }
}
const c = new Circle(10);
console.log(c);
Circle {Symbol(): 10}
    Symbol(): 10
    [[Prototype]]: Object
        Symbol(): ƒ [_draw]()
        constructor: class Circle
        [[Prototype]]: Object

Pri­vate Mem­bers Us­ing ES6 WeakMaps

WeakMap is a new type in ES6 to im­ple­ment Pri­vate Prop­er­ties and Meth­ods in an Ob­ject. A WeakMap is es­sen­tial­ly a dic­tio­nary where keys are ob­jects and val­ues can be any­thing, and the rea­son we call them weak maps is be­cause the keys are weak. So if there are no ref­er­ences to these keys, there will be garbage col­lec­tor.

const _radius = new WeakMap();
class Circle {
    constructor(radius) {
        _radius.set(this, radius);
    }
}
const c = new Circle(10);
console.log(c);
Circle {}
    [[Prototype]]: Object
        constructor: class Circle
        [[Prototype]]: Object

In the above ex­pres­sion _radius.set(this, ra­dius);

  • _​​​radius is our WeakMap,
  • .set() is a method of the WeakMap type,
  • this is the key (the cur­rent ob­ject in our case),
  • ra­dius is the prop­er­ty we set to that key.

We can ac­cess a Prop­er­ty de­fined by a WeakMap in the fol­low­ing way.

const _radius = new WeakMap();
class Circle {
    constructor(radius) {
        _radius.set(this, radius);
    }
    
    getRadius() {
        return _radius.get(this);
    }
}
const c = new Circle(10);
console.log(c.getRadius());
10

In or­der to de­fine a Pri­vate Method with this ap­proach we need an­oth­er WeakMap.

const _radius = new WeakMap();
const _move = new WeakMap();
class Circle {
    constructor(radius) {
        _radius.set(this, radius);
        
        _move.set(this, function() {
           console.log('move', this); // we added 'this' here in order to see what it means in this context
        });
    }
    
    getRadius() {
        return _radius.get(this);
    }
    
    getMove() {
        return _move.get(this)();   // because it will return a function we are calling that function by ()
    }
}
const c = new Circle(10);
c.getMove();
move undefined

We can see the pri­vate ._​​​move() method re­turns un­de­fined for this. This is be­cause the body of the class is ex­e­cut­ed in Strict Mode and the call­back func­tion is just a reg­u­lar func­tion – read the sec­tion above . We can solve this prob­lem by us­ing Ar­row Func­tion (as it is shown be­low) or we could change this of the call­back func­tion in some oth­er way.

const _radius = new WeakMap();
const _move = new WeakMap();
class Circle {
    constructor(radius) {
        _radius.set(this, radius);
        
        _move.set(this, () => {
           console.log('move', this); // we added 'this' here in order to see what it means in this context
        });
    }
    
    getRadius() {
        return _radius.get(this);
    }
    
    getMove() {
        return _move.get(this)();   // because it will return a function we are calling that function by ()
    }
}
const c = new Circle(10);
c.getMove();
move Circle {}

Get­ter and Set­ters with ES6 Class­es

Let's re­mind you, in or­der to de­fine Get­ter and Set­ters when we use Con­struc­tor func­tions, we've used the Object.defineProperty() method like is shown be­low. Al­so we've made some prop­er­ties pri­vate by defin­ing vari­ables in­side the Con­struc­tor.

function Circle(radius) {
    let _radius = radius;
    
    Object.defineProperty(this, 'radius', {
        get: function() {
            return radius;
        },
        
        set: function(value) {
            if (value <= 0) throw new Error('Invalid radus value!');
            radius = value;
        }
    });
}
const c1 = new Circle(10);
c1.radius;
10
c1.radius = 15;
c1.radius;
15

Defin­ing Get­ters and Set­ters with­in ES6 Class­es is much eas­i­er. Note in the fol­low­ing ex­am­ple we have one prop­er­ty called ra­dius, made pri­vate by WeakMap, so it is not ac­ces­si­ble from the out­side.

const _radius = new WeakMap();
class Circle {
    constructor(radius) {
        _radius.set(this, radius);
    }
    
    get radius() {
        return _radius.get(this);
    }
    
    set radius(value) {
        if (value <= 0) throw new Error('Invalid radus value!');
        _radius.set(this, value);
    }
}
const c2 = new Circle(20);
c2.radius;
20
c2.radius = 25;
c2.radius;
25

In­her­i­tance and ES6 Class­es

Let's start with one class called Shape, which has one method called .move().

class Shape {
    move() {
        console.log('move');
    }
}

Now let's cre­ate an­oth­er class called Cir­cle which has its own method called .draw(), but we want to in­her­it al­so the .move() method from the Shape class. With­in ES6 Class­es this op­er­a­tion is much eas­i­er and clean­er than of the ap­proach used with Con­struc­tor func­tions – we do not need to change the Pro­to­type and re­set the Con­struc­tor.

class Circle extends Shape {
    draw() {
        console.log('draw');
    }
}

Now let's cre­ate a Cir­cle ob­ject and in­spect it.

const c1 = new Circle();
console.log(c1);
Circle {}
    [[Prototype]]: Shape
        draw: ƒ draw()
        constructor: class Circle
        [[Prototype]]: Object
            move: ƒ move()
            constructor: class Shape
            [[Prototype]]: Object

Let's imag­ine all of our shapes need a col­or – so we nee to ad such prop­er­ty at the Shape class.

class Shape {
    constructor(color) {
        this.color = color;
    }
    
    move() {
        console.log('move');
    }
}

At this point, if we have a con­struc­tor at the base class (in this ex­am­ple Shape), when we cre­at­ing de­rived class (in this ex­am­ple Cir­cle) which have its own con­struc­tor we mist ini­tial­ize the base class con­struc­tor by us­ing the su­per key­word, oth­er­wise we will get an er­ror.

class Circle extends Shape {
    constructor(color, radius) {
        super(color);
        this.radius = radius;
    }
    
    draw() {
        console.log('draw');
    }
}

Now let's cre­ate a new Cir­cle ob­ject, set val­ues for the col­or and ra­dius prop­er­ties and in­spect it.

const c2 = new Circle('red', 10);
console.log(c2);
Circle {color: 'red', radius: 10}
    color: "red"
    radius: 10
    [[Prototype]]: Shape
        draw: ƒ draw()
        constructor: class Circle
        [[Prototype]]: Object
            move: ƒ move()
            constructor: class Shape
            [[Prototype]]: Object

Method Over­rid­ing and ES6 Class­es

class Shape {
    move() {
        console.log('Shape move');
    }
}
class Circle extends Shape {
    move() {
        console.log('Circle move');
    }
}
const c1 = new Circle();

Now if we call c1.move() we will see the child im­ple­men­ta­tion of the method is used.

c1.move();
Circle move

The rea­son for that goes back to the Pro­to­typ­i­cal In­her­i­tance – so when ac­cess­ing a prop­er­ty or method the JavaScript en­gine walks on the Pro­to­typ­i­cal In­her­i­tance Tree from the child all the way to the par­ent and eval­u­ate the first ac­ces­si­ble prop­er­ty. Let's imag­ine we have sce­nario where we want reuse some of the code that have been im­ple­ment­ed at the par­ent move() method – in that case we can call that by us­ing the su­per key­word.

class Circle extends Shape {
    move() {
        super.move();
        console.log('Circle move');
    }
}
const c2 = new Circle();
c2.move();
Shape move
Circle move