Как примечание перед началом - я использую здесь ECMAScript вместо JavaScript, поскольку ActionScript 1 и 2 демонстрируют точно такое же поведение во время выполнения.
Те из нас, кто работает в более «традиционном» объектно-ориентированном мире (читайте Java / C # / PHP), считают идею расширения класса во время выполнения почти полностью чужой. Я имею в виду, серьезно, это должен быть мой ОБЪЕКТ. Мой ОБЪЕКТ будет идти вперед и делать вещи, которые были поставлены перед Детские классы EXTEND Other CLASSES . У него очень структурированное, прочное, каменное чувство. И, по большей части, это работает, и это работает достаточно хорошо. (И это одна из причин, по которой Гослинг спорил, и я думаю, что большинство из нас согласились бы довольно эффективно, что оно так хорошо подходит для массивных систем)
ECMAScript, с другой стороны, следует гораздо более примитивной концепции ООП. В ECMAScript наследование классов - это, поверьте или нет, гигантский шаблон декоратора. Но это не просто шаблон декоратора, который, как вы могли бы сказать, присутствует в C ++ и 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
, основополагающую работу в Lambda Calculus. В ней он предлагает два способа взглянуть на функции с несколькими аргументами. Во-первых, их можно рассматривать как функции, которые принимают синглетоны, кортежи, тройки и т. Д. В основном f (x, y, z) следует понимать как f, которая принимает параметр (x, y, z). (Кстати, по моему скромному мнению, это является основным стимулом для структуры списков аргументов Python, но это гипотеза).
Другое (и для наших целей (и, если честно, целей Церкви) более важное) определение было подхвачено Маккарти. Вместо этого f (x, y, z) следует переводить в f (x g (y h (z))). Разрешение самого внешнего метода может исходить из ряда состояний, которые были сгенерированы внутренними вызовами функций. Это сохраненное внутреннее состояние является самой основой замыкания, которое, в свою очередь, является одной из основ современного ООП. Замыкания позволяют передавать закрытые исполняемые состояния между различными точками.
Предоставлена книга «Земля Лиспа»:
; 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 обязательно будет неопределенным!
Поскольку все this
внутри исходного определения будут экземплярами этого объекта, изменяя 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 );