Большие хитрости JavaScript
Нести ничего не стоит так сложно,Тем более, если нести далеко.
Наутилус Помпилиус
Данная набла является продолжением предыдущей.
В предыдущей набле я довольно часто использовал инструкцию
Листинг 1
x = "Hello";
alert("x="+x);
и
Листинг 2
var y = "Hello";
alert("y="+y);
Казалось бы, обе эти программы работают одинаково. Так есть ли разница? Чтобы узнать об этом, запустим следующий код:
Листинг 3
function Test() {
x = "Hello";
var y = "Hello";
alert("x="+x);
alert("y="+y);
alert("window.x="+window.x);
alert("window.y="+window.y);
}
Test();
Вы увидите интересную вещь. Оказывается, если переменной присваивается значение в функции без использования
Тем не менее, код
Наверное, вы уже поняли, что при помощи
Т.к. для каждого фрейма на странице существует свой объект
Замыкания в JavaScript
Perl славен тем, что это язык, который работает с кодом точно так же, как с данными. Иными словами, код (функции) для
Как ни странно, JavaScript в этом отношении наиболее похож на Perl. Возможно, вы и не догадывались, что привычный всем код
Листинг 4
function hello(world) { alert("Hello, "+world) }
в действительности обозначает для JavaScript-интерпретатора обыкновенный оператор присваивания:
Листинг 5
hello = function(world) { alert("Hello, "+world) };
Такой вот интересный оператор. Видите, в JavaScript тоже можно создавать функции во время работы программы (а именно, в момент выполнения оператора присваивания).
Может быть, вы спросите, какая разница, где создавать
Замыкание — это функция, плюс все те лексические переменные из охватывающего контекста, которые она использует. Когда мы используем оператор
Проиллюстрирую сказанное на примере. Создадим массив из 100 функций, каждая из которых будет печатать квадрат некоторого числа.
Листинг 6
// Создает одну функцию, которая печатает квадрат
// указанного числа.
function createFunc(n) {
return function() { alert(n*n) };
}
// Создает number таких функций и возвращает их массив.
function create(number) {
var arr = [];
for (var i=1; i<number; i++) {
arr[i] = createFunc(i);
}
return arr;
}
// Теперь создаем все функции...
var arr = create(100);
// ...и запускаем четвертую по счету.
arr[4]();
В данном примере замыкание создается в функции
Отличия от Perl
К сожалению, замыкания в JavaScript все же работают чуть по-другому, чем в Perl. В последнем каждая функция-замыкание имеет свой собственный набор лексических переменных, никак друг с другом не связанных. Мы могли бы написать так:
Листинг 7
// Создает number таких функций и возвращает их массив.
function create(number) {
var arr = [];
for (var i=1; i<number; i++) {
arr[i] = function() { alert(i*i) };
}
return arr;
}
в надежде, что это заработает. Однако в JavaScript данный способ создаст 100 одинаковых функций, каждая из которых будет печатать 100*100! Почему же так происходит?
Дело в том, что в замыкание попадает не сама переменная, а ссылка на нее. (Это и неудивительно: ведь в JavaScript все переменные хранят ссылки на объекты.) Таким образом, в последнем примере все 100 замыканий, созданных в цикле, будут разделять одну-единственную общую переменную
Для того, чтобы исправить ситуацию, мы написали отдельную функцию
Оказывается, можно обойтись и без создания функции
Листинг 8
function create(n) {
var arr = [];
for (var i=1; i<n; i++) {
// Создаем функцию...
arr[i] = function(x) {
// создание замыкания с лексической x
return function() { alert(x*x) }
}(i); // и тут же ее вызываем с параметром i!
}
return arr;
}
Это работает по той же самой причине, что и код
Я догадываюсь, что данный материал может показаться весьма сложным для первоначального понимания (особенно для тех, кто первый раз слышит о замыканиях как таковых). К сожалению, это не просто
Создание классов
Я видел в нескольких руководствах (и даже книгах!) по JavaScript примерно следующий код для создания нового класса (для
Листинг 9
// Конструктор.
function NewClass() {
this.property = 123;
this.method1 = NewClass_method1();
this.method2 = NewClass_method2();
// ...
}
// Методы.
function NewClass_method1(x) {
alert("Вызван method1("+x+"), property="+this.property);
}
function NewClass_method2(x) {
alert("Вызван method2("+x+")");
this.method1();
}
// Создаем объект и проверяем работу.
var obj = new NewClass();
obj.method1(10);
Но позвольте, это же так же нелепо, как писать:
Листинг 10
var zero = 0;
var one = 1;
var ten = 10;
...
// везде вместо цифр используем "константы"
alert(one * ten);
Это настолько же некрасиво, как использовать
Листинг 11
#define begin {
#define end }
#define then
#define write printf
с тем, чтобы писать на Си паскалеподобные программы:
Листинг 12
if (a > 10) then begin
write(a);
end;
В общем, когда классы сложные, и в них много методов, использование приведенного выше
Использование прототипов
Что же можно предложить взамен? Код, который я увидел полгода назад в Mozilla, создавал классы так:
Листинг 13
function NewClass() { /* пусто */ }
// Определяем первый метод.
NewClass.prototype.method1 = function(x) {
alert("Вызван method1("+x+")");
}
// Определяем второй метод.
NewClass.prototype.method2 = function(x) {
alert("Вызван method2("+x+")");
this.method1();
}
// Создаем объект и проверяем работу.
var obj = new NewClass();
obj.method1(10);
Считается, что
Фактически,
Прямое создание методов
Способ с прототипами довольно стандартен, однако он имеет и недостатки. При объявлении каждого метода мы вынуждены без конца твердить имя класса, к которому он принадлежит. Если вдруг понадобится это имя поменять, придется делать это во всем исходнике. Существует еще один метод, который мало того, лишен описанных недостатков, но еще и позволяет создавать закрытые (private) свойства класса. Вот он:
Листинг 14
// Конструктор.
function NewClass() {
this.property = 123;
// Создаем методы класса прямо в конструкторе.
this.method1 = function(x) {
alert("Вызван method1("+x+")");
}
// То же самое.
this.method2 = function(x) {
alert("Вызван method2("+x+")");
this.method1();
}
}
// Создаем объект и проверяем работу.
var obj = new NewClass();
obj.method1(10);
Вы видите, что нам ни в одном месте не пришлось упоминать дважды какое-либо имя. А значит, добавлять методы в класс будет чрезвычайно
За счет использования замыканий можно реализовать private-свойства класса:
Листинг 15
// Конструктор.
function NewClass() {
this.from = "author"; // открытое свойство (this)
var name = "somebody"; // закрытое свойство (var)
// Приветствие.
this.hello = function() {
// закрытые свойства пишем без this
alert("Hello, "+name+", from "+this.from);
}
// Метод для установки значения закрытого свойства.
this.setName = function(n) {
name = n;
}
}
// Создаем объект и проверяем работу.
var obj = new NewClass();
obj.setName("world");
obj.hello();
// Другой объект - для проверки.
var obj1 = new NewClass();
obj1.hello();
Данный пример позволяет убедиться, что
Дзен JavaScript-а
Возможно, вы в данную секунду ощущаете некоторую путаницу в мыслях. Если это так, она означает только одно: вы смутно почувствовали, что в JavaScript ... нет никаких классов! Может быть, вы этого еще не осознаете. Все, чем мы оперируем в JavaScript, является объектами, и только. Функция, целое число, хэш,
Также нет никакой существенной разницы между свойствами и методами.
Листинг 16
// Это корректный код!
(function(n) { alert(n) })(10);
Кроме того, «просто функций» также не существует: любая функция является в действительности
В общем,
Наследование
Как известно, без пруда не выловишь и рыбку из него. Эта пословица особенно хорошо применима к JavaScript: раз в нем нет классов, их и наследовать нельзя.
Еще одна аналогия. В буддийской концепции ум нельзя уничтожить. Но не потому, что он прочен, как скала, непоколебим и неуничтожим, вовсе нет! Просто ум не обладает ни одним свойством, к которому можно было бы применить термин «уничтожить». А раз нельзя уничтожить часть, нельзя избавиться и от целого. Точно так же, в JavaScript наследовать классы нельзя.
Тем не менее, можно наследовать объекты. Собственно, все «наследование» сводится к присваиванию нового значения прототипу объекта (свойству
Листинг 17
// Базовый "класс".
function Base() {}
Base.prototype.f1 = function() { alert(1) }
// Производный "класс".
function Derive() {}
Derive.prototype = new Base(); // без new нельзя!
Derive.prototype.f2 = function() { alert(2) }
var obj = new Derive();
obj.f1(); // вызывается функция базового объекта
Видите, мы фактически делаем класс, производный от объекта, а не от другого
Изменение стандартных прототипов
При помощи прототипов можно делать интересные вещи. Например, вы можете добавить в стандартный класс
Листинг 18
var x = 10;
Number.prototype.sqr = function() { return this*this }
alert(x.sqr());
Обратите внимание на то, что прототип класса был изменен уже после создания переменной
Также можно менять прототипы и любых других встроенных «классов» (Object, Array, String, Function и т. д.). Например, вы можете добавить функцию
Листинг 19
String.prototype.dup = function(n) {
var s = "", t = this.toString();
while (--n >= 0) s += t;
return s;
}
Запустив этот код, вы можете в любом месте программы писать что-то вроде
Наконец, давайте перепишем функцию
Листинг 20
Array.prototype.toString =
Object.prototype.toString = function() {
var cont = [];
var addslashes = function(s) {
// Использовать replace НЕЛЬЗЯ - в Опере
// происходит зацикливание, т.к. из replace
// зачем-то вызывается Object.toString().
return
s.split('\\').join('\\\\').split('"').join('\\"');
}
for (var k in this) {
if (cont.length) cont[cont.length-1] += ",";
var v = this[k];
var vs = '';
if (v.constructor == String)
vs = '"' + addslashes(v) + '"';
else
vs = v.toString();
if (this.constructor == Array)
cont[cont.length]
else
cont[cont.length] = k + ": " + vs;
}
// Здесь тоже нельзя делать replace()!
cont = " " + cont.join("\n").split("\n").join("\n ");
var s = cont;
if (this.constructor == Object) {
s = "{\n"+cont+"\n}";
} else if (this.constructor == Array) {
s = "[\n"+cont+"\n]";
}
return s;
}
Пример использования:
Листинг 21
var hash = {
color: "red",
artefact: "pill",
actors: {
supplier: "Morp\"heus",
consumer: "Neo"
},
numbers: [10, 20, 30],
slashquote: "with \\ (slash) and \" (quote)"
}
alert(hash);
Итого
JavaScript напоминает Perl, но не во всем. Хэши, замыкания, регулярные
Материал, описанный в данной набле, конечно же, не уникален. Вы можете найти в Гугле массу статей и даже книг на эту тему. Например, первое, что мне попалось: http://wdh.suncloud.ru/js01.htm.