JavaScript OOP Course 6: ES6 Classes: Difference between revisions
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=" | ----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=" | </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=" | </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=" | '''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=" | </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=" | </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=" | </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=" | </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=" | </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=" | </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=" | : <syntaxhighlight lang="javascript" class="code-continue"> | ||
sayHello(); | sayHello(); | ||
Line 100: | Line 100: | ||
console.log('Hello'); | console.log('Hello'); | ||
} | } | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </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=" | : <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=" | * '''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=" | * '''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=" | <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=" | <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=" | <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=" | <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=" | </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=" | 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 | '''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=" | 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=" | 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=" | 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=" | </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=" | </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=" | 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=" | '''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=" | '''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=" | </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 | |||
=== 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=" | 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=" | </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 | '''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=" | 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=" | <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=" | </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=" | </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=" | </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=" | </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=" | ----'''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=" | </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=" | <syntaxhighlight lang="javascript" class="code-continue"> | ||
const _radius = new WeakMap(); | const _radius = new WeakMap(); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
<syntaxhighlight lang="javascript" class=" | <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=" | </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=" | 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=" | </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=" | </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=" | ----'''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=" | </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=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
c.getMove(); | c.getMove(); | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </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=" | </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=" | </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=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
c.getMove(); | c.getMove(); | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </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=" | 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=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
const c1 = new Circle(10); | const c1 = new Circle(10); | ||
</syntaxhighlight><syntaxhighlight lang="javascript" class=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
c1.radius; | c1.radius; | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue"> | ||
10 | 10 | ||
</syntaxhighlight><syntaxhighlight lang="javascript" class=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
c1.radius = 15; | c1.radius = 15; | ||
c1.radius; | c1.radius; | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </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=" | </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=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
class Circle { | class Circle { | ||
constructor(radius) { | constructor(radius) { | ||
Line 508: | Line 507: | ||
} | } | ||
} | } | ||
</syntaxhighlight><syntaxhighlight lang="javascript" class=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
const c2 = new Circle(20); | const c2 = new Circle(20); | ||
</syntaxhighlight><syntaxhighlight lang="javascript" class=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
c2.radius; | c2.radius; | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue"> | ||
20 | 20 | ||
</syntaxhighlight><syntaxhighlight lang="javascript" class=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
c2.radius = 25; | c2.radius = 25; | ||
c2.radius; | c2.radius; | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </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=" | 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=" | </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=" | </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=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
console.log(c1); | console.log(c1); | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </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=" | </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=" | 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=" | </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=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
console.log(c2); | console.log(c2); | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </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=" | <syntaxhighlight lang="javascript" class="code-continue"> | ||
class Shape { | class Shape { | ||
move() { | move() { | ||
Line 593: | Line 592: | ||
} | } | ||
} | } | ||
</syntaxhighlight><syntaxhighlight lang="javascript" class=" | </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=" | </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=" | </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=" | </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=" | 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=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
const c2 = new Circle(); | const c2 = new Circle(); | ||
</syntaxhighlight><syntaxhighlight lang="javascript" class=" | </syntaxhighlight><syntaxhighlight lang="javascript" class="code-continue"> | ||
c2.move(); | c2.move(); | ||
</syntaxhighlight><syntaxhighlight lang="shell-session" class=" | </syntaxhighlight><syntaxhighlight lang="shell-session" class="code-continue"> | ||
Shape move | Shape move | ||
Circle move | Circle move |
Latest revision as of 08:23, 20 June 2024
References
- Code with Mosh: The Ultimate JavaScript Mastery Series – Part 2
- W3School: JavaScript Tutorial
- BabelJs.io a JavaScript compiler. Use next generation JavaScript, today.
See also:
- JavaScript Objects: Classes for Objects definition
- JavaScript Functions: This Keyword in JavaScript
- JavaScript Functions: Changing
this
- JavaScript OOP Objects: Constructors and Classes
- JavaScript OOP Objects: Function's Methods –
.call()
and.apply()
ES6 Classes
- Classes are a new way to create Objects and Prototypical Inheritance.
- Classes in JavaScript are not like classes in other languages like C#, Java and so on.
- Classes in JavaScript are syntactic sugar over Prototypical Inheritance. It is important to know how the Prototypical Inheritance works before learning this new syntax which is cleaner and simpler.
- Creating new objects by ES6 Classes enforce the use of the
new
operator. - JavaScript engine executes the body of the Classes in Strict Mode, no matter
'use strict';
is engaged or not.
Let's begin with the following Constructor function that will be converted 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 using ES6 Classes in the following way. Step 1. Define the body of the class. In this body we can define properties and methods.
class Circle {
}
Step 2. There is a special method that is called constructor()
and it is used to initialize the objects. The constructor()
method is like the Constructor function shown above. When we define a method inside the special constructor()
method this new method will be part of the new
object instance.
class Circle {
constructor(radius) {
this.radius = radius;
this.draw = function() {
console.log('draw');
};
}
}
Step 3. Define the prototype members – in this case the .area()
method. When we define a method outside the special constructor()
method this new method will be part of the prototype of new
object. When we defining methods outside the constructor()
we can use the simplified syntax shown below.
class Circle {
constructor(radius) {
this.radius = radius;
this.draw = function() {
console.log('draw');
};
}
area() {
return this.radius * this.radius * Math.PI;
}
}
Step 4. Create a new object and inspect 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 typeof Circle
class. Tit is a function – the ES6 Classes are essentially functions!
typeof Circle
'function'
Hoisting
Let's discuss what we have learned about the Functions. In JavaScript are available two way to define a function:
- 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.
sayHello(); function sayHello() { console.log('Hello'); }
Hello
- Function Expression Syntax. These functions are not hoisted. It is like when we defining a variable that contains a primitive – number, 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 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.
class Circle { // Class definition }
- Class Expression Syntax. This is rarely used syntax – probably you won't see it in the practice.
const Circle = class { // Class definition }; // Should be determined by semicolon
Static Methods
In classical object oriented languages we have two tips of methods:
- 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.
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 example the expression
JSON.parse(str).radius
tests whether the input argumentstr
is valid JSON format and extract theradius
property if it is available.
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 .create()
method is not available at the class instance – the circle
object.
circle.create('{"radius": 1}');
Uncaught TypeError: circle.parse is not a function
at index.js:18
But the .create()
method is available at the class Circle
itself.
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 implementation we can create new instances (objects) of the class Circle
in the following way.
const c = Circle.create('{"radius": 1}');
One more time:
- We use static methods to create utility functions that are not part of particular object.
The this
Keyword
The this
keyword references the Object that is executing the current Function. For example if a function is method of an Object this
references to that Object itself. Otherwise if that function is a regular function
(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.
this
within Functions and Methods
Let's declare a constructor function (by using Function Expression Syntax) and create an object instance of Circle.
const Circle = function(radius) {
this.radius = radius;
this.logThis = function() { console.log(this); };
};
const c = new Circle(1);
If we use the Method Call Syntax for the .logThis()
method, we are going to see the new Circle object in the console, because this
of the .logThis()
method points to that object – remember the new
operator which creates a new object and set this
from the Constructor to points to that new object.
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 reference to the method in a variable.
const logThis = c.logThis; // by omitting () we just creating a reference to a function and not calling it
Note: By omitting ()
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.
console.log(logThis);
ƒ () { console.log(this); }
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.
logThis(); // this is 'function call syntax'
Window {window: Window, self: Window, document: document, name: '', location: Location, …}
...
...
Strict Mode changing behavior of this
within Functions
When we enable the Strict Mode, by placing 'use strict';
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 this
keyword within functions. So if enabling the Strict Mode the output of the above function will be undefined
instead of the Window Object.
'use strict';
// the rest code of the program...
logThis();
undefined
Note:
'use strict';
cannot be used within the console, it should be part of our script file.- When we enable
'use strict';
optionthis
of the functions not longer point to the global object which is the Window Object in the bowsers and Global Object in Nod.js The reason for this is to prevent us from accidently modifying the global object.
this
Keyword and ES6 Classes
JavaScript engine executes the body of the Classes in Strict Mode, no matter 'use strict';
is engaged or not in the beginning of our program. Let's define a Circle
class with .logThis()
method and do repeat the above steps – we will see undefined
in the console.
class Circle {
constructor(radius) {
this.radius = radius;
}
logThis() { console.log(this); }
}
const c = new Circle(1);
const logThis = c.logThis;
logThis();
undefined
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.
class Circle {
constructor(radius) {
this.radius = radius;
}
}
const c = new Circle(10);
c.radius;
10
Let's imagine we want to make the radius
property from the above 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 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.
class Circle {
constructor(radius) {
this._radius = radius;
}
}
const c = new Circle(10);
c._radius;
10
Private Members Using ES6 Symbols
In ES6 we have a new primitive type called Symbol. Symbol()
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 new
operator, otherwise we will get an error. Every time we call the Symbol()
function it generates an unique value. If we do a comparison like Symbol() === Symbol()
we will get false
.
So let's define a new variable of this type (by using the underscore naming convention) and the unique value generated by the Symbol()
function to name a property of an object by using a Bracket notation.
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 Circle object has one property named Symbol()
. If we set multiple properties using Symbols, the property names all will show up a Symbol()
but internally they are unique. This property is semi private - we cannot access this property directly in our code.
console.log(Object.getOwnPropertyNames(c));
[]
From the above command we see our object c
doesn't have any regular property.
console.log(Object.getOwnPropertySymbols(c));
[Symbol()]
0: Symbol()
length: 1
[[Prototype]]: Array(0)
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 c
.
const key = Object.getOwnPropertySymbols(c)[0];
const radius = c[key];
console.log(radius);
10
To make a Method private by using Symbol, we need to define another Symbol and use ES6 feature called Computed Property 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
Private Members Using ES6 WeakMaps
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.
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 expression _radius.set(this, radius);
_radius
is our WeakMap,.set()
is a method of the WeakMap type,this
is the key (the current object in our case),radius
is the property we set to that key.
We can access a Property defined by a WeakMap in the following 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 order to define a Private Method with this approach we need another 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 private ._move()
method returns undefined
for this
. 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 change this
of the callback function in some other 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 {}
Getter and Setters with ES6 Classes
Let's remind you, in order to define Getter and Setters when we use Constructor functions, we've used the Object.defineProperty()
method like is shown below. Also we've made some properties private by defining variables inside the Constructor.
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
Defining Getters and Setters within ES6 Classes is much easier. Note in the following example we have one property called radius
, made private by WeakMap, so it is not accessible from the outside.
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
Inheritance and ES6 Classes
Let's start with one class called Shape
, which has one method called .move()
.
class Shape {
move() {
console.log('move');
}
}
Now let's create another class called Circle
which has its own method called .draw()
, but we want to inherit also the .move()
method from the Shape
class. Within ES6 Classes this operation is much easier and cleaner than of the approach used with Constructor functions – we do not need to change the Prototype and reset the Constructor.
class Circle extends Shape {
draw() {
console.log('draw');
}
}
Now let's create a Circle object and inspect 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 imagine all of our shapes need a color – so we nee to ad such property at the Shape
class.
class Shape {
constructor(color) {
this.color = color;
}
move() {
console.log('move');
}
}
At this point, if we have a constructor at the base class (in this example Shape
), when we creating derived class (in this example Circle
) which have its own constructor we mist initialize the base class constructor by using the super
keyword, otherwise we will get an error.
class Circle extends Shape {
constructor(color, radius) {
super(color);
this.radius = radius;
}
draw() {
console.log('draw');
}
}
Now let's create a new Circle object, set values for the color
and radius
properties and inspect 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 Overriding and ES6 Classes
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 implementation of the method is used.
c1.move();
Circle move
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 move()
method – in that case we can call that by using the super
keyword.
class Circle extends Shape {
move() {
super.move();
console.log('Circle move');
}
}
const c2 = new Circle();
c2.move();
Shape move
Circle move