[Перевод] Вы правда знаете о том, что такое массивы?
Там, где я тружусь, от веб-разработчиков ожидают знания PHP и JavaScript. Я, проводя собеседования, обнаружил, что достаточно задать всего один простой вопрос для того чтобы узнать о том, насколько глубоко разработчик понимает инструменты, которыми пользуется каждый день. Вот этот вопрос:
Каковы сходства и различия массивов в JavaScript и в PHP?
Одно дело — умение писать код. И совершенно другое — понимание внутренних механизмов используемых языков.
Ответ на этот единственный вопрос даёт мне целое море сведений о собеседуемом. Ведь почти в каждом распространённом языке есть массивы. Легко выдвинуть предположение, в соответствии с которым массивы в разных языках — это, более или менее, одно и то же. Многие программисты так и делают.
Это — некорректное предположение, ведущее к множеству мелких ошибок, к написанию нерационально устроенного кода, к невозможности эффективно пользоваться сильными сторонами языка.
Массивы и их родной язык — C
Язык C — это не первый в истории язык программирования, но это — язык, который сильнее других повлиял на IT-индустрию. Многие разработчики учили в институтах C в качестве первого языка. И PHP, и JavaScript что-то взяли от C. В результате можно наблюдать некоторое сходство между этими языками и C, и именно анализ массивов в C позволит показать то, как далеко эти структуры данных продвинулись с 1972 года.
В C массивы строго типизированы и имеют фиксированную длину.
int myArray[10];
int fibonacci[10] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34};
Выше показана пара объявлений массивов. Они могут хранить только целые числа, количество которых не превышает 10.
При работе с подобными массивами используется цикл for
. Этот паттерн, без наличия реальной необходимости, копируется во многих других языках программирования:
int i, sum;
for (i = 0; i < 9; i++) {
sum += fibonacci[i];
}
Подобная конструкция не выглядит дикой ни в JavaScript, ни в PHP. Но именно здесь и кроется опасность.
Массивы в JavaScript
Можно представить себе, что массивы в JavaScript очень похожи на массивы в C. И правда — в JS совершенно нормально смотрятся следующие конструкции:
let myArray = [];
let fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];
Однако массивы в JavaScript и в C — это разные вещи. Например, следующее, совершенно очевидно, в C невозможно:
myArray[0] = 5;
myArray[1] = 5.5;
myArray[2] = 'cat';
myArray[3] = [1,2,3];
myArray[4] = (a,b) => {a+b};
myArray[1000] = 'mind blown';
// myArray = [5, 5.5, 'cat', [1,2,3], (a,b) => {a+b}];
В JavaScript массивы имеют переменную длину. Тип их содержимого не контролируется — точно так же, как и тип обычных переменных. Язык берёт на себя управление памятью, в результате длина массива способна увеличиваться или уменьшаться, а разработчик может об этом не задумываться. JavaScript-массивы, на самом деле, очень похожи на списки.
Перебор массива можно организовать, пользуясь неудачным способом, позаимствованным из C:
let sum = 0;
for (i = 0; i < fibonacci.length; i++) {
sum += fibonacci[i];
}
Однако у нас нет необходимости в использовании такого подхода к перебору JS-массивов. Например, тут имеются ненужные промежуточные переменные. В такой конструкции вполне могут возникать ошибки, причиной которых являются неопределённые или некорректные значения. Есть ли какое-то значение в элементе массива fibonacci[10]
? А если значение там есть — является ли оно целым числом?
Но в JavaScript имеются гораздо более совершенные механизмы для работы с массивами. Массивы в JS — это не просто некие простейшие структуры данных. Они, как и функции, являются объектами первого класса. У них есть методы, позволяющие адекватно решать различные задачи:
let sum = fibonacci
.filter(Number.isInteger)
.reduce(
(x,y) => {return x+y}, 0
);
Это гораздо лучше, чем перебирать массив с помощью цикла for
.
Некоторые методы массивов
Кроме того, как уже говорилось, длина массивов в JS, в отличие от длины С-массивов, не фиксирована. Это позволяет оказывать на массивы довольно интересные воздействия, влияющие на их длину. Так, можно, пользуясь методом pop
, извлечь из массива последний элемент. А метод push
позволяет добавить новый элемент в конец массива. Метод unshift
позволяет добавить элемент в начало массива. А метод shift
— извлечь первый элемент массива. Используя разные комбинации этих методов, с массивами можно работать как со стеками или очередями. Тут всё зависит от потребностей программиста.
Массивы в PHP
Массивы в PHP почти похожи на JavaScript-массивы.
Они, как и JS-массивы, отличаются переменной длиной и слабой типизацией. Поэтому может возникнуть соблазн решить, что массивы в PHP и в JS — это одно и то же.
$myArray = [];
$fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];
$myArray[0] = 5;
$myArray[1] = 5.5;
$myArray[2] = 'cat';
$myArray[3] = [1,2,3];
$myArray[4] = function($a, $b) { return $a + $b; };
Лямбда-функции в PHP не так красивы, как похожие функции в JS (в ES6), но этот пример, написанный на PHP, функционально эквивалентен ранее рассмотренному JS-примеру.
Здесь можно использовать и аналоги вышеописанных функций для добавления элементов в массив и извлечения их из него (array_push
, array_pop
, array_shift
, array_unshift
).
Но на JavaScript (как и на C) нельзя написать нечто подобное следующему (написать похожий код на JavaScript, конечно, можно, но работать это будет не так, как в PHP):
$myArray['banana'] = 'yellow fruit';
$myArray[5] = 'is alive';
$myArray[0.02] = 'the 2%';
В PHP, с технической точки зрения, массивы — это хэш-таблицы или словари. В них используются пары вида ключ/значение. Ключи могут быть любыми примитивными значениями: целыми числами, числами с плавающей запятой, строками. Так как в основе PHP-массивов лежат словари, поиск значений по ключу в этих массивах отличается чрезвычайной эффективностью. А именно, временная сложность поиска составляет O(1)
.
Это означает, что PHP-массивы могут с успехом выполнять роль простых поисковых таблиц:
$colours = [
'red' => '#FF0000',
'green' => '#00FF00',
'blue' => '#0000FF',
'orange' => '#FF6600',
];
PHP-массивы дают разработчику множество гибких возможностей. Эти массивы можно сортировать по ключу и по значению. Можно, например, «перевернуть» массив с помощью array_flip
, поменяв местами ключи и значения, что даёт возможность весьма эффективно организовывать поиск в массиве нужных данных.
Поиск конкретного значения в обычном массиве имеет временную сложность O(n)
, так как в ходе поиска нужно проверить каждое значение, хранящееся в массиве. А в PHP легко сделать так, чтобы временная сложность такой же операции составила бы O(1)
:
$users = [
1 => 'Andi',
2 => 'Benny',
3 => 'Cara',
4 => 'Danny',
5 => 'Emily',
];
$lookupTable = array_flip($users);
return $lookupTable['Benny'];
Конечно, что-то подобное доступно и в JavaScript, хотя тут уже надо будет прибегнуть к возможностям объектов. Но из-за этого придётся пойти на некоторые компромиссы. А именно, при работе с объектами в распоряжении разработчика не будет методов массивов вроде тех, о которых мы говорили выше.
Если продолжить разговор о PHP-массивах, то можно сказать, что их перебор организован просто и безопасно. Здесь есть возможность применить цикл for
, напоминающий такой же цикл из C, но, прежде чем это сделать, стоит как следует подумать о том, зачем поступать именно так. PHP, благодаря циклам foreach
, позволяет решать проблемы, характерные для массивов переменной длины, способных содержать значения разных типов:
$sum = 0;
foreach ($myArray as $key => $value) {
$sum += is_numeric($value) ? $value : 0;
}
В цикле даётся доступ и к ключам, и к значениям, что позволяет программисту работать и с тем, и с другим.
Стоит отметить, что PHP-массивы отличаются от JS-массивов тем, что в PHP для выполнения некоторых операций с массивами приходится пользоваться внешними по отношению к ним функциями:
$sum =
array_reduce(
array_filter($fibonacci, 'is_numeric'),
function ($x, $y) { return $x + $y; },
0
};
Это — функционально, но не так красиво, как в JavaScript. Если вы хотите писать код для работы с PHP-массивами, который напоминает код, используемый в JavaScript (существуют сильные аргументы в пользу такого подхода), то вам, возможно, стоит взглянуть на специализированное решение. Скажем — на класс Collection
из фреймворка Laravel. Однако PHP позволяет создавать объекты, возможности которых напоминают возможности массивов (их, например, можно обрабатывать в циклах foreach
).
Если PHP — это ваш основной язык программирования — вы, привыкнув к нему, вполне можете забыть о той мощи, которая таится в его фундаментальных механизмах.
PHP-массивы — это, в двух словах, самая недооценённая и самая незаметная возможность языка, которая, если ей правильно пользоваться, способна принести огромную пользу.
Итоги: вопрос и ответ
Вопрос: Каковы сходства и различия массивов в JavaScript и в PHP?
Ответ: в PHP и JavaScript массивы — это, по сути, слабо типизированные списки переменной длины. В JavaScript ключами элементов массивов являются упорядоченные целые числа. В PHP массивы можно сравнить и со списками, которые поддерживают сортировку, и со словарями, в которых удобно осуществлять поиск элементов по ключу. Ключи PHP-массивов могут быть любыми значениями примитивных типов, а сортировать такие массивы можно по ключам или по значениям.
Уважаемые читатели! Как вы думаете, каких стандартных возможностей больше всего не хватает JavaScript-массивам?