Определение методов через prototype vs с использованием этого в конструкторе-действительно разница в производительности?

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

Способ 1:

function MyClass() {
    var privateInstanceVariable = 'foo';
    this.myFunc = function() { alert(privateInstanceVariable ); }
}

Способ 2:

function MyClass() { }

MyClass.prototype.myFunc = function() { 
    alert("I can't use private instance variables. :("); 
}

Я много раз читал людей слова что использование метода 2 более эффективно, поскольку все экземпляры имеют одну и ту же копию функции, а не каждый получает свой собственный. Однако определение функций через прототип имеет огромный недостаток - это делает невозможным наличие частного экземпляра переменная.

хотя, теоретически, использование метода 1 дает каждому экземпляру объекта свою собственную копию функции (и, таким образом, использует гораздо больше памяти, не говоря уже о времени, необходимом для распределения) - это то, что на самом деле происходит на практике? Похоже, что оптимизация веб-браузеров может легко сделать, чтобы распознать этот чрезвычайно распространенный шаблон и на самом деле иметь все экземпляры ссылки на объект тот же копия функций, определенных с помощью этого " конструктора должностные обязанности." Тогда он может дать экземпляру только свою собственную копию функции, если она будет явно изменена позже.

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

6 ответов


см.http://jsperf.com/prototype-vs-this

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

если у вас есть узкое место производительности в вашем приложении, это вряд ли будет так, если вы не создаете экземпляры 10000+ объектов на каждом шаге какой-либо произвольной анимации, например.

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

Я добавлю, что в JavaScript существует соглашение свойств префикса, которые должны рассматриваться как частные с подчеркиванием (например,_process()). Большинство разработчиков поймут и избегнут этих свойств, если они не готовы отказаться от социального контракта, но в этом случае вы можете не угодить им. То, что я хочу сказать, таково: вам, вероятно, действительно не нужно правда частные переменные...


в новой версии Chrome, это.метод примерно на 20% быстрее прототипа.метод, но создание нового объекта все еще медленнее.

Если вы можете повторно использовать объект вместо создания нового, это может быть 50% - 90% быстрее, чем создание новых объектов. Плюс преимущество отсутствия сбора мусора, которое огромно:

http://jsperf.com/prototype-vs-this/59


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

Я создал тестовый случай на jsperf, чтобы продемонстрировать это:

http://jsperf.com/prototype-vs-this/10


вы, возможно, не рассматривали это, но размещение метода непосредственно на объекте на самом деле лучше в одном смысле:

  1. вызовы метода являются очень немного быстрее (см. Этот тест jsperf), так как цепочка прототипов не должна консультироваться для решения метода.

однако разница в скорости почти незначительна. Кроме того, размещение метода на прототипе лучше в двух более впечатляющих пути:

  1. быстрее создавать экземпляры (см. Этот тест jsperf)
  2. использует меньше памяти

Как сказал Джеймс, эта разница может быть важна, если вы создаете тысячи экземпляров класса.

тем не менее, я могу представить себе движок JavaScript, который распознает, что функция, которую вы прикрепляете к каждому объекту, не изменяется в экземплярах и, таким образом, сохраняет только одну копию функция в памяти, со всеми методами экземпляра, указывающими на общую функцию. На самом деле, кажется, что Firefox делает некоторые специальные оптимизации, как это, но Chrome не является.


в сторону:

вы правы, что невозможно получить доступ к переменным частного экземпляра из внутренних методов на прототипах. Поэтому я думаю, что вы должны задать себе вопрос: цените ли вы возможность сделать переменные экземпляра действительно частными по сравнению с использованием наследования и прототипирование? Я лично думаю, что сделать переменные действительно частными не так важно и просто использовать префикс подчеркивания (например, "это._myVar") означает, что, хотя переменная является публичной, ее следует считать частной. Тем не менее, в ES6, по-видимому, есть способ иметь оба из обоих миров!


короче говоря, используйте метод 2 для создания свойств / методов, которыми будут делиться все экземпляры. Они будут "глобальными", и любые изменения в них будут отражены во всех инстанциях. Используйте метод 1 для создания конкретных свойств/методов экземпляра.

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

надеюсь, что это помогает. :)


этот ответ следует рассматривать как расширение остальных ответов, заполняющих недостающие точки. Как личный опыт, так и критерии включены.

  1. использование наследования прототипа и соглашения для обозначения элементов как частных упрощает отладку, поскольку вы можете легко перемещаться по графу объектов из консоли или отладчика. С другой стороны, такая условность несколько усложняет запутывание и облегчает другим людям собственные скрипты на вашем сайте. Это одна из причин, по которой частный подход получил популярность. Это не настоящая безопасность, но вместо этого добавляет сопротивления. К сожалению, многие люди все еще думают, что это действительно способ программирования безопасного JavaScript. Поскольку отладчики стали действительно хорошими, обфускация кода занимает свое место. Если вы ищете недостатки безопасности, где слишком много на клиенте,это шаблон дизайна, который вы можете захотеть посмотреть.
  2. Конвенция позволяет защищенные свойства с небольшой суетой. Это может быть благословением и проклятием. Это облегчает некоторые проблемы наследования, поскольку оно менее ограничительно. У вас все еще есть риск столкновения или увеличения когнитивной нагрузки при рассмотрении того, где еще можно получить доступ к свойству. Самостоятельная сборка объектов позволяет вам делать некоторые странные вещи, где вы можете обойти ряд проблем наследования, но они могут быть нетрадиционными. Мои модули, как правило, имеют богатую внутреннюю структуру, где вещи не вытаскиваются до функциональность необходима в другом месте (общая) или предоставляется, если это не требуется извне. Шаблон конструктора, как правило, приводит к созданию автономных сложных модулей больше, чем просто фрагментарных объектов. Если ты этого хочешь, то все в порядке. В противном случае, если вы хотите более традиционную структуру и макет ООП, я бы, вероятно, предложил регулировать доступ по соглашению. В моих сценариях использования сложный ООП не часто оправдан, и модули делают трюк.
  3. все тесты здесь минимальный. В реальном мире использование модулей, вероятно, будет более сложным, что делает хит намного больше, чем показывают тесты. Довольно распространено иметь частную переменную с несколькими методами, работающими над ней, и каждый из этих методов добавит больше накладных расходов на инициализацию, которые вы не получите с наследованием прототипа. В большинстве случаев это не имеет значения, потому что только несколько экземпляров таких объектов плавают вокруг, хотя в совокупности это может сложиться.
  4. есть предположение что методы прототипа медленнее вызывать из-за поиска прототипа. Это не несправедливое предположение, я сам сделал то же самое, пока не проверил его. На самом деле это сложно, и некоторые тесты предполагают, что аспект тривиален. Между prototype.m = f, this.m = f и this.m = function... последний выполняет значительно лучше, чем первые два, которые выполняют примерно то же самое. Если бы только поиск прототипа был значительной проблемой, то последние две функции вместо этого выполняли бы первую значительно. Вместо чего-то по крайней мере, в том, что касается канарейки, происходит что-то странное. Возможно, функции оптимизированы в соответствии с тем, что они являются членами. В игру вступает множество соображений производительности. У вас также есть различия для доступа к параметрам и доступа к переменным.
  5. Объем Памяти. Здесь это не обсуждается. Предположение, которое вы можете сделать заранее, вероятно, верно, заключается в том, что наследование прототипа обычно будет гораздо более эффективным для памяти, и согласно моим тестам оно находится в генеральный. Когда вы создаете свой объект в своем конструкторе, вы можете предположить, что каждый объект, вероятно, будет иметь свой собственный экземпляр каждой функции, а не общий, большую карту свойств для своих личных свойств и, вероятно, некоторые накладные расходы, чтобы сохранить область конструктора открытой. Функции, которые работают в частной области, чрезвычайно и непропорционально требуют памяти. Я считаю, что во многих сценариях пропорциональная разница в памяти будет намного более значительной, чем пропорциональная разница в циклах процессора.
  6. Графической Памяти. Вы также можете заглушить двигатель, делая GC более дорогим. Профилировщики, как правило, показывают время, проведенное в GC в эти дни. Это не только проблема, когда дело доходит до выделения и освобождения большего. Вы также создадите более крупный объектный график для прохождения и тому подобное, чтобы GC потреблял больше циклов. Если вы создадите миллион объектов, а затем едва прикоснетесь к ним, в зависимости от двигателя может получиться больше окружающего влияние производительности, чем вы ожидали. Я доказал, что это, по крайней мере, заставляет gc работать дольше, когда объекты удаляются. То есть существует корреляция с используемой памятью и временем, которое требуется для GC. Однако есть случаи, когда время одинаково независимо от памяти. Это указывает на то, что структура графика (слои косвенности, количество элементов и т. д.) оказывает большее влияние. Это не всегда легко предсказать.
  7. не многие люди используют цепные прототипы в том числе и я, должен признать. Прототип цепи может быть дорогим в теории. Кто-то будет, но я не измерил стоимость. Если вы вместо этого строите свои объекты полностью в конструкторе, а затем имеете цепочку наследования, поскольку каждый конструктор вызывает родительский конструктор на себя, теоретически доступ к методу должен быть намного быстрее. С другой стороны, вы можете выполнить эквивалент, если это имеет значение (например, сгладить прототипы по цепочке предков), и вы не возражаете сломать такие вещи, как hasOwnProperty, возможно, instanceof и т. д., Если вам это действительно нужно. В любом случае вещи начинают усложняться, как только вы идете по этой дороге, когда дело доходит до хаков производительности. Возможно, в конце концов ты сделаешь то, что не должен делать.
  8. многие люди напрямую не используют ни один из представленных вами подходов. Вместо этого они делают свои собственные вещи, используя анонимные объекты, позволяющие обмениваться методами любым способом (например, mixins). Существует также ряд рамок, которые реализуют собственные стратегии организации модулей и объектов. Это в значительной степени основанные на конвенции пользовательские подходы. Для большинства людей и для вас вашей первой задачей должна быть организация, а не производительность. Это часто осложняется тем, что Javascript дает много способов достижения вещей по сравнению с языками или платформами с более явной поддержкой ООП/пространства имен/модулей. Когда дело доходит до производительности, я бы сказал, чтобы избежать крупных подводных камней в первую очередь.
  9. появился новый символ тип, который должен работать для частных переменных и методов. Существует несколько способов использовать это, и это вызывает множество вопросов, связанных с производительностью и доступом. В моих тестах производительность символов не была большой по сравнению со всем остальным, но я никогда не тестировал их тщательно.

отказ от ответственности:

  1. есть много дискуссий о производительности и не всегда есть постоянно правильный ответ на этом сценарии использования и двигателей изменение. Всегда профиль, но также всегда измеряется более чем одним способом, поскольку профили не всегда точны или надежны. Избегайте значительных усилий по оптимизации, если нет определенно очевидной проблемы.
  2. вероятно, лучше вместо этого включать проверки производительности для чувствительных областей в автоматизированное тестирование и запускать при обновлении браузеров.
  3. помните, что иногда время автономной работы имеет значение, а также ощутимую производительность. Самое медленное решение может оказаться быстрее после запуск оптимизирующего компилятора на нем (т. е. компилятор может иметь лучшее представление о том, когда доступны переменные ограниченной области, чем свойства, помеченные как частные по соглашению). Рассмотрим бэкэнд, такой как node.js. Это может потребовать лучшей задержки и пропускной способности, чем вы часто найдете в браузере. Большинству людей не нужно беспокоиться об этих вещах с чем-то вроде проверки для регистрационной формы, но количество различных сценариев, где такие вещи могут иметь значение возрастающий.
  4. вы должны быть осторожны с инструментами отслеживания выделения памяти для сохранения результата. В некоторых случаях, когда я не возвращал и не сохранял данные, он был полностью оптимизирован или частота дискретизации была недостаточной между экземплярами/неферментированными, оставив меня чесать голову о том, как массив инициализирован и заполнен до миллиона, зарегистрированного как 3.4 KiB в профиле распределения.
  5. в реальном мире в большинстве случаев единственный способ, чтобы действительно оптимизировать заявки писать в первую очередь так что вы можете измерить его. Есть десятки и сотни факторов, которые могут вступить в игру, если не тысячи в любом данном сценарии. Двигатели также делают вещи, которые могут привести к асимметричным или нелинейным характеристикам производительности. Если вы определяете функции в конструкторе, они могут быть функциями стрелки или традиционными, каждый ведет себя по-разному в определенных ситуациях, и я понятия не имею о других типах функций. Классы тоже не ведут себя одинаково в плане как производительность для прототипированных конструкторов, которые должны быть эквивалентны. Вы также должны быть очень осторожны с критериями. Прототипированные классы могут иметь отсроченную инициализацию различными способами, особенно если ваш прототип также ваши свойства (совет, не). Это означает, что можно занижать стоимость инициализации и завышать стоимость мутации доступа/свойства. Я также видел признаки прогрессивной оптимизации. В этих случаях я заполнил большой массив экземплярами объектов, которые идентичны и по мере увеличения числа экземпляров объекты, по-видимому, постепенно оптимизируются для памяти до точки, где остаток одинаков. Также возможно, что эти оптимизации также могут значительно повлиять на производительность процессора. Эти вещи сильно зависят не только от кода, который вы пишете, но и от того, что происходит во время выполнения, например, количество объектов, дисперсия между объектами и т. д.