ECMAScript 6 Class

2018-02-28

ECMAScript 6 CLASSES

Class in ECMAScript 5

1
2
3
4
5
6
7
8
9
10
function PersonType(name){
this.name = name;
}
PersonType.prototype.sayName = function(){
console.log(this.name);
};
var person = new PersonType("Nick");
person.sayName(); // "Nick"
console.log(person instanceof PersonTypes); //true;
console.log(person instanceof Object);// true

Class Declarations in ECMAScript 6

A Basic Class Declaration

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class PersonClass {
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
}
let person = new PersonClass("Nick");
person.sayName(); //"Nick"
console.log(person instanceof PersonClass);//true
console.log(person instanceof Object);//true
console.log(typeof PersonClass);// "function"
console.log(typeof PersonClass.prototype.sayName); //"function"
  • Class declarations are not hoisted, like let declarations, so they exist in the termporal dead zone until exectution reaches the declaration
  • All code inside the class declarations runs in strict mode automatically
  • All methods are nonenumerable
  • When lacking the constructor methods, it will throws an error
  • Calling the class constructor without new throws an error
  • Class method use the concise syntax. Therefore, there’s no need to use function keyword
  • Own properties can be created inside a class constructor (recomannded) or method.
  • Class prototypes are read-only
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// equivalent of PersonClass

let PersonType2 = (function(){
"use strict";
const PersonType2 = function(name){
if(typeof new.target === "undefined"){
throw new Error("Constructor must be called with new.");
}
this.name = name;
}
Object.defineProperty(PersonType2.prototype, "sayName", {
value: function() {
if (typeof new.target !== "undefined"){
throw new Error ("Methods cannot be called with new");
}
console.log(this.name);
},
enumerable: false,
writable: true,
configurable: true
});
return PersonType2;
}())

Class Expressions

Anonymous Class Expression

1
2
3
4
5
6
7
8
let PersonClass = class {
constructor(name){
this.name = name;
}
sayName() {
console.log(this.name);
}
}
  • Class expression are functionally equivalent to class declarations.
  • Expect that the anonymous class expression has an empty name property.

Named Class Expression

1
2
3
4
5
6
7
8
9
10
11
12

let PersonClass = class PersonClass2 {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}

}
console.log(typeof PersonClass); // "function"
console.log(typeof PersonClass2); //"undefined"
  • The PersonClass2 identifier exists only within the class definition so it can be used inside class methods
    -As following code shows the different:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    let PersonClass = (function(){
    "use strict";
    const PersonClass2 = function(name){
    if(typeof new.target === "undefined"){
    throw new Error("Constructor must be called with new.")
    }
    this.name = name;
    }
    Object.defineProperty(PersonClass2.prototype, "sayName", {
    value: function(){
    if(typeof new.target !== "undefined"){
    throw new Error("Method cannot be called with new");
    }
    console.log(this.name);
    },
    enumerable: false,
    writable: true,
    configurable: true
    });
    return PersonClass2;
    }())

Accessor Properties

  • Classes allow you to define accessor properties on the prototype
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class CustomHTMLElement {
    constructor(element) {
    this.element = element;
    }
    get html() {
    return this.element.innerHTML;
    }
    set html(value) {
    this.element.innerHTML = value;
    }
    }
    var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html");
    console.log("get" in descriptor); //true
    console.log("set" in descriptor); // true
    console.log(descriptor.enumerable); //false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// equivalent no-class version
let CustomHTMLElement = (function(){
"use strict";
const CustomHTMLElement = function(element){
if(typeof new.target === "undefined"){
throw new Error("Constructor must be called with new.");
}
this.element = element;
};
Object.defineProperty(CustomHTMLElement.prototype, "html",{
enumerable: false,
configurable: true,
get: function() {
return this.element.innerHTML;
},
set: function(value) {
this.element.innerHTML = value;
}
});
return CustomHTMLElement;
}());

Computed Member Names

  • Glass Methods and accessor properties can also have computed names, which using square brackets around an expression and has the same syntax you use for object literal computed names.
1
2
3
4
5
6
7
8
9
10
11
let methodName = 'sayName';
class PerosnClass {
constructor(name){
this.name = name;
}
[methodName]() {
console.log(this.name);
}
}
let me = new PersonClass("Nick");
me.sayName(); //"Nick"

Generator Methods

1
2
3
4
5
6
7
8
9
class MyClass {
*createIterator(){
yield 1;
yield 2;
yield 3;
}
}
let instance = new MyClass();
let interator = instance.createIterator();
  • Generator are more useful for a collection of values. You can define the default iterator for a class by using Symbol.iterator to define a generator method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Collection {
constructor() {
this.items = [];
}
*[Symbol.iterator]() {
yield *this.items.values();// values() is the default generator of an array
}
}

var collection = new Collection();
collection.items.push(2);
collection.items.push(3);
collection.items.push(4);
for(let x of collection) {
console.log(x);
}
//Output:
//2
//3
//4

Static Members

  • Static members in ECMAScript 5
1
2
3
4
5
6
7
8
9
10
function PersonType(name){
this.name = name;
}
PersonType.create = function(name) {
return new PersonType(name);
}
PersonType.prototype.sayName = function() {
console.log(this.name);
}
var person = PersonType.create("Nick");
  • ECMAScript 6 use the formal static annotation before method or accessor property name.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

    class PersonClass {
    constructor(name){
    this.name = name;
    }
    sayName(){
    console.log(this.name);
    }
    static create(name){
    return new PersonClass(name);
    }
    }
    let person = PersonClass.create("Nick");
  • static can’t be used with the consturctor method definition

Inheritance with Derived Classes

  • The Inheritance pior to ECMAScript 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Rectangle(length, width){
this.length = length;
this.width = width;
}

Rectangle.prototype.getArea = function() {
return this.length * this.width;
};

function Square(length){
Rectangle.call(this, length, length);
}

Square.prototype = Object.create(Rectangle.prototype, {
constructor: Square,
enumerable: true,
writable: true,
configurable: true
});
var square = new Square(3);

console.log(square.getAre()); // 9
console.log(square instanceof Square);
console.log(square instanceof Rectangle);
  • Classes make inheritance easier to implement by using the extends keyword to specify the function from which the class should inherit.
  • The prototypes are automatically adjusted, and you can access the base class constructor by calling the super() method .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Rectangle {
constructor(length, width){
this.length = length;
this.width = width;
}
getArea(){
return this.length * this.width;
}
}

class Square extends Rectangle {
constructor(length){
super(length, length);
}
}
var square = new Square(3);

console.log(square.getArea()); // 9
console.log(square instanceof Square); // true
console.log(square instanceof Rectangle); // true
  • the Square class inherits from Rectangle using the extends keyword. The Square constructor uses super() to call the Rectangle constructor with the specified arguments.
  • Classes that inherit from the other classes are referred as derived classes, which require you to use super() if you specify a constructor.
  • If you choose not to use a constructor, super() is automatically called fro you with arguments upon creating a new instance of the class
  • not use super() in a nonderived class
  • class super() before accessing this in the constructor. super() is responsible for initializing this.
1
2
3
4
5
6
7
8
9
10
class Square extends Rectangle {
// no constuctor
}
// is equivalent to

class Square extends Rectangle {
constructor(...args){
super(...args);
}
}

Shadowing Class Methods

  • The methods on derived classes always shadow methods of the same name on the base class
  • call the base class version of the method by using the super.getArea() method

Inherited Static Members

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Rectangle {
constructor(legnth, width){
this.length = length;
this.width = width;
}
getArea() {
return this.length * this.width;
}
static create(length, width) {
return new Rectangle(length, width);
}
}

class Square extends Rectangle {
constructor(length){
super(length, length);
}
}
var rect = Square.create(3, 4);
console.log( rect instanceof Rectangle); //true
console.log(rect.getArea()); //12
console.log(rect instanceof Square); // false

Dervied Classes from Expressions

  • You can use extends with any expression as long as the expression resolves to a function with [[Construct]] and a prototype.
  • It’s possible to create different inheritance approaches.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
let SerializableMixin = {
serialize() {
return JSON.stringify(this);
}
};
let AreaMinxin = {
getArea() {
return this.length * this.width;
}
};

function mixin(...mixins){
var base = function(){};
Object.assign(base.prototype, ...mixins);
return base;
};

class Square extends mixin(AreaMixin, SerializableMixin){
constructor(length){
super();
this.length = length;
this.width = length;
}
};

var x = new Square(3);
console.log(x.getArea());
console.log(x.serialize());
  • you can use any expression after extends, but not all expressions result in a valid class. Specifically, using null or a generator function will cause errors. because there is no [[Construct]] to call

Inheriting from Built-Ins

  • Custmoized Array in ECMAScript 5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // built - in array behavior
    var colors = [];
    colors[0] = 'red';
    console.log(colors.length); //1

    colors.length = 0;
    console.log(colors[0]);

    // trying to inheirt from array in ES5

    function MyArray(){
    Array.apply(this, arguments);
    }

    MyArray.prototype = Object.create(Array.prototype, {
    constructor: {
    value: MyArray,
    writable: true,
    configurable: true,
    enumerable: true
    }
    });
    var colors = new MyArray();
    colors[0] = "red";
    console.log(colors.length); //0

    colors.length = 0;
    console.log(colors[0]); //"red"
  • The length and numeric properties on an instance of MyArray don’t behave the same way they do for the built-in array

  • the value of this is first created by the derived type and then the base type constructor is called, which meas this start out as an instance of MyArray and then is decorated with additional properties from Array
1
2
3
4
5
6
7
8
9
10
class MyArray extends Array {
//empty
}

var colors = new MyArray();
colors[0] = "red";
console.log(colors.length); //1

colors.length = 0;
console.log(colors[0]); // undefined
  • In ECMAScript 6, the value of this is first created by the base and then modified by the dervied class constructor. The result is that this starts with all the built-in functionality of the base.

The Symbol.species Property

  • A convenient aspect of inheriting from built-ins is that any method that returns an instance of the built-in will automatically return a derived class instance instead.
1
2
3
4
5
6
7
8
class MyArray extends Array {
//empty
}

let items = new MyArray(1, 2, 3, 4), subitems = items.slice(1, 3);

console.log(items instanceof MyArray); // true
console.log(subitems instanceof MyArray);//true
  • Behind the scenes, the Symbol.species property is actually making this change.
  • The Symbol.species well-known symbol is used to define a static accessor property that returns a function.
  • That function is a constructor to use whenever an instance of the class must be created inside an instance method, instead of using the constructor.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// implement on a custom class
class MyClass {
static get [Symbol.species] () {
return this;
}

constructor(value) {
this.value = value;
}

clone() {
return new this.constructor[Symbol.species](this.value);
}
}
  • Any call to this.constructor[Symbol.species] returns MyClass.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

class MyClass{
static get [Symbol.species]() {
return this;
}

constructor(value){
this.value = value;
}

clone() {
return new this.constructor[Symbol.species](this.value);
}
}
class MyDerivedClass1 extends MyClass {
//empty
}
class MyDerivedClass2 extends MyClass {
static get [Symbol.species]() {
return MyClass;
}
}
let instance1 = new MyDerivedClass1("foo"),
clone1 = instance1.clone(),
instance2 = new MyDerivedClass2("bar"),
clone2 = instance2.clone();

console.log(clone1 instanceof MyClass); //true
console.log(clone1 instanceof MyDerivedClass1); //true
console.log(clone2 instanceof MyClass); //true
console.log(clone2 instanceof MyDerivedClass2); //false

Using new.target in Class Constructors

  • new.target ‘s value changes depending on how a function is called.
  • the new.target is always defined inside class constructors. But the value may not always be the same
1
2
3
4
5
6
7
8
9
10
11
12
13
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
this.length = length;
this.width = width;
}
}
class Square extends Rectangle {
constructor(length){
super(length, length);
}
}
var obj = new Square(3);//outputs false
  • Square is calling the Rectangle constructor, so new.target is equal to Square when the Rectange constructor is called.
  • This gives each constructor the ability to alter its behavior based on how it’s being called.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// abstract base class 
class Shape {
constructor() {
if(new.target === Shape) {
throw new Error("This class cannot be instantiated directly");
}
}
}
class Rectangle extends Shape {
constructor(length, width){
super();
this.length = length;
this.width = width;
}
}
var x = new Shape() ; //throws an error
var y = new Rectangle(3, 4); //no error
console.log(y instanceof Shape); // true