В този блог ще проучим как прочутият механизъм за повдигане се появява в JavaScript. Преди да се потопим, нека се заемем с това какво е повдигане.

Hoisting е механизъм на JavaScript, при който променливите и декларациите на функции се преместват в горната част на техния обхват преди изпълнение на кода.

Неизбежно това означава, че без значение къде са декларирани функции и променливи, те се преместват в горната част на техния обхват, независимо дали техният обхват е глобален или локален.

Съдържание

  • undefined срещу ReferenceError
  • Променливи за повдигане
  • ES5
  • ES6
  • Уроци по повдигане
  • Заключение

За отбелязване обаче е фактът, че повдигащият механизъм премества само декларацията. Заданията са оставени на място.

Ако някога сте се чудили защо сте могли да извиквате функции, преди да ги напишете в кода си, тогава прочетете!

# undefined срещу ReferenceError

Преди да започнем сериозно, нека обсъдим няколко неща.

console.log(typeof variable); // Output: undefined

Това ни води до нашата първа бележка:

В JavaScript на недекларирана променлива се присвоява стойност undefined при изпълнение и също е от тип undefined.

Нашата втора точка е:

console.log(variable); // Output: ReferenceError: variable is not defined

В JavaScript се хвърля ReferenceError при опит за достъп до недекларирана преди това променлива.

Поведението на JavaScript при работа с променливи става нюансирано поради повдигане. Ще разгледаме това по-задълбочено в следващите раздели.

# Подемни променливи

Следното е жизненият цикъл на JavaScript и е показателно за последователността, в която се извършва декларирането и инициализирането на променлива.

Въпреки това, тъй като JavaScript ни позволява да декларираме и инициализираме нашите променливи едновременно, това е най-използваният модел:

var a = 100;

Както споменахме преди, всички декларации на променливи и функции се издигат до горната част на техния обхват. Трябва също така да добавя, че декларациите на променливи се обработват, преди да бъде изпълнен код.

За разлика от това обаче, недекларираните променливи не съществуват, докато не бъде изпълнен кодът, който ги присвоява. Следователно, присвояването на стойност на недекларирана променлива имплицитно я създава като глобална променлива, когато присвояването се изпълни. Това означава, че всички недекларирани променливи са глобални променливи.

За да демонстрирате това поведение, вижте следното:

function hoist() {
  a = 20;
  var b = 100;
}
hoist();
console.log(a); 
/* 
Accessible as a global variable outside hoist() function
Output: 20
*/
console.log(b); 
/*
Since it was declared, it is confined to the hoist() function scope.
We can't print it out outside the confines of the hoist() function.
Output: ReferenceError: b is not defined
*/

Тъй като това е една от ексцентричностите на начина, по който JavaScript обработва променливи, се препоръчва винаги да се декларират променливи, независимо дали са във функция или глобален обхват. Това ясно очертава как интерпретаторът трябва да ги обработва по време на изпълнение.

#ES5

вар

Обхватът на променлива, декларирана с ключовата дума var, е нейният текущ контекст на изпълнение. Това е или обграждаща функция, или глобална за променливи, декларирани извън която и да е функция. Нека да разгледаме няколко примера, за да разберем какво означава това:

Глобални променливи

console.log(hoist); // Output: undefined

var hoist = 'The variable has been hoisted.';

Очаквахме резултатът от дневника да бъде: ReferenceError: hoist is not defined, но вместо това изходът му е undefined.

защо се е случило това

Това откритие ни доближава до спора за нашата плячка.

JavaScript е вдигнал декларацията на променливата. Ето как изглежда горният код за интерпретатора:

var hoist;

console.log(hoist); // Output: undefined
hoist = 'The variable has been hoisted.';

Поради това можем да използваме променливи, преди да ги декларираме. Трябва обаче да внимаваме, защото повдигнатата променлива се инициализира със стойност undefined. Най-добрият вариант би бил да декларираме и инициализираме нашата променлива преди употреба.

Променливи с функционален обхват

Както видяхме по-горе, променливите в глобален обхват се издигат в горната част на обхвата. След това нека да разгледаме как се повдигат променливи с функционален обхват.

function hoist() {
  console.log(message);
  var message='Hoisting is all the rage!'
}

hoist();

Направете обосновано предположение каква може да бъде нашата продукция.

Ако сте познали, undefined прав сте. Ако не сте, не се притеснявайте, скоро ще стигнем до дъното на това.

Ето как интерпретаторът разглежда горния код:

function hoist() {
  var message;
  console.log(message);
  message='Hoisting is all the rage!'
}

hoist(); // Ouput: undefined

Декларацията на променливата, var message, чийто обхват е функцията hoist(), се издига в горната част на функцията.

За да избегнем този капан, ще се погрижим да декларираме и инициализираме променливата, преди да я използваме:

function hoist() {
  var message='Hoisting is all the rage!'
  return (message);
}

hoist(); // Ouput: Hoisting is all the rage!

Строг режим

Благодарение на помощната програма на es5 версията на JavaScript, известна като строг режим, можем да бъдем по-внимателни относно това как декларираме нашите променливи. Като активираме строг режим, ние избираме ограничен вариант на JavaScript, който няма да толерира използването на променливи, преди да бъдат декларирани.

Изпълнение на нашия код в строг режим:

  1. Елиминира някои тихи грешки в JavaScript, като ги променя на изрични грешки при изхвърляне, които ще бъдат изплюти от интерпретатора.
  2. Коригира грешки, които затрудняват JavaScript двигателите да извършват оптимизации.
  3. Забранява някои синтаксиси, които вероятно ще бъдат дефинирани в бъдещи версии на JavaScript.

Разрешаваме строг режим, като предшестваме нашия файл или функция с

'use strict';
// OR
"use strict";

Нека го тестваме.

'use strict';
console.log(hoist); // Output: ReferenceError: hoist is not defined
hoist = 'Hoisted';

Можем да видим, че вместо да приемем, че сме пропуснали да декларираме нашата променлива, use strict ни е спрял, като изрично е хвърлил Reference error. Опитайте го без строго използване и вижте какво ще се случи.

Стриктният режим обаче се държи по различен начин в различните браузъри, така че е препоръчително да извършите цялостно тестване на функциите, преди да разчитате на него в производствената среда.

#ES6

ECMAScript 6, ECMAScript 2015, известен също като ES6, е най-новата версия на стандарта ECMAScript, тъй като написването на тази статия, януари 2017 и въвежда няколко промени в es5.

Интерес за нас е как промените в стандарта влияят върху декларирането и инициализирането на променливите на JavaScript.

позволявам

Преди да започнем, трябва да отбележим факта, че променливите, декларирани с ключовата дума let, са с блоков обхват, а не с функционален обхват. Това е важно, но не трябва да ни притеснява тук. Накратко обаче това просто означава, че обхватът на променливата е обвързан с блока, в който е декларирана, а не с функцията, в която е декларирана.

Нека започнем, като разгледаме поведението на ключовата дума let.

console.log(hoist); // Output: ReferenceError: hoist is not defined ...
let hoist = 'The variable has been hoisted.';

Както преди, за ключовата дума var очакваме изходът от дневника да бъде undefined. Въпреки това, тъй като es6 let не се отнася любезно към нас, използвайки недекларирани променливи, интерпретаторът изрично изплюва грешка Reference.

Това гарантира, че винаги първо декларираме нашите променливи.

Тук обаче все още трябва да внимаваме. Реализация като следната ще доведе до изход от undefined вместо Reference error.

let hoist;
console.log(hoist); // Output: undefined
hoist = 'Hoisted'

Следователно, за да бъдем внимателни, трябва да декларираме и след това да присвоим нашите променливи на стойност, преди да ги използваме.

конст

Ключовата дума const беше въведена в es6, за да позволи неизменни променливи. Тоест променливи, чиято стойност не може да бъде променена, след като бъде присвоена.

С const, точно както с let, променливата се издига в горната част на блока.

Нека да видим какво се случва, ако се опитаме да присвоим отново стойността, прикрепена към променлива const.

const PI = 3.142;
PI = 22/7; // Let's reassign the value of PI
console.log(PI); // Output: TypeError: Assignment to constant variable.

Как const променя декларацията на променлива? Нека да разгледаме.

console.log(hoist); // Output: ReferenceError: hoist is not defined
const hoist = 'The variable has been hoisted.';

Подобно на ключовата дума let, вместо безшумно излизане с undefined, интерпретаторът ни спасява, като изрично хвърля Reference error.

Същото се случва, когато използвате const във функции.

function getCircumference(radius) {
  console.log(circumference)
  circumference = PI*radius*2;
  const PI = 22/7;
}
getCircumference(2) // ReferenceError: circumference is not defined

С const es6 отива по-далеч. Интерпретаторът хвърля грешка, ако използваме константа, преди да я декларираме и инициализираме.

Нашият линтер също бързо ни информира за това престъпление:

PI was used before it was declared, which is illegal for const variables.

В световен мащаб,

const PI;
console.log(PI); // Ouput: SyntaxError: Missing initializer in const declaration
PI=3.142;

Следователно постоянната променлива трябва да бъде както декларирана, така и инициализирана преди употреба.

Като пролог към този раздел е важно да се отбележи, че наистина JavaScript повдига променливи, декларирани с es6 let и const. Разликата в този случай е как ги инициализира. Променливите, декларирани с let и const, остават неинициализирани в началото на изпълнението, докато променливите, декларирани с var, се инициализират с стойност на недефинирано.

# Подемни функции

Функциите на JavaScript могат да бъдат класифицирани свободно като следното:

  1. Декларации на функции
  2. Функционални изрази

Ще проучим как повдигането се влияе от двата типа функции.

Декларации на функции

Те са със следната форма и се повдигат изцяло до върха. Сега можем да разберем защо JavaScript ни позволява да извикаме функция привидно преди да я декларираме.

hoisted(); // Output: "This function has been hoisted."
function hoisted() {
  console.log('This function has been hoisted.');
};

Функционални изрази

Функционалните изрази обаче не се повдигат.

expression(); //Output: "TypeError: expression is not a function
var expression = function() {
  console.log('Will this work?');
};

Нека опитаме комбинацията от декларация на функция и израз.

expression(); // Ouput: TypeError: expression is not a function
var expression = function hoisting() {
  console.log('Will this work?');
};

Както можем да видим по-горе, декларацията на променливата е повдигната, но не е присвояване на функция. Следователно интерпретаторът хвърля TypeError, тъй като вижда expression като променлива, а не функция.

# Подемни класове

JavaScript класовете също могат да бъдат класифицирани свободно като:

  1. Декларации на класове
  2. Класови изрази

Декларации на класове

Подобно на своите функционални аналогове, декларациите на класове на JavaScript се повдигат. Те обаче остават неинициализирани до оценката. Това на практика означава, че трябва да декларирате клас, преди да можете да го използвате.

var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: ReferenceError: Hobbit is not defined

class Hobbit {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
  }
}

Сигурен съм, че сте забелязали, че вместо да получим undefined, ние получаваме Reference error. Това доказателство потвърждава нашата позиция, че декларациите за класове са повдигнати.

Ако обръщате внимание на вашия линтер, той ни дава полезен съвет.

Hobbit was used before it is declared, which is illegal for class variables

Така че, що се отнася до декларациите на класове, за да получите достъп до декларацията на класа, първо трябва да декларирате.

class Hobbit {
  constructor(height, weight) {
    this.height = height;
    this.weight = weight;
  }
}

var Frodo = new Hobbit();
Frodo.height = 100;
Frodo.weight = 300;
console.log(Frodo); // Output: { height: 100, weight: 300 }

Класови изрази

Подобно на своите функционални двойници, класовите изрази не се повдигат.

Ето един пример с неименуван или анонимен вариант на израза на класа.

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor

var Polygon = class {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Ето един пример с наименуван израз на клас.

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square); // Output: TypeError: Polygon is not a constructor


var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

Правилният начин да го направите е следният:

var Polygon = class Polygon {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
};

var Square = new Polygon();
Square.height = 10;
Square.width = 10;
console.log(Square);

# Заключение

Нека обобщим какво видяхме досега:

  1. Докато използвате es5 var, опитът да използвате недекларирани променливи ще доведе до присвояване на стойност на променливата undefined при повдигане.
  2. Докато използвате es6 let и const, използването на недекларирани променливи ще доведе до Референтна грешка, тъй като променливата остава неинициализирана при изпълнение.

Следователно,

  1. Трябва да си създадем навик да декларираме и инициализираме JavaScript променливите преди употреба.
  2. Използването на строг режим в JavaScript es5 може да помогне за излагането на недекларирани променливи.

Надявам се тази статия да послужи като добро въведение в концепцията за повдигане в JavaScript и да стимулира интереса ви към тънкостите на езика JavaScript.