PHP

От веб-разработчика на собеседовании требуется продемонстрировать хорошие знания PHP и Javascript. Всего один простой вопрос помогает выявить, действительно ли разбирается человек в том инструментарии, который использует в своей каждодневной работе:

«Каковы сходства и различия между массивами на JavaScript и PHP?»

Одно дело — использовать массивы для сокращения кода, другое — полностью понимать при этом основы того языка, с которым работаешь.

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

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

Массивы в родном языке — языке C

Язык C не самый первый из языков программирования, но именно он оказывает самое большое влияние. Для многих разработчиков C был первым их языком в университете. PHP и JavaScript произошли отчасти именно из этого языка, поэтому есть в них некое сходство, позволяющее показать, насколько далеко массивы ушли вперёд с 1972 года.

В языке C массивы строго типизированы и имеют фиксированную длину.

int myArray[10];
int fibonacci[10] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34};

Вот массив. Он может содержать не больше десяти элементов и только целочисленные значения.

Как им пользоваться? Выполняем итеративный обход с помощью цикла for. Сие действо воспроизводится зачастую безо всякой необходимости во многих языках программирования:

int i, sum;
for (i = 0; i < 9; i++) {
  sum += fibonacci[i];
}

Вот и разработчику Javascript или PHP это знакомо. И в этом кроется опасность.

Массивы на JavaScript

Многие думают, что массивы на JavaScript такие же, как на С:

let myArray = [];
let fibonacci = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];

Но это не так. Вот как работают массивы на JavaScript (очевидно, что на 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. Управление памятью здесь возложено на язык JavaScript, поэтому массив может без проблем увеличиваться и сжиматься. На JavaScript массив — это самый настоящий список.

Итеративный обход массива выполняется кое-как (или на примитивном уровне):

let sum = 0;
for (i = 0; i < fibonacci.length; i++) {
  sum += fibonacci[i];
}

Причины: наличие ненужных промежуточных переменных, а также возможность ошибок из-за неопределённых или неверных значений. Это ещё вопрос: есть ли в fibonacci[10] какое-то определённое значение? А если есть, то кто сказал, что это целое число?

Но на JavaScript массивы — это не просто массивы. Это первоклассные объекты, подобные функциям. С такими задачами, как итеративный обход, здесь прекрасно справляются методы класса:

let sum = fibonacci
   .filter(Number.isInteger)
   .reduce(
      (x,y) => {return x+y}, 0
    );

Так-то лучше!

Кроме того, массивы на JavaScript, в отличие от массивов на C, не фиксированы по длине: возможности их роста ограничиваются лишь вашей фантазией. Можно удалить последний элемент массива с помощью pop и добавить в конец массива другой элемент, используя push. Можно удалить элементы из начала с помощью shifting или добавить их, используя unshifting. Можно использовать какую-то другую комбинацию действий, обращаясь с массивом на JavaScript, как со стеком или очередью, в зависимости от конкретного применения.

Массивы на PHP

Слон

Массивы на PHP выглядят почти так же, как на JavaScript: у них переменная длина и они слабо типизированы. У вас даже может возникнуть соблазн посчитать их одинаковыми.

$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; };

Лямбда-функции не такие элегантные, как в ES6 на JavaScript, но по функциональности не уступают тем, что уже были приведены в статье ранее.

Здесь так же можно использовать push и pop для добавления и удаления элементов (array_push, array_pop), как и shift и unshift (array_shift, array_unshift).

Словарь

Но на C так делать уже нельзя:

$myArray['banana'] = 'yellow fruit';
$myArray[5] = 'is alive';
$myArray[0.02] = 'the 2%';

На PHP массивы, по сути, представляют собой хеш-таблицы или словари. Для хранения значений они используют ключи, в роли которых могут выступать и целые числа, и числа с плавающей запятой, и строки. Поскольку это словарь, поиск значений по ключам здесь проходит максимально эффективно — за время O(1).

То есть массивы в PHP могут использоваться как таблицы поиска:

$colours = [
  'red' => '#FF0000',
  'green' => '#00FF00',
  'blue' => '#0000FF',
  'orange' => '#FF6600',
];

При этом пользователи получают максимальную вариативность применения: массивы на PHP можно отсортировать по ключам и по значениям, их можно транспонировать, переворачивая ключи и значения и повышая таким образом эффективность поиска.

Вообще на поиск конкретного значения в обычном неотсортированном массиве уходит 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;
}

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

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

$sum = 
  array_reduce(
    array_filter($fibonacci, 'is_numeric'),
    function ($x, $y) { return $x + $y; },
    0
  };

Задействовать эти функции — это функционально, извините за каламбур, но не так элегантно. Если вам нравится такой код (и у вас есть причины его использовать), можете обратиться к какой-нибудь библиотеке (коллекциям Laravel, например). На PHP есть возможность создавать объекты, работающие так же, как массивы (которые можно использовать, скажем, в цикле foreach).

Работая с таким языком программирования, как PHP, часто забываешь, сколько мощи заложено в одной из базовых конструкций языка — массиве.

А в двух словах можно сказать, что массивы на PHP — это такой спящий гигант со сверхвозможностями, порой скрытыми и недооценёнными.

Подводим итог

Итак, на вопрос «каковы сходства и различия между массивами на JavaScript и PHP?» есть правильный ответ:

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

Читайте также:


Перевод статьи Haoran Un: My Favourite Web Developer Interview Question: Do you really understand arrays?