Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Почему методы массива находятся в прототипе, а не в конструкторе? (https://javascript.ru/forum/misc/39824-pochemu-metody-massiva-nakhodyatsya-v-prototipe-ne-v-konstruktore.html)

krantadan 13.07.2013 15:28

Почему методы массива находятся в прототипе, а не в конструкторе?
 
Приветствую)
Почему стандартные объекты в JavaScript (вроде строк или массивов) наследуют свои методы из прототипа, вместо того, чтобы задать их присваиваниями в своём конструкторе?

То есть почему в Array делается так:
function Array() {}
Array.prorotype = {
    push: function (...) {...},
    ...
};


Вместо того, чтобы сделать так:
function Array() {
    this.push = function (...) {...};
    ...
}


Ведь именно конструктор - место для задания полей будущего объекта, а прототип - это способ реализовать наследование. Я хочу сказать, что такая архитектура не типична, и если бы вам нужно было реализовать массивы в C++, то вы бы обошлись одним классом, а не двумя с наследованием.
Так что каждый раз при создании своих объектов приходится решать для себя дилемму: следовать этой странной яваскриптовой традиции или сделать "по-нормальному". Может быть я чего-то не понимаю, хочется понять причины такой вот яваскриптовой традиции.
У меня есть некоторые догадки на этот счёт, но мне бы хотелось узнать ваше мнение)
Вот мои мысли:

1) Расширение функциональности.
Чтобы можно было легко расширить функциональность всех строк.
Кроме того можно легко поменять прототип для всех новых объектов.
Это правда считается дурным тоном, нарушением инкапсуляции и вообще ведёт к конфликтам. Так что в нормальных классовых языках этому вообще нет аналога.

2) Производительность.
Немного ускоряется создание новых объектов, так как нет множества присваиваний в конструкторе. Однако также немного замедляется обращение к методам, так как приходится дольше их искать.

3) Красивая структура.
Чтобы получилась более красивая структура объектов. Если унаследоваться от прототипа, а не от полноценного массива, то потомок не будет засоряться локальными для массива данными вроде length. Хотя обратное вовсе не вредит. Напротив, тогда этот length можно не инициализировать руками в своём потомке.

danik.js 13.07.2013 15:42

Цитата:

Сообщение от krantadan
если бы вам нужно было реализовать массивы в C++, то вы бы обошлись одним классом, а не двумя с наследованием

В C++ свой способ хранения методов и статичных свойств, в javascript - свой. Если для каждого инстанса создавать весь набор методов то объем потребляемой памяти возрастет в разы. В C++ если не ошибаюсь все методы хрянятся отдельно от инстансов. В JS вот тоже самое. И в чем тогда непонимание?

krantadan 13.07.2013 16:18

Вряд ли так уж в разы. Функция - это особый вид объекта. Объекты копируются по ссылке, а не по значению. Значит, чтобы избежать излишнего потребления памяти достаточно вытащить определения методов из конструктора (чтобы эти функции-методы не создавались каждый раз при создании нового объекта).

Было:
function Array() {
    this.push = function (...) {...};
    ...
}


Стало:
function push(...) {...}
function Array() {
    this.push = push;
}


Тогда мы тратим память лишь на хранение ссылок в каждом инстансе, а это совсем не так страшно, так как ссылка мало весит.

nerv_ 13.07.2013 16:40

Цитата:

Сообщение от krantadan
Почему стандартные объекты в JavaScript (вроде строк или массивов) наследуют свои методы из прототипа, вместо того, чтобы задать их присваиваниями в своём конструкторе?

var x = 1 /* и тут ты задаешь присваиванием в своем конструкторе */ + 2 /* тут тоже */ + 3 /* и тут */

т.о. для того, чтобы сложить три числа, тебе надо вручную построить три объекта. Аналогично и со строками. Не бред ли?

var x = 1 + 2 + 3 // ===
var y = 1..valueOf() + 2..valueOf() + 3..valueOf();

alert( x );
alert( y );


хотя, скорее всего, примитивы оборачиватюся в объекты только при необходимости

В целом могу сказать, что: "хотите писать на js, забудьте про классы, привыкайте (наслаждайтесь) прототипами"

Цитата:

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

это встроено в язык - прототипы

krantadan 13.07.2013 17:15

Цитата:

Сообщение от nerv_
т.о. для того, чтобы сложить три числа, тебе надо вручную построить три объекта. Аналогично и со строками. Не бред ли?

Не понял мысль. Числа - элементарный тип. При их сложении вообще не вызываются какие-либо конструкторы.

Цитата:

Сообщение от nerv_
это встроено в язык - прототипы

Нет, не встроено. Прототип - это в первую очередь средство решения архитектурных задач, а не оптимизации.

Например, если вам надо сделать цепочку наследований, такого плана:
{
    a: function () {},
    __proto__: {
        b: function () {},
        __proto__: {
            c: function () {}
        }
    }
}


То вы не сможете решить прототипами задачу оптимизации, так как прототип уже использован под наследование:
function A() { this.a = function () {}; }
A.prototype = new B(); // Сюда не получится запихнуть реализацию метода a.

function B() { this.b = function() {}; }
B.prototype = { c: function () {} };

nerv_ 13.07.2013 18:08

Цитата:

Сообщение от krantadan
Числа - элементарный тип. При их сложении вообще не вызываются какие-либо конструкторы

и при этом эти "элементарные типы" имеют методы и свойства :)

Цитата:

Сообщение от krantadan
То вы не сможете решить прототипами задачу оптимизации

function A() { this.a = A.a  }
A.prototype = new B(); // Сюда не получится запихнуть реализацию метода a.
A.a = function () {};

function B() { this.b = function() {}; }
B.prototype = { c: function () {} };

? (один из вариантов)

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

забыли про
function A() { }
A.prototype = new B(); // Сюда не получится запихнуть реализацию метода a.
A.prototype.a = function () {};

function B() { this.b = function() {}; }
B.prototype = { c: function () {} };

krantadan 13.07.2013 18:46

Цитата:

Сообщение от nerv_
и при этом эти "элементарные типы" имеют методы и свойства

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

В вашем 1-м варианте вы предварительно сохраняете ссылку на метод в свойство конструктора. Я сохраняю в независимую переменную. Куда бы мы её не сохраняли - принцип один и он не имеет отношения к теме наследований.
А во 2-м предложенном варианте вы создадите объект другой структуры:
{ // A
    __proto__: { // B
        a: function () {},
        b: function () {},
        __proto__: { // C
            c: function () {}
        }
    }
}

Что-то я тут расфлудился) Надеюсь, я никого не утомил своими изысканиями.

nerv_ 13.07.2013 22:56

Цитата:

Сообщение от krantadan
В вашем 1-м варианте вы предварительно сохраняете ссылку на метод в свойство конструктора. Я сохраняю в независимую переменную. Куда бы мы её не сохраняли - принцип один и он не имеет отношения к теме наследований.

Цитата:

Сообщение от krantadan
То вы не сможете решить прототипами задачу оптимизации

решил, без помощи прототипов - не создаю функцию каждый раз при создании экземпляра, а использую одну, заранее созданную

Цитата:

Сообщение от krantadan
А во 2-м предложенном варианте вы создадите объект другой структуры:

нет

krantadan 15.07.2013 14:15

Думаю, что я понял почему так сделано)
Дело не в том, что это какой-то идеально-правильный способ реализовывать ООП в яваскрипте. Всё дело в литералах. 'text' - литерал строки, [ 1, 2, 3 ] - литерал массива, /^nya$/ - литерал регулярки и т.д. Ими все пользуются и никто от них не собирается отказываться. Каждому литералу соответствует конструктор (можно сказать класс). Мы не можем заменить этот класс на другой. Поэтому единственная возможность расширить/изменить функционал классов, связанных с литералами - модифицировать сам класс. И вот нам дали эту единственную возможность - средствами языка через прототип. Между прочим та же проблема есть в других языках (например Ruby) и решается она там точно также, через модификацию стандартного класса. А если же мы строим какие-то свои классы (которым не соответствуют никакие литералы языка), то совершенно не обязательно и не нужно в общем случае городить этот дополнительный слой наследований.

Riim 15.07.2013 15:21

Цитата:

Сообщение от krantadan
то совершенно не обязательно и не нужно в общем случае городить этот дополнительный слой наследований

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


Часовой пояс GMT +3, время: 21:22.