18 KiB
Псевдомассив аргументов "arguments"
В JavaScript любая функция может быть вызвана с произвольным количеством аргументов.
[cut] Например:
//+ run
function go(a,b) {
alert("a="+a+", b="+b);
}
go(1); // a=1, b=undefined
go(1,2); // a=1, b=2
go(1,2,3); // a=1, b=2, третий аргумент не вызовет ошибку
[smart header="В JavaScript нет "перегрузки" функций"]
В некоторых языках программист может создать две функции с одинаковым именем, но разным набором аргументов, а при вызове интерпретатор сам выберет нужную:
function log(a) {
...
}
function log(a,b,c) {
...
}
*!*
log(a); // вызовется первая функция
log(a,b,c); // вызовется вторая функция
*/!*
Это называется "полиморфизмом функций" или "перегрузкой функций". В JavaScript ничего подобного нет.
Может быть только одна функция с именем log
, которая вызывается с любыми аргументами.
А уже внутри она может посмотреть, с чем вызвана и по-разному отработать.
В примере выше второе объявление log
просто переопределит первое.
[/smart]
Доступ к "лишним" аргументам
Как получить значения аргументов, которых нет в списке параметров?
Доступ к ним осуществляется через "псевдо-массив" arguments.
Он содержит список аргументов по номерам: arguments[0]
, arguments[1]
..., а также свойство length
.
Например, выведем список всех аргументов:
//+ run
function sayHi() {
for (var i=0; i<arguments.length; i++) {
alert("Привет, " + arguments[i]);
}
}
sayHi("Винни", "Пятачок"); // 'Привет, Винни', 'Привет, Пятачок'
Все параметры находятся в arguments
, даже если они есть в списке. Код выше сработал бы также, будь функция объявлена sayHi(a,b,c)
.
[warn header="Связь между arguments
и параметрами"]
В старом стандарте JavaScript псевдо-массив arguments
и переменные-параметры ссылаются на одни и те же значения.
В результате изменения arguments
влияют на параметры и наоборот.
Например:
//+ run
function f(x) {
arguments[0] = 5; // меняет переменную x
alert(x); // 5
}
f(1);
Наоборот:
//+ run
function f(x) {
x = 5;
alert(arguments[0]); // 5, обновленный x
}
f(1);
В современной редакции стандарта это поведение изменено. Аргументы отделены от локальных переменных:
//+ run
function f(x) {
"use strict"; // для браузеров с поддержкой строгого режима
arguments[0] = 5;
alert(x); // не 5, а 1! Переменная "отвязана" от arguments
}
f(1);
Если вы не используете строгий режим, то чтобы переменные не менялись "неожиданно", рекомендуется никогда не изменять arguments
.
[/warn]
arguments -- это не массив
Частая ошибка новичков -- попытка применить методы Array
к arguments
. Это невозможно:
//+ run
function sayHi() {
var a = arguments.shift(); // ошибка! нет такого метода!
}
sayHi(1);
Дело в том, что arguments
-- это не массив Array
.
В действительности, это обычный объект, просто ключи числовые и есть length
. На этом сходство заканчивается. Никаких особых методов у него нет, и методы массивов он тоже не поддерживает.
Впрочем, никто не мешает сделать обычный массив из arguments
, например так:
//+ run
var args = [];
for(var i=0; i<arguments.length; i++) {
args[i] = arguments[i];
}
Такие объекты иногда называют "коллекциями" или "псевдомассивами".
Пример: копирование свойств copy(dst, src1, src2...) [#copy]
Иногда встаёт задача -- скопировать в существующий объект свойства из одного или нескольких других.
Напишем для этого функцию copy
. Она будет работать с любым числом аргументов, благодаря использованию arguments
.
Синтаксис:
- copy(dst, src1, src2...)
- Копирует свойства из объектов `src1, src2,...` в объект `dst`. Возвращает получившийся объект.
Использование:
- Для объединения нескольких объектов в один:
//+ run var vasya = { age: 21, name: 'Вася', surname: 'Петров' }; var user = { isAdmin: false, isEmailConfirmed: true }; var student = { university: 'My university' }; // добавить к vasya свойства из user и student *!* copy(vasya, user, student); */!* alert(vasya.isAdmin); // false alert(vasya.university); // My university
- Для создания копии объекта `user`:
// скопирует все свойства в пустой объект var userClone = copy({}, user);
Такой "клон" объекта может пригодиться там, где мы хотим изменять его свойства, при этом не трогая исходный объект
user
.В нашей реализации мы будем копировать только свойства первого уровня, то есть вложенные объекты как-то особым образом не обрабатываются. Впрочем, её можно расширить.
А вот и реализация:
//+ autorun
function copy() {
var dst = arguments[0];
for (var i=1; i<arguments.length; i++) {
var arg = arguments[i];
for (var key in arg) {
dst[key] = arg[key];
}
}
return dst;
}
Здесь первый аргумент copy
-- это объект, в который нужно копировать, он назван dst
. Для упрощения доступа к нему можно указать его прямо в объявлении функции:
*!*
function copy(dst) {
*/!*
// остальные аргументы остаются безымянными
for (var i=1; i<arguments.length; i++) {
var arg = arguments[i];
for (var key in arg) {
dst[key] = arg[key];
}
}
return dst;
}
Аргументы по умолчанию через ||
Если функция вызвана с меньшим количеством аргументов, чем указано, то отсутствующие аргументы считаются равными undefined
.
Зачастую в случае отсутствия аргумента мы хотим присвоить ему некоторое "стандартное" значение или, иначе говоря, значение "по умолчанию". Это можно удобно сделать при помощи оператора логическое ИЛИ ||
.
Например, функция showWarning
, описанная ниже, должна показывать предупреждение. Для этого она принимает ширину width
, высоту height
, заголовок title
и содержимое contents
, но большая часть этих аргументов необязательна:
function showWarning(width, height, title, contents) {
width = width || 200; // если не указана width, то width = 200
height = height || 100; // если нет height, то height = 100
title = title || "Предупреждение";
//...
}
Это отлично работает в тех ситуациях, когда "нормальное" значение параметра в логическом контексте отлично от false
. В коде выше, при передаче width = 0
или width = null
, оператор ИЛИ заменит его на значение по умолчанию.
А что, если мы хотим использовать значение по умолчанию только если width === undefined
? В этом случае оператор ИЛИ уже не подойдёт, нужно поставить явную проверку:
function showWarning(width, height, title, contents) {
if (width !== undefined) width = 200;
if (height !== undefined) height = 100;
if (title !== undefined) title = "Предупреждение";
//...
}
"Именованные аргументы"
Именованные аргументы -- альтернативная техника работы с аргументами, которая вообще не использует arguments
.
Некоторые языки программирования позволяют передать параметры как-то так: f(width=100, height=200)
, то есть по именам, а что не передано, тех аргументов нет. Это очень удобно в тех случаях, когда аргументов много, сложно запомнить их порядок и большинство вообще не надо передавать, по умолчанию подойдёт.
Такая ситуация часто встречается в компонентах интерфейса. Например, у "меню" может быть масса настроек отображения, которые можно "подкрутить" но обычно нужно передать всего один-два главных параметра, а остальные возьмутся по умолчанию.
В JavaScript для этих целей используется передача аргументов в виде объекта, а в его свойствах мы передаём параметры.
Получается так:
function showWarning(options) {
var width = options.width || 200; // по умолчанию
var height = options.height || 100;
var title = options.title || "Предупреждение";
// ...
}
showWarning({
Вызвать такую функцию очень легко. Достаточно передать объект аргументов, указав в нем только нужные:
showWarning({
contents: "Вы вызвали функцию" // и всё понятно!
});
Сравним это с передачей аргументов через список:
showWarning(null, null, "Предупреждение!");
// мысль программиста "а что это за null, null в начале? ох, надо глядеть описание функции"
Не правда ли, объект -- гораздо проще и понятнее?
Еще один бонус кроме красивой записи -- возможность повторного использования объекта аргументов:
var opts = {
width: 400,
height: 200,
contents: "Текст"
};
showWarning(opts);
opts.contents = "Другой текст";
*!*
showWarning(opts); // вызвать с новым текстом, без копирования других аргументов
*/!*
Именованные аргументы применяются во многих JavaScript-фреймворках.
Устаревшее свойство arguments.callee [#arguments-callee]
[warn header="Используйте NFE вместо arguments.callee
"]
Это свойство устарело, при use strict
оно не работает.
Единственная причина, по которой оно тут -- это то, что его можно встретить в старом коде, поэтому о нём желательно знать.
Современная спецификация рекомендует использовать "именованные функциональные выражения (NFE)".
[/warn]
В старом стандарте JavaScript объект arguments
не только хранил список аргументов, но и содержал в свойстве arguments.callee
ссылку на функцию, которая выполняется в данный момент.
Например:
//+ run
function f() {
alert( arguments.callee === f ); // true
}
f();
Эти два примера будут работать одинаково:
// подвызов через NFE
var factorial = function f(n) {
return n==1 ? 1 : n**!*f(n-1)*/!*;
};
// подвызов через arguments.callee
var factorial = function(n) {
return n==1 ? 1 : n**!*arguments.callee(n-1)*/!*;
};
В учебнике мы его использовать не будем, оно приведено для общего ознакомления.
arguments.callee.caller
Устаревшее свойство arguments.callee.caller
хранит ссылку на функцию, которая вызвала данную.
[warn header="Это свойство тоже устарело"]
Это свойство было в старом стандарте, при use strict
оно не работает, как и arguments.callee
.
Также ранее существовало более короткое свойство arguments.caller
. Но это уже раритет, оно даже не кросс-браузерное. А вот свойство arguments.callee.caller
поддерживается везде, если не использован use strict
, поэтому в старом коде оно встречается.
[/warn]
Пример работы:
//+ run
f1();
function f1() {
alert(arguments.callee.caller); // null, меня вызвали из глобального кода
f2();
}
function f2() {
alert(arguments.callee.caller); // f1, функция, из которой меня вызвали
f3();
}
function f3() {
alert(arguments.callee.caller); // f2, функция, из которой меня вызвали
}
В учебнике мы это свойство также не будем использовать.
Итого
- Полный список аргументов, с которыми вызвана функция, доступен через `arguments`.
- Это псевдомассив, то есть объект, который похож на массив, в нём есть нумерованные свойства и `length`, но методов массива у него нет.
- В старом стандарте было свойство `arguments.callee` со ссылкой на текущую функцию, а также свойство `arguments.callee.caller`, содержащее ссылку на функцию, которая вызвала данную. Эти свойства устарели, при `use strict` обращение к ним приведёт к ошибке.
- Для указания аргументов по умолчанию, в тех случаях, когда они заведомо не `false`, удобен оператор `||`.
В тех случаях, когда возможных аргументов много и, в особенности, когда большинство их имеют значения по умолчанию, вместо работы с arguments
организуют передачу данных через объект, который как правило называют options
.
Возможен и гибридный подход, при котором первый аргумент обязателен, а второй -- options
, который содержит всевозможные дополнительные параметры:
function showMessage(text, options) {
// показать сообщение text, настройки показа указаны в options
}