Негледзячы на тое, што JavaScript добра працуе з сінтаксісам фігурных дужак для блокаў, у ім няма падтрымкі блочнай зоны бачнасці; усё што ёсць на гэты конт у мове - зона бачнасці функцыі.
function test() { // зона бачнасці
for(var i = 0; i < 10; i++) { // не зона бачнасці
// лічым
}
console.log(i); // 10
}
Заўвага: Калі не выкарыстана ў прысвойванні, аператары return або аргуменце функцыі, натацыя
{...}будзе інтэрпрэтавана як блочны выраз, а не як літэрал аб'екта. Гэта з'ява, сумесна з аўтаматычнай устаўкай коскі з кропкай, можа прывесці да хітрых памылак.
Таксама JavaScript не падтрымлівае выразныя прасторы імёнаў, усё аб'яўляецца ў агульнадаступнай прасторы імёнаў.
Для кожнай спасылкі на пераменную, JavaScript пойдзе ўверх па ўсіх зонах бачнасці,
пакуль не знойдзе яе. У выпадку, калі ён дойдзе да глабальнай прасторы імён і ўсё
яшчэ не знойдзе неабходнае імя, ён уздыме ReferenceError.
// скрыпт A
foo = '42';
// скрыпт B
var foo = '42'
Вышэйпрыведзеныя скрыпты маюць розныя вынікі. Скрыпт A аб'яўляе пераменную
foo у глабальнай зоне бачнасці, скрыпт B аб'яўляе foo у актуальнай зоне
бачнасці.
Паўторымся, гэта абсалютна не той жа самы вынік: не выкарыстоўваенне var
можа мець сур'ёзныя наступствы.
// глабальная зона бачнасці
var foo = 42;
function test() {
// лакальная зона бачнасці
foo = 21;
}
test();
foo; // 21
З-за таго, што аператар var прапушчаны ўнутры функцыі test, значэнне foo
у глабальнай прасторы імён будзе перазапісаным. Хаця першапачаткова гэта можа
падацца невялікай праблемай, не выкарыстоўванне var у кодзе на тысячы радкоў,
прывядзе да жахлівых, цяжкіх для адладкі памылак.
// глабальная прастора імёнаў
for(var i = 0; i < 10; i++) {
subLoop();
}
function subLoop() {
// прастора імёнаў subLoop
for(i = 0; i < 10; i++) { // аператар var прапушчаны
// робім чароўныя рэчы!
}
}
Знешні цыкл скончыцца пасля першага выкліка subLoop, бо subLoop перазапісвае
глабальную пераменную i. Выкарыстоўваючы var для другога цыкла for можна
было б пазбегнуць памылкі. Ніколі не прапускайце аператар var, акрамя
выпадкаў, калі змена дадзеных у знешняй зоне бачнасці ёсць пажаданым вынікам.
Адзіная крыніца лакальных пераменных у JavaScript гэта параметры функцыі
і пераменныя аб'яўленыя праз аператар var.
// глабальная зона бачнасці
var foo = 1;
var bar = 2;
var i = 2;
function test(i) {
// лакальная зона бачнасці функцыі test
i = 5;
var foo = 3;
bar = 4;
}
test(10);
foo і i гэта лакальныя пераменныя унутры зоны бачнасці функцыі test,
а вось прызначэнне bar перазапіша глабальныю пераменную з тым жа іменем.
JavaScript падымае аб'яўленні. Гэта азначае, што абодва аб'яўленні аператараў
var і function падымуцца на верх іх зоны бачнасці.
bar();
var bar = function() {};
var someValue = 42;
test();
function test(data) {
if (false) {
goo = 1;
} else {
var goo = 2;
}
for(var i = 0; i < 100; i++) {
var e = data[i];
}
}
Вышэйпрыведзены код трансфармуецца перад пачаткам выконвання. JavaScript падымае
аператары var, як і аб'яўленне function, наверх бліжэйшай зоны бачнасці.
// аператар var перамяшчаецца сюды
var bar, someValue; // па змоўчванню - 'undefined'
// аб'яўленне функцыі таксама падымаецца наверх
function test(data) {
var goo, i, e; // адсутная блочная зона бачнасці перайшла сюды
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // падае з TypeError бо ўсё яшчэ 'undefined'
someValue = 42; // прысвойванні не падымаюцца
bar = function() {};
test();
Адсутнасць блочнай зоны бачнасці не толькі падыме аператар var па-за межы цыкла
і яго цела, але таскама зробіць вынік некаторых канструкцый if не-інтуітыўным.
Хоць у арыгінальным кодзе падаецца што канструкцыя if змяняе глабальную
пераменную goo, на дадзены момант гэта мяняе лакальную пераменную - пасля
таго, як было прыменена падыманне.
Без ведаў аб падыманні, можна падумаць што код ніжэй кіне ReferenceError.
// правярае ці было SomeImportantThing праініцыалізавана
if (!SomeImportantThing) {
var SomeImportantThing = {};
}
Але канешне, гэта працуе праз тое, што аператар var быў падняты на верх
глабальнай зоны бачнасці.
var SomeImportantThing;
// тут нейкі код можа ініцыалізаваць SomeImportantThing, або не
// тут у гэтым можна ўпэўніцца
if (!SomeImportantThing) {
SomeImportantThing = {};
}
Усе зоны бачнасці ў JavaScript, уключаючы глабальную зону бачнасці, маюць
адмысловае імя this, аб'яўленае ўнутры іх, якое спасылаецца
на актуальны аб'ект.
Зоны бачнасці функцый таксама маюць імя arguments,
аб'яўленае ў іх, якое спасылаецца на аргументы, што былі перададзеныя ў функцыю.
Напрыклад, калі паспрабаваць атрымаць доступ да пераменнай foo унутры зоны
бачнасці функцыі, JavaScript будзе шукаць імя ў наступным парадку:
- У выпадку калі прысутнічае канструкцыя
var fooу актуальнай зоне бачнасці, ёна і выкарыстоўваецца. - Калі параметр функцыі мае імя
foo, ён будзе выкарыстаны. - Калі сама функцыя называецца
foo, яна будзе выкарыстана. - Пераходзіць у знешнюю зону бачнасці, і пачынае з пункта #1.
Заўвага: Наява параметра названага
argumentsперадухіліць стварэнне параметраargumentsпа змоўчванні.
Вялікая праблема, звязаная з выкарыстоўваннем глабальнай прасторы імёнаў, гэта высокая верагоднасць перасячэння імёнаў пераменных. У JavaScript, гэта праблема можа быць лёгка пазбегнута праз выкарыстанне ананімных абгортак.
(function() {
// аўтаномная "прастора імён"
window.foo = function() {
// адкрытае замыканне
};
})(); // імгненнае выкананне функцыі
Ананімныя функцыі з'яўляюцца выразамі; таму каб быць выкліканымі, яны спачатку маюць быць ацэненымі.
( // ацэньваем функцыю ўнутры дужак
function() {}
) // вяртаем аб'ект функцыі
() // выклік выніку ацэнкі
Ёсць і іншыя спосабы ацаніць і імгненна выклікаць выраз функцыі, хаця яны і адрозніваюцца па сінтаксісу, паводзяць сябе аднолькава.
// Яшчэ некалькі спосабаў наўпрост выклікаць функцыю
!function(){}()
+function(){}()
(function(){}());
// і так далей...
Рэкамендуецца заўсёды выкарыстоўваць ананімную абгортку каб інкапсуліраваць код у яго асабістай прасторы імёнаў. Гэта не толькі абараняе код ад перасячэння імёнаў, але і дапамагае падзяляць праграму на модулі.
Таксама выкарыстанне глабальных пераменных лічыцца дрэннай практыкай. Любое іх выкарыстоўванне - прыкмета дрэнна напісанага кода, схільнага да памылак, і цяжага ў падтрымцы.