Pull to refresh

Магия JavaScript: arguments

JavaScript *
arguments — очень специфическая штука, о которой новички и даже любители знают только то, что это «вроде массив, но какой-то неправильный». На самом деле, у него есть ряд интересных особенностей. Предлагаю в топике пофантазировать на тему TypeHinting, аргументов по-умолчанию и всякого другого.
(function (foo, bar) {
	console.log(typeof arguments); // ?
	
	arguments[0] = 42;
	console.log(foo); // ?
})(10, 20);


А также покажу интересную идею-библиотеку
function test (foo, bar) {
    Args(arguments).defaults(100, 100);

    return [foo, bar];
};

test(      ); // 100, 100
test(15    ); //  15, 100
test(21, 42); //  21,  42



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

Что такое arguments


Это хэш. Обычный хэш, как var object = {}
(function () { console.log(
	typeof arguments, // object
	Object.getPrototypeOf(arguments) == Object.prototype // true
) })();


Сделать из него массив просто:
var array = Array.prototype.slice.call(arguments, 0);
// или покороче, но менее производительно:
var array = [].slice.call(arguments, 0);


Мы вызываем метод slice прототипа Array от лица arguments.

Что есть в arguments


arguments.length — количество аргументов, переданных в функцию.
var count = function () {
	console.log(arguments.length);
};

count(); // 0
count(first, second); // 2


Не забывайте, что у каждой функции тоже есть свойство length, которое указывает на то, сколько элементов объявлено в её заголовке:

function one   (foo) {};
function three (foo, bar, qux) {};

console.log(  one.length); // 1
console.log(three.length); // 3


arguments.callee — ссылка на саму функцию.

function foo () {
	console.log(arguments.callee === foo); // true
}


Таким образом можно проверить, передано ли правильное количество элементов, или нет:

function test (foo, bar, qux) {
	return arguments.callee.length === arguments.length;
}

test(1); // false
test(1,2,3); // true


Аргументы в arguments


В arguments содержится также список переданных аргументов.
function test (foo, bar) {
	console.log(foo, bar); // 'a', 'b'
	console.log(arguments[0], arguments[1]); // 'a', 'b'
}
test('a', 'b');


Теперь к интересному. Многие не знают, что объект arguments — содержит на самом деле ссылки, а не значения, и тесно связан с аргументами:
(function (foo) {
	arguments[0] = 42;
	console.log(foo); // 42!
	
	foo = 20;
	console.log(arguments[0]); // 20
})(5);


При этом связь достаточно крепкая:

function foo (qux) {
	change(arguments);
	return qux;
};

function change(a) {
	a[0] = 42;
}

foo(10); // 42


Что из этого можно получить?


Во многих языках программирования есть «переменные по-умолчанию». К примеру, php:
function ($foo = 30, $bar = 'test') {
	var_dump($foo);
	var_dump($bar);
}


В javascript оно будет выглядеть как-то так:
function (foo, bar) {
	if (typeof foo === 'undefined') foo = 30;
	if (typeof bar === 'undefined') bar = 'test';
	
	console.log(foo, bar);
}


Зная особенности arguments можно создать красивый интерфейс:
function test(foo, bar) {
	Args(arguments).defaults(30, 'test');
	
	console.log(foo, bar)
}

test(); // 30, 'test'


С помощью такого кода:

function Args (args) {
	if (this instanceof Args) {
		this.args = args;
	} else {
		// Если создано не через new, а просто вызвана функция, создаем и возвращаем новый объект
		return new Args(args);
	}
};
Args.prototype = {
	defaults: function () {
		var defaults = arguments;
		for (var i = defaults.length; i--;) {
			if (typeof args[i] === 'undefined') args[i] = defaults[i];
		}
		return this;
	}
};


Аналогично можно сделать автоматическое приведение типов:

function test(foo) {
	Args(arguments)
		.defaults(10)
		.cast(Number);
	
	console.log(foo)
}

test('0100'); // 100


Или Type Hinting:

function test(foo, bar) {
	Args(arguments).types(Foo, Bar);
	
	// code
}

test(new Foo(), new Bar());
test(1, 2); // Error


Из интересных идей — сообщение, что все аргументы обязательны:

function test (foo, bar, qux) {
	Args(arguments).allRequired();
}

test(1,2,3); // success
test(1,2); // Error: 3 args required, 2 given


Заключение


Все эти идеи и возможности (и даже больше) я оформил в библиотеку — Args.js.
Согласен, что кое-какие вещи (как TypeHinting) не совсем подходят к идеологии языка. В то же время например defaults — очень удобная штука, имхо.
Пока что это прототип и, перед тем как вы будете его использовать — будьте уверены, что оно вам действительно нужно, а не что вы просто стараетесь из прекрасного языка сделать что-то похожее на C#.
Предлагаю обсудить, покритиковать код, найти пару багов и закоммитить несколько строк кода)

Args.js



К сожалению, из-за бага в трёх популярных браузерах(IE, Fx, Opera) я не смог добиться желаемого эффекта, полноценно самое вкусное заработало только в Chrome (ну по крайней мере в node.js работать будет)). Надеюсь, решим эту проблему вместе.

UPD: В комментах выяснили, что таки это бага Хрома, но, зато, какая приятная! Спасибо jamayka
Tags:
Hubs:
Total votes 99: ↑93 and ↓6 +87
Views 62K
Comments Comments 37