html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Нужны ли в JavaScript классы?

JavaScript принято считать прототип-ориентированным языком программирования. Но, как ни странно, этим подходом практически никто не пользуется: большинство популярных JS-фреймворков явно или неявно оперируют классами.
В этой статье я хочу рассказать об альтернативном способе программирования на JavaScript, без использования классов и конструкторов — чистым прототип-ориентированным ООП и особенностях его реализации на ECMA Script 5.

ООП можно разделить на две группы: класс-ориентированное (классическое) и прототип-ориентированное. Классический подход отражает взгляд Аристотеля на мир, в котором всё описывается идеальными понятиями. Прототипное ООП ближе к философии Людвига Витгенштейна, которая не полагается на строгую категоризацию и классификацию всего и вся, а пытается представить понятия предметной области материальными и интуитивно понятными (насколько это возможно). Типичным аргументом в пользу прототипирования является то, что обычно намного проще сначала разобраться в конкретных примерах, а только потом, изучая и обобщая их, выделить некоторые абстрактные принципы и впоследствии их применять.

JavaScript, согласно этой классификации, находится где-то посередине: с одной стороны, в нем присутствуют прототипы, с другой — классы и оператор new, как средство создания новых объектов, что не свойственно прототип-ориентированному подходу.

Классы


В JavaScript нет классов, скажете вы. Я бы не стал так утверждать.
Под классами в JS я подразумеваю функции-конструкторы: функции, вызываемой при создании экземпляра (выполнении оператора new), со ссылкой на прототип — объект, содержащий свойства (данные) и методы (функции) класса.

Как известно, в ЕСМА Script 6 возможно таки введут ключевое слово class:

   class Duck{
        constructor(name){
            this.name = name;
        },
        quack(){
            return this.name +" Duck: Quack-quack!";
        }
    }
    
    /// Наследование

    class TalkingDuck extends Duck{
        constructor(name){
            super(name);
        },
        quack(){
            return super.quack() + " My name is " + this.name;
        }
    }
    
    /// Инстанцирование

    var donald = new TalkingDuck("Donald");

Но по сути, ничего существенного (например модификаторов public, private) данное нововведение не принесет. Это нечто иное, как синтаксический сахар для подобной конструкции:

    var Duck = function(name){
    	this.name = name;
	};

    Duck.prototype.quack = function(){
        return this.name +" Duck: Quack-quack!";
    };
    
    /// Наследование

	var TalkingDuck = function(name){
		Duck.call(this, name);
	}

	TalkingDuck.prototype = Object.create(Duck.prototype);
	TalkingDuck.prototype.constructor = TalkingDuck;

	TalkingDuck.prototype.quack = function(){
        return Duck.prototype.quack.call(this) + " My name is " + this.name;
    };
    
    /// Инстанцирование

    var donald = new TalkingDuck("Donald");

Следовательно, классы в текущей версии JS уже есть, только нет удобной синтаксической конструкции для их создания.
В конце-концов, давайте определимся, что же такое класс. Вот определение (из википедии):
Класс — разновидность абстрактного типа данных в ООП, характеризуемый способом своего построения. Суть отличия классов от других абстрактных типов данных состоит в том, что при задании типа данных класс определяет одновременно и интерфейс, и реализацию для всех своих экземпляров, а вызов метода-конструктора обязателен.
Следуя этому определению, функция-конструктор является классом:
Функция-конструктор это абстрактный тип данных? — Да.
Функция-конструктор (вместе с свойствами из прототипа) определяет одновременно и интерфейс, и реализацию? — Да.
Вызов конструктора при создании экземпляра обязателен? — Да.

Прототипы


Прототип отличается от класса тем, что:
  1. Это уже готовый к использованию объект, не нуждающийся в инстанцировании. Он может иметь собственное состояние (state). Можно сказать что прототип является классом и экземпляром объединенными в одну сущность, грубо говоря, Singleton'ом.
  2. Вызов конструктора при создании объекта (клонировании прототипа) не обязателен.

Суть прототипного ООП сама по себе очень простая. Даже проще чем классического. Сложности в JS возникают из-за попытки сделать его похожим на то, как это реализовано в Java: в Java создание новых объектов производится с помощью оператора new, применяемого к классу. В JS — аналогично. Но, т.к. JS вроде как прототипный язык, и классов в нем не должно быть по определению, было введено понятие функция-конструктор. Беда в том, что синтаксиса для нормального описания связки конструктор-прототип в JavaScript'e нет. В итоге имеем море библиотек, исправляющих это досадное упущение.
В прототип-ориентированном подходе нет оператора new, а создание новых объектов производится путем клонирования уже существующих.

Наследование


Итак, суть прототипного (делегирующего) наследования состоит в том, что один объект может ссылаться на другой, что делает его прототипом. Если при обращении к свойству/методу оно не будет найдено в самом объекте, поиск продолжится в прототипе, а далее в прототипе прототипа и т.д.

    var $duck = {
        name: "",
        quack: function(){
            return this.name +" Duck: Quack-quack!";
        }
    };
    var donald = {
        __proto__: $duck,
        name: "Donald"
    };
    var daffy = {
        __proto__: $duck,
        name: "Daffy"
    };
    
    console.log( donald.quack() ); // Donald Duck: Quack-quack!
    console.log( daffy.quack()  ); // Daffy Duck: Quack-quack!
    console.log( $duck.isPrototypeOf(donald) ); // true

daffy и donald используют один общий метод quack(), который предоставляет им прототип $duck. С прототипной точки зрения donald и daffy являются клонами объекта $duck, а с класс-ориентированной — “экземплярами класса” $duck.
Eсли же добавить/изменить некоторые методы непосредственно в объекте donald (или daffy), тогда его можно будет считать еще и “наследником класса” $duck.

Не забываем, что свойство __proto__ не стандартизировано, и использовать его можно только для дебага. Официально манипулировать свойством __proto__ возможно методами Object.create и Object.getPrototypeOf, появившимися в ECMAScript 5:

    var donald = Object.create($duck, {
        name: {value: "Donald"}
    });
    var daffy = Object.create($duck, {
        name: {value: "Daffy"}
    });


Инициализация


В отличии от класс-ориентированного подхода, наличие конструктора и его вызов при создании объекта на базе прототипа (клонировании) не обязателен.
Как же тогда инициализировать свойства объекта?
Простые, не калькулируемые значения по умолчанию для свойств можно сразу присвоить прототипу:

var proto = {
    name: "Unnamed"
};

А если нужно использовать калькулируемые значения, то вместе с ECMA Script 5 нам на помощь приходит:

Ленивая (отложенная) инициализация


Ленивая инициализация это техника, позволяющая инициализировать свойство при первом к нему обращении:
var obj = {
    get lazy(){
        console.log("Инициализация свойства lazy...");
        // Вычисляем значение:
        var value = "Лениво инициализированное свойство " + this.name;
        
        // Переопределяем свойство, для того чтобы при следующем
        // обращении к нему, оно не вычислялось заново:
        Object.defineProperty(this, 'lazy', {
            value: value, 
            writable: true, enumerable: true
        });
        console.log("Инициализация окончена.");
        return value;
    },
    // оставляем возможность инициализировать свойство 
    // самостоятельно, в обход функции-инициализатора 
    // (если это не будет влиять на согласованность объекта):
          set lazy(value){
        console.log("Установка свойства lazy...");
        Object.defineProperty(this, 'lazy', {
            value: value, 
            writable: true, enumerable: true
        });
    },
    name: "БезИмени"
};
console.log( obj.lazy );
// Инициализация свойства lazy...
// Лениво инициализированное свойство БезИмени

console.log( obj.lazy );// Инициализатор не запускается снова
// Лениво инициализированное свойство БезИмени

obj.lazy = "Переопределено";// Сеттер не запускается, т.к. свойство уже инициализировано
console.log( obj.lazy );
// Переопределено

К плюсам этой техники можно отнести:
  • Разбиение конструктора на более мелкие методы-аксессоры “автоматически”, как предотвращение появлению длинных конструкторов (см. длинный метод).
  • Прирост в производительности, т.к. не используемые свойства инициализироваться не будут.


Сравнительная таблица

Прототип Класс (ECMA Script 5) Класс (ECMA Script 6)
Описание типа данных («класса»)
var $duck = {
  name: "Unnamed",
  get firstWords(){
    var value = this.quack();
    Object.defineProperty(
      this, 'firstWords',
      {value: value}
    );
    return value;
  },
  quack: function(){
    return this.name
      +" Duck: Quack-quack!";
  }
};
var Duck = function(name){
  this.name = name||"Unnamed";
  this.firstWords = this.quack();
};
Duck.prototype.quack = function(){
  return this.name
    +" Duck: Quack-quack!";
};
class Duck{
  constructor(name="Unnamed"){
    this.name = name;
    this.firstWords = this.quack();
  },
  quack(){
    return this.name
      +" Duck: Quack-quack!";
  }
}
Наследование
var $talkingDuck = Object.create($duck);

$talkingDuck.quack = function(){
  return $duck.quack.call(this)
    + " My name is "
    + this.name;
};
var TalkingDuck = function(name){
  Duck.call(this, name);
}

TalkingDuck.prototype = Object.create(Duck.prototype);
 
TalkingDuck.prototype.constructor = TalkingDuck;
TalkingDuck.prototype.quack = function(){
  return Duck.prototype.quack.call(this)
    + " My name is " 
    + this.name;
};
class TalkingDuck extends Duck{
  constructor(name){
    super(name);
  },
  quack(){
    return super.quack()
      + " My name is " 
      + this.name;
  }
}
Создание объектов-экземпляров
var donald = Object.create($talkingDuck);
donald.name = "Donald";
var donald = new TalkingDuck("Donald");
var donald = new TalkingDuck("Donald");




Список использованной литературы:
Dr. Axel Rauschmayer — Myth: JavaScript needs classes
Antero Taivalsaari — Classes vs. prototypes: some philosophical and historical observations [PDF]
Mike Anderson — Advantages of prototype-based OOP over class-based OOP
Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

2

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции