Техника симулирования класса и наследственности в Javascript (ECMAScript)

1- Класс в Javascript

Javascript это на самом деле мир функций и объектов. Первоначально он разработан просто, без четкого принципа о классе. Возможно создатели  Javascript и не могли подумать что в один день, этот язык будет использоваться так распространенно.
Объект в  Javascript сущность с несколькими парами "Ключ/Значение", и вы можете получить доступ в значения через объект и ключ.
object-example1.js
var tom =  {
  name: "Tom",
  country: "USA"
};

// Access:
console.log( tom.name ); // Tom
console.log( tom.country ); // USA
console.log( tom["name"] ); // Tom
console.log( tom["country"] ); // USA
 
Вы можете добавить новый  "Ключ/Значение" в готовый объект или удалить его определенную пару  "Ключ/Значение".
object-example2.js
var tom =  {
  name: "Tom",
  country: "USA"
};

// Delete property - country
delete tom["country"]; // Same as: delete tom.country;

// Add property - gender
tom["gender"] = "Male"; // Same as: tom.gender = "Male";


// Access:
console.log( tom.name ); // Tom
console.log( tom["name"] ); // Tom

console.log( tom["country"] ); // undefined
console.log( tom.country ); // undefined

console.log( tom["gender"] ); // Male
console.log( tom.gender ); // Male
Класс это современная концепция в таких языках, как  Java, C#,... Класс это дизайн, который помогает быстро создавать объекты с одинаковой структурой. Первая версия  Javascript не имеет данную концепцию.
Javascript становится все более и более важным, поэтому его необходимо обновить. Дизайнеры  Javascript пытаются смоделировать концепцию Класс, основываясь на готовые концепции в  Javascript. Синтаксис симулирования класса представлен в  ES3, ES5, но только дойдя до  ES6 мы получаем современный синтаксис и удовлетворяем всех.
Сначала, для упрощения рассмотри современный синтаксис представленный в  ECMAScript 6 чтобы создать класс  Rectangle (Прямоугольник), с 2-мя свойствами (property) width (ширина) и height (длина). Данный класс имеет метод  getArea() возвращает площадь данного прямоугольника.
es6-class-example.js
// ECMAScript 6 class:

class Rectangle  {

  constructor (width , height)  {
      this.width = width;
      this.height = height;
  }

  getArea()  {
    return this.width * this.height;
  }

}


// ------------- TEST -------------------

var rect = new Rectangle(10, 5);

var area = rect.getArea();

console.log("Width: " + rect.width);
console.log("Height: " + rect.height);
console.log("Area: " + area);

 
Создать подкласс класс очень просто с  ES6:
es6-inheritance-example.js
class Shape  {

    constructor( x, y) {
        this.setLocation(x, y);
    }

    setLocation(x, y) {
        this.x = x;
        this.y = y;
    }
}

// Subclass:
class Circle extends Shape {

    constructor(x, y, radius) {
        // Call Shape's constructor via super
        super(x, y);
        this.radius = radius;
    }

    getArea() {
      return Math.PI * this.radius * this.radius;
    }
}

// ----- TEST ----


var circle = new Circle(0, 2, 5);

console.log( circle.getArea() );

 
ES6 представил современный синтаксис для создания класса и подклассов, но техника на самом деле не поменялась. Синтаксис у  ES6 скрыл все неясные понятия у  Javascript когда он пытается симулировать класс. В данной статье мы обсудим с вами способ, который используется  ES3 & ES5 для сиумилрования класса и наследственность.

2- Класс и наследственность в ES3

ES3 использует ключевое слово  function для определения  "конструктор объекта". Вы создаете новый объект, когда вы вызываете данную функцию с помощью оператора  new:
// ECMAScript 3 class.

function Rectangle(width, height)  {
   this.width = width;
   this.height = height;
}

// Create an Object:
var rect = new Rectangle(10, 5);
 
Изображение ниже иллюстрирует действие выполненное исполняющей машиной  Javascript:
  1. Объект 'rect' создан (он является обычным объектом, ничего особенного)
  2. Добавить свойство (property) __proto__ объекту 'rect', это скрытое свойство.
  3. Переменная this будет направлена на адрес объекта 'rect'.
  4. Добавить свойство (property) width объекту 'rect'.
  5. Добавить свойство (property) height объекту ​​​​​​​'rect'.
Концепция  prototype будет представлена в ES3. Рассмотрим какова его цель?
// ECMAScript 3 class.

function Rectangle(width, height)  {
   this.width = width;
   this.height = height;
}

Rectangle.prototype.bgColor = "red";
Rectangle.prototype.borderColor = "blue";

// Create an Object:
var rect = new Rectangle(10, 5);



console.log(rect); // Rectangle { width: 10, height: 5 }
console.log(rect.__proto__); // Rectangle { bgColor: 'red',borderColor: 'blue' }

console.log(rect.__proto__.bgColor); // red
console.log(rect.__proto__.borderColor); // blue

// (Read the explanation**)
console.log(rect.bgColor); // red
console.log(rect.borderColor); // blue

 
Что происходит когда машина выполняющая  Javascript встречает выражение  myObject.myProperty?
  • Ответ: Он проверяет имеет ли объект myObject свойство (property) myProperty или нет, если есть то он получает доступ в данное свойство, наоборот он получает доступ в myObject.__proto__.myProperty.
es3-proto-example3.js
var rect =  {
   __proto__ : { bgColor : "red", borderColor : "blue" },
   width: 10,
   height: 5
}


console.log(rect.width); // 10

console.log(rect.__proto__.bgColor); // red


console.log(rect.bgColor); // red

 

new AFunction(args) vs AFunction.call(anObj, args)

Наследственность

Для  ES3 вы можете симулировать наследственный класс другого класса разными способами, но это не просто как вы это делаете в  ES6, и довольно непонятен многим программистам.
Для простоты, я дам пример наследственности, и анализ правила работы исполняющей машины  Javascript в данном случае.
  1. Класс Animal имеет 2 свойтсва (property) name & gender.
  2. Класс Cat наследует от класса Animal, имеет 1 свойство color.
es3-inheritance-example1.js
function Animal(n, g) {
    this.name = n;
    this.gender = g;
}


function Cat(n, g, c) {
    Animal.call(this, n, g);
    this.color = c;
}


var tom = new Cat("Male", "Tom", "Black");

// Cat { gender: 'Male', name: 'Tom', color: 'Black' }
console.log(tom);

Наследственность и роль прототипа (ES3)

es3-inheritance-example2.js

// Class: Animal
function Animal(n, g) {
    this.name = n;
    this.gender = g;
}

Animal.prototype.sleep = function()  {
    console.log("Animal sleeping..");
}

Animal.prototype.move = function()  {
    console.log("Animal moving..");
}

// Class: Cat
function Cat(n, g, c) {
    Animal.call(this, n, g); // IMPORTANT!!
    this.color = c;
}

// IMPORTANT!!
var TempFunc = function() {}; // Temporary class.
TempFunc.prototype = Animal.prototype;
Cat.prototype = new TempFunc();
// ------------------


Cat.prototype.cry = function()  {
    console.log("Meo meo");
}

// Override 'move' method of Animal.
Cat.prototype.move = function() {
    console.log("Cat moving..");
}

var tom = new Cat("Male", "Tom", "Black");

// Cat { gender: 'Male', name: 'Tom', color: 'Black' }
console.log(tom);

tom.move(); // Cat moving..
tom.sleep(); // Animal sleeping..
tom.cry(); // Meo meo
 

3- Класс и наследственность в ES5

Object.create(srcObject)

Метод  Object.create(srcObject) создает новый объект  newObject, при этом  newObject.__proto__ является копией у srcObject.
method-object-create-example.js
var john = {
  name: "John",
  gender: "Male"
};

var other = Object.create(john);


console.log(other.__proto__); // { name: 'John', gender: 'Male' }
 

Наследственность (ES3 + Object.create)

Использование метода  Object.create(srcObject) у ES5 помогает вам легче симулировать класс и наследственность по сравнению с  ES3.
es5-inheritance-example1.js
// Class: Animal
function Animal(n, g) {
    this.name = n;
    this.gender = g;
}

Animal.prototype.sleep = function()  {
    console.log("Animal sleeping..");
}

Animal.prototype.move = function()  {
    console.log("Animal moving..");
}

// Class: Cat
function Cat(n, g, c) {
    Animal.call(this, n, g); // IMPORTANT!!
    this.color = c;
}

Cat.prototype = Object.create(Animal.prototype); // IMPORTANT!!

Cat.prototype.cry = function()  {
    console.log("Meo meo");
}

// Override 'move' method of Animal.
Cat.prototype.move = function() {
    console.log("Cat moving..");
}

var tom = new Cat("Male", "Tom", "Black");

// Cat { gender: 'Male', name: 'Tom', color: 'Black' }
console.log(tom);

tom.move(); // Cat moving..
tom.sleep(); // Animal sleeping..
tom.cry(); // Meo meo