Як працюють блокування JavaScript?

Як би ви пояснили закриття JavaScript для кого-то, у кого є знання про концепції, з яких вони складаються (наприклад, функції, змінні і т.п.), Але не розуміють самих замикань?

Я бачив приклад схеми, наведений у Вікіпедії, але, на жаль, це не допомогло.

7654
21 сент. заданий e-satis 21 сент. 2008-09-21 17:12 '08 о 17:12 2008-09-21 17:12
@ 89 відповідей
  • 1
  • 2
  • 3

Закриття JavaScript для початківців

Представлено Morris on Tue, 2006-02-21 10:19. Спільнота змінено з тих пір.

Закриття не чари

На цій сторінці пояснюється закриття, щоб програміст міг їх зрозуміти - використовуючи робочий код JavaScript. Це не для гуру або функціональних програмістів.

Закриття не складно зрозуміти, як тільки основне поняття буде зашито. Однак їх неможливо зрозуміти, прочитавши теоретичні або академічно обґрунтовані пояснення!

Ця стаття призначена для програмістів з деяким досвідом програмування на основній мові і може читати наступну функцію JavaScript:

першокласних функцій ;  це вираз, який може посилатися на змінні в межах своєї області (коли воно було оголошено раніше), призначатися змінної, передаватися як аргумент функції або повертатися як результат функції. 

приклад закриття

Наступний код повертає посилання на функцію:

 function say667() { // Local variable that ends up within closure var num = 42; var say = function() { console.log(num); } num++; return say; } var sayNumber = say667(); sayNumber(); // logs 43 

Приклад 4.

Всі три глобальних функції мають загальну посилання на один і той же закриття, тому що всі вони оголошені протягом одного виклику setupSomeGlobals() .

 function sayAlice() { var say = function() { console.log(alice); } // Local variable that ends up within closure var alice = 'Hello Alice'; return say; } sayAlice()();// logs "Hello Alice" 

Tricky: також зверніть увагу, say змінна say також знаходиться всередині замикання, і до неї може бути sayAlice() будь-яка інша функція, яка може бути оголошена в sayAlice() , або до неї можна було б отримати рекурсивно всередині внутрішньої функції.

Приклад 6.

Це для всіх людей справжня магія, тому вам потрібно це зрозуміти. Будьте дуже обережні, якщо ви визначаєте функцію всередині циклу: локальні змінні з замикання можуть не діяти так, як ви могли б спочатку подумати.

Вам потрібно зрозуміти функцію "змінної підйому" в Javascript, щоб зрозуміти цей приклад.

 function newClosure(someNum, someRef) { // Local variables that end up within closure var num = someNum; var anArray = [1,2,3]; var ref = someRef; return function(x) { num += x; anArray.push(num); console.log('num: ' + num + '; anArray: ' + anArray.toString() + '; ref.someVar: ' + ref.someVar + ';'); } } obj = {someVar: 4}; fn1 = newClosure(4, obj); fn2 = newClosure(5, obj); fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4; fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4; obj.someVar++; fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5; fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5; 

резюме

Якщо все здається абсолютно незрозумілим, найкраще пограти з прикладами. Читання пояснень набагато складніше, ніж розуміння прикладів. Мої пояснення закриттів і стекових фреймів і т.д. Чи не є технічно правильними - це грубі спрощення, покликані допомогти зрозуміти. Після того, як основна ідея буде вирішена, ви можете отримати деталі пізніше.

Кінцеві пункти:

  • Всякий раз, коли ви використовуєте function всередині іншої функції, використовується закриття.
  • Всякий раз, коли ви використовуєте eval() всередині функції, використовується закриття. Текст, який ви eval може посилатися на локальні змінні функції, і в eval ви можете навіть створювати нові локальні змінні за допомогою eval('var foo = …')
  • Коли ви використовуєте new Function(…) ( конструктор функції ) всередині функції, вона не створює закриття. (Нова функція не може посилатися на локальні змінні зовнішньої функції.)
  • Закриття в JavaScript подібно зберігання копії всіх локальних змінних, як і при виході з функції.
  • Ймовірно, краще за все подумати, що замикання завжди створюється як запис функції, а локальні змінні додаються до цього закриття.
  • Новий набір локальних змінних зберігається кожного разу, коли викликається функція з замиканням (враховуючи, що функція містить всередині неї декларацію функції, або посилання на цю внутрішню функцію або повернена, або зовнішнє посилання зберігається для неї якимось чином).
  • Дві функції можуть виглядати так, як ніби вони мають один і той же вихідний текст, але мають зовсім іншу поведінку через їх прихованого закриття. Я не думаю, що JavaScript-код дійсно може дізнатися, чи є у функції посилання закриття чи ні.
  • Якщо ви намагаєтеся виконати будь-які зміни динамічного вихідного коду (наприклад: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola')); ), він не буде працювати, якщо myFunction є закриттям (звичайно, ви навіть не подумали б про заміну рядків вихідного коду під час виконання, але ...).
  • Можна отримати оголошення функцій всередині декларацій функцій всередині функцій mdash, і ви можете отримати закриття на більш ніж одному рівні.
  • Я думаю, що зазвичай замикання є терміном як для функції, так і для захоплених змінних. Зверніть увагу, що я не використовую це визначення в цій статті!
  • Я підозрюю, що замикання в JavaScript відрізняються від тих, які зазвичай зустрічаються на функціональних мовами.

зв'язку

завдяки

Якщо ви тільки що дізналися про закриття (тут або де-небудь ще!), То мене цікавить будь-яка зворотний зв'язок від вас про будь-які зміни, які ви могли б запропонувати, щоб зробити цю статтю яснішою. Надіслати повідомлення для morrisjohns.com (morris_closure @). Зверніть увагу, що я не гуру на JavaScript - ні на закритті.


Оригінальний пост Морріса можна знайти в Інтернет-архіві .

6329
21 сент. відповідь дан Joel Anair 21 сент. 2008-09-21 17:18 '08 о 17:18 2008-09-21 17:18

Всякий раз, коли ви бачите ключове слово function в інший функції, внутрішня функція має доступ до змінних у зовнішній функції.

 function foo(x) { var tmp = 3; return function (y) { console.log(x + y + (++tmp)); // will also log 16 } } var bar = foo(2); // bar is now a closure. bar(10); 

Вищезгадана функція також буде записувати 16, тому що bar все ще може посилатися на x і tmp , навіть якщо він більше не знаходиться всередині області.

Однак, оскільки tmp все ще висить навколо закриття внутрішньої bar , він також збільшується. Він буде збільшуватися кожного разу, коли ви викликаєте bar .

Найпростішим прикладом замикання є наступне:

border=0

 function foo(x) { var tmp = 3; return function (y) { console.log(x + y + tmp); x.memb = x.memb ? x.memb + 1 : 1; console.log(x.memb); } } var age = new Number(2); var bar = foo(age); // bar is now a closure referencing age. bar(10); 

Як і очікувалося, кожен виклик bar(10) буде збільшувати x.memb . Не можна очікувати, що x просто посилається на той же об'єкт, що і age змінна! Після пари дзвінків в bar age.memb буде 2! Це посилання є основою для витоків пам'яті з об'єктами HTML.

3825
21 сент. відповідь дан Ali 21 сент. 2008-09-21 18:16 '08 о 18:16 2008-09-21 18:16

ПЕРЕДМОВА: цей відповідь була написана, коли питання було:

Як і старий Альберт, він сказав: "Якщо ви не можете пояснити це шестирічній дитині, ви самі цього не розумієте". Добре, я спробував пояснити закриття JS 27-річному одного і повністю зазнав невдачі.

Чи може хто-небудь подумати, що мені 6, і дивно цікавиться цим питанням?

Я майже впевнений, що я був одним з тих людей, які намагалися взяти початковий питання буквально. З тих пір це питання кілька разів мутував, тому моя відповідь тепер може здаватися неймовірно дурним і недоречним. Сподіваюся, загальна ідея цієї історії залишається для деяких цікавою.


Я великий шанувальник аналогії і метафори при поясненні складних понять, тому дозвольте мені спробувати свої сили в історії.

Давним давно:

Була принцеса ...

 function princess() { 

Вона жила в прекрасному світі, повному пригод. Вона зустріла свого принца Шарля, поїхала навколо свого світу на єдинорога, боролася з драконами, зустрічалася з промовистими тваринами і багатьма іншими фантастичними речами.

  var adventures = []; function princeCharming() {  } var unicorn = {  }, dragons = [  ], squirrel = "Hello!";  

Але їй завжди доводилося повертатися в свій нудний світ клопоту і дорослих.

  return { 

І вона часто розповідала їм про свій останній дивовижному пригоді як принцеси.

  story: function() { return adventures[adventures.length - 1]; } }; } 

Але все, що вони побачили, це маленька дівчинка ...

 var littleGirl = princess(); 

... розповідаючи історії про магію і фантазії.

 littleGirl.story(); 

І хоча дорослі знали про справжніх принцес, вони ніколи не повірили б в єдинорогів або драконів, тому що вони ніколи їх не побачили. Дорослі сказали, що вони існують тільки всередині уяви маленької дівчинки.

Але ми знаємо справжню істину; що маленька дівчинка з принцесою всередині ...

... насправді принцеса з маленькою дівчинкою всередині.

2273
24 июня '11 в 21:49 2011-06-24 21:49 відповідь дан Jacob Swartwood 24 червня '11 о 21:49 2011-06-24 21:49

Беручи це питання серйозно, ми повинні з'ясувати, що типовий 6-річний чоловік здатний когнітивно, хоча, за загальним визнанням, той, хто цікавиться JavaScript, не так типовий.

Про розвиток дитинства: від 5 до 7 років говориться:

Ваша дитина зможе слідувати двохетапним напрямками. Наприклад, якщо ви скажете своїй дитині: "Ідіть на кухню і візьміть мішок для сміття", вони зможуть запам'ятати цей напрямок.

Ми можемо використовувати цей приклад для пояснення замикань наступним чином:

Кухня - це закриття, в якому є локальна змінна, звана trashBags . Всередині кухні є функція getTrashBag яка отримує один мішок для сміття і повертає його.

Ми можемо кодувати це в JavaScript наступним чином:

696
02 сент. відповідь дан dlaliberte 02 сент. 2011-09-02 18:23 '11 о 18:23 2011-09-02 18:23

солом'яний чоловік

Мені потрібно знати, скільки разів натиснута кнопка і щось робити на кожному третьому кліці ...

Досить очевидне рішення

 <button id="button">Click Me!</button> 

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

Рассмотрим этот вариант

 <button id="button">Click Me!</button>