Пользователь
56,7
карма
0,0
рейтинг
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
Александр Швец @Quadratoff
карма 56,7
рейтинг 0,0
Реклама

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

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

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

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

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

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

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

Нда…
Quadratoff
0
нет, не осталось, это я забыл исправить. спасибо :)
Quadratoff
0
a классы еще не прилепили, все еще обсуждается
Quadratoff
0
super не особо вписывается в идеологию JS:
super.method() вызывается в контексте this, a не в контексте super, как следовало бы ожидать.
Разве что, можно предположить что super это прокси с ловушкой get, которая возвращает super[method].bind(this).
rock
0
Что значит «еще не прилепили»? Давно в working draft и оттуда уже явно не выпилят — на них многое завязано.
Quadratoff
0
Както я это пропустил… Ок. «прилепили» :)
Finom
0
Насколько мне известно, реализации классов создаются только лишь для добавления синтаксического сахара.
«Традиционное» наследование:
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 ];
}

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

В случае обычного конструктора, мы можем определить нужные нам свойства, передав аргументы при инициализации:
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);

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

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

Сам я — огромный любитель штук, типа «о, можно еще и так сделать!», но с опытом приходит понимание того, что любой подход требует проверки на проекте, и часто получается так, что прикольная штука может только навредить. Если покажете какой-нибудь хотя-бы небольшой проект и он не будет похож на кашу, я признаю свою ошибку и извинюсь.
rock
+1
У 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});
Quadratoff
+3
я считаю что
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, который для каждого класса нужно писать отдельный.

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

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

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

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

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

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

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

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

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

(лично мне set ни разу не был нужен)
Но, к сожалению, такое нельзя использовать в прототипах.
Quadratoff
0
Да, так красивее. Почему нельзя в прототипах?
Infocatcher
0
Геттер так не удаляется. А если бы и удалился – это сломало бы прототип (и все будущие и еще не инициализированные экземпляры).
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
:)
Infocatcher
0
Ну, а вот так уже все в порядке:
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);
Quadratoff
0
Геттер так не удаляется
а зачем ему удалятся из прототипа? Инстансы работают как надо, чем вам не нравится 1 и 2 и 3 и 3?
Quadratoff
0
ok. я понял
mrjj
–1
Я склонен воспринимать ООП всего лишь как еще одну систему контрактов между создаваемыми сущностями.

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

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

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

в остатке остается лишь чисто инженерный вопрос — вычислительная стоимость подобных развлечений. оптимизация на уровне мета-модели стоит дорого, а зачастую и вовсе не реализуема — поэтому получается хоть и похоже, но на порядок медленнее. вот и вся разница. :)
trigger_af
0
Спасибо, узнал много интересной информации по тому, как сделать наследование, но всё же по заголовку статьи надеялся услышать больше ответов на вопрос «нужны ли классы или прототипы (наследование) вообще для задачи конструирования пользовательского интерфейса на JS?»

Например, если не рассматривать только задачу создания UI, то обычно классы нужны для того, чтобы описать иерархию между элементами, например, Rectangle->Element->Container->Window, когда весь интерфейс делается вручную с нуля или в рамках UI библиотеки какой-то. А тут вроде как у нас браузер предоставляет уже все элементы типа input, div и т.д. С другой стороны, их надо как-то расширять, значит наверное всё-таки нужно?

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

Интересные публикации

Вакансии