Статья в разработке!

В этой статье познакомимся с таким понятием как область видимости переменных и функций. Т.е. рассмотрим к каким переменным и функциям можно обращаться из одной или другой части кода.

Область видимости (scope). Цепочка областей видимости

Область видимости определяет доступность (видимость) переменных и функций вида function declaration statement.

Области видимости создаются во время выполнения JavaScript программы (сценария).

В языке JavaScript (до ECMAScript 2015) выделяют 2 области видимости:

  • глобальная (переменная или функция, созданная в этой области видимости, может быть доступна из любой точки программы);
  • локальная или функциональная (переменная или функция, созданная в этой области видимости, может быть доступна только внутри неё).

Локальная или функциональная область видимости создаётся во время вызова функции. При этом локальная область у каждого вызова функции, даже одной и той же, будет своя.

// global scope (глобальная область видимости)
function salute(welcomeText) {
  console.log(welcomeText);
}
salute('Привет'); // вызов функции salute
salute('Здравствуйте'); // вызов функции salute


[[Scope]] — это скрытое внутреннее свойство функции, которое она получает во время вызова. Данное свойство содержит ссылку на ту область видимости, в которой данная функция была объявлена.

Пример:

// global scope (глобальная область видимости)
var num = 15;
function outputNum() {
  console.log(num);
}
outputNum(); // 15


В приведённом примере во время вызова функции outputNum будет создана локальная область, а также установлено в качестве значения [[Scope]] область в которой данная функция была объявлена.

Т.к. переменной num нет в текущей области, то интерпретатор посредством [[Scope]] перейдёт в область (в данном случае глобальную) и попытается найти её там. В этой области (глобальной) она есть. А это значит, что в качестве num будет использоваться значение переменной num, находящейся в глобальной области видимости.

JavaScript всегда начинает поиск переменной или функции с текущей области видимости. Если она в ней не будет найдена, то интерпретатор переместится к следующей области, указанной в [[Scope]], и попробует отыскать её там. После этого действия повторяются, т.е. при отсутствии искомой переменной или функции в просматриваемой области видимости, интерпретатор перемещается к следующей области посредством [[Scope]] и пытается обнаружить её там.

В результате поиск всегда заканчивается одним из двух нижеприведённых сценариев:

Первый сценарий: интерпретатор встретил искомое имя идентификатора в какой-нибудь области. В этом случае он берёт его значение. Поиск искомого имени идентификатора в других областях видимости, т.е. дальнейшие перемещения по [[Scope]], не выполняются.

Второй сценарий: интерпретатор не нашёл нужный идентификатор. В этом случае он бросает ошибку о том, что данный идентификатор не определён. Данный сценарий может возникнуть только тогда, когда интерпретатор в поиске переменной или функции дошёл до глобальной области видимости и не нашёл её в ней.

Глобальная область видимости — это последнее звено в цепочке областей видимости. Она не содержит ссылку на другую область, дальше неё ничего нет.

Последовательность областей видимости, которые интерпретатор использует при разрешении имени идентификатора, называется в JavaScript цепочкой областей видимости (scope chain).

В этом примере интерпретатор при разрешении переменной color дойдёт от текущей до глобальной области видимости:

// global scope (глобальная область видимости)
var color = 'green';
function outputColor() {
  function displayColor() {
    console.log(color);
  }
  displayColor(); // "green"
}
outputColor();


В этом примере значение переменной drink будет взято из локальной области, созданной во время вызова внешней функции:

// global scope (глобальная область видимости)
var drink = 'молоко';
function outputDrink() {
  var drink = 'яблочный сок';
  function displayDrink {
    console.log(drink);
  }
  displayDrink(); // "яблочный сок"
}
outputDrink();

В этом примере показано то, что в глобальной области видимости нельзя обратиться к переменным и функциям, находящимся в локальной области видимости.

var v1 = 15;
function f1() {
  var v2 = 10;
  function f2() {
    console.log('f2');
  }
}
console.log(v1); // 15
f1(); // вызов функции f1
f2(); // нельзя вызвать функцию f2, так как она не видна в глобальной области видимости
console.log(v2); // нельзя обратиться к v2, так как она не видна в глобальной области видимости

При разрешении имени, интерпретатор начинает поиск, начиная с текущей области видимости. Поэтому этот пример выведет в консоль текст «Дима».

// global scope
var name = 'Петя';
function getName() {
  var name = 'Иван';
  function displayName() {
    var name = 'Дима';
    console.log(name);  // "Дима"
  }
  displayName(); // вызов функции displayName
}
getName(); // вызов функции getName

Если в функции displayName не была бы объявлена переменная name, то интерпретатор не обнаружил бы её в области, созданной во время вызова этой функции. В этом случае он взял бы значение из области видимости, созданной во время вызова функции getName. В результате в консоль был бы выведен текст «Иван».

В этом примере функция f1 объявлена в глобальной области видимости. Поэтому свойство [[Scope]] функции f1, во время её вызова, будет содержать ссылку на глобальную область видимости даже несмотря на то, что данная функция вызвана в локальной области видимости, созданной во время вызова функции f2.

// global scope
var num = 10;
function f1() {
  console.log(num);
}
function f2() {
  num = 20;
  f1(); // 10, т.к. [[Scope]] = global scope
}
f2(); // [[Scope]] = global scope

Поднятие (hoisting) функций и переменных

Функции вида function declaration statement можно использовать в JavaScript до их объявления. Это происходит из-за того что они поднимаются (hoisting) или другими словами «перемещаются» в начало текущего контекста.

// вызвать функцию greet
greet();
// объявление функции greet
function greet() {
  console.log('Привет!');
}

Но поднятие в JavaScript выполняется не только функций вида function declaration statement, но и переменных, объявленных с помощью ключевого слова var. При этом поднятие осуществляется только самого объявления переменной.

console.log(width); // undefined
var width = 500;

Вышеприведённый код после поднятия будет аналогичен следующему:

var width;
console.log(width); // undefined
width = 500;

Локальные и глобальные переменные

В JavaScript переменные, созданные в глобальной области видимости называются глобальными, а переменные созданные в локальной области видимости соответственно локальными.

Иными словами, глобальные переменные — это переменные, объявленные вне тела какой-либо функции, а локальные — это переменные, объявленные внутри тела какой-либо функции.

// глобальная область видимости
var age = 32; // глобальная переменная
function sayHello() { // объявление функции sayHello
  var myName = "Вася"; // локальная переменная (не будет доступна вне этой функции)
  console.log("Привет, " + myName);
}
function sayAge() { // обявление функции sayAge 
  console.log("Мне, " + age + " года"); // будет использоваться значение глобальной переменной, т.к. локальной переменной с именем age нет 
}
// вызов функции sayHello
sayHello(); // Привет, Вася
// вызов функции sayAge
sayAge(); // Мне 32 года
console.log("Возраст: " + age); //Возраст: 32
console.log("Моё имя: " + myName); // Ошибка, переменная myName не определена


В JavaScript до ECMAScript 2015 (6 версия) блочных областей видимости не было. Т.е. любая переменная созданная с помощью ключевого слова var внутри блока будет видима и за его пределами.

Пример:

if (true) {
  var subject = "Математика"; // переменная будет видна за пределами данного блока
}
console.log(subject); // "Математика"

В ECMAScript 2015 (6) были введены ключевые слова let и const. Они предназначены для создания переменных и констант, видимость которых будет ограничено блоком в котором они объявлены. Блочная область видимости в JavaScript определяется посредством фигурных скобок.

Например, переменная subject не будет видна за пределами блока:

{
  let subject = "Математика"; // переменная не будет видна за пределами данного блока
}
console.log(subject); // Uncaught ReferenceError: subject is not defined

Кроме этого переменные, объявленные с помощью let и константы, созданные посредством const в JavaScript не поднимаются (hoisting). Т.е. к ним нельзя обратиться до их непосредственного объявления.

{
  console.log(osName); // Uncaught ReferenceError: subject is not defined
  let osName = "Linux"; 
}
console.log(subject);

Контекст функции. Ключевое слово this

Под контекстом функции понимается объект, в рамках которого выполняется функция. Обратиться к этому объекту внутри функции можно с помощью ключевого слова this.

Если функция не является методом объекта, то this будет указывать на window (глобальный объект).

function myFunc() {
  return this;
}
console.log(this); // window

В строгом режиме this в вышеприведённом примере будет равно undefined.

'use strict';
function myFunc() {
  return this;
}
console.log(this); // undefined

Если функция – это метод объекта, то this внутри тела функции будет указывать на него.

Пример 1:

var myObj = {
  propA: 27,
  methA: function() {
    return this.propA; // this = myObj
  }
}
myObj.methA(); // 27

Пример 2:

// объект myFish
var myFish = {
  fish : 'Треска',
  getFish: function() { // функция (метод объекта myFish)
    return this.fish;
  },
  getOwnObject: function() { // функция (метод объекта myFish)
    return this;
  }
}
// вывести в консоль результат выполнения метода getFish
console.log(myFish.getFish()); // "Треска"
// вывести в консоль результат выполнения метода getOwnObject
console.log(myFish.getOwnObject()); // объект myFish

Указание контекста функции. Методы call и apply

В JavaScript можно явно указать контекст, в котором необходимо вызвать функцию.

Первый способ — это использовать метод call.

Синтаксис:

// func - функция
// context -  контекст, в котором нужно вызвать функцию
// arg1, arg2, ... - список аргументов, которые нужно передать функции
funс.call(context[, arg1[, arg2[, ...]]]);

Пример:

// объект user
var user = {
  name: 'Василий',
  age: 27
}
// объявление функции getUserAge
function getUserAge() {
  return this.age;
}
// вызов функции в контексте объекта user
console.log(getUserAge.call(user)); // 27

Второй способ — это использовать метод apply. Данный метод аналогичен call. Единственное отличие apply от call заключается в том, что аргументы в нём указываются посредством массива. Этот вариант передачи аргументов является очень удобным, особенно тогда когда их количество заранее неизвестно.

Синтаксис:

// func - функция
// context -  контекст, в котором нужно вызвать функцию
// argArray - массив аргументов, которые нужно передать функции
funс.apply(context[, argArray]);

В качестве контекста методам call и apply, кроме объекта, можно ещё установить специальные значения null и undefined.

Не в строгом режиме в этом случае this будет указывать на объект window.

В строгом режиме this будет равно null.

Привязка функции к контексту (метод bind)

Функция (метод) в JavaScript не сохраняет контекст (объект) к которому она относится.

В этом очень просто убедиться, если, например, сначала сохранить ссылку на функцию (метод) в некоторую переменную. А после этого вызвать эту функцию, используя данную переменную.

Например:

'use strict';
var mouse = {
  model: 'M100',
  getModel: function() {
    return this.model;
  }
}
// сохраним в переменную ссылку на метод
var getModelMouse = mouse.getModel;
// вызовем метод getModelMouse и выведем его результат в консоль
console.log(getModelMouse()); // Uncaught TypeError: Cannot read property 'model' of undefined

В результате вызова getModelMouse будет возвращена ошибка, т.к. контекст, к которому относится данный метод, будет потерян.

Метод bind предназначен для явной привязки контекста (this) к функции. Он в отличие от методов call и apply не вызывает функцию. Механизм этого метода заключается в создании функции-обёртки, которая будет устанавливать необходимый контекст целевой функции.

Кроме этого, эта функция-обёртка будет результатом, который метод bind будет возвращать в результате своего выполнения.

Метод bind также позволяет задать аргументы, которые необходимо передать функции в момент её вызова. Данные аргументы будут переданы целевой функции до аргументов, которые ей будут установлены явно при её вызове.

Синтаксис метода bind:

// func - функция
// context -  контекст, с которым нужно связать функцию
// arg1, arg2, ... - список аргументов, которые нужно передать функции
func.bind(context[, arg1[, arg2[, ...]]])

Например, изменим вышеприведённый пример. А именно, не просто сохраним ссылку на метод, а выполним это с привязкой его к объекту mouse:

// сохраним в переменную ссылку на метод с привязкой его к контексту mouse
var getModelMouse = mouse.getModel.bind(mouse);
// вызовем метод getModelMouse и выведем его результат в консоль
console.log(getModelMouse()); // "M100"

В этом примере привяжем функцию changeScore к разным объектам. Функция changeScore, привязанная к первому объекту будет доступна по идентификатору changeScoreUser0001, а ко второму — по changeScoreUser0002.

'use strict';

function changeScore(amount) {
  this.score = this.score + amount;
  console.log(this.score);
}

var user0001 = {
  name = 'Афанасий',
  score = 1300
}

var user0001 = {
  name = 'Анастасия',
  score = 2500
}

var changeScoreUser0001 = changeScore.bind(user0001);
var changeScoreUser0002 = changeScore.bind(user0002);

changeScoreUser0001(400); // 1700
changeScoreUser0001(-800); // 900

changeScoreUser0002(-700); // 1800
changeScoreUser0001(300); // 2100

В этом примере, рассмотрим как осуществляется передача аргументов функции, привязанной к некоторому объекту с помощью bind.

'use strict';

function checkInRange(min, max, value) {
if (typeof value !== 'number') {
return false;
}
return value >= min && value

Источник: itchief.ru