JavaScript с нуля - [44]

Шрифт
Интервал


// и результат…

console.log(shuffleArray);

После выполнения этого кода конечным результатом будет перегруппировка содержимого. Такая функциональность весьма полезна. Я бы даже сказал, что слишком полезна. Возможность производить перемешивание должна быть частью объекта Array и являться легко доступной наряду с такими его методами, как push, pop, slice и др.

Если бы функция shuffle была частью объекта array, то мы могли бы с легкостью использовать ее следующим образом:

let shuffleArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

shuffleArray.shuffle();

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

Поехали!

И снова приветствуем прототип!

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

let tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

Если бы мы построили диаграмму всей иерархии объекта tempArray, то выглядела бы она, как показано на рис. 19.1.

Рис. 19.1. Паутина объектов (или лжи!), которые существуют под поверхностью

Слева у нас объект tempArray, являющийся экземпляром Array.prototype, который, в свою очередь, является экземпляром основного Object.prototype. Теперь нам нужно расширить возможности нашего массива функцией shuffle. Это означает, что нужно найти способ внедрить эту функцию в Array.prototype, как показывает рис. 19.2.

Рис. 19.2. Здесь должна поселиться наша функция shuffle!

Здесь мы сталкиваемся с проявлением пресловутой странности JavaScript. У нас нет доступа к коду, формирующему функциональность массива. Мы также не можем найти функцию или объект, формирующие сам Array, и внедрить shuffle в них, как это делается в случае с пользовательским объектом. Наши встроенные объекты, подобные Array, определены в вулканических глубинах браузера, куда ни одно человеческое существо не может попасть. Поэтому здесь нам нужен иной подход.

При этом другом подходе мы тайком прокрадываемся и прикрепляем нужную функциональность к свойству prototype объекта Array. Выглядит это примерно так:

Array.prototype.shuffle = function () {

let input = this;


for (let i = input.length — 1; i >= 0; i-) {


let randomIndex = Math.floor(Math.random() * (i + 1));

let itemAtIndex = input[randomIndex];


input[randomIndex] = input[i];

input[i] = itemAtIndex;

}

return input;

}

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

function shuffle(input) {

.

.

.

.

.

}

Вместо этого, так как отныне функция является частью Array, на этот массив указывает ключевое слово this внутри ее тела:

Array.prototype.shuffle = function () {

let input = this;

.

.

.

.

}

Возвращаясь к предыдущему шагу, как только этот код будет запущен, функция shuffle окажется бок о бок со встроенными методами, которые объект Array выражает через Array.prototype, как показано на рис. 19.3.

Рис. 19.3. Великий успех! Теперь функция shuffle на своем месте

С этого момента, если нам понадобится обратиться к возможностям shuffle, мы можем использовать для этого изначально желаемый подход:

let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

numbers.shuffle();

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

Спорность расширения встроенных объектов

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

Вы не контролируете будущее встроенного объекта

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

Некоторую функциональность не следует расширять или переопределять

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

Array.prototype.slice = function () {

let input = this;

input[0] = "This is an awesome example!";


return input;

}


let tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] tempArray.slice();


// и результат будет…

console.log(tempArray);

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

Что почитать

Подробное обсуждение этого противоречия ищите на ветке StackOverflow: http://stackoverflow.com/questions/8859828/.


Рекомендуем почитать
Pro Git

Разработчику часто требуется много сторонних инструментов, чтобы создавать и поддерживать проект. Система Git — один из таких инструментов и используется для контроля промежуточных версий вашего приложения, позволяя вам исправлять ошибки, откатывать к старой версии, разрабатывать проект в команде и сливать его потом. В книге вы узнаете об основах работы с Git: установка, ключевые команды, gitHub и многое другое.В книге рассматриваются следующие темы:основы Git;ветвление в Git;Git на сервере;распределённый Git;GitHub;инструменты Git;настройка Git;Git и другие системы контроля версий.


Java 7

Рассмотрено все необходимое для разработки, компиляции, отладки и запуска приложений Java. Изложены практические приемы использования как традиционных, так и новейших конструкций объектно-ориентированного языка Java, графической библиотеки классов Swing, расширенной библиотеки Java 2D, работа со звуком, печать, способы русификации программ. Приведено полное описание нововведений Java SE 7: двоичная запись чисел, строковые варианты разветвлений, "ромбовидный оператор", NIO2, новые средства многопоточности и др.


MFC и OpenGL

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


Симуляция частичной специализации

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


Обработка событий в С++

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


Питон — модули, пакеты, классы, экземпляры

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