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

Почему прототипирование JavaScript?

Это может поразить вас как грамматически неправильный и, возможно, безумный вопрос, но вот что я имею в виду: пытаясь понять концепцию prototype в JavaScript, я столкнулся с примерами, которые были несколько более или менее сложными версиями следующих

//Guitar function constructor
function Guitar(color, strings) {
    this.color = color;
    this.strings = strings;
}
//Create a new instance of a Guitar
var myGuitar = new Guitar('Black', ['D', 'A', 'D', 'F', 'A', 'E']);
//Adding a new method to Guitar via prototype
Guitar.prototype.play = function (chord) {
    alert('Playing chord: ' + chord);
};
//Now make use of this new method in a pre-declared instance
myGuitar.play('D5');

Итак, к моей проблеме: Почему, черт возьми, вы хотели бы это сделать? Почему бы вам не просто поместить функцию play в Guitar для начала? Зачем объявлять экземпляр и затем добавлять методы позже? Единственная причина, по которой я могу видеть, - это если вы хотите, чтобы myGuitar не имел доступа к play, когда он изначально был создан, но я не могу придумать ни одного примера, объясняющего причину, почему вы хотите что-то вроде этого.

Похоже, было бы более полезно сделать это:

function Guitar(color, string) {
    this.color = color;
    this.strings = strings;
    this.play = function (chord) {
        alert('Playing chord: ' + chord);
    };
}
var myGuitar = new Guitar('White', ['E', 'A', 'D', 'G', 'B', 'E']);
myGuitar.play('E7#9');

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

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

Изменить: Некоторые ответы:

  • Когда я сказал: "Зачем объявлять экземпляр, а потом добавлять методы позже?" Я больше критиковал все примеры, которые я вижу, которые играют в порядке моего первого примера. Когда этот порядок изменен, как и в приведенном ниже ответе Harmen, он визуально визуально визуализирует немного больше. Однако это не меняет того факта, что в том же духе, что и в моем первом примере, вы можете создать пустой конструктор объектной функции, объявить 100 экземпляров этого объекта, а затем только после этого определить, каков исходный объект на самом деле, методов и свойств через prototype. Возможно, это обычно делается таким образом, чтобы намекнуть на идею "Копировать против ссылки", описанную ниже.
  • На основании нескольких ответов, вот мое новое понимание: если вы добавите все свои свойства и методы в конструктор объектной функции, затем создайте 100 экземпляров этого объекта, вы получите 100 копий всех свойств и методов. Вместо этого, если вы добавите все свои свойства и методы в конструктор конструктора объектов prototype, затем создайте 100 экземпляров этого объекта, вы получите 100 ссылок на единственную (1) копию свойств объекта и методов. Это, очевидно, быстрее и эффективнее, и поэтому используется prototype (помимо изменения таких вещей, как String и Image, как указано ниже). Итак, почему бы не сделать это:

(Маркированные списки ломают любой код сразу после них, по-видимому, поэтому я должен добавить строку отдельного текста здесь)

function Guitar(color, strings) {
    this.prototype.color = color;
    this.prototype.strings = strings;
    this.prototype.play = function (chord) {
        alert('Playing chord: ' + chord);
    };
}
var myGuitar = new Guitar('Blue', ['D', 'A', 'D', 'G', 'B', 'E']);
myGuitar.play('Dm7');
4b9b3361

Ответ 1

Итак, к моей проблеме: Почему, черт возьми, вы хотели бы это сделать? Почему бы вам не просто начать играть на гитаре? Зачем объявлять экземпляр и затем добавлять методы позже?

Javascript не является "классическим" языком наследования. Он использует прототипное наследование. Это так, как есть. В этом случае правильным способом создания метода в классе является использование метода на прототипе. Обратите внимание, что я помещаю "класс" в кавычки, поскольку, строго говоря, JS не имеет понятия "класс". В JS вы работаете над объектами, которые определяются как функции.

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

Обратите внимание на разницу. В примере "почему не так", который вы опубликовали, каждый раз, когда вы создаете новую гитару, вам нужно создать новый метод воспроизведения, который будет таким же, как и любой другой метод воспроизведения. Однако, если игра находится на прототипе, все гитары заимствуют у одного и того же прототипа, поэтому все они используют один и тот же код для игры. Его различие между x количеством гитар, каждый с одинаковым игровым кодом (так что у вас есть x копий пьесы) против x количество гитар, разделяющих один и тот же игровой код (1 копия игры, независимо от того, сколько гитар). Компромисс, конечно, заключается в том, что во время исполнения игра должна быть связана с объектом, для которого она предназначена для определения области обзора, но у javascript есть методы, которые позволяют вам делать это очень эффективно и легко (а именно методы call и apply)

Многие фреймворки javascript определяют свои собственные утилиты для создания "классов". Как правило, они позволяют вам писать код, например, пример, который вы сказали, который вы хотели бы видеть. За кулисами они выполняют функции на прототипе для вас.


EDIT - ответ на ваш обновленный вопрос, почему никто не может

function Guitar() {
    this.prototype.play = function()....
}

это связано с тем, как javascript создает объекты с ключевым словом "new". См. Второй ответ здесь - в основном при создании экземпляра javascript создает объект, а затем присваивает свойства прототипа. Так что this.prototype.play действительно не имеет смысла; на самом деле, если вы попробуете, вы получите сообщение об ошибке.

Ответ 2

Как примечание перед началом - я использую ECMAScript здесь вместо JavaScript, поскольку ActionScript 1 и 2 демонстрируют точно такое же поведение во время выполнения.

Те из нас, кто работают в более "традиционном" объектно-ориентированном мире (читают Java/С#/PHP), находят идею расширения класса во время работы почти полностью чуждым. Я имею в виду, серьезно, это должен быть мой ОБЪЕКТ. Мой OBJECT выйдет вперед и сделает THINGS, которые были SET FORTH. Детские классы EXTEND другие классы. У этого есть очень структурированное, твердое, настроенное в камне чувство к этому. И, по большей части, это работает, и оно работает достаточно хорошо. (И это одна из причин, по которой Гослинг спорил, и я думаю, что большинство из нас согласится довольно эффективно, что оно так хорошо подходит для массовых систем)

ECMAScript, с другой стороны, следует гораздо более примитивной концепции ООП. В ECMAScript наследование классов полностью, верьте или нет, гигантский узор декоратора. Но это не просто шаблон декоратора, который вы можете сказать, присутствует в С++ и Python (и вы можете легко сказать, что это декораторы). ECMAScript позволяет назначить прототип класса экземпляру.

Представьте, что это происходит в Java:

class Foo {
    Foo(){}
}

class Bar extends new Foo() {
    // AAAHHHG!!!! THE INSANITY!
}

Но это именно то, что доступно в ECMAScript (я считаю, что Io также допускает нечто подобное, но не цитирую меня).

Причина, по которой я сказал, что это примитивно, заключается в том, что этот тип философии дизайна очень тесно связан с тем, как Маккарти использовал Lambda Calculus для реализации Lisp. Это больше связано с идеей closures, чем, например, Java OOP.

Итак, еще в тот же день, Церковь Алонсо написала The Calculi Lambda Conversion, семенную работу в "Исчислении Лямбды". В нем он предлагает два способа взглянуть на функции с несколькими аргументами. Во-первых, их можно рассматривать как функции, которые принимают одиночные числа, кортежи, тройки и т.д. В основном f (x, y, z) понимается как f, который принимает параметр (x, y, z). (Кстати, мое скромное мнение, что это основной импульс для структуры списков аргументов Pythons, но это гипотеза).

Другой (и для наших целей (и, честно говоря, цели Церкви) более важен) определение было подхвачено Маккарти. f (x, y, z) следует вместо этого перевести в f (x g (y h (z))). Разрешение самого внешнего метода может исходить из ряда состояний, которые были вызваны внутренними вызовами функций. Это сохраненное внутреннее состояние является самой основой закрытия, которое, в свою очередь, является одним из оснований для современного ООП. Закрытие позволяет передавать замкнутые исполняемые состояния между разными точками.

Отвлечение любезно предоставлено книгой Land Of Lisp:

; Can you tell what this does? It it is just like your favorite 
; DB’s sequence!
; (getx) returns the current value of X. (increment) adds 1 to x 
; The beauty? Once the let parens close, x only exists in the 
; scope of the two functions! passable enclosed executable state!
; It is amazingly exciting!
(let (x 0)
  ; apologies if I messed up the syntax
  (defun increment ()(setf x (+ 1 x)))
  (defun getx ()(x)))

Теперь, что это связано с ECMAScript против Java? Ну, когда объект создается в ECMAScript, он может следовать за этим шаблоном почти точно:

 function getSequence()
{
     var x = 0;
     function getx(){ return x }
     function increment(){ x++ }
     // once again, passable, enclosed, executable state
     return { getX: getX, increment:increment}
}

И здесь начинается запуск прототипа. Наследование в ECMAScript означает: "Начните с объекта A и добавьте его". Он не копирует его. Он принимает это волшебное состояние, и ECMAScript добавляет его. И это самый источник и вершина, почему он должен допускать MyClass.prototype.foo = 1.

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

По большей части стилистически полезно ставить все ваши определения в одном и том же месте, но иногда это невозможно. Например, расширения jQuery работают, основываясь на идее непосредственного добавления прототипа объекта jQuery. Библиотека Prototype фактически имеет специализированный способ расширения определений классов, которые он использует последовательно.

Если я правильно помню Prototype.js, это примерно так:

 var Sequence = function(){}

 // Object.extend takes all keys & values from the right object and
 // adds them to the one on the left.
 Object.extend( Sequence.prototype, (function()
 {
     var x = 0;
     function getx(){ return x }
     function increment(){ x++ }
     return { getX: getX, increment:increment}
  })());

Что касается использования ключевого слова prototype внутри исходного определения, то, скорее всего, это не будет работать в большинстве случаев, потому что "this" относится к экземпляру объекта, который определен (в момент создания экземпляра). Если экземпляр также не имел свойство "prototype", this.prototype обязательно будет undefined!

Поскольку все this s внутри исходного определения будут экземплярами этого объекта, будет достаточно модифицировать this. Но (и я улыбаюсь, когда говорю это, потому что он идет вместе с прототипом), каждый this имеет свойство constructor.

 // set the id of all instances of this "class". Event those already 
 // instantiated...
 this.constructor.prototype.id = 2
 console.log( this.id );

Ответ 3

Если вы не используете прототип, каждый раз, когда вы вызываете конструктор Guitar, вы создадите новую функцию. Если вы создаете много объектов Guitar, вы заметите разницу в производительности.

Другой причиной использования прототипов является эмулирование классического наследования.

var Instrument = {
    play: function (chord) {
      alert('Playing chord: ' + chord);
    }
};

var Guitar = (function() {
    var constructor = function(color, strings) {
        this.color = color;
        this.strings = strings;
    };
    constructor.prototype = Instrument;
    return constructor;
}());

var myGuitar = new Guitar('Black', ['D', 'A', 'D', 'F', 'A', 'E']);
myGuitar.play('D5');

В этом примере гитара расширяет инструмент и, следовательно, имеет функцию "играть". Вы также можете переопределить функцию "играть" инструмента на гитаре, если хотите.

Ответ 4

JavaScript - это прототип языка, довольно редкая порода. Это не произвольно, это требование языка, который оценивается в реальном времени и способен "eval", динамические модификации и REPL.

Прототипическое наследование может быть понято по сравнению с объектно-ориентированным программированием на основе определений класса "живого" времени выполнения вместо статических предопределенных.

Изменить: также полезно другое объяснение, украденное из следующей ссылки. На объектно-ориентированном языке (класс → объект/экземпляр) все возможные свойства любого данного X перечисляются в классе X, а экземпляр заполняет свои собственные значения для каждого из них. В прототипическом наследовании вы описываете только различия между существующей ссылкой на живой X и похожими, но разными живыми Y, и нет мастер-копии.

http://web.media.mit.edu/~lieber/Lieberary/OOP/Delegation/Delegation.html

Сначала вам нужно понять контекст. JavaScript - это интерпретируемый язык, который выполняется и может быть изменен в живой среде. Сама внутренняя структура программы может быть изменена во время выполнения. Это создает различные ограничения и преимущества любого компилируемого языка или даже связанного с CLR языка, такого как .Net.

Понятие "eval" /REPL требует набора динамической переменной. Вы не можете эффективно жить - редактировать среду, где у вас должны быть предопределенные монолитные структуры наследования на основе классов. Это бессмысленно, вы можете просто прекомпилировать сборку или байт-код.

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

С этой стратегией JavaScript в основном банки на все, что "живет". Ничто не запрещено, нет "определенных и совершенных" классов, которые вы никогда не сможете коснуться. Там нет "Одних истинных шотландцев" среди переменных, которые более святы, чем ваш код, потому что все подчиняется тем же правилам, что и код, который вы решили написать сегодня.

Последствия этого выражены, а также очень человеческие. Это подталкивает разработчиков языка к использованию легкого и эффективного касания в предоставлении собственных объектов. Если они делают плохую работу, они просто узурпируют платформу и перестраивают свои собственные (читайте источник MooTools, это буквально переопределяет/восстанавливает все, начиная с Function и Object). Вот как совместимость обеспечивается на платформах, таких как старые версии Internet Explorer. Он продвигает библиотеки, которые мелкие и узкие, плотно функциональные. Глубокое наследование приводит к тому, что наиболее используемые части (легко) вишня выбраны и становятся окончательной библиотекой. Широкие библиотеки приводят к разрыву, когда люди выбирают, какие предметы им нужны, потому что укусить легко, а не невозможно, как в большинстве других сред.

Концепция микро-библиотек однозначно расцветает в JavaScript, и это абсолютно можно проследить до основ языка. Это поощряет эффективность и краткость с точки зрения потребления человеком способами, которые не продвигают ни один другой язык (который я знаю).

Ответ 5

Первый способ, который вы даете, быстрее, и он действительно начинает иметь смысл, когда вы пишете его в другом порядке:

//Guitar function constructor
function Guitar(color, strings) {
  this.color = color;
  this.strings = strings;
}

Guitar.prototype.play = function (chord) {
  alert('Playing chord: ' + chord);
};

var myGuitar = new Guitar('Black', ['D', 'A', 'D', 'F', 'A', 'E']);

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

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


И, возможно, эта альтернативная версия имеет для вас еще больше смысла:

function Guitar(){
  // constructor
}

Guitar.prototype = {
  play: function(a){
    alert(a);
  },

  stop: function(){
    alert('stop it');
  }
};

Ответ 6

С одной стороны, вы можете использовать прототип для расширения объектов, встроенных в язык JavaScript (например, String). Я предпочитаю второй пример для пользовательских объектов.

Ответ 7

Javascript основан на прототипах. Когда в Риме, как римляне делают это, когда в JS, используйте прототипное наследование.

Это более эффективно, потому что метод наследуется на каждом объекте. Если это не прототипный метод, каждый экземпляр этого объекта будет иметь свой собственный метод play. Зачем идти неэффективным маршрутом и нетрадиционным для маршрута JS, когда мы можем перейти на эффективный и естественный маршрут JS?

Ответ 8

У вас уже есть много хороших ответов, поэтому я не прохожу через все ваши очки.

Зачем объявлять экземпляр и потом добавлять методы позже?

Это неверно. Объект прототипа существует независимо от каждого экземпляра. Это свойство объекта функции (функция-конструктор).
Когда вы создаете новый экземпляр, он "наследует" все свойства от прототипа (на самом деле он ссылается на него).

На самом деле имеет смысл, если вы думаете об объектах и ​​ссылках: лучше (по памяти) делиться одной ссылкой на объект, чем каждый экземпляр, имеющий свою собственную копию объекта (объектом в этом случае будет функция play).

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

Ответ 9

Он основан на шаблоне проектирования прототипа Prototype. Эта ссылка на Википедию имеет приятное обсуждение.

http://en.wikipedia.org/wiki/Prototype_pattern

Ответ 10

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

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

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