Характеристики на работа или „Защо обичам JavaScript“ Затваряния, създаване на прототипи и контекст
Тази тема ще обхваща:
- Затваряния
- създаване на прототипи
- Контекст на изпълнение
Предговор
Затваряния или „Тези необичайни обхвати“
Същността на затварянията е проста:вътре във функцията можете да използвате всички променливи, които са налични на мястото, където е декларирана функцията.
Когато кодът се изпълни, той ще изведе текста "Hello World", както се очаква. Същността на случващото се е проста - създава се глобална променливаtitleсъс стойността "Hello World", която се показва на потребителя чрезalert. В този пример, дори ако пропуснем ключовата думаvar, кодът пак ще работи правилно поради глобалния контекст. Но повече за това по-късно.
Сега нека се опитаме да декларираме същата променлива, но вътре във функцията:
В резултат на изпълнението на кода се генерира грешката "'title' is undefined" - "променливата 'title' не е декларирана". Това се дължи на механизма за обхват на локалната променлива:всички променливи, декларирани във функция, са локални и се виждат само вътре в тази функция. Или по-просто: ако декларираме някаква променлива вътре във функция, тогава няма да имаме достъп до тази променлива извън тази функция.
За да изведете надписа "Hello World", трябва да извикатеalertв извиканата функция:
Или върнете стойност от функция:
Тъй като функцията е обект, това означава, че механизмът на обхвата на променливите се простира до функции: функция, декларирана в друга функция, се вижда само там, където е декларирана
В този пример променливатаtitleе декларирана два пъти - първият път глобално и вторият път във функцията. Благодаря ти за това, което има вътрефункцияexampleпроменливаtitleе декларирана с помощта на ключовата думаvar, тя става локална и не е свързана с променливатаtitle, декларирана преди функцията. Резултатът от изпълнението на кода първо ще отпечата "internal" (вътрешна променлива) и след това "external" (глобална променлива).
Ако премахнем ключовата думаvarот редаvar title = 'internal', изпълнението на кода ще доведе до съобщението "internal" два пъти. Това се дължи на факта, че при извикването на функцията не декларирахме локалната променливаtitle, а презаписахме стойността на глобалната променлива!
По този начин можете да видите, че използването на ключовата дума var прави променливата локална, като гарантира, че няма конфликти с външни променливи (например в PHP всички променливи във функция са локални по подразбиране; и за да се обърнете към глобална променлива, трябва да я декларирате като глобална, като използвате ключовата дума global).
И така, как определяте коя променлива се използва във функция?
- Когато се опитвате да използвате (получите стойността на) променлива, ще се генерира грешка, че променливата не е декларирана
- Когато се опитате да присвоите стойност на променлива, променливата ще бъде създадена в глобалния обхват и ще й бъде присвоена стойността.
След като изпълним кода, получаваме "internal" и двата пъти. Присвояването на стойност на заглавната променлива във функциятаAсъздава глобална променлива, която може да се използва и извън функцията. Имайте предвид, че присвояването на стойност на променлива (и следователно създаването на глобална променлива) се случва на етапа на извикване на функциятаA, така че опитът за извикване наalert(title)преди извикването на функциятаAще генерирагрешка.
Както знаете, всички локални променливи се създават наново с всяко ново извикване на функция. Например, имаме функцияA, вътре в която е декларирана променливатаtitle:
След като функциятаAбъде изпълнена, променливатаtitleвече няма да съществува и не може да бъде достъпна по никакъв начин. Опитът за достъп до променливата по какъвто и да е начин ще доведе до грешка, че променливата не е декларирана.
Преди да стартираме този пример, нека се опитаме да разгледаме поведението на променливатаtitleлогически: когато функциятаgetTitleсе изпълнява, променливата се създава и когато извикването приключи, тя се унищожава. Въпреки това, когато се извика функциятаgetTitle, се връща обект с две динамично декларирани функцииshowTitleиsetTitle, които използват тази променлива. Какво се случва, ако извикате тези функции?
Ако изпълните функциятаgetTitleотново, можете да видите, че променливатаtitle, както и функциитеshowTitle/setTitle, се създават наново всеки път и не са свързани с предишни изпълнения по никакъв начин:
Изпълнявайки кода (не забравяйте да добавите кода на функциятаgetTitleпо-горе), ще бъдат генерирани две съобщения: "Hello World 1" и "Hello World 2" (подобно поведение се използва за емулиране на частни променливи).
Оставете теорията и най-простите примери и се опитайте да разберете как можете да се възползвате от затварянията на практика:
Първата е способността да се поддържа глобалният обхват чист.
Проблемът с конфликтите в глобален мащаб е очевиден. Прост пример: ако на страницата са включени няколко javascript файла, които декларират функциятаshowTitle, тогава, когато се извикаshowTitle, ще се изпълни последната декларирана функция. Чесъщото важи и за декларираните променливи.
В резултат на това всички декларирани променливи и функции няма да бъдат налични в глобалния обхват, което означава, че няма да има конфликти.
В резултат на изпълнението на кода ще се генерира съобщението "Hello World" - локалната функцияshowTitleе станала достъпна глобално под иметоshowSimpleTitle, докато използва „затворената“ променливаtitle, която не е достъпна извън нашата анонимна функция.
защото ние обвихме всичко в анонимна функция, която се изпълнява незабавно, можете да подадете параметри към тази функция, които вътре в тази функция ще бъдат достъпни под локални имена. Пример с jQuery:
Ще изведе грешка, ако глобалната променлива $ не е jQuery. И това се случва, ако в допълнение към jQuery се използва друга библиотека, която използва функцията $, например Prototype.JS. Челно решение:
Ще работи и ще работи правилно. Но не много красива. Има по-красиво решение - да декларирате локална променлива $ като аргумент на функцията, предавайки jQuery обекта там:
Ако си спомним, че всички аргументи на функцията са локални променливи по подразбиране, става ясно, че сега вътре в нашата анонимна функция, $, не е свързана с глобалния $ обект по никакъв начин, като е препратка към jQuery обекта. За да направите трика с анонимните функции по-ясен, можете да направите анонимната функция неанонимна - декларирайте функцията и веднага я стартирайте:
Е, ако все пак трябва да извикате глобалната функция $, можете да използватеwindow.$.
Когато използвате модела на събитието, често възникват ситуации, когато трябва да окачите едно и също събитие, но на различни елементи. Например, имаме 10 елемента div, при щракване върху които трябва да извикамеalert(N), където N е някакъв уникален номер на елемент.
Най-простото решение с използване на затваряне:
Изпълнението на този код обаче води до "неочакван" резултат - всички кликове показват едно и също число - 11. Познайте защо?
Отговорът е прост: стойността на променливатаcounterсе взема в момента, в който се щракне върху елемента. И тъй като до този момент стойността на променливата е станала 11 (условието за излизане от цикъла), числото 11 се показва съответно за всички елементи.
Правилното решение е динамично генериране на функция за обработка на щракване отделно за всеки елемент:
При този подход използваме анонимна функция, която приемаcounterкато параметър и връща динамична функция. Вътре в анонимната функциялокалнатапроменливаiCounterсъдържа текущата стойност наcounterпо време на извикването на функцията. И тъй като всеки път, когато се извика която и да е функция, всички локални променливи се декларират наново (създава се ново затваряне), тогава, когато се извика нашата анонимна функция, всеки път ще се връща нова динамична функция с вече „затворено“ число.
Казано по-просто, като стартирате функцията за втори (трети, четвърти.) път, всички локални променливи ще бъдат създадени в паметта наново и няма да имат нищо общо с променливите, създадени по време на предишното извикване на функцията.
Труден? Мисля, че първия път, да. Но не е нужно да имате куп глобални променливи и да правите проверки във функцията „откъде ми се обадиха. ". И с използването на jQuery.each, който по подразбиране ще извика предадената функция, кодът става още по-прост и по-четим:
Благодарение на затварянията можете да пишете красив, кратък и разбираем код, като наименувате променливи и функции както желаете; и бъдете сигурни, че този код не е такъвще бъде в конфликт с други включени скриптове.
Прототипиране или "Искам да направя обект от клас"
Ако имаме много обекти, тогава ще бъде по-удобно да направим отделна функция, която връща обект:
Същото може да се направи с помощта на прототипи:
И малък илюстративен пример за разширяване на възможностите на съществуващи обекти с помощта на прототипи
Трябва да получите името на деня от седмицата от някаква дата, но вграденият обект Date съдържа само метод getDay, който връща числово представяне на деня от седмицата от 0 до 6: от неделя до събота.
Можете да направите това:
Или използвайте прототипиране и разширете вградения обект за дата:
Мисля, че вторият метод е по-елегантен и не затрупва обхвата с "допълнителна" функция. От друга страна, има мнение, че разширяването на вградени обекти е лоша форма, ако няколко души работят върху кода. Така че трябва да внимавате с това.
Контекст на изпълнение или „Това е загадъчно това“
От примера можете да видите, че когато се извикаexampleObject.showTitle(), функцията се извиква като метод на обект и вътре във функциятаthisсе отнася до обектаexampleObject, който е извикал функцията. Самите функции не са обвързани с обект по никакъв начин и съществуват отделно. Контекстното обвързване се осъществява директно по време на извикването на функцията:
Ако, когато се извика функция, тя (функцията) не препраща към никакъв обект, тогаватовавъв функцията ще препраща към глобалния обектпрозорец. Тези. ако просто извикатеshowTitle(), тогава ще се генерира грешка, че променливатаtitleне е декларирана; обаче, ако декларирате глобална променливаtitle, тогава функцията ще покаже стойността на тази променлива:
За да демонстрира товаконтекстът на функцията се определя точно по време на извикването, ще дам пример, когато функцията първоначално съществува само като метод на обекта:
Това ще отпечата съобщението "Глобално заглавие", което показва, че по време на извикването на функциятаthisсочи към глобалния обектwindow, а не обектаexampleObject. Това е така, защото в редаvar showTitle = exampleObject.showTitleполучаваме само препратка към функция и когато се извикаshowTitle(), няма препратка към оригиналния обектexampleObject, което караthisда препраща към обектаwindow.
Опростяване:ако функцията се извиква като свойство на обект, тогаваthisще препраща към този обект. Ако няма извикващ,thisще препраща към глобалния обектwindow.
Пример за често срещана грешка:
Когато щракнете върху DIV с идентификатор "exampleDiv" вместо очакваното "Примерно заглавие", ще се покаже "Глобално заглавие". Това се дължи на факта, че даваме функция на събитието click, но не даваме обекта; и в резултат на това функцията се стартира без обвързване с обекта. За да стартирате функция, обвързана с обект, трябва да извикате функцията с препратка към обекта:
Любимият на всички IE до версия 9 обаче не поддържа тази функция. Следователно повечето JS библиотеки независимо прилагат тази функция по един или друг начин. Например в jQuery това е прокси:
Същността на подхода е доста проста - когато се извика jQuery.proxy, се връща анонимна функция, която с помощта на затваряния извиква оригиналната функция в контекста на предадения обект.
Без да използвате параметри на функцията, и двете работят по един и същ начин - функциитеapplyиcallсе различават само по начина, по който параметрите се предават при извикване:
| Повече ▼Подробна информация по разглежданите теми можете да намерите тук: