Loading...

замыкание

        замыкание

 

JavaScript - бул функцияга багытталган күчтүү тил. Ал бизге көп эркиндик берет. Функция динамикалык түрдө түзүлүп, башка өзгөрмөгө көчүрүлүп же башка функцияга аргумент катары берилип, кийинчерээк таптакыр башка жерден чакырылышы мүмкүн.

Функция тышкы чөйрөдөн өзгөрмөлөргө кире аларын билебиз, бул өзгөчөлүк абдан көп колдонулат.

Бирок тышкы өзгөрмөлөр өзгөргөндө эмне болот? Функция акыркы мааниге ээ болобу же функция түзүлгөн учурда бар болгонбу?

Ал эми функция коддун башка жерине көчүп, ошол жерден чакырылганда эмне болот - ал өзүнүн жаңы жайгашкан жеринин тышкы өзгөрмөлөрүнө кире алабы?

Мындай учурларда ар кандай тилдер өзүн башкача алып жүрүшөт жана бул бөлүмдө биз JavaScriptтин жүрүм-турумун карайбыз.

Бир-эки суроо

Биринчиден, эки жагдайды карап көрөлү, андан кийин ички механизмдерди этап-этабы менен изилдеп чыгабыз, ошондо сиз келечекте ушул жана татаал суроолорго жооп бере аласыз.

  1. Функция sayHiтышкы өзгөрмө колдонот name. Функция аткарылганда кандай маанини колдонот?

    let name = "John";
    
    function sayHi() {
      alert("Hi, " + name);
    }
    
    name = "Pete";
    
    sayHi(); // что будет показано: "John" или "Pete"?

    Мындай жагдайлар браузерде да, серверде да көп кездешет. Функция түзүлгөндөн кийин, мисалы, колдонуучунун кандайдыр бир аракетинен же тармак сурамынан кийин иштөөнү пландаштырса болот.

    Демек, суроо, ал акыркы өзгөрүүлөргө мүмкүнчүлүк алат?

  2. Функция makeWorkerбашка функцияны түзүп, аны кайтарат. Жаңы функцияны башка жерден чакырса болот. Ал түзүлгөн жерден, же аткарылган жерден, же экөө тең тышкы өзгөрмөлөргө кире алабы?

    function makeWorker() {
      let name = "Pete";
    
      return function() {
        alert(name);
      };
    }
    
    let name = "John";
    
    // create a function
    let work = makeWorker();
    
    // call it
    work(); // что будет показано? "Pete" (из места создания) или "John" (из места выполнения)

Лексикалык чөйрө

Эмне болуп жатканын түшүнүү үчүн, келгиле, адегенде "өзгөрмө" деген эмне экенин талкуулайлы.

JavaScript-те ар бир аткарылуучу функциянын, код блогунун жана скрипттин аны менен байланышкан ички (жашыруун) объектиси бар, ал лексикалык чөйрө LexicalEnvironment деп аталат .

Лексикалык чөйрө объектиси эки бөлүктөн турат:

  1. Environment Record - бул бардык локалдык өзгөрмөлөрдү касиеттер катары сактаган объект (ошондой эле башка маалымат, мисалы, value this).

  2. Сырткы лексикалык чөйрөгө шилтеме - башкача айтканда, сырттагы кодго дал келген (учурдагы тармал кашаалардын сыртында).

"Өзгөрмө" бул жөн гана өзгөчө ички объекттин менчиги: Айлана-чөйрөнүн рекорду. "Өзгөрмөлөрдү алуу же өзгөртүү" "бул объектинин касиетин алуу же өзгөртүү" дегенди билдирет.

Мисалы, бул жөнөкөй коддо бир гана лексикалык чөйрө бар:

 

Бул бүткүл скрипт менен байланышкан глобалдык лексикалык чөйрө деп аталат.

Жогорудагы сүрөттө тик бурчтук Environment Record (өзгөрмө сактагыч), ал эми жебе тышкы чөйрөгө шилтемени билдирет. Глобалдык лексикалык чөйрөнүн сырткы чөйрөсү жок, ошондуктан ал null.

Ал эми өзгөрмө жарыялоодо жана дайындоодо кандай өзгөрөт:

 

Оң жактагы кутучалар кодду аткаруу учурунда глобалдык лексикалык чөйрөнүн кандай өзгөрөрүн көрсөтөт:

  1. Сценарийдин башында лексикалык чөйрө бош.
  2. Өзгөрмө аныктамасы пайда болот let phrase. Анын белгиленген мааниси жок, ошондуктан undefined.
  3. Өзгөрмө phraseмаани ыйгарылган.
  4. Өзгөрмө phraseмаанини өзгөртөт.

Азырынча баары жөнөкөй көрүнөт, туурабы?

Бардыгы:

  • Өзгөрмө - бул учурда аткарылып жаткан блок/функция/скрипт менен байланышкан атайын ички объектинин касиети.
  • Өзгөрмөлөр менен иштөө бул объектинин касиеттери менен иштөө болуп саналат.

Функция декларациясы

Азырынча биз өзгөрмөлөрдү гана карап чыктык. Эми Функциянын Декларациясын карап көрөлү.

менен жарыяланган өзгөрмөлөрдөн айырмаланып let, алар аткаруу аларга жеткенде толук инициализацияланбайт, бирок лексикалык чөйрө түзүлгөндөн мурда.

Жогорку деңгээлдеги функциялар үчүн бул скрипт аткарыла баштаган учурду билдирет.

Мына ошондуктан биз Функциянын декларациясы менен жарыяланган функцияны аны аныктай электе чакыра алабыз.

Төмөнкү код лексикалык чөйрөдө башынан эле бир нерсе бар экенин көрсөтүп турат. sayБул Функция Декларациясы болгондуктан бар . Кийинчерээк пайда болот phrase, аркылуу жарыяланган let:

 

Ички жана тышкы лексикалык чөйрө

Эми функция тышкы өзгөрмөгө киргенде эмне болорун карап көрөлү.

Чалуу учурунда say()тышкы өзгөрмө колдонот phrase. Келгиле, эмне болуп жатканын майда-чүйдөсүнө чейин карап көрөлү.

Функция ишке киргенде, ал үчүн локалдык өзгөрмөлөрдү жана чакыруу параметрлерин сактоо үчүн автоматтык түрдө жаңы лексикалык чөйрө түзүлөт.

Мисалы, say("John")ал төмөнкүдөй көрүнөт (аткаруу жебе менен белгиленген сызыкта):

 

Ошентип, функцияны чакыруу процессинде бизде эки лексикалык чөйрө бар: ички (чакырылган функция үчүн) жана тышкы (глобалдык):

  • Ички лексикалык чөйрө учурдагы аткарылууга туура келет say.

    nameАнда функциянын аргументи болгон бир өзгөрмө бар . Биз чакырабыз say("John"), ошондуктан өзгөрмөнүн мааниси nameболот "John".

  • Сырткы лексикалык чөйрө – глобалдык лексикалык чөйрө.

    Ал өзгөрмө phraseжана функциянын өзүн камтыйт.

Ички лексикалык чөйрөнүн outerтышкы чөйрөгө байланышы бар.

Код өзгөрмөгө киргиси келгенде, алгач ички лексикалык чөйрөдө, андан кийин тышкы чөйрөдө, андан кийин кийинкисинде жана башка глобалдык чөйрөгө чейин издейт.

Эгерде өзгөрмө табылбай калса, анда ката болот strict mode. Болбосо strict mode, артка карай шайкештик үчүн, жок өзгөрмөгө ыйгаруу ошол ат менен жаңы глобалдык өзгөрмө жаратат.

Биздин мисалда издөө кандайча иштээрин карап көрөлү:

  • alertИчки бөлүккө sayкиргиси келгенде name, ал дароо функциянын лексикалык чөйрөсүндө өзгөрмө табат.
  • phraseАл локалдык жактан жок болгон ага киргиси келгенде , ал тышкы лексикалык чөйрөгө шилтемени ээрчип, ошол жерден өзгөрмө табат.
 

Эми биз бөлүмдүн башынан биринчи суроого жооп алдык.

Функция тышкы өзгөрмөлөрдүн учурдагы маанисин, б.а. алардын акыркы маанисин алат

Өзгөрмөлөрдүн эски баалуулуктары эч жерде сакталбайт. Функция өзгөрмөгө жетүүнү каалаганда, ал учурдагы маанисин өзүнүн же тышкы лексикалык чөйрөсүнөн алат.

Ошентип, биринчи суроого жооп Pete:

@A@let name = "John";

function sayHi() {
  alert("Hi, " + name);
}

name = "Pete"; // (*)

sayHi(); // Pete@A@

Жогорудагы коддун аткарылышынын тартиби:

  1. Глобалдык лексикалык чөйрө бар name: "John".
  2. Онлайнда (*)глобалдык өзгөрмө өзгөрдү, азыр name: "Pete".
  3. Функция аткарылып, сырттан sayHi()өзгөрмө алган учур . nameЭми өзгөрмө буга чейин барабар болгон глобалдык лексикалык чөйрөдөн "Pete".
Бир чакырык – бир лексикалык чөйрө

Функция аткарылган сайын жаңы функциянын лексикалык чөйрөсү түзүлөөрүн эске алыңыз.

Жана, эгерде функция бир нече жолу чакырылса, анда ар бир чалуу өзүнүн лексикалык чөйрөсүнө ээ болот, өзүнүн локалдык өзгөрмөлөрү жана ушул чакырыкка мүнөздүү параметрлери бар.

Лексикалык чөйрө өзгөчө ички объект болуп саналат

«Лексикалык чөйрө» өзгөчө ички объект болуп саналат. Биз аны кодубузга алып, түз өзгөртө албайбыз. JavaScript кыймылдаткычы өзү аны оптималдаштырып, эстутумду бошотуу үчүн пайдаланылбаган өзгөрмөлөрдү жок кыла алат жана башка ички трюктарды жасай алат, бирок объекттин көрүнгөн жүрүм-туруму сүрөттөлгөндөй бойдон калууга тийиш.

уяланган функциялар

Функция башка функциянын ичинде түзүлгөндө "уяланган" деп аталат.

Муну JavaScriptте жасоо абдан оңой.

Биз муну кодубузду төмөнкүдөй уюштуруу үчүн колдоно алабыз:

function sayHiBye(firstName, lastName) {

 @A@ // функция-помощник, которую мы используем ниже
  function getFullName() {
    return firstName + " " + lastName;
  }

  alert( "Hello, " + getFullName() );
  alert( "Bye, " + getFullName() );

}@A@

Бул жерде уяча функция getFullName()ыңгайлуулук үчүн түзүлгөн. Ал тышкы өзгөрмөлөргө кире алат жана ошону менен толук атын басып чыгара алат. Уюшкан функциялар JavaScript'те абдан кеңири таралган.

Кызыгы, уя салынган функцияны кайра кайтарса болот: же жаңы объекттин касиети катары (эгерде тышкы функция объектти методдору менен түзсө), же өзүнөн. Анан каалаган жерде колдонсо болот. Кайсы жерде болбосун, ал ошол эле тышкы өзгөрмөлөргө кире алат.

Мисалы, бул жерде конструктордогу жаңы объектке уя салынган функция дайындалат :

@A@// функция-конструктор возвращает новый объект
function User(name) {

  // методом объекта становится вложенная функция
  this.sayHi = function() {
    alert(name);
  };
}

let user = new User("John");
user.sayHi(); // "John" (у кода метода "sayHi" есть доступ к внешней переменной "name")@A@

Бул жерде биз жөн гана "эсептөөчү" функциясын түзүп, кайтарабыз:

@A@function makeCounter() {
  let count = 0;

  return function() {
    return count++; // есть доступ к внешней переменной "count"
  };
}

let counter = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1
alert( counter() ); // 2@A@

Келгиле, бир мисал менен уланталы makeCounter. Ал ар бир чакырылган сайын кийинки санды кайтарып турган "эсептөөчү" функциясын түзөт. Жөнөкөйлүгүнө карабастан, бул коддун бир аз өзгөртүлгөн версиялары практикада, мисалы, псевдококустук сан генераторунда жана башка көптөгөн учурларда колдонулат .

Ал ичинен кантип иштейт?

Ички функция аткарыла баштаганда, өзгөрмө count++ичинен сыртка карай изделет. Жогорудагы мисал үчүн, тартип мындай болот

  1. Кыстарылган функциянын жергиликтүү өзгөрмөлөрү…
  2. Тышкы функциянын өзгөрмөлөрү...
  3. Глобалдык өзгөрмөлөргө жеткенге чейин уланат.

Бул мисалда countкадамда табылат 2. Тышкы өзгөрмө өзгөртүлгөндө, ал табылган жери өзгөрөт. Демек, count++ал өзүнө тиешелүү болгон лексикалык чөйрөдө сырткы өзгөрмө таап, баалуулугун жогорулатат. Бизде болгон сыяктуу let count = 1.

Эми эки суроону карап көрөлү:

  1. countКандайдыр бир жол менен эсептегичти таандык эмес коддон баштапкы абалга келтире алабызбы makeCounter? Мисалы, alertжогорудагы коддогу чалуудан кийин.
  2. Эгер биз makeCounterбир нече жолу чалсак, көптөгөн функциялар бизге кайтарылат counter. Алар көз карандысызбы же бирдей өзгөрмөлүүбү count?

Окууну улантуудан мурун бул суроолорго жооп берүүгө аракет кылыңыз.

Даярсызбы?

Макул, суроолорго жооп берели.

  1. Мындай мүмкүнчүлүк жок: count- функциянын локалдык өзгөрмөсү, биз ага сырттан кире албайбыз.
  2. Ар бир чакыруу үчүн makeCounter()функция үчүн жаңы лексикалык чөйрө түзүлөт, анын өзүнүн count. Ошентип, натыйжада функциялар counterкөз карандысыз.

Бул жерде демо болуп саналат:

@A@function makeCounter() {
  let count = 0;
  return function() {
    return count++;
  };
}

let counter1 = makeCounter();
let counter2 = makeCounter();

alert( counter1() ); // 0
alert( counter1() ); // 1

alert( counter2() ); // 0 (независимо)@A@

Мен тышкы өзгөрмөлөр менен кырдаал азыр ачык деп үмүттөнөм. Көпчүлүк жагдайлар үчүн бул түшүнүк жетиштүү, бирок спецификацияда биз жөнөкөйлүк үчүн өткөрүп жиберген бир катар деталдар бар. Кийинки, биз кененирээк эмне болуп жатканын талдайбыз.

Айлана-чөйрөнү майда-чүйдөсүнө чейин

Бул жерде кадам-кадам мисалында эмне болот makeCounter. Ар бир майда-чүйдөсүнө чейин түшүнүү үчүн аларды карап чыгуу.

[[Environment]]Сураныч , бул жерде сүрөттөлгөн кошумча мүлккө көңүл буруңуз . Жөнөкөйлүк үчүн буга чейин айткан эмеспиз.

  1. Скрипт биринчи жолу аткарыла баштаганда, глобалдык лексикалык чөйрө гана болот:

    Бул баштапкы учурда makeCounter, бир гана функция бар, анткени ал Функциянын Декларациясы. Ал азырынча ишке аша элек.

    Бардык функциялар "төрөлгөндө" [[Environment]]алар түзүлгөн жердин лексикалык чөйрөсүнө тиешелүү жашыруун касиетке ээ.

    Биз бул тууралуу сүйлөшө элекпиз, функциялар кайдан түзүлгөнүн кантип билишет.

    Бул учурда, makeCounterглобалдык лексикалык чөйрөдө түзүлгөн, ошондуктан [[Environment]]ага шилтеме камтыйт.

    Башкача айтканда, функция өзү түзүлгөн лексикалык чөйрөгө шилтемени түбөлүккө эстейт. Жана [[Environment]]бул шилтемени камтыган функциянын жашыруун касиети.

  2. Код иштей берет, жаңы глобалдык өзгөрмө жарыяланып counter, ага чалуунун натыйжасы дайындалат makeCounter. Бул жерде котормочу биринчи сапта турган учурдун сүрөтү makeCounter():

    Чакыруу учурунда makeCounter()анын өзгөрмөлөрүн жана аргументтерин сактоо үчүн лексикалык чөйрө түзүлөт.

    Бардык лексикалык чөйрөлөр сыяктуу эле, ал эки нерсени камтыйт:

    1. Жергиликтүү өзгөрмөлөр менен курчап турган чөйрөнү жазуу. Биздин учурда count, жападан жалгыз локалдык өзгөрмө (менен сызык пайда болгондо let count) аткарылат.
    2. [[Environment]]Функциянын маанисине коюлган тышкы чөйрөгө шилтеме . Мында [[Environment]]функция makeCounterглобалдык лексикалык чөйрөнү билдирет.

    Ошентип, азыр бизде эки лексикалык чөйрө бар: биринчиси глобалдык, экинчиси учурдагы чакыруу үчүн makeCounter, глобалдык объектке тышкы шилтеме менен.

  3. Аткаруу учурунда makeCounter()кичинекей уяча функция түзүлөт.

    Функцияны жарыялоонун кайсы ыкмасы колдонулганы маанилүү эмес: Функция декларациясы же Функциянын туюнтмасы. [[Environment]]Бардык функциялар алар түзүлгөн лексикалык чөйрөгө тиешелүү касиетке ээ . Биздин жаңы кичинекей функциябыз менен да ушундай болуп жатат.

    Биздин жаңы уяча функциябыз үчүн маани учурдагы лексикалык чөйрө (ал түзүлгөн жерде) [[Environment]]болот :makeCounter()

    Бул кадамда ички функция түзүлгөнүн, бирок али чакырыла электигин эске алыңыз. Ичиндеги код function() { return count++ }аткарылган эмес.

  4. Аткаруу улантылат, чалуу makeCounter()аяктайт жана натыйжа (кичинекей уяча функция) глобалдык өзгөрмөгө дайындалат counter:

    Бул функцияда бир гана сап бар: return count++, ал функцияны чакырганда аткарылат.

  5. Чакырганда counter()бул чакыруу үчүн жаңы лексикалык чөйрө түзүлөт. Ал бош, анткени counterжергиликтүү өзгөрмөлөрдүн өзүндө жергиликтүү өзгөрмөлөр жок. Бирок ал түзүлгөн мурунку чалуунун өзгөрмөлөрүнө мүмкүнчүлүк берген [[Environment]] counterтышкы лексикалык чөйрөгө шилтеме катары колдонулат .outermakeCountercounter

    Эми, чалуу өзгөрмө издегенде count, ал адегенде өзүнүн лексикалык чөйрөсүн (бош) карайт, андан кийин мурунку чалуунун лексикалык чөйрөсүн makeCounter(), аны кайдан табат.

    Сураныч, бул жерде эстутум башкаруу кантип иштээрин эске алыңыз. Ал бир нече убакыт мурун аткарылып бүтсө да , анын лексикалык чөйрөсү эс-тутумда кала берет, анткени ага тиешелүү makeCounter()уяланган функция бар .[[Environment]]

    Көпчүлүк учурларда лексикалык чөйрө объектиси аны колдоно ала турган функция болгондо гана бар. Эч ким калбай калганда гана экология бузулат.

  6. Чалуу counter()маанини гана кайтарбастан count, аны жогорулатат. Сураныч, өзгөртүү "жеринде" ишке ашарын эске алыңыз. Маани countтабылган чөйрөдө өзгөчө өзгөрөт

  7. Төмөнкү чалуулар counter()да ушундай кылат.

Эми бөлүмдүн башынан экинчи суроого жооп айкын болушу керек.

work()Төмөндөгү коддогу функция nameсырткы лексикалык чөйрөгө шилтеме аркылуу түзүлгөн жерден алат:

Ошентип, натыйжа болот "Pete".

Бирок, эгерде makeWorker()жок болсо let name, анда издөө андан ары уланмак жана жогорудагы чынжырдан көрүп тургандай, глобалдык өзгөрмө алынмак. Бул учурда, натыйжасы болмок "John".

замыкание

Программалоодо жалпы термин бар: "замыкание" - аны ар бир иштеп чыгуучу билиши керек.

замыкание- бул тышкы өзгөрмөлөрдү эстеп турган жана аларга кире алган функция. Кээ бир тилдерде бул мүмкүн эмес, же жабууну жасоо үчүн функция өзгөчө түрдө жазылышы керек. Бирок, жогоруда айтылгандай, JavaScript'те бардык функциялар түпкүлүгүндө жабылат (бир гана өзгөчөлүк бар, ал Синтаксисте камтылат "жаңы функция" ).

Башкача айтканда, алар жашыруун касиети менен кайда түзүлгөнүн автоматтык түрдө эстеп калышат [[Environment]]жана бардыгы тышкы өзгөрмөлөргө кире алышат.

Интервьюда алдыңкы иштеп чыгуучудан: "Жабуу деген эмне?" деп сураганда, туура жооп жабууну аныктоо жана JavaScript'теги бардык функциялар жабылуу экендигин түшүндүрүү жана техникалык деталдар жөнүндө бир нече сөз болушу мүмкүн: касиети жана [[Environment]]лексикалык чөйрө кантип иштейт.

Code Blocks and Loops, IIFE

Мурунку мисалдар функцияларга багытталган. Бирок лексикалык чөйрө коддун бардык блоктору үчүн бар {...}.

Лексикалык чөйрө код блогу аткарылганда түзүлөт жана ал блок үчүн локалдык өзгөрмөлөрдү камтыйт. Бул жерде эки мисал болуп саналат.

if

Төмөнкү мисалда өзгөрмө userблокто гана бар if:

Аткаруу блокко тийгенде if, ал блок үчүн жаңы лексикалык чөйрө түзүлөт.

Анын тышкы чөйрөгө шилтемеси бар, ошондуктан phraseаны табууга болот. Бирок ичинде жарыяланган бардык өзгөрмөлөр, Функциянын туюнтмасы, ошондой эле Функциянын катуу режимдеги декларациясы ifанын лексикалык чөйрөсүндө калат жана сырттан көрүнбөйт.

Мисалы, аяктагандан кийин, ifкийинкиси alertкөрүнбөйт user, бул катага алып келет.

for while

Цикл үчүн ар бир итерация өзүнүн өзүнчө лексикалык чөйрөсүнө ээ. Эгерде өзгөрмө ичинде жарыяланган болсо for(let ...), анда ал да анда болот:

@A@for (let i = 0; i < 10; i++) {
  // У каждой итерации цикла своё собственное лексическое окружение
  // {i: value}
}

alert(i); // Ошибка, нет такой переменной@A@

Эскертүү: let iвизуалдык жактан сыртта жайгашкан {...}. Бирок конструкция forбул жагынан өзгөчө, циклдин ар бир итерациясында учурдагы менен өзүнүн лексикалык чөйрөсү болот i.

Жана сыяктуу эле if, ылдыйда цикл iкөрүнбөйт.

Code Blocks

{...}Биз ошондой эле "жергиликтүү масштабдагы" өзгөрмөлөрдү изоляциялоо үчүн "жөнөкөй" код блокторун колдоно алабыз .

Мисалы, браузерде бардык скрипттер (башкалары type="module") бир жалпы глобалдык чөйрөнү бөлүшөт. Ошентип, бир скриптте глобалдык өзгөрмө түзсөк, ал башкаларында жеткиликтүү болот. Бирок эки скрипт бир эле өзгөрмө атын колдонуп, бири-биринин үстүнөн жазса, бул чыр-чатактын булагы болуп калат.

Бул өзгөрмөнүн аты кеңири колдонулган сөз болсо жана сценарийдин авторлору бири-бирин билбесе болот.

Эгер биз мындан качууну кааласак, скриптти толугу менен же анын бир бөлүгүн изоляциялоо үчүн код блогун колдонсок болот:

 
 
@A@{
  // сделать какую-нибудь работу с локальными переменными, которые не должны быть видны снаружи

  let message = "Hello";

  alert(message); // Hello
}

alert(message); // Ошибка: переменная message не определена@A@

Блоктун өзүнүн лексикалык чөйрөсү болгондуктан, анын сыртындагы код (же башка скриптте) блоктун өзгөрмөлөрүн көрбөйт.

IIFE

Мурда JavaScript код блогунун деңгээлинде лексикалык чөйрөгө ээ болгон эмес.

Ошентип, программисттер бир нерсе ойлоп табышы керек болчу. Жана алар жасаган нерсе "дароо чакырылган функция туюнтмалары" (IIFE аббревиатурасы) деп аталат, бул декларациядан кийин дароо иштей турган функцияны билдирет.

Бул биз бүгүн колдонушубуз керек болгон нерсе эмес, бирок сиз аны эски скрипттерде көргөндүктөн, анын кантип иштээрин түшүнүү пайдалуу.

IIFE мындай көрүнөт:

@A@(function() {

  let message = "Hello";

  alert(message); // Hello

})();@A@

Бул жерде Функция туюнтмасы түзүлүп, дароо чакырылат. Ошентип, код дароо аткарылат жана өзүнүн жергиликтүү өзгөрмөлөрү бар.

Функциянын туюнтмасы кашаага оролот, анткени JavaScript аны негизги код агымында (function {...})жолуктурганда , ал аны Функция Декларациясынын башталышы катары чечмелейт. "function"Бирок Функция Декларациясынын аталышы болушу керек, андыктан бул код катаны жаратат:

@A@// Попробуйте объявить и сразу же вызвать функцию
function() { // <-- Error: Unexpected token (

  let message = "Hello";

  alert(message); // Hello

}();@A@

"Макул, ат кошолу" десек дагы, ал иштебейт, анткени JavaScript Функция декларациясын дароо чакырууга жол бербейт.

@A@// ошибка синтаксиса из-за скобок ниже
function go() {

}(); // <-- не можете вызывать Function Declaration немедленно@A@

Ошентип, функциянын тегерегиндеги кашаалар JavaScript'ке функция башка туюнтма контекстинде түзүлгөнүн көрсөтүү үчүн амал болуп саналат, демек, бул функциянын туюнтмасы: ага ат керек эмес жана аны дароо чакырса болот.

Функция туюнтмасы дегенди билдире турган JavaScript-ти көрсөтүүнүн башка жолдору бар: кашаалардан тышкары:

@A@// Пути создания IIFE

(function() {
  alert("Скобки вокруг функции");
})();

(function() {
  alert("Скобки вокруг всего");
}());

!function() {
  alert("Выражение начинается с логического оператора NOT");
}();

+function() {
  alert("Выражение начинается с унарного плюса");
}();@A@

Жогоруда айтылган бардык учурларда, биз Функция туюнтмасын жарыялайбыз жана аны дароо аткарабыз. Дагы бир жолу белгилей кетүү керек, учурда мындай кодду жазуунун кереги жок.

Таштанды чогултуу

Адатта, лексикалык чөйрө тазаланып, функция аткарылгандан кийин өчүрүлөт. Мисалы:

@A@function f() {
  let value1 = 123;
  let value2 = 456;
}

f();@A@

Бул жерде лексикалык чөйрөнүн техникалык касиеттери болгон эки баалуулук бар. Бирок ал f()аяктагандан кийин бул лексикалык чөйрө жеткиликсиз болуп калат, ошондуктан ал эстутумдан өчүрүлөт.

...Бирок, эгерде аткарылгандан кийин дагы эле жеткиликтүү болгон уяча функция бар болсо , анда ал тышкы лексикалык чөйрөгө тиешелүү fкасиетке ээ , ошону менен аны жеткиликтүү, "тирүү" калтырат:[[Environment]]

@A@function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // g доступно и продолжает держать внешнее лексическое окружение в памяти@A@

Эскертүү, эгерде f()ал бир нече жолу чакырылса жана кайтарылган функциялар сакталса, анда бардык тиешелүү лексикалык чөйрө объекттери эстутумда сакталып кала берет. Төмөнкү коддо үч ушундай функция бар:

@A@function f() {
  let value = Math.random();

  return function() { alert(value); };
}

// три функции в массиве, каждая из них ссылается на лексическое окружение
// из соответствующего вызова f()
let arr = [f(), f(), f()];@A@

Лексикалык чөйрө объектиси жеткиликсиз болгондо өлөт (башка объект сыяктуу). Башка сөз менен айтканда, ал өзүнө тиешелүү жок дегенде бир уя функция болгондо гана бар.

Төмөнкү коддо, ал gжеткиликсиз болгондон кийин, функциянын лексикалык чөйрөсү (жана, демек, value) эстутумдан алынып салынат:

@A@function f() {
  let value = 123;

  function g() { alert(value); }

  return g;
}

let g = f(); // пока g существует,
// соответствующее лексическое окружение существует

g = null; // ...а теперь память очищается@A@

Практикада оптималдаштыруу

Көрүнүп тургандай, теорияда функция тирүү кезинде бардык тышкы өзгөрмөлөр да сакталат.

Бирок иш жүзүндө JavaScript кыймылдаткычтары муну оптималдаштырууга аракет кылышат. Алар өзгөрмөлөрдүн колдонулушун анализдейт жана эгер коддон тышкы өзгөрмө колдонулбаганын түшүнүү оңой болсо, ал өчүрүлөт.

V8деги (Chrome, Opera) маанилүү терс таасирлердин бири - мүчүлүштүктөрдү оңдоо учурунда мындай өзгөрмө жеткиликсиз болуп калат.

Иштеп чыгуучу куралдары ачык болгон Chrome'до төмөнкү мисалды иштетип көрүңүз.

Код тындырылса, консолго жазыңыз alert(value).

 
 
@A@function f() {
  let value = Math.random();

  function g() {
    debugger; // в консоли: напишите alert(value); Такой переменной нет!
  }

  return g;
}

let g = f();
g();@A@

Көрүнүп тургандай, мындай өзгөрмө жок! Теориялык жактан алганда, ал жеткиликтүү болушу керек, бирок кыймылдаткычты оптималдаштыруу астында калды.

Бул күлкүлүү (эгер аны тез чече алсаңыз) мүчүлүштүктөрдү оңдоо көйгөйлөрүнө алып келиши мүмкүн. Алардын бири, биз бирдей аталыштар менен туура эмес тышкы өзгөрмө көрө алабыз:

 
 
@A@let value = "Сюрприз!";

function f() {
  let value = "ближайшее значение";

  function g() {
    debugger; // в консоли: напишите alert(value); Сюрприз!
  }

  return g;
}

let g = f();
g();@A@
Көрүшкөнчө!

V8дин бул өзгөчөлүгүн билүү жакшы. Эгер сиз Chrome/Operaда мүчүлүштүктөрдү оңдоп жатсаңыз, эртеби-кечпи ага туш болосуз.

Бул мүчүлүштүктөрдү оңдоочу ката эмес, тескерисинче, V8дин өзгөчөлүгү. Балким, убакыттын өтүшү менен бул өзгөрөт. Сиз ар дайым бул баракчадагы мисалды иштетүү менен муну сынай аласыз.

Tasks

маанилүүлүгү: 5

Бул жерде биз эки эсептегич жасайбыз: counterжана counter2, ошол эле функцияны колдонуу менен makeCounter.

Алар көз карандысызбы? Экинчи эсептегич эмнени көрсөтөт? 0,1же 2,3башка нерсеби?

@A@function makeCounter() {
  let count = 0;

  return function() {
    return count++;
  };
}

let counter = makeCounter();
let counter2 = makeCounter();

alert( counter() ); // 0
alert( counter() ); // 1

alert( counter2() ); // ?
alert( counter2() ); // ?@A@
чечим
маанилүүлүгү: 5

Бул жерде конструктор функциясынын жардамы менен эсептегич объект түзүлөт.

Иштейби? Эмне көрсөтөт?

@A@function Counter() {
  let count = 0;

  this.up = function() {
    return ++count;
  };
  this.down = function() {
    return --count;
  };
}

let counter = new Counter();

alert( counter.up() ); // ?
alert( counter.up() ); // ?
alert( counter.down() ); // ?@A@
чечим
 

Кодду караңыз. Акыркы сапта чалуунун жыйынтыгы кандай болот?

@A@let phrase = "Hello";

if (true) {
  let user = "John";

  function sayHi() {
    alert(`${phrase}, ${user}`);
  }
}

sayHi();@A@
чечим
маанилүүлүгү: 4

sumМындай иштеген функцияны жазыңыз : sum(a)(b) = a+b.

Ооба, дал ушундай, кош кашааларды колдонуу менен (тип катасы эмес).

Мисалы:

@A@sum(1)(2) = 3
sum(5)(-1) = 4@A@
чечим
маанилүүлүгү: 5

arr.filter(f)Бизде массивдер үчүн курулган метод бар . Ал бардык элементтерди чыпкалайт f. Эгерде ал кайтып келсе true, анда элемент кайтарылган массивге кошулат.

"Колдонууга даяр" чыпкалардын топтомун түзүңүз:

  • inBetween(a, b)– ортосунда aжана b(кошкондо).
  • inArray([...])бул массивде.

Алар төмөнкүдөй колдонулушу керек:

  • arr.filter(inBetween(3,6))– 3 жана 6 (кошкондо) ортосундагы маанилерди гана тандайт.
  • arr.filter(inArray([1,2,3]))- массив элементтеринин бирине дал келген элементтерди гана тандайт

Мисалы:

@A@/* .. ваш код для inBetween и inArray */
let arr = [1, 2, 3, 4, 5, 6, 7];

alert( arr.filter(inBetween(3, 6)) ); // 3,4,5,6

alert( arr.filter(inArray([1, 2, 10])) ); // 1,2@A@

Тапшырма үчүн тесттер менен кумкоргонду ачыңыз.

чечим
маанилүүлүгү: 5

Бизде сорттоо керек болгон объекттердин массивдери бар:

@A@let users = [
  { name: "John", age: 20, surname: "Johnson" },
  { name: "Pete", age: 18, surname: "Peterson" },
  { name: "Ann", age: 19, surname: "Hathaway" }
];@A@

Кадимки жол бул сыяктуу болот:

@A@// по имени (Ann, John, Pete)
users.sort((a, b) => a.name > b.name ? 1 : -1);

// по возрасту (Pete, Ann, John)
users.sort((a, b) => a.age > b.age ? 1 : -1);

Ушундай кылып кыскарта алабызбы?

users.sort(byField('name'));
users.sort(byField('age'));@A@

Башкача айтканда, функциянын ордуна биз жөн гана жазабыз byField(fieldName).

byFieldБул үчүн колдонула турган функцияны жазыңыз .

чечим
маанилүүлүгү: 5

Төмөнкү код жебелердин массивин түзөт ( shooters).

Ар бир функция алардын сериялык номерлерин көрсөтүү үчүн иштелип чыккан. Бирок бир жерден ката кетти...

 
 
@A@function makeArmy() {
  let shooters = [];

  let i = 0;
  while (i < 10) {
    let shooter = function() { // функция shooter
      alert( i ); // должна выводить порядковый номер
    };
    shooters.push(shooter);
    i++;
  }

  return shooters;
}

let army = makeArmy();

army[0](); // у 0-го стрелка будет номер 10
army[5](); // и у 5-го стрелка тоже будет номер 10
// ... у всех стрелков будет номер 10, вместо 0, 1, 2, 3...@A@

Эмне үчүн бардык ок ​​атуучулардын саны бирдей? Кодду оңдоп, ал ойдогудай иштеши үчүн.