В JavaScript във всяка функция има предефинирана променлива arguments. Тя представлява масиво подобна структура съдържаща аргументите подадени към функцията и callee . В поста ще се постарая да обясня какво точно е callee и как би било полезно.
Само да поясня за думата “масиво подобна структура”, преди да продължа нататък. Това значи, че arguments изглежда като масив. Т.е може да се достъпват елементи от него с arguments[n] и има свойството length, което оказва броя на елементите. Но до тук свършват прилики му с масива, в повечето браузъри arguments няма нито един метод. Това е една от главните причина в prototype.js да я има $A функцията.
Така, но да се върнем на arguments.callee, какво точно е това? В callee се записва връзка (или референция, ако това звучи по-добре) към текущата функция, т.е. функция в която е arguments. Това може да се илюстрира със следния пример:
function foo(){
console.log(arguments.callee == foo);
}
foo(); // true
Защо толкова често foo се ползва в примери, някои знае ли ? Даже страница в wikipedia има – foobar.
Два прости примера
// пример едно:
// функция която брои колко колко пъти е била викана.
function example1(){
console.log(++arguments.callee.count);
}
example1.count = 0;
example1(); // 1
example1(); // 2
// и така на татък
// пример две:
// функция, която проверява дали броя на подадените аргументи е толкова колкото се очакват
function example2(a, b){
if (arguments.length == arguments.callee.length){
// тук може и throw "error" да се хвърли, но за примера и това върши работа
console.log(
'expected ' + arguments.callee.length + ' argument(s) given ' + arguments.length
);
}
}
example2(); // грешка
example2(1); // грешка
example2(1, 2); // работи
example2(1, 2 , 3); // грешка</pre>
Във втория пример използвам Functon.length, който указва колко аргумента очаква дадената функция.
Два полезни примера
Като цяло с argument.callee може да се правят доста магии. В практиката повечето случаи, в които съм го ползвал е, когато съм имал анонимна функция и искам да направя нещо със самата функция и ми трябва връзка към нея.
Пример1: Имате event handler при click и искате след първото натискане този handler да се махне.
В допълнение може да се направи при click event handler-ът да се предава от елемент на елемент, но това няма да го пиша, за да не натоварвам примера (ако някои иска може да го напиша допълнително).
// предполагам, че се ползва prototype.js
$('element').observe('click', function(){
alert('this will be shown only one time');
this.stopObserving('click', arguments.callee);
});
Пример2: прост ефект – увеличаване на размерите на квадрат. Използва анонимна функция която се само извиква с timeout, докато div-а не достигне максималния си размер. В повечето случаи interval би вършил работа, но принципа е важен в случая.
// тук създавам примерен div
var element = new Element('div').setStyle({
width: '10px',
height: '10px',
backgroundColor: 'red'
});
document.body.appendChild(element);
var step = 10, max = 1000, time = 10;
(function(){
var width = parseInt(element.style.width) + step,
height = parseInt(element.style.height) + step;
element.style.width = width + 'px';
element.style.height = height + 'px';
if (max > width && max > height){
setTimeout(arguments.callee, time);
}
})();
Малко допълнителна информация за arguments
Оказва се, че някои браузъри дефинират arguments само, когато е необходимо. Т.е. ако в тялото на фунцията не се използва никъде arguments, тя въобще не се дефинира. Което спестява време и ускорява самия кода. Тук има повече информация по този въпрос. Този факт е интересен и си мисля, че повече javascript интерпретатори ще използват подобен похват в бъдеще.

June 19th, 2009 at 4:49 am
(имаш много гате анти спам филтър)
Та казвах (за трети път), че до колкото съм чел, Фоо е алтернативно спелуване на Фу, което е част от ФУБАР, което е сложно прилагателно описващо ситуацията в България в момента.
Съгласен съм, че Prototype е много удобен, но да не се прекалява с него
Примерно защо да се ползва $$(“body”).first() , когато има готов селектор document.body, който трябва само да се extend-не т.е. $(document.body). По този начин пестиш един getElementsByTagName(“body”), едно итериране на елементите (нищо, че е само 1), и после изрично викане на първия елемент от масив с един елемент.
(абе от бая време сакам те коментирам и сега намирам за какво да се хвана)
June 19th, 2009 at 11:14 am
Благодаря за коментара
Така махнах spam филтъра, досега съм имал само 2 spam коментара, така че ще видя какво ще стане ако го няма 1-2 дни.
За $$(”body”).first() си прав, цялото нещо съм го заменил с document.body.appendChild(element).
December 12th, 2009 at 9:56 pm
Хубаво е да се спомене, че в ECMA 262 5 edition, в strict mode, стойноста на свойството `callee’ на обекта който бива сочен от arguments е undefined. Все още не съм се зачитал навътре в документацията на ECMA5, но това ми се струва много много глупаво. Не разбирам с какво ще помогне на developer-ите като цяло това нещо. arguments.callee както си написал е идеален начин да се прави рекурсия с анонимни функции.
Последното нещо, което си написал за оптимизацията на ECMA имплементациите. По идея `arguments’ свойството на AO/VO се създава при влизане в Execution Context-а.
Вътрешното свойство [[Prototype]] сочи обекта сочен от Object.prototype. Тоест наследява от Object.prototype, за това и не е масив.
След това се създава `callee’ свойството, което от своя страна има {DontEnum} атрибут. `callee’ е рефернция към функцията която е била извикана.
След това се създава `length’ свойството. Стойността му е броя на аргументите за които е предоставена стойност от caller-a. На length свойството отново се слага атрибут {DontEnum}.
За всеки подаден аргумент от caller-а за когото е предоставена стойност се създава свойство на обекта сочен от `arguments’. 0 => N. На всяко свойство отново се слага атрибут {DontEnum}. Това означава, че тези свойствa не могат да се итерират във for-in.
Та относно оптимизацията, за която си споменал. Да това може да се окаже, че не е много бързо в някой случай, но като цяло не ми се вижда много тежка операция, при положение, че така или иначе при влизане в “Execution Context”, се създава AO/VO. За всяко деклариране на променливи и функций се създава свойство на VO/AO с името на идентификатора. Присвоява се стойност undefined. И това всичко се случва при влизането в Execution Context-а.
December 13th, 2009 at 5:59 pm
И аз малко се разочаровах, когато разбрах arguments.callee няма да го има вече. Иначе благодаря за обяснението как работят arguments.
Докато за оптимизацията много зависи от самата имплементация, също така това не би ме спряло да ползвам arguments когато ми трябват.