Що таке короткий вступ до лексичного обстеження?
Що таке короткий вступ до лексичного обстеження?
Відповіді:
Я розумію їх на прикладах. :)
По-перше, лексична сфера (також її називають статичною сферою ) у C-подібному синтаксисі:
void fun()
{
int x = 5;
void fun2()
{
printf("%d", x);
}
}
Кожен внутрішній рівень може отримати доступ до своїх зовнішніх рівнів.
Існує ще один спосіб, який називається динамічним діапазоном, використовуваним першою реалізацією Lisp , знову в C-подібному синтаксисі:
void fun()
{
printf("%d", x);
}
void dummy1()
{
int x = 5;
fun();
}
void dummy2()
{
int x = 10;
fun();
}
Тут fun
можна або отримати доступ x
до dummy1
або dummy2
, або будь-яку x
в будь-яку функцію, яка дзвонить fun
із x
заявленим в ній.
dummy1();
буде надруковано 5,
dummy2();
надрукує 10.
Перший називається статичним, оскільки його можна вивести під час компіляції, а другий - динамічним, оскільки зовнішня область є динамічною і залежить від ланцюгового виклику функцій.
Я вважаю, що статичне обстеження легше для ока. Більшість мов зрештою пішли цим шляхом, навіть Лісп (можна зробити обидві, правда?). Динамічне масштабування - це як передача посилань усіх змінних на викликану функцію.
Як приклад того, чому компілятор не може вивести зовнішню динамічну сферу функції, розглянемо наш останній приклад. Якщо ми пишемо щось подібне:
if(/* some condition */)
dummy1();
else
dummy2();
Ланцюг виклику залежить від стану часу виконання. Якщо це правда, то ланцюжок викликів виглядає так:
dummy1 --> fun()
Якщо умова помилкова:
dummy2 --> fun()
Зовнішня сфера застосування fun
в обох випадках викликає дзвінок плюс дзвінок, що телефонує, і так далі .
Зазначимо лише, що мова С не дозволяє вкладених функцій, ні динамічного масштабування.
JavaScript
. Тому я вважаю, що це не слід позначати як прийняту відповідь. Лексична сфера конкретно в JS відрізняється
for
типова проблема всередині циклу. Лексична область для JavaScript тільки на функціональному рівні , якщо тільки ES6 let
або const
використовується.
Давайте спробуємо якомога коротше визначення:
Лексичне оцінювання визначає, як імена змінних розв’язуються в вкладених функціях: внутрішні функції містять область батьківських функцій, навіть якщо батьківська функція повернулася .
Це все, що там є!
var scope = "I am global";
function whatismyscope(){
var scope = "I am just a local";
function func() {return scope;}
return func;
}
whatismyscope()()
Наведений вище код поверне "Я просто місцевий". Це не повернеться "Я глобальний". Оскільки функція func () підраховує те, де спочатку було визначено, що знаходиться в межах функції Whatismyscope.
Це не буде турбувати те, що його називають (загальний масштаб / навіть в межах іншої функції), тому значення глобального масштабу I I global не друкується.
Це називається лексичним обстеженням, де " функції виконуються за допомогою ланцюга діапазону, який діяв, коли вони були визначені " - відповідно до Посібника з визначення JavaScript.
Лексичний обсяг - це дуже дуже потужне поняття.
Сподіваюся, це допомагає .. :)
Лексичний (AKA статичний) масштаб відноситься до визначення сфери застосування змінної виходячи виключно з її положення в текстовому корпусі коду. Змінна завжди посилається на її середовище верхнього рівня. Це добре зрозуміти стосовно динамічної сфери.
Область застосування визначає область, де доступні функції, змінні тощо. Наприклад, доступність змінної визначається в її контексті, скажімо, функція, файл або об'єкт, вони визначені в. Ми зазвичай називаємо ці локальні змінні.
Лексична частина означає, що ви можете отримати сферу застосування, прочитавши вихідний код.
Лексичний обсяг також відомий як статичний обсяг.
Динамічний діапазон визначає глобальні змінні, які можна викликати або посилати з будь-якого місця після їх визначення. Іноді їх називають глобальними змінними, навіть незважаючи на те, що глобальні змінні в більшості програмних мов мають лексичну сферу. Це означає, що зчитування коду може бути отримано, що змінна є доступною в цьому контексті. Можливо, потрібно знайти довідку про використання або включення, щоб знайти встановлення або визначення, але код / компілятор знає про змінну в цьому місці.
У динамічному масштабуванні, навпаки, ви спочатку шукаєте локальну функцію, потім шукаєте функцію, яка викликала локальну функцію, потім шукаєте в тій функції, яка викликала цю функцію тощо, вгору до стеку викликів. "Динамічний" означає зміну, оскільки стек виклику може бути різним щоразу, коли викликається певна функція, і тому функція може вражати різні змінні залежно від того, звідки вона викликана. (див. тут )
Щоб побачити цікавий приклад для динамічної області, дивіться тут .
Детальніше дивіться тут і тут .
Деякі приклади в Delphi / Object Pascal
Delphi має лексичну сферу застосування.
unit Main;
uses aUnit; // makes available all variables in interface section of aUnit
interface
var aGlobal: string; // global in the scope of all units that use Main;
type
TmyClass = class
strict private aPrivateVar: Integer; // only known by objects of this class type
// lexical: within class definition,
// reserved word private
public aPublicVar: double; // known to everyboday that has access to a
// object of this class type
end;
implementation
var aLocalGlobal: string; // known to all functions following
// the definition in this unit
end.
Найближчий Delphi до динамічної сфери - пара функцій RegisterClass () / GetClass (). Про його використання дивіться тут .
Скажімо, час виклику RegisterClass ([TmyClass]) для реєстрації певного класу неможливо передбачити, прочитавши код (він викликається методом натискання кнопки, який називається користувачем), і отримає код GetClass ('TmyClass'). результат чи ні. Виклик до RegisterClass () не повинен бути в лексичній області блоку за допомогою GetClass ();
Іншою можливістю для динамічної області застосування є анонімні методи (закриття) у Delphi 2009, оскільки вони знають змінні своєї функції виклику. Він не слідує шляху виклику звідти рекурсивно і тому не є повністю динамічним.
Я люблю повнофункціональні мовно-агностичні відповіді людей, як-то @Arak. Оскільки це питання позначене тегом JavaScript , я хотів би чіпнути деякі нотатки, дуже характерні для цієї мови.
У JavaScript нами вибір:
var _this = this; function callback(){ console.log(_this); }
callback.bind(this)
Варто зазначити, я думаю, що JavaScript насправді не має динамічного масштабування . .bind
коригує this
ключове слово, і це близько, але технічно не те саме.
Ось приклад, що демонструє обидва підходи. Ви робите це кожен раз, коли приймаєте рішення про те, як розгорнути зворотні дзвінки, щоб це стосувалося обіцянок, обробників подій тощо.
Ось, що можна назвати Lexical Scoping
зворотними дзвінками в JavaScript:
var downloadManager = {
initialize: function() {
var _this = this; // Set up `_this` for lexical access
$('.downloadLink').on('click', function () {
_this.startDownload();
});
},
startDownload: function(){
this.thinking = true;
// Request the file from the server and bind more callbacks for when it returns success or failure
}
//...
};
Ще одним способом розширення є використання Function.prototype.bind
:
var downloadManager = {
initialize: function() {
$('.downloadLink').on('click', function () {
this.startDownload();
}.bind(this)); // Create a function object bound to `this`
}
//...
Ці методи, наскільки я знаю, поведінково рівнозначні.
bind
не впливає на область застосування.
IBM визначає це як:
Частина програми або сегментної одиниці, до якої застосовується декларація. Ідентифікатор, оголошений у рутині, відомий у межах цієї програми та у всіх вкладених процедурах. Якщо вкладена рутина оголошує елемент з тим самим іменем, зовнішній елемент недоступний у вкладеній програмі.
Приклад 1:
function x() {
/*
Variable 'a' is only available to function 'x' and function 'y'.
In other words the area defined by 'x' is the lexical scope of
variable 'a'
*/
var a = "I am a";
function y() {
console.log( a )
}
y();
}
// outputs 'I am a'
x();
Приклад 2:
function x() {
var a = "I am a";
function y() {
/*
If a nested routine declares an item with the same name,
the outer item is not available in the nested routine.
*/
var a = 'I am inner a';
console.log( a )
}
y();
}
// outputs 'I am inner a'
x();
Лексична сфера означає, що у вкладеній групі функцій внутрішні функції мають доступ до змінних та інших ресурсів їх батьківського обсягу . Це означає, що функції дитини лексично пов'язані з контекстом виконання їхніх батьків. Лексичний обсяг іноді також називають статичним .
function grandfather() {
var name = 'Hammad';
// 'likes' is not accessible here
function parent() {
// 'name' is accessible here
// 'likes' is not accessible here
function child() {
// Innermost level of the scope chain
// 'name' is also accessible here
var likes = 'Coding';
}
}
}
Що ви помітите про лексичну область - це те, що воно працює вперед, тобто ім'я може бути доступне контекстами виконання його дітьми. Але це не працює назад до батьків, це означає, що батьки likes
не можуть отримати доступ до змінної .
Це також говорить нам, що змінні, що мають однакове ім'я в різних контекстах виконання, отримують перевагу зверху внизу стеку виконання. Змінна, що має ім’я, подібне до іншої змінної, у найпотаємнішій функції (найвищий контекст стеку виконання) матиме вищий пріоритет.
Зауважте, що це взято звідси .
Простою мовою, лексична область - це змінна, визначена за межами вашої області, або верхня область автоматично доступна всередині вашої області, а це означає, що вам не потрібно вводити її туди.
Приклад:
let str="JavaScript";
const myFun = () => {
console.log(str);
}
myFun();
// Вихід: JavaScript
bind
. З ними bind
більше не потрібно. Для отримання додаткової інформації про цю перевірку зміни stackoverflow.com/a/34361380/11127383
Існує важлива частина розмови, що стосується лексичного та динамічного масштабування , якого немає: чітке пояснення тривалості масштабованої змінної - або коли можна отримати доступ до змінної.
Динамічне масштабування лише дуже вільно відповідає "глобальному" масштабуванню в тому, як ми традиційно думаємо про це (причина, за якою я привожу порівняння між ними, - це те, що вже згадувалося - і мені особливо не подобається пов'язане пояснення статті ); це, мабуть, найкраще, ми не робимо порівняння між глобальним та динамічним, хоча, згідно з прив’язаною статтею, нібито, "... [це] корисно як заміна глобально змінених змінних".
Отже, простою англійською мовою, яка важлива відмінність між двома механізмами оцінювання?
Лексичний обсяг був визначений дуже добре у відповідях, наведених вище: змінні, розміщені в межах лексичної точки зору, доступні - або доступні - на локальному рівні функції, в якій вона була визначена.
Однак - оскільки це не фокус ОП - динамічне оцінювання не приділяло великої уваги, а привернутої увазі означає, що йому, мабуть, потрібно трохи більше (це не критика інших відповідей, а скоріше "о, що відповідь, яку ми хотіли б, щоб було трохи більше "). Отже, ось ще трохи:
Динамічне масштабування означає, що змінна є доступною для більшої програми протягом життя виклику функції - або під час виконання функції. Дійсно, Вікіпедія насправді справляється з приємною роботою з поясненням різниці між ними. Щоб не придумати це, ось текст, який описує динамічне масштабування:
... [I] n динамічного масштабування (або динамічного діапазону), якщо сфера імені змінної є певною функцією, то її область - це період часу, протягом якого функція виконує функцію: поки функція працює, ім'я змінної існує , і пов'язаний зі своєю змінною, але після повернення функції ім'я змінної не існує.
Лексичний обсяг означає, що функція шукає змінні в контексті, де вона була визначена, а не в області безпосередньо навколо неї.
Подивіться, як працює лексичний обсяг у Lisp, якщо ви хочете більш детально. Вибрана відповідь Кайла Кроніна в динамічних та лексичних змінних у Common Lisp набагато чіткіша, ніж відповіді тут.
Випадково я дізнався про це лише в класі Lisp, і це трапляється застосувати і в JavaScript.
Я запустив цей код у консолі Chrome.
// JavaScript Equivalent Lisp
var x = 5; //(setf x 5)
console.debug(x); //(print x)
function print_x(){ //(defun print-x ()
console.debug(x); // (print x)
} //)
(function(){ //(let
var x = 10; // ((x 10))
console.debug(x); // (print x)
print_x(); // (print-x)
})(); //)
Вихід:
5
10
5
Лексична область в JavaScript означає, що змінна, визначена поза функцією, може бути доступною всередині іншої функції, визначеної після оголошення змінної. Але навпаки не вірно; змінні, визначені всередині функції, не будуть доступні поза цією функцією.
Ця концепція широко використовується при закритті JavaScript.
Скажімо, у нас є наведений нижче код.
var x = 2;
var add = function() {
var y = 1;
return x + y;
};
Тепер, коли ви зателефонуєте add () ->, це надрукує 3.
Отже, функція add () отримує доступ до глобальної змінної, x
яка визначена перед методом add. Це називається завдяки лексичному обстеженню в JavaScript.
add()
функція, де викликується відразу за вказаним фрагментом коду, вона також надрукує 3. Лексичне масштабування не означає просто, що функція може отримати доступ до глобальних змінних за межами локального контексту. Отже, приклад коду насправді не допомагає показати, що означає лексичне визначення. Показ лексичного обстеження в коді дійсно потребує зустрічного прикладу або принаймні пояснення інших можливих тлумачень коду.
Лексична область стосується лексики ідентифікаторів (наприклад, змінних, функцій тощо), видимих з поточного положення в стеку виконання.
- global execution context
- foo
- bar
- function1 execution context
- foo2
- bar2
- function2 execution context
- foo3
- bar3
foo
і bar
завжди знаходяться в лексиці доступних ідентифікаторів, оскільки вони глобальні.
Коли function1
виконується, він має доступ до лексики foo2
, bar2
, foo
, і bar
.
Коли function2
виконується, він має доступ до лексики foo3
, bar3
, foo2
, bar2
, foo
, і bar
.
Причина, що глобальна та / або зовнішня функції не мають доступу до внутрішніх ідентифікаторів функцій, полягає в тому, що виконання цієї функції ще не відбулося, і тому жоден з її ідентифікаторів не був виділений в пам'ять. Більше того, щойно внутрішній контекст виконується, він видаляється із стеку виконання, тобто всі його ідентифікатори були зібрані сміттям і більше не доступні.
Нарешті, саме тому вкладений контекст виконання ВЖЕ завжди може отримати доступ до контексту виконання його предків, і, отже, він має доступ до більшого лексикону ідентифікаторів.
Подивитися:
Особлива подяка @ robr3rd за допомогу у спрощенні наведеного визначення.
Ось інший кут у цьому питанні, який ми можемо досягти, зробивши крок назад і подивившись на роль масштабування в більш широких рамках інтерпретації (запуск програми). Іншими словами, уявіть, що ви будували інтерпретатор (або компілятор) для мови і відповідали за обчислення результатів, дали програму та деякий вклад до неї.
Інтерпретація передбачає відстеження трьох речей:
Стан - а саме змінні та посилання на пам'ять у купі та стеку.
Операції в цьому стані, а саме кожен рядок коду у вашій програмі
Середа , в якій дана операція виконується , а саме - проекція стану на операцію.
Інтерпретатор починається з першого рядка коду програми, обчислює його середовище, запускає рядок у цьому середовищі та фіксує його вплив на стан програми. Потім слід керувати потоком програми для виконання наступного рядка коду та повторює процес до кінця програми.
Спосіб обчислення середовища для будь-якої операції здійснюється за допомогою формального набору правил, визначених мовою програмування. Термін "зв'язування" часто використовується для опису відображення загального стану програми до значення в навколишньому середовищі. Зауважимо, що під загальним станом ми маємо на увазі не глобальну державу, а скоріше загальну суму кожного досяжного визначення у будь-якій точці виконання).
Це рамка, в якій визначена проблема масштабування. Тепер до наступної частини наших варіантів.
Це суть динамічного масштабування , де середовище, в якому працює будь-який код, пов'язане зі станом програми, визначеним контекстом його виконання.
Іншими словами, з лексичним розмахом середовище, яке бачить будь-який код, пов'язане із станом, пов'язаним із сферою дії, визначеною явно мовою, наприклад блоком або функцією.
Стародавнє питання, але ось мій погляд на це.
Лексична (статична) область стосується сфери змінної у вихідному коді .
Такою мовою, як JavaScript, де функції можна передавати навколо, додавати та повторно приєднувати до різних об'єктів, можливо, ви хочете, що цей обсяг залежатиме від того, хто викликає функцію в той час, але це не так. Зміна області застосування таким чином буде динамічною областю, і JavaScript цього не робить, за винятком випадків, якщо вони this
посилаються на об'єкт.
Для ілюстрації пункту:
var a='apple';
function doit() {
var a='aardvark';
return function() {
alert(a);
}
}
var test=doit();
test();
У прикладі змінна a
визначена глобально, але у тіні doit()
функція є тіньовою . Ця функція повертає ще одну функцію, яка, як бачите, покладається наa
змінну поза її власною областю.
Якщо запустити це, ви побачите, що використовуване значення є aardvark
, а не apple
яке, хоча воно і входить до сфери використанняtest()
функції, не входить до лексичної сфери вихідної функції. Тобто, використовувана область - це сфера, яка відображається у вихідному коді, а не область, де функція фактично використовується.
Цей факт може мати дратівливі наслідки. Наприклад, ви можете вирішити, що простіше організувати свої функції окремо, а потім використовувати їх, коли настане час, наприклад, в обробці подій:
var a='apple',b='banana';
function init() {
var a='aardvark',b='bandicoot';
document.querySelector('button#a').onclick=function(event) {
alert(a);
}
document.querySelector('button#b').onclick=doB;
}
function doB(event) {
alert(b);
}
init();
<button id="a">A</button>
<button id="b">B</button>
Цей зразок коду робить один з кожного. Ви можете бачити, що завдяки лексичному оцінці кнопка A
використовує внутрішню змінну, а кнопкаB
- ні. Ви можете закінчити функції гніздування більше, ніж хотілося б.
До речі, в обох прикладах ви також помітите, що внутрішні лексично змінені змінні зберігаються, навіть незважаючи на те, що функція, що містить функцію, виконала свій хід. Це називається закриттям і стосується доступу вкладеної функції до зовнішніх змінних, навіть якщо зовнішня функція закінчена. JavaScript повинен бути достатньо розумним, щоб визначити, чи не потрібні ці змінні, і якщо ні, чи зможе їх збирати сміття.
Я зазвичай вчуся на прикладі, і ось трохи:
const lives = 0;
function catCircus () {
this.lives = 1;
const lives = 2;
const cat1 = {
lives: 5,
jumps: () => {
console.log(this.lives);
}
};
cat1.jumps(); // 1
console.log(cat1); // { lives: 5, jumps: [Function: jumps] }
const cat2 = {
lives: 5,
jumps: () => {
console.log(lives);
}
};
cat2.jumps(); // 2
console.log(cat2); // { lives: 5, jumps: [Function: jumps] }
const cat3 = {
lives: 5,
jumps: () => {
const lives = 3;
console.log(lives);
}
};
cat3.jumps(); // 3
console.log(cat3); // { lives: 5, jumps: [Function: jumps] }
const cat4 = {
lives: 5,
jumps: function () {
console.log(lives);
}
};
cat4.jumps(); // 2
console.log(cat4); // { lives: 5, jumps: [Function: jumps] }
const cat5 = {
lives: 5,
jumps: function () {
var lives = 4;
console.log(lives);
}
};
cat5.jumps(); // 4
console.log(cat5); // { lives: 5, jumps: [Function: jumps] }
const cat6 = {
lives: 5,
jumps: function () {
console.log(this.lives);
}
};
cat6.jumps(); // 5
console.log(cat6); // { lives: 5, jumps: [Function: jumps] }
const cat7 = {
lives: 5,
jumps: function thrownOutOfWindow () {
console.log(this.lives);
}
};
cat7.jumps(); // 5
console.log(cat7); // { lives: 5, jumps: [Function: thrownOutOfWindow] }
}
catCircus();
Ця тема сильно пов'язана із вбудованою bind
функцією та введена у функції ECMAScript 6 Arrow . Це було дуже прикро, тому що для кожного нового методу "класу" (фактично функція), який ми хотіли використати, ми повинні були bind
це зробити, щоб мати доступ до області застосування.
JavaScript за замовчуванням не встановлює його обсяг this
на функції (вона не встановлює контекст на this
). За замовчуванням ви повинні чітко сказати, який контекст ви хочете мати.
Функції стрілки автоматично набувають так звану лексичну область (мають доступ до визначення змінної у її блоці, що містить). При використанні функцій стрілки вона автоматично прив'язується this
до місця, де в першу чергу була визначена функція стрілки, а контекст цієї функції стрілки - її містить блок.
Подивіться, як це працює на практиці на найпростіших прикладах нижче.
Перед функціями стрілки (без лексичного діапазону за замовчуванням):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const globalScope = programming.getLanguage;
console.log(globalScope()); // Output: undefined
const localScope = programming.getLanguage.bind(programming);
console.log(localScope()); // Output: "JavaScript"
За допомогою функцій зі стрілками (лексичний обсяг за замовчуванням):
const programming = {
language: "JavaScript",
getLanguage: function() {
return this.language;
}
}
const arrowFunction = () => {
console.log(programming.getLanguage());
}
arrowFunction(); // Output: "JavaScript"