Scope или scoping
(Область действия или видимости): Это определение доступности переменных и функций внутри определенных блоков кода. Существует глобальная область действия, область действия функций и облатсь действия блока.
Lexical scoping(scope)
(Лексическая область видимости): контролируется размещением функций и блоков в коде. Основная суть в том, что дочерние функции лексически связаны с окружением их родителей и дочернии функции будут иметь доступ к переменным функции родителя. И то, что переменная существует только внутри своей функции, и к ней нельзя получить доступ извне.
Лексическую область иногда также называют статической областью из за того что область видимости определяется в момент определения функции (в отличие от динамической области видимости, при которой это происходит в момент вызова функции)
В общем, если лексическое окружение это про то, где переменные находятся, то теперь мы говорим про то, где они доступны.
Как мы уже ранее сказали, существует три вида области действия, сейчас разберем их подробнее.
Глобальная область действия относится к коду верхнего уровня.
scope
, доступны вездеconst hello = 'hello'; // глобальная переменная
console.log(hello) // hello
function hi() {
console.log(hello) // переменная доступна
}
hi() // hello
Каждая функция образует scope
функций. Это как бы в противоположность глобальному scope
поэтому и называется так же локальным scope
.
function hi() {
const hello = 'hello';
console.log(hello);
return hello;
}
hi(); // hello
// если попробуем снаружи, то получим ошибку
console.log(hello) // ReferenceError: hello is not defined
Раньше только функции могли создавать свой scope
, но начиная с es6
, scope
имеют любые блоки кода.
До es6
существовал только глобальный scope
и scope
функций. Переменные var
была как раз переменной области действия функции.
То есть var
была доступна везде в глобальном коде, кроме функций. Как раз вспоминаем про variable environment
.
А в es6
появился block scope
и переменные const
и let
- они переменные области действия блока. Они доступны только в рамках блока кода(цикл, функция, просто блок и тд).
if (true) { // блок кода
const one = 1;
let two = 2;
var three = 3; // var будет доступна и извне
console.log(one); // 1
console.log(two); // 2
console.log(three); // 3
}
console.log(one); // ReferenceError
console.log(two); // ReferenceError
console.log(three); // 3 наша переменная var
{ // блок кода
function hi() {
console.log('hi');
}
}
// наша функция доступна
hi(); // hi
Но если мы будем использовать "use strict"
, то мы не сможем вызвать функцию, так как она станет ограничена блоками кода.
"use strict";
{
function hi() {
console.log('hi');
}
}
hi(); // ReferenceError
Вспомним концептуальное представление lexical environment
из урока про лексическое окружение.
Смотрим на свойство outer
.
ExecutionContext:
// global EC
LexicalEnvironment:
// global LE
hi -> 'hi'
outer: null // вот эта ссылка ведет на родителя
LexicalEnvironment:
// if LE
name -> 'Dima'
surname -> "Lubimyi"
outer: global // вот эти ссылки и образуют цепь областей видимости
Пример с функциями:
let c = 3;
function a() {
let a = 1;
console.log(c); // 3
function b() {
let b = 2;
console.log(a); // 1
console.log(с); // 3
}
b();
}
a();
Теперь разберем каждый контекст:
// глобальный контекст
ExecutionContext:
// global EC
LexicalEnvironment:
// global LE
c -> 3
outer: null
// контекст функции a
ExecutionContext:
// fun a EC
LexicalEnvironment:
// fun a LE
a -> 1
// ищет в глобальной области
outer: global
// контекст функции b
ExecutionContext:
// fun b EC
LexicalEnvironment:
// fun b LE
b -> 2
// ищет в области функции a
outer: <function a>
Когда во время фазы выполнения компилятор ищет переменную в лексическом окружении и не находит ее, он идет по ссылке в свойстве outer
, которая ссылается на родителя и ищет переменную там, если ее нет и там, то так дальше по цепи.
Если переменная не найдена ни в одном из лексических окружений, возникает ошибка.
Это и есть scope chain
. А сам процесс называется разрешение идентификаторов(identifier resolution)
Посмотрим еще на один пример:
function f1() { // родительская функция
const all = `ВСЕМ`;
function hi() { // дочерняя функция
const hello = 'ПРИВЕТ';
console.log(`${all} ${bye}`);
}
hi()
function bye() { // еще дочерняя функция
const bye = 'ПОКА';
console.log(`${all} ${hello}`);
}
bye()
}
f1();
В коде выше будет ошибка, так как мы пытаемся получить переменные одной дочерней функции у другой дочерней функции в том же scope
.
Но обе эти функции имеют доступ к переменной их родительской функции.
Происходит это из за правил lexical scoping
, не одна из этих функций не записана друг в друге, у них один родитель и доступ они имеют только к нему.