Массив в JavaScript: slice против splice

В JavaScript использование slice вместо splice, и наоборот, является распространённой ошибкой среди новичков и даже профессионалов. И хотя оба метода очень похожи по названию, они выполняют два абсолютно разных действия. На практике этой ошибки можно избежать, если подобрать API, который будет анализировать правильность записи функции.

Согласно спецификации ECMAScript 5.1 раздел 15.4.4.10, метод slice для массивов имеет много общего с аналогичным методом slice для строк. В данном документе указано, что slice принимает два аргумента, начальный и конечный. Далее он возвращает новый массив, содержащий элементы начиная с указанного начального индекса вплоть до элемента, расположенного сразу перед конечным индексом. Для понимания, приведём простой пример того, что делает slice:

'abc'.slice(1,2)           // "b"
[14, 3, 77].slice(1, 2)    //  [3]

Важным качеством метода slice является то, что он не изменяет массив, который использует. Следующий фрагмент кода иллюстрирует его поведение. Как видите, в массиве x элементы сохранены, а в y помещены полученные после обработки массива x методом slice.

var x = [14, 3, 77];
var y = x.slice(1, 2);
console.log(x);          // [14, 3, 77]
console.log(y);          // [3]

Согласно той же спецификации, раздел 15.4.4.12, метод splice также принимает, как минимум, два аргумента, однако, его предназначение абсолютно иное:

[14, 3, 77].slice(1, 2)     //  [3]
[14, 3, 77].splice(1, 2)    //  [3, 77]

Вдобавок к этому, splice модифицирует массив, для которого он вызван. В этом, впрочем, нет ничего удивительного, ведь имя splice подразумевает данное поведение («splice» — «наращивать, сращивать», прим. переводчика).

var x = [14, 3, 77]
var y = x.splice(1, 2)
console.log(x)           // [14]
console.log(y)           // [3, 77]

Создавая свой программный модуль, важно выбрать API, который минимизирует путаницу со slice и splice. В идеале, пользователь вашего модуля должен иметь возможность определиться, какой из этих методов ему нужен, без дополнительного чтения документации. Какие условные обозначения стоит выбрать?

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

var p = new Point(100, 75);
p.translate(25, 25); //переместить
console.log(p);       // { x: 125, y: 100 }

var q = new Point(200, 100);
var s = q.translated(10, 50); //перемещённая
console.log(q);       // { x: 200, y: 100 }
console.log(s);       // { x: 210, y: 150 }

Обратите внимание на разницу между методом translate(), который служит для перемещения точки в двухмерной системе декартовых координат, и методом translated(), который служит лишь для создания преобразованной версии. Объект-точка p изменился, потому что для него был использован translate. В то же время, объект q остаётся неизменным, так как translated() его не модифицирует, а лишь возвращает новый вариант в виде нового объекта s.

Если использовать такие условные обозначения последовательно по всему приложению, вероятность путаницы существенно снизится. И в один прекрасный день ваши пользователи смогут с уверенностью заявить: «Вот теперь всё понятно».