Подтвердить что ты не робот

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

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

4b9b3361

Ответ 1

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

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

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

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Однако разность скоростей почти ничтожна. Кроме того, создание метода на прототипе лучше двумя более эффективными способами:

  • Быстрее создавать экземпляры (jsperf)
  • Использует меньше памяти

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

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


ASIDE:

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

Ответ 5

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

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

Надеюсь, это поможет.:)

Ответ 6

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

Что касается моего опыта, я использую конструкторы для того, чтобы буквально строить мои объекты религиозно, независимо от того, являются ли методы частными или нет. Основная причина в том, что, когда я начал, это был самый простой подход к мне, так что это не особое предпочтение. Возможно, это было так просто, как мне нравится видимая инкапсуляция, а прототипы немного развоплощены. Мои частные методы также будут назначаться как переменные в области. Хотя это моя привычка и держит вещи красиво самостоятельно, это не всегда лучшая привычка, и я иногда ударяю о стены. Помимо сумасшедших сценариев с высокой динамической самосборкой в ​​соответствии с конфигурационными объектами и макетом кода, на мой взгляд, это более слабый подход, особенно если производительность является проблемой. Знание того, что внутренняя часть является частной, полезна, но вы можете достичь этого с помощью других средств с правильной дисциплиной. Если производительность не является серьезным соображением, используйте все, что лучше всего работает для этой задачи.

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

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

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