Не нужно считать из-за заголовка эту статью очередной лекцией нуба для нубов или переводом/пересказом ранее опубликованных статей. Она написана исходя из большого опыта, многочисленных «граблей», а также желания передать опыт в доступной форме.
Я думаю, даже профи смогут вынести из статьи что-то для себя, особенно это касается строгого режима (strict mode).
Сегодня мы работаем по плану:Понятие переменной. Особенности переменных в JS
Значения внутри переменных
Типы переменных
Замыкания
Перекрытие переменных
Формат JSON для быстрого объявления объектов
Специфические переменные
Чтение этой статьи подразумевает, что вы уже минимально знакомы с языком JavaScript.Дисклеймер: я не собираюсь оперировать общепринятыми понятиями/терминами, я покажу вам своё представление о том, какой я вижу структуру языка. Если моё мнение совпадает точкой зрения других, значит, это случайность, но я только рад. Вашим правом остаётся давать своё согласие или несогласие с автором. Тогда во втором случае, пожалуйста, цитируйте меня в тех местах, где вы не согласны и предлагайте свой вариант изложения.
Понятие переменной. Особенности переменных в JS
Переменной в JavaScript (как в принципе и в других языках) можно назвать имя, состоящее из набора допустимых символов (должно начинаться с латинских букв любого регистра, _ или $ и далее содержать в себе теже символы а также цифры, в регулярном выражении: /[A-Za-z_$]{1}[A-Za-z_$d]*/; должно не совпадать с ключевым словом языка), с помощью которого (через указание его в коде) производится обращение к каким-либо данным либо изменение этих данных.
Перед использованием переменной необходимо объявить ее с помощью операции присваивания либо ключа var. Обращение к необъявленной переменной вызовет ошибку.
Имя переменной (в JavaScript) чувствительно к регистру, что существенно расширяет количество переменных, которые могут быть созданы при данном количестве символов в имени, например, при двух символах в имени VR, vr, Vr, vR — разные переменные, в то время как в нечувствительных к регистру языках все эти записи обозначали бы одну и ту же переменную.
Проще говоря, концепция переменных — «написал имя — получил данные, связанные с этим именем»:
VARIABLE=10; //объявляем переменную: указываем некоторое имя и обязательно присваиваем ему значение (для глобальных)
console.log(VARIABLE); //обращаемся к переменной по имени «VARIABLE»
Если вы уберете первую строчку вышеприведенного кода, то на практике узнаете, что случается при обращении к необъявленной переменной.
Особенностью переменных в JavaScript является их нетипизированность, т.е. нет разницы, какое значение хранится внутри данной переменной, будь это просто число или DOM-объект. Это означает, что в любой момент созданную переменную можно будет использовать для других целей:
VARIABLE=1; //хранит число
VARIABLE="100500"; //уже хранит строку
VARIABLE=[1,2,3]; //уже хранит объект (массив)
Значения внутри переменных
Значения, хранимые внутри переменной, могут быть простыми и сложными. Попытаемся разобраться, в чем разница.
Для начала необходимо объяснить схему, по которой происходит хранение данных в памяти. Особенностью динамического языка является то, что физически (т.е. уже в памяти устройства) переменная хранит в себе не значение, а ссылку на область памяти, хранящее это значение. Не будем вдаваться в подробности, а ограничимся лишь представлением о том, что в этой области памяти может быть как и простое значение в виде числа, так и ссылка на объект, который внутри себя может содержать и ссылки на другие объекты.
Сборщик мусора — это специальный механизм, удаляющий все объекты, которые в процессе использования стали недоступными. А недоступными становятся такие объекты, на которые в коде не осталось ни одной ссылки.
Простые значения переменных
Сюда можно отнести числа, строки и булевы значения, а также значения null, undefined, NaN, бесконечности. А простыми они являются из-за того что: 1) они уникальны (нет таких ссылок, которые указывали бы на одну и ту же область памяти, где хранятся их значения); 2) таким значениям нельзя присвоить пользовательские свойства; 3) при изменении содержащей их переменной они мгновенно удаляются из памяти.
Объяснение в примерах:
A=B=1; //сохранили в памяти число
A.prop=1; //присваиваем пользовательское свойство
alert(A.prop); //undefined
//А и B имеют совершенно разные области памяти, в которых записаны их значения
Грубо говоря, для таких значений сборщик мусора даже не считает количество ссылок, а сразу очищает память. И это логично.
Сложные (ссылочные) значения переменных
Сюда относятся объекты любого типа (кроме значения null): простые объекты, массивы, регулярные выражения… Проще говоря, всё, что не является простым типом. Для таких объектов ситуация иная: если на один и тот объект указывает несколько ссылок, то объект не удаляется из физической памяти до тех пор, пока не будет удалена последняя ссылка на него. Но это нужно только для общего представления, клиентский программист не должен заботиться об этом.
Ссылочным переменным можно присвоить пользовательские свойства, на этом всё и строится. С помощью использования свойств можно строить структуры любых видов (графы/деревья/списки и др.).
Стоит отметить объекты-массивы и объекты функции: первые оптимизированы для быстрейшей выборки по индексам, вторые — для хранения внутри себя замыканий и выполнения кода.
Типы переменных
Используя разные способы объявления можно создать локальную или глобальную переменную.
Глобальные переменные
Такие переменные доступны при обращении из любого участка кода любого скрипта, если до этого они были объявлены. Особенностью их в том, что они являются свойствами объекта window, поэтому их можно удалить оператором delete. Объявляется глобальная переменная присваиванием без использования слова var:
EXMPL=100500; //создаём глобальную переменную
delete EXMPL; //и удаляем её
Особое внимание следует обратить на именованные функции. Рассмотрим пример:
E=function E(){delete E;}; //если вызвать функцию, что произойдёт?
Если вы не смогли дать ответ на вопрос внутри кода, то отвечаю за вас: ничего не произойдет, глобальная переменная E не удалится. Все дело в том, что при разрешении области видимости в операторе delete происходит обращение в первую очередь к имени функции, в результате чего возвращается вызываемая функция, попытка удалить которую ни к чему не приводит. Для решения следующей проблемы следует изменить имя функции либо передавать E оператору delete как свойство объекта window. В следующем пункте мы узнаем, что же на самом деле происходит в этом случае.Замечание: глобальные переменные невозможно создать приведенным способом в строгом режиме. Это нововведение предназначено для невозможности случайного создания глобальной переменной. В этом случае создавайте такую переменную как свойство объекта window.
Локальные переменные
Такие переменные видимы только внутри определенного блока, их жизнь (время, в течение которого к ним можно обратиться) ограничена временем выполнения функции, в которой они объявлены (если, конечно, в этом же блоке они не будут замкнуты; об этом в следующей главе). К локальным переменным также относятся именованные аргументы функций, а также сами именованные функции (это касается предыдущего примера).
Особенностью локальных переменных является то, что их можно объявить в любом месте функции, в таком случае переменная считается объявленной во всем теле этой функции:
(function init(){ //функция-обертка
alert(v); //undefined
v=1;
alert(v); //1
var v;
})();
Как видно из примера, обращение к еще необъявленной (и неназначенной) переменной не вызывает ошибки, но возвращает неопределенное значение.
Особое место занимают локальные переменные, объявленные не внутри функций, а в глобальном контексте выполнения. В этом случае они записываются, как и глобальные, в объект window. Но отличительной особенностью является то, что такие свойства НЕЛЬЗЯ УДАЛИТЬ с помощью оператора delete:
var Z=1;
delete Z; //саму по себе локальную переменную не удалить
delete window.Z; //но и свойство Z тоже нельзя удалить!
alert(Z); //1
Замыкания
Механизм замыканий реализован через обращения из текущей функции (обозначим её единичкой — 1, чтобы понимать, о какой функции речь) к переменной, которая не была создана в данной функции (1), но создана ранее в одной из функций (2), находящейся в стеке вызовов, когда была СОЗДАНА текущая функция (1). Простейший пример:
window.value=(function init(){
var CLOSURE=100, //переменная, которую мы ЗАМЫКАЕМ
FNC=function with_closure(){ //функция, внутри которой замыкание
return CLOSURE; //собственно, обращение к замкнутой переменной
//Как видите, она объявлена не в теле этой функции, а в теле функции, создающей ЭТУ функцию
};
return FNC;
})();
Итак, если запустить window.value(), то получим значение 100, взятое из переменной CLOSURE.
Значения замкнутых переменных можно не только получать, но и устанавливать. Таким образом и создаются интерфейсы:
(function init(){
var PRECISION=3; //точность
return {
setPercision:function setPercision(value){ //функция-фильтр пользовательских значений: устанавливаются только числа в пределах от 2 до 10 включительно, остальные значения игнорируются
if(typeof value=="number")
PRECISION=Math.min(10,Math.max(2,parseInt(value)));
return PRECISION;
},
calculate:function calculate(radius){
return Math.round(Math.PI*Math.pow(10,PRECISION))/ Math.pow(10,PERCISION)*radius*radius;
}
};
})();
Смысл замкнутой переменной в таком интерфейсе следующий: в одном месте она используется как некоторое необходимое для вычислений значение (в данном случае — для управления точностью радиуса), а в другом — для записи в неё новых значений, причем все неподходящие значения игнорируются, что нужно для невозможности присваивания недопустимых (в данном случае — нечисловых) значений. К тому же к этой переменной нельзя обратиться извне функции (без отладчика).
Данный пример, хоть и тривиален, но, на моё мнение очень хорошо показывает, для чего нужны замыкания.
Перекрытие переменных
Такие ситуации случаются, когда в вызываемой функции создаётся переменная с именем, которое уже было использовано для переменной среди функций в стеке вызовов. В этой ситуации обращение к ранее созданной локальной переменной становится невозможным. Если перекрывается глобальная переменная, то она все равно останется доступной, но только через объект window.
Ситуации перекрытия могут возникнуть как из-за случайности, так и из-за необходимости специально сделать недоступной ранее созданную локальную переменную. Такой случай я как-нибудь опишу в статье про создание загрузчика.
Формат JSON для быстрого объявления объектов
Данный формат является подмножеством формата YAML, он позволяет довольно лаконично описать структуру создаваемого объекта. Информация находится здесь.
JavaScript предъявляет менее строгие требования к JSON внутри кода:
1) имена свойств можно не брать в кавычки;
2) значениями могут быть функции и значения undefined, а также любые встроенные и пользовательские (как глобальные, так и текущего контекста) объекты JavaScript (Math, RegExp и др.).Замечание: в строгом режиме невозможно создать объект, у которого есть свойства с одинаковыми именами. Это же правило касается и функций: их аргументы не могут иметь одинаковых имён.
Специфические переменные
Я хотел бы сделать несколько замечаний по поводу некоторых переменных. Хотя это не совсем «переменные», но ведут они себя именно как переменные: их значения могут участвовать в операциях. Если быть более точным, то это специфические объекты.
Переменная this
Эта переменная одновременно является и ключевым словом. В момент обращения она указывает на контекст выполнения — тот объект, от имени которого была запущена данная функция:
(function init(){ //запускается из глобального контекста
alert(this==window); //true
})();
Q={
run:function run(){
alert(this==Q); //true, если вызов был привязан методом bind или запуск был произведен с помощью записи Q.run()
}
};
Q.run();
Во всех операциях её можно использовать как обычную переменную. Одно правило: ей нельзя присвоить значение — это вызовет.Замечание: в строгом режиме нельзя вернуть значение this —оно заменяется на неопределенное значение.
Переменная window
Эта переменная доступна из любого места в коде. В нее [записываются как свойства] глобальные переменные, в ней содержатся все основные объекты ядра и пользовательского JavaScript. С ее помощью можно «достучаться» до родительского фрейма. Она содержит методы для управления окном.
Переменная arguments
О ней можно сказать, что она доступна только внутри вызванной функции (вне функции к ней можно обратиться через _func_name_.arguments, но такое обращение вернет null). Основной её задачей является поиндексная репрезентация переданных в функцию аргументов. Содержит в себе ссылку на выполняющуюся функцию и количество формально переданных аргументов.
Особенностью является двухсторонняя связь с именованным аргументом (спасибо theshock за объяснение):
(function f(a){
a=2; //изменяет arguments[0]
alert(arguments[0]); // 2 !!!
})(1);
(function f2(a){
arguments[0]=2; //изменяет a;
alert(a); // 2 !!!
})(1);
Замечание: в строгом режиме использование arguments было достаточно ограничено.
Послесловие
Спасибо, что дочитали. Надеюсь, вам было интересно. Если вы поддержите идею, то я напишу еще несколько статей про такие, казалось бы, известные вещи.
Оставляйте в комментариях свои отзывы, если понадобится, я внесу корректировочные правки.