Pull to refresh

Comments 35

Классы это удобно. Человеческое мышление так устроено: мы категоризируем вещи.
Ну и если мне в приложении нужно будет n кошек и m собак, я сделаю три класса (Animal, Dog и Cat с очевидными отношениями), а не буду городить громазду на прототипах и фабриках. А если мне будет нужна одна кошка и одна собака, то я еще подумаю, как я буду эти, смахивающие на синглтоны, объекты покрывать тестами.
Классическая проблема наследования.

Проблема в том, что то как мы «категоризируем» вещи плохо ложится на программирование.

Поэтому мы придумываем строго определенные абстракции, которые можно использовать.
Эта проблема возникает только в языках с плохой рефлексией, имхо.
Но эта проблема придумывания «красивых» иерхаических зависимостей существует независимо от того есть ли рефлексия или нет.
Не без этого, согласен. Но в языках с динамической типизацией и/или рефлексией проблемы типа «положить все в один контейнер, потом вызвать специфичный для каждого объекта метод» либо нет, либо она решается достаточно просто. В том же JS (а мы сейчас про него, а не про С++) достаточно instanceof.
А разве конструктор в сабклассе Dog, не должен быть таким?
  constructor(type, name, breed){
    super(type);
    this.name = name;
    this.breed = breed;
  }
В каком именно способе? Для Способа 2 он так и выглядит.
во втором способе, сейчас там такой конструктор:
  constructor(name, breed){
    super("dog");
    this.name = name;
    this.breed = breed;
  }
Но ведь тогда type надо будет передавать при создании каждого экземпляра.
var sparkie = new Dog("dog", "Sparkie", "Border Collie");

Я понимаю, что возникает вопрос, а что делать с type в последующих подклассах Dog. Но в данном случае, мне кажется, что автор просто хотел привести простой пример того как использовать ключевое слово class.
Мне просто интересно, а Вам какой Способ больше импонирует?
Я такой способ последнее время использую:

function Parent(property){
    this.property = property;
    this.method = function(){}
}

function Child(){/* extends */Parent
    .apply(this, arguments);

    var privateVar = "";
    this.getPrivate = function(){return privateVar}
}

var child = new Child( 11 );
У вас для каждого приватного метода каждого инстанса создаётся замыкание. Это приводит к лишнему потреблению памяти и снижению скорости. При создании большого числа объектов это может дать печальный эффект.
методы я обычно выношу:
function Parent(property){
    this.property = property;
    this.method = method;
}
function method(){}

а классы с предполагаемо большим количеством инстансов оформляю с использованием прототипов (таких классов обычно не много получается).
Такие «вынесенные» методы уже не имеют доступа к «приватным» полям.
Помогите, пожалуйста, разобраться.
А есть ли реальная потребность создавать такое большое количество инстансов, что это может привести к излишнему потреблению памяти? Можете привести какой-нибудь реальный пример?
У меня была задача в реальном времени рисовать список задач на десятки тысяч штук. У каждой задачи есть название, статус, список тегов, список подзадач, дата начала, дата завершения и ещё пачка других данных. По всем этим данным нужно было в реальном времени строить различные сортировки/группировки. В общем, при открытии такого списка требовалось держать в памяти сотни тысяч объектов.
Не хочу показаться занудным, но этот случай мне кажется нетипичным. Детальных условий задачи я, конечно, не знаю, может на самом деле была такая необходимость все это делать на клиенте силами js, не привлекая сервер.
Под нетипичные задачи всегда приходится как-нибудь да оптимизировать код.

Мне хочется понять, реально ли необходимо заморачиваться на экономию памяти в замыканиях при решении обычных задач.
Если есть возможность, то стоит. Так как когда появятся «нетипичные требования» придётся переписывать пол приложения для оптимизации.
Логика понятна.
Спасибо за ответ.
Первый и второй способы наиболее быстрые, потребляют минимум памяти и легко статически анализируются (что даёт адекватные подсказки в IDE, подсветку ошибок и опять же лучшую JIT-оптимизацию).
Cкорость и экономность — это уже отдельный вопрос, оптимизация досигается за счет деталей реализации VM. В v8, например, есть такая штука как hidden classes. Очень много кода написано с использованием первого способа, вероятно потому так и оптимизировано. Стандартный конфликт: «написать чтобы работало быстро и мало кушало» против «написать все нормально, чтобы там SOLID и т.п.».
В первых двух способах нет такого конфликта.
Там речь про динамическое изменение прототипа. Эта фича не даёт инлайнить метод предка из-за чего снижает производительность. Тем не менее возможность в рантайме похачить методы предка — не относится ни к «всё нормально», ни к «SOLID». Так что правильно там возмущаются, что не стоит ради неё снижать производительность.
Кроме того, проблема с производительностью легко решается, если кешировать прототип: jsperf.com/6to5-inherit/2
Причем здесь SOLID, который был упомянут только для примера? Либо «быстро», либо «правильно», «правильная» реализация второго способа быстрой быть не может без оптимизаций движка, коих на данный момент нет. Ваш «method right» совсем не «right» — кэшировать прототип, согласно стандарту, нельзя, он может быть изменен, его необходимо получать динамически, в чём и проблема. У вас — применение первого способа вместо второго. Как и loose mode.
Касательно прототипа — корректно, касательно получения свойства — нет. Вы думаете, зачем нужен хелпер _get? Это реализация внутреннего метода [[Get]], который дополнительным аргументом принимает Receiver — контекст исполнения геттера. У вас геттер, которым может оказаться свойство прототипа родителя, будет исполнен в контексте прототипа родителя. Простой пример выводит 2, undefined. Без запуска геттера в контексте инстанса будет изменен прототип родителя и выведено 1, 2.
Странный, но по стандарту должен работать. Я не защищаю логику классов по умолчанию в Babel, а объясняю, почему оно сделано так, а не иначе (иначе зачем бы я создавал ту ищью? :) ). В V8 абсолютно та же ситуация. Не хотите такого оверхеда из-за совсем уж невероятных кейсов — используйте loose mode — получите примерно тот же код, что предлагали изначально, только код этот скорее относится к первому способу из данной статьи, а не второму. Да и, по крайней мере, пока, второй способ не может претендовать на хорошую оптимизацию, как вы утверждали.
Да, мы отвлеклись :-) Плюсы последних 2 способов наследования не перекрывают их минусов, так что не вижу смысла их предпочитать.
Переопределение прототипа не дает возможности использовать obj.constructor, так что лучше так не делать.
prototype.constructor обычно тоже переопределяют.
Я не говорю, что это проблема, но в даном коде этого нет.
а уж как я то надеюсь, что мне никогда не придется поддерживать код автора…
Sign up to leave a comment.

Articles