13 июня 2013 в 00:02

Нужны ли в 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$ = {// "$" в этом контексте читается как "прототип": duck$ == Duck.prototype
        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$. V8 так и делает, создавая скрытые классы при каждом добавлении свойства.

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

    var donald = Object.create(duck$, {
        name: {value: "Donald"}
    });
    console.log( Object.getPrototypeOf(donald) === duck$ ); // true


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


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

var proto = {
    name: "Unnamed"
};

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

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


Ленивая инициализация это техника, позволяющая инициализировать свойство при первом к нему обращении:
var obj = {
    name: "БезИмени",
    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
        });
    }
};
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$, {
  quack: {value: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");


Часть 2 — Производительность: создание классов через __proto__



Список использованной литературы:
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
+116
53673
812
Quadratoff 27,9 G+

Комментарии (59)

+5
agentx001, #
Очень-очень крутая статья! Спасибо за «линивую инициацию», не знал… И ребята, давайте не будем разводить срач, данный подход имеет право на жизнь, верно?
+1
Quadratoff, #
пожалуйста ))) рад стараться :)
+2
kamiram, #
мало того. он мощнее. но несколько опаснее.
+10
Alexey2005, #
Прототипное наследование в JS неполноценно, т.к. отсутствует способ как клонировать объект, так и связать его с прототипом.
Т.е. в «классовом» варианте у нас есть класс, и далее с помощью new мы можем наштамповать сколько угодно объектов (инстанцирование).
В прототипном же варианте у нас есть прототип, и… И дальше-то что? Мы не можем ни прописать ссылку на прототип в явном виде, ни склонировать этот самый прототип для последующих изменений, а должны использовать new или Object.create, попутно извращаясь над «прототипной функцией»-псевдоконструктором, что по сути является замаскированным использованием классов.
Вот если бы можно было сделать так:

var obj1= {… };
var obj2=clone(obj1);
obj2.newMethod=...;

или хотя бы вот так:

var obj1={… };
var obj2={ newMethod: function() {...} };
obj2.setPrototype(obj1);

вот тогда можно было бы говорить о прототипном наследовании. А так — в JS используется обычная классовая система, только с чрезвычайно уродливым негуманоидным синтаксисом. И хорошо, что создатели стандарта наконец-то это заметили.
0
Quadratoff, #
что по вашему такое «клон»? Мне кажется, на эту роль подходят объекты, производимые Object.create
+5
Quadratoff, #
первый ваш пример будет работать, если сделать:
var clone = Object.create;
0
Alexey2005, #
Спасибо, так действительно работает. Жаль, что этой возможности не было в стандарте с самого начала — тогда не потребовалось бы городить огород с function.prototype и new.
+1
megalol, #
Ну так имплементация Object.create есть в книге Крокфорда (http://stackoverflow.com/questions/2766057/what-is-happening-in-crockfords-object-creation-technique), не?
+1
rock, #
Мало того, и второй вариант работать будет в движках, поддерживающих __proto__, если ваш 'setPrototype' реализует obj2.__proto__=obj1. Свойство __proto__ включено в спецификацию ES6, да и setPrototypeOf там присутствует. Хотя изменять прототип существующего объекта — еще то извращение.
0
Quadratoff, #
Не могу для себя прояснить вопрос, чем Object.setPrototypeOf(obj) лучше obj.setPrototype()?
setPrototype более привычно и объектно-ориентированно, почему расширяют Object а не Object.prototype?
+1
rock, #
Лично я бы ничего против лишних методов Object.prototype не имел, если бы объекты и ассоциативные массивы в js небыли бы одним и тем же. А так — задаешь пустой хэш нотацией {}, а в нем уже несколько, хоть неперечислимых и в прототипе, но свойств, уже присутствует. И обратное — методы прототипа перекрываются свойствами объекта.
// Пример 1:
// телефонный справочник
var phone={
  'Вася':'+7987654',
  'Петя':'+7654321'
}
function number(name){
  if(name in phone)return phone[name];
  return 'Пользователь отсутствует в базе'
}
console.log(number('Вася'));          //=>'+7987654'
console.log(number('hasOwnProperty'));//=>function hasOwnProperty() { [native code] }
// Пример 2:
var phone={
  'Вася':'+7987654',
  'Петя':'+7654321'
}
function number(name){
  if(phone.hasOwnProperty(name))return phone[name];
  return 'Пользователь отсутствует в базе'
}
console.log(number('Вася'));          //=>'+7987654'
phone['hasOwnProperty']='+7666666';   //=>Добавляем нового абонента
console.log(number('Вася'));          //=>Упс
–1
lexazloy, #
Это и хорошо — можно перебивать. А если нужен каноничный метод, то:
Object.hasOwnProperty.call(phone, name)

JS вариативный. Здорово же.
0
rock, #
В любом другом случае — здорово, но не в случае с объектами-коллекциями. Лишняя громоздкая проверка на каждое обращение к элементу. Есть, конечно, Object.create(null), но тогда прощай ie8- (можно эмулировать через фрейм, но изврат) и простая запись коллекции фигурными скобками.
0
Quadratoff, #
спасибо, хороший пример.
0
RReverser, #
Потому что Object.prototype слишком обобщен и не хотят пересекаться с кастомными методами/полями любых объектов, которые только могут быть созданы и придуманы.
+2
monolithed, #
В последней 15-ой ревизии спецификации ES6, псевдо-свойство __proto__ вынесли в приложение B, это значит что его имплементация в небраузерных движках опцианальна.
Между тем Object.prototype.__proto__ находится в приложение F (свалка)
–5
Setrino, #
Последние от ECMA уже на Java syntax похожь)
–1
bO_oblik, #
То есть они прилепили классы в ECMA Script 6, но до сих пор осталось вот это сумасшествие: this.name = name||"Unnamed"?

Нда…
0
Quadratoff, #
нет, не осталось, это я забыл исправить. спасибо :)
0
Quadratoff, #
a классы еще не прилепили, все еще обсуждается
0
Quadratoff, #
super не особо вписывается в идеологию JS:
super.method() вызывается в контексте this, a не в контексте super, как следовало бы ожидать.
Разве что, можно предположить что super это прокси с ловушкой get, которая возвращает super[method].bind(this).
0
rock, #
Что значит «еще не прилепили»? Давно в working draft и оттуда уже явно не выпилят — на них многое завязано.
0
Quadratoff, #
Както я это пропустил… Ок. «прилепили» :)
0
Finom, #
Насколько мне известно, реализации классов создаются только лишь для добавления синтаксического сахара.
«Традиционное» наследование:
A = function() {};
A.prototype = { a: 3, constructor: A };
inherits(A,B); // некая функция, наследующая А от В

Наследование, реализованное в виде классов:
A = Class(B, { constructor: function() {}, a: 3 });

Как видно, во втором случае мы получаем компактность и очевидность того, что класс А наследуется от класса В. Иначе, при достаточно крупной функции-конструкторе и\или при увесистом прототипе, нам придется скроллить вниз, дабы понять, наследует ли класс А что-либо.

С чистым Object.create (без оберток) cитуация еще хуже, если нам требуется запустить конструктор и добавить свойств:
A = function() {};
A.prototype = { a: 3 };
a = Object.create(B.prototype);
A.call(a);
for( var i in A.prototype ) {
  a[ i ] = A.prototype[ i ];
}

Все крики о том, что надо использовать прототипы, а не классы, считаю бессмысленными, так как, в итоге, реализации классов всё равно используют прототипы и являются не более чем синтаксическим сахаром.
0
Quadratoff, #
Я говорю не сколько о том, что использовать прототипы, а о том что не использовать функции- конструкторы
–1
Finom, #
Как, в таком случае, будет выглядеть большое приложение, хотя бы на 100 тыс. строк? Я действительно не понимаю. Ваш вариант выглядит, так скажем, грязновато.
0
Finom, #
Перепутал порядок. A.call(a) идет конечно же, после for.
0
Quadratoff, #
А for этот зачем вообще? Это множественное наследование?
0
Finom, #
Дабы поместить в объект общие для всех подобных объектов свойства и методы.
0
Finom, #
И не обращайте внимания, я действительно не очень внимательно прочел статью в первый раз, ожидая типичную ругань на обертки, стараюсь исправиться ниже.
0
Quadratoff, #
Вы, наверное, статью не читали. А, ну да, для чего читать «бессмысленное». В ней описывается как можно обойтись без функций со свойством prototype (над которыми все обертки и строятся).
0
Finom, #
Сам не люблю подобные комментарии, поэтому перечитал статью. Несмотря на это, я всё еще не понимаю, как это должно выглядеть в крупном проекте.

В случае обычного конструктора, мы можем определить нужные нам свойства, передав аргументы при инициализации:
A = function( x, y ) { this.x = x; this.y = y; };
a = new A(1,2);

В вашем случае, придется устанавливать свойства вручную, после каждой инициализации.
a = Object.create(proto);
a.x = 1;
a.y = 2;

Теперь, имея горсть объектов, желая не помещать x и y в каждый из них, вам придется перелопатить все инициализации, убрав последние две строки кода, которого я показал. Но можно сделать и так:
a = Object.create(proto);
a.init(1,2)

или
a = Object.create(proto).init(1,2);

Но, тогда, чем это лучше запуска конструктора?

Кроме этого, мне понравилась идея ленивой инициализации (хотя и выглядит она очень страшно), но я совершенно не понимаю, в каких случаях этот подход может быть лучше «большого» конструктора.

Сам я — огромный любитель штук, типа «о, можно еще и так сделать!», но с опытом приходит понимание того, что любой подход требует проверки на проекте, и часто получается так, что прикольная штука может только навредить. Если покажете какой-нибудь хотя-бы небольшой проект и он не будет похож на кашу, я признаю свою ошибку и извинюсь.
+1
rock, #
У Object.create 2 параметра. 2ой как раз и задает свойства инстанса, но в формате дескрипторов, что не очень удобно.
Object.create(proto,{x:{value:1},y:{value:2}})

Можно это упростить, написав хелперы:
// Object.getOwnPropertyDescriptors есть в ES6
var getDescs=Object.getOwnPropertyDescriptors||function(obj){
  return Object.getOwnPropertyNames(obj).reduce(function(rez,k){
    rez[k]=Object.getOwnPropertyDescriptor(obj,k);
    return rez},{})
}
function create(obj,props){
  return Object.create(obj,getDescs(props))
}

// Используем
create(proto,{x:1,y:2});
+3
Quadratoff, #
я считаю что
a.width = 1; a.height = 2; a.size = 3; a.draggable = true; a.editable = true; ...

более понятно чем
a.init(1,2,3,true,true ...)

ну a лучше всего будет
a.setValues({width: 1, height: 2, size: 3, draggable: true, editable: true ...})

тут метод setValues общий, из Object.prototype, в отличии от вашего init, который для каждого класса нужно писать отдельный.

Не совсем понял что вы имеете ввиду. Что зачем перелопатитъ надо?
0
Finom, #
Перелопатить, если захотите не использовать свойства x и y из моего примера (например, захотите инкапсулировать эти аргументы).
0
Quadratoff, #
Плохо в js с инкапсуляцией :(
Если делать это через замыкания в конструкторе, то методы, обращающиеся к свойству, прийдется тоже создавать в конструкторе, и положить их в прототип уже не получится.

А насчет перелопатить, этого прийдется делать не больше чем изменять аргументы конструктору (во всех вызовах). Тут большой разницы я не вижу.
0
jMas, #
А используя get-ттеры и set-ттеры можно добиться инкапсуляции? По крайней мере есть параметр enumerable который позволяет «скрывать» свойства при использовании в циклах.
0
Quadratoff, #
можно, в не strict коде, используя deprecated caller
0
Quadratoff, #
Поправочка: не можно, а возможно :)
0
Finom, #
И еще, по поводу «длинного метода»: большой метод легко делится на мелкие.
0
Quadratoff, #
легко-нелегко, а делить его или не делить решается программистом. А с ленивой инициализацией делить придется обязательно всегда.
0
aivanov, #
Во втором примере ошибка.
Попытка вызвать donald.quack() после
var donald = new TalkingDuck("Donald");
вызывает бесконечную рекурсию.
0
Quadratoff, #
Спасибо, исправил.
+5
Develar, #
Все это разбивается о реальность — V8 использует hidden классы и вся инициализация должна производиться в конструкторе, а не где-то еще. Я автор ecma5 реализации в Котлине (https://github.com/develar/kotlin) и там сейчас не используется оператор new, а просто вызов функции и вот такой подход с Object.create. Но в итоге оказалось, что плюсов никаких и нет, смысла не использовать оператор new нет. А минусы есть — код хуже оптимизируется V8.
0
Quadratoff, #
Я нашел решение — __proto__. Он оказался даже быстрее конструкторов с оператором new.
Создал фреймворк из 3х строчек кода :)
github.com/quadroid/clonejs-nano
–1
alexDark, #
Еще один «эксперт» пришел в JS из правильных ООП языков и решил похвастаться.

К чему вся эта статья и сравнение идеологий и идиом, если автор абсолютно не представляет чем класс отличается от объекта с точки зрения хранения его в памяти. Нельзя объекты JS называть классами, это намного более масштабируемые структуры.
+1
Quadratoff, #
К чему вообще весь этот мир? Кругом тлен и безысходность. Да? :)
ECMA Script 6 называет эти объекты классами, почему мне нельзя?
Как мои знания или незнания тонкостей реализации транслятора JS влияют на суть статьи? В ней же обсуждаются идеологии и идиомы, как вы успели заметить.

P.S.
В JS я пришел из бейсика.
0
BR0kEN, #
Не обращайте внимания, просто alexDark считает себя умнее вас и что все, априори, должны знать то, что описано в публикации. Осознав это он решил завуалированно сообщить свои мысли сообществу. Посему ему можно рекомендовать к прочтению этот замечательный перевод: habrahabr.ru/post/178747/

p.s. За статью спасибо, вынес для себя кое-что.
–2
vanxant, #
прототип является классом и экземпляром объединенными в одну сущность, грубо говоря, Singleton'ом

wat???
+3
stalkerg, #
Часто начинаешь понимать что в JS и объекты не нужны. :)
Прототипное наследование может даже в чём то хорошо но все разработчики интерпретаторов и JIT оптимизаторов орут, что именно из-за этого происходит проседание по скорости. Гугл вон даже из-за этого Dart начал делать.

Классическое ООП на классах не сильно сковывает руки (хотя есть такое) но зато даёт возможность написать в разы более производительных интерпретаторов/компиляторов.

К JS ещё есть к слову притензия… это постоянное создание объектов (буквально на каждый чих да и ещё с замыканием), что приводит к фрагментированию памяти, увеличивает вероятность утечек и прочее.

Всё чаще начинают писать тяжёлое ПО на JS (игры и т.д.) и все эти проблемы вылезают. В итоге мы видим всякие Float32Array и прочие радости.
0
NekR, #
Так, ну давайте по порядку. Во первых, Object.create это очень медленно, по сравнению с конструктором. Проверьте.
Во вторых, концепт о приватных свойствах есть и уже часть будующей спецификации ES6. Подробности можно посмотреть тут — webreflection.blogspot.ru/2013/03/simulating-es6-symbols-in-es5.html
Опять же, вы можете сказать, что это не реальные приватные свойства. Однако они будут. Плюс, ещё возможность совместимости в ES5.
0
Infocatcher, #
Мне больше вот так нравится:
var obj = {
	get lazy() {
		delete this.lazy;
		return this.lazy = "Лениво инициализированное свойство " + this.name;
	},
	//set lazy(value) {
	//	delete this.lazy;
	//	return this.lazy = value;
	//},
	name: "БезИмени"
};

(лично мне set ни разу не был нужен)
Но, к сожалению, такое нельзя использовать в прототипах.
0
Quadratoff, #
Да, так красивее. Почему нельзя в прототипах?
0
Infocatcher, #
Геттер так не удаляется. А если бы и удалился – это сломало бы прототип (и все будущие и еще не инициализированные экземпляры).
function F(i) {
	this.i = i;
}
F.prototype = {
	get lazy() {
		delete this.lazy;
		return this.lazy = "Lazy: " + this.i;
	}
};
var f1 = new F(1);
var f2 = new F(2);
alert(f1.lazy + "\n" + f2.lazy); // 1 и 2
f1.i = f2.i = 3;
alert(f1.lazy + "\n" + f2.lazy); // 3 и 3


А еще в Firefox 3.6 и более старых выдавало ошибку
setting a property that has only a getter
:)
0
Infocatcher, #
Ну, а вот так уже все в порядке:
function F(i) {
	this.i = i;
}
F.prototype = {
	get lazy() {
		var value = "Lazy: " + this.i;
		Object.defineProperty(this, "lazy", {
			value: value,
			writable: true,
			enumerable: true
		});
		return value;
	}
};
var f1 = new F(1);
var f2 = new F(2);
alert(f1.lazy + "\n" + f2.lazy);
f1.i = f2.i = 3;
alert(f1.lazy + "\n" + f2.lazy);
0
Quadratoff, #
Геттер так не удаляется
а зачем ему удалятся из прототипа? Инстансы работают как надо, чем вам не нравится 1 и 2 и 3 и 3?
0
Quadratoff, #
ok. я понял
–1
mrjj, #
Я склонен воспринимать ООП всего лишь как еще одну систему контрактов между создаваемыми сущностями.

Если у нас есть фабрика, которая при одинаковых вводных производит одинаковые объекты, то есть императивно навязывает этот контракт всем создаваемым объектам, эта фабрика и есть класс.

Если мы делаем фабрику таких фабрик, то можем в рамках нее реализовать наследование и прочие взаимодействия между классами.

Если все эти уровни композиции будут забиваться на уровне готового синтаксиса, то кому-то станет проще, (особенно разработчикам IDE хе-хе). Меньше будет возможности из-за недопонимания как работает собственный код встрелить себе в ногу и всякие for-of служат приблизительно тому же. Понимание же при этом может как появиться, так и не появиться.
+1
funca, #
абстракция является одной из основных концепций программирования. поэтому что угодно можно написать на чем угодно. в С тоже можно программировать в ООП стиле, думая про классы, наследование и т.п. и в JavaScript ни кто не мешает эмулировать классы через прототипы и клонирование.

в остатке остается лишь чисто инженерный вопрос — вычислительная стоимость подобных развлечений. оптимизация на уровне мета-модели стоит дорого, а зачастую и вовсе не реализуема — поэтому получается хоть и похоже, но на порядок медленнее. вот и вся разница. :)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.