В JavaScript функция является одним из типов данных, специфика которого заключается в том, что к нему можно обращаться, вызывая его как функцию и как конструктор. Но если это тип данных, то, что бы с ним работать, нам нужно получить ссылку на него, которая может содержатся в какой-то переменной. Это и происходит при объявлении функции, по сути имя функции - это переменная, которой присваивается значение - функция. В этом смысле следующие две строчки эквивалентны:
function x(){}
var x = function(){}
Между этими строками есть небольшое отличие, но в данном случае оно не существенно. Так вот, само название ПЕРЕМЕННАЯ говорит нам о том, что она может менять своё значение, по-этому опираться на то, что эта переменная будет всегда ссылаться на один раз присвоенную ей функцию, нельзя. Следующий пример это демонстрирует:function x(){};
//...
x = 5;
//...
x(); //Error!
Но что же делать, если про функцию всё-таки нужно узнать её имя? Например, у нас может быть написан следующий код:function x1(){}
var x2;
if (x1) x2 = x1;
x1 = 5;
В результате ссылка на функцию есть, но как её найти, имея лишь саму функцию - не понятно.В некотором частном случае, а именно если объявление функции и манипуляции с ней происходили в глобальном контексте (глобальной области видимости переменных), выяснение имени возможно.
Здесь следует сказать пару слов о глобальном контексте. В JavaScript определён объект
Global
, доступный по ссылке this
. Это означает, что если мы объявляем переменную не внутри какой-либо функции, а просто в сценарии, то она автоматически становится свойством объекта Global
и к ней можно обращаться не только по имени, но и как к свойству этого объекта:var x = 5;
alert(this.x); // '5'
В частном, но наиболее часто встречающемся случае клиентского JavaScript (client-side JavaScript), т.е. расширения стандарта ECMAScript в браузерах моделью DOM, нам доступна ссылка window
, укзывающая на объект Window
, который является расширением объекта Global
для браузерной среды, и в нём, соответственно, так же можно, обращаться к его свойствам как к переменным, однако в общем случае использование ссылки window
нельзя назвать корректным, по крайней мере лично мне хотелоось бы писать решения, которые работали бы, кроме клиентского JavaScript, ещё и, скажем, в Java-программах, использующих Scripting API, поддержка которого есть в JDK6.Со ссылкой же на объект
Global
есть некоторая сложность, связанная с тем, что она по сути и доступна будет лишь в глобальном контексте и в функциях, которые не являются свойствами различных объектов, поскольку в других местах ссылка this
будет ссылаться на другие объекты. Что бы решить эту проблему, предлагаю ввести специальную переменную global
, которая будет ссылаться на глобальный контекст:var /** @type {Global} */ global = this;
Теперь нам будет проще написать нужную функцию, но куда её лучше всего разместить? Мне представляется логичным помещение её в Function.prototype
, поскольку тогда она станет доступна у всех функций (т.к. любая функция представляет собой экземпляр объекта Function и ссылается по-этому своей ссылкой __proto__
на объект Function.prototype
).Итак, какой код получаем в результате:
var /** @type {Global} */ global = this;
//...
/**
* Функция, возвращающая имя функции this, если оно присутствует в глобальном
* контексте.
*
* @return {string} имя функии, доступное по ссылке this или null, если имя не
* найдено.
*/
Function.prototype.getName = function() {
for (var /** @type {string} */ i in global)
if (global[i] === this)
return i;
return null;
}
//...
//Тестируем:
//Объект узнаёт имя своего конструктора:
function x1(){
this.showMyName = function() {
alert('My constructor name is ' + this.constructor.getName());
}
}
y = new x1;
y.showMyName(); // 'My constructor name is x1'
var x2;
if (x1) x2 = x1;
x1 = 5;
y.showMyName(); // 'My constructor name is x2'
// Функция узнаёт своё имя:
function x3(){ alert('My name is ' + arguments.callee.getName()); }
x3(); // 'My name is x3'
// Узнаём у функции её имя:
function x4(){}
alert(x4.getName()); // 'x4'
Таким образом, функция, если ссылка на неё присутствует в глобальном контексте, всегда сможет узнать своё имя.Update: К сожалению, в последней на сегодняшний день версии Internet Explorer`а - 8 (тестировал на версии 8.0.7600.16385) - всем переменным глобального пространства имён и, соответственно, функциям, автоматически присваивается модификатор {Don`t Enum}, из-за чего данный метод в этом браузере не работает... :(
3 комментария:
А как быть если в конструкторе нужно узнать имя создаваемого объекта? Например:
var name = myObject();
Хочу знать имя name в функции конструкторе myObject(). перебор в цикле свойств объекта window в ИЕ выдает только стандартные объекты и обработчики (в остальных браузерах работает). Причем если вызвать window['name'] то все нормально - получим ссылку на name, а в цикле никак(имеется в виду ИЕ). Работает также если объявить через
this.name = new myObject() даже в локальных областях видимости. Вобщем очень хочется знать name в конструкторе :) как быть?
опечатался :)
var name = new myObject();
Такого способа я не знаю. Вообще-то, согласно правилам семантики языка, в выражении var name = new myObject(); сначала происходит создание переменной name типа undefined, затем вычисляется правая часть, т.е. создаётся новый объект и вызывается для него конструктор, и только потом результат этого выражения присваивается переменной name. Т.е. на этапе работы конструктора нельзя понять, какой из undefined-переменных будет присвоен результат этой работы. К тому же мы не можем быть полностью уверены, что результат работы конструктора вообще попадёт в переменную в глобальной области видимости - с таким же успехом это может быть поле какого-либо объекта или инкапсулированная в каком-либо замыкании переменная...
Мне кажется, если задача стоит так, лучше использовать другой подход - инициалилизацию объекта, когда он уже создан, например - и вынести зависимуж от имени переменной логику в него.
Отправить комментарий