diff --git a/.gitignore b/.gitignore index 6f90fd190..1a71fb7c8 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ sftp-config.json Thumbs.db +/svgs \ No newline at end of file diff --git a/1-js/01-getting-started/1-intro/article.md b/1-js/01-getting-started/1-intro/article.md index b859299b5..f26f3fb24 100644 --- a/1-js/01-getting-started/1-intro/article.md +++ b/1-js/01-getting-started/1-intro/article.md @@ -24,11 +24,11 @@ Browser-ն ունի իր մեջ ներառված engine, որը նաև անվա Տարբեր engin-ներ ունեն տարբեր «կեղծանուններ»՝ -- [V8](https://en.wikipedia.org/wiki/V8_(JavaScript_engine)) -- Chrome-ում և Opera-ում։ +- [V8](https://hy.wikipedia.org/wiki/V8_(JavaScript_%D5%B7%D5%A1%D6%80%D5%AA%D5%AB%D5%B9)) -- Chrome-ում, Opera-ում և Edge-ում։ - [SpiderMonkey](https://en.wikipedia.org/wiki/SpiderMonkey) -- Firefox-ում։ -- ...Կան այլ կեղծանուններ, ինչպես «Trident» և «Chakra», IE-ի տարբերակների համար, «ChakraCore»-ը Microsoft Edge-ի համար, «Nitro» և «SquirrelFish»՝ Safari-ի, և այլն։ +- ...Կան այլ կեղծանուններ, ինչպես «Chakra» IE-ի համար, «JavaScriptCore», «Nitro» և «SquirrelFish»՝ Safari-ի համար, և այլն։ -Նշված տերմինները պետք է հիշել, քանի որ նրանք շատ են օգտագործվում համացանցի հոդվածներում։ Մենք նույնպես կօգտագործենք։ Օրինակ, եթե նշված է, որ «Ա հատկությունը օգտագործվում է V8-ում», ապա այն հավանաբար կաշխատի Chrome-ում և Opera-ում։ +Նշված տերմինները պետք է հիշել, քանի որ նրանք շատ են օգտագործվում համացանցի հոդվածներում։ Մենք նույնպես կօգտագործենք։ Օրինակ, եթե նշված է, որ «Ա հատկությունը օգտագործվում է V8-ում», ապա այն հավանաբար կաշխատի Chrome-ում, Opera-ում և Edge-ում։ ```smart header="Ինչպե՞ս են աշխատում engine-ները" @@ -43,7 +43,7 @@ Engine-ը բարեփոխումներ (optimization) է անում ամեն քա ## Ի՞նչ է կարող browser-ի միջի JavaScript-ը անել -Ժամանակակից JavaScript-ը «ապահով» ծրագրավորման լեզու է։ Այն չի տրամադրում low-level հասանելիություն հիշողությանը կան CPU-ին, քանի որ սկզբնապես ստեղծված է եղել browser-ների համար, որոնք դրա կարիքը չունեն +Modern JavaScript is a "safe" programming language. It does not provide low-level access to memory or the CPU, because it was initially created for browsers which do not require it. JavaScript-ի հնարավորությունները մեծ մասով կախված են միջավայրից, որում այն աշխատում է։ Օրինակ՝ [Node.js](https://wikipedia.org/wiki/Node.js)-ը ունի ֆունկցիաներ, որոնք թույլատրում են JavaScript-ին կարդալ կամ գրել որևէ ֆայլի մեջ, կատարել ցանցային հարցումներ և այլն։ @@ -59,7 +59,7 @@ Browser-ի միջի JavaScript-ը կարող է անել ամեն ինչ կապ ## Ի՞նչ չի կարող JavaScript-ը անել -JavaScript-ի հնարավորությունները browser-ում սահմանափակ են ապահովության նկատառումներով։ Նպատակն է կանխել չար կայքերի հասանելիությունը օգտագործողի անձնական տվյալներին։ +JavaScript-ի հնարավորությունները browser-ում սահմանափակ են ապահովության նկատառումներով։ Նպատակն է կանխել չարամիտ կայքերի հասանելիությունը օգտագործողի անձնական տվյալներին։ Այսպիսի սահմանափակումների օրինակներ են՝ @@ -67,19 +67,19 @@ JavaScript-ի հնարավորությունները browser-ում սահման Ժամանակակից browser-ները թույլ են տալիս աշխատել ֆայլերի հետ, բայց հասանելիությունը տրամադրվում է միայն որոշակի դեպքերում, օրինակ՝ երբ օգտագործողը «նետում» է ֆայլերը պատուհանի մեջ կամ ընտրում `` tag-ի միջոցով։ - Գոյություն ունեն մեթոդներ տեսախցիկի կամ խոսափողի հասանելիություն տրամադրելու, բայց նրանք պահանջում են օգտագործողի կողմից կատարված կամավոր գործողություն, որպեսզի էջը գաղտիաբար չօգտագործի տեսախցիկը և տվյալներ ուղարկի [NSA](https://en.wikipedia.org/wiki/National_Security_Agency)-ին։ -- Տարբեր պատուհաններ հիմնականում չգիտեն իրար մասին։ Որոշ դեպքերում, սակայն, գիտեն, օրինակ՝ երբ մի էջը օգտագործում է JavaScript, որ բացի մյուսը։ Սակայն նույնիսկ այս դեպքում, մի էջի JavaScript-ը չի կարող կառավարել մյուսը, եթե նրանք տարբեր աղբյուրներից են։ + Գոյություն ունեն մեթոդներ տեսախցիկին, կամ խոսափողին և այլ սարքերնի հասանելիություն տրամադրելու, բայց դրանք պահանջում են օգտագործողի կողմից կատարված կամավոր գործողություն, որպեսզի էջը գաղտնիաբար չօգտագործի տեսախցիկը և տվյալներ ուղարկի [NSA](https://en.wikipedia.org/wiki/National_Security_Agency)-ին։ +- Տարբեր պատուհանները հիմնականում չգիտեն իրար մասին։ Որոշ դեպքերում, սակայն, գիտեն, օրինակ՝ երբ մի էջը օգտագործում է JavaScript, որ բացի մյուսը։ Սակայն նույնիսկ այս դեպքում, մի էջի JavaScript-ը չի կարող հասանելիություն ունենալ մյուս էջին, եթե նրանք տարբեր կայքից են (տարբեր դոմեյնից, protocol-ից կամ port-ից)։ - Սա կոչվում է «Նույն աղբյուրի կանոն»։ Երկու էջերն էլ պետք է պայմանավորվեն տվյալների փոխանակման համար և պարունակեն հատուկ JavaScript կոդ, որը կառավարում է դա։ Մենք կխոսենք դրա մասին դասընթացի մեջ։ + Սա կոչվում է «Same Origin Policy»։ Երկու էջերն էլ պետք է պայմանավորվեն տվյալների փոխանակման համար և պարունակեն հատուկ JavaScript կոդ, որը կառավարում է դա։ Մենք կխոսենք դրա մասին դասընթացի մեջ։ - Այս սահմանափակումը, նույնպես, օգտագործողի ապահովության համար է։ `http://anysite.com`-ից էջը, որը օգտագործողը բացել է, չպետք է հասանելիություն ունենա ուրիշ պատուհանի `http://gmail.com`-ին ու գողանա տվյալներ։ -- JavaScript-ը կարող է հեշտորեն կապվել ցանցով server-ի հետ, որտեղից որ կայքը գալիս է։ Բայց նրա հնարավորությունը, տվյալներ ստանալ այլ աղբյուրներից, սահմանափակ է։ Չնայած, որ հնարավոր է, սակայն այն պահանջում է հատուկ համաձայնություն server-ից։ + Այս սահմանափակումը, նույնպես, օգտագործողի ապահովության համար է։ `http://anysite.com`-ից էջը, որը օգտագործողը բացել է, չպետք է հասանելիություն ունենա ուրիշ պատուհանին, օրինակ `http://gmail.com`-ին, ու գողանա տվյալներ։ +- JavaScript-ը կարող է հեշտորեն կապվել ցանցով server-ի հետ, որտեղից որ կայքը գալիս է։ Բայց նրա հնարավորությունը, տվյալներ ստանալ այլ աղբյուրներից, սահմանափակ է։ Չնայած, որ հնարավոր է, սակայն այն պահանջում է հատուկ համաձայնություն (արտահայտված HTTP header-ներով) server-ից։ Կրկին, սա արված է օգտագործողի ապահովության համար։ ![](limitations.svg) -Այսպիսի սահմանափակումներ չկան, երբ JavaScript-ը օգտագործվում է browser-ից դուրս, օրինակ՝ server-ներում։ Ժամանակակից browser-ները նույնպես ունեն plug-in-ներ, որոնք կարող են հարցնել հավելյալ թույլտվություններ։ +Այսպիսի սահմանափակումներ չկան, երբ JavaScript-ը օգտագործվում է browser-ից դուրս, օրինակ՝ server-ներում։ Ժամանակակից browser-ները նույնպես ունեն plug-in-ներ, որոնք կարող են հարցնել հավելյալ թույլտվություններ։ -## Ի՞նչն է սարքում JavaScript-ը յուրօրինակ +## Ի՞նչն է դարձնում JavaScript-ը յուրօրինակ Կան գոնե *երեք* լավ բաներ JavaScript-ի մասին՝ @@ -88,11 +88,11 @@ JavaScript-ի հնարավորությունները browser-ում սահման + Հասարակ բաները արվում են հասարակ կերպով։ + Բոլոր մեծ browser-ները աշխատում են JavaScript—ի հետ։ ``` -JavaScript-ը միակ borwser-ային տեխնոլոգիան է, որը ներառում է այս 3 կետերը։ +JavaScript-ը միակ browser-ային տեխնոլոգիան է, որը ներառում է այս 3 կետերը։ Ահա, թե ինչն է սարքում դրան այսքան տարբերվող։ Ահա, թե ինչու է ամենատարածված գործիքը browser-ային ինտերֆեյսեր սարքելու համար։ -Այսպիսով, JavaScript-ը թույլ է տալիս ստեղծել server-ներ, mobile ծրագրեր և այլն։ +Ինչպես նաև, JavaScript-ը թույլ է տալիս ստեղծել server-ներ, mobile ծրագրեր և այլն։ ## Լեզուներ՝ JavaScript-ի «վրա» @@ -100,21 +100,23 @@ JavaScript-ի գրելաձևը բոլորին չի բավարարում։ Տար Դա սպասելի է, քանի որ ծրագրերն ու պահանջները տարբեր են բոլորի մոտ։ -Վերջերս բազում նոր լեզուներ են առաջացել, որոնք *transpile* են լինում (փոխվում) JavaScript-ի մինչև աշխատելը։ +Վերջերս բազում նոր լեզուներ են առաջացել, որոնք *transpile* են լինում (փոխվում) JavaScript-ի մինչև browser-ում աշխատելը։ Ժամանակակից գործիքները դարձնում են փոփոխվելը շատ արագ և թափանցիկ, թույլատրելով ծրագրավորողներին աշխատել այլ լեզվով, և ավտո-փոխակերպում են այն JavaScript-ի աննկատ կերպով։ Այսպիսի լեզուների օրինակներ են՝ -- [CoffeeScript](http://coffeescript.org/)-ը ունի կարճ գրելաձև, թույլատրելով ծրագրավորողին գրելկ ավելի մաքուր և ճշգրիտ կոդ։ Հիմնականում Ruby-ի ծրագրավորողներն են հավանում այն։ -- [TypeScript](http://www.typescriptlang.org/)-ը կենտրոնացած է խիստ տիպերի ներմուծման վրա, հեշտացնելով դժվար համակարգերի ծրագրավորումն ու հետագա ապահովումը։ Այն ստեղծվել է Microsoft-ի կողմից։ -- [Flow](http://flow.org/)-ը նույնպես ավելացնում է տվյալների տիպեր, սակայն այլ կերպով է աշխատում։ Ստեղծված է Facebook-ի կողմից։ +- [CoffeeScript](https://coffeescript.org/)-ը ունի կարճ գրելաձև, թույլ է տալիս ծրագրավորողին գրել ավելի մաքուր և ճշգրիտ կոդ։ Հիմնականում Ruby-ի ծրագրավորողներն են հավանում այն։ +- [TypeScript](https://www.typescriptlang.org/)-ը կենտրոնացած է խիստ տիպերի ներմուծման վրա, հեշտացնելով դժվար համակարգերի ծրագրավորումն ու հետագա ապահովումը։ Այն ստեղծվել է Microsoft-ի կողմից։ +- [Flow](https://flow.org/)-ը նույնպես ավելացնում է տվյալների տիպեր, սակայն այլ կերպ է աշխատում։ Ստեղծված է Facebook-ի կողմից։ - [Dart](https://www.dartlang.org/)-ը առանձին լեզու է, որը ունի իր սեփական engine-ը և աշխատում է ոչ-browser-ային միջավայրներում, ինչպես mobile ծրագրերում, սակայն կարող է թարգմանվել JavaScript-ի։ Ստեղծվել է Google-ի կողմից։ +- [Brython](https://brython.info/) թարգմանում է Python-ը JavaScript-ի։ +- [Kotlin](https://kotlinlang.org/docs/reference/js-overview.html) ժամանակակից, հակիրճ և ապահով ծրագրավորման լեզու է, կա հնարավորություն կոդը թարգմանելու նաև browser-ների or Node-ի համար։ Կան ուրիշներ։ Իհարկե, եթե նույնիսկ օգտագործենք թարգմանված լեզուները, պետք է իմանանք JavaScript, որպեսզի հասկանանք՝ ինչ ենք անում։ ## Ամփոփում - JavaScript-ը սկզբում ստեղծվել է միայն browser-ների համար, սակայն այն հիմա օգտագործվում է շատ այլ միջավայրներում նույնպես։ -- Այսօր JavaScript-ը ունի իր ուրույն տեղը ամենաընդունված browser-ի լեզուն, HTML/CSS-ի հետ լրիվ ներառմամբ։ -- Կան շատ լեզուներ, որոնք «թարգմանվում» են JavaScript-ի և տրամադրում որոշ հատկություններ։ Խորհուրդ է տրվում աչքի անց կացնել դրանք, գոնե մակերեսային, JavaScript-ում խորանալուց առաց։ +- Այսօր JavaScript-ը ունի իր ուրույն տեղը որպես ամենաընդունված browser-ի լեզու, HTML/CSS-ի հետ լրիվ ներառմամբ։ +- Կան շատ լեզուներ, որոնք «թարգմանվում» են JavaScript-ի և տրամադրում որոշ հատկություններ։ Խորհուրդ է տրվում աչքի անցկացնել դրանք, գոնե մակերեսային, JavaScript-ում խորանալուց հետո։ diff --git a/1-js/01-getting-started/1-intro/limitations.svg b/1-js/01-getting-started/1-intro/limitations.svg index a7863c63c..76ea43fd7 100644 --- a/1-js/01-getting-started/1-intro/limitations.svg +++ b/1-js/01-getting-started/1-intro/limitations.svg @@ -1 +1 @@ -https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file +https://javascript.info<script> ... </script>https://gmail.comhttps://javascript.info \ No newline at end of file diff --git a/1-js/01-getting-started/2-manuals-specifications/article.md b/1-js/01-getting-started/2-manuals-specifications/article.md index 85a7737cb..5a5e356e8 100644 --- a/1-js/01-getting-started/2-manuals-specifications/article.md +++ b/1-js/01-getting-started/2-manuals-specifications/article.md @@ -1,42 +1,37 @@ -# Manuals and specifications +# Ձեռնարկներ և բնութագրեր -This book is a *tutorial*. It aims to help you gradually learn the language. But once you're familiar with the basics, you'll need other sources. +Այս գիրքը *ուսուցողական ձեռնարկ* է, որի նպատակն է օգնել Ձեզ աստիճանաբար յուրացնել լեզուն: Երբ ծանոթանաք հիմնական հասկացություններին, ուսուցումը շարունակելու համար այլ աղբյուրների կարիք կլինի: -## Specification +## Բնութագիր (Specification) -[The ECMA-262 specification](https://www.ecma-international.org/publications/standards/Ecma-262.htm) contains the most in-depth, detailed and formalized information about JavaScript. It defines the language. +[ECMA-262 ստանդարտը](https://www.ecma-international.org/publications/standards/Ecma-262.htm) պարունակում է ամենախորը, մանրամասն և պատշոնապես ձևակերպված տեղեկատվությունը JavaScript-ի մասին: Այն սահմանում է լեզուն: -But being that formalized, it's difficult to understand at first. So if you need the most trustworthy source of information about the language details, the specification is the right place. But it's not for everyday use. +Բայց լեզվի նման ձևակերպումը դժվար է հասկանալ սկզբում: Եթե Ձեզ պետք է լեզվի մանրամասների մասին տեղեկատվության ամենավստահելի աղբյուրը, ապա այս պաշտոնական փաստաթուղթն է հենց այդ աղբյուրը: Սակայն այն նախատեսված չէ ամենօրյա օգտագործման համար: -A new specification version is released every year. In-between these releases, the latest specification draft is at . +Ամեն տարի թողարկվում է հատկորոշման նոր տարբերակ: Այս թողարկումների միջև ամենաթարմ սևագիրը գտնվում է հասցեում: -To read about new bleeding-edge features, including those that are "almost standard" (so-called "stage 3"), see proposals at . +Լեզվի նորագույն հնարավորությունների մասին կարդալու համար (ներառյալ «գրեթե ստանդարտ»՝ այսպես կոչված «stage 3» հնարավորությունները) այցելեք առաջարկությունների կայքը՝ հասցեով: -Also, if you're in developing for the browser, then there are other specs covered in the [second part](info:browser-environment) of the tutorial. +Ինչպես նաև, եթե Դուք լուծում եք մշակում վեբ զննիչների համար, ապա ձեռնարկի [երկրորդ մասում](info:browser-environment) ունենք տեղեկատվություն այլ բնութագրերի մասին: -## Manuals +## Ձեռնարկներ -- **MDN (Mozilla) JavaScript Reference** is a manual with examples and other information. It's great to get in-depth information about individual language functions, methods etc. +- **MDN (Mozilla) JavaScript Reference**-ը հիմնական ձեռնարկ է՝ հագեցած օրինակներով և այլ տեղեկություններով: Այն օգտակար է լեզվի առանձին հնարավորությունների, մեթոդների և այլնի մասին խորացած տեղեկություններ ստանալու համար. - One can find it at . + Այն կարելի է գտնել հասցեով: - Although, it's often best to use an internet search instead. Just use "MDN [term]" in the query, e.g. to search for `parseInt` function. +Հաճախ ավելի լավ է կատարել որոնում համացանցում: Ուղղակի օգտագործեք "MDN [տերմին]"-ը որոնման մեջ, օրինակ՝ , `parseInt` ֆունկցիան որոնելու համար. +## Համատեղելիության աղյուսակներ -- **MSDN** – Microsoft manual with a lot of information, including JavaScript (often referred to as JScript). If one needs something specific to Internet Explorer, better go there: . +JavaScript-ը զարգացող լեզու է, և պարբերաբար նոր հնարավորություններ են ավելացվում: - Also, we can use an internet search with phrases such as "RegExp MSDN" or "RegExp MSDN jscript". +Օգտվեք հետևյալ կայքերից՝ տարբեր վեբ զննիչներում և շարժիչներում դրանց հասանելիությունը ստուգելու համար. -## Compatibility tables +- - աջակցության աղյուսակներ՝ յուրաքանչյուր հատկանիշի համար, օրինակ՝ ցույց է տալիս այն շարժիչները, որոնցում սպասարկվում են գաղտնագրման ժամանակակից ֆուկցիաները: : +- - աղյուսակ է, կազմված լեզվի հնարավորություններից և շարժիչներից, որոնցում այդ հնարավորությունները սպասարկվում են կամ չեն սպասարկվում: -JavaScript is a developing language, new features get added regularly. +Բոլոր այս կայքերն օգտակար են առօրյա աշխատանքում, քանի որ պարունակում են արժեքավոր տեղեկություններ լեզվի հատկանիշների, դրանց սպասարկման մասին և այլն․․․ -To see their support among browser-based and other engines, see: - -- - per-feature tables of support, e.g. to see which engines support modern cryptography functions: . -- - a table with language features and engines that support those or don't support. - -All these resources are useful in real-life development, as they contain valuable information about language details, their support etc. - -Please remember them (or this page) for the cases when you need in-depth information about a particular feature. +Խնդրում ենք մտապահել այդ կայքերը (կամ այս էջը) այն դեպքերի համար, երբ անհրաժեշտ կլինի մանրամասն տեղեկություններ ստանալ որևէ հնարավորության մասին: diff --git a/1-js/01-getting-started/3-code-editors/article.md b/1-js/01-getting-started/3-code-editors/article.md index d03f03def..96ef12669 100644 --- a/1-js/01-getting-started/3-code-editors/article.md +++ b/1-js/01-getting-started/3-code-editors/article.md @@ -1,46 +1,49 @@ -# Code editors +# Խմբագրիչներ -A code editor is the place where programmers spend most of their time. +Ծրագրավորողները ժամանակի մեծ մասն անցկացում են խմբագրիչներում: -There are two main types of code editors: IDEs and lightweight editors. Many people use one tool of each type. +Կան երկու հիմնական տիպի խմբագրիչներ. IDE-ներ (ամբողջացված մշակման միջավայրեր) և թեթև խմբագրիչներ: Շատերն օգտագործում են դրանցից որևէ մեկը: ## IDE -The term [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) (Integrated Development Environment) refers to a powerful editor with many features that usually operates on a "whole project." As the name suggests, it's not just an editor, but a full-scale "development environment." +[IDE](https://en.wikipedia.org/wiki/Integrated_development_environment)-ն (ամբողջացված մշակման միջավայր) հզոր խմբագրիչ է բազմաթիվ հնարավորություններով, որում կարելի է մշակել և կառավարել ամբողջական նախագծեր: Ինչպես անունն է հուշում՝ այն ուղղակի խմբագրիչ չէ, այլ մշակման ամփոփ միջավայր: -An IDE loads the project (which can be many files), allows navigation between files, provides autocompletion based on the whole project (not just the open file), and integrates with a version management system (like [git](https://git-scm.com/)), a testing environment, and other "project-level" stuff. +IDE-ն բեռնում է նախագիծը (որը կարող է պարունակել բազմաթիվ ֆայլեր), թույլ է տալիս նավարկել ֆայլերի միջև, տրամադրում է ինքնալրացում (autocompletion), ինտեգրվում է տարբերակների կառավարման համակարգերի (VCS) հետ (օրինակ՝ [git](https://git-scm.com/)), ունի փորձարկման միջավայր և այլն: -If you haven't selected an IDE yet, consider the following options: +IDE-ների օրնակներ են. -- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). -- [WebStorm](http://www.jetbrains.com/webstorm/) (cross-platform, paid). +- [Visual Studio Code](https://code.visualstudio.com/) (հասանելի է տարբեր հարթակների համար, անվճար է): +- [WebStorm](https://www.jetbrains.com/webstorm/) (հասանելի է տարբեր հարթակների համար, վճարովի է): -For Windows, there's also "Visual Studio", not to be confused with "Visual Studio Code". "Visual Studio" is a paid and mighty Windows-only editor, well-suited for the .NET platform. It's also good at JavaScript. There's also a free version [Visual Studio Community](https://www.visualstudio.com/vs/community/). +Windows-ի համար կա նաև «Visual Studio»-ն, որը տարբերվում է «Visual Studio Code»-ից. «Visual Studio»-ն վճարովի և բազմաֆունկցիոնալ խմբագրիչ է, ստեղծված միայն Windows-ի համար և հարմարեցված .NET հարթակի համար: Ունի նաև [Visual Studio Community](https://www.visualstudio.com/vs/community/) անվճար տարբերակը: -Many IDEs are paid, but have a trial period. Their cost is usually negligible compared to a qualified developer's salary, so just choose the best one for you. +Շատ IDE-ներ վճարովի են, բայց ունեն փորձարկման շրջան: Նրանց գինը սովորաբար աննշան է փորձառու ծրագրավորողի աշխատավարձի հետ համեմատած, այնպես որ ուղղակի ընտրեք այն, որը Ձեր կարիքներին առավել լավ է համապատասխանում: -## Lightweight editors +## Թեթև խմբագրիչներ -"Lightweight editors" are not as powerful as IDEs, but they're fast, elegant and simple. +«Թեթև խմբագրիչներ»-ը IDE-ների նման հզոր չեն, բայց արագագործ են և պարզ: -They are mainly used to open and edit a file instantly. +Հիմնականում օգտագործվում են ֆայլերը արագ խմբագրելու համար: -The main difference between a "lightweight editor" and an "IDE" is that an IDE works on a project-level, so it loads much more data on start, analyzes the project structure if needed and so on. A lightweight editor is much faster if we need only one file. +IDE-ների և «թեթև խմբագրիչներ»-ի հիմնական տարբերությունն այն է, որ IDE-ն աշխատում է նախագծի մակարդակով, բեռնում է շատ ավելի տվայլներ, անհրաժեշտության դեպքում վերլուծում է նախագծի կառուցվածը և այլն: Թեթև խմբագրիչը շատ ավելի արագ է աշխատում, եթե միայն մեկ ֆայլի հետ գործ ունենք: -In practice, lightweight editors may have a lot of plugins including directory-level syntax analyzers and autocompleters, so there's no strict border between a lightweight editor and an IDE. +Գործնականում թեթև խմբագրիչները կարող են ունենալ բազմաթիվ հավելումներ (plugins), որոնք կարող են խմբագրիչի հնարավորություններ մեծացնել, այնպես որ թեթև խմբագրիչների և IDE-ների մեջ խիստ տարանջատում չկա: -The following options deserve your attention: +Հետևյալ խնբագրիչներն արժանի են Ձեր ուշադրությանը. -- [Atom](https://atom.io/) (cross-platform, free). -- [Visual Studio Code](https://code.visualstudio.com/) (cross-platform, free). -- [Sublime Text](http://www.sublimetext.com) (cross-platform, shareware). -- [Notepad++](https://notepad-plus-plus.org/) (Windows, free). -- [Vim](http://www.vim.org/) and [Emacs](https://www.gnu.org/software/emacs/) are also cool if you know how to use them. +- [Sublime Text](http://www.sublimetext.com) (հասանելի է տարբեր հարթակների համար, վճարովի է). +- [Notepad++](https://notepad-plus-plus.org/) (Windows, անվճար է). +- [Vim](http://www.vim.org/), [Emacs](https://www.gnu.org/software/emacs/). -## Let's not argue +## Այլ խմբագրիչներ -The editors in the lists above are those that either I or my friends whom I consider good developers have been using for a long time and are happy with. +Վերոնշյալ խմբագրիչները իմ, իմ ընկերների, որվքեր իմ համար լավ ծրագրավորողներ են օգտագործվում են երկար ժանանակ, և մենք գոհ ենք այդ խմբագրիչներով: -There are other great editors in our big world. Please choose the one you like the most. +Ընդհանրապես ասած գոյություն ունեն նաև այլ խմբագրիչներ: Դուք ազատ են ընտրել այն խմբագրիչը, որ ձեզ դուր է գալիս։ -The choice of an editor, like any other tool, is individual and depends on your projects, habits, and personal preferences. +Խմբագրիչի (ինչպես մնացած գործիքների) ընտրությունը Ձեր անձնական գործն է և կարող է կախված լինել նախագծից, սովորություններից, կամ անձնական նախընտրություններից: + +Հեղինակի անձնական կարծիքը հետևյալն է. + +- Frontend նախագծերի համար ես կօգտագործեի [Visual Studio Code](https://code.visualstudio.com/)։ +- Հակառակ դեպքում, այլ պլատֆորմների/լեզուների համար կդիտարկիե այլ խմբագրչներ, օրինակ XCode (Mac), Visual Studio (Windows) կամ Jetbrains ընտանիքի խմբագրիչ (Webstorm, PHPStorm, RubyMine, և այլն, կախված լեզվից)։ diff --git a/1-js/01-getting-started/4-devtools/article.md b/1-js/01-getting-started/4-devtools/article.md index c84d92704..0b214f49b 100644 --- a/1-js/01-getting-started/4-devtools/article.md +++ b/1-js/01-getting-started/4-devtools/article.md @@ -1,64 +1,63 @@ -# Developer console +# Ծրագրավորման բարձակ (Developer console) -Code is prone to errors. You will quite likely make errors... Oh, what am I talking about? You are *absolutely* going to make errors, at least if you're a human, not a [robot](https://en.wikipedia.org/wiki/Bender_(Futurama)). +Կոդը կարող է պարունակել սխալներ: Դուք ամենայն հավանականությամբ թույլ կտաք սխալներ ... Օ, ինչի՞ մասին եմ խոսում: Դուք *անպայման* սխալներ եք գործելու, եթե դուք իհարկե մարդ եք, ոչ թե [ռոբոտ](https://en.wikipedia.org/wiki/Bender_(Futurama))։ -But in the browser, users don't see errors by default. So, if something goes wrong in the script, we won't see what's broken and can't fix it. +Բայց զննիչում օգտվողները հիմնականում չեն տեսնում սխալներ: Այսպիսով, եթե սցենարում ինչ-որ բան այն չէ, մենք չենք տեսնի, թե որն է սխալը և չենք կարող շտկել այն: -To see errors and get a lot of other useful information about scripts, "developer tools" have been embedded in browsers. +Սխալները տեսնելու և սցենարների վերաբերյալ շատ այլ օգտակար տեղեկություններ ստանալու համար «ծրագրավորման գործիքները» տեղադրվել են վեբ զննիչներում: -Most developers lean towards Chrome or Firefox for development because those browsers have the best developer tools. Other browsers also provide developer tools, sometimes with special features, but are usually playing "catch-up" to Chrome or Firefox. So most developers have a "favorite" browser and switch to others if a problem is browser-specific. +Ծրագրավորողներից շատերը ծրագրավորման համար օգտագործում են Chrome կամ Firefox, քանի որ այդ զննիչներն ունեն ծրագրավորման լավագույն գործիքները: Մյուս զննիչներն էլ տրամադրում են ծրագրավորման գործիքներ, որոնք երբեմն ունեն հատուկ առանձնահատկություններ, բայց սովորաբար դրանք նման են Chrome-ի կամ Firefox-ի ծրագրավորման գործիքներին: Այսպիսով, ծրագրավորողների մեծամասնությունը ունի «սիրված» զննիչ և անցնում է մյուս զննիչներին, եթե խնդիրը հատուկ է տվյալ զննիչին: -Developer tools are potent; they have many features. To start, we'll learn how to open them, look at errors, and run JavaScript commands. +Ծրագրավորման գործիքները ուժեղ են, դրանք շատ առանձնահատկություններ ունեն: Սկսելու համար մենք կսովորենք, թե ինչպես բացել դրանք, դիտել սխալները և գործարկել JavaScript հրամաններ: ## Google Chrome -Open the page [bug.html](bug.html). +Բացեք [bug.html](bug.html) էջը։ -There's an error in the JavaScript code on it. It's hidden from a regular visitor's eyes, so let's open developer tools to see it. +Այնտեղ JavaScript կոդի մեջ սխալ կա: Այն թաքնված է սովորական այցելուի աչքից, ուստի եկեք բացենք ծրագրավորման գործիքները՝ այն տեսնելու համար: -Press `key:F12` or, if you're on Mac, then `key:Cmd+Opt+J`. +Սեղմեք `key:F12` կամ, եթե ունեք Mac, սեղմեք `key:Cmd+Opt+J`։ -The developer tools will open on the Console tab by default. +Ծրագրավորման գործիքները ինքնուրույն կբացվեն բարձակի ներդիրում: -It looks somewhat like this: +Դա ունի այսպիսի տեսք։ ![chrome](chrome.png) -The exact look of developer tools depends on your version of Chrome. It changes from time to time but should be similar. +Ծրագրավորման գործիքների տեսքը կախված է Chrome-ի տարբերակից: Այն ժամանակ առ ժամանակ փոխվում է, բայց պետք է հիմնականում նման լինի: -- Here we can see the red-colored error message. In this case, the script contains an unknown "lalala" command. -- On the right, there is a clickable link to the source `bug.html:12` with the line number where the error has occurred. +- Այստեղ մենք կարող ենք տեսնել կարմիրով գունավորված սխալի հաղորդագրությունը: Այս դեպքում սցենարը պարունակում է անհայտ «lalala» հրաման: +- Աջ կողմում կա աղբյուրի վրա սեղմվող հղում `bug.html:12` տողի համարով, որտեղ սխալը տեղի է ունեցել: -Below the error message, there is a blue `>` symbol. It marks a "command line" where we can type JavaScript commands. Press `key:Enter` to run them (`key:Shift+Enter` to input multi-line commands). +Սխալի հաղորդագրության տակ կա կապույտ գույնի `>` նշան։ Այն նշում է այն տողը, որտեղ մենք կարող ենք մուտքագրել JavaScript հրամաններ: Սեղմեք `key:Enter` դրանք գործարկելու համար: -Now we can see errors, and that's enough for a start. We'll come back to developer tools later and cover debugging more in-depth in the chapter . +Այժմ մենք կարող ենք տեսնել սխալները, որը բավական է սկզբի համար: Ավելի ուշ մենք կանդրադառնանք ծրագրավորման գործիքներին և ավելի խորը կծանոթանանք սխալների ողղմանը հետևյալ գլխում . +```smart header="Բազմատող մուտք" +Սովորաբար, երբ մենք կոդ ենք գրում բարձակի (console) մեջ և սեղմում ենք `key:Enter`, այն կատարվում է: -## Firefox, Edge, and others +Բազմաթիվ տողեր ներմուծելու համար սեղմեք `key:Shift+Enter`։ Այս կերպ կարելի է մուտքագրել JavaScript կոդի երկար ֆրագմենտներ: +``` + +## Firefox, Edge, և ուրիշները -Most other browsers use `key:F12` to open developer tools. +Շատ այլ զննիչներ օգտագործում են `key:F12` ծրագրավորման գործիքները բացելու համար: -The look & feel of them is quite similar. Once you know how to use one of these tools (you can start with Chrome), you can easily switch to another. +Նրանց տեսքը և աշխատանքը բավականին նման է: Երբ իմանաք, թե ինչպես օգտագործել այս գործիքներից մեկը (կարող եք սկսել Chrome-ից), կարող եք հեշտությամբ անցնել մյուսին: ## Safari -Safari (Mac browser, not supported by Windows/Linux) is a little bit special here. We need to enable the "Develop menu" first. +Safari (Mac զննիչ, հասանելի չէ Windows և Linux ՕՀ-երում) այն մի փոքր առանձնահատուկ է: Մենք նախ պետք է միացնենք «ծրագրավորման գործիքները»: -Open Preferences and go to the "Advanced" pane. There's a checkbox at the bottom: +Բացեք Preferences էջը և գնացեք "Advanced" պատուհան։ Ներքևում checkbox կա ծրագրավորման գործիքների համար։ ![safari](safari.png) -Now `key:Cmd+Opt+C` can toggle the console. Also, note that the new top menu item named "Develop" has appeared. It has many commands and options. - -```smart header="Multi-line input" -Usually, when we put a line of code into the console, and then press `key:Enter`, it executes. - -To insert multiple lines, press `key:Shift+Enter`. This way one can enter long fragments of JavaScript code. -``` +Հիմա `key:Cmd+Opt+C` կարող է բացել բարձակը (console): Նաև նշենք, որ հայտնվել է «ծրագրավորման գործիքներ» անվանմամբ ընտրացանկի նոր վերին կետը: Այն ունի շատ հրամաններ և տարբերակներ: -## Summary +## Ամփոփում -- Developer tools allow us to see errors, run commands, examine variables, and much more. -- They can be opened with `key:F12` for most browsers on Windows. Chrome for Mac needs `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (need to enable first). +- Ծրագրավորման գործիքները մեզ թույլ են տալիս տեսնել սխալներ, գործարկել հրամաններ, ուսումնասիրել փոփոխականները և ավելին: +- Դրանք կարելի է բացել `key:F12` Windows-ի համար նախատեսված շատ զննիչներում՝ օրինակ Chrome-ում։ Mac-ի համար պետք է սեղմել `key:Cmd+Opt+J`, Safari: `key:Cmd+Opt+C` (բայց անհրաժեշտ է նախ ակտիվացնել). -Now we have the environment ready. In the next section, we'll get down to JavaScript. +Այժմ մեր միջավայրը պատրաստ է: Հաջորդ բաժնում մենք կանդրադառնանք JavaScript- ին: diff --git a/1-js/01-getting-started/index.md b/1-js/01-getting-started/index.md index b327c7860..e36a66a4c 100644 --- a/1-js/01-getting-started/index.md +++ b/1-js/01-getting-started/index.md @@ -1,3 +1,3 @@ -# An introduction +# Ներածություն -About the JavaScript language and the environment to develop with it. +JavaScript լեզվի և այն միջավայրի մասին, որով ծրագրավորելու ենք: diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html new file mode 100644 index 000000000..ff1d871b0 --- /dev/null +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md index e69de29bb..81552913b 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/solution.md @@ -0,0 +1,2 @@ + +[html src="https://github.com/index.html"] diff --git a/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md b/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md index afed6a91d..b3650ef64 100644 --- a/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md +++ b/1-js/02-first-steps/01-hello-world/1-hello-alert/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Show an alert +# Ցուցադրել հաղորդագրություն -Create a page that shows a message "I'm JavaScript!". +Ստեղծեք էջ, որը ցույց է տալիս «I'm JavaScript!» հաղորդագրությունը։ -Do it in a sandbox, or on your hard drive, doesn't matter, just ensure that it works. +Կարող եք անել այստեղ, կամ ձեր համակարգչում, տարբերություն չկա։ Միայն համոզվեք, որ լուծումն աշխատում է։ [demo src="https://github.com/solution"] diff --git a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md index f42c41e6d..50e1a0547 100644 --- a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md +++ b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/solution.md @@ -1,8 +1,8 @@ -The HTML code: +HTML կոդը՝ [html src="https://github.com/index.html"] -For the file `alert.js` in the same folder: +`alert.js` ֆայլը՝ [js src="https://github.com/alert.js"] diff --git a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md index 26168d6a7..3aafb3982 100644 --- a/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md +++ b/1-js/02-first-steps/01-hello-world/2-hello-alert-ext/task.md @@ -2,8 +2,8 @@ importance: 5 --- -# Show an alert with an external script +# Ցուցադրել հաղորդագրություն արտաքին սկրիպտի միջոցով -Take the solution of the previous task . Modify it by extracting the script content into an external file `alert.js`, residing in the same folder. +Վերցրեք նախորդ խնդրի լուծումը ։ Տեղափոխեք script պիտակի պարունակությունը `alert.js` ֆայլի մեջ, որը պետք է գտնվի նույն պանակում։ -Open the page, ensure that the alert works. +Բացեք էջը և համոզվեք, որ լուծումն աշխատում է։ diff --git a/1-js/02-first-steps/01-hello-world/article.md b/1-js/02-first-steps/01-hello-world/article.md index f5487b915..d5ec0e815 100644 --- a/1-js/02-first-steps/01-hello-world/article.md +++ b/1-js/02-first-steps/01-hello-world/article.md @@ -1,17 +1,17 @@ # Hello, world! -This part of the tutorial is about core JavaScript, the language itself. +Ձեռնարկի այս մասը վերաբերում է բուն JavaScript լեզվին։ -But we need a working environment to run our scripts and, since this book is online, the browser is a good choice. We'll keep the amount of browser-specific commands (like `alert`) to a minimum so that you don't spend time on them if you plan to concentrate on another environment (like Node.js). We'll focus on JavaScript in the browser in the [next part](/ui) of the tutorial. +Մեզ հարկավոր է աշխատանքային միջավայր, որտեղ գործարկելու ենք մեր սկրիպտերը։ Քանի որ այս ձեռնարկը առցանց է, ապա զննիչը լավ ընտրություն է։ Զննիչներին հատուկ հրամանները (օրինակ՝ `alert`) կօգտագործենք հնարավորինս քիչ, որպեսզի դրանց վրա շատ ժամանակ չծախսեք, եթե պլանավորում եք այլ միջավայրում աշխատել (օրինակ՝ Node.js)։ Մենք կկենտրոնանանք զննիչներում JavaScript–ի աշխատանքին ձեռնարկի [հաջորդ բաժնում](/ui)։ -So first, let's see how we attach a script to a webpage. For server-side environments (like Node.js), you can execute the script with a command like `"node my.js"`. +Սկզբում տեսնենք, թե ինչպես էջին կցել որևէ սկրիպտ։ Սերվերային միջավայրերում (օրինակ՝ Node.js) սկրիպտը կարելի է գործարկել հետևյալ կամ նմանատիպ հրամանով՝ `"node my.js"`։ -## The "script" tag +## «script» պիտակը (tag) -JavaScript programs can be inserted into any part of an HTML document with the help of the ` */!* -

...After the script.

+

...սկրիպտից հետո։

@@ -35,24 +35,24 @@ For instance: ``` ```online -You can run the example by clicking the "Play" button in the right-top corner of the box above. +Կարող եք գործարկել օրինակը սեղմելով «Play» կոճակը։ ``` -The ` ``` - This trick isn't used in modern JavaScript. These comments hide JavaScript code from old browsers that didn't know how to process the ` ``` -Here, `/path/to/script.js` is an absolute path to the script from the site root. One can also provide a relative path from the current page. For instance, `src="https://github.com/script.js"` would mean a file `"script.js"` in the current folder. +Այստեղ, `/path/to/script.js`–ը կայքի արմատից (root) սկրիպտի բացարձակ ուղին է (absolute path)։ Կարելի է տրամադրել նաև հարաբերական ուղի տվյալ էջից։ Օրինակ՝ `src="https://github.com/script.js"`–ը, ինչպես `src="https://github.com/script.js"`-ը, նշանակում է, որ `"script.js"` ֆայլը գտնվում է տվյալ պանակում (folder)։ -We can give a full URL as well. For instance: +Կարելի է նաև նշել ամբողջական URL։ Օրինակ՝ ```html - + ``` -To attach several scripts, use multiple tags: +Մի քանի սկրիպտեր կցելու համար կարելի է օգտագործել մի քանի պիտակներ՝ ```html @@ -90,29 +90,29 @@ To attach several scripts, use multiple tags: ``` ```smart -As a rule, only the simplest scripts are put into HTML. More complex ones reside in separate files. +Սովորաբար միայն պարզ սկրիպտերն են ներդնում HTML–ում։ Առավել բարդերը տեղակայվում են առանձին ֆայլերում։ -The benefit of a separate file is that the browser will download it and store it in its [cache](https://en.wikipedia.org/wiki/Web_cache). +Առանձին ֆայլի առավելությունն այն է, որ զննիչը ներբեռնում է այն և պահում իր [cache](https://en.wikipedia.org/wiki/Web_cache)–ում։ -Other pages that reference the same script will take it from the cache instead of downloading it, so the file is actually downloaded only once. +Մյուս էջերը, որոնք հղում ունեն այդ նույն սկրիպտին, նորից ներբեռնելու փոխարեն կվերցնեն այն cache–ից։ -That reduces traffic and makes pages faster. +Սա էջերը դարձնոմ է ավելի արագ և նվազեցնում է traffic–ը։ ``` -````warn header="If `src` is set, the script content is ignored." -A single ` ``` -We must choose either an external ` @@ -122,11 +122,11 @@ The example above can be split into two scripts to work: ``` ```` -## Summary +## Ամփոփում -- We can use a ``. +- Կարող ենք օգտագործել ``։ -There is much more to learn about browser scripts and their interaction with the webpage. But let's keep in mind that this part of the tutorial is devoted to the JavaScript language, so we shouldn't distract ourselves with browser-specific implementations of it. We'll be using the browser as a way to run JavaScript, which is very convenient for online reading, but only one of many. +Այնքա՜ն բան կա սովորելու զննիչներում սկրիպտերի աշխատանքի և էջերի հետ դրանց փոխազդեցության մասին։ Հիշենք, որ ձեռնարկի այս բաժինը նվիրված է JavaScript լեզվին, այնպես որ պետք չէ ինքներս մեզ շեղենք զննիչներին հատուկ առանձնահատկություններով։ Մենք կօգտագործենք զննիչը JavaScript կոդը գործարկելու համար, ինչը հարմար է առցանց ընթերցանության համար։ diff --git a/1-js/02-first-steps/02-structure/article.md b/1-js/02-first-steps/02-structure/article.md index b18aab19e..33eede157 100644 --- a/1-js/02-first-steps/02-structure/article.md +++ b/1-js/02-first-steps/02-structure/article.md @@ -1,44 +1,44 @@ -# Code structure +# Կոդի կառուցվածքը -The first thing we'll study is the building blocks of code. +Առաջին հերթին ուսումնասիրենք կոդի կառուցման տարրական էլեմենտները։ -## Statements +## Դրույթներ (statements) -Statements are syntax constructs and commands that perform actions. +Դրույթները շարահյուսական (syntax) կառույցներ և հրամաններ են, որոնք իրականացնում են գործողություններ։ -We've already seen a statement, `alert('Hello, world!')`, which shows the message "Hello, world!". +Մենք արդեն հանդիպել են դրույթի օրինակի՝ `alert('Hello, world!')`, որի գործողությունն է ցույց տալ «Hello, world!» հաղորդագրությունը։ -We can have as many statements in our code as we want. Statements can be separated with a semicolon. +Կոդում կարող ենք ունենալ այնքան դրույթներ, որքան կամենանք։ Դրույթներն իրարից բաժանվում են կետ-ստորակետով (semicolon)։ -For example, here we split "Hello World" into two alerts: +Օրինակ՝ բաժանենք «Hello, world!»–ը երկու alert դրույթների․ ```js run no-beautify alert('Hello'); alert('World'); ``` -Usually, statements are written on separate lines to make the code more readable: +Դրույթները սովորաբար տեղադրում են առանձին տողերում՝ կոդն ավելի ընթեռնելի դարձնելու համար։ ```js run no-beautify alert('Hello'); alert('World'); ``` -## Semicolons [#semicolon] +## Կետ-ստորակետ [#semicolon] -A semicolon may be omitted in most cases when a line break exists. +Կետ-ստորակետը դեպքերի մեծամասնությունում կարելի է բաց թողել, եթե առակա է նոր տողին անցում․ -This would also work: +Հետևյալ օրինակը կաշխատի՝ ```js run no-beautify alert('Hello') alert('World') ``` -Here, JavaScript interprets the line break as an "implicit" semicolon. This is called an [automatic semicolon insertion](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion). +Այստեղ JavaScript–ն ընկալում է նոր տողին անցումը որպես կետ-ստորակետ։ Սա կոչվում է [կետ-ստորակետի ինքաշխատ ավելացում](https://tc39.github.io/ecma262/#sec-automatic-semicolon-insertion) (automatic semicolon insertion)։ -**In most cases, a newline implies a semicolon. But "in most cases" does not mean "always"!** +**Դեպքերի մեծամասնությունում նոր տողին անցումը դիտարկվում է որպես կետ-ստորակետ։ Կան դեպքեր, երբ այս պնդումը ճիշտ չէ։** -There are cases when a newline does not mean a semicolon. For example: +Որոշ դեպքերում նոր տողին անցումը չի դիտվում որպես կետ-ստորակետ։ Օրինակ՝ ```js run no-beautify alert(3 + @@ -46,114 +46,107 @@ alert(3 + + 2); ``` -The code outputs `6` because JavaScript does not insert semicolons here. It is intuitively obvious that if the line ends with a plus `"+"`, then it is an "incomplete expression", so the semicolon is not required. And in this case that works as intended. +Կոդն արտածում է `6`, քանի որ JavaScript–ն այստեղ կետ-ստորակետ չի ավելացնում։ Ակնհայտ է․ քանի որ տողն ավարտվում է `"+"`–ով, ապա այն անավարտ արտահայտություն է և կետ–ստորակերի կարիք չկա։ -**But there are situations where JavaScript "fails" to assume a semicolon where it is really needed.** +**Կան դեպքեր, երբ JavaScript–ը կետ–ստորակետ չի դնում, բայց իրականում այն անհրաժեշտ է։** -Errors which occur in such cases are quite hard to find and fix. +Այս դեպքերում առաջացող սխալները բավական դժվար է գտնել և ուղղել։ -````smart header="An example of an error" -If you're curious to see a concrete example of such an error, check this code out: +````smart header="Սխալի օրինակ" +Ահա կոդ, որի միջոցով կհանգենք նման սխալի՝ ```js run -[1, 2].forEach(alert) +alert("Hello"); + +[1, 2].forEach(alert); ``` -No need to think about the meaning of the brackets `[]` and `forEach` yet. We'll study them later. For now, just remember the result of the code: it shows `1` then `2`. +Այժմ չմտածենք այն մասին, թե ինչ են նշանակում `[]` փակագծերը և `forEach`–ը։ Դրանց մասին սովորելու ենք հետագայում։ Այս պահին հիշենք այս կոդի արդյունքը՝ այն արտածում է `Hello`, `1`, հետո `2`։ -Now, let's add an `alert` before the code and *not* finish it with a semicolon: +Հիմա եկեք `alert`-ից հետո գտնվող կետ–ստորակետը ջնջենք՝ ```js run no-beautify -alert("There will be an error") +alert("Hello") -[1, 2].forEach(alert) +[1, 2].forEach(alert); ``` -Now if we run the code, only the first `alert` is shown and then we have an error! - -But everything is fine again if we add a semicolon after `alert`: -```js run -alert("All fine now"); - -[1, 2].forEach(alert) -``` - -Now we have the "All fine now" message followed by `1` and `2`. - +Միակ տարբերությունը բերված կոդում այդ կետ֊ստորակետն է, որն այլևս չկա։ +Եթե գործարկենք այս կոդը միայն `Hello`֊ը կարտածվի (սխալը տեսնելու համար անհրաժեշտ է բացել բարձակը)։ -The error in the no-semicolon variant occurs because JavaScript does not assume a semicolon before square brackets `[...]`. +Սխալն առաջանում է այն պատճառով, որ JavaScript–ը `[...]`–ից առաջ կետ–ստորակետ չի դնում։ Քանի որ կետ–ստորակետն ինքնաշխատ չի ավելացվում, այս օրինակում ողջ կոդը դիտվում է որպես մեկ դրույթ։ -So, because the semicolon is not auto-inserted, the code in the first example is treated as a single statement. Here's how the engine sees it: +Ահա, թե ինչպես է JavaScript շարժիչը տեսնում այն՝ ```js run no-beautify -alert("There will be an error")[1, 2].forEach(alert) +alert("Սրանից հետո սխալ է լինելու")[1, 2].forEach(alert) ``` -But it should be two separate statements, not one. Such a merging in this case is just wrong, hence the error. This can happen in other situations. +Պարզ է, որ այստեղ երկու դրույթ պետք է լինի՝ ոչ թե մեկ։ Դրույթների նման միաձուլումն էլ հանգեցնում է սխալին։ Սա կարող է տեղի ունենալ նաև այլ իրավիճակներում։ ```` -We recommend putting semicolons between statements even if they are separated by newlines. This rule is widely adopted by the community. Let's note once again -- *it is possible* to leave out semicolons most of the time. But it's safer -- especially for a beginner -- to use them. +Խորհուրդ ենք տալիս դրույթներից հետո դնել կետ–ստորակետ, նույնիսկ եթե դրույթներն առաձին տողերում են։ Սա տարածված կանոն է։ Եվս մեկ անգամ կրկնենք․ դեպքերի մեծամասնությունում *հնարավոր է* բաց թողել կետ–ստորակետը, բայց ավելի ապահով է (հատկապես սկսնակների համար) օգտագործել այն։ -## Comments +## Մեկնաբանություններ [#code-comments] -As time goes on, programs become more and more complex. It becomes necessary to add *comments* which describe what the code does and why. +Ժամանակի ընթացքում ծրագրերի բարդությունն աճում է։ Անհրաժեշտ է լինում հաճախ *մեկնաբանությունների* միջոցով նկարագրել, թե ինչ է անում տվյալ կոդը և ինչու։ -Comments can be put into any place of a script. They don't affect its execution because the engine simply ignores them. +Մեկնաբանությունները կարող են զետեղվել սկրիպտի կամայական հատվածում։ Դրանք չեն ազդում կոդի աշխատանքի վրա, քանի որ արհամարհվում են շարժիչի կողմից։ -**One-line comments start with two forward slash characters `//`.** +**Միատող մեկնաբանությունները սկսվում են կրկնակի շեղ գծերով՝ `//`։** -The rest of the line is a comment. It may occupy a full line of its own or follow a statement. +Տողի մնացած մասը դառնում է մեկնաբանություն։ Այն կարող է զբաղեցնել ամբողջ տողը, կամ գտնվել դրույթից հետո։ -Like here: +Ինչպես հետևյալ օրինակում՝ ```js run -// This comment occupies a line of its own +// Այս մեկնաբանությունը գտնվում է առանձին տողում և զբաղեցնում է ամբողջ տողը alert('Hello'); -alert('World'); // This comment follows the statement +alert('World'); // Այս մեկնաբանությունը գտնվում է դրույթից հետո ``` -**Multiline comments start with a forward slash and an asterisk /* and end with an asterisk and a forward slash */.** +**Բազմատող մեկնաբանությունները սկսվում են շեղ գծով և աստղանիշով՝ /*, վերջանում աստղանիշով և շեղ գծով՝ */։** -Like this: +Ինչպես այս օրինակում՝ ```js run -/* An example with two messages. -This is a multiline comment. +/* Այս օրինակն արտածում է երկու հաղորդագրություն։ +Սա բազմատող մեկնաբանություն է։ */ alert('Hello'); alert('World'); ``` -The content of comments is ignored, so if we put code inside /* ... */, it won't execute. +Մեկնաբանության պարունակությունն արհամարհվում է, այնպես որ, եթե կոդ տեղադրենք /* ... */–ի մեջ, ապա այն չի գործարկվի։ -Sometimes it can be handy to temporarily disable a part of code: +Սովորաբար օգտակար է ժամանակավորապես անջատել կոդի մի մասը՝ ```js run -/* Commenting out the code +/* Այս կոդը գտնվում է մեկնաբանության մեջ և չի գործարկվի alert('Hello'); */ alert('World'); ``` -```smart header="Use hotkeys!" -In most editors, a line of code can be commented out by pressing the `key:Ctrl+/` hotkey for a single-line comment and something like `key:Ctrl+Shift+/` -- for multiline comments (select a piece of code and press the hotkey). For Mac, try `key:Cmd` instead of `key:Ctrl`. +```smart header="Օգտագործեք ստեղների համադրություններ" +Բազմաթիվ խմբագրիչներում տողը կարելի է զետեղել մեկնաբանության մեջ, սեղմելով `key:Ctrl+/` ստեղները՝ միատող մեկնաբանության համար, և `key:Ctrl+Shift+/` ստեղները (կամ նմանատիպ այլ համադրություն)՝ բազմատող մեկնաբանությունների համար։ Mac–ում `key:Ctrl`–ի փոխարեն օգտագործեք `key:Cmd`, իսկ `key:Shift`–ի փոխարեն՝ `key:Option`։ ``` -````warn header="Nested comments are not supported!" -There may not be `/*...*/` inside another `/*...*/`. +````warn header="Մեկնաբանությունները չեն ներդրվում" +Չի կարելի ներդնել `/*...*/`–ը այլ բազմատող մեկնաբանության մեջ։ -Such code will die with an error: +Այս կոդը սխալ կառաջացնի՝ ```js run no-beautify /* - /* nested comment ?!? */ + /* ներդրված մեկնաբանություն ?!? */ */ alert( 'World' ); ``` ```` -Please, don't hesitate to comment your code. +Աշխատեք թողնել մեկնաբանություններ ձեր կոդում։ -Comments increase the overall code footprint, but that's not a problem at all. There are many tools which minify code before publishing to a production server. They remove comments, so they don't appear in the working scripts. Therefore, comments do not have negative effects on production at all. +Մեկնաբանությունները մեծացնում են սկրիպտի ծավալը, սակայն դա ընդհանրապես խնդիր չէ։ Կան բազմաթիվ գործիքներ, որոնք հնարավորինս փոքրացնում են կոդը և բերում վերջնական տեսքի՝ միչև այն սերվեր վերբեռնելը։ Դրանք ջնջում են մեկնաբանությունները, այնպես որ վերջնական սկրիպտում դրանք բացակայում են։ -Later in the tutorial there will be a chapter that also explains how to write better comments. +Այս ձեռնարկի բաժնում կանդրադառնանք այն հարցին, թե ինչպես գրել լավ մեկնաբանություններ։ diff --git a/1-js/02-first-steps/03-strict-mode/article.md b/1-js/02-first-steps/03-strict-mode/article.md index 573d76bc5..93adc3598 100644 --- a/1-js/02-first-steps/03-strict-mode/article.md +++ b/1-js/02-first-steps/03-strict-mode/article.md @@ -1,57 +1,58 @@ -# The modern mode, "use strict" +# "use strict" -For a long time, JavaScript evolved without compatibility issues. New features were added to the language while old functionality didn't change. +Երկար ժամանակ JavaScript–ը զարգանում էր առանց համատեղելիության խնդիրների։ Լեզվում նոր հնարավորություններ էին ավելացվում, իսկ հները փոփոխության չէին ենթարկվում։ -That had the benefit of never breaking existing code. But the downside was that any mistake or an imperfect decision made by JavaScript's creators got stuck in the language forever. +Այս մոտեցման առավելությունն այն էր, որ գոյություն ունեցող կոդը միշտ աշխատում էր առանց փոփոխության։ Մյուս կողմից, JavaScript–ը նախագծողների ցանկացած սխալը, կամ թերի որոշումը դատապարտված էր մնալ լեզվում ընդմիշտ։ -This was the case until 2009 when ECMAScript 5 (ES5) appeared. It added new features to the language and modified some of the existing ones. To keep the old code working, most such modifications are off by default. You need to explicitly enable them with a special directive: `"use strict"`. +Սա ճիշտ էր մինչև 2009 թ․, երբ հայտնվեց ECMAScript 5–ը (ES5)։ Այն լեզվում ավելացրեց նոր հնարավորություններ և փոփոխեց որոշ արդեն գոյություն ունեցողները։ Որպեսզի հին կոդն աշխատեր, այդ փոփոխությունները անջատած էին լռելյայն։ Դրանք պետք էր բացահայտ միացնել, օգատգործելով հատուկ հրահանգ՝ `"use strict"`։ -## "use strict" +## "use strict" հրահանգը -The directive looks like a string: `"use strict"` or `'use strict'`. When it is located at the top of a script, the whole script works the "modern" way. +Այս հրահանգն իրենից ներկայացնում է սովորական տող՝ `"use strict"`, կամ `'use strict'`։ Երբ այն գտնվում է սկրիպտի վերևում, ապա սկրիպն աշխատում է ժամանակակից ձևով։ -For example: +Օրինակ՝ ```js "use strict"; -// this code works the modern way +// այս կոդն աշխատում է ժամանակակից ձևով ... ``` -We will learn functions (a way to group commands) soon. Looking ahead, let's note that `"use strict"` can be put at the beginning of the function body instead of the whole script. Doing that enables strict mode in that function only. But usually, people use it for the whole script. +Շուտով սովորելու ենք ֆունկցիաների մասին, այնպես որ նախօրոք ասենք, որ `"use strict"`–ը կարելի է տեղադրել ֆունկցիայի սկզբում։ Դա միացնում է ժամանակակից ռեժիմը միայն այդ ֆունկցիայում։ Սովորաբար `"use strict"`–ն օգտագործվում է ողջ սկրիպտի համար։ +````warn header="Համոզվեք, որ \"use strict\"–ը գտվում է վերևում" +Համոզվեք, որ `"use strict"`–ը գտվում է սկրիպտի վերևում, հակառակ դեպքում այն կարող է չաշխատել։ -````warn header="Ensure that \"use strict\" is at the top" -Please make sure that `"use strict"` is at the top of your scripts, otherwise strict mode may not be enabled. - -Strict mode isn't enabled here: +Այս օրինակում այն չի աշխատում՝ ```js no-strict alert("some code"); -// "use strict" below is ignored--it must be at the top +// "use strict"–ն այստեղ արհամարհվում է՝ այն պետք է գտնվի ամենավերևում "use strict"; -// strict mode is not activated +// ժամանակակից ռեժիմը միացված չէ ``` -Only comments may appear above `"use strict"`. +`"use strict"`–ից առաջ կարող են լինել միայն մեկնաբանություններ՝ ```` -```warn header="There's no way to cancel `use strict`" -There is no directive like `"no use strict"` that reverts the engine to old behavior. +```warn header="Հնարավոր չէ չեղարկել `use strict`–ի գործողությունը" +Գոյություն չունի `"no use strict"` հրահանգ, որը չեղարկում է շարժիչի պահվածքը։ -Once we enter strict mode, there's no return. +Այսպիսով՝ `use strict`–ի գործողությունը չի կարելի չեղարկել։ ``` -## Browser console +## Զննիչի բարձակը (browser console) + +[Ծրագրավորման բարձակ](info:devtools)–ում `use strict`–ը լռելյայն չի օգտագործում։ -For the future, when you use a browser console to test features, please note that it doesn't `use strict` by default. +Երբեմն `use strict`–ի կիրառումը կարող է հանգեցնել սխալ արդյունքների։ -Sometimes, when `use strict` makes a difference, you'll get incorrect results. +Ունեմն ինչպե՞ս օգտագործել `use strict`–ը բարձակում։ -You can try to press `key:Shift+Enter` to input multiple lines, and put `use strict` on top, like this: +Կարող եք սեղմել `key:Shift+Enter` ստեղները մի քանի տող ներմուծելու համար և գրել `use strict` վերևում՝ ```js 'use strict'; @@ -59,27 +60,26 @@ You can try to press `key:Shift+Enter` to input multiple lines, and put `use str ``` -It works in most browsers, namely Firefox and Chrome. +Սա կաշխատի հիմնական զննիչներում (Firefox, Chrome)։ -If it doesn't, the most reliable way to ensure `use strict` would be to input the code into console like this: +Եթե օգտագործում եք շատ հին զննիչ, ապա կարելի է `use strict`–ը դնել ֆունկցիայի մեջ՝ ```js (function() { 'use strict'; - // ...your code... + // ...ձեր կոդն այստեղ... })() ``` -## Always "use strict" +## Արդյոք պե՞տք է օգտագործել "use strict" հրահանգը + +Պատասխանը կարող է ակնհայտ թվալ, բայց դա այդպես չէ։ -We have yet to cover the differences between strict mode and the "default" mode. +Գուցե ինչ–որ մեկը խորդուրդ տա սկսել բոլոր սկրիպտերը `"use strict"`–ով։ Սակայն, ժամանակակից JavaScript–ի «class»–երը և «module»–ները (որոնք մենք հետո կուսումնասիրենք), օգտագործում են `use strict`–ը լռելյայն։ Այնպես որ կարիք չկա օգտագործել `"use strict"` հրահանգը այդ դեպքերում։ -In the next chapters, as we learn language features, we'll note the differences between the strict and default modes. Luckily, there aren't many and they actually make our lives better. +**Այսպիսով՝ մենք կօգտագործենք `"use strict";`–ի մեր սկրիպտերում։ Հետո, երբ ձեր կոդն ամբողջովին կգտնվի «class»–երում և «module»–ներում՝ կարող եք այն բաց թողել։** -For now, it's enough to know about it in general: +Հաջորդ բաժիններում, որտեղ կսովորենք լեզվի այլ հնարավորությունների մասին, կտեսնենք տարբերությունը `"use strict"`–ով և առանց դրա։ -1. The `"use strict"` directive switches the engine to the "modern" mode, changing the behavior of some built-in features. We'll see the details later in the tutorial. -2. Strict mode is enabled by placing `"use strict"` at the top of a script or function. Several language features, like "classes" and "modules", enable strict mode automatically. -3. Strict mode is supported by all modern browsers. -4. We recommended always starting scripts with `"use strict"`. All examples in this tutorial assume strict mode unless (very rarely) specified otherwise. +Այս ձեռնարկի բոլոր օրինակները ենթադրում են `"use strict"`–ի կիրառում, եթե հակառակը նշված չէ (խիստ հազվադեպ դեպքերում)։ diff --git a/1-js/02-first-steps/04-variables/1-hello-variables/solution.md b/1-js/02-first-steps/04-variables/1-hello-variables/solution.md index 9249e1c84..adc05f57c 100644 --- a/1-js/02-first-steps/04-variables/1-hello-variables/solution.md +++ b/1-js/02-first-steps/04-variables/1-hello-variables/solution.md @@ -1,7 +1,7 @@ -In the code below, each line corresponds to the item in the task list. +Ստորև բերված կոդում յուրաքանչյուր տող համապատասխանում է հանձնարարության ամեն մի կետի: ```js run -let admin, name; // can declare two variables at once +let admin, name; // կարող ենք հայտարարել միանգամից երկու փոփոխական name = "John"; diff --git a/1-js/02-first-steps/04-variables/1-hello-variables/task.md b/1-js/02-first-steps/04-variables/1-hello-variables/task.md index 84f009e8c..25b05f765 100644 --- a/1-js/02-first-steps/04-variables/1-hello-variables/task.md +++ b/1-js/02-first-steps/04-variables/1-hello-variables/task.md @@ -2,9 +2,9 @@ importance: 2 --- -# Working with variables +# Աշխատում ենք փոփոխականների հետ -1. Declare two variables: `admin` and `name`. -2. Assign the value `"John"` to `name`. -3. Copy the value from `name` to `admin`. -4. Show the value of `admin` using `alert` (must output "John"). +1. Հայտարարեք երկու փոփոխական. `admin` և `name`: +2. `name`-ին վերագրեք `"John"` արժեքը: +3. Պատճենեք `name`-ի արժեքը `admin`-ի մեջ: +4. Ցույց տվեք `admin`-ի արժեքը `alert`-ի միջոցով (պետք է արտատպի "John"): diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md index d56e54d28..bfafac0eb 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/solution.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/solution.md @@ -1,21 +1,21 @@ -## The variable for our planet +## Մեր մոլորակի անունը պարունակող փոփոխականը -That's simple: +Այստեղ ամեն ինչ պարզ է՝ ```js let ourPlanetName = "Earth"; ``` -Note, we could use a shorter name `planet`, but it might be not obvious what planet it refers to. It's nice to be more verbose. At least until the variable isNotTooLong. +Կարող էինք նաև օգտագործել ավելի կարճ `planet` անունը, բայց հասկանալի չէր լինի, թե որ մոլորակի անունն է այն: Ավելի լավ է փոփոխականի անունը լինի երկար, բայց հասկանալի: Բայց ոչ շատ երկար: -## The name of the current visitor +## Տվյալ հաճախորդի անունը պահող փոփոխականը ```js let currentUserName = "John"; ``` -Again, we could shorten that to `userName` if we know for sure that the user is current. +Կարող էինք ընտրել նաև `userName` անունը, եթե հաստատ գիտենք, որ հաճախորդը տվյալ պահին կայքում է: -Modern editors and autocomplete make long variable names easy to write. Don't save on them. A name with 3 words in it is fine. +Ժամանակակից խմբագրիչները կարող են նպաստել փոփոխականների երկար անուններ գրելուն: Արժի փոփոխականի անունը պահել կարճ, ոչ ավել, քան երեք բառ: -And if your editor does not have proper autocompletion, get [a new one](/code-editors). +Եվ եթե Ձեզ խմբագրիչը չունի կոդի ինքնաշխատ հուշումներ (autocompletion) տրամադրելու հնարավորությունը, խորհուրդ ենք տալիս [փոխել այն](/code-editors): diff --git a/1-js/02-first-steps/04-variables/2-declare-variables/task.md b/1-js/02-first-steps/04-variables/2-declare-variables/task.md index f364badf4..b03486f48 100644 --- a/1-js/02-first-steps/04-variables/2-declare-variables/task.md +++ b/1-js/02-first-steps/04-variables/2-declare-variables/task.md @@ -2,7 +2,7 @@ importance: 3 --- -# Giving the right name +# Ընտրում ենք փոփոխականի ճիշտ անուն -1. Create a variable with the name of our planet. How would you name such a variable? -2. Create a variable to store the name of a current visitor to a website. How would you name that variable? +1. Հայտարարեք փոփոխական, որը կպարունակի մեր մոլորակի անունը: Ի՞նչ կդնեիք նման փոփոխականի անունը: +2. Հայտարարեք փոփոխական, որը կպարունակի կայքի տվյալ հաճախորդի անունը: Ի՞նչ կդնեիք նման փոփոխականի անունը: diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md index acd643fde..f4de464d8 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/solution.md @@ -1,5 +1,5 @@ -We generally use upper case for constants that are "hard-coded". Or, in other words, when the value is known prior to execution and directly written into the code. +Ընդհանուր առմամբ մեծատառերով գրվում են այսպես կոչված «hard-coded» հաստատունները: Այլ կերպ ասած, երբ հաստատունի արժեքը նախօրոք հայտնի է մինչև կոդը գործարկելը, և այդ արժեքը ուղղակի գրված է կոդում: -In this code, `birthday` is exactly like that. So we could use the upper case for it. +Այս օրինակում `birthday`-ը պարունակում է նման արժեք, այնպես որ իմաստ ունի այն գրել մեծատառերով: -In contrast, `age` is evaluated in run-time. Today we have one age, a year after we'll have another one. It is constant in a sense that it does not change through the code execution. But it is a bit "less of a constant" than `birthday`: it is calculated, so we should keep the lower case for it. +Մյուս կողմից, `age`-ի արժեքը հայտնի է դառնում միայն երբ կոդը գործարկվում է: Տարեցտարի այդ փոփոխականի արժեքը փոխվում է: Այն հաստատուն է այն իմաստով, որ իր արժեքը չի փոխում կոդի գործարկման ժամանակ: Բայց այն «ավելի քիչ հաստատուն» է, քան `birthday`-ը, քանի որ արժեքը հաշվվում է սկրիպտի աշխատանքի ընթացքում: Այսպիսով՝ արժի գրել այն փոքրատառ: diff --git a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md index 5fd18f90a..39f2eee67 100644 --- a/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md +++ b/1-js/02-first-steps/04-variables/3-uppercast-constant/task.md @@ -2,9 +2,9 @@ importance: 4 --- -# Uppercase const? +# Մեծատառ հաստատուններ -Examine the following code: +Դիտարկենք ստորև բերված կոդը՝ ```js const birthday = '18.04.1982'; @@ -12,13 +12,14 @@ const birthday = '18.04.1982'; const age = someCode(birthday); ``` -Here we have a constant `birthday` date and the `age` is calculated from `birthday` with the help of some code (it is not provided for shortness, and because details don't matter here). +Այստեղ ունենք `birthday` հաստատունը, որն ամսաթիվ է պարունակում, և `age` հաստատունը։ -Would it be right to use upper case for `birthday`? For `age`? Or even for both? +`age`-ը հաշվարկվում է `birthday`-ից `someCode()`-ի, ինչը նշանակում է ֆունկցիայի կանչի, ինչի մասին դեռ չենք խոսել (բայց շուտով կխոսենք)։ Այստեղ մանրամասները չենք քննարկում հակիրճության համար, կարևորն այն է, որ `age`-ը ինչ-որ կերտ հաշվարկվում է `birthday`-ի հիման վրա: + +Ճիշտ կլինե՞ր մեծատառերով գրել `birthday`-ը: Իսկ `age`-ը՞: Կամ գուցե երկու՞սն էլ: ```js -const BIRTHDAY = '18.04.1982'; // make uppercase? +const BIRTHDAY = '18.04.1982'; // սա՞ գրել մեծատառերով -const AGE = someCode(BIRTHDAY); // make uppercase? +const AGE = someCode(BIRTHDAY); // թե՞ age-ը ``` - diff --git a/1-js/02-first-steps/04-variables/article.md b/1-js/02-first-steps/04-variables/article.md index 6d680b3b0..d7d389d46 100644 --- a/1-js/02-first-steps/04-variables/article.md +++ b/1-js/02-first-steps/04-variables/article.md @@ -1,61 +1,61 @@ -# Variables +# Փոփոխականներ -Most of the time, a JavaScript application needs to work with information. Here are two examples: -1. An online shop -- the information might include goods being sold and a shopping cart. -2. A chat application -- the information might include users, messages, and much more. +JavaScript ծրագրերը հաճախ աշխատում են տեղեկատվության հետ: Ահա երկու օրինակ. +1. Առցանց խանութ -- տեղեկատվության մաս են կազմում վաճառվող ապրանքները, զամբյուղը (shopping cart): +2. A chat application -- տեղեկատվությունթան մաս են կազմում օգտատերերը, հաղորդագրությունները և այլն: -Variables are used to store this information. +Փոփոխականներն օգտագործվում են այս ամենը պահելու համար: -## A variable +## Փոփոխական -A [variable](https://en.wikipedia.org/wiki/Variable_(computer_science)) is a "named storage" for data. We can use variables to store goodies, visitors, and other data. +[Փոփոխականը](https://en.wikipedia.org/wiki/Variable_(computer_science)) տվյալների «պահեստարան» է: Փոփոխականները կարելի է օգտագործել տարբեր տիպի տվյալներ պահելու համար: -To create a variable in JavaScript, use the `let` keyword. +JavaScript-ում փոփոխական ստեղծելու համար կիրառվում է `let` հիմնաբառը: -The statement below creates (in other words: *declares*) a variable with the name "message": +Ստորև բերված հայտարատությունը ստեղծում է (այլ կերպ ասած *հայտարարում է*) «message» անունով փոփոխական. ```js let message; ``` -Now, we can put some data into it by using the assignment operator `=`: +Այդ փոփոխականի մեջ որևէ տվյալ տեղադրելու համար կիրառենք վերագրման օպերատորը՝ `=`. ```js let message; *!* -message = 'Hello'; // store the string +message = 'Hello'; // տեղադրել տողը «message» անունով փոփոխականի մեջ */!* ``` -The string is now saved into the memory area associated with the variable. We can access it using the variable name: +Այդ տողն այժմ պահվում է հիշողության այն հատվածում, որը վերագրված է մեր փոփոխականին: Փոփոխականի պարունակությունը կարելի է կարդալ փոփոխականի անվան միջոցով. ```js run let message; message = 'Hello!'; *!* -alert(message); // shows the variable content +alert(message); // ցույց է տալիս փոփոխականի պարունակությունը */!* ``` -To be concise, we can combine the variable declaration and assignment into a single line: +Հակիրճ լինելու համար կհամատեղենք փոփոխականների հայտարարումն ու արժեքի վերագրումը մեկ տողում. ```js run -let message = 'Hello!'; // define the variable and assign the value +let message = 'Hello!'; // հայտատարում և արժեք ենք վերագրում փոփոխականին alert(message); // Hello! ``` -We can also declare multiple variables in one line: +Նույն տողում կարելի է հայտարարել մի քանի փոփոխականներ. ```js no-beautify let user = 'John', age = 25, message = 'Hello'; ``` -That might seem shorter, but we don't recommend it. For the sake of better readability, please use a single line per variable. +Չնայած սա կարող է հարմար թվալ, այնուամենայնիվ մենք խորհուրդ ենք տալիս յուրաքանչյուր փոփոխական հայտարարել նոր տողում: -The multiline variant is a bit longer, but easier to read: +Մի քանի տողում փոփոխականներ հայտարարելը ավելի երկար է, բայց և ավելի հեշտ է դրանք ընթեռնելը. ```js let user = 'John'; @@ -63,14 +63,15 @@ let age = 25; let message = 'Hello'; ``` -Some people also define multiple variables in this multiline style: +Երբեմն փոփոխականները հայտարարվում են հետևյալ կերպ. + ```js no-beautify let user = 'John', age = 25, message = 'Hello'; ``` -...Or even in the "comma-first" style: +...կամ էլ այսպես: ```js no-beautify let user = 'John' @@ -78,47 +79,47 @@ let user = 'John' , message = 'Hello'; ``` -Technically, all these variants do the same thing. So, it's a matter of personal taste and aesthetics. - +Սրանք բոլորն էլ նույն արդյունքն ունեն և յուրաքանչյուր ոք ընտրում է ըստ ճաշակի և նախընտրության: -````smart header="`var` instead of `let`" -In older scripts, you may also find another keyword: `var` instead of `let`: +````smart header="`var`՝ `let`-ի փոխարեն" +`let`-ի փոխարեն կարող է հանդիպել նաև `var` հիմնաբառը. ```js *!*var*/!* message = 'Hello'; ``` -The `var` keyword is *almost* the same as `let`. It also declares a variable, but in a slightly different, "old-school" way. +`var` հիմնաբառը *գրեթե* նույն `let`-ն է: Այն նույնպես հայտարարում է փոփոխական, բայց կան որոշ տարբերություններ: -There are subtle differences between `let` and `var`, but they do not matter for us yet. We'll cover them in detail in the chapter . +`let`-ի և `var`-ի մեջ կան փոքր տարբերություններ, որոնք մենք դեռ չեն դիտարկի: Այդ տարբերությունների մասին մանրամասն կխոսենք բաժնում: ```` ## A real-life analogy -We can easily grasp the concept of a "variable" if we imagine it as a "box" for data, with a uniquely-named sticker on it. +Հեշտությամբ կարելի է հասկանալ «փոփոխական»-ի գաղափարը, եթե պատկերացնենք այն որպես տվյալների «արկղ», որն իրեն առանձնահատուկ պիտակ ունի: -For instance, the variable `message` can be imagined as a box labeled `"message"` with the value `"Hello!"` in it: +Օրինակ՝ `message` փոփոխականը կարելի է պատկերացնել որպես `"message"` պիտակով արկղ, որի մեջ պահվում է `"Hello!"` արժեքը: ![](variable.svg) -We can put any value in the box. +Արկղի մեջ կարող ենք դնել ցանկացած արժեք: + +Այդ արժեքները կարող ենք փոխել ըստ անհրաժեշտության. -We can also change it as many times as we want: ```js run let message; message = 'Hello!'; -message = 'World!'; // value changed +message = 'World!'; // արժեքը փոխված է alert(message); ``` -When the value is changed, the old data is removed from the variable: +Երբ արժեքը փոխվում է, հին տվյալները դառնում են անհասանելի. ![](variable-change.svg) -We can also declare two variables and copy data from one into the other. +Մենք նաև կարող ենք սահմանել երկու փոփոխական և պատճենել դրանցից մեկի տվյալները մյուսի մեջ: ```js run let hello = 'Hello world!'; @@ -126,99 +127,113 @@ let hello = 'Hello world!'; let message; *!* -// copy 'Hello world' from hello into message +// պատճենել 'Hello world'-ը hello-ից message message = hello; */!* -// now two variables hold the same data +// այժմ երկու փոփոխականները պարունակում են նույն տվյալները alert(hello); // Hello world! alert(message); // Hello world! ``` -```smart header="Functional languages" -It's interesting to note that there exist [functional](https://en.wikipedia.org/wiki/Functional_programming) programming languages, like [Scala](http://www.scala-lang.org/) or [Erlang](http://www.erlang.org/) that forbid changing variable values. +````warn header="Նույն փոփոխականը մեկից ավել անգամ սահմանելը սխալ է առաջացնում" +Փոփոխականը կարող է սահմանվել միայն մեկ անգամ: + +Փոփոխականի հայտարարման կրկնությունը սխալ է. + +```js run +let message = "This"; + +// կրկնվող 'let' հայտարարությունները առաջացնում են սխալներ +let message = "That"; // SyntaxError: 'message' has already been declared +``` +Այսպիսով՝ մենք պետք է փոփոխականները հայտարարենք մեկ անգամ, այնուհետև օգտագործենք առանց `let` հիմնաբառի: +```` + +```smart header="Ֆունկցիոնալ լեզուներ" +Հետաքրքիր է իմանալ, որ գոյություն ունեն [ֆունկցիոնալ](https://hy.wikipedia.org/wiki/%D5%96%D5%B8%D6%82%D5%B6%D5%AF%D6%81%D5%AB%D5%B8%D5%B6%D5%A1%D5%AC_%D5%AE%D6%80%D5%A1%D5%A3%D6%80%D5%A1%D5%BE%D5%B8%D6%80%D5%B8%D6%82%D5%B4) ծրագրավորման լեզուներ, օրինակ՝ [Haskell](https://en.wikipedia.org/wiki/Haskell), որոնք արգելում են փոփոխականների արժեքների փոփոխությունները: -In such languages, once the value is stored "in the box", it's there forever. If we need to store something else, the language forces us to create a new box (declare a new variable). We can't reuse the old one. +Նման լեզուներում, երբ արժեքները տեղադրվում են «արկղների» դրանք այլևս փոխել հնարավոր չէ: Եթե անհրաժեշտ է պահել այլ արժեք, լեզուն ստիպում է ստեղծել նոր արկղ (հայտարարել նոր փոփոխական): -Though it may seem a little odd at first sight, these languages are quite capable of serious development. More than that, there are areas like parallel computations where this limitation confers certain benefits. Studying such a language (even if you're not planning to use it soon) is recommended to broaden the mind. +Չնայած առաջին հայացքից այդ պահվածքը կարող է տարօրինակ թվալ, այս լեզուները իրոք օգտագործվում են լուրջ նախագծեր մշակելու համար: Ավելին, կան ոլորտներ (օր.՝ զուգահեռ հաշվարկներ), որտեղ այս սահմանափակումները շահավետ են: ``` -## Variable naming [#variable-naming] +## Փոփոխականների անվանումը [#variable-naming] -There are two limitations on variable names in JavaScript: +JavaScript-ում փոփոխականների անունների հետ կապված կա երկու սահմանափակում. -1. The name must contain only letters, digits, or the symbols `$` and `_`. -2. The first character must not be a digit. +1. Անունը պետք է պարունակի միայն տառեր, թվեր, կամ `$` և `_` նշանները: +2. Փոփոխականի անվան առաջին նշանը չպետք է լինի թիվ: -Examples of valid names: +Ստորև բերված են վավեր անունների օրինակներ. ```js let userName; let test123; ``` -When the name contains multiple words, [camelCase](https://en.wikipedia.org/wiki/CamelCase) is commonly used. That is: words go one after another, each word except first starting with a capital letter: `myVeryLongName`. +Երբ փոփոխականի անունը պարունակում է մի քանի բառեր, սովորաբար օգտագործվում է [camelCase](https://en.wikipedia.org/wiki/CamelCase): camelCase-ի օգինակ է հետևյալ անունը՝ `myVeryLongName`: -What's interesting -- the dollar sign `'$'` and the underscore `'_'` can also be used in names. They are regular symbols, just like letters, without any special meaning. +Ինչպես նշեցինք `'$'` և `'_'` նշանները ևս կարող են օգտագործվել փոփոխականների անուններում, սակայն դրանք որոշակի հատուկ իմաստ չունեն: -These names are valid: +Հետևյալ անունները վավեր են. ```js run untrusted -let $ = 1; // declared a variable with the name "$" -let _ = 2; // and now a variable with the name "_" +let $ = 1; // հայտարարել փոփոխական "$" անունով +let _ = 2; // և "_" անունով alert($ + _); // 3 ``` -Examples of incorrect variable names: +Սրանք անվավեր անունների օրինակներ են. ```js no-beautify -let 1a; // cannot start with a digit +let 1a; // փոփոխականի անունը չի կարող սկսել թվով -let my-name; // hyphens '-' aren't allowed in the name +let my-name; // գծիկները թույլատրված չեն անուններում ``` -```smart header="Case matters" -Variables named `apple` and `AppLE` are two different variables. +```smart header="Case-ը կարևոր է" +`apple` և `AppLE` անուններով փոփոխականները երկու տարբեր փոփոխականներ են: ``` -````smart header="Non-Latin letters are allowed, but not recommended" -It is possible to use any language, including cyrillic letters or even hieroglyphs, like this: +````smart header="Ոչ լատիներեն տառերը թույլատրված են, բայց խորհուրդ չի տրվում դրանք օգտագործել" +Հնարավոր է օգտագործել ցանկացած լեզվի տառեր, ներառյալ կիրիլյան տառեր, չինական լոգոգրամներ, և այլն, օրինակ՝ ```js let имя = '...'; let 我 = '...'; ``` -Technically, there is no error here, such names are allowed, but there is an international tradition to use English in variable names. Even if we're writing a small script, it may have a long life ahead. People from other countries may need to read it some time. +Այստեղ սխալ չկա։ Նման անունները վավեր են, սակայն ամենուր ընդունված է օգտագործել անգլերենը: Նույնիսկ եթե փոքր սկրիպտ եք գրում, այն հնարավոր է կարդան նաև այլազգի ծրագրավորողները: ```` -````warn header="Reserved names" -There is a [list of reserved words](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords), which cannot be used as variable names because they are used by the language itself. +````warn header="Վերապահված անուններ (reserved names)" +Գոյություն ունի [վերապահված բառերի ցանկ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#Keywords), որոնք չի կարելի օգտագործել որպես փոփոխականի անուն, քանի որ դրանք օգտագործվում են հենց լեզվի կողմից: -For example: `let`, `class`, `return`, and `function` are reserved. +Օրինակ՝ `let`, `class`, `return`, և `function` հիմնաբառերը վերապահված են: -The code below gives a syntax error: +Ստորև բերված կոդը սկալ է պարունակում. ```js run no-beautify -let let = 5; // can't name a variable "let", error! -let return = 5; // also can't name it "return", error! +let let = 5; // փոփոխականի անունը չի կարող լինել "let" +let return = 5; // ինչպես նաև "return" ``` ```` -````warn header="An assignment without `use strict`" +````warn header="Փոփոխականի վերագրումն առանց `use strict`-ի" -Normally, we need to define a variable before using it. But in the old times, it was technically possible to create a variable by a mere assignment of the value without using `let`. This still works now if we don't put `use strict` in our scripts to maintain compatibility with old scripts. +Սովորաբար փոփոխականներն օգտագործելուց առաջ պետք է դրանք հայտարարել: Նախկինում հնարավոր էր ստեղծել փոփոխական միայն վերագրման միջոցով՝ առանց օգտագործելու `let`-ը: Դա այժմ ևս հնարավոր է, եթե սկրիպտում չլինի `use strict`-ը: ```js run no-strict -// note: no "use strict" in this example +// ուշադրություն դրաձրեք, որ այս սկրիպտում "use strict" չկա -num = 5; // the variable "num" is created if it didn't exist +num = 5; // "num" փոփոխականը ստեղծվում է, եթե այն գոյություն չուներ alert(num); // 5 ``` -This is a bad practice and would cause an error in strict mode: +Սա սխալ է առաջացնում «strict mode»-ում. ```js "use strict"; @@ -229,15 +244,15 @@ num = 5; // error: num is not defined ``` ```` -## Constants +## Հաստատուններ -To declare a constant (unchanging) variable, use `const` instead of `let`: +Հաստատուն սահմանելու համար `let`-ի փոխարեն օգտագործեք `const`. ```js const myBirthday = '18.04.1982'; ``` -Variables declared using `const` are called "constants". They cannot be reassigned. An attempt to do so would cause an error: +`const`-ով սահմանված փոփոխականները կոչվում են «հաստատուններ». դրանց չի կարելի նոր արժեք վերագրել: Նման փորձը սխալի կհանգեցնի. ```js run const myBirthday = '18.04.1982'; @@ -245,16 +260,15 @@ const myBirthday = '18.04.1982'; myBirthday = '01.01.2001'; // error, can't reassign the constant! ``` -When a programmer is sure that a variable will never change, they can declare it with `const` to guarantee and clearly communicate that fact to everyone. - +Եթե վստահ եք, որ փոփոխականի արժեքը երբեր չի փոխվի, սահմանեք այն որպես `const` դա ապահովելու և բոլորին այդ փաստն ակնհայտ դարձնելու համար: -### Uppercase constants +### Մեծատառ հաստատուններ -There is a widespread practice to use constants as aliases for difficult-to-remember values that are known prior to execution. +Ընդունված է հաստատունները օգտագործել դժվար հիշվող արժեքների համար, որոնք հայտնի են նախօրոք: -Such constants are named using capital letters and underscores. +Նման հաստատունները գրվում են մեծատառ և ընդգծիկներով (underscores): -For instance, let's make constants for colors in so-called "web" (hexadecimal) format: +Օրինակ՝ սահմանենք հաստատուններ որոշ գույների համար. ```js run const COLOR_RED = "#F00"; @@ -262,69 +276,70 @@ const COLOR_GREEN = "#0F0"; const COLOR_BLUE = "#00F"; const COLOR_ORANGE = "#FF7F00"; -// ...when we need to pick a color +// ...գույնն օգտագործելու համար let color = COLOR_ORANGE; alert(color); // #FF7F00 ``` -Benefits: +Առավելությունները. + +- `COLOR_ORANGE`-ը շատ ավելի հեշտ է մտապահել, քան `"#FF7F00"`-ը, +- ավելի հեշտ է սխալ գրել `"#FF7F00"`-ը, քան `COLOR_ORANGE`-ը, +- կոդը կարդալիս `COLOR_ORANGE`-ն ավելի շատ իմաստ է արտահայտում, քան `#FF7F00`-ը: -- `COLOR_ORANGE` is much easier to remember than `"#FF7F00"`. -- It is much easier to mistype `"#FF7F00"` than `COLOR_ORANGE`. -- When reading the code, `COLOR_ORANGE` is much more meaningful than `#FF7F00`. +Եկեք հասկանանք երբ է պետք հաստատունները գրել մեծատառերով, իսկ երբ ոչ: -When should we use capitals for a constant and when should we name it normally? Let's make that clear. +«Հաստատունն» ուղղակի փոփոխական է, որի արժեքը չի փոխվում: Կան հաստատուններ, որոնց արժեքը հայտնի է մինչև սկրիպտը գործարկելը (ինչպես, օրինակ, վերոնշյալ գույները) և կան հաստատուններ, որոնց արժեքը հայտնի է դառնում միայն սկրիպտի աշխատանքի ընթացքում և այն չի փոխվում սկզբնական վերագրումից հետո: -Being a "constant" just means that a variable's value never changes. But there are constants that are known prior to execution (like a hexadecimal value for red) and there are constants that are *calculated* in run-time, during the execution, but do not change after their initial assignment. +Օրինակ՝ -For instance: ```js const pageLoadTime = /* time taken by a webpage to load */; ``` -The value of `pageLoadTime` is not known prior to the page load, so it's named normally. But it's still a constant because it doesn't change after assignment. +`pageLoadTime`-ի արժեքը հայտնի չէ, քանի դեռ էջն ամբողջությամբ չի բեռնվել, այդ իսկ պատճառով այն մեծատառ չէ։ Սակայն այն հաստատուն է, քանի որ վերագրելուց հետո արժեքը չի փոխվում: -In other words, capital-named constants are only used as aliases for "hard-coded" values. +Այլ կերպ ասած՝ մեծատառերով գրված հաստատունները օգտագործվում են միայն որպես «կոշտ կոդավորված» արժեքների փոխանուններ: -## Name things right +## Ճիշտ անունների ընտրությունը կարևոր է -Talking about variables, there's one more extremely important thing. +Երբ խոսում ենք փոփոխականների մասին մի կարևոր բան կա, որը պետք է միշտ հիշել: -A variable name should have a clean, obvious meaning, describing the data that it stores. +Փոփոխականը պետք է ունենա պարզ, միարժեք հասկանալի, և պարունակող արժեքը բնութագրող անուն: -Variable naming is one of the most important and complex skills in programming. A quick glance at variable names can reveal which code was written by a beginner versus an experienced developer. +Փոփոխականները անվանելը կարևորագույն և դժվար ունակություններից է ծրագրավորման մեջ: Արագ նայելով փոփոխականնրի անուններին կարելի է կարծիք կազվել կոդը գրած ծրագրավորողի ունակությունների մասին: -In a real project, most of the time is spent modifying and extending an existing code base rather than writing something completely separate from scratch. When we return to some code after doing something else for a while, it's much easier to find information that is well-labeled. Or, in other words, when the variables have good names. +Իրական նախագծերում հիմնական ժամանակը ծախսվում է գոյություն ունեցող կոդը փոփոխելու և զարգացնելու վրա: Երբ վերադառնում ենք մեր գրած կոդին որոշ ժամանակ հետո շատ ավելի հեշտ է վերհիշել այն, երբ փոփոխականներն իմաստավոր անուններ ունեն: -Please spend time thinking about the right name for a variable before declaring it. Doing so will repay you handsomely. +Հորդորում ենք ծախսել որոշ ժամանակ մտածելու ճիշտ անուններ փոփոխականների համար մինչև դրանք հայտարարելը: Հավատացեք, դա Ձեզ հետագայում միայն կօգնի: -Some good-to-follow rules are: +Ահա որոշ կանոններ, որոնց արժի հետևել. -- Use human-readable names like `userName` or `shoppingCart`. -- Stay away from abbreviations or short names like `a`, `b`, `c`, unless you really know what you're doing. -- Make names maximally descriptive and concise. Examples of bad names are `data` and `value`. Such names say nothing. It's only okay to use them if the context of the code makes it exceptionally obvious which data or value the variable is referencing. -- Agree on terms within your team and in your own mind. If a site visitor is called a "user" then we should name related variables `currentUser` or `newUser` instead of `currentVisitor` or `newManInTown`. +- օգտագործեք հեշտ ընթեռնելի անուններ, օրինակ՝ `userName`, կամ `shoppingCart`, +- մի օգտագործեք հապավումներ կամ նմանատիպ կարճ անուններ՝ `a`, `b`, `c`, եթե, իհարկե, գիտեք ինչ եք ունում, +- անուններն ընտրեք հնարավորինս նկարագրող և հակիրճ: `data`-ն և `value`-ն վատ անունների օրինակներ են, քանի որ նման անունները ոչինչ չեն ասում: Նման անունները կարելի է միայն օգտագործել այն դեպքերում, երբ կոդի համատեքստը (context) միանշանակ հասկանալի է դարձնում, թե ինչ արժեքներ են այդ փոփոխականները պարունակում, +- եկեք համաձայնության Ձեր թիմով, կամ ինքներդ Ձեզ հետ: Եթե Ձեր կայքի հաճախորդին անվանում եք «user», ապա նրա հետ կապված փոփոխականները պետք է ունենան նմանատիպ անուններ՝ `currentUser`, կամ `newUser` և ոչ թե `currentVisitor`, կամ `newManInTown`: -Sounds simple? Indeed it is, but creating descriptive and concise variable names in practice is not. Go for it. +Այս ամենը պարզ է հնչում, սակայն գործնականում հասկանալի և հակիրճ փոփոխականների անուններ մտածելն այնքան էլ հեշտ չէ: Ինքներդ փորձեք և կհամոզվեք: -```smart header="Reuse or create?" -And the last note. There are some lazy programmers who, instead of declaring new variables, tend to reuse existing ones. +```smart header="Օգտագործե՞լ եղած փոփոխականը, թե՞ նորը մտածել" +Որոշ ծույլ ծրագրավորողներ փորձում են կրկին օգտագործել հայտարարած փոփոխականները՝ նորերը սահմանելու փոխարեն: -As a result, their variables are like boxes into which people throw different things without changing their stickers. What's inside the box now? Who knows? We need to come closer and check. +Արդյունքում նրանց փոփոխականները նմանվում են արկղերի, որոնց մեջ տարբեր տիպի իրեր են նետվում, առանց արկի պիտակը փոխելու: Այպես դժվարանում է հասկանալ, թե ինչ է պահվում այդ արկղում: -Such programmers save a little bit on variable declaration but lose ten times more on debugging. +Նման դեպքերում ծրագրավորողները մի փոքր խնայում են փոփոխականները հայտարարելիս, սակայն ավելի շատ ժամանակ են ծախսում, երբ խնդիրներ են առաջանում: -An extra variable is good, not evil. +Այսպիսով՝ ավել փոփոխականը վատ բան չէ: -Modern JavaScript minifiers and browsers optimize code well enough, so it won't create performance issues. Using different variables for different values can even help the engine optimize your code. +Ժամանակակից JavaScript-ում կան գործիքներ, որոնք փոքրացնում և լավարկում (optimize) են կոդն այն աստիճան, որ ավել փոփոխականներ ունենալը խնդիրներ չի առաջացնի: Նույնիսկ հակառակը. տարբեր արժեքները տարբեր փոփոխականներում պահելը կարող է օգնել շարժիչին այս գործը կատարելիս: ``` -## Summary +## Ամփոփում -We can declare variables to store data by using the `var`, `let`, or `const` keywords. +Տվյալները պահելու համար կարող ենք հայտարարել փոփոխականներ, օգտագործելով `var`, `let`, կամ `const` հիմնաբառերը: -- `let` -- is a modern variable declaration. -- `var` -- is an old-school variable declaration. Normally we don't use it at all, but we'll cover subtle differences from `let` in the chapter , just in case you need them. -- `const` -- is like `let`, but the value of the variable can't be changed. +- `let` -- ներկայումս փոփոխականները հայտարարվում են այս հիմնաբառով: +- `var` -- նախկինում օգտագործվում էր փոփոխականներ հայտարարելու համար: Այժմ այն չենք օգտագործում, իսկ սրա և `let`-ի տարբերությունները կքննարկենք բաժնում: +- `const` -- նման է `let`-ին, սակայն այս հիմնաբառով հայտարարված փոփոխականի արժեքը չի փոխվում: -Variables should be named in a way that allows us to easily understand what's inside them. +Փոփոխականների անունները պետք է պարզ հուշեն, թե ինչ արժեք է պարունակում տվյալ փոփոխականը։ diff --git a/1-js/02-first-steps/04-variables/variable-change.svg b/1-js/02-first-steps/04-variables/variable-change.svg index 427a6388c..1b2679238 100644 --- a/1-js/02-first-steps/04-variables/variable-change.svg +++ b/1-js/02-first-steps/04-variables/variable-change.svg @@ -1 +1 @@ -"World!""Hello!"message \ No newline at end of file +"World!""Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/04-variables/variable.svg b/1-js/02-first-steps/04-variables/variable.svg index 5d15c9e4e..1c3d8b0cb 100644 --- a/1-js/02-first-steps/04-variables/variable.svg +++ b/1-js/02-first-steps/04-variables/variable.svg @@ -1 +1 @@ -"Hello!"message \ No newline at end of file +"Hello!"message \ No newline at end of file diff --git a/1-js/02-first-steps/05-types/1-string-quotes/solution.md b/1-js/02-first-steps/05-types/1-string-quotes/solution.md index 68a13c15b..90f3892a7 100644 --- a/1-js/02-first-steps/05-types/1-string-quotes/solution.md +++ b/1-js/02-first-steps/05-types/1-string-quotes/solution.md @@ -1,15 +1,15 @@ -Backticks embed the expression inside `${...}` into the string. +Թեք չակերտները ներառում են `${...}`-ի մեջ գրված արտահայտության արդյունքը տողի մեջ։ ```js run -let name = "Ilya"; +let name = "David"; -// the expression is a number 1 +// արտահայտությունը 1 թիվն է alert( `hello ${1}` ); // hello 1 -// the expression is a string "name" +// արտահայտությունը "name" տողն է alert( `hello ${"name"}` ); // hello name -// the expression is a variable, embed it -alert( `hello ${name}` ); // hello Ilya +// արտահայտությունը փոփոխական, ներդնենք այն +alert( `hello ${name}` ); // hello David ``` diff --git a/1-js/02-first-steps/05-types/1-string-quotes/task.md b/1-js/02-first-steps/05-types/1-string-quotes/task.md index 14ea6b4d6..07005f96c 100644 --- a/1-js/02-first-steps/05-types/1-string-quotes/task.md +++ b/1-js/02-first-steps/05-types/1-string-quotes/task.md @@ -2,16 +2,16 @@ importance: 5 --- -# String quotes +# Տողի չակերտները -What is the output of the script? +Ինչպիսի՞ն կլինի սկրիպտի արտաբերած արդյունքը։ ```js -let name = "Ilya"; +let name = "David"; alert( `hello ${1}` ); // ? alert( `hello ${"name"}` ); // ? alert( `hello ${name}` ); // ? -``` \ No newline at end of file +``` diff --git a/1-js/02-first-steps/05-types/article.md b/1-js/02-first-steps/05-types/article.md index 0da617373..631e958fb 100644 --- a/1-js/02-first-steps/05-types/article.md +++ b/1-js/02-first-steps/05-types/article.md @@ -1,203 +1,241 @@ -# Data types +# Տվյալների տիպերը -A variable in JavaScript can contain any data. A variable can at one moment be a string and at another be a number: +JavaScript-ում արժեքը միշտ պատկանում է տվյալների որոշակի տիպի։ Օրինակ՝ տող (string) կամ թիվ (number)։ + +JavaScript-ում կան տվյալների ութ հիմնական տիպեր։ Այս գլխում մենք կանրադառնանք դրանց մասնակիորեն, իսկ հաջորդ գլուխներում կխոսենք դրանցից յուրաքանչյուրի մասին ավելի մանրամասն։ + +Փոփոխականը կարող է պարունակել ցանկացած տիպի տվյալ։ Օրինակ՝ սկզբում այն կարող է պարունակել տող (string), իսկ հետո՝ փոխարինվել թվով (number)․ ```js -// no error -let message = "hello"; +// Սխալ չկա +let message = "բարև"; message = 123456; ``` -Programming languages that allow such things are called "dynamically typed", meaning that there are data types, but variables are not bound to any of them. +Այն լեզուները, որոնք թույլ են տալիս նման պահվածք, կոչվում են «դինամիկ տիպավորված»։ Դա նշանակում է, որ տիպերը գոյություն ունեն, բայց փոփոխականները կապված չեն դրանցից ոչ մեկի հետ։ -There are seven basic data types in JavaScript. Here, we'll cover them in general and in the next chapters we'll talk about each of them in detail. - -## A number +## Թիվ (Number) ```js let n = 123; n = 12.345; ``` -The *number* type represents both integer and floating point numbers. +*number* տիպը ներկայացնում է ինչպես ամբողջ թվերը, այնպես էլ տասնորդական թվերը։ -There are many operations for numbers, e.g. multiplication `*`, division `/`, addition `+`, subtraction `-`, and so on. +Թվերի համար գոյություն ունեն բազմաթիվ գործողություններ, օրինակ՝ բազմապատկում `*`, բաժանաում `/`, գումարում `+`, հանում `-` և այլն. -Besides regular numbers, there are so-called "special numeric values" which also belong to this data type: `Infinity`, `-Infinity` and `NaN`. +Բացի սովորական թվերից կան նաև այսպես կոչված «հատուկ թվային արժեքներ», որոնք նույնպես պատկանում են այս տվյալների տիպին․ `Infinity`, `-Infinity` և `NaN`։ -- `Infinity` represents the mathematical [Infinity](https://en.wikipedia.org/wiki/Infinity) ∞. It is a special value that's greater than any number. +- `Infinity`-ն ներկայացնում է մաթեմատիկական [անվերջությունը](https://hy.wikipedia.org/wiki/%D4%B1%D5%B6%D5%BE%D5%A5%D6%80%D5%BB%D5%B8%D6%82%D5%A9%D5%B5%D5%B8%D6%82%D5%B6_(%D5%B4%D5%A1%D5%A9%D5%A5%D5%B4%D5%A1%D5%BF%D5%AB%D5%AF%D5%A1)) ∞։ Այն հատուկ արժեք է, որը մեծ է ցանկացած թվից։ - We can get it as a result of division by zero: + Մենք կարող ենք ստանալ այն որպես զրոյի վրա բաժանման արդյունք, ```js run alert( 1 / 0 ); // Infinity ``` - Or just reference it directly: + կամ վերցնելով այն ակնհայտ կերպով։ ```js run alert( Infinity ); // Infinity ``` -- `NaN` represents a computational error. It is a result of an incorrect or an undefined mathematical operation, for instance: +- `NaN`-ը նշանակում է հաշվողական սխալ։ Այն սխալ կամ անորոշ մաթեմատիկական գործողույան արդյունք է, օրինակ․ ```js run - alert( "not a number" / 2 ); // NaN, such division is erroneous + alert( "ոչ թվային արժեք" / 2 ); // NaN, բաժանումը հնարավոր չէ ``` - `NaN` is sticky. Any further operation on `NaN` returns `NaN`: + `NaN`-ը անփոփոխ արժեք է։ Ցանկացած գործողություն `NaN`-ի հետ վերադարձնում է `NaN`: ```js run - alert( "not a number" / 2 + 5 ); // NaN + alert( NaN + 1 ); // NaN + alert( 3 * NaN ); // NaN + alert( "ոչ թվային արժեք" / 2 - 1 ); // NaN ``` - So, if there's a `NaN` somewhere in a mathematical expression, it propagates to the whole result. + Եթե մաթեմատիկական արտահայտության որևէ անդամ `NaN` է, ապա ամբողջ արտահայտության արդյունքը կլինի `NaN`։ Միակ բացառությունը `NaN ** 0` = `1` է։ + +```smart header="Մաթեմատիկական գործողությունները անվտանգ են" +Մաթեմատիկական գործողությունները JavaScript-ում «անվտանգ» են։ Մենք կարող ենք կատարել ցանկացած գործողություն՝ բաժանել զրոյի, ոչ թվային տողերի հետ վարվել ինչպես թվերի հետ։ + +Սկրիպտը երբեք կանգ չի առնի՝ շպրտելով սխալ։ Վատագույն դեպքում, որպես արդյունք, մենք կստանանք `NaN`։ +``` + +Հատուկ թվային արժեքները պատկանում են «թիվ» (number) տիպին։ Իրականում դրանք թվեր չեն բառի ուղիղ իմաստով: + +Թվերի հետ աշխատանքին ավելի մանրամասն կծանոթանանք գլխում։ + +## BigInt [#bigint-type] + +JavaScript-ում «number» տիպը չի կարող ներկայացնել թվային արժեքներ, որոնք մեծ են (253-1)-ից (նույնն է, ինչ `9007199254740991`), կամ փոքր են -(253-1)-ից՝ բացասական թվերի դեպքում։ + +Իսկապես ճշգրիտ լինելու համար, «number» տիպը կարող է պահել ավելի մեծ ամբողջ թվեր (մինչև 1.7976931348623157 * 10308), բայց անվտանգ ամբողջ թվերի միջակայքից դուրս ±(2): 53-1) ճշգրտության սխալ կլինի, քանի որ ոչ բոլոր թվանշաններն են տեղավորվում ֆիքսված 64-բիթանոց պահեստում: Այնպես որ, «մոտավոր» արժեք կարող է պահվել: + +Օրինակ, այս երկու թվերը (անվտանգ տիրույթից անմիջապես վերև) նույնն են. + +```js +console.log(9007199254740991 + 1); // 9007199254740992 +console.log(9007199254740991 + 2); // 9007199254740992 +``` + +Այսպես ասած՝ (253-1)-ից մեծ բոլոր կենտ ամբողջ թվերն ընդհանրապես չեն կարող պահվել «number» տիպում։ + +Շատ նպատակների համար ±(253-1) միջակայքը բավական է, բայց երբեմն մեզ անհրաժեշտ է իսկապես մեծ ամբողջ թվերի ամբողջ միջակայքը, օրինակ. գաղտնագրության կամ միկրովայրկյանային ճշգրտությամբ ժամանակի համար: -```smart header="Mathematical operations are safe" -Doing maths is "safe" in JavaScript. We can do anything: divide by zero, treat non-numeric strings as numbers, etc. +`BigInt`-ը վերջերս է ավելացվել լեզվում՝ կամայական մեծության թվեր նեկայացնելու նպատակով։ -The script will never stop with a fatal error ("die"). At worst, we'll get `NaN` as the result. +`BigInt` արժեք ստեղծվում է թվին վերջից կցելով `n`․ + +```js +// «n»-ը վերջում նշանակում է, որ արժեքը BigInt տիպի է +const bigInt = 1234567890123456789012345678901234567890n; ``` -Special numeric values formally belong to the "number" type. Of course they are not numbers in the common sense of this word. +Քանի որ `BigInt` թվերը հազվադեպ են օգտագործվում, դրանց այստեղ չենք անդրադառնա, այլ կդիտարկենք առանձին՝ գլխում։ Կարդացե՛ք այն, եթե նման մեծ թվերի օգտագոծման կարիք կունենաք։ -We'll see more about working with numbers in the chapter . -## A string +```smart header="Համատեղելիության խնդիրներ" +Այս պահին `BigInt`-ը համատեղելի է Firefox/Chrome/Edge/Safari զննիչների հետ, բայց ոչ IE-ի։ +``` + +Կարող եք ստուգել [*MDN* BigInt compatibility table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility) հոդվածը, հասկանալու համար, թե զննիչների որ տարբերակների հետ է այն համատեղելի։ -A string in JavaScript must be surrounded by quotes. +## Տող (String) + +JavaScript-ում տողը պետք է շրջապատված լինի չակերտներով։ ```js -let str = "Hello"; -let str2 = 'Single quotes are ok too'; -let phrase = `can embed ${str}`; +let str = "Բարև"; +let str2 = 'Եզակի չակերտները նույպես թույլատրելի են'; +let phrase = `թեք չակերտները կարող են ներառել փոփոխականներ ${str}`; ``` -In JavaScript, there are 3 types of quotes. +JavaScript-ում գոյություն ոնեն 3 տեսակի չակերտներ. -1. Double quotes: `"Hello"`. -2. Single quotes: `'Hello'`. -3. Backticks: `Hello`. +1. Կրկնակի չակերտներ․ `"Բարև"`։ +2. Եզակի չակերտներ․ `'Բարև'`։ +3. Թեք չակերտներ․ `Բարև`։ -Double and single quotes are "simple" quotes. There's no difference between them in JavaScript. +Կրկնակի և եզակի չակերտները «պարզ» չակերտներ են։ JavaScript-ում դրանց միջև տարբերություններ չկան։ -Backticks are "extended functionality" quotes. They allow us to embed variables and expressions into a string by wrapping them in `${…}`, for example: +Թեք չակերտները ունեն «ընդլայնված ֆունկցիոնալություն»։ Դրանք թույլ են տալիս ներդնել փոփոխականներ և արտահայտություններ տողի մեջ՝ դրանք `${…}`-ի մեջ պարփակելու միջոցով, օրինակ․ ```js run -let name = "John"; +let name = "Դավիթ"; -// embed a variable -alert( `Hello, *!*${name}*/!*!` ); // Hello, John! +// ներդնենք փոփոխականը +alert( `Բարև, *!*${name}*/!*!` ); // Բարև, Դավիթ! -// embed an expression -alert( `the result is *!*${1 + 2}*/!*` ); // the result is 3 +// ներդնենք արտահայտություն +alert( `արդյունքը կլինի *!*${1 + 2}*/!*` ); // արդյունքը կլինի 3 ``` -The expression inside `${…}` is evaluated and the result becomes a part of the string. We can put anything in there: a variable like `name` or an arithmetical expression like `1 + 2` or something more complex. +`${…}`-ի մեջ ներդրված արտահայտությունը գնահատվում է, և արդյունքը դառնում է տողի մի մասը։ Մենք կարող ենք տեղադրել այնտեղ ամեն ինչ՝ փոփոխական (`name`) կամ թվաբանական արտահայտություն՝ `1 + 2`-ի նման կամ ավելի բարդ։ + +Ուշադրություն դարձրեք այն փաստին, որ սա հնարավոր է միայն թեք չակերտների դեպքում։ Մյուս չակերտները չունեն այսպիսի ներդրման ֆունկցիոնալություն։ -Please note that this can only be done in backticks. Other quotes don't have this embedding functionality! ```js run -alert( "the result is ${1 + 2}" ); // the result is ${1 + 2} (double quotes do nothing) +alert( "արդյունքը կլինի ${1 + 2}" ); // արդյունքը կլինի ${1 + 2} (կրկնակի չակերտները ոչինչ չեն անում) ``` -We'll cover strings more thoroughly in the chapter . +Մենք կանրադառնանք տողերին ավելի մանրամասն գլխում։ -```smart header="There is no *character* type." -In some languages, there is a special "character" type for a single character. For example, in the C language and in Java it is `char`. +```smart header="Գոյություն չունի *character* տիպ։" +Որոշ լեզուներում գոյություն ունի հատուկ "character" տիպ եզակի սիմվոլների համար։ Օրինակ՝ C և Java լեզուներում այն կոչվում է «char»։ -In JavaScript, there is no such type. There's only one type: `string`. A string may consist of only one character or many of them. +JavaScript-ում չկա նման տիպ։ Կա միայն մեկ տիպ՝ `string`։ string-ը կարող է լինել դատարկ, ունենալ մեկ կամ բազմաթիվ նիշեր։ ``` -## A boolean (logical type) +## Boolean (տրամաբանական տիպ) -The boolean type has only two values: `true` and `false`. +Boolean տիպը ունի երկու արժեք․ `true` և `false`։ -This type is commonly used to store yes/no values: `true` means "yes, correct", and `false` means "no, incorrect". +Այս տիպը հիմնականում օգտագործվում է այո/ոչ արժեքներ պահելու համար․ `true` նշանակում է «այո, ճիշտ է», իսկ `false` նշանակում է «ոչ, սխալ է»։ -For instance: +Օրինակ՝ ```js -let nameFieldChecked = true; // yes, name field is checked -let ageFieldChecked = false; // no, age field is not checked +let nameFieldChecked = true; // այո, անվանական դաշտը ստուգված է +let ageFieldChecked = false; // ոչ, տարիքային դաշտը ստուգված չէ ``` -Boolean values also come as a result of comparisons: +Boolean արժեքները ստացվում են նաև որպես համեմատությունների արդյունք․ ```js run let isGreater = 4 > 1; -alert( isGreater ); // true (the comparison result is "yes") +alert( isGreater ); // true (համեմատության արդյունքն է «այո») ``` -We'll cover booleans more deeply in the chapter . +Մենք կանրադառնանք boolean տիպին ավելի մանրամասն գլխում։ -## The "null" value +## «null» արժեքը -The special `null` value does not belong to any of the types described above. +`null` հատուկ արժեքը չի պատկանում վերոնշյալ տիպերից ոչ մեկին։ -It forms a separate type of its own which contains only the `null` value: +Այն ձևավորում է առանձին տիպ, որը պարունակում է `null` արժեքը: ```js let age = null; ``` -In JavaScript, `null` is not a "reference to a non-existing object" or a "null pointer" like in some other languages. +JavaScript-ում `null`-ը «գոյություն չունեցող օբյեկտի հղում» կամ «զրոյական ցուցիչ» չէ, ինչպես որոշ այլ լեզուներում։ -It's just a special value which represents "nothing", "empty" or "value unknown". +Այն պարզապես հատուկ արժեք է, որը նշանակում է «ոչինչ», «դատարկություն» կամ «անհայտ արժեք»։ -The code above states that `age` is unknown or empty for some reason. +Վերոնշյալ կոդը պնդում է, որ `age` փոփոխականի արժեքը անհայտ է։ -## The "undefined" value +## «undefined» արժեքը -The special value `undefined` also stands apart. It makes a type of its own, just like `null`. +`undefined` հատուկ արժեքը նույնպես ուրույն է։ Այն ձևավորում է իր սեփական տիպը, ճիշտ ինչպես `null`-ը։ -The meaning of `undefined` is "value is not assigned". +`undefined` նշանակում է, որ «արժեք չի վերագրվել»։ -If a variable is declared, but not assigned, then its value is `undefined`: +Եթե փոփոխականը հայտարարված է, բայց նրան արժեք չի վերագրվել, ապա նրա արժեքը կլինի `undefined`․ ```js run -let x; +let age; -alert(x); // shows "undefined" +alert(age); // ցույց կտա «undefined» ``` -Technically, it is possible to assign `undefined` to any variable: +Տեխնիկապես փոփոխականին հնարավոր է ակնհայտ կերպով վերագրել `undefined` արժեքը․ ```js run -let x = 123; +let age = 100; -x = undefined; +// արժեքը դարձնել «undefined» +age = undefined; -alert(x); // "undefined" +alert(age); // «undefined» ``` -...But we don't recommend doing that. Normally, we use `null` to assign an "empty" or "unknown" value to a variable, and we use `undefined` for checks like seeing if a variable has been assigned. +...Բայց խորհուրդ չի տրվում այդպես անել։ Սովորաբար `null`-ը օգտագործվում է փոփոխականին «դատարկ» կամ «անհայտ» արժեք վերագրելու համար, մինչդեռ `undefined`-ը իրենից ենթադրում է նախնական լռելյայն (default) արժեք՝ չվերագրված փոփոխականների համար։ -## Objects and Symbols +## Օբյեկտներ և սիմվոլներ -The `object` type is special. +`object` տիպը յուրահատուկ է։ -All other types are called "primitive" because their values can contain only a single thing (be it a string or a number or whatever). In contrast, objects are used to store collections of data and more complex entities. We'll deal with them later in the chapter after we learn more about primitives. +Մնացած բոլոր տիպերը կոչվում են «պրիմիտիվ» տիպեր, քանի որ նրանց արժեքները պարզ արժեքներ են (լինի այն տող, թիվ կամ այլ արժեք)։ Օբյեկտները, իրենց հերթին, օգտագործվում են տվյալների համախումբ և ավելի բարդ կառուցվածքներ պահեստավորելու նպատակով։ -The `symbol` type is used to create unique identifiers for objects. We mention it here for completeness, but we'll study it after objects. +Օբյեկտները լեզվում կարևոր տեղ են զբաղեցնում և հատուկ ուշադրության են արժանի։ Մենք կզբաղվենք դրանցով ավելի ուշ՝ գլխում՝ պրիմիտիվ տիպերը ավելի խորը ուսումնասիրելուց հետո։ -## The typeof operator [#type-typeof] +`symbol` տիպը օգտագործվում է օբյեկտներում չկրկնվող նույնացուցիչներ ստեղծելու համար։ Մենք այստեղ նշում ենք դրա գոյության փաստը՝ տիպերի նկարագիրը լիարժեք դարձնելու համար, բայց թողնում ենք դրա ուսումնասիրությունը հետագայի համար, երբ արդեն ուսումնասիրած կլինենք օբյեկտները։ -The `typeof` operator returns the type of the argument. It's useful when we want to process values of different types differently or just want to do a quick check. +## typeof օպերատորը [#type-typeof] -It supports two forms of syntax: +`typeof` օպերատորը վերադարձնում է իր արգումենտի տիպը։ Այն օգտակար է, երբ մենք ուզում ենք մշակել տարբեր տիպերի արժեքները տարբեր կերպով, կամ պարզապես արագ ստուգում անել -1. As an operator: `typeof x`. -2. As a function: `typeof(x)`. - -In other words, it works with parentheses or without them. The result is the same. - -The call to `typeof x` returns a string with the type name: +`typeof x`-ի կանչը վերադարձնում է տող, որը պարունակում է տիպի անվանումը․ ```js typeof undefined // "undefined" typeof 0 // "number" +typeof 10n // "bigint" + typeof true // "boolean" typeof "foo" // "string" @@ -217,29 +255,42 @@ typeof alert // "function" (3) */!* ``` -The last three lines may need additional explanation: +Վերջին երեք տողերը ունեն հավելյալ պարզաբանման կարիք․ + +1. `Math`-ը ներկառուցված օբյեկտ է, որը տրամադրում է մաթեմատիկական տարբեր գործողություններ և հաստատուններ։ Մենք կուսումնասիրենք այն գլխում։ Այստեղ այն պարզապես ծառայում է որպես օբյեկտի օրինակ։ +2. `typeof null`-ի արդյունքը `"object"` է։ Այն `typeof` օպերատորի վարքագծի պաշտոնապես ընդունված սխալ է, որը եկել է JavaScript-ի ստեղծման վաղ ժամանակներից և մնացել է լեզվում համատեղելիության նպատակով։ Անշուշտ, `null`-ը օբյեկտ չէ։ Այն հատուկ արժեք է, որը ունի իր սեփական տիպը։ +3. `typeof alert`-ի արդյունքը `"function"` է, քանի որ `alert`-ը ֆունկցիա է։ Մենք կուսումնասիրենք ֆունկցիաները հաջորդ գլուխներում, որտեղ կիմանանք, որ JavaScript-ը չունի առանձին function տիպ։ Ֆունկցիաները պատկանում են object տիպին։ Բայց `typeof`-ը մշակում է դրանք հատուկ ձևով՝ վերադարձնելով `"function"` արժեքը։ Դա նույնպես գալիս է JavaScript-ի պատմության վաղ ժամանակներից։ Տեխնիկապես վարքագիծը ճիշտ չէ, բայց պրակտիկայում կարող է հարմար լինել։ -1. `Math` is a built-in object that provides mathematical operations. We will learn it in the chapter . Here, it serves just as an example of an object. -2. The result of `typeof null` is `"object"`. That's wrong. It is an officially recognized error in `typeof`, kept for compatibility. Of course, `null` is not an object. It is a special value with a separate type of its own. So, again, this is an error in the language. -3. The result of `typeof alert` is `"function"`, because `alert` is a function. We'll study functions in the next chapters where we'll also see that there's no special "function" type in JavaScript. Functions belong to the object type. But `typeof` treats them differently, returning `"function"`. That's not quite correct, but very convenient in practice. +```smart header="The `typeof(x)` syntax" +You may also come across another syntax: `typeof(x)`. It's the same as `typeof x`. +To put it clear: `typeof` is an operator, not a function. The parentheses here aren't a part of `typeof`. It's the kind of parentheses used for mathematical grouping. + +Usually, such parentheses contain a mathematical expression, such as `(2 + 2)`, but here they contain only one argument `(x)`. Syntactically, they allow to avoid a space between the `typeof` operator and its argument, and some people like it. + +Some people prefer `typeof(x)`, although the `typeof x` syntax is much more common. +``` -## Summary +## Ամփոփում -There are 7 basic data types in JavaScript. +JavaScript-ում գոյություն ունեն տվյալների 8 տիպեր։ -- `number` for numbers of any kind: integer or floating-point. -- `string` for strings. A string may have one or more characters, there's no separate single-character type. -- `boolean` for `true`/`false`. -- `null` for unknown values -- a standalone type that has a single value `null`. -- `undefined` for unassigned values -- a standalone type that has a single value `undefined`. -- `object` for more complex data structures. -- `symbol` for unique identifiers. +- Տվյալների յոթ պրիմիտիվ տիպեր՝ + - `number` բոլոր տեսակի թվերի համար․ ամբողջ և տասնորդական, ամբողջ թվերը սահմանափակված են ±(253-1) միջակայքում։ + - `bigint` կամայական մեծության ամբողջ թվերի համար։ + - `string` տողերի համար։ Տողը կարող է պարունակել զրո կամ ավել նիշեր, չկա նիշի համար նախատեսված առանձին տիպ։ + - `boolean` `true`/`false` արժեքների համար։ + - `null` անհայտ արժեքների համար․ առանձին տիպ է, որը ունի մեկ արժեք՝ `null`։ + - `undefined` չվերագրված արժեքների համար․ առանձին տիպ է, որը ունի մեկ արժեք՝ `undefined`։ + - `object` ավելի բարդ տվյալների կառուցվածքների համար։ + - `symbol` չկրկնվող նույնացուցիչների համար։ +- Եվ տվյալների ոչ պրիմիտիվ մեկ տիպ՝ + - `object` տվյալների բարդ կառուցվածքների համար։ -The `typeof` operator allows us to see which type is stored in a variable. +`typeof`-ը թույլ է տալիս ստուգել, թե ինչ տիպի արժեք է պահված փոփոխականում։ -- Two forms: `typeof x` or `typeof(x)`. -- Returns a string with the name of the type, like `"string"`. -- For `null` returns `"object"` -- this is an error in the language, it's not actually an object. +- Ունի երկու ձև․ `typeof x` կամ `typeof(x)`։ +- Վերադարձնում է տող, որը պարունակում է տիպի անվանումը, օրինակ՝ `"string"`։ +- `null`-ի դեպքում վերադարձնում է `"object"`․ սա լեզվի սխալ է, այն իրականում օբյեկտ չէ։ -In the next chapters, we'll concentrate on primitive values and once we're familiar with them, we'll move on to objects. +Հաջորդ գլուխներում մենք կկենտրոնանանք պրիմիտիվ արժեքների վրա և երբ ծանոթանանք դրանց հետ, կանցնենք օբյեկտների ուսումնասիրությանը։ diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md new file mode 100644 index 000000000..8f9b064c3 --- /dev/null +++ b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/solution.md @@ -0,0 +1,24 @@ +JavaScript կոդը․ + +```js demo run +let name = prompt("Ի՞նչ է Ձեր անունը։", ""); +alert(name); +``` + +Ամբողջական էջը․ + +```html + + + + + + + + +``` diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md new file mode 100644 index 000000000..e398ce6d0 --- /dev/null +++ b/1-js/02-first-steps/06-alert-prompt-confirm/1-simple-page/task.md @@ -0,0 +1,9 @@ +importance: 4 + +--- + +# Պարզ էջ + +Կառուցե՛ք էջ, որը օգտատիրոջից կհարցնի իր անունը և կպատկերի այն։ + +[demo] diff --git a/1-js/02-first-steps/06-alert-prompt-confirm/article.md b/1-js/02-first-steps/06-alert-prompt-confirm/article.md new file mode 100644 index 000000000..1ab9f8a1a --- /dev/null +++ b/1-js/02-first-steps/06-alert-prompt-confirm/article.md @@ -0,0 +1,105 @@ +# Փոխազդեցություն: alert, prompt, confirm + +Քանի որ մենք օգտագործելու ենք զննիչները որպես ներկայացման տիրույթ, եկեք ուսումնասիրենք մի քանի ֆունկցիաներ՝ նախատեսված օգտատիրոջ հետ փոխազդեցության համար․ `alert`, `prompt` և `confirm`։ + +## alert + +Այս ֆունկցիայի հետ մենք արդեն առնչվել ենք։ Այն ցույց է տալիս հաղորդագրություն և սպասում է օգտատիրոջ՝ «OK» կոճակը սեղմելուն։ + +Օրինակ․ + +```js run +alert("Բարև"); +``` + +Հաղորդագրությունը պարունակող փոքր պատուհանը կոչվում է *մոդալ պատուհան* (modal window)։ «Մոդալը» նշանակում է, որ օգտատերը չի կարող փոխազդել էջի մնացած մասի հետ, սեղմել այլ կոճակներ և այլն, քանի դեռ նա փոխազդում է պատուհանի հետ։ Այս պարագայում՝ քանի դեռ չի սեղմել «OK» կոճակը։ + +## prompt + +`prompt` ֆունկցիան ընդունում է երկու արգումենտ․ + +```js no-beautify +result = prompt(title, [default]); +``` + +Այն կպատկերի տեքստային հաղորդագրություն պարունակող մոդալ պատուհան, տեքստի մուտքագրման դաշտ և OK/Cancel կոճակներ։ + +`title` +։ Տեքստը, որը կպատկերվի պատուհանում։ + +`default` +: Ոչ պարտադիր երկրորդ արգումենտ՝ մուտքագրման դաշտի նախնական արժեքի համար։ + +```smart header="Քառակուսի փակագծերը `[...]` շարահյուսությունում" +Վերոնշյալ շարահյուսությունում `default`-ը շրջապատող քառակուսի փակագծերը նշանակում են, որ պարամետրի առկայությունը պարտադիր չէ։ +``` + +Օգտատերը կարող է մուտքագրման դաշտում լրացնել ինչ-որ բան և սեղմել OK։ Մուտքագրված տեքստը կվերագրվի `result` փոփոխականին։ Օգտատերը նաև կարող է չեղարկել մուտքագրումը՝ սեղմելով Cancel կամ ստեղնաշարի `key:Esc` կոճակները։ Այդ դեպքում `result`-ին կվերագրվի `null` արժեքը։ + +`prompt`-ի կանչը վերադարձնում է մուտքագրման դաշտում նշված տեքստը կամ `null`, եթե մուտքագրումը չեղարկվել է։ + +Օրինակ․ + +```js run +let age = prompt('Քանի՞ տարեկան ես։', 100); + +alert(`Դու ${age} տարեկան ես։`); // Դու 100 տարեկան ես։ +``` + +````warn header="IE-ի համար միշտ սահմանեք լռելյայն (`default`) արժեք" +Երկրորդ պարամետրը պարտադիր չէ, բայց, եթե չսահմանենք այն, Internet Explorer-ը կտեղադրի `"undefined"` տեքստը prompt-ի մուտքագրման դաշտում։ + +Աշխատեցրեք կոդը Internet Explorer-ում՝ արդյունքը տեսնելու համար․ + +```js run +let test = prompt("Թեստ"); +``` + +Այսպիսով, որպեսզի prompt-ը ունենա նորմալ տեսք IE-ում, խորհուրդ է տրվում միշտ սահմանել երկրորդ արգումենտը․ + +```js run +let test = prompt("Թեստ", ''); // <-- IE-ի համար +``` +```` + +## confirm + +Շարահյուսությունը (syntax)․ + +```js +result = confirm(question); +``` + +`confirm` ֆունկցիան պատկերում է մոդալ պատուհան `question` հարցման տեքստով և երկու կոճակով՝ OK և Cancel։ + +Արդյունքը կլինի `true`, եթե սեղմվի OK կոճակը, և `false`՝ հակառակ դեպքում։ + +Օրինակ․ + +```js run +let isBoss = confirm("Դու՞ք եք այստեղ գլխավորը։"); + +alert( isBoss ); // true, եթե սեղմվի OK կոճակը +``` + +## Ամփոփում + +Մենք ուսումնասիրեցինք 3 զննիչային (browser-specific) ֆունկցիաներ, որոնք փոխազդում են օգտատիրոջ հետ․ + +`alert` +: պատկերում է հաղորդագրություն։ + +`prompt` +: պատկերում է հաղորդագրություն, սպասելով օգտատիրոջ կողմից տեքստի մուտքագրմանը։ Այն վերադարձնում է տեքստը, կամ, եթե սեղմվել է Cancel կամ `key:Esc` կոճակը, ապա վերադարձնում է `null`։ + +`confirm` +: պատկերում է հաղորդագրություն և սպասում է օգտատիրոջ «OK» կամ «Cancel» սեղմելուն։ OK-ի դեպքում վերադարձնում է `true`, իսկ Cancel/`key:Esc`-ի դեպքում՝ `false`։ + +Այս բոլոր մեթոդները մոդալային են․ դրանք կանգնեցնում են սկրիպտի իրականցումը և թույլ չեն տալիս օգտատիրոջը փոխազդել էջի մնացած մասի հետ, քանի դեռ պատուհանը չի փակվել։ + +Վերոնշյալ մեթոդները ունեն երկու սահմանափակում․ + +1. Մոդալ պատուհանի դիրքը որոշվում է զննիչի կողմից։ Սովորաբար այն լինում է կենտրոնում։ +2. Պատուհանի տեսքը նույնպես որոշվում է զննիչի կողմից։ Մենք չենք կարող այն փոփոխել։ + +Այսպիսին է պարզության գինը։ Կան շատ այլ եղանակներ՝ ավելի գեղեցիկ պատուհաններ պատկերելու և օգտատիրոջ հետ ավելի լայն փոխազդեցություն ապահովելու համար, բայց եթե «ճոխությունը» կարևոր չէ, այս մեթոդները հիանալի լուծում են։ diff --git a/1-js/02-first-steps/06-type-conversions/article.md b/1-js/02-first-steps/06-type-conversions/article.md deleted file mode 100644 index 20d093ea4..000000000 --- a/1-js/02-first-steps/06-type-conversions/article.md +++ /dev/null @@ -1,159 +0,0 @@ -# Type Conversions - -Most of the time, operators and functions automatically convert the values given to them to the right type. - -For example, `alert` automatically converts any value to a string to show it. Mathematical operations convert values to numbers. - -There are also cases when we need to explicitly convert a value to the expected type. - -```smart header="Not talking about objects yet" -In this chapter, we won't cover objects. Instead, we'll study primitives first. Later, after we learn about objects, we'll see how object conversion works in the chapter . -``` - -## String Conversion - -String conversion happens when we need the string form of a value. - -For example, `alert(value)` does it to show the value. - -We can also call the `String(value)` function to convert a value to a string: - -```js run -let value = true; -alert(typeof value); // boolean - -*!* -value = String(value); // now value is a string "true" -alert(typeof value); // string -*/!* -``` - -String conversion is mostly obvious. A `false` becomes `"false"`, `null` becomes `"null"`, etc. - -## Numeric Conversion - -Numeric conversion happens in mathematical functions and expressions automatically. - -For example, when division `/` is applied to non-numbers: - -```js run -alert( "6" / "2" ); // 3, strings are converted to numbers -``` - -We can use the `Number(value)` function to explicitly convert a `value` to a number: - -```js run -let str = "123"; -alert(typeof str); // string - -let num = Number(str); // becomes a number 123 - -alert(typeof num); // number -``` - -Explicit conversion is usually required when we read a value from a string-based source like a text form but expect a number to be entered. - -If the string is not a valid number, the result of such a conversion is `NaN`. For instance: - -```js run -let age = Number("an arbitrary string instead of a number"); - -alert(age); // NaN, conversion failed -``` - -Numeric conversion rules: - -| Value | Becomes... | -|-------|-------------| -|`undefined`|`NaN`| -|`null`|`0`| -|true and false | `1` and `0` | -| `string` | Whitespaces from the start and end are removed. If the remaining string is empty, the result is `0`. Otherwise, the number is "read" from the string. An error gives `NaN`. | - -Examples: - -```js run -alert( Number(" 123 ") ); // 123 -alert( Number("123z") ); // NaN (error reading a number at "z") -alert( Number(true) ); // 1 -alert( Number(false) ); // 0 -``` - -Please note that `null` and `undefined` behave differently here: `null` becomes zero while `undefined` becomes `NaN`. - -````smart header="Addition '+' concatenates strings" -Almost all mathematical operations convert values to numbers. A notable exception is addition `+`. If one of the added values is a string, the other one is also converted to a string. - -Then, it concatenates (joins) them: - -```js run -alert( 1 + '2' ); // '12' (string to the right) -alert( '1' + 2 ); // '12' (string to the left) -``` - -This only happens when at least one of the arguments is a string. Otherwise, values are converted to numbers. -```` - -## Boolean Conversion - -Boolean conversion is the simplest one. - -It happens in logical operations (later we'll meet condition tests and other similar things) but can also be performed explicitly with a call to `Boolean(value)`. - -The conversion rule: - -- Values that are intuitively "empty", like `0`, an empty string, `null`, `undefined`, and `NaN`, become `false`. -- Other values become `true`. - -For instance: - -```js run -alert( Boolean(1) ); // true -alert( Boolean(0) ); // false - -alert( Boolean("hello") ); // true -alert( Boolean("") ); // false -``` - -````warn header="Please note: the string with zero `\"0\"` is `true`" -Some languages (namely PHP) treat `"0"` as `false`. But in JavaScript, a non-empty string is always `true`. - -```js run -alert( Boolean("0") ); // true -alert( Boolean(" ") ); // spaces, also true (any non-empty string is true) -``` -```` - -## Summary - -The three most widely used type conversions are to string, to number, and to boolean. - -**`String Conversion`** -- Occurs when we output something. Can be performed with `String(value)`. The conversion to string is usually obvious for primitive values. - -**`Numeric Conversion`** -- Occurs in math operations. Can be performed with `Number(value)`. - -The conversion follows the rules: - -| Value | Becomes... | -|-------|-------------| -|`undefined`|`NaN`| -|`null`|`0`| -|true / false | `1 / 0` | -| `string` | The string is read "as is", whitespaces from both sides are ignored. An empty string becomes `0`. An error gives `NaN`. | - -**`Boolean Conversion`** -- Occurs in logical operations. Can be performed with `Boolean(value)`. - -Follows the rules: - -| Value | Becomes... | -|-------|-------------| -|`0`, `null`, `undefined`, `NaN`, `""` |`false`| -|any other value| `true` | - - -Most of these rules are easy to understand and memorize. The notable exceptions where people usually make mistakes are: - -- `undefined` is `NaN` as a number, not `0`. -- `"0"` and space-only strings like `" "` are true as a boolean. - -Objects aren't covered here. We'll return to them later in the chapter that is devoted exclusively to objects after we learn more basic things about JavaScript. diff --git a/1-js/02-first-steps/07-operators/article.md b/1-js/02-first-steps/07-operators/article.md deleted file mode 100644 index a1373eade..000000000 --- a/1-js/02-first-steps/07-operators/article.md +++ /dev/null @@ -1,444 +0,0 @@ -# Operators - -We know many operators from school. They are things like addition `+`, multiplication `*`, subtraction `-`, and so on. - -In this chapter, we'll concentrate on aspects of operators that are not covered by school arithmetic. - -## Terms: "unary", "binary", "operand" - -Before we move on, let's grasp some common terminology. - -- *An operand* -- is what operators are applied to. For instance, in the multiplication of `5 * 2` there are two operands: the left operand is `5` and the right operand is `2`. Sometimes, people call these "arguments" instead of "operands". -- An operator is *unary* if it has a single operand. For example, the unary negation `-` reverses the sign of a number: - - ```js run - let x = 1; - - *!* - x = -x; - */!* - alert( x ); // -1, unary negation was applied - ``` -- An operator is *binary* if it has two operands. The same minus exists in binary form as well: - - ```js run no-beautify - let x = 1, y = 3; - alert( y - x ); // 2, binary minus subtracts values - ``` - - Formally, in the examples above we have two different operators that share the same symbol: the negation operator, a unary operator that reverses the sign, and the subtraction operator, a binary operator that subtracts one number from another. - -## String concatenation, binary + - -Now, let's see special features of JavaScript operators that are beyond school arithmetics. - -Usually, the plus operator `+` sums numbers. - -But, if the binary `+` is applied to strings, it merges (concatenates) them: - -```js -let s = "my" + "string"; -alert(s); // mystring -``` - -Note that if one of the operands is a string, the other one is converted to a string too. - -For example: - -```js run -alert( '1' + 2 ); // "12" -alert( 2 + '1' ); // "21" -``` - -See, it doesn't matter whether the first operand is a string or the second one. The rule is simple: if either operand is a string, the other one is converted into a string as well. - -However, note that operations run from left to right. If there are two numbers followed by a string, the numbers will be added before being converted to a string: - - -```js run -alert(2 + 2 + '1' ); // "41" and not "221" -``` - -String concatenation and conversion is a special feature of the binary plus `+`. Other arithmetic operators work only with numbers and always convert their operands to numbers. - -For instance, subtraction and division: - -```js run -alert( 2 - '1' ); // 1 -alert( '6' / '2' ); // 3 -``` - -## Numeric conversion, unary + - -The plus `+` exists in two forms: the binary form that we used above and the unary form. - -The unary plus or, in other words, the plus operator `+` applied to a single value, doesn't do anything to numbers. But if the operand is not a number, the unary plus converts it into a number. - -For example: - -```js run -// No effect on numbers -let x = 1; -alert( +x ); // 1 - -let y = -2; -alert( +y ); // -2 - -*!* -// Converts non-numbers -alert( +true ); // 1 -alert( +"" ); // 0 -*/!* -``` - -It actually does the same thing as `Number(...)`, but is shorter. - -The need to convert strings to numbers arises very often. For example, if we are getting values from HTML form fields, they are usually strings. What if we want to sum them? - -The binary plus would add them as strings: - -```js run -let apples = "2"; -let oranges = "3"; - -alert( apples + oranges ); // "23", the binary plus concatenates strings -``` - -If we want to treat them as numbers, we need to convert and then sum them: - -```js run -let apples = "2"; -let oranges = "3"; - -*!* -// both values converted to numbers before the binary plus -alert( +apples + +oranges ); // 5 -*/!* - -// the longer variant -// alert( Number(apples) + Number(oranges) ); // 5 -``` - -From a mathematician's standpoint, the abundance of pluses may seem strange. But from a programmer's standpoint, there's nothing special: unary pluses are applied first, they convert strings to numbers, and then the binary plus sums them up. - -Why are unary pluses applied to values before the binary ones? As we're going to see, that's because of their *higher precedence*. - -## Operator precedence - -If an expression has more than one operator, the execution order is defined by their *precedence*, or, in other words, the default priority order of operators. - -From school, we all know that the multiplication in the expression `1 + 2 * 2` should be calculated before the addition. That's exactly the precedence thing. The multiplication is said to have *a higher precedence* than the addition. - -Parentheses override any precedence, so if we're not satisfied with the default order, we can use them to change it. For example, write `(1 + 2) * 2`. - -There are many operators in JavaScript. Every operator has a corresponding precedence number. The one with the larger number executes first. If the precedence is the same, the execution order is from left to right. - -Here's an extract from the [precedence table](https://developer.mozilla.org/en/JavaScript/Reference/operators/operator_precedence) (you don't need to remember this, but note that unary operators are higher than corresponding binary ones): - -| Precedence | Name | Sign | -|------------|------|------| -| ... | ... | ... | -| 16 | unary plus | `+` | -| 16 | unary negation | `-` | -| 14 | multiplication | `*` | -| 14 | division | `/` | -| 13 | addition | `+` | -| 13 | subtraction | `-` | -| ... | ... | ... | -| 3 | assignment | `=` | -| ... | ... | ... | - -As we can see, the "unary plus" has a priority of `16` which is higher than the `13` of "addition" (binary plus). That's why, in the expression `"+apples + +oranges"`, unary pluses work before the addition. - -## Assignment - -Let's note that an assignment `=` is also an operator. It is listed in the precedence table with the very low priority of `3`. - -That's why, when we assign a variable, like `x = 2 * 2 + 1`, the calculations are done first and then the `=` is evaluated, storing the result in `x`. - -```js -let x = 2 * 2 + 1; - -alert( x ); // 5 -``` - -It is possible to chain assignments: - -```js run -let a, b, c; - -*!* -a = b = c = 2 + 2; -*/!* - -alert( a ); // 4 -alert( b ); // 4 -alert( c ); // 4 -``` - -Chained assignments evaluate from right to left. First, the rightmost expression `2 + 2` is evaluated and then assigned to the variables on the left: `c`, `b` and `a`. At the end, all the variables share a single value. - -````smart header="The assignment operator `\"=\"` returns a value" -An operator always returns a value. That's obvious for most of them like addition `+` or multiplication `*`. But the assignment operator follows this rule too. - -The call `x = value` writes the `value` into `x` *and then returns it*. - -Here's a demo that uses an assignment as part of a more complex expression: - -```js run -let a = 1; -let b = 2; - -*!* -let c = 3 - (a = b + 1); -*/!* - -alert( a ); // 3 -alert( c ); // 0 -``` - -In the example above, the result of expression `(a = b + 1)` is the value which was assigned to `a` (that is `3`). It is then used for further evaluations. - -Funny code, isn't it? We should understand how it works, because sometimes we see it in JavaScript libraries, but shouldn't write anything like that ourselves. Such tricks definitely don't make code clearer or readable. -```` - -## Remainder % - -The remainder operator `%`, despite its appearance, is not related to percents. - -The result of `a % b` is the remainder of the integer division of `a` by `b`. - -For instance: - -```js run -alert( 5 % 2 ); // 1 is a remainder of 5 divided by 2 -alert( 8 % 3 ); // 2 is a remainder of 8 divided by 3 -alert( 6 % 3 ); // 0 is a remainder of 6 divided by 3 -``` - -## Exponentiation ** - -The exponentiation operator `**` is a recent addition to the language. - -For a natural number `b`, the result of `a ** b` is `a` multiplied by itself `b` times. - -For instance: - -```js run -alert( 2 ** 2 ); // 4 (2 * 2) -alert( 2 ** 3 ); // 8 (2 * 2 * 2) -alert( 2 ** 4 ); // 16 (2 * 2 * 2 * 2) -``` - -The operator works for non-integer numbers as well. - -For instance: - -```js run -alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root, that's maths) -alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) -``` - -## Increment/decrement - - - -Increasing or decreasing a number by one is among the most common numerical operations. - -So, there are special operators for it: - -- **Increment** `++` increases a variable by 1: - - ```js run no-beautify - let counter = 2; - counter++; // works the same as counter = counter + 1, but is shorter - alert( counter ); // 3 - ``` -- **Decrement** `--` decreases a variable by 1: - - ```js run no-beautify - let counter = 2; - counter--; // works the same as counter = counter - 1, but is shorter - alert( counter ); // 1 - ``` - -```warn -Increment/decrement can only be applied to variables. Trying to use it on a value like `5++` will give an error. -``` - -The operators `++` and `--` can be placed either before or after a variable. - -- When the operator goes after the variable, it is in "postfix form": `counter++`. -- The "prefix form" is when the operator goes before the variable: `++counter`. - -Both of these statements do the same thing: increase `counter` by `1`. - -Is there any difference? Yes, but we can only see it if we use the returned value of `++/--`. - -Let's clarify. As we know, all operators return a value. Increment/decrement is no exception. The prefix form returns the new value while the postfix form returns the old value (prior to increment/decrement). - -To see the difference, here's an example: - -```js run -let counter = 1; -let a = ++counter; // (*) - -alert(a); // *!*2*/!* -``` - -In the line `(*)`, the *prefix* form `++counter` increments `counter` and returns the new value, `2`. So, the `alert` shows `2`. - -Now, let's use the postfix form: - -```js run -let counter = 1; -let a = counter++; // (*) changed ++counter to counter++ - -alert(a); // *!*1*/!* -``` - -In the line `(*)`, the *postfix* form `counter++` also increments `counter` but returns the *old* value (prior to increment). So, the `alert` shows `1`. - -To summarize: - -- If the result of increment/decrement is not used, there is no difference in which form to use: - - ```js run - let counter = 0; - counter++; - ++counter; - alert( counter ); // 2, the lines above did the same - ``` -- If we'd like to increase a value *and* immediately use the result of the operator, we need the prefix form: - - ```js run - let counter = 0; - alert( ++counter ); // 1 - ``` -- If we'd like to increment a value but use its previous value, we need the postfix form: - - ```js run - let counter = 0; - alert( counter++ ); // 0 - ``` - -````smart header="Increment/decrement among other operators" -The operators `++/--` can be used inside expressions as well. Their precedence is higher than most other arithmetical operations. - -For instance: - -```js run -let counter = 1; -alert( 2 * ++counter ); // 4 -``` - -Compare with: - -```js run -let counter = 1; -alert( 2 * counter++ ); // 2, because counter++ returns the "old" value -``` - -Though technically okay, such notation usually makes code less readable. One line does multiple things -- not good. - -While reading code, a fast "vertical" eye-scan can easily miss something like `counter++` and it won't be obvious that the variable increased. - -We advise a style of "one line -- one action": - -```js run -let counter = 1; -alert( 2 * counter ); -counter++; -``` -```` - -## Bitwise operators - -Bitwise operators treat arguments as 32-bit integer numbers and work on the level of their binary representation. - -These operators are not JavaScript-specific. They are supported in most programming languages. - -The list of operators: - -- AND ( `&` ) -- OR ( `|` ) -- XOR ( `^` ) -- NOT ( `~` ) -- LEFT SHIFT ( `<<` ) -- RIGHT SHIFT ( `>>` ) -- ZERO-FILL RIGHT SHIFT ( `>>>` ) - -These operators are used very rarely. To understand them, we need to delve into low-level number representation and it would not be optimal to do that right now, especially since we won't need them any time soon. If you're curious, you can read the [Bitwise Operators](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators) article on MDN. It would be more practical to do that when a real need arises. - -## Modify-in-place - -We often need to apply an operator to a variable and store the new result in that same variable. - -For example: - -```js -let n = 2; -n = n + 5; -n = n * 2; -``` - -This notation can be shortened using the operators `+=` and `*=`: - -```js run -let n = 2; -n += 5; // now n = 7 (same as n = n + 5) -n *= 2; // now n = 14 (same as n = n * 2) - -alert( n ); // 14 -``` - -Short "modify-and-assign" operators exist for all arithmetical and bitwise operators: `/=`, `-=`, etc. - -Such operators have the same precedence as a normal assignment, so they run after most other calculations: - -```js run -let n = 2; - -n *= 3 + 5; - -alert( n ); // 16 (right part evaluated first, same as n *= 8) -``` - -## Comma - -The comma operator `,` is one of the rarest and most unusual operators. Sometimes, it's used to write shorter code, so we need to know it in order to understand what's going on. - -The comma operator allows us to evaluate several expressions, dividing them with a comma `,`. Each of them is evaluated but only the result of the last one is returned. - -For example: - -```js run -*!* -let a = (1 + 2, 3 + 4); -*/!* - -alert( a ); // 7 (the result of 3 + 4) -``` - -Here, the first expression `1 + 2` is evaluated and its result is thrown away. Then, `3 + 4` is evaluated and returned as the result. - -```smart header="Comma has a very low precedence" -Please note that the comma operator has very low precedence, lower than `=`, so parentheses are important in the example above. - -Without them: `a = 1 + 2, 3 + 4` evaluates `+` first, summing the numbers into `a = 3, 7`, then the assignment operator `=` assigns `a = 3`, and the rest is ignored. It's like `(a = 1 + 2), 3 + 4`. -``` - -Why do we need an operator that throws away everything except the last expression? - -Sometimes, people use it in more complex constructs to put several actions in one line. - -For example: - -```js -// three operations in one line -for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { - ... -} -``` - -Such tricks are used in many JavaScript frameworks. That's why we're mentioning them. But usually they don't improve code readability so we should think well before using them. diff --git a/1-js/02-first-steps/07-type-conversions/article.md b/1-js/02-first-steps/07-type-conversions/article.md new file mode 100644 index 000000000..501a9849f --- /dev/null +++ b/1-js/02-first-steps/07-type-conversions/article.md @@ -0,0 +1,150 @@ +# Տիպերի փոխարկումներ (Type Conversions) + +Հաճախ օպերատորները և ֆունկցիաները ինքնաբերաբար փոխարկում են իրենց տրված արժեքները անհրաժեշտ տիպի։ + +Օրինակ՝ `alert`-ը ինքնաբերաբար փոխարկում է ցանկացած արժեք տողի (string)՝ այն պատկերելու համար։ Մաթեմատիկական գործողությունները փոխարկում են արժեքները թվերի։ + +Նաև լինում են դեպքեր, երբ մենք կարիք ենք ունենում բացահայտորեն փոխարկել արժեքը անհրաժեշ տիպի։ + +```smart header="Դեռևս չենք խոսում օբյեկտների մասին" +Այս գլխում մենք չենք լուսաբանի օբյեկտները։ Առայժմ կխոսենք միայն պրիմիտիվների մասին։ + +Հետագայում, երբ կծանոթանանք օբյեկտների հետ գլխում, կտեսնենք, թե ինչպես են փոխարկվում օբյեկտները։ +``` + +## Տողային փոխարկում (String Conversion) + +Տողի փոխարկումը տեղի է ունենում, երբ կարիք է լինում ներկայացնել արժեքի տողային տեսքը։ + +Օրինակ՝ `alert(value)`-ն կատարում է փոխարկում՝ արժեքը պատկերելու համար։ + +Մենք կարող ենք կանչել `String(value)` ֆունկցիան՝ արժեքը տողի փոխարկելու համար․ + +```js run +let value = true; +alert(typeof value); // boolean + +*!* +value = String(value); // այստեղ value-ին վերագրվեց "true" տողը +alert(typeof value); // string +*/!* +``` + +Տողի փոխարկման արդյունքը հիմնականում ակնհայտ է։ `false`-ը դառնում է `"false"`, `null`-ը դառնում է `"null"` և այլն։ + +## Թվային փոխարկում (Numeric Conversion) + +Մաթեմատիկական ֆունկցիաներում և արտահայտություններում այլ արժեքներիից թվին փոխարկումը տեղի է ունենում ավտոմատ։ + +Օրինակ՝ երբ բաժանումը `/` կիրառվում է ոչ թվային արժեքների վրա․ + +```js run +alert( "6" / "2" ); // 3, տողերը փոխարկվում են թվերի +``` + +Մենք կարող ենք կիրառել `Number(value)` ֆունկցիան՝ `value`-ն բացահայտ կերպով թվի փոխարկելու համար․ + +```js run +let str = "123"; +alert(typeof str); // string + +let num = Number(str); // դառնում է թիվ 123 + +alert(typeof num); // number +``` + +Բացահայտ փոխարկումը հաճախ նախընտրելի է, երբ որևէ թվի մուտքագրում ակնկալող տեքստային դաշտից արժեք վերցնելու կարիք կա։ + +Երբ տողից թվի փոխարկումը վավեր չէ, այդ փոխարկման արդյունքը կլինի `NaN`։ Օրինակ․ + +```js run +let age = Number("կամայական տող, թվի փոխարեն"); + +alert(age); // NaN, փոխարկումը ձախողվել է +``` + +Թվային փոխարկումների կանոնները․ + +| Արժեքը | Փոխարկվում է... | +|-------|-------------| +|`undefined`|`NaN`| +|`null`|`0`| +|true և false | `1` և `0` | +| `string` | Սկզբում և վերջում եղած բացատները (ներառյալ space-ներ, թաբեր `\t`, նոր տողեր `\n`, և այլն) հեռացվում են։ Եթե ստացված տողը դատարկ է, արդյունքը դառնում է `0`։ Հակառակ դեպքում, ոչ դատարկ տողից «կարդացվում է» թիվը։ Ձախողման դեպքում արդյունքը դառնում է `NaN`։ | + +Օրինակներ․ + +```js run +alert( Number(" 123 ") ); // 123 +alert( Number("123z") ); // NaN (փոխարկումը ձախողվել է "z"-ի առկայության պատճառով) +alert( Number(true) ); // 1 +alert( Number(false) ); // 0 +``` + +Նշենք, որ `null`-ը և `undefined`-ը իրենց տարբեր կերպ են դրսևորում՝ `null`-ը դառնում է զրո, մինչդեռ `undefined`-ը դառնում է `NaN`։ + +Մաթեմատիկական օպերատորների գերակշիռ մասը նույնպես իրականացնում է նման փոխարկում։ Մենք կուսումնասիրենք դրանք հաջորդ գլխում։ + +## Տրամաբանական փոխարկում (Boolean Conversion) + +Տրամաբանական փոխարկումը ամենապարզն է։ + +Այն տեղի է ունենում տրամաբանական գործողություններում (ավելի ուշ կծանոթանանք պայմանական ստուգումների և նման այլ կառուցվածքների հետ), բայց նաև կարող է իրականացվել բացահայտ կերպով՝ `Boolean(value)` ֆունկցիայի միջոցով։ + +Փոխարկման կանոնները․ + +- Արժեքները, որոնք ենթադրում են «դատարկություն», ինչպես `0`-ն, դատարկ տողը, `null`-ը, `undefined`-ը և `NaN`-ը, դառնում են `false`։ +- Մնացած արժեքները դառնում են `true`։ + +Օրինակ՝ + +```js run +alert( Boolean(1) ); // true +alert( Boolean(0) ); // false + +alert( Boolean("բարև") ); // true +alert( Boolean("") ); // false +``` + +````warn header="Հիշե՛ք, զրո պարունակող տողը `\"0\"` `true` է" +Որոշ լեզուներ (օրինակ՝ PHP-ն) ընկալում են `"0"` տողը որպես `false`։ Բայց JavaScript-ում ոչ դատարկ տողը միշտ `true` է։ + +```js run +alert( Boolean("0") ); // true +alert( Boolean(" ") ); // բացատը նույնպես true է (ցանկացած ոչ դատարկ տող true է) +``` +```` + +## Ամփոփում + +Առավել հաճախ կիրառվող տիպային փոխարկումները տողային, թվային և տրամաբանական փոխարկումներն են։ + +**`Տողային փոխարկում`** -- Տեղի է ունենում ինչ-որ բանի պատկերման դեպքում։ Կարող է իրականացվել `String(value)`-ի միջոցով։ Տողային փոխարկումը պրիմիտիվ արժեքների դեպքում կանխատեսելի է։ + +**`Թվային փոխարկում`** -- Տեղի է ունենում մաթեմատիկական գործողություններում։ Կարող է իրականացվել `Number(value)`-ի միջոցով։ + +Փոխարկումը ենթարկվում է հետևյալ կանոններին․ + +| Արժեքը | Փոխարկվում է... | +|-------|-------------| +|`undefined`|`NaN`| +|`null`|`0`| +|true / false | `1 / 0` | +| `string` | Տողերը կարդացվում են ինչպես կան, եզրերում եղած բացատները (ներառյալ space-ներ, թաբեր `\t`, նոր տողեր `\n`, և այլն) անտեսվում են։ Դատարկ տողը դառնում է `0`։ Ձախողման դեպքում ստացվում է `NaN`։ | + +**`Տրամաբանական փոխարկում`** -- Տեղի է ունենում տրամաբանական գործողություններում։ Կարող է իրականացվել `Boolean(value)`-ի միջոցով։ + +Ենթարկվում է հետևյալ կանոններին․ + +| Արժեքը | Փոխարկվում է... | +|-------|-------------| +|`0`, `null`, `undefined`, `NaN`, `""` |`false`| +|ցանկացած այլ արժեք| `true` | + + +Այս կանոնների մեծ մասը հեշտ է հասկանալ և հիշել։ Բացառություն են կազմում հետևյալ դեպքերը, որտեղ մարդիկ հաճախակի են սխալվում․ + +- `undefined`-ը որպես թիվ `NaN` է, ոչ թե `0`։ +- `"0"`-ն և միայն բացատներից կազմված տողը՝ ինչպես `" "`, true են։ + +Այս գլխում չխոսվեց օբյեկտների մասին։ Մենք կվերադառնանք դրանց ավելի ուշ՝ ամբողջությամբ օբյեկտներին նվիրված գլխում, JavaScript-ի տարրական գաղափարները ավելի հիմնովին ուսումնասիրելուց հետո։ diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md deleted file mode 100644 index 6437b512e..000000000 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/solution.md +++ /dev/null @@ -1,21 +0,0 @@ - - -```js no-beautify -5 > 4 → true -"apple" > "pineapple" → false -"2" > "12" → true -undefined == null → true -undefined === null → false -null == "\n0\n" → false -null === +"\n0\n" → false -``` - -Some of the reasons: - -1. Obviously, true. -2. Dictionary comparison, hence false. -3. Again, dictionary comparison, first char of `"2"` is greater than the first char of `"1"`. -4. Values `null` and `undefined` equal each other only. -5. Strict equality is strict. Different types from both sides lead to false. -6. Similar to `(4)`, `null` only equals `undefined`. -7. Strict equality of different types. diff --git a/1-js/02-first-steps/08-comparison/article.md b/1-js/02-first-steps/08-comparison/article.md deleted file mode 100644 index d889b1328..000000000 --- a/1-js/02-first-steps/08-comparison/article.md +++ /dev/null @@ -1,209 +0,0 @@ -# Comparisons - -We know many comparison operators from maths: - -- Greater/less than: a > b, a < b. -- Greater/less than or equals: a >= b, a <= b. -- Equals: `a == b` (please note the double equals sign `=`. A single symbol `a = b` would mean an assignment). -- Not equals. In maths the notation is , but in JavaScript it's written as an assignment with an exclamation sign before it: a != b. - -## Boolean is the result - -Like all other operators, a comparison returns a value. In this case, the value is a boolean. - -- `true` -- means "yes", "correct" or "the truth". -- `false` -- means "no", "wrong" or "not the truth". - -For example: - -```js run -alert( 2 > 1 ); // true (correct) -alert( 2 == 1 ); // false (wrong) -alert( 2 != 1 ); // true (correct) -``` - -A comparison result can be assigned to a variable, just like any value: - -```js run -let result = 5 > 4; // assign the result of the comparison -alert( result ); // true -``` - -## String comparison - -To see whether a string is greater than another, JavaScript uses the so-called "dictionary" or "lexicographical" order. - -In other words, strings are compared letter-by-letter. - -For example: - -```js run -alert( 'Z' > 'A' ); // true -alert( 'Glow' > 'Glee' ); // true -alert( 'Bee' > 'Be' ); // true -``` - -The algorithm to compare two strings is simple: - -1. Compare the first character of both strings. -2. If the first character from the first string is greater (or less) than the other string's, then the first string is greater (or less) than the second. We're done. -3. Otherwise, if both strings' first characters are the same, compare the second characters the same way. -4. Repeat until the end of either string. -5. If both strings end at the same length, then they are equal. Otherwise, the longer string is greater. - -In the examples above, the comparison `'Z' > 'A'` gets to a result at the first step while the strings `"Glow"` and `"Glee"` are compared character-by-character: - -1. `G` is the same as `G`. -2. `l` is the same as `l`. -3. `o` is greater than `e`. Stop here. The first string is greater. - -```smart header="Not a real dictionary, but Unicode order" -The comparison algorithm given above is roughly equivalent to the one used in dictionaries or phone books, but it's not exactly the same. - -For instance, case matters. A capital letter `"A"` is not equal to the lowercase `"a"`. Which one is greater? The lowercase `"a"`. Why? Because the lowercase character has a greater index in the internal encoding table JavaScript uses (Unicode). We'll get back to specific details and consequences of this in the chapter . -``` - -## Comparison of different types - -When comparing values of different types, JavaScript converts the values to numbers. - -For example: - -```js run -alert( '2' > 1 ); // true, string '2' becomes a number 2 -alert( '01' == 1 ); // true, string '01' becomes a number 1 -``` - -For boolean values, `true` becomes `1` and `false` becomes `0`. - -For example: - -```js run -alert( true == 1 ); // true -alert( false == 0 ); // true -``` - -````smart header="A funny consequence" -It is possible that at the same time: - -- Two values are equal. -- One of them is `true` as a boolean and the other one is `false` as a boolean. - -For example: - -```js run -let a = 0; -alert( Boolean(a) ); // false - -let b = "0"; -alert( Boolean(b) ); // true - -alert(a == b); // true! -``` - -From JavaScript's standpoint, this result is quite normal. An equality check converts values using the numeric conversion (hence `"0"` becomes `0`), while the explicit `Boolean` conversion uses another set of rules. -```` - -## Strict equality - -A regular equality check `==` has a problem. It cannot differentiate `0` from `false`: - -```js run -alert( 0 == false ); // true -``` - -The same thing happens with an empty string: - -```js run -alert( '' == false ); // true -``` - -This happens because operands of different types are converted to numbers by the equality operator `==`. An empty string, just like `false`, becomes a zero. - -What to do if we'd like to differentiate `0` from `false`? - -**A strict equality operator `===` checks the equality without type conversion.** - -In other words, if `a` and `b` are of different types, then `a === b` immediately returns `false` without an attempt to convert them. - -Let's try it: - -```js run -alert( 0 === false ); // false, because the types are different -``` - -There is also a "strict non-equality" operator `!==` analogous to `!=`. - -The strict equality operator is a bit longer to write, but makes it obvious what's going on and leaves less room for errors. - -## Comparison with null and undefined - -There's a non-intuitive behavior when `null` or `undefined` are compared to other values. - -For a strict equality check `===` -: These values are different, because each of them is a different type. - - ```js run - alert( null === undefined ); // false - ``` - -For a non-strict check `==` -: There's a special rule. These two are a "sweet couple": they equal each other (in the sense of `==`), but not any other value. - - ```js run - alert( null == undefined ); // true - ``` - -For maths and other comparisons `< > <= >=` -: `null/undefined` are converted to numbers: `null` becomes `0`, while `undefined` becomes `NaN`. - -Now let's see some funny things that happen when we apply these rules. And, what's more important, how to not fall into a trap with them. - -### Strange result: null vs 0 - -Let's compare `null` with a zero: - -```js run -alert( null > 0 ); // (1) false -alert( null == 0 ); // (2) false -alert( null >= 0 ); // (3) *!*true*/!* -``` - -Mathematically, that's strange. The last result states that "`null` is greater than or equal to zero", so in one of the comparisons above it must be `true`, but they are both false. - -The reason is that an equality check `==` and comparisons `> < >= <=` work differently. Comparisons convert `null` to a number, treating it as `0`. That's why (3) `null >= 0` is true and (1) `null > 0` is false. - -On the other hand, the equality check `==` for `undefined` and `null` is defined such that, without any conversions, they equal each other and don't equal anything else. That's why (2) `null == 0` is false. - -### An incomparable undefined - -The value `undefined` shouldn't be compared to other values: - -```js run -alert( undefined > 0 ); // false (1) -alert( undefined < 0 ); // false (2) -alert( undefined == 0 ); // false (3) -``` - -Why does it dislike zero so much? Always false! - -We get these results because: - -- Comparisons `(1)` and `(2)` return `false` because `undefined` gets converted to `NaN` and `NaN` is a special numeric value which returns `false` for all comparisons. -- The equality check `(3)` returns `false` because `undefined` only equals `null`, `undefined`, and no other value. - -### Evade problems - -Why did we go over these examples? Should we remember these peculiarities all the time? Well, not really. Actually, these tricky things will gradually become familiar over time, but there's a solid way to evade problems with them: - -Just treat any comparison with `undefined/null` except the strict equality `===` with exceptional care. - -Don't use comparisons `>= > < <=` with a variable which may be `null/undefined`, unless you're really sure of what you're doing. If a variable can have these values, check for them separately. - -## Summary - -- Comparison operators return a boolean value. -- Strings are compared letter-by-letter in the "dictionary" order. -- When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check). -- The values `null` and `undefined` equal `==` each other and do not equal any other value. -- Be careful when using comparisons like `>` or `<` with variables that can occasionally be `null/undefined`. Checking for `null/undefined` separately is a good idea. diff --git a/1-js/02-first-steps/07-operators/1-increment-order/solution.md b/1-js/02-first-steps/08-operators/1-increment-order/solution.md similarity index 100% rename from 1-js/02-first-steps/07-operators/1-increment-order/solution.md rename to 1-js/02-first-steps/08-operators/1-increment-order/solution.md diff --git a/1-js/02-first-steps/07-operators/1-increment-order/task.md b/1-js/02-first-steps/08-operators/1-increment-order/task.md similarity index 100% rename from 1-js/02-first-steps/07-operators/1-increment-order/task.md rename to 1-js/02-first-steps/08-operators/1-increment-order/task.md diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/solution.md b/1-js/02-first-steps/08-operators/2-assignment-result/solution.md similarity index 100% rename from 1-js/02-first-steps/07-operators/2-assignment-result/solution.md rename to 1-js/02-first-steps/08-operators/2-assignment-result/solution.md diff --git a/1-js/02-first-steps/07-operators/2-assignment-result/task.md b/1-js/02-first-steps/08-operators/2-assignment-result/task.md similarity index 100% rename from 1-js/02-first-steps/07-operators/2-assignment-result/task.md rename to 1-js/02-first-steps/08-operators/2-assignment-result/task.md diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md similarity index 70% rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md rename to 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md index 4964a623a..7370b66af 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/solution.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/solution.md @@ -9,9 +9,8 @@ true + false = 1 "$" + 4 + 5 = "$45" "4" - 2 = 2 "4px" - 2 = NaN -7 / 0 = Infinity -" -9 " + 5 = " -9 5" // (3) -" -9 " - 5 = -14 // (4) +" -9 " + 5 = " -9 5" // (3) +" -9 " - 5 = -14 // (4) null + 1 = 1 // (5) undefined + 1 = NaN // (6) " \t \n" - 2 = -2 // (7) @@ -23,4 +22,4 @@ undefined + 1 = NaN // (6) 4. The subtraction always converts to numbers, so it makes `" -9 "` a number `-9` (ignoring spaces around it). 5. `null` becomes `0` after the numeric conversion. 6. `undefined` becomes `NaN` after the numeric conversion. -7. Space characters, are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. +7. Space characters are trimmed off string start and end when a string is converted to a number. Here the whole string consists of space characters, such as `\t`, `\n` and a "regular" space between them. So, similarly to an empty string, it becomes `0`. diff --git a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md similarity index 98% rename from 1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md rename to 1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md index 930c71514..068420c7d 100644 --- a/1-js/02-first-steps/06-type-conversions/1-primitive-conversions-questions/task.md +++ b/1-js/02-first-steps/08-operators/3-primitive-conversions-questions/task.md @@ -16,7 +16,6 @@ true + false "$" + 4 + 5 "4" - 2 "4px" - 2 -7 / 0 " -9 " + 5 " -9 " - 5 null + 1 diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md new file mode 100644 index 000000000..cf9c62b9f --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/solution.md @@ -0,0 +1,32 @@ +Պատճառը այն է որ մուտքագրված թվերը վերդարձվում են որպես տող։ + +Այսպիսով փոփոխականները ունեն `"1"` և `"2"` արժեքները։ + +```js run +let a = "1"; // prompt("First number?", 1); +let b = "2"; // prompt("Second number?", 2); + +alert(a + b); // 12 +``` + +Այն, ինչ մենք պետք է անենք, տողերը թվեր փոխարկելն է `+`-ից առաջ։ Օրինակ՝ կիրառելով `Number()` կամ դնելով դիմացից `+`։ + +Օրինակ՝ `prompt`-ից անմիջապես առաջ: + +```js run +let a = +prompt("First number?", 1); +let b = +prompt("Second number?", 2); + +alert(a + b); // 3 +``` + +Կամ `alert`-ի մեջ: + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); + +alert(+a + +b); // 3 +``` + +Կիրառվել են և ունար և բինար `+`վերջին կոդւոմ։ Հիանալի տեսք ունի, այդպես չէ՞ diff --git a/1-js/02-first-steps/08-operators/4-fix-prompt/task.md b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md new file mode 100644 index 000000000..d15855531 --- /dev/null +++ b/1-js/02-first-steps/08-operators/4-fix-prompt/task.md @@ -0,0 +1,18 @@ +importance: 5 + +--- + +# Ուղղել գումարումը + +Գրված է կոդի հատված, որը օգտագործողից ստանում է երկու թվեր և ցուցադրում է դրանց գումարը։ + +Սա աշխատում է սխալ։ Ելքային արդյունքը, պատկերված օրինակում `12`-է (լռելայն արժեքների դեպքում)։ + +Ինչու՞ ուղղել դա։ Արդյունքը պետք է լինի `3`. + +```js run +let a = prompt("First number?", 1); +let b = prompt("Second number?", 2); + +alert(a + b); // 12 +``` diff --git a/1-js/02-first-steps/08-operators/article.md b/1-js/02-first-steps/08-operators/article.md new file mode 100644 index 000000000..1bfccc7df --- /dev/null +++ b/1-js/02-first-steps/08-operators/article.md @@ -0,0 +1,480 @@ +# Մաթեմատիկական տարրական գործողություններ + +Մենք գպրոցից գիտեն շատ գործողություններ։ Դրանցից են, օրինակ գումարում `+`, բազմապատկում `*`, հանում `-` և այլն։ + +Այս հատվածում, մենք կսկսենք պարզ գործողույթուններից, ապա կկենտրոնանանք JavaScript-ի հատուկ ասպեկտների վրա, որոնք գոյություն չունեն դպրոցական թվաբանության մեջ։ + +## "ունար", "բինար" և "օպերանդ" հասկացությունները + +Մինչ առաջ անցնելը, եկեք հասկանանք որոշ տերմինների նշանակությունը։ + +- *Նշան* -- հասկացություն, որը կազմում է գործողությունների հիմքը։ Օրինակ ՝ `5 * 2` –ի բազմապատկման մեջ կան երկու օպերանդներ՝ ձախ օպերանդը՝ `5`, իսկ աջը՝ `2`: Երբեմն, մարդիկ «օպերանդներ»-ի փոխարեն, անվանում են «արգումենտներ»: +- Գործողությունը համարվում է *ունար* եթե այն ունի միայն մեկ օպերանդ։ Օրինակ՝ ունար ժխտումը `-` հակադարձում է թվի նշանը: + + ```js run + let x = 1; + + *!* + x = -x; + */!* + alert( x ); // -1, կիրառվել է ունար ժխտում + ``` +- Գործողույթունը համարվում է *երկուական* եթե այն ունի երկու օպերանդ. Նույն մինուսը գոյություն ունի նաև հանման տեսքով: + + ```js run no-beautify + let x = 1, y = 3; + alert( y - x ); // 2, երկուական մինուսը ցույց է տալիս արժեքների տարբերությունը + ``` + + Վերը նշված օրինակներում մենք ունենք երկու տարբեր օպերատորներ, որոնք կիրառում են նույն նշանը. Ժխտման օպերատոր, նշանը հակադարձող ունարի օպերատոր և հանում օպերատոր, երկուական օպերատոր, որը հանում է մեկ թիվը մյուսից: + +## Մաթեմատիկա + +Հետևյալ մաթեմատիկական գործողությունները աջակցվում են․ + +- Գումարում `+`, +- Հանում `-`, +- Բազմապատկում `*`, +- Բաժանում `/`, +- Մնացորդ `%`, +- Աստիճան բարձրացնել (անգլ․՝ Exponentiation) `**`. + +Առաջին չորս գործողությունները պարզ են, մինչդեռ `%` և `**` գործողությունների համար, մի քան խոսք ավելին։ + +### Մնացորդ % + +Մնացորդի օպերատորը հետևյալն է `%`, չնայած նշանի պատկերմանը, դա կապված չէ տոկոսների հետ: + +Հետրյալ հավասարման `a % b` արդյունքը [remainder](https://en.wikipedia.org/wiki/Remainder) `a` բաժանած `b` մնացորդն է։ + +Օրինակ՝ + +```js run +alert( 5 % 2 ); // 1, 5-ը բաժանած 2-ի մնացորդը +alert( 8 % 3 ); // 2, 8-ը բաժանած 3-ի մնացորդը +alert( 8 % 4 ); // 0, 8-ը բաժանած 4-ի մնացորդը +``` + +### Աստիճան բարձրացնել ** + +Աստիճան բարձրացնելու օպերատորը `a ** b` բարձրացնում է `a`-ն `b`-ի աստիճան. + +Դպրոցական մաթեմատիկայում այն գրում ենք հետևյալ տեսքով ab։ + +Օրինակ՝ + +```js run +alert( 2 ** 2 ); // 2² = 4 +alert( 2 ** 3 ); // 2³ = 8 +alert( 2 ** 4 ); // 2⁴ = 16 +``` + +Ինչպես մաթեմատիկայում, այստեղ նույնպես աստիճան բարձրացնելու օպերատորը հասանելի է ոչ ամբողջ թվերի դեպքում ևս։ + +Օրինակ՝ ½-ի աստիճանը: + +```js run +alert( 4 ** (1/2) ); // 2 (power of 1/2 is the same as a square root) +alert( 8 ** (1/3) ); // 2 (power of 1/3 is the same as a cubic root) +``` + + +## Տողերի միավորումը երկուական +-ով + +Եկեք ծանոթանանք JavaScript-ի այլ հնարավորությունների հետ։ + +Սովորաբար, `+` գործողությունը գումարում է թվերը։ + +Բայց, երբ երկուական `+`-ը կիրառովում է տողերի համար, դա միացնում է դրանք․ + +```js +let s = "my" + "string"; +alert(s); // mystring +``` + +Նշում, եթե ցանկացած օպրանդ տող է, ապա մյուս օպերանդը նույնպես ձևափոխվում է տողի։ + +Օրինակ՝ + +```js run +alert( '1' + 2 ); // "12" +alert( 2 + '1' ); // "21" +``` + +Կապ չունի այն, որ առաջին օպերանդն է տող, թե երկրորդը․ + +Դիտարկենք ավելի բարդ օրինակ՝ + +```js run +alert(2 + 2 + '1' ); // "41", այլ ոչ թե "221" +``` + +Այստեղ օպերատորները աշխատում են հաջորդաբար։ Առաջին `+`-ը գումարում է երկու թվերը, վարադարձնում է `4`, ապա հաջորդ `+`-ը ավալցանում`1` տողային փոփոխականը, այսպիսով դա նման է հետևյալին `4 + '1' = '41'`. + +```js run +alert('1' + 2 + 2); // "122", այլ ոչ թե "14" +``` +Այստեղ առաջին օպերանդը տող է, կոմպիլյատորը վերաբերվում է մյուս օպերանդներին նույնպես տող . `2`-ը կմիավորվի `'1'`-ի հետ, այսպիսով դա նման է հետևյալին `'1' + 2 = "12"` և `"12" + 2 = "122"`. + +Երկուական `+`-ը միակ օպերատորնե, որը նման կերպ է վարվում տողերի հետ։ Մյուս թվաբանական օպերատորները աշխատում են միայն թվերի հետ և միշտ կերպափոխում են օպերանդները թվերի։ + +Դիտարկենք հանման և բաժանման ցուցադրությունը․ + +```js run +alert( 6 - '2' ); // 4, '2'-ը ձևափոխվում է թվի +alert( '6' / '2' ); // 3, երկու օպերանդներնել ձևափոխվում են թվերի +``` + +## Թվային կերպափոխումներ, ունար + + +Գոյություն ունի `+`-ի երկու տեսակ․ երկուական տեսակ, որը մենք կիրառեցինք վերևում և ունար տեսակ։ + +Ունար գումարում կամ մեկ այլ խոսքով `+` որը կիրառվում է մեկ արժեքների դեպքում, թվերի վրա չի ազդում։ Բայց եթե օպերանդը թիվ չէ, ապա ունար գումարումը այն կերպափոխում է թվի։ + +Օրինակ՝ + +```js run +// Թվերի վրա ազդեցություն չի ունենում +let x = 1; +alert( +x ); // 1 + +let y = -2; +alert( +y ); // -2 + +*!* +// Կերպափոխում է ոչ թվային արժեքները +alert( +true ); // 1 +alert( +"" ); // 0 +*/!* +``` + +Այն իրականում անում է նույնը, ինչ `Number(...)`-ը, բայց կարճ տարբերակով։ + +Տողերը թվերի կերպափոխելու անհրաժեշտությունը շատ հաճախ է առաջանում։ Օրինակ՝ եթե մենք ստանում ենք դաշտերը HTML ֆորմայից, դրանք սովորաբար տողային տիպի են։ Ի՞նչ կլինի այդ դեպքում դրանց գումարը։ + +Երկուական գումարումը դրանք կավելացնի իրար ինչպես տող։ + +```js run +let apples = "2"; +let oranges = "3"; + +alert( apples + oranges ); // "23", երկուական գումարը միացնում է տողերը իրար հետ +``` + +Եթե մենք ուզում ենք նրանց վերաբերվել որպես թվերի, կարիք կա կերպափոխելու դրանք և կատարել դրանց գումարումը: + +```js run +let apples = "2"; +let oranges = "3"; + +*!* +// երկու արժեքները մինչև երկուական գումարմանը մասնակցելը, կերպափոխվում են թվերի +alert( +apples + +oranges ); // 5 +*/!* + +// երկար տարբերակը +// alert( Number(apples) + Number(oranges) ); // 5 +``` + +Մաթեմատիկական տեսանկյունից, պլյուսների առատությունը կարող է տարօրինակ թվալ։ Բայց ծրագրավորողների տեսանկյունից, հատուկ ոչինչ չկա: ունար պլյուսը կիրառվում է սկզբից, դրանք կերպափոխում են տողերը թվերի, իսկ երկուական պլյուսը գումարում է դրանք։ + +Ինչու՞ է ունար պլյուսը կիրառվում սկզբից, նախքան երկուականը, դա կախված է *բարձր առաջնահերթություն*. + +## Օպերատորների առաջնահերթություն + +Եթե արդահայտությունը ունի մեկից ավել օպերատորներ, ապա կատարման հերթականությունը սահմանվում է կախված նրանց *առաջնահերթությունից*, կամ այլ բառով, օպերատորների կանխադրված առաջնահերթ կարգը։ + +Դպրոցից մենք գիտեն արտահայտությունում բազմապատկման կարգի մասին `1 + 2 * 2` պետք է կատարվի մինչ գումարումը։ Դա հենց առաջնահերթությունն է: Ասում են, որ բազմապատկումը ունի *ավելի բարձր առաջնահերթություն* քան թե գումարումը։ + +Փակագծերը ավելի առաջնահերթ են քան մյուսները, ուստի, եթե մենք համամիտ չենք առաջնահերթությունից, կարող ենք օգտագործել դրանք, այն փոխելու համար: Օրինակ՝ գրելով `(1 + 2) * 2`. + +Կան բազմաթիվ օպերատորներ JavaScript-ում։ Յուրաքանչյուր օպերատոր ունի իր համապատասխան առաջնահերթության համարը: Ավելի մեծ թիվ ունեցողը կատարվում է առաջինը։ Եթե առաջնահերթությունը նույնն է, ապա կատարման հերթականությունը ձախից դեպի աջ է։ + +Ահա քաղվածքը [precedence table](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_Precedence) (դուք պարտավոր չեք հիշել սա, բայց նշեք, որ ունար օպերատորներն ավելի բարձր են, քան համապատասխան երկուականները): + +| Առաջնահերթություն | Անվանում | Նշան | +|------------|------|------| +| ... | ... | ... | +| 14 | ունար պլյուս | `+` | +| 14 | ունար ժխտում | `-` | +| 13 | աստիճան բարձրացում | `**` | +| 12 | բազմապատկում | `*` | +| 12 | բաժանում | `/` | +| 11 | գումարում | `+` | +| 11 | հանում | `-` | +| ... | ... | ... | +| 2 | վերագրում | `=` | +| ... | ... | ... | + +Կարող ենք տեսնել, "ունար պլյուս"-ը ունի `14` գերակայությունը, որը մեծ է քան `11`-ը "գումարում" (երկուական պլյուս)։ Ահա թե ինչու է `"+apples + +oranges"` արտահայտությունում ունար պլյուսը կատարվում նախքան գումարումը։ + +## Վերագրում + +Պետք է նշել այն, որ վերագրումը `=` նույնպես օպերատոոր է։ Այն գերակայության աղյուսակում ունի շատ ցածր դիրք՝ `2`։ + +Ահա ինչու, երբ մենք վերագրում ենք փոփոխկան, ինչպիսին է `x = 2 * 2 + 1`, հաշվարկը կատարվում է սկզբում և հետո կատարվում է`=`, պահպանելով արդյունքը `x`-ում։ + +```js +let x = 2 * 2 + 1; + +alert( x ); // 5 +``` + +### Վերագրումը = վերադարձնում է արժեք + +Վերագրման `=` օպերատոր լինլու փաստը, ունի ոչ "կախարդական" ենթատեքստ։ + +JavaScript-ում բոլոր օպերատորները վերադարձնում են արժեք։ Դա ակնհայտ է `+`-ի և `-`-ի համար, բայց նաև `=`-ի համար։ + +`x = value`-ի կանչը գրանցում է `value`-ն `x`-ի մեջ *և վերադարձնում է այն*։ + +Այա վերագրման կիրառման օրինակ, որպես բարդ արտահայտության հատված: + +```js run +let a = 1; +let b = 2; + +*!* +let c = 3 - (a = b + 1); +*/!* + +alert( a ); // 3 +alert( c ); // 0 +``` + +Վերը նշված օրինակում, հետևյալ արտահայտության `(a = b + 1)` արժեքը դա այն է, որը պետք է վերագրվի `a`-ին (դա `3`-ն է)։ Այնուհետեւ այն օգտագործվում է հետագա արժեվորման համար։ + +Զվարճալի կոդ է, այնպես չէ՞: Մենք պետք է հասկանանք թե ինչպես է այն աշխատում, քանի որ երբեմն այն կարող եք հանդիպել JavaScript-ի գրադարաններում։ + +Ամեն դեպքում, խնդրում եմ, կոդը այդպես մի գրեք: Նման հնարքները հաստատ հստակ ու հասկանալի չեն դարձնում կոդը: + +### Կապակցված վերագրումներ + +Մեկ այլ հետաքրքիր առանձնահատկություն է վերգրումների կապակցումը․ + +```js run +let a, b, c; + +*!* +a = b = c = 2 + 2; +*/!* + +alert( a ); // 4 +alert( b ); // 4 +alert( c ); // 4 +``` + +Կապակցումը կատարվում է աջից դեպի ձախ։ Առաջին կատարվում է ամենաաջակողմյան `2 + 2` արդահայտությունը, ապա վերագրվում է ձախ կողմի փոփոխականին․ `c`, `b` և `a`։ Ամենավերջում, բոլոր փոփոխականները հավաքվում է մեկ արժեքի մեջ։ + +Կրկին անգամ, ընթերցելիության նկատառումներից ելնելով, լավ է որ այն բաժանված է մի քանի տողերի․ + +```js +c = 2 + 2; +b = c; +a = c; +``` +Սա հեշտ է ընթերցվում, հատկապես այն դեպքւոմ, երբ աչքը արագ է դիտարկում կոդը։ + +## Փոփոխել տեղում + +Երբեմն մենք կարիք ենք ունենում, կիրառել օպերատորը և պահպանել նոր արդյունքը նույն փոփոխականի մեջ։ + +Օրինակ՝ + +```js +let n = 2; +n = n + 5; +n = n * 2; +``` + +Այս ամենը կարող ենք կարճ գրել `+=` և `*=`: + +```js run +let n = 2; +n += 5; // այժմ n = 7 (նույնն է ինչ n = n + 5) +n *= 2; // այժմ n = 14 (նույնն է ինչ n = n * 2) + +alert( n ); // 14 +``` + +Կարճ "ձևափոխել և վերագրել" օպերատորները գոյություն ունեն բոլոր մաթեմատիկական և բիթային օպերատորների համար․ `/=`, `-=` և այլն։ + +Նման օպերատորներն ունեն նույն գերակայությունը, ինչ սովորական վերագրումները, այնպես որ նրանք աշխատում են շատ այլ հաշվարկներից հետո. + +```js run +let n = 2; + +n *= 3 + 5; // right part evaluated first, same as n *= 8 + +alert( n ); // 16 +``` + +## Ինկրեմենտ/դեկրեմենտ + + + +Թիվը մեկով մեծացնելը կամ փոքրացնելը ամենատարածված թվային գործողություններից է։ + +Այսպիսով դրա համար կան հատուկ օպերատորներ․ + +- **Ինկրեմենտ** `++` փոփոխականի ավելացումը 1-ով․ + + ```js run no-beautify + let counter = 2; + counter++; // կաշխատի որպես counter = counter + 1, բայց կարճ տարբերակով + alert( counter ); // 3 + ``` +- **Դեկրեմենտ** `--` փոքրացնել փոփոխականը 1-ով․ + + ```js run no-beautify + let counter = 2; + counter--; // կաշխատի որպես counter = counter - 1, բայց կարճ տարբերակով + alert( counter ); // 1 + ``` + +```warn +Ինկրեմենտ/դեկրեմենտ պետք է կրատվի փոփոխականների համար։ Այն փորձելով օգտագործել օրինակ՝ `5++` փոփոխականի համար կստանանք սխալ։ +``` + +`++` և `--` օպերատորները կարող են տեղադրվել փոփոխականից առաջ և հետո։ + +- Երբ օպերատորը կիրառվում է փոփոխականից հետո, դա համարվում է "պոստֆիքս կառուցվածք"․ `counter++`. +- "պրեֆիքս կառուցվածք"-ը երբ կիրառվում է փոփոխականից առաջ․ `++counter`. + +Բոլոր գործեղությունները կատարում են նույնը․ ավելացնում են `counter`-ին `1`. + +Կա՞ արդյոք տարբերություն․ Այո, բայց դա մենք կարող ենք տեսնել դա միայն վերադարձվող արժեքում `++/--`. + +Եկեք պարզաբանենք։ Ինչպես գիտենք բոլոր օպերատորները վերադարձնում են արժեք։ Ինկրեմենտ/դեկրեմենտ-ը բացառություն չեն։ Պրեֆիքս տեսքը վերադարձնում է նոր արժեք, մինչ դեռ պոստֆիքը վերադարձնում է հին արժեքը (նախքան ինկրեմենտ/դեկտրեմենտ-ը). + +Տարբերությունը տեսնելու համար, դիտարկենք օրինակը․ + +```js run +let counter = 1; +let a = ++counter; // (*) + +alert(a); // *!*2*/!* +``` + +Հետևյալ տողում `(*)`, *պրեֆիք*-ը `++counter` ավելացնում է `counter`-ի արժեքը և վերադարձնում է նոր արժեքը, `2`։ Այսպիսով `alert`-ը ցույց կտա`2`։ + +Դիտարկենք պոստֆիքս կառուցվածքը․ + +```js run +let counter = 1; +let a = counter++; // (*) ++counter-ը ձևափոխվել է counter++ - ի + +alert(a); // *!*1*/!* +``` + +Հետևյալ տեղում `(*)`, *պոստվիքս* կառուցվածքը `counter++` նույնպես ավելացնում է `counter`-ի արժեքը, բայց վերադարձնում է *հին* արժեքը (նախքան ավելացումը)։ Այսպիսով `alert`-ը ցույց կտա `1`։ + +Ամփոփում․ + +- Եթե ինկրեմենտ/դեկրեմենտ-ի արժեքները չեն օգտագործվում, ապա տարբերություն չկա թե որ մեկը կկիրառվի․ + + ```js run + let counter = 0; + counter++; + ++counter; + alert( counter ); // 2, վերը նշված տողերը իրականացնում են նույնը + ``` +- Եթե մենք ցանկանում են ավելացնել արժեքը *և* անմիջապես օգտագործել օպերատորի արժեքը, մենք պետք է օգտագործենք պրեֆիքս կառուցվածքը․ + + ```js run + let counter = 0; + alert( ++counter ); // 1 + ``` +- Եթե ցանկանում ենք ավելացնել արժեքը, բայց օգտագործել նախորդ արժեքը, կարիք կա կիրառելու պոստֆիքս կառուցվածքը․ + + ```js run + let counter = 0; + alert( counter++ ); // 0 + ``` + +````smart header="Increment/decrement among other operators" +Հետևյալ օպերատորները `++/--` կարող են կիրառվել նաև արտահայտությունների ներսում։ Նրանց գերակայությունն ավելի բարձր է, քան մյուս թվաբանական գործողություններինը։ + +Օրինակ՝ + +```js run +let counter = 1; +alert( 2 * ++counter ); // 4 +``` + +Համեմատել հետևյալի հետ + +```js run +let counter = 1; +alert( 2 * counter++ ); // 2, քանի որ counter++ վերադարձնում է "հին" արժեքը +``` + +Չնայած տեխնիկապես սա լավ է, սովորաբար նման կիարառությունը կոդը դարձնում է քիչ ընթեռնելի։ Մի տողը կատարում է բազմաթիվ գործողություններ -- դա լավ չէ։ + +Քանի դեռ ընթերցվում է կոդը, արագ "ուղղահայաց" դիտարկումը կարող է բաց քողնել այնպիսի բան, ինչպիսին է `counter++`-ը և ակնհայտ չի լինի, որ փոփոխականի արժեքը ավելացել է։ + +Մենք խորհուրդ ենք տալիս "մեկ տող -- մեկ գործողություն" ոճը: + +```js run +let counter = 1; +alert( 2 * counter ); +counter++; +``` +```` + +## Բիթային օպերատորներ + +Բիթային օպերատորները արգումենտներին վերաբերվում են որպես 32-բիթ երկարությամբ ամբողջ թվերի և աշխատում են իրենց երկուական ներկայացման մակարդակով: + +Այս օպերատորները կոնկրետ JavaScript-ինը չեն։ Դրանք աջակցվում են ծրագրավորման լեզուների մեծ մասում։ + +Օպերատորների ցանկը․ + +- և ( `&` ) +- կամ ( `|` ) +- Բացառող կամ (XOR) ( `^` ) +- ժխտում ( `~` ) +- Ձախ տեղաշարժ ( `<<` ) +- Աջ տեխաշարժ ( `>>` ) +- Զրոյի ավելացմամբ աջ տեղաշարժ ( `>>>` ) + +Այս օպերատորները շատ հազվադեպ են օգտագործվում, երբ մենք պետք է կիրառենք թվերը ամենացածր (բիթային) մակարդակում։ Մեզ այս օպերատորները շուտով պետք չեն լինի, քանի որ վեբ ծրագրավորման մեջ դրանք քիչ են օգտագործում, բայց որոշ հատուկ ոլորտներում, ինչպիսին է գաղտնագրությունը, դրանք կիրառվում են։ Անհարժեշտության դեպքում կարող եք կարդալ [Բիթային օպերատորներ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators#Bitwise) հոդվածը MDN-ում։ + +## Ստորակետ + +Ստորակետ օպերատորը `,` ամենահազվագյուտ և անսովոր օպերատորներից է։ Երբեմն դա օգտագործվում է ավելի կարճ կոդ գրելու համար, այնպես որ մենք պետք է դա իմանանք, որպեսզի հասկանանք, թե ինչ է կատարվում: + +Հետևյալ օպերատորը թույլ է տալիս հաշվարկել մի քանի արտահայտություններ բաժանելով դրանք `,`-ով։ Նրանցից յուրաքանչյուրը հաշվարկվում է, բայց վերադարձվում է միայն վերջին արդյունքը։ + +Օրինակ՝ + +```js run +*!* +let a = (1 + 2, 3 + 4); +*/!* + +alert( a ); // 7 (3 + 4-ի արդյունքը) +``` + +Այստես առաջին արտահայտությունը`1 + 2` վերլուծվում է, և արդյունքը անտեսվում է. Որից հետո, `3 + 4` արտահայտությունը վերլուծվում է, և վերադարձվում է որպես արդյունք։ + +```smart header="Comma has a very low precedence" +Խնդրում ենք նկատի ունենալ, որ ստորակետի օպերատորը շատ ցածր առաջնահերթություն ունի, ցածր քան `=`, այնպես որ փակագծերը կարևոր են վերը նշված օրինակում։ + +Առանց դրանց: `a = 1 + 2, 3 + 4` վերլուծվում է `+`-ը առաջին հերթին, ստանալով `a = 3, 7`, ապա վերագրամն օպերատորը `=` վերագրում է `a = 3`, իսկ մնացածը անտեսվում է։ Դա նման է հետևյալին `(a = 1 + 2), 3 + 4`։ +``` + +Ինչու՞ է մեզ պետք օպերատոր, որը անտեսում է ամեն ինչ, բացի վերջին արտահայտությունից: + +Երբեմն, երբեմն մարդիկ օգտագործում են դա, առավել բարդ կառուցվածքներում,մի շարք գործողություններ դնելում համար մեկ տողում։ + +Օրինակ՝ + +```js +// երեք օպերատորներ մեկ տողում +for (*!*a = 1, b = 3, c = a * b*/!*; a < 10; a++) { + ... +} +``` + +Նման հնարքներ կիրառվում են JavaScript-ի մի շարք ֆրեյմվորկներում։ Ահա թե ինչում մենք դիտարկեցինք դրանք։ Բայց երբեմն դրանք չեն բարելավում կոդի ընթեռնելիությունը, ուստի դրանք օգտագործելուց առաջ պետք է լավ մտածել։ diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md b/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md deleted file mode 100644 index 903ee7ff3..000000000 --- a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/solution.md +++ /dev/null @@ -1,24 +0,0 @@ -JavaScript-code: - -```js demo run -let name = prompt("What is your name?", ""); -alert(name); -``` - -The full page: - -```html - - - - - - - - -``` diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md b/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md deleted file mode 100644 index a65a654e0..000000000 --- a/1-js/02-first-steps/09-alert-prompt-confirm/1-simple-page/task.md +++ /dev/null @@ -1,9 +0,0 @@ -importance: 4 - ---- - -# A simple page - -Create a web-page that asks for a name and outputs it. - -[demo] diff --git a/1-js/02-first-steps/09-alert-prompt-confirm/article.md b/1-js/02-first-steps/09-alert-prompt-confirm/article.md deleted file mode 100644 index 8ba414e9c..000000000 --- a/1-js/02-first-steps/09-alert-prompt-confirm/article.md +++ /dev/null @@ -1,109 +0,0 @@ -# Interaction: alert, prompt, confirm - -In this part of the tutorial we cover JavaScript language "as is", without environment-specific tweaks. - -But we'll still be using the browser as our demo environment, so we should know at least a few of its user-interface functions. In this chapter, we'll get familiar with the browser functions `alert`, `prompt` and `confirm`. - -## alert - -Syntax: - -```js -alert(message); -``` - -This shows a message and pauses script execution until the user presses "OK". - -For example: - -```js run -alert("Hello"); -``` - -The mini-window with the message is called a *modal window*. The word "modal" means that the visitor can't interact with the rest of the page, press other buttons, etc. until they have dealt with the window. In this case -- until they press "OK". - -## prompt - -The function `prompt` accepts two arguments: - -```js no-beautify -result = prompt(title, [default]); -``` - -It shows a modal window with a text message, an input field for the visitor, and the buttons OK/Cancel. - -`title` -: The text to show the visitor. - -`default` -: An optional second parameter, the initial value for the input field. - -The visitor may type something in the prompt input field and press OK. Or they can cancel the input by pressing Cancel or hitting the `key:Esc` key. - -The call to `prompt` returns the text from the input field or `null` if the input was canceled. - -For instance: - -```js run -let age = prompt('How old are you?', 100); - -alert(`You are ${age} years old!`); // You are 100 years old! -``` - -````warn header="In IE: always supply a `default`" -The second parameter is optional, but if we don't supply it, Internet Explorer will insert the text `"undefined"` into the prompt. - -Run this code in Internet Explorer to see: - -```js run -let test = prompt("Test"); -``` - -So, for prompts to look good in IE, we recommend always providing the second argument: - -```js run -let test = prompt("Test", ''); // <-- for IE -``` -```` - -## confirm - -The syntax: - -```js -result = confirm(question); -``` - -The function `confirm` shows a modal window with a `question` and two buttons: OK and Cancel. - -The result is `true` if OK is pressed and `false` otherwise. - -For example: - -```js run -let isBoss = confirm("Are you the boss?"); - -alert( isBoss ); // true if OK is pressed -``` - -## Summary - -We covered 3 browser-specific functions to interact with visitors: - -`alert` -: shows a message. - -`prompt` -: shows a message asking the user to input text. It returns the text or, if Cancel button or `key:Esc` is clicked, `null`. - -`confirm` -: shows a message and waits for the user to press "OK" or "Cancel". It returns `true` for OK and `false` for Cancel/`key:Esc`. - -All these methods are modal: they pause script execution and don't allow the visitor to interact with the rest of the page until the window has been dismissed. - -There are two limitations shared by all the methods above: - -1. The exact location of the modal window is determined by the browser. Usually, it's in the center. -2. The exact look of the window also depends on the browser. We can't modify it. - -That is the price for simplicity. There are other ways to show nicer windows and richer interaction with the visitor, but if "bells and whistles" do not matter much, these methods work just fine. diff --git a/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md new file mode 100644 index 000000000..7712f06bf --- /dev/null +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/solution.md @@ -0,0 +1,21 @@ + + +```js no-beautify +5 > 4 → true +"apple" > "pineapple" → false +"2" > "12" → true +undefined == null → true +undefined === null → false +null == "\n0\n" → false +null === +"\n0\n" → false +``` + +Պատճառներից որոշները. + +1. Միանշանակ, true. +2. Բառարանային համեմատություն, հետևաբար՝ false: `"a"`-ն ավելի փոքր է, քան `"p"`-ն։ +3. Կրկին բառարանային համեմատություն, առաջին նշան `"2"`-ը ավելի մեծ է, քան առաջինը նշան `"1"`-ը։ +4. `null` և `undefined` արժեքները հավասար են միայն իրար։ +5. Տարբեր տիպերի խիստ հավասարության ստուգման արդյունքը false է։ +6. Նման է `(4)`-ին, `null`-ը միայն հավասար է `undefined`-ին։ +7․ Տարբեր տիպերի խիստ հավասարության ստուգում: \ No newline at end of file diff --git a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md similarity index 55% rename from 1-js/02-first-steps/08-comparison/1-comparison-questions/task.md rename to 1-js/02-first-steps/09-comparison/1-comparison-questions/task.md index be7f75ddd..68f05da54 100644 --- a/1-js/02-first-steps/08-comparison/1-comparison-questions/task.md +++ b/1-js/02-first-steps/09-comparison/1-comparison-questions/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Comparisons +# Համեմատություններ -What will be the result for these expressions? +Ի՞նչ են հետևյալ արտահայտությունների արժեքները: ```js no-beautify 5 > 4 @@ -15,4 +15,3 @@ undefined === null null == "\n0\n" null === +"\n0\n" ``` - diff --git a/1-js/02-first-steps/09-comparison/article.md b/1-js/02-first-steps/09-comparison/article.md new file mode 100644 index 000000000..06c46b1ef --- /dev/null +++ b/1-js/02-first-steps/09-comparison/article.md @@ -0,0 +1,216 @@ +# Համեմատություններ + +Մենք գիտենք համեմատության բազմաթիվ գործողություններ մաթեմատիկայից։ + +JavaScript-ում դրանք գրվում են հետևյալ տեսքով․ + +- Մեծ/փոքր։ a > b, a < b։ +- Մեծ/փոքր կամ հավասար։ a >= b, a <= b։ +- Հավասար՝ `a == b`: Ուշադրություն դարձրեք, որ `==` գործողությունով ստուգվում է հավասարությունը, մինչդեռ `a = b` նշանակում է վերագրում։ +- Ոչ հավասար։ Մաթեմատիկայում այս նշանը հետևյալն է՝ , բայց JavaScript-ում այն ներկայացվում է հետևյալ տեսքով՝ a != b։ + +Այս հոդվածում մենք կսովորենք համեմատության տարբեր գործողությունները, թե ինչպես է JavaScript-ը կատարում դրանք, ներառյալ կարևոր առանձնահատկությունները։ + +Վերջում դուք կիմանաք, թե ինչպես խուսափել «JavaScript-ի տարօրիանկություններ»-ի հետ կապված խնդիրներից։ + +## Արդյունքը տրամաբանական տիպի է + +Համեմատության բոլոր գործողությունները վերադարձնում են տրամաբանական տիպի արժեք․ + +- `true` - նշանակում է "այո", "ճիշտ" կամ "իրական". +- `false` - նշանակում է "ոչ", "սխալ" կամ "ոչ իրական/կեղծ". Օրինակ․ + +Օրինակ՝ + +```js run +alert( 2 > 1 ); // true (ճիշտ) +alert( 2 == 1 ); // false (սխալ) +alert( 2 != 1 ); // true (ճիշտ) +``` + +Համեմատության արդյունքը կարելի է վերագրել փոփոխականի, ինչպես ցանկացած արժեք․ + +```js run +let result = 5 > 4; // վերագրել համեմատության արդյունքը +alert(result); // true (ճիշտ) +``` + +## Տողերի համեմատություն + +Եթե ցանկանում ենք ստուգել թե որ տողային փոփոխականն է մեծ, JavaScript-ը օգտագործում է այսպես կոչված "բառարան" (անգլ․՝ dictionary) կամ "բառարանային" (անգլ․՝ lexicographical) հերթականություն։ + +Այլ կերպ ասած՝ տողերը համեմատվում են տառ առ տառ։ + +Օրինակ՝ + +```js run +alert( 'Z' > 'A' ); // true +alert( 'Glow' > 'Glee' ); // true +alert( 'Bee' > 'Be' ); // true +``` + +Երկու տողային փոփոխականների համեմատման ալգորիթմը շատ պարզ է․ + +1. Համեմատել երկու տողերի առաջին նշանները: +2․ Եթե առաջին տողի առաջին նշանը մեծ (կամ փոքր) է, քան մյուս տողինը, ապա առաջին տողը մեծ (կամ փոքր) է երկրորդից: +3. Հակառակ դեպքում, եթե տողերի առաջին նշանները հավասար են, ապա նույն ձևով համեմատվում են երկրորդ նշանները: +4. Գործողությունը կրկնվում է մինչև տողերից որևէ մեկի ավարտը։ +5. Եթե տողերն ունեն նույն երկարությունը, ապա դրանք հավասար են։ Հակառակ դեպքում, մեծ երկարություն ունեցող տողը համարվում է ավելի մեծ: + +Առաջին օրինակում `'Z' > 'A'` համեմատության արդյունքը կստացվի առաջին քայլից։ + +Երկրորդ համեմատությունը `'Glow'` և `'Glee'`, պահանջում է ավել քայլեր, քանի որ տողերը համեմատվում են նիշ առ նիշ․ + +1. `G`-ն նույնն է ինչ `G`-ն։ +2. `l`-ը նույնն է ինչ `l`-ը։ +3. `o`-ն մեծ է քան `e`-ն։ Այստեղ համեմատթույունն ավարտվում է, քանի որ առաջին տողը ավելի մեծ է, քան երկրորդը։ + +```smart header="Ոչ իրական բառարան, այլ Unicode-ային հերթականություն" +Վերևում բերված համեմատության ալգորիթմը մոտավորապես համարժեք է բառարաններում կամ հեռախոսագրքերում օգտագործվողին, բայց նույնը չէ: + +Օրինակ՝ մեծատառ `"A"`-ն հավասար չէ փոքրատառ `"a"`-ին։ Ո՞ր մեկն է մեծ. փոքրատառ `"a"`-ն։ Ինչու՞, քանի որ փոքրատառ նշանը ունի ավելի մեծ ինդեքս կոդավորման աղյուսակում, որն օգտագործում է JavaScript-ը (Unicode)։ Մենք կվերադառնանք սրա մանրամասներին, և բխող հետևանքներին բաժնում։ +``` + +## Տարբեր տիպերի համեմատությունը + +Երբ համեմատությունը կատարվում է տարբեր տիպերի միջև, JavaScript-ը դրանք ձևափոխում է թվի: + +Օրինակ՝ + +```js run +alert( '2' > 1 ); // true, տողային '2' փոփոխականը ձևափոխվում է թվային 2-ի +alert( '01' == 1 ); // true, տողային '01' փոփոխականը ձևափոխվում է թվային 1-ի +``` + +Տրամաբանական արժեքների դեպքում `true`-ն ձևափոխվում է `1`-ի, իսկ `false`-ը՝ `0`-ի։ + +Օրինակ՝ + +```js run +alert( true == 1 ); // true +alert( false == 0 ); // true +``` + +````smart header="Հետաքրքիր հետևանք" +Միաժամանակ հնարավոր է․ + + - Երկու արժեքներ նույնն են։ + - Նրանցից մեկը տրամաբանական `true`-է, իսկ մյուսը տրամաբանական `false`-է։ + +Օրինակ՝ + +```js run +let a = 0; +alert( Boolean(a) ); // false + +let b = "0"; +alert( Boolean(b) ); // true + +alert(a == b); // true! +``` + +JavaScript-ի տեսանկյունից, այս արդյունքները համարվում են ճիշտ։ Տողային արժեքը ձևափոխվում է թվային տիպի (ուստի `"0"`-ն ձևափոխվում է `0`-ի), մինչդեռ բացահայտ `Boolean`-փոխակերպումը կիրառում է մեկ այլ կանոն։ +```` + +## Խիստ հավասարություն + +Սովորական հավասարության ստուգումը `==` խնդրահարույց է։ Այն չի կարող տարբերակել `0`-ն `false`-ից։ + +```js run +alert( 0 == false ); // true +``` + +Նույն բանը կատարվում է դատարկ տողի հետ։ + +```js run +alert( '' == false ); // true +``` + +Սա պատահում է, քանի որ `==` գործողությանը տարբեր տիպերի համեմատման ժամանակ արժեքները ձևափոխում է թվերի։ Դատարկ տողը, ինչպես `false`-ը, դառնում է զրո։ + +Ի՞նչ պետք է անել, եթե ցանկանում ենք տարբերակել `0`-ն `false`-ից։ + +**Խիստ համեմատության գործողությունը `===` ստուգում է հավասարությունը առանց տիպային կերպափոխումների:** + +Այլ կերպ ասած, եթե `a`-ն և `b`-ն տարբերի տիպերի են, ապա `a === b`-ն անմիջապես կվերադարձնի `false`, առանց տիպերի կերպափոխման։ + +Եկեք փորձենք դա․ + +```js run +alert(0 === false); // false, քանի որ արժեքները ունեն տարբեր տիպեր +``` + +Գոյություն ունի "խիստ ոչ հավասար" գործողության նշանը `!==`, որը նման է `!=`-ին։ + +Խիստ հավասարության օպերատորը մի փոքր ավելի երկար է գրվում, բայց ակնհայտ է դառնում այն, որ դա ավելի քիչ է սխալներ առաջացնում։ + +## Համեմատություն null-ի և undefined-ի հետ + +Գոյություն ունի ոչ ինտուիտիվ պահվածք, երբ `null`-ը կամ `undefined`-ը համեմատվում են այլ արժեքների հետ։ + +Խիստ համեմատության `===` դեպքում․ +այս արժեքները տարբեր են, քանի որ նրանցից մեկը ունի այլ տիպ + + ```js run + alert( null === undefined ); // false + ``` + +Ոչ խիստ համեմատության `==` դեպքում․ +Գոյություն ունի հատուկ կանոն։ Այս երկուսը համահունչ են, դրանք հավասար են իրար հետ, բայց հավասար չեն ոչ արժեքներով։ + + ```js run + alert( null == undefined ); // true + ``` + +Մաթեմատիկական և այլհամեմատություններում `< > <= >=`․ +`null/undefined`-ը ձևափոխվում են թվերի, `null`-ը ձևափոխվում է `0`-ի, մինչդեռ `undefined`-ը ձևափոխվում է `NaN`-ի։ + +Այժմ եկեք դիտարկենք մի քանի հետաքրքիր բաներ, որոնք տեղի են ունենում, երբ կիրառում ենք այս կանոնները: Եվ, որ ավելի կարևոր է, ինչպես չնկնել այդ ծուղակը։ + +### Տարօրինակ արդյունք․ null vs 0 + +Եկեք համեմատենք `null`-ը զրոյի հետ․ + +```js run +alert( null > 0 ); // (1) false +alert( null == 0 ); // (2) false +alert( null >= 0 ); // (3) *!*true*/!* +``` + +Մաթեմատիկորեն, դա տարօրինակ է։ Վերջին արդյունքը, որ «`null`-ը մեծ կամ հավասար է զրոյի», համեմատություններից գոնե մեկը պետք է լինի `true`, բայց երկուսն էլ սխալ են։ + +Դրա պատճառըն այն է, որ `==` և `> < >= <=` գործողությունները գործում են տարբեր։ Հավասարության ստուգումը կերպափոխում է `null`-ը թվի, այսինքն՝ 0-ի։ Ահա թե ինչու `null >= 0`-ն ճիշտ է, իսկ `null > 0`-ը սխալ։ + +Մյուս կողմից, `==`-ը ստուգում է `undefined`-ը և `null`-ը առանց ձևափոխման, դրանք հավասար են միմյանց, բայց հավասար չեն այլ փոփոխականի, ահա թե ինչու `null == 0` արտահայտությունը արժեքը `false` է: + +### Անհամեմատելի undefined-ը + +`undefined` արժեքը չի կարող համեմատվել այլ արժեքների հետ․ + +```js run +alert( undefined > 0 ); // false (1) +alert( undefined < 0 ); // false (2) +alert( undefined == 0 ); // false (3) +``` + +Ինչու՞ այն նման չէ զրոյին, համեմատության արդյունքը միշտ կեղծ է: + +Մենք ստանում ենք այս արդյունքը, քանի որ․ + +- Համեմատություններ `(1)`-ը և `(2)`-ը վերադարձնում են `false`, քանի որ `undefined`-ը ձևափոխվում է `NaN`-ի, իսկ `NaN`-ը հատուկ թվային արժեք է, որը բոլոր համեմատությունների դեպքում վերադարձնում է `false`։ +- Համեմատություն `(3)`-ը վերադարձնում է `false`, քանի որ `undefined`-ը հավասար է միայն `null`-ին և `undefined`-ին, և ոչ մի այլ արժեքի: + +### Խուսափեք խնդիրներից + +Ինչու՞ դիտարկեցինք այս օրինակները: Պե՞տք է անընդհատ հիշել այս առանձնահատկությունները: Դե, իրականում ոչ: Փաստորեն, նմանատիպ բարդությունները ժամանակի ընթացքում աստիճանաբար հայտնի կդառնան, բայց այդ խնդիրներից խուսափելու հնարավոր միջոցներ կան. + +- Ցանկացած համեմատությանը `undefined/null`-ի հետ, բացի խիստ հավասարությունից, հարկավոր է հատուկ ուշադրություն։ +- Մի օգտագործեք `>= > < <=` համեմատությունները այն արժեքների հետ, որոնք կարող են լինել `null/undefined`, եթե իսկապես վստահ չեք, թե ինչ եք անում։ Եթե փոփոխականը կարող է ունենալ այս արժեքները, ստուգեք դրանք առանձին: + +## Ամփոփում + +- Համեմատման գործողությունները վերադարձնում են տրամաբանական արժեք: +- Տողերը համեմատվում են տառ առ տառ, բառարանային հերթականությամբ: +- Երբ տարբեր տիպեր ունեցող արժեքները համեմատվում են, դրանք ձևափոխվում են թվերի (բացառությամբ խիստ հավասարության համեմատության): +- `null` և `undefined` արժեքները հավասար են (`==`) մեկը մյուսին և հավասար չեն որևէ այլ արժեքի: +- Զգուշացեք օգտագործել այնպիսի համեմատություններ ինչպիսիք են `>`, կամ `<` այն փոփոխականների հետ, որոնք կարող են լինել `null/undefined`։ Լավ գաղափար է `null/undefined`-ը ստուգել առանձին։ diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md index 51f1d4680..406ff73c0 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/solution.md @@ -1,12 +1,12 @@ -**Yes, it will.** +**Այո, կցուցադրվի:** -Any string except an empty one (and `"0"` is not empty) becomes `true` in the logical context. +Ցանկացած տող, բացի դատարկից (իսկ `"0"`-ն դատարկ չէ) տրամաբանական համատեքստում դառնում է `true`: -We can run and check: +Մենք կարող ենք գործարկել և ստուգել. ```js run if ("0") { - alert( 'Hello' ); + alert( 'Ողջույն' ); } ``` diff --git a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md index 5f16cda85..88820ffa5 100644 --- a/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md +++ b/1-js/02-first-steps/10-ifelse/1-if-zero-string/task.md @@ -1,14 +1,14 @@ -importance: 5 +կարևորությունը՝ 5 --- -# if (a string with zero) +# if (զրոյով տող) -Will `alert` be shown? +Կցուցադրվի՞ արդյոք `alert`-ը: ```js if ("0") { - alert( 'Hello' ); + alert( 'Ողջույն' ); } ``` diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg index ea4d122f6..422be45cc 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2.svg @@ -1 +1 @@ -BeginYou don't know? “ECMAScript”!Right!What's the “official” name of JavaScript?OtherECMAScript \ No newline at end of file +BeginYou don't know? “ECMAScript”!Right!What's the “official” name of JavaScript?OtherECMAScript diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html index ea9966653..e9cda3bf3 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/ifelse_task2/index.html @@ -5,12 +5,12 @@ diff --git a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md index a4d943245..ce72e7cce 100644 --- a/1-js/02-first-steps/10-ifelse/2-check-standard/task.md +++ b/1-js/02-first-steps/10-ifelse/2-check-standard/task.md @@ -1,12 +1,12 @@ -importance: 2 +կարևորությունը՝ 2 --- -# The name of JavaScript +# JavaScript-ի անվանումը -Using the `if..else` construct, write the code which asks: 'What is the "official" name of JavaScript?' +Օգտագործելով `if..else` կառուցվածքը, գրեք կոդ, որը կհարցնի. «Ո՞րն է JavaScript-ի «պաշտոնական» անվանումը:»։ -If the visitor enters "ECMAScript", then output "Right!", otherwise -- output: "Didn't know? ECMAScript!" +Եթե այցելուն մուտքագրում է «ECMAScript», ապա արտատպեք «Ճիշտ է:», հակառակ դեպքում՝ արտատպեք «Դուք չգիտե՞ք. ECMAScript:»: ![](ifelse_task2.svg) diff --git a/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html b/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html index f168360dd..3a7684e0b 100644 --- a/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html +++ b/1-js/02-first-steps/10-ifelse/3-sign/if_sign/index.html @@ -6,7 +6,7 @@ + +Function handler is called on this input: +
+ + +

+ +Debounced function debounce(handler, 1000) is called on this input: +
+ + +

+ + + \ No newline at end of file diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md index 4f5867ded..83e75f315 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/solution.md @@ -1,28 +1,13 @@ ```js demo -function debounce(f, ms) { - - let isCooldown = false; - +function debounce(func, ms) { + let timeout; return function() { - if (isCooldown) return; - - f.apply(this, arguments); - - isCooldown = true; - - setTimeout(() => isCooldown = false, ms); + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, arguments), ms); }; - } -``` - -A call to `debounce` returns a wrapper. There may be two states: -- `isCooldown = false` -- ready to run. -- `isCooldown = true` -- waiting for the timeout. - -In the first call `isCooldown` is falsy, so the call proceeds, and the state changes to `true`. +``` -While `isCooldown` is true, all other calls are ignored. +A call to `debounce` returns a wrapper. When called, it schedules the original function call after given `ms` and cancels the previous such timeout. -Then `setTimeout` reverts it to `false` after the given delay. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md index 2620f1c71..5b0fcc5f8 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/03-debounce/task.md @@ -4,21 +4,48 @@ importance: 5 # Debounce decorator -The result of `debounce(f, ms)` decorator should be a wrapper that passes the call to `f` at maximum once per `ms` milliseconds. +The result of `debounce(f, ms)` decorator is a wrapper that suspends calls to `f` until there's `ms` milliseconds of inactivity (no calls, "cooldown period"), then invokes `f` once with the latest arguments. -In other words, when we call a "debounced" function, it guarantees that all future calls to the function made less than `ms` milliseconds after the previous call will be ignored. +In other words, `debounce` is like a secretary that accepts "phone calls", and waits until there's `ms` milliseconds of being quiet. And only then it transfers the latest call information to "the boss" (calls the actual `f`). -For instance: +For instance, we had a function `f` and replaced it with `f = debounce(f, 1000)`. -```js no-beautify -let f = debounce(alert, 1000); +Then if the wrapped function is called at 0ms, 200ms and 500ms, and then there are no calls, then the actual `f` will be only called once, at 1500ms. That is: after the cooldown period of 1000ms from the last call. -f(1); // runs immediately -f(2); // ignored +![](debounce.svg) -setTimeout( () => f(3), 100); // ignored ( only 100 ms passed ) -setTimeout( () => f(4), 1100); // runs -setTimeout( () => f(5), 1500); // ignored (less than 1000 ms from the last run) +...And it will get the arguments of the very last call, other calls are ignored. + +Here's the code for it (uses the debounce decorator from the [Lodash library](https://lodash.com/docs/4.17.15#debounce)): + +```js +let f = _.debounce(alert, 1000); + +f("a"); +setTimeout( () => f("b"), 200); +setTimeout( () => f("c"), 500); +// debounced function waits 1000ms after the last call and then runs: alert("c") +``` + +Now a practical example. Let's say, the user types something, and we'd like to send a request to the server when the input is finished. + +There's no point in sending the request for every character typed. Instead we'd like to wait, and then process the whole result. + +In a web-browser, we can setup an event handler -- a function that's called on every change of an input field. Normally, an event handler is called very often, for every typed key. But if we `debounce` it by 1000ms, then it will be only called once, after 1000ms after the last input. + +```online + +In this live example, the handler puts the result into a box below, try it: + +[iframe border=1 src="https://github.com/debounce" height=200] + +See? The second input calls the debounced function, so its content is processed after 1000ms from the last input. ``` -In practice `debounce` is useful for functions that retrieve/update something when we know that nothing new can be done in such a short period of time, so it's better not to waste resources. +So, `debounce` is a great way to process a sequence of events: be it a sequence of key presses, mouse movements or something else. + +It waits the given time after the last call, and then runs its function, that can process the result. + +The task is to implement `debounce` decorator. + +Hint: that's just a few lines if you think about it :) diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js index 5339c8d11..e671438f6 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/_js.view/test.js @@ -7,8 +7,8 @@ describe("throttle(f, 1000)", function() { } before(function() { - f1000 = throttle(f, 1000); this.clock = sinon.useFakeTimers(); + f1000 = throttle(f, 1000); }); it("the first call runs now", function() { @@ -44,4 +44,20 @@ describe("throttle(f, 1000)", function() { this.clock.restore(); }); -}); \ No newline at end of file +}); + +describe('throttle', () => { + + it('runs a forwarded call once', done => { + let log = ''; + const f = str => log += str; + const f10 = throttle(f, 10); + f10('once'); + + setTimeout(() => { + assert.equal(log, 'once'); + done(); + }, 20); + }); + +}); diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md index c844016d3..6950664be 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/solution.md @@ -12,11 +12,10 @@ function throttle(func, ms) { savedThis = this; return; } + isThrottled = true; func.apply(this, arguments); // (1) - isThrottled = true; - setTimeout(function() { isThrottled = false; // (3) if (savedArgs) { @@ -33,7 +32,7 @@ function throttle(func, ms) { A call to `throttle(func, ms)` returns `wrapper`. 1. During the first call, the `wrapper` just runs `func` and sets the cooldown state (`isThrottled = true`). -2. In this state all calls memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call. -3. ...Then after `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`). And if we had ignored calls, then `wrapper` is executed with last memorized arguments and context. +2. In this state all calls are memorized in `savedArgs/savedThis`. Please note that both the context and the arguments are equally important and should be memorized. We need them simultaneously to reproduce the call. +3. After `ms` milliseconds pass, `setTimeout` triggers. The cooldown state is removed (`isThrottled = false`) and, if we had ignored calls, `wrapper` is executed with the last memorized arguments and context. The 3rd step runs not `func`, but `wrapper`, because we not only need to execute `func`, but once again enter the cooldown state and setup the timeout to reset it. diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md index 567c9ce7a..cbd473196 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/04-throttle/task.md @@ -4,16 +4,21 @@ importance: 5 # Throttle decorator -Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper, passing the call to `f` at maximum once per `ms` milliseconds. Those calls that fall into the "cooldown" period, are ignored. +Create a "throttling" decorator `throttle(f, ms)` -- that returns a wrapper. -**The difference with `debounce` -- if an ignored call is the last during the cooldown, then it executes at the end of the delay.** +When it's called multiple times, it passes the call to `f` at maximum once per `ms` milliseconds. + +Compared to the debounce decorator, the behavior is completely different: +- `debounce` runs the function once after the "cooldown" period. Good for processing the final result. +- `throttle` runs it not more often than given `ms` time. Good for regular updates that shouldn't be very often. + +In other words, `throttle` is like a secretary that accepts phone calls, but bothers the boss (calls the actual `f`) not more often than once per `ms` milliseconds. Let's check the real-life application to better understand that requirement and to see where it comes from. **For instance, we want to track mouse movements.** -In browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). - +In a browser we can setup a function to run at every mouse movement and get the pointer location as it moves. During an active mouse usage, this function usually runs very frequently, can be something like 100 times per second (every 10 ms). **We'd like to update some information on the web-page when the pointer moves.** ...But updating function `update()` is too heavy to do it on every micro-movement. There is also no sense in updating more often than once per 100ms. @@ -31,8 +36,8 @@ A code example: ```js function f(a) { - console.log(a) -}; + console.log(a); +} // f1000 passes calls to f at maximum once per 1000 ms let f1000 = throttle(f, 1000); diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/article.md b/1-js/06-advanced-functions/09-call-apply-decorators/article.md index 8536cf314..c5d785493 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/article.md +++ b/1-js/06-advanced-functions/09-call-apply-decorators/article.md @@ -36,11 +36,11 @@ function cachingDecorator(func) { slow = cachingDecorator(slow); -alert( slow(1) ); // slow(1) is cached -alert( "Again: " + slow(1) ); // the same +alert( slow(1) ); // slow(1) is cached and the result returned +alert( "Again: " + slow(1) ); // slow(1) result returned from cache -alert( slow(2) ); // slow(2) is cached -alert( "Again: " + slow(2) ); // the same as the previous line +alert( slow(2) ); // slow(2) is cached and the result returned +alert( "Again: " + slow(2) ); // slow(2) result returned from cache ``` In the code above `cachingDecorator` is a *decorator*: a special function that takes another function and alters its behavior. @@ -75,7 +75,7 @@ let worker = { }, slow(x) { - // actually, there can be a scary CPU-heavy task here + // scary CPU-heavy task here alert("Called with " + x); return x * this.someMethod(); // (*) } @@ -149,8 +149,8 @@ let user = { name: "John" }; let admin = { name: "Admin" }; // use call to pass different objects as "this" -sayHi.call( user ); // this = John -sayHi.call( admin ); // this = Admin +sayHi.call( user ); // John +sayHi.call( admin ); // Admin ``` And here we use `call` to call `say` with the given context and phrase: @@ -209,7 +209,7 @@ To make it all clear, let's see more deeply how `this` is passed along: 2. So when `worker.slow(2)` is executed, the wrapper gets `2` as an argument and `this=worker` (it's the object before dot). 3. Inside the wrapper, assuming the result is not yet cached, `func.call(this, x)` passes the current `this` (`=worker`) and the current argument (`=2`) to the original method. -## Going multi-argument with "func.apply" +## Going multi-argument Now let's make `cachingDecorator` even more universal. Till now it was working only with single-argument functions. @@ -236,7 +236,7 @@ There are many solutions possible: For many practical applications, the 3rd variant is good enough, so we'll stick to it. -Also we need to replace `func.call(this, x)` with `func.call(this, ...arguments)`, to pass all arguments to the wrapped function call, not just the first one. +Also we need to pass not just `x`, but all arguments in `func.call`. Let's recall that in a `function()` we can get a pseudo-array of its arguments as `arguments`, so `func.call(this, x)` should be replaced with `func.call(this, ...arguments)`. Here's a more powerful `cachingDecorator`: @@ -284,6 +284,8 @@ There are two changes: - In the line `(*)` it calls `hash` to create a single key from `arguments`. Here we use a simple "joining" function that turns arguments `(3, 5)` into the key `"3,5"`. More complex cases may require other hashing functions. - Then `(**)` uses `func.call(this, ...arguments)` to pass both the context and all arguments the wrapper got (not just the first one) to the original function. +## func.apply + Instead of `func.call(this, ...arguments)` we could use `func.apply(this, arguments)`. The syntax of built-in method [func.apply](mdn:js/Function/apply) is: @@ -299,18 +301,18 @@ The only syntax difference between `call` and `apply` is that `call` expects a l So these two calls are almost equivalent: ```js -func.call(context, ...args); // pass an array as list with spread operator -func.apply(context, args); // is same as using apply +func.call(context, ...args); +func.apply(context, args); ``` -There's only a minor difference: +They perform the same call of `func` with given context and arguments. -- The spread operator `...` allows to pass *iterable* `args` as the list to `call`. -- The `apply` accepts only *array-like* `args`. +There's only a subtle difference regarding `args`: -So, these calls complement each other. Where we expect an iterable, `call` works, where we expect an array-like, `apply` works. +- The spread syntax `...` allows to pass *iterable* `args` as the list to `call`. +- The `apply` accepts only *array-like* `args`. -And for objects that are both iterable and array-like, like a real array, we technically could use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. +...And for objects that are both iterable and array-like, such as a real array, we can use any of them, but `apply` will probably be faster, because most JavaScript engines internally optimize it better. Passing all arguments along with the context to another function is called *call forwarding*. @@ -344,7 +346,7 @@ function hash(args) { } ``` -...Unfortunately, that won't work. Because we are calling `hash(arguments)` and `arguments` object is both iterable and array-like, but not a real array. +...Unfortunately, that won't work. Because we are calling `hash(arguments)`, and `arguments` object is both iterable and array-like, but not a real array. So calling `join` on it would fail, as we can see below: @@ -372,7 +374,7 @@ hash(1, 2); The trick is called *method borrowing*. -We take (borrow) a join method from a regular array `[].join`. And use `[].join.call` to run it in the context of `arguments`. +We take (borrow) a join method from a regular array (`[].join`) and use `[].join.call` to run it in the context of `arguments`. Why does it work? diff --git a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg index 5fc7743f0..9b63cb982 100644 --- a/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg +++ b/1-js/06-advanced-functions/09-call-apply-decorators/decorator-makecaching-wrapper.svg @@ -1 +1 @@ -wrapperaround the function \ No newline at end of file +wrapperaround the function \ No newline at end of file diff --git a/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md index f8b83d7a2..c90851c2b 100644 --- a/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md +++ b/1-js/06-advanced-functions/10-bind/6-ask-partial/task.md @@ -8,7 +8,7 @@ The task is a little more complex variant of . The `user` object was modified. Now instead of two functions `loginOk/loginFail`, it has a single function `user.login(true/false)`. -What to pass `askPassword` in the code below, so that it calls `user.login(true)` as `ok` and `user.login(false)` as `fail`? +What should we pass `askPassword` in the code below, so that it calls `user.login(true)` as `ok` and `user.login(false)` as `fail`? ```js function askPassword(ok, fail) { diff --git a/1-js/06-advanced-functions/10-bind/article.md b/1-js/06-advanced-functions/10-bind/article.md index b8c545b1c..6d65e7dd1 100644 --- a/1-js/06-advanced-functions/10-bind/article.md +++ b/1-js/06-advanced-functions/10-bind/article.md @@ -83,10 +83,12 @@ let user = { setTimeout(() => user.sayHi(), 1000); -// ...within 1 second -user = { sayHi() { alert("Another user in setTimeout!"); } }; +// ...the value of user changes within 1 second +user = { + sayHi() { alert("Another user in setTimeout!"); } +}; -// Another user in setTimeout?!? +// Another user in setTimeout! ``` The next solution guarantees that such thing won't happen. @@ -98,7 +100,7 @@ Functions provide a built-in method [bind](mdn:js/Function/bind) that allows to The basic syntax is: ```js -// more complex syntax will be little later +// more complex syntax will come a little later let boundFunc = func.bind(context); ``` @@ -159,9 +161,16 @@ let user = { let sayHi = user.sayHi.bind(user); // (*) */!* +// can run it without an object sayHi(); // Hello, John! setTimeout(sayHi, 1000); // Hello, John! + +// even if the value of user changes within 1 second +// sayHi uses the pre-bound value which is reference to the old user object +user = { + sayHi() { alert("Another user in setTimeout!"); } +}; ``` In the line `(*)` we take the method `user.sayHi` and bind it to `user`. The `sayHi` is a "bound" function, that can be called alone or passed to `setTimeout` -- doesn't matter, the context will be right. @@ -178,8 +187,8 @@ let user = { let say = user.say.bind(user); -say("Hello"); // Hello, John ("Hello" argument is passed to say) -say("Bye"); // Bye, John ("Bye" is passed to say) +say("Hello"); // Hello, John! ("Hello" argument is passed to say) +say("Bye"); // Bye, John! ("Bye" is passed to say) ``` ````smart header="Convenience method: `bindAll`" @@ -193,7 +202,7 @@ for (let key in user) { } ``` -JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(obj)](http://lodash.com/docs#bindAll) in lodash. +JavaScript libraries also provide functions for convenient mass binding , e.g. [_.bindAll(object, methodNames)](https://lodash.com/docs#bindAll) in lodash. ```` ## Partial functions @@ -238,7 +247,7 @@ The call to `mul.bind(null, 2)` creates a new function `double` that passes call That's called [partial function application](https://en.wikipedia.org/wiki/Partial_application) -- we create a new function by fixing some parameters of the existing one. -Please note that here we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. +Please note that we actually don't use `this` here. But `bind` requires it, so we must put in something like `null`. The function `triple` in the code below triples the value: @@ -270,7 +279,7 @@ What if we'd like to fix some arguments, but not the context `this`? For example The native `bind` does not allow that. We can't just omit the context and jump to arguments. -Fortunately, a helper function `partial` for binding only arguments can be easily implemented. +Fortunately, a function `partial` for binding only arguments can be easily implemented. Like this: @@ -304,7 +313,7 @@ The result of `partial(func[, arg1, arg2...])` call is a wrapper `(*)` that call - Then gives it `...argsBound` -- arguments from the `partial` call (`"10:00"`) - Then gives it `...args` -- arguments given to the wrapper (`"Hello"`) -So easy to do it with the spread operator, right? +So easy to do it with the spread syntax, right? Also there's a ready [_.partial](https://lodash.com/docs#partial) implementation from lodash library. diff --git a/1-js/06-advanced-functions/12-arrow-functions/article.md b/1-js/06-advanced-functions/12-arrow-functions/article.md index 1d6b04394..8730277ad 100644 --- a/1-js/06-advanced-functions/12-arrow-functions/article.md +++ b/1-js/06-advanced-functions/12-arrow-functions/article.md @@ -52,7 +52,7 @@ let group = { *!* this.students.forEach(function(student) { // Error: Cannot read property 'title' of undefined - alert(this.title + ': ' + student) + alert(this.title + ': ' + student); }); */!* } @@ -87,7 +87,7 @@ For instance, `defer(f, ms)` gets a function and returns a wrapper around it tha ```js run function defer(f, ms) { return function() { - setTimeout(() => f.apply(this, arguments), ms) + setTimeout(() => f.apply(this, arguments), ms); }; } @@ -118,9 +118,9 @@ Here we had to create additional variables `args` and `ctx` so that the function Arrow functions: -- Do not have `this`. -- Do not have `arguments`. -- Can't be called with `new`. -- (They also don't have `super`, but we didn't study it. Will be in the chapter ). +- Do not have `this` +- Do not have `arguments` +- Can't be called with `new` +- They also don't have `super`, but we didn't study it yet. We will on the chapter -That's because they are meant for short pieces of code that do not have their own "context", but rather works in the current one. And they really shine in that use case. +That's because they are meant for short pieces of code that do not have their own "context", but rather work in the current one. And they really shine in that use case. diff --git a/1-js/07-object-properties/01-property-descriptors/article.md b/1-js/07-object-properties/01-property-descriptors/article.md index 9f8f85d9c..0a945b377 100644 --- a/1-js/07-object-properties/01-property-descriptors/article.md +++ b/1-js/07-object-properties/01-property-descriptors/article.md @@ -3,7 +3,7 @@ As we know, objects can store properties. -Till now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing. +Until now, a property was a simple "key-value" pair to us. But an object property is actually a more flexible and powerful thing. In this chapter we'll study additional configuration options, and in the next we'll see how to invisibly turn them into getter/setter functions. @@ -19,7 +19,7 @@ We didn't see them yet, because generally they do not show up. When we create a First, let's see how to get those flags. -The method [Object.getOwnPropertyDescriptor](mdn:js/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. +The method [Object.getOwnPropertyDescriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor) allows to query the *full* information about a property. The syntax is: ```js @@ -54,7 +54,7 @@ alert( JSON.stringify(descriptor, null, 2 ) ); */ ``` -To change the flags, we can use [Object.defineProperty](mdn:js/Object/defineProperty). +To change the flags, we can use [Object.defineProperty](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). The syntax is: @@ -66,7 +66,7 @@ Object.defineProperty(obj, propertyName, descriptor) : The object and its property to apply the descriptor. `descriptor` -: Property descriptor to apply. +: Property descriptor object to apply. If the property exists, `defineProperty` updates its flags. Otherwise, it creates the property with the given value and flags; in that case, if a flag is not supplied, it is assumed `false`. @@ -123,7 +123,7 @@ user.name = "Pete"; // Error: Cannot assign to read only property 'name' Now no one can change the name of our user, unless they apply their own `defineProperty` to override ours. ```smart header="Errors appear only in strict mode" -In the non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. +In non-strict mode, no errors occur when writing to non-writable properties and such. But the operation still won't succeed. Flag-violating actions are just silently ignored in non-strict. ``` Here's the same example, but the property is created from scratch: @@ -134,7 +134,7 @@ let user = { }; Object.defineProperty(user, "name", { *!* value: "John", - // for new properties need to explicitly list what's true + // for new properties we need to explicitly list what's true enumerable: true, configurable: true */!* @@ -148,7 +148,7 @@ user.name = "Pete"; // Error Now let's add a custom `toString` to `user`. -Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add `toString` of our own, then by default it shows up in `for..in`, like this: +Normally, a built-in `toString` for objects is non-enumerable, it does not show up in `for..in`. But if we add a `toString` of our own, then by default it shows up in `for..in`, like this: ```js run let user = { @@ -162,7 +162,7 @@ let user = { for (let key in user) alert(key); // name, toString ``` -If we don't like it, then we can set `enumerable:false`. Then it won't appear in `for..in` loop, just like the built-in one: +If we don't like it, then we can set `enumerable:false`. Then it won't appear in a `for..in` loop, just like the built-in one: ```js run let user = { @@ -194,7 +194,7 @@ alert(Object.keys(user)); // name The non-configurable flag (`configurable:false`) is sometimes preset for built-in objects and properties. -A non-configurable property can not be deleted. +A non-configurable property can't be deleted, its attributes can't be modified. For instance, `Math.PI` is non-writable, non-enumerable and non-configurable: @@ -214,49 +214,67 @@ alert( JSON.stringify(descriptor, null, 2 ) ); So, a programmer is unable to change the value of `Math.PI` or overwrite it. ```js run -Math.PI = 3; // Error +Math.PI = 3; // Error, because it has writable: false // delete Math.PI won't work either ``` +We also can't change `Math.PI` to be `writable` again: + +```js run +// Error, because of configurable: false +Object.defineProperty(Math, "PI", { writable: true }); +``` + +There's absolutely nothing we can do with `Math.PI`. + Making a property non-configurable is a one-way road. We cannot change it back with `defineProperty`. -To be precise, non-configurability imposes several restrictions on `defineProperty`: -1. Can't change `configurable` flag. -2. Can't change `enumerable` flag. -3. Can't change `writable: false` to `true` (the other way round works). -4. Can't change `get/set` for an accessor property (but can assign them if absent). +**Please note: `configurable: false` prevents changes of property flags and its deletion, while allowing to change its value.** -Here we are making `user.name` a "forever sealed" constant: +Here `user.name` is non-configurable, but we can still change it (as it's writable): ```js run -let user = { }; +let user = { + name: "John" +}; + +Object.defineProperty(user, "name", { + configurable: false +}); + +user.name = "Pete"; // works fine +delete user.name; // Error +``` + +And here we make `user.name` a "forever sealed" constant, just like the built-in `Math.PI`: + +```js run +let user = { + name: "John" +}; Object.defineProperty(user, "name", { - value: "John", writable: false, configurable: false }); -*!* // won't be able to change user.name or its flags // all this won't work: -// user.name = "Pete" -// delete user.name -// defineProperty(user, "name", { value: "Pete" }) -Object.defineProperty(user, "name", {writable: true}); // Error -*/!* +user.name = "Pete"; +delete user.name; +Object.defineProperty(user, "name", { value: "Pete" }); ``` -```smart header="\"Non-configurable\" doesn't mean \"non-writable\"" -Notable exception: a value of non-configurable, but writable property can be changed. +```smart header="The only attribute change possible: writable true -> false" +There's a minor exception about changing flags. -The idea of `configurable: false` is to prevent changes to property flags and its deletion, not changes to its value. +We can change `writable: true` to `false` for a non-configurable property, thus preventing its value modification (to add another layer of protection). Not the other way around though. ``` ## Object.defineProperties -There's a method [Object.defineProperties(obj, descriptors)](mdn:js/Object/defineProperties) that allows to define many properties at once. +There's a method [Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperties) that allows to define many properties at once. The syntax is: @@ -282,7 +300,7 @@ So, we can set many properties at once. ## Object.getOwnPropertyDescriptors -To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](mdn:js/Object/getOwnPropertyDescriptors). +To get all property descriptors at once, we can use the method [Object.getOwnPropertyDescriptors(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptors). Together with `Object.defineProperties` it can be used as a "flags-aware" way of cloning an object: @@ -300,7 +318,7 @@ for (let key in user) { ...But that does not copy flags. So if we want a "better" clone then `Object.defineProperties` is preferred. -Another difference is that `for..in` ignores symbolic properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic ones. +Another difference is that `for..in` ignores symbolic and non-enumerable properties, but `Object.getOwnPropertyDescriptors` returns *all* property descriptors including symbolic and non-enumerable ones. ## Sealing an object globally @@ -308,24 +326,24 @@ Property descriptors work at the level of individual properties. There are also methods that limit access to the *whole* object: -[Object.preventExtensions(obj)](mdn:js/Object/preventExtensions) +[Object.preventExtensions(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) : Forbids the addition of new properties to the object. -[Object.seal(obj)](mdn:js/Object/seal) +[Object.seal(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/seal) : Forbids adding/removing of properties. Sets `configurable: false` for all existing properties. -[Object.freeze(obj)](mdn:js/Object/freeze) +[Object.freeze(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze) : Forbids adding/removing/changing of properties. Sets `configurable: false, writable: false` for all existing properties. And also there are tests for them: -[Object.isExtensible(obj)](mdn:js/Object/isExtensible) +[Object.isExtensible(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isExtensible) : Returns `false` if adding properties is forbidden, otherwise `true`. -[Object.isSealed(obj)](mdn:js/Object/isSealed) +[Object.isSealed(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isSealed) : Returns `true` if adding/removing properties is forbidden, and all existing properties have `configurable: false`. -[Object.isFrozen(obj)](mdn:js/Object/isFrozen) +[Object.isFrozen(obj)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/isFrozen) : Returns `true` if adding/removing/changing properties is forbidden, and all current properties are `configurable: false, writable: false`. These methods are rarely used in practice. diff --git a/1-js/07-object-properties/02-property-accessors/article.md b/1-js/07-object-properties/02-property-accessors/article.md index dc541b6da..c2aa35d53 100644 --- a/1-js/07-object-properties/02-property-accessors/article.md +++ b/1-js/07-object-properties/02-property-accessors/article.md @@ -1,11 +1,11 @@ # Property getters and setters -There are two kinds of properties. +There are two kinds of object properties. -The first kind is *data properties*. We already know how to work with them. All properties that we've been using till now were data properties. +The first kind is *data properties*. We already know how to work with them. All properties that we've been using until now were data properties. -The second type of properties is something new. It's *accessor properties*. They are essentially functions that work on getting and setting a value, but look like regular properties to an external code. +The second type of property is something new. It's an *accessor property*. They are essentially functions that execute on getting and setting a value, but look like regular properties to an external code. ## Getters and setters @@ -53,7 +53,7 @@ alert(user.fullName); // John Smith */!* ``` -From outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. +From the outside, an accessor property looks like a regular one. That's the idea of accessor properties. We don't *call* `user.fullName` as a function, we *read* it normally: the getter runs behind the scenes. As of now, `fullName` has only a getter. If we attempt to assign `user.fullName=`, there will be an error: @@ -94,11 +94,7 @@ alert(user.name); // Alice alert(user.surname); // Cooper ``` -As the result, we have a "virtual" property `fullName`. It is readable and writable, but in fact does not exist. - -```smart header="No way to handle `delete`" -There's no similar method to handle deletion of an accessor property. Only getter/setter methods may exist. -``` +As the result, we have a "virtual" property `fullName`. It is readable and writable. ## Accessor descriptors @@ -138,7 +134,7 @@ alert(user.fullName); // John Smith for(let key in user) alert(key); // name, surname ``` -Please note once again that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. +Please note that a property can be either an accessor (has `get/set` methods) or a data property (has a `value`), not both. If we try to supply both `get` and `value` in the same descriptor, there will be an error: @@ -189,9 +185,9 @@ Technically, external code is able to access the name directly by using `user._n ## Using for compatibility -One of the great uses of accessors -- they allow to take control over a "regular" data property at any moment by replacing it with getter and setter and tweak its behavior. +One of the great uses of accessors is that they allow to take control over a "regular" data property at any moment by replacing it with a getter and a setter and tweak its behavior. -Imagine, we started implementing user objects using data properties `name` and `age`: +Imagine we started implementing user objects using data properties `name` and `age`: ```js function User(name, age) { diff --git a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md index 421b57e0a..bc2db47fe 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/2-search-algorithm/task.md @@ -6,7 +6,7 @@ importance: 5 The task has two parts. -We have objects: +Given the following objects: ```js let head = { diff --git a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md index b37499bad..ed8482c07 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/3-proto-and-this/task.md @@ -2,7 +2,7 @@ importance: 5 --- -# Where it writes? +# Where does it write? We have `rabbit` inheriting from `animal`. diff --git a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md index 6f9fb279e..50171123d 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md +++ b/1-js/08-prototypes/01-prototype-inheritance/4-hamster-proto/task.md @@ -2,11 +2,11 @@ importance: 5 --- -# Why two hamsters are full? +# Why are both hamsters full? We have two hamsters: `speedy` and `lazy` inheriting from the general `hamster` object. -When we feed one of them, the other one is also full. Why? How to fix it? +When we feed one of them, the other one is also full. Why? How can we fix it? ```js run let hamster = { diff --git a/1-js/08-prototypes/01-prototype-inheritance/article.md b/1-js/08-prototypes/01-prototype-inheritance/article.md index 593a0a016..ef6c7ffeb 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/article.md +++ b/1-js/08-prototypes/01-prototype-inheritance/article.md @@ -12,11 +12,11 @@ In JavaScript, objects have a special hidden property `[[Prototype]]` (as named ![prototype](object-prototype-empty.svg) -The prototype is a little bit "magical". When we want to read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, such thing is called "prototypal inheritance". Many cool language features and programming techniques are based on it. +When we read a property from `object`, and it's missing, JavaScript automatically takes it from the prototype. In programming, this is called "prototypal inheritance". And soon we'll study many examples of such inheritance, as well as cooler language features built upon it. The property `[[Prototype]]` is internal and hidden, but there are many ways to set it. -One of them is to use `__proto__`, like this: +One of them is to use the special name `__proto__`, like this: ```js run let animal = { @@ -27,19 +27,11 @@ let rabbit = { }; *!* -rabbit.__proto__ = animal; +rabbit.__proto__ = animal; // sets rabbit.[[Prototype]] = animal */!* ``` -```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" -Please note that `__proto__` is *not the same* as `[[Prototype]]`. That's a getter/setter for it. - -It exists for historical reasons, in modern language it is replaced with functions `Object.getPrototypeOf/Object.setPrototypeOf` that also get/set the prototype. We'll study the reasons for that and these functions later. - -By the specification, `__proto__` must only be supported by browsers, but in fact all environments including server-side support it. For now, as `__proto__` notation is a little bit more intuitively obvious, we'll use it in the examples. -``` - -If we look for a property in `rabbit`, and it's missing, JavaScript automatically takes it from `animal`. +Now if we read a property from `rabbit`, and it's missing, JavaScript will automatically take it from `animal`. For instance: @@ -62,7 +54,7 @@ alert( rabbit.eats ); // true (**) alert( rabbit.jumps ); // true ``` -Here the line `(*)` sets `animal` to be a prototype of `rabbit`. +Here the line `(*)` sets `animal` to be the prototype of `rabbit`. Then, when `alert` tries to read property `rabbit.eats` `(**)`, it's not in `rabbit`, so JavaScript follows the `[[Prototype]]` reference and finds it in `animal` (look from the bottom up): @@ -130,6 +122,8 @@ alert(longEar.jumps); // true (from rabbit) ![](proto-animal-rabbit-chain.svg) +Now if we read something from `longEar`, and it's missing, JavaScript will look for it in `rabbit`, and then in `animal`. + There are only two limitations: 1. The references can't go in circles. JavaScript will throw an error if we try to assign `__proto__` in a circle. @@ -137,6 +131,18 @@ There are only two limitations: Also it may be obvious, but still: there can be only one `[[Prototype]]`. An object may not inherit from two others. +```smart header="`__proto__` is a historical getter/setter for `[[Prototype]]`" +It's a common mistake of novice developers not to know the difference between these two. + +Please note that `__proto__` is *not the same* as the internal `[[Prototype]]` property. It's a getter/setter for `[[Prototype]]`. Later we'll see situations where it matters, for now let's just keep it in mind, as we build our understanding of JavaScript language. + +The `__proto__` property is a bit outdated. It exists for historical reasons, modern JavaScript suggests that we should use `Object.getPrototypeOf/Object.setPrototypeOf` functions instead that get/set the prototype. We'll also cover these functions later. + +By the specification, `__proto__` must only be supported by browsers. In fact though, all environments including server-side support `__proto__`, so we're quite safe using it. + +As the `__proto__` notation is a bit more intuitively obvious, we use it in the examples. +``` + ## Writing doesn't use prototype The prototype is only used for reading properties. @@ -197,13 +203,16 @@ alert(admin.fullName); // John Smith (*) // setter triggers! admin.fullName = "Alice Cooper"; // (**) + +alert(admin.fullName); // Alice Cooper, state of admin modified +alert(user.fullName); // John Smith, state of user protected ``` Here in the line `(*)` the property `admin.fullName` has a getter in the prototype `user`, so it is called. And in the line `(**)` the property has a setter in the prototype, so it is called. ## The value of "this" -An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where the properties `this.name` and `this.surname` are written: into `user` or `admin`? +An interesting question may arise in the example above: what's the value of `this` inside `set fullName(value)`? Where are the properties `this.name` and `this.surname` written: into `user` or `admin`? The answer is simple: `this` is not affected by prototypes at all. @@ -211,7 +220,7 @@ The answer is simple: `this` is not affected by prototypes at all. So, the setter call `admin.fullName=` uses `admin` as `this`, not `user`. -That is actually a super-important thing, because we may have a big object with many methods and inherit from it. Then inherited objects can run its methods, and they will modify the state of these objects, not the big one. +That is actually a super-important thing, because we may have a big object with many methods, and have objects that inherit from it. And when the inheriting objects run the inherited methods, they will modify only their own states, not the state of the big object. For instance, here `animal` represents a "method storage", and `rabbit` makes use of it. @@ -246,13 +255,13 @@ The resulting picture: ![](proto-animal-rabbit-walk-3.svg) -If we had other objects like `bird`, `snake` etc inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects. +If we had other objects, like `bird`, `snake`, etc., inheriting from `animal`, they would also gain access to methods of `animal`. But `this` in each method call would be the corresponding object, evaluated at the call-time (before dot), not `animal`. So when we write data into `this`, it is stored into these objects. As a result, methods are shared, but the object state is not. ## for..in loop -The `for..in` loops over inherited properties too. +The `for..in` loop iterates over inherited properties too. For instance: @@ -267,7 +276,7 @@ let rabbit = { }; *!* -// Object.keys only return own keys +// Object.keys only returns own keys alert(Object.keys(rabbit)); // jumps */!* @@ -277,7 +286,7 @@ for(let prop in rabbit) alert(prop); // jumps, then eats */!* ``` -If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](mdn:js/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. +If that's not what we want, and we'd like to exclude inherited properties, there's a built-in method [obj.hasOwnProperty(key)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty): it returns `true` if `obj` has its own (not inherited) property named `key`. So we can filter out inherited properties (or do something else with them): diff --git a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg index fe4c2cace..eb79c19ff 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/object-prototype-empty.svg @@ -1 +1 @@ -prototype objectobject[[Prototype]] \ No newline at end of file +prototype objectobject[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg index 3e81f262b..4bf580ae7 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-chain.svg @@ -1 +1 @@ -eats: true walk: functionanimaljumps: truerabbit[[Prototype]]earLength: 10longEar[[Prototype]] \ No newline at end of file +eats: true walk: functionanimaljumps: truerabbit[[Prototype]]earLength: 10longEar[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg index 3cf5c4c78..838c78395 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-2.svg @@ -1 +1 @@ -eats: true walk: functionanimalwalk: functionrabbit[[Prototype]] \ No newline at end of file +eats: true walk: functionanimalwalk: functionrabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg index acd420631..d791e5390 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk-3.svg @@ -1 +1 @@ -walk: function sleep: functionanimalrabbit[[Prototype]]name: "White Rabbit" isSleeping: true \ No newline at end of file +walk: function sleep: functionanimalrabbit[[Prototype]]name: "White Rabbit" isSleeping: true \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg index ebdef9587..b32471028 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit-walk.svg @@ -1 +1 @@ -eats: true walk: functionanimaljumps: truerabbit[[Prototype]] \ No newline at end of file +eats: true walk: functionanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg index 735e1f2b3..4f3c1bc0e 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-animal-rabbit.svg @@ -1 +1 @@ -eats: trueanimaljumps: truerabbit[[Prototype]] \ No newline at end of file +eats: trueanimaljumps: truerabbit[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg index 433bc613f..bf0baf013 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/proto-user-admin.svg @@ -1 +1 @@ -name: "John" surname: "Smith" set fullName: functionisAdmin: true name: "Alice" surname: "Cooper"useradmin[[Prototype]] \ No newline at end of file +name: "John" surname: "Smith" set fullName: functionisAdmin: true name: "Alice" surname: "Cooper"useradmin[[Prototype]] \ No newline at end of file diff --git a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg index d32585b43..32a9858f8 100644 --- a/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg +++ b/1-js/08-prototypes/01-prototype-inheritance/rabbit-animal-object.svg @@ -1 +1 @@ -toString: function hasOwnProperty: function ...Object.prototypeanimal[[Prototype]][[Prototype]][[Prototype]]nulleats: truerabbitjumps: true \ No newline at end of file +toString: function hasOwnProperty: function ...Object.prototypeanimal[[Prototype]][[Prototype]][[Prototype]]nulleats: truerabbitjumps: true \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md index 4b8522d3d..2838c125a 100644 --- a/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md +++ b/1-js/08-prototypes/02-function-prototype/1-changing-prototype/task.md @@ -20,7 +20,7 @@ alert( rabbit.eats ); // true ``` -1. We added one more string (emphasized), what `alert` shows now? +1. We added one more string (emphasized). What will `alert` show now? ```js function Rabbit() {} @@ -54,7 +54,7 @@ alert( rabbit.eats ); // true alert( rabbit.eats ); // ? ``` -3. Like this (replaced one line)? +3. And like this (replaced one line)? ```js function Rabbit() {} diff --git a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md index 355722403..372d50dd6 100644 --- a/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md +++ b/1-js/08-prototypes/02-function-prototype/4-new-object-same-constructor/solution.md @@ -15,7 +15,7 @@ alert( user2.name ); // Pete (worked!) It worked, because `User.prototype.constructor == User`. -..But if someone, so to speak, overwrites `User.prototype` and forgets to recreate `"constructor"`, then it would fail. +..But if someone, so to speak, overwrites `User.prototype` and forgets to recreate `constructor` to reference `User`, then it would fail. For instance: @@ -38,7 +38,12 @@ Why `user2.name` is `undefined`? Here's how `new user.constructor('Pete')` works: 1. First, it looks for `constructor` in `user`. Nothing. -2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has nothing. -3. The value of `User.prototype` is a plain object `{}`, its prototype is `Object.prototype`. And there is `Object.prototype.constructor == Object`. So it is used. +2. Then it follows the prototype chain. The prototype of `user` is `User.prototype`, and it also has no `constructor` (because we "forgot" to set it right!). +3. Going further up the chain, `User.prototype` is a plain object, its prototype is the built-in `Object.prototype`. +4. Finally, for the built-in `Object.prototype`, there's a built-in `Object.prototype.constructor == Object`. So it is used. -At the end, we have `let user2 = new Object('Pete')`. The built-in `Object` constructor ignores arguments, it always creates an empty object -- that's what we have in `user2` after all. +Finally, at the end, we have `let user2 = new Object('Pete')`. + +Probably, that's not what we want. We'd like to create `new User`, not `new Object`. That's the outcome of the missing `constructor`. + +(Just in case you're curious, the `new Object(...)` call converts its argument to an object. That's a theoretical thing, in practice no one calls `new Object` with a value, and generally we don't use `new Object` to make objects at all). \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/article.md b/1-js/08-prototypes/02-function-prototype/article.md index 29b3773eb..b1ef51826 100644 --- a/1-js/08-prototypes/02-function-prototype/article.md +++ b/1-js/08-prototypes/02-function-prototype/article.md @@ -2,7 +2,7 @@ Remember, new objects can be created with a constructor function, like `new F()`. -If `F.prototype` is an object, then `new` operator uses it to set `[[Prototype]]` for the new object. +If `F.prototype` is an object, then the `new` operator uses it to set `[[Prototype]]` for the new object. ```smart JavaScript had prototypal inheritance from the beginning. It was one of the core features of the language. @@ -41,7 +41,7 @@ That's the resulting picture: On the picture, `"prototype"` is a horizontal arrow, meaning a regular property, and `[[Prototype]]` is vertical, meaning the inheritance of `rabbit` from `animal`. ```smart header="`F.prototype` only used at `new F` time" -`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. After that, there's no connection between `F.prototype` and the new object. Think of it as a "one-time gift". +`F.prototype` property is only used when `new F` is called, it assigns `[[Prototype]]` of the new object. If, after the creation, `F.prototype` property changes (`F.prototype = `), then new objects created by `new F` will have another object as `[[Prototype]]`, but already existing objects keep the old one. ``` @@ -158,9 +158,9 @@ Rabbit.prototype = { In this chapter we briefly described the way of setting a `[[Prototype]]` for objects created via a constructor function. Later we'll see more advanced programming patterns that rely on it. -Everything is quite simple, just few notes to make things clear: +Everything is quite simple, just a few notes to make things clear: -- The `F.prototype` property (don't mess with `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called. +- The `F.prototype` property (don't mistake it for `[[Prototype]]`) sets `[[Prototype]]` of new objects when `new F()` is called. - The value of `F.prototype` should be either an object or `null`: other values won't work. - The `"prototype"` property only has such a special effect when set on a constructor function, and invoked with `new`. diff --git a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg index 35cdc61f1..59d60b397 100644 --- a/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg +++ b/1-js/08-prototypes/02-function-prototype/function-prototype-constructor.svg @@ -1 +1 @@ -Rabbitprototypeconstructordefault "prototype" \ No newline at end of file +Rabbitprototypeconstructordefault "prototype" \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg index 3489ecdd8..ede4e1227 100644 --- a/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg +++ b/1-js/08-prototypes/02-function-prototype/proto-constructor-animal-rabbit.svg @@ -1 +1 @@ -eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file +eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg index 3e11f275b..54b3d7980 100644 --- a/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg +++ b/1-js/08-prototypes/02-function-prototype/rabbit-prototype-constructor.svg @@ -1 +1 @@ -default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file +default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/article.md b/1-js/08-prototypes/03-native-prototypes/article.md index b50e779ec..bdfc86dd8 100644 --- a/1-js/08-prototypes/03-native-prototypes/article.md +++ b/1-js/08-prototypes/03-native-prototypes/article.md @@ -2,7 +2,7 @@ The `"prototype"` property is widely used by the core of JavaScript itself. All built-in constructor functions use it. -First we'll see at the details, and then how to use it for adding new capabilities to built-in objects. +First we'll look at the details, and then how to use it for adding new capabilities to built-in objects. ## Object.prototype @@ -33,7 +33,9 @@ We can check it like this: let obj = {}; alert(obj.__proto__ === Object.prototype); // true -// obj.toString === obj.__proto__.toString == Object.prototype.toString + +alert(obj.toString === obj.__proto__.toString); //true +alert(obj.toString === Object.prototype.toString); //true ``` Please note that there is no more `[[Prototype]]` in the chain above `Object.prototype`: @@ -99,12 +101,12 @@ alert(f.__proto__.__proto__ == Object.prototype); // true, inherit from objects The most intricate thing happens with strings, numbers and booleans. -As we remember, they are not objects. But if we try to access their properties, then temporary wrapper objects are created using built-in constructors `String`, `Number`, `Boolean`, they provide the methods and disappear. +As we remember, they are not objects. But if we try to access their properties, temporary wrapper objects are created using built-in constructors `String`, `Number` and `Boolean`. They provide the methods and disappear. These objects are created invisibly to us and most engines optimize them out, but the specification describes it exactly this way. Methods of these objects also reside in prototypes, available as `String.prototype`, `Number.prototype` and `Boolean.prototype`. ```warn header="Values `null` and `undefined` have no object wrappers" -Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes too. +Special values `null` and `undefined` stand apart. They have no object wrappers, so methods and properties are not available for them. And there are no corresponding prototypes either. ``` ## Changing native prototypes [#native-prototype-change] @@ -129,9 +131,9 @@ So, generally, modifying a native prototype is considered a bad idea. **In modern programming, there is only one case where modifying native prototypes is approved. That's polyfilling.** -Polyfilling is a term for making a substitute for a method that exists in JavaScript specification, but is not yet supported by current JavaScript engine. +Polyfilling is a term for making a substitute for a method that exists in the JavaScript specification, but is not yet supported by a particular JavaScript engine. -Then we may implement it manually and populate the built-in prototype with it. +We may then implement it manually and populate the built-in prototype with it. For instance: @@ -144,7 +146,7 @@ if (!String.prototype.repeat) { // if there's no such method // actually, the code should be a little bit more complex than that // (the full algorithm is in the specification) - // but even an imperfect polyfill is often considered good enough for use + // but even an imperfect polyfill is often considered good enough return new Array(n + 1).join(this); }; } @@ -179,18 +181,18 @@ obj.join = Array.prototype.join; alert( obj.join(',') ); // Hello,world! ``` -It works, because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property, it doesn't check that the object is indeed the array. And many built-in methods are like that. +It works because the internal algorithm of the built-in `join` method only cares about the correct indexes and the `length` property. It doesn't check if the object is indeed an array. Many built-in methods are like that. Another possibility is to inherit by setting `obj.__proto__` to `Array.prototype`, so all `Array` methods are automatically available in `obj`. But that's impossible if `obj` already inherits from another object. Remember, we only can inherit from one object at a time. -Borrowing methods is flexible, it allows to mix functionality from different objects if needed. +Borrowing methods is flexible, it allows to mix functionalities from different objects if needed. ## Summary - All built-in objects follow the same pattern: - - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype` etc). - - The object itself stores only the data (array items, object properties, the date). -- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype`, `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects. -- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. Probably the only allowable case is when we add-in a new standard, but not yet supported by the engine JavaScript method. + - The methods are stored in the prototype (`Array.prototype`, `Object.prototype`, `Date.prototype`, etc.) + - The object itself stores only the data (array items, object properties, the date) +- Primitives also store methods in prototypes of wrapper objects: `Number.prototype`, `String.prototype` and `Boolean.prototype`. Only `undefined` and `null` do not have wrapper objects +- Built-in prototypes can be modified or populated with new methods. But it's not recommended to change them. The only allowable case is probably when we add-in a new standard, but it's not yet supported by the JavaScript engine diff --git a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg index 35cdc61f1..59d60b397 100644 --- a/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg +++ b/1-js/08-prototypes/03-native-prototypes/function-prototype-constructor.svg @@ -1 +1 @@ -Rabbitprototypeconstructordefault "prototype" \ No newline at end of file +Rabbitprototypeconstructordefault "prototype" \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg index 770c908c5..ebb4f3205 100644 --- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-array-tostring.svg @@ -1 +1 @@ -toString: function ...Array.prototypetoString: function ...Object.prototype[[Prototype]][[Prototype]][1, 2, 3] \ No newline at end of file +toString: function ...Array.prototypetoString: function ...Object.prototype[[Prototype]][[Prototype]][1, 2, 3] \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg index 4989df56b..4d6129e0a 100644 --- a/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg +++ b/1-js/08-prototypes/03-native-prototypes/native-prototypes-classes.svg @@ -1 +1 @@ -toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file +toString: function other object methodsObject.prototypenullslice: function other array methods[[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]][[Prototype]]Array.prototypecall: function other function methodsFunction.prototypetoFixed: function other number methodsNumber.prototype[1, 2, 3]function f(args) { ... }5 \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg index 38c33cae1..9630e68e2 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-1.svg @@ -1 +1 @@ -constructor: Object toString: function ...Object.prototypeObjectobj = new Object()[[Prototype]]prototype \ No newline at end of file +constructor: Object toString: function ...Object.prototypeObjectobj = new Object()[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg index 858f8317e..9ccb34229 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype-null.svg @@ -1 +1 @@ -obj[[Prototype]]null \ No newline at end of file +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg index 8d3d0beee..024dd3021 100644 --- a/1-js/08-prototypes/03-native-prototypes/object-prototype.svg +++ b/1-js/08-prototypes/03-native-prototypes/object-prototype.svg @@ -1 +1 @@ -constructor: Object toString: function ...Object.prototypeObjectprototype \ No newline at end of file +constructor: Object toString: function ...Object.prototypeObjectprototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg b/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg deleted file mode 100644 index 3489ecdd8..000000000 --- a/1-js/08-prototypes/03-native-prototypes/proto-constructor-animal-rabbit.svg +++ /dev/null @@ -1 +0,0 @@ -eats: truename: "White Rabbit"animalRabbitrabbit[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg index 3e11f275b..54b3d7980 100644 --- a/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg +++ b/1-js/08-prototypes/03-native-prototypes/rabbit-prototype-constructor.svg @@ -1 +1 @@ -default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file +default "prototype"Rabbitrabbit[[Prototype]]prototypeconstructor \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md index a92e17900..f3c9cf0e5 100644 --- a/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md +++ b/1-js/08-prototypes/04-prototype-methods/2-dictionary-tostring/solution.md @@ -28,4 +28,4 @@ alert(dictionary); // "apple,__proto__" When we create a property using a descriptor, its flags are `false` by default. So in the code above, `dictionary.toString` is non-enumerable. -See the the chapter [](info:property-descriptors) for review. +See the chapter [](info:property-descriptors) for review. diff --git a/1-js/08-prototypes/04-prototype-methods/article.md b/1-js/08-prototypes/04-prototype-methods/article.md index a229483f1..34b977e9f 100644 --- a/1-js/08-prototypes/04-prototype-methods/article.md +++ b/1-js/08-prototypes/04-prototype-methods/article.md @@ -3,15 +3,18 @@ In the first chapter of this section, we mentioned that there are modern methods to setup a prototype. -The `__proto__` is considered outdated and somewhat deprecated (in browser-only part of the JavaScript standard). +Setting or reading the prototype with `obj.__proto__` is considered outdated and somewhat deprecated (moved to the so-called "Annex B" of the JavaScript standard, meant for browsers only). -The modern methods are: +The modern methods to get/set a prototype are: -- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. - [Object.getPrototypeOf(obj)](mdn:js/Object/getPrototypeOf) -- returns the `[[Prototype]]` of `obj`. - [Object.setPrototypeOf(obj, proto)](mdn:js/Object/setPrototypeOf) -- sets the `[[Prototype]]` of `obj` to `proto`. -These should be used instead of `__proto__`. +The only usage of `__proto__`, that's not frowned upon, is as a property when creating a new object: `{ __proto__: ... }`. + +Although, there's a special method for this too: + +- [Object.create(proto[, descriptors])](mdn:js/Object/create) -- creates an empty object with given `proto` as `[[Prototype]]` and optional property descriptors. For instance: @@ -22,7 +25,7 @@ let animal = { // create a new object with animal as a prototype *!* -let rabbit = Object.create(animal); +let rabbit = Object.create(animal); // same as {__proto__: animal} */!* alert(rabbit.eats); // true @@ -36,7 +39,9 @@ Object.setPrototypeOf(rabbit, {}); // change the prototype of rabbit to {} */!* ``` -`Object.create` has an optional second argument: property descriptors. We can provide additional properties to the new object there, like this: +The `Object.create` method is a bit more powerful, as it has an optional second argument: property descriptors. + +We can provide additional properties to the new object there, like this: ```js run let animal = { @@ -57,32 +62,39 @@ The descriptors are in the same format as described in the chapter ... get __proto__: function set __proto__: functionObject.prototypeObjectobj[[Prototype]]prototype \ No newline at end of file +... get __proto__: function set __proto__: functionObject.prototypeObjectobj[[Prototype]]prototype \ No newline at end of file diff --git a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg index 858f8317e..9ccb34229 100644 --- a/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg +++ b/1-js/08-prototypes/04-prototype-methods/object-prototype-null.svg @@ -1 +1 @@ -obj[[Prototype]]null \ No newline at end of file +obj[[Prototype]]null \ No newline at end of file diff --git a/1-js/09-classes/01-class/1-rewrite-to-class/task.md b/1-js/09-classes/01-class/1-rewrite-to-class/task.md index 05365e410..4477de679 100644 --- a/1-js/09-classes/01-class/1-rewrite-to-class/task.md +++ b/1-js/09-classes/01-class/1-rewrite-to-class/task.md @@ -4,6 +4,6 @@ importance: 5 # Rewrite to class -The `Clock` class is written in functional style. Rewrite it the "class" syntax. +The `Clock` class (see the sandbox) is written in functional style. Rewrite it in the "class" syntax. P.S. The clock ticks in the console, open it to see. diff --git a/1-js/09-classes/01-class/article.md b/1-js/09-classes/01-class/article.md index d6c729d25..135d24929 100644 --- a/1-js/09-classes/01-class/article.md +++ b/1-js/09-classes/01-class/article.md @@ -51,7 +51,7 @@ user.sayHi(); When `new User("John")` is called: 1. A new object is created. -2. The `constructor` runs with the given argument and assigns `this.name` to it. +2. The `constructor` runs with the given argument and assigns it to `this.name`. ...Then we can call object methods, such as `user.sayHi()`. @@ -68,7 +68,7 @@ So, what exactly is a `class`? That's not an entirely new language-level entity, Let's unveil any magic and see what a class really is. That'll help in understanding many complex aspects. -In JavaScript, a class is a kind of a function. +In JavaScript, a class is a kind of function. Here, take a look: @@ -110,15 +110,15 @@ alert(typeof User); // function alert(User === User.prototype.constructor); // true // The methods are in User.prototype, e.g: -alert(User.prototype.sayHi); // alert(this.name); +alert(User.prototype.sayHi); // the code of the sayHi method // there are exactly two methods in the prototype alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi ``` -## Not just a syntax sugar +## Not just a syntactic sugar -Sometimes people say that `class` is a "syntax sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same without `class` keyword at all: +Sometimes people say that `class` is a "syntactic sugar" (syntax that is designed to make things easier to read, but doesn't introduce anything new), because we could actually declare the same thing without using the `class` keyword at all: ```js run // rewriting class User in pure functions @@ -127,7 +127,7 @@ Sometimes people say that `class` is a "syntax sugar" (syntax that is designed t function User(name) { this.name = name; } -// any function prototype has constructor property by default, +// a function prototype has "constructor" property by default, // so we don't need to create it // 2. Add the method to prototype @@ -140,13 +140,13 @@ let user = new User("John"); user.sayHi(); ``` -The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntax sugar to define a constructor together with its prototype methods. +The result of this definition is about the same. So, there are indeed reasons why `class` can be considered a syntactic sugar to define a constructor together with its prototype methods. Still, there are important differences. -1. First, a function created by `class` is labelled by a special internal property `[[FunctionKind]]:"classConstructor"`. So it's not entirely the same as creating it manually. +1. First, a function created by `class` is labelled by a special internal property `[[IsClassConstructor]]: true`. So it's not entirely the same as creating it manually. - And unlike a regular function, a class constructor must be called with `new`: + The language checks for that property in a variety of places. For example, unlike a regular function, it must be called with `new`: ```js run class User { @@ -166,6 +166,7 @@ Still, there are important differences. alert(User); // class User { ... } ``` + There are other differences, we'll see them soon. 2. Class methods are non-enumerable. A class definition sets `enumerable` flag to `false` for all methods in the `"prototype"`. @@ -179,7 +180,7 @@ Besides, `class` syntax brings many other features that we'll explore later. ## Class Expression -Just like functions, classes can be defined inside another expression, passed around, returned, assigned etc. +Just like functions, classes can be defined inside another expression, passed around, returned, assigned, etc. Here's an example of a class expression: @@ -209,7 +210,6 @@ new User().sayHi(); // works, shows MyClass definition alert(MyClass); // error, MyClass name isn't visible outside of the class ``` - We can even make classes dynamically "on-demand", like this: ```js run @@ -218,7 +218,7 @@ function makeClass(phrase) { return class { sayHi() { alert(phrase); - }; + } }; } @@ -229,7 +229,7 @@ new User().sayHi(); // Hello ``` -## Getters/setters, other shorthands +## Getters/setters Just like literal objects, classes may include getters/setters, computed properties etc. @@ -264,25 +264,14 @@ class User { let user = new User("John"); alert(user.name); // John -user = new User(""); // Name too short. +user = new User(""); // Name is too short. ``` -The class declaration creates getters and setters in `User.prototype`, like this: +Technically, such class declaration works by creating getters and setters in `User.prototype`. -```js -Object.defineProperties(User.prototype, { - name: { - get() { - return this._name - }, - set(name) { - // ... - } - } -}); -``` +## Computed names [...] -Here's an example with a computed property in brackets `[...]`: +Here's an example with a computed method name using brackets `[...]`: ```js run class User { @@ -298,18 +287,24 @@ class User { new User().sayHi(); ``` -## Class properties +Such features are easy to remember, as they resemble that of literal objects. + +## Class fields ```warn header="Old browsers may need a polyfill" -Class-level properties are a recent addition to the language. +Class fields are a recent addition to the language. ``` -In the example above, `User` only had methods. Let's add a property: +Previously, our classes only had methods. + +"Class fields" is a syntax that allows to add any properties. + +For instance, let's add `name` property to `class User`: ```js run class User { *!* - name = "Anonymous"; + name = "John"; */!* sayHi() { @@ -317,10 +312,94 @@ class User { } } -new User().sayHi(); +new User().sayHi(); // Hello, John! +``` + +So, we just write " = " in the declaration, and that's it. + +The important difference of class fields is that they are set on individual objects, not `User.prototype`: + +```js run +class User { +*!* + name = "John"; +*/!* +} + +let user = new User(); +alert(user.name); // John +alert(User.prototype.name); // undefined +``` + +We can also assign values using more complex expressions and function calls: + +```js run +class User { +*!* + name = prompt("Name, please?", "John"); +*/!* +} + +let user = new User(); +alert(user.name); // John ``` -The property `name` is not placed into `User.prototype`. Instead, it is created by `new` before calling the constructor, it's a property of the object itself. + +### Making bound methods with class fields + +As demonstrated in the chapter functions in JavaScript have a dynamic `this`. It depends on the context of the call. + +So if an object method is passed around and called in another context, `this` won't be a reference to its object any more. + +For instance, this code will show `undefined`: + +```js run +class Button { + constructor(value) { + this.value = value; + } + + click() { + alert(this.value); + } +} + +let button = new Button("hello"); + +*!* +setTimeout(button.click, 1000); // undefined +*/!* +``` + +The problem is called "losing `this`". + +There are two approaches to fixing it, as discussed in the chapter : + +1. Pass a wrapper-function, such as `setTimeout(() => button.click(), 1000)`. +2. Bind the method to object, e.g. in the constructor. + +Class fields provide another, quite elegant syntax: + +```js run +class Button { + constructor(value) { + this.value = value; + } +*!* + click = () => { + alert(this.value); + } +*/!* +} + +let button = new Button("hello"); + +setTimeout(button.click, 1000); // hello +``` + +The class field `click = () => {...}` is created on a per-object basis, there's a separate function for each `Button` object, with `this` inside it referencing that object. We can pass `button.click` around anywhere, and the value of `this` will always be correct. + +That's especially useful in browser environment, for event listeners. ## Summary @@ -344,6 +423,6 @@ class MyClass { } ``` -`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and settors are written to `MyClass.prototype`. +`MyClass` is technically a function (the one that we provide as `constructor`), while methods, getters and setters are written to `MyClass.prototype`. In the next chapters we'll learn more about classes, including inheritance and other features. diff --git a/1-js/09-classes/01-class/class-user.svg b/1-js/09-classes/01-class/class-user.svg index 5ac0146a6..418d71d18 100644 --- a/1-js/09-classes/01-class/class-user.svg +++ b/1-js/09-classes/01-class/class-user.svg @@ -1 +1 @@ -sayHi: functionUserUser.prototypeprototypeconstructor: User \ No newline at end of file +sayHi: functionUserUser.prototypeprototypeconstructor: User \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js index ca613ca5e..be2053cfc 100644 --- a/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js +++ b/1-js/09-classes/02-class-inheritance/2-clock-class-extended/solution.view/extended-clock.js @@ -1,7 +1,7 @@ class ExtendedClock extends Clock { constructor(options) { super(options); - let { precision=1000 } = options; + let { precision = 1000 } = options; this.precision = precision; } diff --git a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg index 3412d9825..63b5a18a1 100644 --- a/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg +++ b/1-js/09-classes/02-class-inheritance/animal-rabbit-extends.svg @@ -1 +1 @@ -constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "White Rabbit"constructorconstructor \ No newline at end of file +constructor: Animal run: function stop: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitnew Rabbit[[Prototype]][[Prototype]]prototypeprototypename: "White Rabbit"constructorconstructorextends \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/article.md b/1-js/09-classes/02-class-inheritance/article.md index 58245e669..464042d82 100644 --- a/1-js/09-classes/02-class-inheritance/article.md +++ b/1-js/09-classes/02-class-inheritance/article.md @@ -1,9 +1,13 @@ # Class inheritance -Let's say we have two classes. +Class inheritance is a way for one class to extend another class. -`Animal`: +So we can create new functionality on top of the existing. + +## The "extends" keyword + +Let's say we have class `Animal`: ```js class Animal { @@ -12,7 +16,7 @@ class Animal { this.name = name; } run(speed) { - this.speed += speed; + this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } stop() { @@ -24,52 +28,19 @@ class Animal { let animal = new Animal("My animal"); ``` -![](rabbit-animal-independent-animal.svg) - - -...And `Rabbit`: - -```js -class Rabbit { - constructor(name) { - this.name = name; - } - hide() { - alert(`${this.name} hides!`); - } -} - -let rabbit = new Rabbit("My rabbit"); -``` - -![](rabbit-animal-independent-rabbit.svg) +Here's how we can represent `animal` object and `Animal` class graphically: +![](rabbit-animal-independent-animal.svg) -Right now they are fully independent. +...And we would like to create another `class Rabbit`. -But we'd want `Rabbit` to extend `Animal`. In other words, rabbits should be based on animals, have access to methods of `Animal` and extend them with its own methods. +As rabbits are animals, `Rabbit` class should be based on `Animal`, have access to animal methods, so that rabbits can do what "generic" animals can do. -To inherit from another class, we should specify `"extends"` and the parent class before the braces `{..}`. +The syntax to extend another class is: `class Child extends Parent`. -Here `Rabbit` inherits from `Animal`: +Let's create `class Rabbit` that inherits from `Animal`: -```js run -class Animal { - constructor(name) { - this.speed = 0; - this.name = name; - } - run(speed) { - this.speed += speed; - alert(`${this.name} runs with speed ${this.speed}.`); - } - stop() { - this.speed = 0; - alert(`${this.name} stands still.`); - } -} - -// Inherit from Animal by specifying "extends Animal" +```js *!* class Rabbit extends Animal { */!* @@ -84,15 +55,18 @@ rabbit.run(5); // White Rabbit runs with speed 5. rabbit.hide(); // White Rabbit hides! ``` -Now the `Rabbit` code became a bit shorter, as it uses `Animal` constructor by default, and it also can `run`, as animals do. +Object of `Rabbit` class have access both to `Rabbit` methods, such as `rabbit.hide()`, and also to `Animal` methods, such as `rabbit.run()`. -Internally, `extends` keyword adds `[[Prototype]]` reference from `Rabbit.prototype` to `Animal.prototype`: +Internally, `extends` keyword works using the good old prototype mechanics. It sets `Rabbit.prototype.[[Prototype]]` to `Animal.prototype`. So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. ![](animal-rabbit-extends.svg) -So, if a method is not found in `Rabbit.prototype`, JavaScript takes it from `Animal.prototype`. +For instance, to find `rabbit.run` method, the engine checks (bottom-up on the picture): +1. The `rabbit` object (has no `run`). +2. Its prototype, that is `Rabbit.prototype` (has `hide`, but not `run`). +3. Its prototype, that is (due to `extends`) `Animal.prototype`, that finally has the `run` method. -As we can recall from the chapter , JavaScript uses prototypal inheritance for build-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`, so dates have generic object methods. +As we can recall from the chapter , JavaScript itself uses prototypal inheritance for built-in objects. E.g. `Date.prototype.[[Prototype]]` is `Object.prototype`. That's why dates have access to generic object methods. ````smart header="Any expression is allowed after `extends`" Class syntax allows to specify not just a class, but any expression after `extends`. @@ -102,8 +76,8 @@ For instance, a function call that generates the parent class: ```js run function f(phrase) { return class { - sayHi() { alert(phrase) } - } + sayHi() { alert(phrase); } + }; } *!* @@ -119,19 +93,20 @@ That may be useful for advanced programming patterns when we use functions to ge ## Overriding a method -Now let's move forward and override a method. As of now, `Rabbit` inherits the `stop` method that sets `this.speed = 0` from `Animal`. +Now let's move forward and override a method. By default, all methods that are not specified in `class Rabbit` are taken directly "as is" from `class Animal`. -If we specify our own `stop` in `Rabbit`, then it will be used instead: +But if we specify our own method in `Rabbit`, such as `stop()` then it will be used instead: ```js class Rabbit extends Animal { stop() { - // ...this will be used for rabbit.stop() + // ...now this will be used for rabbit.stop() + // instead of stop() from class Animal } } ``` -...But usually we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. +Usually, however, we don't want to totally replace a parent method, but rather to build on top of it to tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process. Classes provide `"super"` keyword for that. @@ -149,7 +124,7 @@ class Animal { } run(speed) { - this.speed += speed; + this.speed = speed; alert(`${this.name} runs with speed ${this.speed}.`); } @@ -176,7 +151,7 @@ class Rabbit extends Animal { let rabbit = new Rabbit("White Rabbit"); rabbit.run(5); // White Rabbit runs with speed 5. -rabbit.stop(); // White Rabbit stands still. White rabbit hides! +rabbit.stop(); // White Rabbit stands still. White Rabbit hides! ``` Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the process. @@ -185,6 +160,7 @@ Now `Rabbit` has the `stop` method that calls the parent `super.stop()` in the p As was mentioned in the chapter , arrow functions do not have `super`. If accessed, it's taken from the outer function. For instance: + ```js class Rabbit extends Animal { stop() { @@ -201,12 +177,11 @@ setTimeout(function() { super.stop() }, 1000); ``` ```` - ## Overriding constructor With constructors it gets a little bit tricky. -Till now, `Rabbit` did not have its own `constructor`. +Until now, `Rabbit` did not have its own `constructor`. According to the [specification](https://tc39.github.io/ecma262/#sec-runtime-semantics-classdefinitionevaluation), if a class extends another class and has no `constructor`, then the following "empty" `constructor` is generated: @@ -255,22 +230,24 @@ let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined. Whoops! We've got an error. Now we can't create rabbits. What went wrong? -The short answer is: constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`. +The short answer is: + +- **Constructors in inheriting classes must call `super(...)`, and (!) do it before using `this`.** ...But why? What's going on here? Indeed, the requirement seems strange. Of course, there's an explanation. Let's get into details, so you'll really understand what's going on. -In JavaScript, there's a distinction between a "constructor function of an inheriting class" and all others. In an inheriting class, the corresponding constructor function is labeled with a special internal property `[[ConstructorKind]]:"derived"`. +In JavaScript, there's a distinction between a constructor function of an inheriting class (so-called "derived constructor") and other functions. A derived constructor has a special internal property `[[ConstructorKind]]:"derived"`. That's a special internal label. -The difference is: +That label affects its behavior with `new`. -- When a normal constructor runs, it creates an empty object and assigns it to `this`. +- When a regular function is executed with `new`, it creates an empty object and assigns it to `this`. - But when a derived constructor runs, it doesn't do this. It expects the parent constructor to do this job. -So if we're making a constructor of our own, then we must call `super`, because otherwise the object for `this` won't be created. And we'll get an error. +So a derived constructor must call `super` in order to execute its parent (base) constructor, otherwise the object for `this` won't be created. And we'll get an error. -For `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here: +For the `Rabbit` constructor to work, it needs to call `super()` before using `this`, like here: ```js run class Animal { @@ -303,6 +280,99 @@ alert(rabbit.earLength); // 10 */!* ``` +### Overriding class fields: a tricky note + +```warn header="Advanced note" +This note assumes you have a certain experience with classes, maybe in other programming languages. + +It provides better insight into the language and also explains the behavior that might be a source of bugs (but not very often). + +If you find it difficult to understand, just go on, continue reading, then return to it some time later. +``` + +We can override not only methods, but also class fields. + +Although, there's a tricky behavior when we access an overridden field in parent constructor, quite different from most other programming languages. + +Consider this example: + +```js run +class Animal { + name = 'animal'; + + constructor() { + alert(this.name); // (*) + } +} + +class Rabbit extends Animal { + name = 'rabbit'; +} + +new Animal(); // animal +*!* +new Rabbit(); // animal +*/!* +``` + +Here, class `Rabbit` extends `Animal` and overrides the `name` field with its own value. + +There's no own constructor in `Rabbit`, so `Animal` constructor is called. + +What's interesting is that in both cases: `new Animal()` and `new Rabbit()`, the `alert` in the line `(*)` shows `animal`. + +**In other words, the parent constructor always uses its own field value, not the overridden one.** + +What's odd about it? + +If it's not clear yet, please compare with methods. + +Here's the same code, but instead of `this.name` field we call `this.showName()` method: + +```js run +class Animal { + showName() { // instead of this.name = 'animal' + alert('animal'); + } + + constructor() { + this.showName(); // instead of alert(this.name); + } +} + +class Rabbit extends Animal { + showName() { + alert('rabbit'); + } +} + +new Animal(); // animal +*!* +new Rabbit(); // rabbit +*/!* +``` + +Please note: now the output is different. + +And that's what we naturally expect. When the parent constructor is called in the derived class, it uses the overridden method. + +...But for class fields it's not so. As said, the parent constructor always uses the parent field. + +Why is there a difference? + +Well, the reason is the field initialization order. The class field is initialized: +- Before constructor for the base class (that doesn't extend anything), +- Immediately after `super()` for the derived class. + +In our case, `Rabbit` is the derived class. There's no `constructor()` in it. As said previously, that's the same as if there was an empty constructor with only `super(...args)`. + +So, `new Rabbit()` calls `super()`, thus executing the parent constructor, and (per the rule for derived classes) only after that its class fields are initialized. At the time of the parent constructor execution, there are no `Rabbit` class fields yet, that's why `Animal` fields are used. + +This subtle difference between fields and methods is specific to JavaScript. + +Luckily, this behavior only reveals itself if an overridden field is used in the parent constructor. Then it may be difficult to understand what's going on, so we're explaining it here. + +If it becomes a problem, one can fix it by using methods or getters/setters instead of fields. ## Super: internals, [[HomeObject]] @@ -463,7 +533,7 @@ It works as intended, due to `[[HomeObject]]` mechanics. A method, such as `long As we've known before, generally functions are "free", not bound to objects in JavaScript. So they can be copied between objects and called with another `this`. -The very existance of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. +The very existence of `[[HomeObject]]` violates that principle, because methods remember their objects. `[[HomeObject]]` can't be changed, so this bond is forever. The only place in the language where `[[HomeObject]]` is used -- is `super`. So, if a method does not use `super`, then we can still consider it free and copy between objects. But with `super` things may go wrong. @@ -472,7 +542,7 @@ Here's the demo of a wrong `super` result after copying: ```js run let animal = { sayHi() { - console.log(`I'm an animal`); + alert(`I'm an animal`); } }; @@ -486,7 +556,7 @@ let rabbit = { let plant = { sayHi() { - console.log("I'm a plant"); + alert("I'm a plant"); } }; @@ -503,7 +573,7 @@ tree.sayHi(); // I'm an animal (?!?) */!* ``` -A call to `tree.sayHi()` shows "I'm an animal". Definitevely wrong. +A call to `tree.sayHi()` shows "I'm an animal". Definitely wrong. The reason is simple: - In the line `(*)`, the method `tree.sayHi` was copied from `rabbit`. Maybe we just wanted to avoid code duplication? @@ -524,7 +594,7 @@ In the example below a non-method syntax is used for comparison. `[[HomeObject]] ```js run let animal = { - eat: function() { // intentially writing like this instead of eat() {... + eat: function() { // intentionally writing like this instead of eat() {... // ... } }; @@ -554,4 +624,4 @@ rabbit.eat(); // Error calling super (because there's no [[HomeObject]]) - So it's not safe to copy a method with `super` from one object to another. Also: -- Arrow functions don't have own `this` or `super`, so they transparently fit into the surrounding context. +- Arrow functions don't have their own `this` or `super`, so they transparently fit into the surrounding context. diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg index 546aa334f..5ea9bf29e 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-array-object.svg @@ -1 +1 @@ -slice: function ...Array.prototypearrhasOwnProperty: function ...Object.prototype[1, 2, 3][[Prototype]][[Prototype]] \ No newline at end of file +slice: function ...Array.prototypearrhasOwnProperty: function ...Object.prototype[1, 2, 3][[Prototype]][[Prototype]] \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg index 3bdda5a02..72e47e34c 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal-2.svg @@ -1 +1 @@ -jump: functionRabbit.prototyperabbiteat: functionAnimal.prototypename: "White Rabbit"[[Prototype]][[Prototype]]Rabbit.prototype.__proto__ = Animal.prototype sets thistoString: function hasOwnProperty: function ...Object.prototype[[Prototype]][[Prototype]]null \ No newline at end of file +jump: functionRabbit.prototyperabbiteat: functionAnimal.prototypename: "White Rabbit"[[Prototype]][[Prototype]]Rabbit.prototype.__proto__ = Animal.prototype sets thistoString: function hasOwnProperty: function ...Object.prototype[[Prototype]][[Prototype]]null \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg index 91f82896d..bced3d355 100644 --- a/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg +++ b/1-js/09-classes/02-class-inheritance/class-inheritance-rabbit-animal.svg @@ -1 +1 @@ -methods of RabbitRabbit.prototyperabbitmethods of AnimalAnimal.prototype[[Prototype]][[Prototype]]properties of rabbit \ No newline at end of file +methods of RabbitRabbit.prototyperabbitmethods of AnimalAnimal.prototype[[Prototype]][[Prototype]]properties of rabbit \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg index bf86db77d..f53fc92de 100644 --- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-animal.svg @@ -1 +1 @@ - constructor: Animal run: function stop: functionAnimal.prototypeAnimalnew Animal[[Prototype]]prototypename: "My animal" \ No newline at end of file + constructor: Animal run: function stop: functionAnimal.prototypeAnimalnew Animal[[Prototype]]prototypename: "My animal" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg index 8a5e25037..2f30a3a90 100644 --- a/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg +++ b/1-js/09-classes/02-class-inheritance/rabbit-animal-independent-rabbit.svg @@ -1 +1 @@ - constructor: Rabbit hide: functionRabbit.prototypeRabbitnew Rabbit[[Prototype]]prototypename: "My rabbit" \ No newline at end of file + constructor: Rabbit hide: functionRabbit.prototypeRabbitnew Rabbit[[Prototype]]prototypename: "My rabbit" \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg index c9c8fea9e..f6450ddc4 100644 --- a/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg +++ b/1-js/09-classes/02-class-inheritance/super-homeobject-wrong.svg @@ -1 +1 @@ -sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file +sayHiplantsayHitreesayHianimalrabbit[[HomeObject]]sayHi \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/this-super-loop.svg b/1-js/09-classes/02-class-inheritance/this-super-loop.svg index 342574da9..4f5f45034 100644 --- a/1-js/09-classes/02-class-inheritance/this-super-loop.svg +++ b/1-js/09-classes/02-class-inheritance/this-super-loop.svg @@ -1 +1 @@ -rabbitlongEarrabbitlongEar \ No newline at end of file +rabbitlongEarrabbitlongEar \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg similarity index 54% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg index 0a1f4382c..915ab9aa6 100644 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/rabbit-extends-object.svg +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/rabbit-extends-object.svg @@ -1 +1 @@ -call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file +call: function bind: function ...Function.prototypeconstructorObjectRabbit[[Prototype]][[Prototype]]constructorcall: function bind: function ...Function.prototypeRabbit[[Prototype]]constructorclass Rabbitclass Rabbit extends Object \ No newline at end of file diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md similarity index 79% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md index ca9e80601..cb9829ce0 100644 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/solution.md +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/solution.md @@ -21,14 +21,14 @@ alert( rabbit.hasOwnProperty('name') ); // true But that's not all yet. -Even after the fix, there's still important difference in `"class Rabbit extends Object"` versus `class Rabbit`. +Even after the fix, there's still an important difference between `"class Rabbit extends Object"` and `class Rabbit`. As we know, the "extends" syntax sets up two prototypes: 1. Between `"prototype"` of the constructor functions (for methods). 2. Between the constructor functions themselves (for static methods). -In our case, for `class Rabbit extends Object` it means: +In the case of `class Rabbit extends Object` it means: ```js run class Rabbit extends Object {} @@ -37,7 +37,7 @@ alert( Rabbit.prototype.__proto__ === Object.prototype ); // (1) true alert( Rabbit.__proto__ === Object ); // (2) true ``` -So `Rabbit` now provides access to static methods of `Object` via `Rabbit`, like this: +So `Rabbit` now provides access to the static methods of `Object` via `Rabbit`, like this: ```js run class Rabbit extends Object {} @@ -67,7 +67,7 @@ alert ( Rabbit.getOwnPropertyNames({a: 1, b: 2})); // Error So `Rabbit` doesn't provide access to static methods of `Object` in that case. -By the way, `Function.prototype` has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. +By the way, `Function.prototype` also has "generic" function methods, like `call`, `bind` etc. They are ultimately available in both cases, because for the built-in `Object` constructor, `Object.__proto__ === Function.prototype`. Here's the picture: diff --git a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md similarity index 92% rename from 1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md rename to 1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md index b82a4255e..1d0f98a74 100644 --- a/1-js/09-classes/02-class-inheritance/3-class-extend-object/task.md +++ b/1-js/09-classes/03-static-properties-methods/3-class-extend-object/task.md @@ -1,4 +1,4 @@ -importance: 5 +importance: 3 --- @@ -38,5 +38,5 @@ class Rabbit extends Object { let rabbit = new Rabbit("Rab"); -alert( rabbit.hasOwnProperty('name') ); // true +alert( rabbit.hasOwnProperty('name') ); // Error ``` diff --git a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg index fab401df6..3e354b895 100644 --- a/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg +++ b/1-js/09-classes/03-static-properties-methods/animal-rabbit-static.svg @@ -1 +1 @@ -constructor: Animal run: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitrabbit[[Prototype]][[Prototype]][[Prototype]]prototypeprototypecomparename: "White Rabbit" \ No newline at end of file +constructor: Animal run: functionAnimal.prototypeconstructor: Rabbit hide: functionRabbit.prototypeAnimalRabbitrabbit[[Prototype]][[Prototype]][[Prototype]]prototypeprototypecomparename: "White Rabbit" \ No newline at end of file diff --git a/1-js/09-classes/03-static-properties-methods/article.md b/1-js/09-classes/03-static-properties-methods/article.md index 8e08514f2..4b493a5e8 100644 --- a/1-js/09-classes/03-static-properties-methods/article.md +++ b/1-js/09-classes/03-static-properties-methods/article.md @@ -1,9 +1,9 @@ # Static properties and methods -We can also assign a method to the class function itself, not to its `"prototype"`. Such methods are called *static*. +We can also assign a method to the class as a whole. Such methods are called *static*. -In a class, they are prepended by `static` keyword, like this: +In a class declaration, they are prepended by `static` keyword, like this: ```js run class User { @@ -19,19 +19,23 @@ User.staticMethod(); // true That actually does the same as assigning it as a property directly: -```js -class User() { } +```js run +class User { } User.staticMethod = function() { alert(this === User); }; + +User.staticMethod(); // true ``` The value of `this` in `User.staticMethod()` call is the class constructor `User` itself (the "object before dot" rule). -Usually, static methods are used to implement functions that belong to the class, but not to any particular object of it. +Usually, static methods are used to implement functions that belong to the class as a whole, but not to any particular object of it. -For instance, we have `Article` objects and need a function to compare them. A natural solution would be to add `Article.compare` method, like this: +For instance, we have `Article` objects and need a function to compare them. + +A natural solution would be to add `Article.compare` static method: ```js run class Article { @@ -61,9 +65,11 @@ articles.sort(Article.compare); alert( articles[0].title ); // CSS ``` -Here `Article.compare` stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. +Here `Article.compare` method stands "above" articles, as a means to compare them. It's not a method of an article, but rather of the whole class. + +Another example would be a so-called "factory" method. -Another example would be a so-called "factory" method. Imagine, we need few ways to create an article: +Let's say, we need multiple ways to create an article: 1. Create by given parameters (`title`, `date` etc). 2. Create an empty article with today's date. @@ -71,7 +77,7 @@ Another example would be a so-called "factory" method. Imagine, we need few ways The first way can be implemented by the constructor. And for the second one we can make a static method of the class. -Like `Article.createTodays()` here: +Such as `Article.createTodays()` here: ```js run class Article { @@ -99,10 +105,21 @@ Static methods are also used in database-related classes to search/save/remove e ```js // assuming Article is a special class for managing articles -// static method to remove the article: +// static method to remove the article by id: Article.remove({id: 12345}); ``` +````warn header="Static methods aren't available for individual objects" +Static methods are callable on classes, not on individual objects. + +E.g. such code won't work: + +```js +// ... +article.createTodays(); /// Error: article.createTodays is not a function +``` +```` + ## Static properties [recent browser=Chrome] @@ -123,14 +140,15 @@ That is the same as a direct assignment to `Article`: Article.publisher = "Ilya Kantor"; ``` -## Inheritance of static methods +## Inheritance of static properties and methods [#statics-and-inheritance] -Static methods are inherited. +Static properties and methods are inherited. -For instance, `Animal.compare` in the code below is inherited and accessible as `Rabbit.compare`: +For instance, `Animal.compare` and `Animal.planet` in the code below are inherited and accessible as `Rabbit.compare` and `Rabbit.planet`: ```js run class Animal { + static planet = "Earth"; constructor(name, speed) { this.speed = speed; @@ -167,9 +185,11 @@ rabbits.sort(Rabbit.compare); */!* rabbits[0].run(); // Black Rabbit runs with speed 5. + +alert(Rabbit.planet); // Earth ``` -Now when we can call `Rabbit.compare`, the inherited `Animal.compare` will be called. +Now when we call `Rabbit.compare`, the inherited `Animal.compare` will be called. How does it work? Again, using prototypes. As you might have already guessed, `extends` gives `Rabbit` the `[[Prototype]]` reference to `Animal`. @@ -180,7 +200,7 @@ So, `Rabbit extends Animal` creates two `[[Prototype]]` references: 1. `Rabbit` function prototypally inherits from `Animal` function. 2. `Rabbit.prototype` prototypally inherits from `Animal.prototype`. -As the result, inheritance works both for regular and static methods. +As a result, inheritance works both for regular and static methods. Here, let's check that by code: @@ -197,7 +217,7 @@ alert(Rabbit.prototype.__proto__ === Animal.prototype); // true ## Summary -Static methods are used for the functionality that belongs to the class "as a whole", doesn't relate to a concrete class instance. +Static methods are used for the functionality that belongs to the class "as a whole". It doesn't relate to a concrete class instance. For example, a method for comparison `Article.compare(article1, article2)` or a factory method `Article.createTodays()`. diff --git a/1-js/09-classes/04-private-protected-properties-methods/article.md b/1-js/09-classes/04-private-protected-properties-methods/article.md index fb1a964c9..91efb89ee 100644 --- a/1-js/09-classes/04-private-protected-properties-methods/article.md +++ b/1-js/09-classes/04-private-protected-properties-methods/article.md @@ -50,7 +50,7 @@ That was a general introduction. In JavaScript, there are two types of object fields (properties and methods): -- Public: accessible from anywhere. They comprise the external interface. Till now we were only using public properties and methods. +- Public: accessible from anywhere. They comprise the external interface. Until now we were only using public properties and methods. - Private: accessible only from inside the class. These are for the internal interface. In many other languages there also exist "protected" fields: accessible only from inside the class and those extending it (like private, but plus access from inheriting classes). They are also useful for the internal interface. They are in a sense more widespread than private ones, because we usually want inheriting classes to gain access to them. @@ -96,7 +96,9 @@ class CoffeeMachine { _waterAmount = 0; set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) { + value = 0; + } this._waterAmount = value; } @@ -114,10 +116,10 @@ class CoffeeMachine { let coffeeMachine = new CoffeeMachine(100); // add water -coffeeMachine.waterAmount = -10; // Error: Negative water +coffeeMachine.waterAmount = -10; // _waterAmount will become 0, not -10 ``` -Now the access is under control, so setting the water below zero fails. +Now the access is under control, so setting the water amount below zero becomes impossible. ## Read-only "power" @@ -159,7 +161,7 @@ class CoffeeMachine { _waterAmount = 0; *!*setWaterAmount(value)*/!* { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this._waterAmount = value; } @@ -190,7 +192,7 @@ There's a finished JavaScript proposal, almost in the standard, that provides la Privates should start with `#`. They are only accessible from inside the class. -For instance, here's a private `#waterLimit` property and the water-checking private method `#checkWater`: +For instance, here's a private `#waterLimit` property and the water-checking private method `#fixWaterAmount`: ```js run class CoffeeMachine { @@ -199,19 +201,23 @@ class CoffeeMachine { */!* *!* - #checkWater(value) { - if (value < 0) throw new Error("Negative water"); - if (value > this.#waterLimit) throw new Error("Too much water"); + #fixWaterAmount(value) { + if (value < 0) return 0; + if (value > this.#waterLimit) return this.#waterLimit; } */!* + setWaterAmount(value) { + this.#waterLimit = this.#fixWaterAmount(value); + } + } let coffeeMachine = new CoffeeMachine(); *!* // can't access privates from outside of the class -coffeeMachine.#checkWater(); // Error +coffeeMachine.#fixWaterAmount(123); // Error coffeeMachine.#waterLimit = 1000; // Error */!* ``` @@ -232,7 +238,7 @@ class CoffeeMachine { } set waterAmount(value) { - if (value < 0) throw new Error("Negative water"); + if (value < 0) value = 0; this.#waterAmount = value; } } @@ -257,7 +263,7 @@ class MegaCoffeeMachine extends CoffeeMachine { } ``` -In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reason to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax. +In many scenarios such limitation is too severe. If we extend a `CoffeeMachine`, we may have legitimate reasons to access its internals. That's why protected fields are used more often, even though they are not supported by the language syntax. ````warn header="Private fields are not available as this[name]" Private fields are special. @@ -279,11 +285,11 @@ With private fields that's impossible: `this['#name']` doesn't work. That's a sy ## Summary -In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation]("https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)"). +In terms of OOP, delimiting of the internal interface from the external one is called [encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)). It gives the following benefits: -Protection for users, so that they don't shoot themselves in the feet +Protection for users, so that they don't shoot themselves in the foot : Imagine, there's a team of developers using a coffee machine. It was made by the "Best CoffeeMachine" company, and works fine, but a protective cover was removed. So the internal interface is exposed. All developers are civilized -- they use the coffee machine as intended. But one of them, John, decided that he's the smartest one, and made some tweaks in the coffee machine internals. So the coffee machine failed two days later. @@ -308,9 +314,9 @@ Hiding complexity **It's always convenient when implementation details are hidden, and a simple, well-documented external interface is available.** -To hide internal interface we use either protected or private properties: +To hide an internal interface we use either protected or private properties: - Protected fields start with `_`. That's a well-known convention, not enforced at the language level. Programmers should only access a field starting with `_` from its class and classes inheriting from it. -- Private fields start with `#`. JavaScript makes sure we only can access those from inside the class. +- Private fields start with `#`. JavaScript makes sure we can only access those from inside the class. Right now, private fields are not well-supported among browsers, but can be polyfilled. diff --git a/1-js/09-classes/05-extend-natives/article.md b/1-js/09-classes/05-extend-natives/article.md index 88dda88e4..28b4c6eb6 100644 --- a/1-js/09-classes/05-extend-natives/article.md +++ b/1-js/09-classes/05-extend-natives/article.md @@ -32,7 +32,7 @@ When `arr.filter()` is called, it internally creates the new array of results us Even more, we can customize that behavior. -We can add a special static getter `Symbol.species` to the class. If exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on. +We can add a special static getter `Symbol.species` to the class. If it exists, it should return the constructor that JavaScript will use internally to create new entities in `map`, `filter` and so on. If we'd like built-in methods like `map` or `filter` to return regular arrays, we can return `Array` in `Symbol.species`, like here: @@ -78,7 +78,7 @@ Normally, when one class extends another, both static and non-static methods are But built-in classes are an exception. They don't inherit statics from each other. -For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no `Array.keys()` and `Date.keys()` static methods. +For example, both `Array` and `Date` inherit from `Object`, so their instances have methods from `Object.prototype`. But `Array.[[Prototype]]` does not reference `Object`, so there's no, for instance, `Array.keys()` (or `Date.keys()`) static method. Here's the picture structure for `Date` and `Object`: diff --git a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg index a0165bccb..be47d7fd9 100644 --- a/1-js/09-classes/05-extend-natives/object-date-inheritance.svg +++ b/1-js/09-classes/05-extend-natives/object-date-inheritance.svg @@ -1 +1 @@ -constructor: Object toString: function hasOwnProperty: function ...Object.prototypeconstructor: Date toString: function getDate: function ...Date.prototypeObjectDatenew Date()[[Prototype]][[Prototype]]prototypeprototypedefineProperty keys ...now parse ...1 Jan 2019 \ No newline at end of file +constructor: Object toString: function hasOwnProperty: function ...Object.prototypeconstructor: Date toString: function getDate: function ...Date.prototypeObjectDatenew Date()[[Prototype]][[Prototype]]prototypeprototypedefineProperty keys ...now parse ...1 Jan 2019 \ No newline at end of file diff --git a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md index e9481912a..5b8dc7de3 100644 --- a/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md +++ b/1-js/09-classes/06-instanceof/1-strange-instanceof/task.md @@ -4,7 +4,7 @@ importance: 5 # Strange instanceof -Why `instanceof` below returns `true`? We can easily see that `a` is not created by `B()`. +In the code below, why does `instanceof` return `true`? We can easily see that `a` is not created by `B()`. ```js run function A() {} diff --git a/1-js/09-classes/06-instanceof/article.md b/1-js/09-classes/06-instanceof/article.md index 7e082aa5a..f9db989ca 100644 --- a/1-js/09-classes/06-instanceof/article.md +++ b/1-js/09-classes/06-instanceof/article.md @@ -2,7 +2,7 @@ The `instanceof` operator allows to check whether an object belongs to a certain class. It also takes inheritance into account. -Such a check may be necessary in many cases, here we'll use it for building a *polymorphic* function, the one that treats arguments differently depending on their type. +Such a check may be necessary in many cases. For example, it can be used for building a *polymorphic* function, the one that treats arguments differently depending on their type. ## The instanceof operator [#ref-instanceof] @@ -46,7 +46,7 @@ alert( arr instanceof Object ); // true Please note that `arr` also belongs to the `Object` class. That's because `Array` prototypically inherits from `Object`. -Normally, `instanceof` operator examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`. +Normally, `instanceof` examines the prototype chain for the check. We can also set a custom logic in the static method `Symbol.hasInstance`. The algorithm of `obj instanceof Class` works roughly as follows: @@ -68,7 +68,7 @@ The algorithm of `obj instanceof Class` works roughly as follows: alert(obj instanceof Animal); // true: Animal[Symbol.hasInstance](obj) is called ``` -2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` equals to one of prototypes in the `obj` prototype chain. +2. Most classes do not have `Symbol.hasInstance`. In that case, the standard logic is used: `obj instanceOf Class` checks whether `Class.prototype` is equal to one of the prototypes in the `obj` prototype chain. In other words, compare one after another: ```js @@ -93,7 +93,7 @@ The algorithm of `obj instanceof Class` works roughly as follows: alert(rabbit instanceof Animal); // true */!* - // rabbit.__proto__ === Rabbit.prototype + // rabbit.__proto__ === Animal.prototype (no match) *!* // rabbit.__proto__.__proto__ === Animal.prototype (match!) */!* @@ -105,9 +105,9 @@ Here's the illustration of what `rabbit instanceof Animal` compares with `Animal By the way, there's also a method [objA.isPrototypeOf(objB)](mdn:js/object/isPrototypeOf), that returns `true` if `objA` is somewhere in the chain of prototypes for `objB`. So the test of `obj instanceof Class` can be rephrased as `Class.prototype.isPrototypeOf(obj)`. -That's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters. +It's funny, but the `Class` constructor itself does not participate in the check! Only the chain of prototypes and `Class.prototype` matters. -That can lead to interesting consequences when `prototype` property is changed after the object is created. +That can lead to interesting consequences when a `prototype` property is changed after the object is created. Like here: @@ -186,11 +186,11 @@ let user = { alert( {}.toString.call(user) ); // [object User] ``` -For most environment-specific objects, there is such a property. Here are few browser specific examples: +For most environment-specific objects, there is such a property. Here are some browser specific examples: ```js run // toStringTag for the environment-specific object and class: -alert( window[Symbol.toStringTag]); // window +alert( window[Symbol.toStringTag]); // Window alert( XMLHttpRequest.prototype[Symbol.toStringTag] ); // XMLHttpRequest alert( {}.toString.call(window) ); // [object Window] diff --git a/1-js/09-classes/06-instanceof/instanceof.svg b/1-js/09-classes/06-instanceof/instanceof.svg index 920be68b7..d63b03a8a 100644 --- a/1-js/09-classes/06-instanceof/instanceof.svg +++ b/1-js/09-classes/06-instanceof/instanceof.svg @@ -1 +1 @@ -Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file +Animal.prototypeObject.prototypeRabbit.prototype[[Prototype]]rabbit[[Prototype]][[Prototype]]null[[Prototype]]= Animal.prototype? \ No newline at end of file diff --git a/1-js/09-classes/07-mixins/article.md b/1-js/09-classes/07-mixins/article.md index da2307926..526b832ef 100644 --- a/1-js/09-classes/07-mixins/article.md +++ b/1-js/09-classes/07-mixins/article.md @@ -69,7 +69,7 @@ let sayMixin = { }; let sayHiMixin = { - __proto__: sayMixin, // (or we could use Object.create to set the prototype here) + __proto__: sayMixin, // (or we could use Object.setPrototypeOf to set the prototype here) sayHi() { *!* @@ -101,21 +101,21 @@ Here's the diagram (see the right part): ![](mixin-inheritance.svg) -That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown on the picture above. +That's because methods `sayHi` and `sayBye` were initially created in `sayHiMixin`. So even though they got copied, their `[[HomeObject]]` internal property references `sayHiMixin`, as shown in the picture above. -As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`, not `User.[[Prototype]]`. +As `super` looks for parent methods in `[[HomeObject]].[[Prototype]]`, that means it searches `sayHiMixin.[[Prototype]]`. ## EventMixin Now let's make a mixin for real life. -An important feature of many browser objects (for instance) is that they can generate events. Events is a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows to easily add event-related functions to any class/object. +An important feature of many browser objects (for instance) is that they can generate events. Events are a great way to "broadcast information" to anyone who wants it. So let's make a mixin that allows us to easily add event-related functions to any class/object. - The mixin will provide a method `.trigger(name, [...data])` to "generate an event" when something important happens to it. The `name` argument is a name of the event, optionally followed by additional arguments with event data. -- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from `.trigger` call. -- ...And the method `.off(name, handler)` that removes `handler` listener. +- Also the method `.on(name, handler)` that adds `handler` function as the listener to events with the given name. It will be called when an event with the given `name` triggers, and get the arguments from the `.trigger` call. +- ...And the method `.off(name, handler)` that removes the `handler` listener. -After adding the mixin, an object `user` will become able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen to such events to load the calendar for the logged-in person. +After adding the mixin, an object `user` will be able to generate an event `"login"` when the visitor logs in. And another object, say, `calendar` may want to listen for such events to load the calendar for the logged-in person. Or, a `menu` can generate the event `"select"` when a menu item is selected, and other objects may assign handlers to react on that event. And so on. @@ -140,7 +140,7 @@ let eventMixin = { * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for (let i = 0; i < handlers.length; i++) { if (handlers[i] === handler) { @@ -154,7 +154,7 @@ let eventMixin = { * this.trigger('select', data1, data2); */ trigger(eventName, ...args) { - if (!this._eventHandlers || !this._eventHandlers[eventName]) { + if (!this._eventHandlers?.[eventName]) { return; // no handlers for that event name } @@ -165,7 +165,7 @@ let eventMixin = { ``` -- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name happens. Technically, there's `_eventHandlers` property, that stores an array of handlers for each event name. So it just adds it to the list. +- `.on(eventName, handler)` -- assigns function `handler` to run when the event with that name occurs. Technically, there's an `_eventHandlers` property that stores an array of handlers for each event name, and it just adds it to the list. - `.off(eventName, handler)` -- removes the function from the handlers list. - `.trigger(eventName, ...args)` -- generates the event: all handlers from `_eventHandlers[eventName]` are called, with a list of arguments `...args`. @@ -193,7 +193,7 @@ menu.on("select", value => alert(`Value selected: ${value}`)); menu.choose("123"); ``` -Now if we'd like any code to react on menu selection, we can listen to it with `menu.on(...)`. +Now, if we'd like any code to react to a menu selection, we can listen for it with `menu.on(...)`. And `eventMixin` mixin makes it easy to add such behavior to as many classes as we'd like, without interfering with the inheritance chain. @@ -203,6 +203,6 @@ And `eventMixin` mixin makes it easy to add such behavior to as many classes as Some other languages allow multiple inheritance. JavaScript does not support multiple inheritance, but mixins can be implemented by copying methods into prototype. -We can use mixins as a way to augment a class by multiple behaviors, like event-handling as we have seen above. +We can use mixins as a way to augment a class by adding multiple behaviors, like event-handling as we have seen above. -Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that. +Mixins may become a point of conflict if they accidentally overwrite existing class methods. So generally one should think well about the naming methods of a mixin, to minimize the probability of that happening. diff --git a/1-js/09-classes/07-mixins/head.html b/1-js/09-classes/07-mixins/head.html index 77ea38b20..20e3a6354 100644 --- a/1-js/09-classes/07-mixins/head.html +++ b/1-js/09-classes/07-mixins/head.html @@ -18,7 +18,7 @@ * menu.off('select', handler) */ off(eventName, handler) { - let handlers = this._eventHandlers && this._eventHandlers[eventName]; + let handlers = this._eventHandlers?.[eventName]; if (!handlers) return; for(let i = 0; i < handlers.length; i++) { if (handlers[i] == handler) { diff --git a/1-js/09-classes/07-mixins/mixin-inheritance.svg b/1-js/09-classes/07-mixins/mixin-inheritance.svg index 2e7ba8b39..1fdc22393 100644 --- a/1-js/09-classes/07-mixins/mixin-inheritance.svg +++ b/1-js/09-classes/07-mixins/mixin-inheritance.svg @@ -1 +1 @@ -sayHi: function sayBye: functionsayHiMixinsay: functionsayMixin[[Prototype]]constructor: User sayHi: function sayBye: functionUser.prototype[[Prototype]]name: ...user[[HomeObject] \ No newline at end of file +sayHi: function sayBye: functionsayHiMixinsay: functionsayMixin[[Prototype]]constructor: User sayHi: function sayBye: functionUser.prototype[[Prototype]]name: ...user[[HomeObject] \ No newline at end of file diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md index 303431d6d..ec0dabc9a 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/solution.md @@ -1,8 +1,8 @@ The difference becomes obvious when we look at the code inside a function. -The behavior is different if there's a "jump out" of `try..catch`. +The behavior is different if there's a "jump out" of `try...catch`. -For instance, when there's a `return` inside `try..catch`. The `finally` clause works in case of *any* exit from `try..catch`, even via the `return` statement: right after `try..catch` is done, but before the calling code gets the control. +For instance, when there's a `return` inside `try...catch`. The `finally` clause works in case of *any* exit from `try...catch`, even via the `return` statement: right after `try...catch` is done, but before the calling code gets the control. ```js run function f() { @@ -11,7 +11,7 @@ function f() { *!* return "result"; */!* - } catch (e) { + } catch (err) { /// ... } finally { alert('cleanup!'); @@ -28,11 +28,11 @@ function f() { try { alert('start'); throw new Error("an error"); - } catch (e) { + } catch (err) { // ... if("can't handle the error") { *!* - throw e; + throw err; */!* } diff --git a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md index c573cc232..b6dc81326 100644 --- a/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md +++ b/1-js/10-error-handling/1-try-catch/1-finally-or-code-after/task.md @@ -6,12 +6,12 @@ importance: 5 Compare the two code fragments. -1. The first one uses `finally` to execute the code after `try..catch`: +1. The first one uses `finally` to execute the code after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } finally { *!* @@ -19,12 +19,12 @@ Compare the two code fragments. */!* } ``` -2. The second fragment puts the cleaning right after `try..catch`: +2. The second fragment puts the cleaning right after `try...catch`: ```js try { work work - } catch (e) { + } catch (err) { handle errors } diff --git a/1-js/10-error-handling/1-try-catch/article.md b/1-js/10-error-handling/1-try-catch/article.md index f81bfc2b4..bf548373a 100644 --- a/1-js/10-error-handling/1-try-catch/article.md +++ b/1-js/10-error-handling/1-try-catch/article.md @@ -1,14 +1,14 @@ -# Error handling, "try..catch" +# Error handling, "try...catch" -No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response and for a thousand other reasons. +No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response, and for a thousand other reasons. Usually, a script "dies" (immediately stops) in case of an error, printing it to console. -But there's a syntax construct `try..catch` that allows to "catch" errors and, instead of dying, do something more reasonable. +But there's a syntax construct `try...catch` that allows us to "catch" errors so the script can, instead of dying, do something more reasonable. -## The "try..catch" syntax +## The "try...catch" syntax -The `try..catch` construct has two main blocks: `try`, and then `catch`: +The `try...catch` construct has two main blocks: `try`, and then `catch`: ```js try { @@ -25,14 +25,14 @@ try { It works like this: 1. First, the code in `try {...}` is executed. -2. If there were no errors, then `catch(err)` is ignored: the execution reaches the end of `try` and goes on skipping `catch`. -3. If an error occurs, then `try` execution is stopped, and the control flows to the beginning of `catch(err)`. The `err` variable (can use any name for it) contains an error object with details about what's happened. +2. If there were no errors, then `catch (err)` is ignored: the execution reaches the end of `try` and goes on, skipping `catch`. +3. If an error occurs, then the `try` execution is stopped, and control flows to the beginning of `catch (err)`. The `err` variable (we can use any name for it) will contain an error object with details about what happened. ![](try-catch-flow.svg) -So, an error inside the `try {…}` block does not kill the script: we have a chance to handle it in `catch`. +So, an error inside the `try {...}` block does not kill the script -- we have a chance to handle it in `catch`. -Let's see examples. +Let's look at some examples. - An errorless example: shows `alert` `(1)` and `(2)`: @@ -45,7 +45,7 @@ Let's see examples. alert('End of try runs'); // *!*(2) <--*/!* - } catch(err) { + } catch (err) { alert('Catch is ignored, because there are no errors'); // (3) @@ -64,7 +64,7 @@ Let's see examples. alert('End of try (never reached)'); // (2) - } catch(err) { + } catch (err) { alert(`Error has occurred!`); // *!*(3) <--*/!* @@ -72,45 +72,45 @@ Let's see examples. ``` -````warn header="`try..catch` only works for runtime errors" -For `try..catch` to work, the code must be runnable. In other words, it should be valid JavaScript. +````warn header="`try...catch` only works for runtime errors" +For `try...catch` to work, the code must be runnable. In other words, it should be valid JavaScript. It won't work if the code is syntactically wrong, for instance it has unmatched curly braces: ```js run try { {{{{{{{{{{{{ -} catch(e) { +} catch (err) { alert("The engine can't understand this code, it's invalid"); } ``` The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phase are called "parse-time" errors and are unrecoverable (from inside that code). That's because the engine can't understand the code. -So, `try..catch` can only handle errors that occur in the valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". +So, `try...catch` can only handle errors that occur in valid code. Such errors are called "runtime errors" or, sometimes, "exceptions". ```` -````warn header="`try..catch` works synchronously" -If an exception happens in "scheduled" code, like in `setTimeout`, then `try..catch` won't catch it: +````warn header="`try...catch` works synchronously" +If an exception happens in "scheduled" code, like in `setTimeout`, then `try...catch` won't catch it: ```js run try { setTimeout(function() { noSuchVariable; // script will die here }, 1000); -} catch (e) { +} catch (err) { alert( "won't work" ); } ``` -That's because the function itself is executed later, when the engine has already left the `try..catch` construct. +That's because the function itself is executed later, when the engine has already left the `try...catch` construct. -To catch an exception inside a scheduled function, `try..catch` must be inside that function: +To catch an exception inside a scheduled function, `try...catch` must be inside that function: ```js run setTimeout(function() { try { - noSuchVariable; // try..catch handles the error! + noSuchVariable; // try...catch handles the error! } catch { alert( "error is caught here!" ); } @@ -125,7 +125,7 @@ When an error occurs, JavaScript generates an object containing the details abou ```js try { // ... -} catch(err) { // <-- the "error object", could use another word instead of err +} catch (err) { // <-- the "error object", could use another word instead of err // ... } ``` @@ -150,7 +150,7 @@ try { *!* lalala; // error, variable is not defined! */!* -} catch(err) { +} catch (err) { alert(err.name); // ReferenceError alert(err.message); // lalala is not defined alert(err.stack); // ReferenceError: lalala is not defined at (...call stack) @@ -175,9 +175,9 @@ try { } ``` -## Using "try..catch" +## Using "try...catch" -Let's explore a real-life use case of `try..catch`. +Let's explore a real-life use case of `try...catch`. As we already know, JavaScript supports the [JSON.parse(str)](mdn:js/JSON/parse) method to read JSON-encoded values. @@ -201,11 +201,11 @@ You can find more detailed information about JSON in the chapter. **If `json` is malformed, `JSON.parse` generates an error, so the script "dies".** -Should we be satisfied with that? Of course, not! +Should we be satisfied with that? Of course not! This way, if something's wrong with the data, the visitor will never know that (unless they open the developer console). And people really don't like when something "just dies" without any error message. -Let's use `try..catch` to handle the error: +Let's use `try...catch` to handle the error: ```js run let json = "{ bad json }"; @@ -217,12 +217,12 @@ try { */!* alert( user.name ); // doesn't work -} catch (e) { +} catch (err) { *!* // ...the execution jumps here alert( "Our apologies, the data has errors, we'll try to request it one more time." ); - alert( e.name ); - alert( e.message ); + alert( err.name ); + alert( err.message ); */!* } ``` @@ -245,7 +245,7 @@ try { alert( user.name ); // no name! */!* -} catch (e) { +} catch (err) { alert( "doesn't execute" ); } ``` @@ -294,11 +294,11 @@ Let's see what kind of error `JSON.parse` generates: ```js run try { JSON.parse("{ bad json o_O }"); -} catch(e) { +} catch (err) { *!* - alert(e.name); // SyntaxError + alert(err.name); // SyntaxError */!* - alert(e.message); // Unexpected token o in JSON at position 2 + alert(err.message); // Unexpected token b in JSON at position 2 } ``` @@ -323,8 +323,8 @@ try { alert( user.name ); -} catch(e) { - alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name +} catch (err) { + alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name } ``` @@ -334,9 +334,9 @@ Now `catch` became a single place for all error handling: both for `JSON.parse` ## Rethrowing -In the example above we use `try..catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just that "incorrect data" thing. +In the example above we use `try...catch` to handle incorrect data. But is it possible that *another unexpected error* occurs within the `try {...}` block? Like a programming error (variable is not defined) or something else, not just this "incorrect data" thing. -Like this: +For example: ```js run let json = '{ "age": 30 }'; // incomplete data @@ -345,7 +345,7 @@ try { user = JSON.parse(json); // <-- forgot to put "let" before user // ... -} catch(err) { +} catch (err) { alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined // (no JSON Error actually) } @@ -353,29 +353,33 @@ try { Of course, everything's possible! Programmers do make mistakes. Even in open-source utilities used by millions for decades -- suddenly a bug may be discovered that leads to terrible hacks. -In our case, `try..catch` is meant to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. +In our case, `try...catch` is placed to catch "incorrect data" errors. But by its nature, `catch` gets *all* errors from `try`. Here it gets an unexpected error, but still shows the same `"JSON Error"` message. That's wrong and also makes the code more difficult to debug. + +To avoid such problems, we can employ the "rethrowing" technique. The rule is simple: + +**Catch should only process errors that it knows and "rethrow" all others.** -Fortunately, we can find out which error we get, for instance from its `name`: +The "rethrowing" technique can be explained in more detail as: + +1. Catch gets all errors. +2. In the `catch (err) {...}` block we analyze the error object `err`. +3. If we don't know how to handle it, we do `throw err`. + +Usually, we can check the error type using the `instanceof` operator: ```js run try { user = { /*...*/ }; -} catch(e) { +} catch (err) { *!* - alert(e.name); // "ReferenceError" for accessing an undefined variable + if (err instanceof ReferenceError) { */!* + alert('ReferenceError'); // "ReferenceError" for accessing an undefined variable + } } ``` -The rule is simple: - -**Catch should only process errors that it knows and "rethrow" all others.** - -The "rethrowing" technique can be explained in more detail as: - -1. Catch gets all errors. -2. In `catch(err) {...}` block we analyze the error object `err`. -2. If we don't know how to handle it, then we do `throw err`. +We can also get the error class name from `err.name` property. All native errors have it. Another option is to read `err.constructor.name`. In the code below, we use rethrowing so that `catch` only handles `SyntaxError`: @@ -395,24 +399,24 @@ try { alert( user.name ); -} catch(e) { +} catch (err) { *!* - if (e.name == "SyntaxError") { - alert( "JSON Error: " + e.message ); + if (err instanceof SyntaxError) { + alert( "JSON Error: " + err.message ); } else { - throw e; // rethrow (*) + throw err; // rethrow (*) } */!* } ``` -The error throwing on line `(*)` from inside `catch` block "falls out" of `try..catch` and can be either caught by an outer `try..catch` construct (if it exists), or it kills the script. +The error throwing on line `(*)` from inside `catch` block "falls out" of `try...catch` and can be either caught by an outer `try...catch` construct (if it exists), or it kills the script. So the `catch` block actually handles only errors that it knows how to deal with and "skips" all others. -The example below demonstrates how such errors can be caught by one more level of `try..catch`: +The example below demonstrates how such errors can be caught by one more level of `try...catch`: ```js run function readData() { @@ -423,11 +427,11 @@ function readData() { *!* blabla(); // error! */!* - } catch (e) { + } catch (err) { // ... - if (e.name != 'SyntaxError') { + if (!(err instanceof SyntaxError)) { *!* - throw e; // rethrow (don't know how to deal with it) + throw err; // rethrow (don't know how to deal with it) */!* } } @@ -435,20 +439,20 @@ function readData() { try { readData(); -} catch (e) { +} catch (err) { *!* - alert( "External catch got: " + e ); // caught it! + alert( "External catch got: " + err ); // caught it! */!* } ``` -Here `readData` only knows how to handle `SyntaxError`, while the outer `try..catch` knows how to handle everything. +Here `readData` only knows how to handle `SyntaxError`, while the outer `try...catch` knows how to handle everything. -## try..catch..finally +## try...catch...finally Wait, that's not all. -The `try..catch` construct may have one more code clause: `finally`. +The `try...catch` construct may have one more code clause: `finally`. If it exists, it runs in all cases: @@ -460,7 +464,7 @@ The extended syntax looks like this: ```js *!*try*/!* { ... try to execute the code ... -} *!*catch*/!*(e) { +} *!*catch*/!* (err) { ... handle errors ... } *!*finally*/!* { ... execute always ... @@ -473,7 +477,7 @@ Try running this code: try { alert( 'try' ); if (confirm('Make an error?')) BAD_CODE(); -} catch (e) { +} catch (err) { alert( 'catch' ); } finally { alert( 'finally' ); @@ -509,7 +513,7 @@ let start = Date.now(); try { result = fib(num); -} catch (e) { +} catch (err) { result = 0; *!* } finally { @@ -527,14 +531,14 @@ You can check by running the code with entering `35` into `prompt` -- it execute In other words, the function may finish with `return` or `throw`, that doesn't matter. The `finally` clause executes in both cases. -```smart header="Variables are local inside `try..catch..finally`" -Please note that `result` and `diff` variables in the code above are declared *before* `try..catch`. +```smart header="Variables are local inside `try...catch...finally`" +Please note that `result` and `diff` variables in the code above are declared *before* `try...catch`. Otherwise, if we declared `let` in `try` block, it would only be visible inside of it. ``` ````smart header="`finally` and `return`" -The `finally` clause works for *any* exit from `try..catch`. That includes an explicit `return`. +The `finally` clause works for *any* exit from `try...catch`. That includes an explicit `return`. In the example below, there's a `return` in `try`. In this case, `finally` is executed just before the control returns to the outer code. @@ -546,7 +550,7 @@ function func() { return 1; */!* - } catch (e) { + } catch (err) { /* ... */ } finally { *!* @@ -559,9 +563,9 @@ alert( func() ); // first works alert from finally, and then this one ``` ```` -````smart header="`try..finally`" +````smart header="`try...finally`" -The `try..finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. +The `try...finally` construct, without `catch` clause, is also useful. We apply it when we don't want to handle errors here (let them fall through), but want to be sure that processes that we started are finalized. ```js function func() { @@ -582,11 +586,11 @@ In the code above, an error inside `try` always falls out, because there's no `c The information from this section is not a part of the core JavaScript. ``` -Let's imagine we've got a fatal error outside of `try..catch`, and the script died. Like a programming error or something else terrible. +Let's imagine we've got a fatal error outside of `try...catch`, and the script died. Like a programming error or some other terrible thing. -Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages) etc. +Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don't see error messages), etc. -There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property, that will run in case of an uncaught error. +There is none in the specification, but environments usually provide it, because it's really useful. For instance, Node.js has [`process.on("uncaughtException")`](https://nodejs.org/api/process.html#process_event_uncaughtexception) for that. And in the browser we can assign a function to the special [window.onerror](mdn:api/GlobalEventHandlers/onerror) property, that will run in case of an uncaught error. The syntax: @@ -628,7 +632,7 @@ For instance: The role of the global handler `window.onerror` is usually not to recover the script execution -- that's probably impossible in case of programming errors, but to send the error message to developers. -There are also web-services that provide error-logging for such cases, like or . +There are also web-services that provide error-logging for such cases, like or . They work like this: @@ -639,14 +643,14 @@ They work like this: ## Summary -The `try..catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. +The `try...catch` construct allows to handle runtime errors. It literally allows to "try" running the code and "catch" errors that may occur in it. The syntax is: ```js try { // run this code -} catch(err) { +} catch (err) { // if an error happened, then jump here // err is the error object } finally { @@ -654,7 +658,7 @@ try { } ``` -There may be no `catch` section or no `finally`, so shorter constructs `try..catch` and `try..finally` are also valid. +There may be no `catch` section or no `finally`, so shorter constructs `try...catch` and `try...finally` are also valid. Error objects have following properties: @@ -662,10 +666,10 @@ Error objects have following properties: - `name` -- the string with error name (error constructor name). - `stack` (non-standard, but well-supported) -- the stack at the moment of error creation. -If an error object is not needed, we can omit it by using `catch {` instead of `catch(err) {`. +If an error object is not needed, we can omit it by using `catch {` instead of `catch (err) {`. We can also generate our own errors using the `throw` operator. Technically, the argument of `throw` can be anything, but usually it's an error object inheriting from the built-in `Error` class. More on extending errors in the next chapter. *Rethrowing* is a very important pattern of error handling: a `catch` block usually expects and knows how to handle the particular error type, so it should rethrow errors it doesn't know. -Even if we don't have `try..catch`, most environments allow to setup a "global" error handler to catch errors that "fall out". In-browser that's `window.onerror`. +Even if we don't have `try...catch`, most environments allow us to setup a "global" error handler to catch errors that "fall out". In-browser, that's `window.onerror`. diff --git a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg index dd15063d0..2c0d71348 100644 --- a/1-js/10-error-handling/1-try-catch/try-catch-flow.svg +++ b/1-js/10-error-handling/1-try-catch/try-catch-flow.svg @@ -1 +1 @@ -BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry { }// code... \ No newline at end of file +BeginNo ErrorsAn error occured in the codeIgnore catch blockIgnore the rest of tryExecute catch blocktry { }// code... \ No newline at end of file diff --git a/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md index bb6b74cfa..754e68f9a 100644 --- a/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md +++ b/1-js/10-error-handling/2-custom-errors/1-format-error/solution.md @@ -2,7 +2,7 @@ class FormatError extends SyntaxError { constructor(message) { super(message); - this.name = "FormatError"; + this.name = this.constructor.name; } } diff --git a/1-js/10-error-handling/2-custom-errors/article.md b/1-js/10-error-handling/2-custom-errors/article.md index 2414ce7ef..d28b07439 100644 --- a/1-js/10-error-handling/2-custom-errors/article.md +++ b/1-js/10-error-handling/2-custom-errors/article.md @@ -2,11 +2,11 @@ When we develop something, we often need our own error classes to reflect specific things that may go wrong in our tasks. For errors in network operations we may need `HttpError`, for database operations `DbError`, for searching operations `NotFoundError` and so on. -Our errors should support basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have `statusCode` property with a value like `404` or `403` or `500`. +Our errors should support basic error properties like `message`, `name` and, preferably, `stack`. But they also may have other properties of their own, e.g. `HttpError` objects may have a `statusCode` property with a value like `404` or `403` or `500`. JavaScript allows to use `throw` with any argument, so technically our custom error classes don't need to inherit from `Error`. But if we inherit, then it becomes possible to use `obj instanceof Error` to identify error objects. So it's better to inherit from it. -As the application grows, our own errors naturally form a hierarchy, for instance `HttpTimeoutError` may inherit from `HttpError`, and so on. +As the application grows, our own errors naturally form a hierarchy. For instance, `HttpTimeoutError` may inherit from `HttpError`, and so on. ## Extending Error @@ -21,9 +21,9 @@ Internally, we'll use `JSON.parse`. If it receives malformed `json`, then it thr Our function `readUser(json)` will not only read JSON, but check ("validate") the data. If there are no required fields, or the format is wrong, then that's an error. And that's not a `SyntaxError`, because the data is syntactically correct, but another kind of error. We'll call it `ValidationError` and create a class for it. An error of that kind should also carry the information about the offending field. -Our `ValidationError` class should inherit from the built-in `Error` class. +Our `ValidationError` class should inherit from the `Error` class. -That class is built-in, here's it approximate code, for us to understand what we're extending: +The `Error` class is built-in, but here's its approximate code so we can understand what we're extending: ```js // The "pseudocode" for the built-in Error class defined by JavaScript itself @@ -38,7 +38,7 @@ class Error { Now let's inherit `ValidationError` from it and try it in action: -```js run untrusted +```js run *!* class ValidationError extends Error { */!* @@ -117,15 +117,15 @@ We could also look at `err.name`, like this: // instead of (err instanceof SyntaxError) } else if (err.name == "SyntaxError") { // (*) // ... -``` +``` The `instanceof` version is much better, because in the future we are going to extend `ValidationError`, make subtypes of it, like `PropertyRequiredError`. And `instanceof` check will continue to work for new inheriting classes. So that's future-proof. -Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (due to a typo in the code or other unknown ones) should fall through. +Also it's important that if `catch` meets an unknown error, then it rethrows it in the line `(**)`. The `catch` block only knows how to handle validation and syntax errors, other kinds (caused by a typo in the code or other unknown reasons) should fall through. ## Further inheritance -The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age`). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. +The `ValidationError` class is very generic. Many things may go wrong. The property may be absent or it may be in a wrong format (like a string value for `age` instead of a number). Let's make a more concrete class `PropertyRequiredError`, exactly for absent properties. It will carry additional information about the property that's missing. ```js run class ValidationError extends Error { @@ -180,7 +180,7 @@ try { The new class `PropertyRequiredError` is easy to use: we only need to pass the property name: `new PropertyRequiredError(property)`. The human-readable `message` is generated by the constructor. -Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all ours custom errors from it. +Please note that `this.name` in `PropertyRequiredError` constructor is again assigned manually. That may become a bit tedious -- to assign `this.name = ` in every custom error class. We can avoid it by making our own "basic error" class that assigns `this.name = this.constructor.name`. And then inherit all our custom errors from it. Let's call it `MyError`. @@ -215,11 +215,39 @@ Now custom errors are much shorter, especially `ValidationError`, as we got rid The purpose of the function `readUser` in the code above is "to read the user data". There may occur different kinds of errors in the process. Right now we have `SyntaxError` and `ValidationError`, but in the future `readUser` function may grow and probably generate other kinds of errors. -The code which calls `readUser` should handle these errors. Right now it uses multiple `if` in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. But if `readUser` function generates several kinds of errors -- then we should ask ourselves: do we really want to check for all error types one-by-one in every code that calls `readUser`? +The code which calls `readUser` should handle these errors. Right now it uses multiple `if`s in the `catch` block, that check the class and handle known errors and rethrow the unknown ones. + +The scheme is like this: + +```js +try { + ... + readUser() // the potential error source + ... +} catch (err) { + if (err instanceof ValidationError) { + // handle validation errors + } else if (err instanceof SyntaxError) { + // handle syntax errors + } else { + throw err; // unknown error, rethrow it + } +} +``` + +In the code above we can see two types of errors, but there can be more. + +If the `readUser` function generates several kinds of errors, then we should ask ourselves: do we really want to check for all error types one-by-one every time? + +Often the answer is "No": we'd like to be "one level above all that". We just want to know if there was a "data reading error" -- why exactly it happened is often irrelevant (the error message describes it). Or, even better, we'd like to have a way to get the error details, but only if we need to. + +The technique that we describe here is called "wrapping exceptions". -Often the answer is "No": the outer code wants to be "one level above all that". It wants to have some kind of "data reading error". Why exactly it happened -- is often irrelevant (the error message describes it). Or, even better if there is a way to get error details, but only if we need to. +1. We'll make a new class `ReadError` to represent a generic "data reading" error. +2. The function `readUser` will catch data reading errors that occur inside it, such as `ValidationError` and `SyntaxError`, and generate a `ReadError` instead. +3. The `ReadError` object will keep the reference to the original error in its `cause` property. -So let's make a new class `ReadError` to represent such errors. If an error occurs inside `readUser`, we'll catch it there and generate `ReadError`. We'll also keep the reference to the original error in its `cause` property. Then the outer code will only have to check for `ReadError`. +Then the code that calls `readUser` will only have to check for `ReadError`, not for every kind of data reading errors. And if it needs more details of an error, it can check its `cause` property. Here's the code that defines `ReadError` and demonstrates its use in `readUser` and `try..catch`: @@ -291,12 +319,12 @@ try { In the code above, `readUser` works exactly as described -- catches syntax and validation errors and throws `ReadError` errors instead (unknown errors are rethrown as usual). -So the outer code checks `instanceof ReadError` and that's it. No need to list possible all error types. +So the outer code checks `instanceof ReadError` and that's it. No need to list all possible error types. -The approach is called "wrapping exceptions", because we take "low level exceptions" and "wrap" them into `ReadError` that is more abstract and more convenient to use for the calling code. It is widely used in object-oriented programming. +The approach is called "wrapping exceptions", because we take "low level" exceptions and "wrap" them into `ReadError` that is more abstract. It is widely used in object-oriented programming. ## Summary -- We can inherit from `Error` and other built-in error classes normally, just need to take care of `name` property and don't forget to call `super`. -- We can use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from the 3rd-party library and there's no easy way to get the class. Then `name` property can be used for such checks. +- We can inherit from `Error` and other built-in error classes normally. We just need to take care of the `name` property and don't forget to call `super`. +- We can use `instanceof` to check for particular errors. It also works with inheritance. But sometimes we have an error object coming from a 3rd-party library and there's no easy way to get its class. Then `name` property can be used for such checks. - Wrapping exceptions is a widespread technique: a function handles low-level exceptions and creates higher-level errors instead of various low-level ones. Low-level exceptions sometimes become properties of that object like `err.cause` in the examples above, but that's not strictly required. diff --git a/1-js/11-async/01-callbacks/article.md b/1-js/11-async/01-callbacks/article.md index c2f67c6cc..57115a909 100644 --- a/1-js/11-async/01-callbacks/article.md +++ b/1-js/11-async/01-callbacks/article.md @@ -2,30 +2,44 @@ # Introduction: callbacks -Many actions in JavaScript are *asynchronous*. +```warn header="We use browser methods in examples here" +To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations. -For instance, take a look at the function `loadScript(src)`: +If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the [next part](/document) of the tutorial. + +Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise. +``` + +Many functions are provided by JavaScript host environments that allow you to schedule *asynchronous* actions. In other words, actions that we initiate now, but they finish later. + +For instance, one such function is the `setTimeout` function. + +There are other real-world examples of asynchronous actions, e.g. loading scripts and modules (we'll cover them in later chapters). + +Take a look at the function `loadScript(src)`, that loads a script with the given `src`: ```js function loadScript(src) { + // creates a ``` -If we really need to make a window-level global variable, we can explicitly assign it to `window` and access as `window.user`. But that's an exception requiring a good reason. +```smart +In the browser, we can make a variable window-level global by explicitly assigning it to a `window` property, e.g. `window.user = "John"`. + +Then all scripts will see it, both with `type="module"` and without it. + +That said, making such global variables is frowned upon. Please try to avoid them. +``` ### A module code is evaluated only the first time when imported -If the same module is imported into multiple other places, its code is executed only the first time, then exports are given to all importers. +If the same module is imported into multiple other modules, its code is executed only once, upon the first import. Then its exports are given to all further importers. -That has important consequences. Let's see that on examples. +The one-time evaluation has important consequences, that we should be aware of. + +Let's see a couple of examples. First, if executing a module code brings side-effects, like showing a message, then importing it multiple times will trigger it only once -- the first time: @@ -129,9 +146,11 @@ import `./alert.js`; // Module is evaluated! import `./alert.js`; // (shows nothing) ``` -In practice, top-level module code is mostly used for initialization, creation of internal data structures, and if we want something to be reusable -- export it. +The second import shows nothing, because the module has already been evaluated. + +There's a rule: top-level module code should be used for initialization, creation of module-specific internal data structures. If we need to make something callable multiple times - we should export it as a function, like we did with `sayHi` above. -Now, a more advanced example. +Now, let's consider a deeper example. Let's say, a module exports an object: @@ -156,54 +175,67 @@ import {admin} from './admin.js'; alert(admin.name); // Pete *!* -// Both 1.js and 2.js imported the same object +// Both 1.js and 2.js reference the same admin object // Changes made in 1.js are visible in 2.js */!* ``` -So, let's reiterate -- the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other modules will see that. +As you can see, when `1.js` changes the `name` property in the imported `admin`, then `2.js` can see the new `admin.name`. -Such behavior allows to *configure* modules on first import. We can setup its properties once, and then in further imports it's ready. +That's exactly because the module is executed only once. Exports are generated, and then they are shared between importers, so if something changes the `admin` object, other importers will see that. -For instance, `admin.js` module may provide certain functionality, but expect the credentials to come into the `admin` object from outside: +**Such behavior is actually very convenient, because it allows us to *configure* modules.** + +In other words, a module can provide a generic functionality that needs a setup. E.g. authentication needs credentials. Then it can export a configuration object expecting the outer code to assign to it. + +Here's the classical pattern: +1. A module exports some means of configuration, e.g. a configuration object. +2. On the first import we initialize it, write to its properties. The top-level application script may do that. +3. Further imports use the module. + +For instance, the `admin.js` module may provide certain functionality (e.g. authentication), but expect the credentials to come into the `config` object from outside: ```js // 📁 admin.js -export let admin = { }; +export let config = { }; export function sayHi() { - alert(`Ready to serve, ${admin.name}!`); + alert(`Ready to serve, ${config.user}!`); } ``` -In `init.js`, the first script of our app, we set `admin.name`. Then everyone will see it, including calls made from inside `admin.js` itself: +Here, `admin.js` exports the `config` object (initially empty, but may have default properties too). + +Then in `init.js`, the first script of our app, we import `config` from it and set `config.user`: ```js // 📁 init.js -import {admin} from './admin.js'; -admin.name = "Pete"; +import {config} from './admin.js'; +config.user = "Pete"; ``` -Another module can also see `admin.name`: +...Now the module `admin.js` is configured. -```js -// 📁 other.js -import {admin, sayHi} from './admin.js'; +Further importers can call it, and it correctly shows the current user: -alert(admin.name); // *!*Pete*/!* +```js +// 📁 another.js +import {sayHi} from './admin.js'; sayHi(); // Ready to serve, *!*Pete*/!*! ``` + ### import.meta The object `import.meta` contains the information about the current module. -Its content depends on the environment. In the browser, it contains the url of the script, or a current webpage url if inside HTML: +Its content depends on the environment. In the browser, it contains the URL of the script, or a current webpage URL if inside HTML: ```html run height=0 ``` @@ -229,18 +261,18 @@ Compare it to non-module scripts, where `this` is a global object: There are also several browser-specific differences of scripts with `type="module"` compared to regular ones. -You may want skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. +You may want to skip this section for now if you're reading for the first time, or if you don't use JavaScript in a browser. ### Module scripts are deferred Module scripts are *always* deferred, same effect as `defer` attribute (described in the chapter [](info:script-async-defer)), for both external and inline scripts. In other words: -- downloading of external module scripts ` @@ -264,21 +296,21 @@ Compare to regular script below: ``` -Please note: the second script actually works before the first! So we'll see `undefined` first, and then `object`. +Please note: the second script actually runs before the first! So we'll see `undefined` first, and then `object`. -That's because modules are deferred, so we wait for the document to be processed. The regular scripts runs immediately, so we saw its output first. +That's because modules are deferred, so we wait for the document to be processed. The regular script runs immediately, so we see its output first. -When using modules, we should be aware that HTML-page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that. +When using modules, we should be aware that the HTML page shows up as it loads, and JavaScript modules run after that, so the user may see the page before the JavaScript application is ready. Some functionality may not work yet. We should put "loading indicators", or otherwise ensure that the visitor won't be confused by that. ### Async works on inline scripts -For non-module scripts, `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document. +For non-module scripts, the `async` attribute only works on external scripts. Async scripts run immediately when ready, independently of other scripts or the HTML document. -For module scripts, it works on any scripts. +For module scripts, it works on inline scripts as well. -For example, the script below has `async`, so it doesn't wait for anyone. +For example, the inline script below has `async`, so it doesn't wait for anything. -It performs the import (fetches `./analytics.js`) and runs when ready, even if HTML document is not finished yet, or if other scripts are still pending. +It performs the import (fetches `./analytics.js`) and runs when ready, even if the HTML document is not finished yet, or if other scripts are still pending. That's good for functionality that doesn't depend on anything, like counters, ads, document-level event listeners. @@ -296,7 +328,7 @@ That's good for functionality that doesn't depend on anything, like counters, ad External scripts that have `type="module"` are different in two aspects: -1. External scripts with same `src` run only once: +1. External scripts with the same `src` run only once: ```html @@ -322,11 +354,11 @@ import {sayHi} from 'sayHi'; // Error, "bare" module // the module must have a path, e.g. './sayHi.js' or wherever the module is ``` -Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet. +Certain environments, like Node.js or bundle tools allow bare modules, without any path, as they have their own ways for finding modules and hooks to fine-tune them. But browsers do not support bare modules yet. ### Compatibility, "nomodule" -Old browsers do not understand `type="module"`. Scripts of the unknown type are just ignored. For them, it's possible to provide a fallback using `nomodule` attribute: +Old browsers do not understand `type="module"`. Scripts of an unknown type are just ignored. For them, it's possible to provide a fallback using the `nomodule` attribute: ```html run + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js new file mode 100644 index 000000000..ea55b4478 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-dom.view/index.js @@ -0,0 +1,24 @@ +const startMessagesBtn = document.querySelector('.start-messages'); // (1) +const closeWindowBtn = document.querySelector('.window__button'); // (2) +const windowElementRef = new WeakRef(document.querySelector(".window__body")); // (3) + +startMessagesBtn.addEventListener('click', () => { // (4) + startMessages(windowElementRef); + startMessagesBtn.disabled = true; +}); + +closeWindowBtn.addEventListener('click', () => document.querySelector(".window__body").remove()); // (5) + + +const startMessages = (element) => { + const timerId = setInterval(() => { // (6) + if (element.deref()) { // (7) + const payload = document.createElement("p"); + payload.textContent = `Message: System status OK: ${new Date().toLocaleTimeString()}`; + element.deref().append(payload); + } else { // (8) + alert("The element has been deleted."); // (9) + clearInterval(timerId); + } + }, 1000); +}; \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg new file mode 100644 index 000000000..2a507dbcd --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-01.svg @@ -0,0 +1,32 @@ + + + + + + + + user + + name: "John" + Object + + <global> + + + + + + + + + + + + + + + + admin + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg new file mode 100644 index 000000000..6cc199a12 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-02.svg @@ -0,0 +1,33 @@ + + + + + + + + + + <global> + + + name: "John" + Object + + + + + + + + + + + + admin + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg new file mode 100644 index 000000000..949a14f9f --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-03.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg new file mode 100644 index 000000000..1177d6580 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-04.svg @@ -0,0 +1,77 @@ + + + + + + + name: "John" + Object + + admin + + + + + + + + + key + value + image-01.jpg + image-02.jpg + image-03.jpg + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + + + + + + + + + + + + + + + WeakRef object + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg new file mode 100644 index 000000000..e738f8e7e --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-05.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + image-02.jpg + image-03.jpg + + key + value + image-01.jpg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WeakRef object + + + + + + + + + + + + + + + + WeakRef object + + + + + undefined + undefined + Deleted by FinalizationRegistry cleanup callback + + + + + + + + + + + + + + + WeakRef object + + + + \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png new file mode 100644 index 000000000..fc33a023a Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-01.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png new file mode 100644 index 000000000..7d8bb01e8 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-02.png differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif new file mode 100644 index 000000000..b81966dda Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-03.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg new file mode 100644 index 000000000..ba60f1e86 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-04.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif new file mode 100644 index 000000000..d34bda4d7 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-05.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg new file mode 100644 index 000000000..b2655540f Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-06.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif new file mode 100644 index 000000000..51f874518 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-07.gif differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg new file mode 100644 index 000000000..5f98aec14 Binary files /dev/null and b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry-demo-08.jpg differ diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css new file mode 100644 index 000000000..e6c9e3960 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.css @@ -0,0 +1,285 @@ +:root { + --mineralGreen: 60, 98, 85; + --viridianGreen: 97, 135, 110; + --swampGreen: 166, 187, 141; + --fallGreen: 234, 231, 177; + --brinkPink: #FA7070; + --silverChalice: 178, 178, 178; + --white: 255, 255, 255; + --black: 0, 0, 0; + + --topBarHeight: 64px; + --itemPadding: 32px; + --containerGap: 8px; +} + +@keyframes zoom-in { + 0% { + transform: scale(1, 1); + } + + 100% { + transform: scale(1.30, 1.30); + } +} + +body, html { + margin: 0; + padding: 0; +} + +.app { + min-height: 100vh; + background-color: rgba(var(--viridianGreen), 0.5); +} + +.header { + height: var(--topBarHeight); + padding: 0 24px; + display: flex; + justify-content: space-between; + align-items: center; + background-color: rgba(var(--mineralGreen), 1); +} + +.header-text { + color: white; +} + +.container { + display: flex; + gap: 24px; + padding: var(--itemPadding); +} + +.item { + width: 50%; +} + +.item--scrollable { + overflow-y: scroll; + height: calc(100vh - var(--topBarHeight) - (var(--itemPadding) * 2)); +} + +.thumbnails-container { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + align-items: center; +} + +.thumbnail-item { + width: calc(25% - var(--containerGap)); + cursor: pointer; + position: relative; +} + +.thumbnail-item:hover { + z-index: 1; + animation: zoom-in 0.1s forwards; +} + +.thumbnail-item--selected { + outline: 3px solid rgba(var(--fallGreen), 1); + outline-offset: -3px; +} + +.badge { + width: 16px; + height: 16px; + display: flex; + justify-content: center; + align-items: center; + padding: 4px; + position: absolute; + right: 8px; + bottom: 8px; + border-radius: 50%; + border: 2px solid rgba(var(--fallGreen), 1); + background-color: rgba(var(--swampGreen), 1); +} + +.check { + display: inline-block; + transform: rotate(45deg); + border-bottom: 2px solid white; + border-right: 2px solid white; + width: 6px; + height: 12px; +} + +.img { + width: 100%; + height: 100%; + object-fit: cover; +} + +.actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; + padding: 0 0 16px 0; + gap: 8px; +} + +.select { + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--swampGreen), 0.5); + background-color: rgba(var(--swampGreen), 1); +} + +.select:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.btn { + outline: none; + padding: 16px; + cursor: pointer; + font-weight: 700; + color: rgba(var(--black), 1); + border: 2px solid rgba(var(--black), 0.5); +} + +.btn--primary { + background-color: rgba(var(--mineralGreen), 1); +} + +.btn--primary:hover:not([disabled]) { + background-color: rgba(var(--mineralGreen), 0.85); +} + +.btn--secondary { + background-color: rgba(var(--viridianGreen), 0.5); +} + +.btn--secondary:hover:not([disabled]) { + background-color: rgba(var(--swampGreen), 0.25); +} + +.btn--success { + background-color: rgba(var(--fallGreen), 1); +} + +.btn--success:hover:not([disabled]) { + background-color: rgba(var(--fallGreen), 0.85); +} + +.btn:disabled { + cursor: not-allowed; + background-color: rgba(var(--silverChalice), 1); + color: rgba(var(--black), 0.5); + border: 2px solid rgba(var(--black), 0.25); +} + +.previewContainer { + margin-bottom: 16px; + display: flex; + width: 100%; + height: 40vh; + overflow: scroll; + border: 3px solid rgba(var(--black), 1); +} + +.previewContainer--disabled { + background-color: rgba(var(--black), 0.1); + cursor: not-allowed; +} + +.canvas { + margin: auto; + display: none; +} + +.canvas--ready { + display: block; +} + +.spinnerContainer { + display: flex; + gap: 8px; + flex-direction: column; + align-content: center; + align-items: center; + margin: auto; +} + +.spinnerContainer--hidden { + display: none; +} + +.spinnerText { + margin: 0; + color: rgba(var(--mineralGreen), 1); +} + +.spinner { + display: inline-block; + width: 50px; + height: 50px; + margin: auto; + border: 3px solid rgba(var(--mineralGreen), 0.3); + border-radius: 50%; + border-top-color: rgba(var(--mineralGreen), 0.9); + animation: spin 1s ease-in-out infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +.loggerContainer { + display: flex; + flex-direction: column; + gap: 8px; + padding: 0 8px 8px 8px; + width: 100%; + min-height: 30vh; + max-height: 30vh; + overflow: scroll; + border-left: 3px solid rgba(var(--black), 0.25); +} + +.logger-title { + display: flex; + align-items: center; + padding: 8px; + position: sticky; + height: 40px; + min-height: 40px; + top: 0; + left: 0; + background-color: rgba(var(--viridianGreen), 1); + font-size: 24px; + font-weight: 700; + margin: 0; +} + +.logger-item { + font-size: 14px; + padding: 8px; + border: 2px solid #5a5a5a; + color: white; +} + +.logger--primary { + background-color: #13315a; +} + +.logger--success { + background-color: #385a4e; +} + +.logger--error { + background-color: #5a1a24; +} \ No newline at end of file diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html new file mode 100644 index 000000000..7ce52f927 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.html @@ -0,0 +1,49 @@ + + + + + + + Photo Library Collage + + + + +

+
+

+ Photo Library Collage +

+
+
+
+ +
+
+
+
+
+ + + + +
+
+
+
+

+
+ +
+
+

Logger:

+
+
+
+
+
+ + + + + diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js new file mode 100644 index 000000000..983b34d9a --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/index.js @@ -0,0 +1,228 @@ +import { + createImageFile, + loadImage, + weakRefCache, + LAYOUTS, + images, + THUMBNAIL_PARAMS, + stateObj, +} from "./utils.js"; + +export const state = new Proxy(stateObj, { + set(target, property, value) { + const previousValue = target[property]; + + target[property] = value; + + if (previousValue !== value) { + handleStateChange(target); + } + + return true; + }, +}); + +// Elements. +const thumbnailsContainerEl = document.querySelector(".thumbnails-container"); +const selectEl = document.querySelector(".select"); +const previewContainerEl = document.querySelector(".previewContainer"); +const canvasEl = document.querySelector(".canvas"); +const createCollageBtn = document.querySelector(".btn-create-collage"); +const startOverBtn = document.querySelector(".btn-start-over"); +const downloadBtn = document.querySelector(".btn-download"); +const spinnerContainerEl = document.querySelector(".spinnerContainer"); +const spinnerTextEl = document.querySelector(".spinnerText"); +const loggerContainerEl = document.querySelector(".loggerContainer"); + +// Renders. +// Render thumbnails previews. +images.forEach((img) => { + const thumbnail = document.createElement("div"); + thumbnail.classList.add("thumbnail-item"); + + thumbnail.innerHTML = ` + + `; + + thumbnail.addEventListener("click", (e) => handleSelection(e, img)); + + thumbnailsContainerEl.appendChild(thumbnail); +}); +// Render layouts select. +LAYOUTS.forEach((layout) => { + const option = document.createElement("option"); + option.value = JSON.stringify(layout); + option.innerHTML = layout.name; + selectEl.appendChild(option); +}); + +const handleStateChange = (state) => { + if (state.loading) { + selectEl.disabled = true; + createCollageBtn.disabled = true; + startOverBtn.disabled = true; + downloadBtn.disabled = true; + previewContainerEl.classList.add("previewContainer--disabled"); + spinnerContainerEl.classList.remove("spinnerContainer--hidden"); + spinnerTextEl.innerText = "Loading..."; + canvasEl.classList.remove("canvas--ready"); + } else if (!state.loading) { + selectEl.disabled = false; + createCollageBtn.disabled = false; + startOverBtn.disabled = false; + downloadBtn.disabled = false; + previewContainerEl.classList.remove("previewContainer--disabled"); + spinnerContainerEl.classList.add("spinnerContainer--hidden"); + canvasEl.classList.add("canvas--ready"); + } + + if (!state.selectedImages.size) { + createCollageBtn.disabled = true; + document.querySelectorAll(".badge").forEach((item) => item.remove()); + } else if (state.selectedImages.size && !state.loading) { + createCollageBtn.disabled = false; + } + + if (!state.collageRendered) { + downloadBtn.disabled = true; + } else if (state.collageRendered) { + downloadBtn.disabled = false; + } +}; +handleStateChange(state); + +const handleSelection = (e, imgName) => { + const imgEl = e.currentTarget; + + imgEl.classList.toggle("thumbnail-item--selected"); + + if (state.selectedImages.has(imgName)) { + state.selectedImages.delete(imgName); + state.selectedImages = new Set(state.selectedImages); + imgEl.querySelector(".badge")?.remove(); + } else { + state.selectedImages = new Set(state.selectedImages.add(imgName)); + + const badge = document.createElement("div"); + badge.classList.add("badge"); + badge.innerHTML = ` +
+ `; + imgEl.prepend(badge); + } +}; + +// Make a wrapper function. +let getCachedImage; +(async () => { + getCachedImage = await weakRefCache(loadImage); +})(); + +const calculateGridRows = (blobsLength) => + Math.ceil(blobsLength / state.currentLayout.columns); + +const drawCollage = (images) => { + state.drawing = true; + + let context = canvasEl.getContext("2d"); + + /** + * Calculate canvas dimensions based on the current layout. + * */ + context.canvas.width = + state.currentLayout.itemWidth * state.currentLayout.columns; + context.canvas.height = + calculateGridRows(images.length) * state.currentLayout.itemHeight; + + let currentRow = 0; + let currentCanvasDx = 0; + let currentCanvasDy = 0; + + for (let i = 0; i < images.length; i++) { + /** + * Get current row of the collage. + * */ + if (i % state.currentLayout.columns === 0) { + currentRow += 1; + currentCanvasDx = 0; + + if (currentRow > 1) { + currentCanvasDy += state.currentLayout.itemHeight; + } + } + + context.drawImage( + images[i], + 0, + 0, + images[i].width, + images[i].height, + currentCanvasDx, + currentCanvasDy, + state.currentLayout.itemWidth, + state.currentLayout.itemHeight, + ); + + currentCanvasDx += state.currentLayout.itemWidth; + } + + state.drawing = false; + state.collageRendered = true; +}; + +const createCollage = async () => { + state.loading = true; + + const images = []; + + for (const image of state.selectedImages.values()) { + const blobImage = await getCachedImage(image.img); + + const url = URL.createObjectURL(blobImage); + const img = await createImageFile(url); + + images.push(img); + URL.revokeObjectURL(url); + } + + state.loading = false; + + drawCollage(images); +}; + +/** + * Clear all settled data to start over. + * */ +const startOver = () => { + state.selectedImages = new Set(); + state.collageRendered = false; + const context = canvasEl.getContext("2d"); + context.clearRect(0, 0, canvasEl.width, canvasEl.height); + + document + .querySelectorAll(".thumbnail-item--selected") + .forEach((item) => item.classList.remove("thumbnail-item--selected")); + + loggerContainerEl.innerHTML = '

Logger:

'; +}; + +const downloadCollage = () => { + const date = new Date(); + const fileName = `Collage-${date.getDay()}-${date.getMonth()}-${date.getFullYear()}.png`; + const img = canvasEl.toDataURL("image/png"); + const link = document.createElement("a"); + link.download = fileName; + link.href = img; + link.click(); + link.remove(); +}; + +const changeLayout = ({ target }) => { + state.currentLayout = JSON.parse(target.value); +}; + +// Listeners. +selectEl.addEventListener("change", changeLayout); +createCollageBtn.addEventListener("click", createCollage); +startOverBtn.addEventListener("click", startOver); +downloadBtn.addEventListener("click", downloadCollage); diff --git a/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js new file mode 100644 index 000000000..f0140c116 --- /dev/null +++ b/1-js/99-js-misc/07-weakref-finalizationregistry/weakref-finalizationregistry.view/utils.js @@ -0,0 +1,321 @@ +const loggerContainerEl = document.querySelector(".loggerContainer"); + +export const images = [ + { + img: "https://images.unsplash.com/photo-1471357674240-e1a485acb3e1", + }, + { + img: "https://images.unsplash.com/photo-1589118949245-7d38baf380d6", + }, + { + img: "https://images.unsplash.com/photo-1527631746610-bca00a040d60", + }, + { + img: "https://images.unsplash.com/photo-1500835556837-99ac94a94552", + }, + { + img: "https://images.unsplash.com/photo-1503220317375-aaad61436b1b", + }, + { + img: "https://images.unsplash.com/photo-1501785888041-af3ef285b470", + }, + { + img: "https://images.unsplash.com/photo-1528543606781-2f6e6857f318", + }, + { + img: "https://images.unsplash.com/photo-1523906834658-6e24ef2386f9", + }, + { + img: "https://images.unsplash.com/photo-1539635278303-d4002c07eae3", + }, + { + img: "https://images.unsplash.com/photo-1533105079780-92b9be482077", + }, + { + img: "https://images.unsplash.com/photo-1516483638261-f4dbaf036963", + }, + { + img: "https://images.unsplash.com/photo-1502791451862-7bd8c1df43a7", + }, + { + img: "https://plus.unsplash.com/premium_photo-1663047367140-91adf819d007", + }, + { + img: "https://images.unsplash.com/photo-1506197603052-3cc9c3a201bd", + }, + { + img: "https://images.unsplash.com/photo-1517760444937-f6397edcbbcd", + }, + { + img: "https://images.unsplash.com/photo-1518684079-3c830dcef090", + }, + { + img: "https://images.unsplash.com/photo-1505832018823-50331d70d237", + }, + { + img: "https://images.unsplash.com/photo-1524850011238-e3d235c7d4c9", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661277758451-b5053309eea1", + }, + { + img: "https://images.unsplash.com/photo-1541410965313-d53b3c16ef17", + }, + { + img: "https://images.unsplash.com/photo-1528702748617-c64d49f918af", + }, + { + img: "https://images.unsplash.com/photo-1502003148287-a82ef80a6abc", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661281272544-5204ea3a481a", + }, + { + img: "https://images.unsplash.com/photo-1503457574462-bd27054394c1", + }, + { + img: "https://images.unsplash.com/photo-1499363536502-87642509e31b", + }, + { + img: "https://images.unsplash.com/photo-1551918120-9739cb430c6d", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661382219642-43e54f7e81d7", + }, + { + img: "https://images.unsplash.com/photo-1497262693247-aa258f96c4f5", + }, + { + img: "https://images.unsplash.com/photo-1525254134158-4fd5fdd45793", + }, + { + img: "https://plus.unsplash.com/premium_photo-1661274025419-4c54107d5c48", + }, + { + img: "https://images.unsplash.com/photo-1553697388-94e804e2f0f6", + }, + { + img: "https://images.unsplash.com/photo-1574260031597-bcd9eb192b4f", + }, + { + img: "https://images.unsplash.com/photo-1536323760109-ca8c07450053", + }, + { + img: "https://images.unsplash.com/photo-1527824404775-dce343118ebc", + }, + { + img: "https://images.unsplash.com/photo-1612278675615-7b093b07772d", + }, + { + img: "https://images.unsplash.com/photo-1522010675502-c7b3888985f6", + }, + { + img: "https://images.unsplash.com/photo-1501555088652-021faa106b9b", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469435-27e091439169", + }, + { + img: "https://images.unsplash.com/photo-1506012787146-f92b2d7d6d96", + }, + { + img: "https://images.unsplash.com/photo-1511739001486-6bfe10ce785f", + }, + { + img: "https://images.unsplash.com/photo-1553342385-111fd6bc6ab3", + }, + { + img: "https://images.unsplash.com/photo-1516546453174-5e1098a4b4af", + }, + { + img: "https://images.unsplash.com/photo-1527142879-95b61a0b8226", + }, + { + img: "https://images.unsplash.com/photo-1520466809213-7b9a56adcd45", + }, + { + img: "https://images.unsplash.com/photo-1516939884455-1445c8652f83", + }, + { + img: "https://images.unsplash.com/photo-1545389336-cf090694435e", + }, + { + img: "https://plus.unsplash.com/premium_photo-1669223469455-b7b734c838f4", + }, + { + img: "https://images.unsplash.com/photo-1454391304352-2bf4678b1a7a", + }, + { + img: "https://images.unsplash.com/photo-1433838552652-f9a46b332c40", + }, + { + img: "https://images.unsplash.com/photo-1506125840744-167167210587", + }, + { + img: "https://images.unsplash.com/photo-1522199873717-bc67b1a5e32b", + }, + { + img: "https://images.unsplash.com/photo-1495904786722-d2b5a19a8535", + }, + { + img: "https://images.unsplash.com/photo-1614094082869-cd4e4b2905c7", + }, + { + img: "https://images.unsplash.com/photo-1474755032398-4b0ed3b2ae5c", + }, + { + img: "https://images.unsplash.com/photo-1501554728187-ce583db33af7", + }, + { + img: "https://images.unsplash.com/photo-1515859005217-8a1f08870f59", + }, + { + img: "https://images.unsplash.com/photo-1531141445733-14c2eb7d4c1f", + }, + { + img: "https://images.unsplash.com/photo-1500259783852-0ca9ce8a64dc", + }, + { + img: "https://images.unsplash.com/photo-1510662145379-13537db782dc", + }, + { + img: "https://images.unsplash.com/photo-1573790387438-4da905039392", + }, + { + img: "https://images.unsplash.com/photo-1512757776214-26d36777b513", + }, + { + img: "https://images.unsplash.com/photo-1518855706573-84de4022b69b", + }, + { + img: "https://images.unsplash.com/photo-1500049242364-5f500807cdd7", + }, + { + img: "https://images.unsplash.com/photo-1528759335187-3b683174c86a", + }, +]; +export const THUMBNAIL_PARAMS = "w=240&h=240&fit=crop&auto=format"; + +// Console styles. +export const CONSOLE_BASE_STYLES = [ + "font-size: 12px", + "padding: 4px", + "border: 2px solid #5a5a5a", + "color: white", +].join(";"); +export const CONSOLE_PRIMARY = [ + CONSOLE_BASE_STYLES, + "background-color: #13315a", +].join(";"); +export const CONSOLE_SUCCESS = [ + CONSOLE_BASE_STYLES, + "background-color: #385a4e", +].join(";"); +export const CONSOLE_ERROR = [ + CONSOLE_BASE_STYLES, + "background-color: #5a1a24", +].join(";"); + +// Layouts. +export const LAYOUT_4_COLUMNS = { + name: "Layout 4 columns", + columns: 4, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUT_8_COLUMNS = { + name: "Layout 8 columns", + columns: 8, + itemWidth: 240, + itemHeight: 240, +}; +export const LAYOUTS = [LAYOUT_4_COLUMNS, LAYOUT_8_COLUMNS]; + +export const createImageFile = async (src) => + new Promise((resolve, reject) => { + const img = new Image(); + img.src = src; + img.onload = () => resolve(img); + img.onerror = () => reject(new Error("Failed to construct image.")); + }); + +export const loadImage = async (url) => { + try { + const response = await fetch(url); + if (!response.ok) { + throw new Error(String(response.status)); + } + + return await response.blob(); + } catch (e) { + console.log(`%cFETCHED_FAILED: ${e}`, CONSOLE_ERROR); + } +}; + +export const weakRefCache = (fetchImg) => { + const imgCache = new Map(); + const registry = new FinalizationRegistry(({ imgName, size, type }) => { + const cachedImg = imgCache.get(imgName); + if (cachedImg && !cachedImg.deref()) { + imgCache.delete(imgName); + console.log( + `%cCLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`, + CONSOLE_ERROR, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--error"); + logEl.innerHTML = `CLEANED_IMAGE: Url: ${imgName}, Size: ${size}, Type: ${type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + } + }); + + return async (imgName) => { + const cachedImg = imgCache.get(imgName); + + if (cachedImg?.deref() !== undefined) { + console.log( + `%cCACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`, + CONSOLE_SUCCESS, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--success"); + logEl.innerHTML = `CACHED_IMAGE: Url: ${imgName}, Size: ${cachedImg.size}, Type: ${cachedImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + return cachedImg?.deref(); + } + + const newImg = await fetchImg(imgName); + console.log( + `%cFETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`, + CONSOLE_PRIMARY, + ); + + const logEl = document.createElement("div"); + logEl.classList.add("logger-item", "logger--primary"); + logEl.innerHTML = `FETCHED_IMAGE: Url: ${imgName}, Size: ${newImg.size}, Type: ${newImg.type}`; + loggerContainerEl.appendChild(logEl); + loggerContainerEl.scrollTop = loggerContainerEl.scrollHeight; + + imgCache.set(imgName, new WeakRef(newImg)); + registry.register(newImg, { + imgName, + size: newImg.size, + type: newImg.type, + }); + + return newImg; + }; +}; + +export const stateObj = { + loading: false, + drawing: true, + collageRendered: false, + currentLayout: LAYOUTS[0], + selectedImages: new Set(), +}; diff --git a/2-ui/1-document/01-browser-environment/article.md b/2-ui/1-document/01-browser-environment/article.md index f680554dd..eedc28fb3 100644 --- a/2-ui/1-document/01-browser-environment/article.md +++ b/2-ui/1-document/01-browser-environment/article.md @@ -1,12 +1,12 @@ # Browser environment, specs -The JavaScript language was initially created for web browsers. Since then it has evolved and become a language with many uses and platforms. +The JavaScript language was initially created for web browsers. Since then, it has evolved into a language with many uses and platforms. -A platform may be a browser, or a web-server or another *host*, even a coffee machine. Each of them provides platform-specific functionality. The JavaScript specification calls that a *host environment*. +A platform may be a browser, or a web-server or another *host*, or even a "smart" coffee machine if it can run JavaScript. Each of these provides platform-specific functionality. The JavaScript specification calls that a *host environment*. -A host environment provides own objects and functions additional to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. +A host environment provides its own objects and functions in addition to the language core. Web browsers give a means to control web pages. Node.js provides server-side features, and so on. -Here's a bird's-eye view of what we have when JavaScript runs in a web-browser: +Here's a bird's-eye view of what we have when JavaScript runs in a web browser: ![](windowObjects.svg) @@ -15,9 +15,9 @@ There's a "root" object called `window`. It has two roles: 1. First, it is a global object for JavaScript code, as described in the chapter . 2. Second, it represents the "browser window" and provides methods to control it. -For instance, here we use it as a global object: +For instance, we can use it as a global object: -```js run +```js run global function sayHi() { alert("Hello"); } @@ -26,17 +26,17 @@ function sayHi() { window.sayHi(); ``` -And here we use it as a browser window, to see the window height: +And we can use it as a browser window, to show the window height: ```js run alert(window.innerHeight); // inner window height ``` -There are more window-specific methods and properties, we'll cover them later. +There are more window-specific methods and properties, which we'll cover later. ## DOM (Document Object Model) -Document Object Model, or DOM for short, represents all page content as objects that can be modified. +The Document Object Model, or DOM for short, represents all page content as objects that can be modified. The `document` object is the main "entry point" to the page. We can change or create anything on the page using it. @@ -49,20 +49,18 @@ document.body.style.background = "red"; setTimeout(() => document.body.style.background = "", 1000); ``` -Here we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: - -- **DOM Living Standard** at +Here, we used `document.body.style`, but there's much, much more. Properties and methods are described in the specification: [DOM Living Standard](https://dom.spec.whatwg.org). ```smart header="DOM is not only for browsers" The DOM specification explains the structure of a document and provides objects to manipulate it. There are non-browser instruments that use DOM too. -For instance, server-side scripts that download HTML pages and process them can also use DOM. They may support only a part of the specification though. +For instance, server-side scripts that download HTML pages and process them can also use the DOM. They may support only a part of the specification though. ``` ```smart header="CSSOM for styling" -CSS rules and stylesheets are structured in a different way than HTML. There's a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/), that explains how they are represented as objects, and how to read and write them. +There's also a separate specification, [CSS Object Model (CSSOM)](https://www.w3.org/TR/cssom-1/) for CSS rules and stylesheets, that explains how they are represented as objects, and how to read and write them. -CSSOM is used together with DOM when we modify style rules for the document. In practice though, CSSOM is rarely required, because usually CSS rules are static. We rarely need to add/remove CSS rules from JavaScript, but that's also possible. +The CSSOM is used together with the DOM when we modify style rules for the document. In practice though, the CSSOM is rarely required, because we rarely need to modify CSS rules from JavaScript (usually we just add/remove CSS classes, not modify their CSS rules), but that's also possible. ``` ## BOM (Browser Object Model) @@ -71,7 +69,7 @@ The Browser Object Model (BOM) represents additional objects provided by the bro For instance: -- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differ between Windows/Linux/Mac etc). +- The [navigator](mdn:api/Window/navigator) object provides background information about the browser and the operating system. There are many properties, but the two most widely known are: `navigator.userAgent` -- about the current browser, and `navigator.platform` -- about the platform (can help to differentiate between Windows/Linux/Mac etc). - The [location](mdn:api/Window/location) object allows us to read the current URL and can redirect the browser to a new one. Here's how we can use the `location` object: @@ -83,12 +81,12 @@ if (confirm("Go to Wikipedia?")) { } ``` -Functions `alert/confirm/prompt` are also a part of BOM: they are directly not related to the document, but represent pure browser methods of communicating with the user. +The functions `alert/confirm/prompt` are also a part of the BOM: they are not directly related to the document, but represent pure browser methods for communicating with the user. ```smart header="Specifications" -BOM is the part of the general [HTML specification](https://html.spec.whatwg.org). +The BOM is a part of the general [HTML specification](https://html.spec.whatwg.org). -Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . +Yes, you heard that right. The HTML spec at is not only about the "HTML language" (tags, attributes), but also covers a bunch of objects, methods, and browser-specific DOM extensions. That's "HTML in broad terms". Also, some parts have additional specs listed at . ``` ## Summary @@ -96,20 +94,20 @@ Yes, you heard that right. The HTML spec at is no Talking about standards, we have: DOM specification -: Describes the document structure, manipulations and events, see . +: Describes the document structure, manipulations, and events, see . CSSOM specification -: Describes stylesheets and style rules, manipulations with them and their binding to documents, see . +: Describes stylesheets and style rules, manipulations with them, and their binding to documents, see . HTML specification : Describes the HTML language (e.g. tags) and also the BOM (browser object model) -- various browser functions: `setTimeout`, `alert`, `location` and so on, see . It takes the DOM specification and extends it with many additional properties and methods. Additionally, some classes are described separately at . -Please note these links, as there's so much stuff to learn it's impossible to cover and remember everything. +Please note these links, as there's so much to learn that it's impossible to cover everything and remember it all. -When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. +When you'd like to read about a property or a method, the Mozilla manual at is also a nice resource, but the corresponding spec may be better: it's more complex and longer to read, but will make your fundamental knowledge sound and complete. To find something, it's often convenient to use an internet search "WHATWG [term]" or "MDN [term]", e.g , . -Now we'll get down to learning DOM, because the document plays the central role in the UI. +Now, we'll get down to learning the DOM, because the document plays the central role in the UI. diff --git a/2-ui/1-document/01-browser-environment/windowObjects.svg b/2-ui/1-document/01-browser-environment/windowObjects.svg index 3b525b2cb..b7e18bb34 100644 --- a/2-ui/1-document/01-browser-environment/windowObjects.svg +++ b/2-ui/1-document/01-browser-environment/windowObjects.svg @@ -1 +1 @@ -windowdocumentObjectnavigatorscreenlocationframeshistoryArrayFunctionXMLHttpRequestBOMJavaScriptDOM \ No newline at end of file +windowdocumentObjectnavigatorscreenlocationframeshistoryArrayFunctionXMLHttpRequestBOMJavaScriptDOM \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/article.md b/2-ui/1-document/02-dom-nodes/article.md index f22452100..f7f2be91d 100644 --- a/2-ui/1-document/02-dom-nodes/article.md +++ b/2-ui/1-document/02-dom-nodes/article.md @@ -22,7 +22,13 @@ document.body.style.background = 'red'; // make the background red setTimeout(() => document.body.style.background = '', 3000); // return back ``` -That was just a glimpse of the DOM's power. Soon we'll learn more ways to manipulate the DOM, but first we need to know about its structure. +Here we used `style.background` to change the background color of `document.body`, but there are many other properties, such as: + +- `innerHTML` -- HTML contents of the node. +- `offsetWidth` -- the node width (in pixels) +- ...and so on. + +Soon we'll learn more ways to manipulate the DOM, but first we need to know about its structure. ## An example of the DOM @@ -32,10 +38,10 @@ Let's start with the following simple document: - About elks + About elk - The truth about elks. + The truth about elk. ``` @@ -45,7 +51,7 @@ The DOM represents HTML as a tree structure of tags. Here's how it looks:
@@ -60,7 +66,7 @@ Tags are *element nodes* (or just elements) and form the tree structure: ` The text inside elements forms *text nodes*, labelled as `#text`. A text node contains only a string. It may not have children and is always a leaf of the tree. -For instance, the `` tag has the text `"About elks"`. +For instance, the `<title>` tag has the text `"About elk"`. Please note the special characters in text nodes: @@ -79,13 +85,13 @@ Here are no space-only text nodes: ```html no-beautify <!DOCTYPE HTML> -<html><head><title>About elksThe truth about elks. +About elkThe truth about elk. ```
@@ -137,7 +143,7 @@ drawHtmlTree(node4, 'div.domtree', 690, 360); ````warn header="Tables always have ``" -An interesting "special case" is tables. By the DOM specification they must have ``, but HTML text may (officially) omit it. Then the browser creates `` in the DOM automatically. +An interesting "special case" is tables. By DOM specification they must have `` tag, but HTML text may omit it. Then the browser creates `` in the DOM automatically. For the HTML: @@ -154,7 +160,7 @@ let node5 = {"name":"TABLE","nodeType":1,"children":[{"name":"TBODY","nodeType": drawHtmlTree(node5, 'div.domtree', 600, 200); -You see? The `` appeared out of nowhere. You should keep this in mind while working with tables to avoid surprises. +You see? The `` appeared out of nowhere. We should keep this in mind while working with tables to avoid surprises. ```` ## Other node types @@ -167,7 +173,7 @@ For example, comments: - The truth about elks. + The truth about elk.
  1. An elk is a smart
  2. *!* @@ -182,7 +188,7 @@ For example, comments:
    @@ -193,7 +199,7 @@ We may think -- why is a comment added to the DOM? It doesn't affect the visual **Everything in HTML, even comments, becomes a part of the DOM.** -Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. We are not going to touch that node, we even don't draw it on diagrams for that reason, but it's there. +Even the `` directive at the very beginning of HTML is also a DOM node. It's in the DOM tree right before ``. Few people know about that. We are not going to touch that node, we even don't draw it on diagrams, but it's there. The `document` object that represents the whole document is, formally, a DOM node as well. @@ -206,15 +212,15 @@ There are [12 node types](https://dom.spec.whatwg.org/#node). In practice we usu ## See it for yourself -To see the DOM structure in real-time, try [Live DOM Viewer](http://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. +To see the DOM structure in real-time, try [Live DOM Viewer](https://software.hixie.ch/utilities/js/live-dom-viewer/). Just type in the document, and it will show up as a DOM at an instant. Another way to explore the DOM is to use the browser developer tools. Actually, that's what we use when developing. -To do so, open the web page [elks.html](elks.html), turn on the browser developer tools and switch to the Elements tab. +To do so, open the web page [elk.html](elk.html), turn on the browser developer tools and switch to the Elements tab. It should look like this: -![](elks.png) +![](elk.svg) You can see the DOM, click on elements, see their details and so on. @@ -224,7 +230,7 @@ Clicking the . The best way to learn the tools is to click here and there, read menus: most options are obvious. Later, when you know them in general, read the docs and pick up the rest. -DOM nodes have properties and methods that allow to travel between them, modify, move around the page and more. We'll get down to them in the next chapters. +DOM nodes have properties and methods that allow us to travel between them, modify them, move around the page, and more. We'll get down to them in the next chapters. diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.png b/2-ui/1-document/02-dom-nodes/domconsole0.png deleted file mode 100644 index 121c11d75..000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole0.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole0.svg b/2-ui/1-document/02-dom-nodes/domconsole0.svg new file mode 100644 index 000000000..eb99f193f --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/domconsole0.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole0@2x.png b/2-ui/1-document/02-dom-nodes/domconsole0@2x.png deleted file mode 100644 index a8953395c..000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole0@2x.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.png b/2-ui/1-document/02-dom-nodes/domconsole1.png deleted file mode 100644 index c04f015cf..000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole1.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/domconsole1.svg b/2-ui/1-document/02-dom-nodes/domconsole1.svg new file mode 100644 index 000000000..02ef5f0a6 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/domconsole1.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/domconsole1@2x.png b/2-ui/1-document/02-dom-nodes/domconsole1@2x.png deleted file mode 100644 index ce0fa0fff..000000000 Binary files a/2-ui/1-document/02-dom-nodes/domconsole1@2x.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/elks.html b/2-ui/1-document/02-dom-nodes/elk.html similarity index 86% rename from 2-ui/1-document/02-dom-nodes/elks.html rename to 2-ui/1-document/02-dom-nodes/elk.html index 7d29f3d4e..dc5d65f54 100644 --- a/2-ui/1-document/02-dom-nodes/elks.html +++ b/2-ui/1-document/02-dom-nodes/elk.html @@ -1,7 +1,7 @@ - The truth about elks. + The truth about elk.
    1. An elk is a smart
    2. diff --git a/2-ui/1-document/02-dom-nodes/elk.svg b/2-ui/1-document/02-dom-nodes/elk.svg new file mode 100644 index 000000000..448eea9d1 --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/elk.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/elks.png b/2-ui/1-document/02-dom-nodes/elks.png deleted file mode 100644 index 03177c40e..000000000 Binary files a/2-ui/1-document/02-dom-nodes/elks.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/elks@2x.png b/2-ui/1-document/02-dom-nodes/elks@2x.png deleted file mode 100644 index e8a15bd5b..000000000 Binary files a/2-ui/1-document/02-dom-nodes/elks@2x.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/inspect.png b/2-ui/1-document/02-dom-nodes/inspect.png deleted file mode 100644 index 075cf9308..000000000 Binary files a/2-ui/1-document/02-dom-nodes/inspect.png and /dev/null differ diff --git a/2-ui/1-document/02-dom-nodes/inspect.svg b/2-ui/1-document/02-dom-nodes/inspect.svg new file mode 100644 index 000000000..60696ec0d --- /dev/null +++ b/2-ui/1-document/02-dom-nodes/inspect.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/2-ui/1-document/02-dom-nodes/inspect@2x.png b/2-ui/1-document/02-dom-nodes/inspect@2x.png deleted file mode 100644 index 8743dd297..000000000 Binary files a/2-ui/1-document/02-dom-nodes/inspect@2x.png and /dev/null differ diff --git a/2-ui/1-document/03-dom-navigation/article.md b/2-ui/1-document/03-dom-navigation/article.md index 3e327b47e..b5f03098c 100644 --- a/2-ui/1-document/03-dom-navigation/article.md +++ b/2-ui/1-document/03-dom-navigation/article.md @@ -149,7 +149,7 @@ There are two important consequences: The first thing is nice. The second is tolerable, because we can use `Array.from` to create a "real" array from the collection, if we want array methods: ```js run - alert( Array.from(document.body.childNodes).filter ); // now it's there + alert( Array.from(document.body.childNodes).filter ); // function ``` ```warn header="DOM collections are read-only" @@ -201,7 +201,7 @@ The parent is available as `parentNode`. For example: -```js +```js run // parent of is alert( document.body.parentNode === document.documentElement ); // true @@ -214,7 +214,7 @@ alert( document.body.previousSibling ); // HTMLHeadElement ## Element-only navigation -Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if there exist. +Navigation properties listed above refer to *all* nodes. For instance, in `childNodes` we can see both text nodes, element nodes, and even comment nodes if they exist. But for many tasks we don't want text or comment nodes. We want to manipulate element nodes that represent tags and form the structure of the page. @@ -226,7 +226,7 @@ The links are similar to those given above, just with `Element` word inside: - `children` -- only those children that are element nodes. - `firstElementChild`, `lastElementChild` -- first and last element children. -- `previousElementSibling`, `nextElementSibling` -- neighbour elements. +- `previousElementSibling`, `nextElementSibling` -- neighbor elements. - `parentElement` -- parent element. ````smart header="Why `parentElement`? Can the parent be *not* an element?" @@ -280,12 +280,12 @@ Till now we described the basic navigation properties. Certain types of DOM elements may provide additional properties, specific to their type, for convenience. -Tables are a great example and a particularly important case for that. +Tables are a great example of that, and represent a particularly important case: **The ``** element supports (in addition to the given above) these properties: - `table.rows` -- the collection of `` elements of the table. - `table.caption/tHead/tFoot` -- references to elements ``, ``. -- `table.tBodies` -- the collection of `` elements (can be many according to the standard). +- `table.tBodies` -- the collection of `` elements (can be many according to the standard, but there will always be at least one -- even if it is not in the source HTML, the browser will put it in the DOM). **``, ``, ``** elements provide the `rows` property: - `tbody.rows` -- the collection of `` inside. @@ -311,8 +311,9 @@ An example of usage:
      `, `
      ``` @@ -320,9 +321,9 @@ The specification: [tabular data](https://html.spec.whatwg.org/multipage/tables. There are also additional navigation properties for HTML forms. We'll look at them later when we start working with forms. -# Summary +## Summary -Given a DOM node, we can go to its immediate neighbours using navigation properties. +Given a DOM node, we can go to its immediate neighbors using navigation properties. There are two main sets of them: diff --git a/2-ui/1-document/03-dom-navigation/dom-links-elements.svg b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg index 20da46c1c..fd0b2826a 100644 --- a/2-ui/1-document/03-dom-navigation/dom-links-elements.svg +++ b/2-ui/1-document/03-dom-navigation/dom-links-elements.svg @@ -1 +1 @@ -document.documentElement <HTML>document.body (if inside body)parent Element<DIV>next Element Siblingprevious Element Siblingchildrenfirst Element Child last Element Child \ No newline at end of file +document.documentElement <HTML>document.body (if inside body)parent Element<DIV>next Element Siblingprevious Element Siblingchildrenfirst Element Child last Element Child \ No newline at end of file diff --git a/2-ui/1-document/03-dom-navigation/dom-links.svg b/2-ui/1-document/03-dom-navigation/dom-links.svg index fccbff1d4..6c34bca4a 100644 --- a/2-ui/1-document/03-dom-navigation/dom-links.svg +++ b/2-ui/1-document/03-dom-navigation/dom-links.svg @@ -1 +1 @@ -documentdocument.documentElement <HTML>document.body (if inside body)parentNode<DIV>nextSiblingpreviousSiblingchildNodesfirstChild lastChild \ No newline at end of file +documentdocument.documentElement <HTML>document.body (if inside body)parentNode<DIV>nextSiblingpreviousSiblingchildNodesfirstChild lastChild \ No newline at end of file diff --git a/2-ui/1-document/04-searching-elements-dom/article.md b/2-ui/1-document/04-searching-elements-dom/article.md index 2f5416887..405129694 100644 --- a/2-ui/1-document/04-searching-elements-dom/article.md +++ b/2-ui/1-document/04-searching-elements-dom/article.md @@ -55,7 +55,7 @@ Also, there's a global variable named by `id` that references the element: ``` ```warn header="Please don't use id-named global variables to access elements" -This behavior is described [in the specification](http://www.whatwg.org/specs/web-apps/current-work/#dom-window-nameditem), so it's kind of standard. But it is supported mainly for compatibility. +This behavior is described [in the specification](https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object), but it is supported mainly for compatibility. The browser tries to help us by mixing namespaces of JS and DOM. That's fine for simple scripts, inlined into HTML, but generally isn't a good thing. There may be naming conflicts. Also, when one reads JS code and doesn't have HTML in view, it's not obvious where the variable comes from. @@ -71,7 +71,7 @@ If there are multiple elements with the same `id`, then the behavior of methods ``` ```warn header="Only `document.getElementById`, not `anyElem.getElementById`" -The method `getElementById` that can be called only on `document` object. It looks for the given `id` in the whole document. +The method `getElementById` can be called only on `document` object. It looks for the given `id` in the whole document. ``` ## querySelectorAll [#querySelectorAll] @@ -103,7 +103,7 @@ Here we look for all `
    3. ` elements that are last children: This method is indeed powerful, because any CSS selector can be used. ```smart header="Can use pseudo-classes as well" -Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `` to the most nested one). +Pseudo-classes in the CSS selector like `:hover` and `:active` are also supported. For instance, `document.querySelectorAll(':hover')` will return the collection with elements that the pointer is over now (in nesting order: from the outermost `` to the most nested one). ``` ## querySelector [#querySelector] @@ -116,9 +116,9 @@ In other words, the result is the same as `elem.querySelectorAll(css)[0]`, but t Previous methods were searching the DOM. -The [elem.matches(css)](http://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. +The [elem.matches(css)](https://dom.spec.whatwg.org/#dom-element-matches) does not look for anything, it merely checks if `elem` matches the given CSS-selector. It returns `true` or `false`. -The method comes in handy when we are iterating over elements (like in array or something) and trying to filter those that interest us. +The method comes in handy when we are iterating over elements (like in an array or something) and trying to filter out those that interest us. For instance: @@ -142,7 +142,7 @@ For instance: *Ancestors* of an element are: parent, the parent of parent, its parent and so on. The ancestors together form the chain of parents from the element to the top. -The method `elem.closest(css)` looks the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. +The method `elem.closest(css)` looks for the nearest ancestor that matches the CSS-selector. The `elem` itself is also included in the search. In other words, the method `closest` goes up from the element and checks each of parents. If it matches the selector, then the search stops, and the ancestor is returned. @@ -154,7 +154,7 @@ For instance:
      • Chapter 1
      • -
      • Chapter 1
      • +
      • Chapter 2
      @@ -178,7 +178,7 @@ So here we cover them mainly for completeness, while you can still find them in - `elem.getElementsByTagName(tag)` looks for elements with the given tag and returns the collection of them. The `tag` parameter can also be a star `"*"` for "any tags". - `elem.getElementsByClassName(className)` returns elements that have the given CSS class. -- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. very rarely used. +- `document.getElementsByName(name)` returns elements with the given `name` attribute, document-wide. Very rarely used. For instance: ```js @@ -363,7 +363,7 @@ There are 6 main methods to search for nodes in DOM: -By far the most used are `querySelector` and `querySelectorAll`, but `getElementBy*` can be sporadically helpful or found in the old scripts. +By far the most used are `querySelector` and `querySelectorAll`, but `getElement(s)By*` can be sporadically helpful or found in the old scripts. Besides that: diff --git a/2-ui/1-document/05-basic-dom-node-properties/article.md b/2-ui/1-document/05-basic-dom-node-properties/article.md index 6c0053d0b..99dde5bcd 100644 --- a/2-ui/1-document/05-basic-dom-node-properties/article.md +++ b/2-ui/1-document/05-basic-dom-node-properties/article.md @@ -10,7 +10,7 @@ Different DOM nodes may have different properties. For instance, an element node Each DOM node belongs to the corresponding built-in class. -The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](http://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. +The root of the hierarchy is [EventTarget](https://dom.spec.whatwg.org/#eventtarget), that is inherited by [Node](https://dom.spec.whatwg.org/#interface-node), and other DOM nodes inherit from it. Here's the picture, explanations to follow: @@ -18,16 +18,39 @@ Here's the picture, explanations to follow: The classes are: -- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class. Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. -- [Node](http://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are concrete node classes that inherit from it, namely: `Text` for text nodes, `Element` for element nodes and more exotic ones like `Comment` for comment nodes. -- [Element](http://dom.spec.whatwg.org/#interface-element) -- is a base class for DOM elements. It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. A browser supports not only HTML, but also XML and SVG. The `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` and `HTMLElement`. -- [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) -- is finally the basic class for all HTML elements. It is inherited by concrete HTML elements: +- [EventTarget](https://dom.spec.whatwg.org/#eventtarget) -- is the root "abstract" class for everything. + + Objects of that class are never created. It serves as a base, so that all DOM nodes support so-called "events", we'll study them later. + +- [Node](https://dom.spec.whatwg.org/#interface-node) -- is also an "abstract" class, serving as a base for DOM nodes. + + It provides the core tree functionality: `parentNode`, `nextSibling`, `childNodes` and so on (they are getters). Objects of `Node` class are never created. But there are other classes that inherit from it (and so inherit the `Node` functionality). + +- [Document](https://dom.spec.whatwg.org/#interface-document), for historical reasons often inherited by `HTMLDocument` (though the latest spec doesn't dictate it) -- is a document as a whole. + + The `document` global object belongs exactly to this class. It serves as an entry point to the DOM. + +- [CharacterData](https://dom.spec.whatwg.org/#interface-characterdata) -- an "abstract" class, inherited by: + - [Text](https://dom.spec.whatwg.org/#interface-text) -- the class corresponding to a text inside elements, e.g. `Hello` in `

      Hello

      `. + - [Comment](https://dom.spec.whatwg.org/#interface-comment) -- the class for comments. They are not shown, but each comment becomes a member of DOM. + +- [Element](https://dom.spec.whatwg.org/#interface-element) -- is the base class for DOM elements. + + It provides element-level navigation like `nextElementSibling`, `children` and searching methods like `getElementsByTagName`, `querySelector`. + + A browser supports not only HTML, but also XML and SVG. So the `Element` class serves as a base for more specific classes: `SVGElement`, `XMLElement` (we don't need them here) and `HTMLElement`. + +- Finally, [HTMLElement](https://html.spec.whatwg.org/multipage/dom.html#htmlelement) is the basic class for all HTML elements. We'll work with it most of the time. + + It is inherited by concrete HTML elements: - [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) -- the class for `` elements, - [HTMLBodyElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlbodyelement) -- the class for `` elements, - [HTMLAnchorElement](https://html.spec.whatwg.org/multipage/semantics.html#htmlanchorelement) -- the class for `` elements, - - ...and so on, each tag has its own class that may provide specific properties and methods. + - ...and so on. + +There are many other tags with their own classes that may have specific properties and methods, while some elements, such as ``, `
      `, `
      ` do not have any specific properties, so they are instances of `HTMLElement` class. -So, the full set of properties and methods of a given node comes as the result of the inheritance. +So, the full set of properties and methods of a given node comes as the result of the chain of inheritance. For example, let's consider the DOM object for an `` element. It belongs to [HTMLInputElement](https://html.spec.whatwg.org/multipage/forms.html#htmlinputelement) class. @@ -36,7 +59,7 @@ It gets properties and methods as a superposition of (listed in inheritance orde - `HTMLInputElement` -- this class provides input-specific properties, - `HTMLElement` -- it provides common HTML element methods (and getters/setters), - `Element` -- provides generic element methods, -- `Node` -- provides common DOM node properties,. +- `Node` -- provides common DOM node properties, - `EventTarget` -- gives the support for events (to be covered), - ...and finally it inherits from `Object`, so "plain object" methods like `hasOwnProperty` are also available. @@ -128,13 +151,13 @@ For instance: ```html run - ``` -**Beware: unlike `innerHTML`, writing to `outerHTML` does not change the element. Instead, it replaces it as a whole in the outer context.** +**Beware: unlike `innerHTML`, writing to `outerHTML` does not change the element. Instead, it replaces it in the DOM.** Yeah, sounds strange, and strange it is, that's why we make a separate note about it here. Take a look. @@ -302,7 +325,7 @@ Consider the example: div.outerHTML = '

      A new element

      '; // (*) *!* - // Wow! The div is still the same! + // Wow! 'div' is still the same! */!* alert(div.outerHTML); //
      Hello, world!
      (**) @@ -310,18 +333,18 @@ Consider the example: Looks really odd, right? -In the line `(*)` we replaced `div` with `

      A new element

      `. In the outer document we can see the new content instead of the `
      `. But, as we can see in line `(**)`, the old `div` variable is still the same! +In the line `(*)` we replaced `div` with `

      A new element

      `. In the outer document (the DOM) we can see the new content instead of the `
      `. But, as we can see in line `(**)`, the value of the old `div` variable hasn't changed! -The `outerHTML` assignment does not modify the DOM element, but removes it from the outer context and inserts a new piece of HTML instead of it. +The `outerHTML` assignment does not modify the DOM element (the object referenced by, in this case, the variable 'div'), but removes it from the DOM and inserts the new HTML in its place. So what happened in `div.outerHTML=...` is: - `div` was removed from the document. -- Another HTML `

      A new element

      ` was inserted instead. -- `div` still has the old value. The new HTML wasn't saved to any variable. +- Another piece of HTML `

      A new element

      ` was inserted in its place. +- `div` still has its old value. The new HTML wasn't saved to any variable. It's so easy to make an error here: modify `div.outerHTML` and then continue to work with `div` as if it had the new content in it. But it doesn't. Such thing is correct for `innerHTML`, but not for `outerHTML`. -We can write to `elem.outerHTML`, but should keep in mind that it doesn't change the element we're writing to. It creates the new HTML on its place instead. We can get references to new elements by querying DOM. +We can write to `elem.outerHTML`, but should keep in mind that it doesn't change the element we're writing to ('elem'). It puts the new HTML in its place instead. We can get references to the new elements by querying the DOM. ## nodeValue/data: text node content @@ -397,7 +420,7 @@ Compare the two:
      ``` -But there are exclusions, for instance `input.value` synchronizes only from attribute -> to property, but not back: +But there are exclusions, for instance `input.value` synchronizes only from attribute -> property, but not back: ```html run @@ -298,16 +298,16 @@ For instance, here for the order state the attribute `order-state` is used:
      ``` -Why the attribute may be preferable to classes like `.order-state-new`, `.order-state-pending`, `order-state-canceled`? +Why would using an attribute be preferable to having classes like `.order-state-new`, `.order-state-pending`, `.order-state-canceled`? -That's because an attribute is more convenient to manage. The state can be changed as easy as: +Because an attribute is more convenient to manage. The state can be changed as easy as: ```js // a bit simpler than removing old/adding a new class div.setAttribute('order-state', 'canceled'); ``` -But there may be a possible problem with custom attributes. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, more attributes appear to suit the needs of developers. There may be unexpected effects in such case. +But there may be a possible problem with custom attributes. What if we use a non-standard attribute for our purposes and later the standard introduces it and makes it do something? The HTML language is alive, it grows, and more attributes appear to suit the needs of developers. There may be unexpected effects in such case. To avoid conflicts, there exist [data-*](https://html.spec.whatwg.org/#embedding-custom-non-visible-data-with-the-data-*-attributes) attributes. diff --git a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md index e127bc0ef..40c75dff3 100644 --- a/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md +++ b/2-ui/1-document/07-modifying-document/1-createtextnode-vs-innerhtml/task.md @@ -6,7 +6,7 @@ importance: 5 We have an empty DOM element `elem` and a string `text`. -Which of these 3 commands do exactly the same? +Which of these 3 commands will do exactly the same? 1. `elem.append(document.createTextNode(text))` 2. `elem.innerHTML = text` diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md index 15238fcf4..1414e90c1 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.md @@ -39,15 +39,19 @@ The clock-managing functions: ```js let timerId; -function clockStart() { // run the clock - timerId = setInterval(update, 1000); +function clockStart() { // run the clock + if (!timerId) { // only set a new interval if the clock is not running + timerId = setInterval(update, 1000); + } update(); // (*) } function clockStop() { clearInterval(timerId); - timerId = null; + timerId = null; // (**) } ``` Please note that the call to `update()` is not only scheduled in `clockStart()`, but immediately run in the line `(*)`. Otherwise the visitor would have to wait till the first execution of `setInterval`. And the clock would be empty till then. + +Also it is important to set a new interval in `clockStart()` only when the clock is not running. Otherways clicking the start button several times would set multiple concurrent intervals. Even worse - we would only keep the `timerID` of the last interval, losing references to all others. Then we wouldn't be able to stop the clock ever again! Note that we need to clear the `timerID` when the clock is stopped in the line `(**)`, so that it can be started again by running `clockStart()`. diff --git a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html index 1bf642b10..84ee26f19 100644 --- a/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/10-clock-setinterval/solution.view/index.html @@ -43,15 +43,19 @@ } function clockStart() { - timerId = setInterval(update, 1000); + // set a new interval only if the clock is stopped + // otherwise we would rewrite the timerID reference to the running interval and wouldn't be able to stop the clock ever again + if (!timerId) { + timerId = setInterval(update, 1000); + } update(); // <-- start right now, don't wait 1 second till the first setInterval works } function clockStop() { clearInterval(timerId); + timerId = null; // <-- clear timerID to indicate that the clock has been stopped, so that it is possible to start it again in clockStart() } - clockStart(); diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md index 25dd58b0c..49243e8e3 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.md +++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.md @@ -1,19 +1,18 @@ The solution is short, yet may look a bit tricky, so here I provide it with extensive comments: - ```js -let sortedRows = Array.from(table.rows) - .slice(1) - .sort((rowA, rowB) => rowA.cells[0].innerHTML > rowB.cells[0].innerHTML ? 1 : -1); +let sortedRows = Array.from(table.tBodies[0].rows) // 1 + .sort((rowA, rowB) => rowA.cells[0].innerHTML.localeCompare(rowB.cells[0].innerHTML)); -table.tBodies[0].append(...sortedRows); +table.tBodies[0].append(...sortedRows); // (3) ``` -1. Get all ``, like `table.querySelectorAll('tr')`, then make an array from them, cause we need array methods. -2. The first TR (`table.rows[0]`) is actually a table header, so we take the rest by `.slice(1)`. -3. Then sort them comparing by the content of the first `` (the name field). -4. Now insert nodes in the right order by `.append(...sortedRows)`. +The step-by-step algorthm: + +1. Get all ``, from ``. +2. Then sort them comparing by the content of the first `` (the name field). +3. Now insert nodes in the right order by `.append(...sortedRows)`. - Tables always have an implicit element, so we need to take it and insert into it: a simple `table.append(...)` would fail. +We don't have to remove row elements, just "re-insert", they leave the old place automatically. - Please note: we don't have to remove them, just "re-insert", they leave the old place automatically. +P.S. In our case, there's an explicit `` in the table, but even if HTML table doesn't have ``, the DOM structure always has it. diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html index 81e985748..40692031a 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html +++ b/2-ui/1-document/07-modifying-document/12-sort-table/solution.view/index.html @@ -1,37 +1,30 @@ - - - - - - - - - - - - - - - - - - - - - - - -
      NameSurnameAge
      JohnSmith10
      PeteBrown15
      AnnLee5
      + + + + + + + + + + + + + + + + + + + + +
      NameSurnameAge
      JohnSmith10
      PeteBrown15
      AnnLee5
      .........
      - - - - + table.tBodies[0].append(...sortedRows); + diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html index e41eb229f..9071c88ee 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html +++ b/2-ui/1-document/07-modifying-document/12-sort-table/source.view/index.html @@ -1,33 +1,27 @@ - - - - - - - - - - - - - - - - - - - - - - - -
      NameSurnameAge
      JohnSmith10
      PeteBrown15
      AnnLee5
      + + + + + + + + + + + + + + + + + + + + +
      NameSurnameAge
      JohnSmith10
      PeteBrown15
      AnnLee5
      .........
      - - - - + diff --git a/2-ui/1-document/07-modifying-document/12-sort-table/task.md b/2-ui/1-document/07-modifying-document/12-sort-table/task.md index 41d6fca29..7cdba35bc 100644 --- a/2-ui/1-document/07-modifying-document/12-sort-table/task.md +++ b/2-ui/1-document/07-modifying-document/12-sort-table/task.md @@ -6,33 +6,29 @@ importance: 5 There's a table: +```html run - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + +
      NameSurnameAge
      JohnSmith10
      PeteBrown15
      AnnLee5
      .........
      NameSurnameAge
      JohnSmith10
      PeteBrown15
      AnnLee5
      .........
      +``` There may be more rows in it. diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md index 6b85168b9..3d1f6698f 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/solution.md @@ -1,9 +1,9 @@ The HTML in the task is incorrect. That's the reason of the odd thing. -The browser has to fix it automatically. But there may be no text inside the ``: according to the spec only table-specific tags are allowed. So the browser adds `"aaa"` *before* the `
      `. +The browser has to fix it automatically. But there may be no text inside the `
      `: according to the spec only table-specific tags are allowed. So the browser shows `"aaa"` *before* the `
      `. Now it's obvious that when we remove the table, it remains. -The question can be easily answered by exploring the DOM using the browser tools. It shows `"aaa"` before the `
      `. +The question can be easily answered by exploring the DOM using the browser tools. You'll see `"aaa"` before the `
      `. The HTML standard specifies in detail how to process bad HTML, and such behavior of the browser is correct. diff --git a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md index 03064ed2c..861f70503 100644 --- a/2-ui/1-document/07-modifying-document/5-why-aaa/task.md +++ b/2-ui/1-document/07-modifying-document/5-why-aaa/task.md @@ -4,7 +4,11 @@ importance: 1 # Why does "aaa" remain? -Run the example. Why does `table.remove()` not delete the text `"aaa"`? +In the example below, the call `table.remove()` removes the table from the document. + +But if you run it, you can see that the text `"aaa"` is still visible. + +Why does that happen? ```html height=100 run
      @@ -18,6 +22,6 @@ Run the example. Why does `table.remove()` not delete the text `"aaa"`? alert(table); // the table, as it should be table.remove(); - // why there's still aaa in the document? + // why there's still "aaa" in the document? ``` diff --git a/2-ui/1-document/07-modifying-document/6-create-list/task.md b/2-ui/1-document/07-modifying-document/6-create-list/task.md index 43b0a34a7..a57e7e2d9 100644 --- a/2-ui/1-document/07-modifying-document/6-create-list/task.md +++ b/2-ui/1-document/07-modifying-document/6-create-list/task.md @@ -10,7 +10,7 @@ For every list item: 1. Ask a user about its content using `prompt`. 2. Create the `
    4. ` with it and add it to `
        `. -3. Continue until the user cancels the input (by pressing `key:Esc` or CANCEL in prompt). +3. Continue until the user cancels the input (by pressing `key:Esc` or via an empty entry). All elements should be created dynamically. diff --git a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md index 67bb5e13d..de8be56e9 100644 --- a/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md +++ b/2-ui/1-document/07-modifying-document/9-calendar-table/solution.md @@ -3,7 +3,7 @@ We'll create the table as a string: `"
    5. ...
      "`, and then assign it t The algorithm: 1. Create the table header with `` and weekday names. -1. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`). -2. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with ``. -3. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `` to the calendar. If that's a Sunday, then add a newline "</tr><tr>". -4. If the month has finished, but the table row is not yet full, add empty `` into it, to make it square. +2. Create the date object `d = new Date(year, month-1)`. That's the first day of `month` (taking into account that months in JavaScript start from `0`, not `1`). +3. First few cells till the first day of the month `d.getDay()` may be empty. Let's fill them in with ``. +4. Increase the day in `d`: `d.setDate(d.getDate()+1)`. If `d.getMonth()` is not yet the next month, then add the new cell `` to the calendar. If that's a Sunday, then add a newline "</tr><tr>". +5. If the month has finished, but the table row is not yet full, add empty `` into it, to make it square. diff --git a/2-ui/1-document/07-modifying-document/article.md b/2-ui/1-document/07-modifying-document/article.md index 9cdb79ddd..75ce1fbb0 100644 --- a/2-ui/1-document/07-modifying-document/article.md +++ b/2-ui/1-document/07-modifying-document/article.md @@ -1,12 +1,12 @@ # Modifying the document -DOM modifications is the key to create "live" pages. +DOM modification is the key to creating "live" pages. Here we'll see how to create new elements "on the fly" and modify the existing page content. ## Example: show a message -Let's see the methods on example. We'll add a message on the page that looks nicer than `alert`. +Let's demonstrate using an example. We'll add a message on the page that looks nicer than `alert`. Here's how it will look: @@ -28,7 +28,7 @@ Here's how it will look: */!* ``` -That was an HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are in the HTML or an external CSS file). +That was the HTML example. Now let's create the same `div` with JavaScript (assuming that the styles are in the HTML/CSS already). ## Creating an element @@ -48,21 +48,28 @@ To create DOM nodes, there are two methods: let textNode = document.createTextNode('Here I am'); ``` +Most of the time we need to create element nodes, such as the `div` for the message. + ### Creating the message -In our case the message is a `div` with `alert` class and the HTML in it: +Creating the message div takes 3 steps: ```js +// 1. Create
      element let div = document.createElement('div'); + +// 2. Set its class to "alert" div.className = "alert"; + +// 3. Fill it with the content div.innerHTML = "Hi there! You've read an important message."; ``` -We created the element, but as of now it's only in a variable. We can't see the element on the page, as it's not yet a part of the document. +We've created the element. But as of now it's only in a variable named `div`, not in the page yet. So we can't see it. ## Insertion methods -To make the `div` show up, we need to insert it somewhere into `document`. For instance, in `document.body`. +To make the `div` show up, we need to insert it somewhere into `document`. For instance, into `` element, referenced by `document.body`. There's a special method `append` for that: `document.body.append(div)`. @@ -90,14 +97,20 @@ Here's the full code: ``` -This set of methods provides more ways to insert: +Here we called `append` on `document.body`, but we can call `append` method on any other element, to put another element into it. For instance, we can append something to `
      ` by calling `div.append(anotherElement)`. -- `node.append(...nodes or strings)` -- append nodes or strings at the end of `node`, -- `node.prepend(...nodes or strings)` -- insert nodes or strings into the beginning of `node`, -- `node.before(...nodes or strings)` –- insert nodes or strings before the `node`, -- `node.after(...nodes or strings)` –- insert nodes or strings after the `node`, +Here are more insertion methods, they specify different places where to insert: + +- `node.append(...nodes or strings)` -- append nodes or strings *at the end* of `node`, +- `node.prepend(...nodes or strings)` -- insert nodes or strings *at the beginning* of `node`, +- `node.before(...nodes or strings)` –- insert nodes or strings *before* `node`, +- `node.after(...nodes or strings)` –- insert nodes or strings *after* `node`, - `node.replaceWith(...nodes or strings)` –- replaces `node` with the given nodes or strings. +Arguments of these methods are an arbitrary list of DOM nodes to insert, or text strings (that become text nodes automatically). + +Let's see them in action. + Here's an example of using these methods to add items to a list and the text before/after it: ```html autorun @@ -121,7 +134,7 @@ Here's an example of using these methods to add items to a list and the text bef ``` -Here's a visual picture what methods do: +Here's a visual picture of what the methods do: ![](before-prepend-append-after.svg) @@ -139,7 +152,7 @@ before after ``` -These methods can insert multiple lists of nodes and text pieces in a single call. +As said, these methods can insert multiple nodes and text pieces in a single call. For instance, here a string and an element are inserted: @@ -150,7 +163,7 @@ For instance, here a string and an element are inserted: ``` -All text is inserted *as text*. +Please note: the text is inserted "as text", not "as HTML", with proper escaping of characters such as `<`, `>`. So the final HTML is: @@ -166,7 +179,7 @@ In other words, strings are inserted in a safe way, like `elem.textContent` does So, these methods can only be used to insert DOM nodes or text pieces. -But what if we want to insert HTML "as html", with all tags and stuff working, like `elem.innerHTML`? +But what if we'd like to insert an HTML string "as html", with all tags and stuff working, in the same manner as `elem.innerHTML` does it? ## insertAdjacentHTML/Text/Element @@ -199,7 +212,7 @@ For instance:

      Bye

      ``` -That's how we can append an arbitrary HTML to the page. +That's how we can append arbitrary HTML to the page. Here's the picture of insertion variants: @@ -534,13 +547,13 @@ So if we need to add a lot of text into HTML dynamically, and we're at page load All these methods return `node`. -- Given a piece of HTML: `elem.insertAdjacentHTML(where, html)`, inserts depending on `where`: +- Given some HTML in `html`, `elem.insertAdjacentHTML(where, html)` inserts it depending on the value of `where`: - `"beforebegin"` -- insert `html` right before `elem`, - `"afterbegin"` -- insert `html` into `elem`, at the beginning, - `"beforeend"` -- insert `html` into `elem`, at the end, - `"afterend"` -- insert `html` right after `elem`. - Also there are similar methods `elem.insertAdjacentText` and `elem.insertAdjacentElement`, they insert text strings and elements, but they are rarely used. + Also there are similar methods, `elem.insertAdjacentText` and `elem.insertAdjacentElement`, that insert text strings and elements, but they are rarely used. - To append HTML to the page before it has finished loading: - `document.write(html)` diff --git a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg index 1811adea7..0843713ce 100644 --- a/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg +++ b/2-ui/1-document/07-modifying-document/before-prepend-append-after.svg @@ -1 +1 @@ -ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file +ol.afterol.appendol.prependol.before(…nodes or strings) \ No newline at end of file diff --git a/2-ui/1-document/07-modifying-document/insert-adjacent.svg b/2-ui/1-document/07-modifying-document/insert-adjacent.svg index 4b247fd72..e26fd023a 100644 --- a/2-ui/1-document/07-modifying-document/insert-adjacent.svg +++ b/2-ui/1-document/07-modifying-document/insert-adjacent.svg @@ -1 +1 @@ -ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file +ol.insertAdjacentHTML(*, html)afterendbeforeendafterbeginbeforebegin \ No newline at end of file diff --git a/2-ui/1-document/08-styles-and-classes/article.md b/2-ui/1-document/08-styles-and-classes/article.md index e6357c8e0..46aaa3b00 100644 --- a/2-ui/1-document/08-styles-and-classes/article.md +++ b/2-ui/1-document/08-styles-and-classes/article.md @@ -128,6 +128,14 @@ setTimeout(() => document.body.style.display = "", 1000); // back to normal If we set `style.display` to an empty string, then the browser applies CSS classes and its built-in styles normally, as if there were no such `style.display` property at all. +Also there is a special method for that, `elem.style.removeProperty('style property')`. So, We can remove a property like this: + +```js run +document.body.style.background = 'red'; //set background to red + +setTimeout(() => document.body.style.removeProperty('background'), 1000); // remove background after 1 second +``` + ````smart header="Full rewrite with `style.cssText`" Normally, we use `style.*` to assign individual style properties. We can't set the full style like `div.style="color: red; width: 100px"`, because `div.style` is an object, and it's read-only. @@ -249,7 +257,7 @@ For instance: ```smart header="Computed and resolved values" There are two concepts in [CSS](https://drafts.csswg.org/cssom/#resolved-values): -1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like `height:1em` or `font-size:125%`. +1. A *computed* style value is the value after all CSS rules and CSS inheritance is applied, as the result of the CSS cascade. It can look like `height:1em` or `font-size:125%`. 2. A *resolved* style value is the one finally applied to the element. Values like `1em` or `125%` are relative. The browser takes the computed value and makes all units fixed and absolute, for instance: `height:20px` or `font-size:16px`. For geometry properties resolved values may have a floating point, like `width:50.5px`. A long time ago `getComputedStyle` was created to get computed values, but it turned out that resolved values are much more convenient, and the standard changed. @@ -261,20 +269,6 @@ So nowadays `getComputedStyle` actually returns the resolved value of the proper We should always ask for the exact property that we want, like `paddingLeft` or `marginTop` or `borderTopWidth`. Otherwise the correct result is not guaranteed. For instance, if there are properties `paddingLeft/paddingTop`, then what should we get for `getComputedStyle(elem).padding`? Nothing, or maybe a "generated" value from known paddings? There's no standard rule here. - -There are other inconsistencies. As an example, some browsers (Chrome) show `10px` in the document below, and some of them (Firefox) -- do not: - -```html run - - -``` ```` ```smart header="Styles applied to `:visited` links are hidden!" @@ -282,7 +276,7 @@ Visited links may be colored using `:visited` CSS pseudoclass. But `getComputedStyle` does not give access to that color, because otherwise an arbitrary page could find out whether the user visited a link by creating it on the page and checking the styles. -JavaScript may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids to apply geometry-changing styles in `:visited`. That's to guarantee that there's no sideway for an evil page to test if a link was visited and hence to break the privacy. +JavaScript may not see the styles applied by `:visited`. And also, there's a limitation in CSS that forbids applying geometry-changing styles in `:visited`. That's to guarantee that there's no side way for an evil page to test if a link was visited and hence to break the privacy. ``` ## Summary diff --git a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md index be47cfd30..796039c2a 100644 --- a/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md +++ b/2-ui/1-document/09-size-and-scroll/1-get-scroll-height-bottom/task.md @@ -4,7 +4,7 @@ importance: 5 # What's the scroll from the bottom? -The `elem.scrollTop` property is the size of the scrolled out part from the top. How to get the size from the bottom scroll (let's call it `scrollBottom`)? +The `elem.scrollTop` property is the size of the scrolled out part from the top. How to get the size of the bottom scroll (let's call it `scrollBottom`)? Write the code that works for an arbitrary `elem`. diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html index ca9c4d579..8f855ecfa 100755 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/ball-half/index.html @@ -9,7 +9,7 @@ background-color: #00FF00; position: relative; } - + #ball { position: absolute; } @@ -20,7 +20,7 @@
      - . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . + . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
      @@ -38,4 +38,4 @@ - \ No newline at end of file + diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg index 515ac7f81..f5bd9f4f9 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md index c6fe6c3bb..afa1d8f50 100644 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.md @@ -24,17 +24,22 @@ ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px'; ``` -**Attention: the pitfall!** +Now the ball is finally centered. + +````warn header="Attention: the pitfall!" The code won't work reliably while `` has no width/height: ```html ``` +```` When the browser does not know the width/height of an image (from tag attributes or CSS), then it assumes them to equal `0` until the image finishes loading. -After the first load browser usually caches the image, and on next loads it will have the size immediately. But on the first load the value of `ball.offsetWidth` is `0`. That leads to wrong coordinates. +So the value of `ball.offsetWidth` will be `0` until the image loads. That leads to wrong coordinates in the code above. + +After the first load, the browser usually caches the image, and on reloads it will have the size immediately. But on the first load the value of `ball.offsetWidth` is `0`. We should fix that by adding `width/height` to ``: diff --git a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html index 9ebe6001e..9f21e5421 100755 --- a/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html +++ b/2-ui/1-document/09-size-and-scroll/4-put-ball-in-center/solution.view/index.html @@ -26,7 +26,7 @@ ``` - From the CSS standpoint, `width:auto` is perfectly normal, but in JavaScript we need an exact size in `px` that we can use in calculations. So here CSS width is useless at all. + From the CSS standpoint, `width:auto` is perfectly normal, but in JavaScript we need an exact size in `px` that we can use in calculations. So here CSS width is useless. -And there's one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar starts to bug with it, because a scrollbar takes the space from the content in some browsers. So the real width available for the content is *less* than CSS width. And `clientWidth/clientHeight` take that into account. +And there's one more reason: a scrollbar. Sometimes the code that works fine without a scrollbar becomes buggy with it, because a scrollbar takes the space from the content in some browsers. So the real width available for the content is *less* than CSS width. And `clientWidth/clientHeight` take that into account. ...But with `getComputedStyle(elem).width` the situation is different. Some browsers (e.g. Chrome) return the real inner width, minus the scrollbar, and some of them (e.g. Firefox) -- CSS width (ignore the scrollbar). Such cross-browser differences is the reason not to use `getComputedStyle`, but rather rely on geometry properties. @@ -268,7 +268,7 @@ Elements have the following geometry properties: - `offsetParent` -- is the nearest positioned ancestor or `td`, `th`, `table`, `body`. - `offsetLeft/offsetTop` -- coordinates relative to the upper-left edge of `offsetParent`. - `offsetWidth/offsetHeight` -- "outer" width/height of an element including borders. -- `clientLeft/clientTop` -- the distance from the upper-left outer corner the inner corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too. +- `clientLeft/clientTop` -- the distances from the upper-left outer corner to the upper-left inner (content + padding) corner. For left-to-right OS they are always the widths of left/top borders. For right-to-left OS the vertical scrollbar is on the left so `clientLeft` includes its width too. - `clientWidth/clientHeight` -- the width/height of the content including paddings, but without the scrollbar. - `scrollWidth/scrollHeight` -- the width/height of the content, just like `clientWidth/clientHeight`, but also include scrolled-out, invisible part of the element. - `scrollLeft/scrollTop` -- width/height of the scrolled out upper part of the element, starting from its upper-left corner. diff --git a/2-ui/1-document/09-size-and-scroll/metric-all.svg b/2-ui/1-document/09-size-and-scroll/metric-all.svg index e7a632a24..20a59e18d 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-all.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-all.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeightoffsetHeightscrollTopclientHeightoffsetTopclientLeftclientWidthclientTopoffsetLeftoffsetWidth \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg index ec3847e9e..e8dd3d60a 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top-rtl.svg @@ -1 +1 @@ -clientTop: 25px = borderclientLeft: 41px \ No newline at end of file +clientTop: 25px = borderclientLeft: 41px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg index c5479234f..8097afa78 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-left-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997.clientTop: 25px = borderclientLeft: 25px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg index feadb321d..2603b05fb 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxclientWidth = 20+284+20 = 324pxclientHeight: 240pxheight: 200pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg index 702a8922f..330d2a7c0 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-client-width-nopadding.svg @@ -1 +1 @@ -clientWidth: 284px = content widthCSS width: 300pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with padding: 0; width: 300px; \ No newline at end of file +clientWidth: 284px = content widthCSS width: 300pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with padding: 0; width: 300px; \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-css.svg b/2-ui/1-document/09-size-and-scroll/metric-css.svg index bf56a011d..1f2e5f780 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-css.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-css.svg @@ -1 +1 @@ -padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +padding: 20pxheight: 200pxpadding: 20pxborder 25pxpadding 20pxcontent width: 284pxborder 25pxpadding 20pxscrollbar 16pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg index 3ba87dada..2d108473e 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-parent.svg @@ -1 +1 @@ -offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file +offsetTop: 180pxoffsetLeft: 180pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoftposition: absolute; left: 180px; top: 180px;offsetParent <MAIN> <DIV> \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg index c6ce482be..4d30d90cc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-offset-width-height.svg @@ -1 +1 @@ -border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file +border 25pxpadding 20pxcontent width: 284pxheight: 200pxborder 25pxpadding 20pxscrollbar 16pxoffsetWidth = 25+20+284+20+16+25 = 390pxoffsetHeight: 290pxIntroduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg index 84a1d12c9..7f72de422 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-top.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/ IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollTopscrollHeight: 723px \ No newline at end of file diff --git a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg index c5232f396..75a24e3bc 100644 --- a/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg +++ b/2-ui/1-document/09-size-and-scroll/metric-scroll-width-height.svg @@ -1 +1 @@ -Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file +Introduction This Ecma Standard is based on several originating technologies, the most well known being JavaScript (Netscape) and JScript (Microsoft). The language was invented by Brendan Eich at Netscape and first appeared in that company’s Navigator 2.0 browser. It has appeared in all subsequent browsers from Netscape and in all browsers from Microsoft starting with Internet Explorer 3.0. The development of this Standard started in November 1996. The first edition of this Ecma Standard was adopted by the Ecma General Assembly of June 1997. That Ecma Standard was submitted to ISO/IEC JTC 1 for adoption under the fast-track procedure, and approved as international standard ISO/IEC 16262, in April 1998. The Ecma General Assembly of June 1998 approved the second edition of ECMA-262 to keep it fully aligned with ISO/IEC 16262. Changes between the first and the second edition are editorial in nature.scrollHeight: 723pxscrollWidth = 324px \ No newline at end of file diff --git a/2-ui/1-document/10-size-and-scroll-window/article.md b/2-ui/1-document/10-size-and-scroll-window/article.md index 306056151..08a2f6576 100644 --- a/2-ui/1-document/10-size-and-scroll-window/article.md +++ b/2-ui/1-document/10-size-and-scroll-window/article.md @@ -1,12 +1,12 @@ # Window sizes and scrolling -How to find out the width and height of the browser window? How to get the full width and height of the document, including the scrolled out part? How to scroll the page using JavaScript? +How do we find the width and height of the browser window? How do we get the full width and height of the document, including the scrolled out part? How do we scroll the page using JavaScript? -For most such requests, we can use the root document element `document.documentElement`, that corresponds to `` tag. But there are additional methods and peculiarities important enough to consider. +For this type of information, we can use the root document element `document.documentElement`, that corresponds to the `` tag. But there are additional methods and peculiarities to consider. ## Width/height of the window -To get window width and height we can use `clientWidth/clientHeight` of `document.documentElement`: +To get window width and height, we can use the `clientWidth/clientHeight` of `document.documentElement`: ![](document-client-width-height.svg) @@ -16,12 +16,12 @@ For instance, this button shows the height of your window: ``` -````warn header="Not `window.innerWidth/Height`" -Browsers also support properties `window.innerWidth/innerHeight`. They look like what we want. So why not to use them instead? +````warn header="Not `window.innerWidth/innerHeight`" +Browsers also support properties like `window.innerWidth/innerHeight`. They look like what we want, so why not to use them instead? -If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return width/height of the visible part of the document, available for the content. +If there exists a scrollbar, and it occupies some space, `clientWidth/clientHeight` provide the width/height without it (subtract it). In other words, they return the width/height of the visible part of the document, available for the content. -...And `window.innerWidth/innerHeight` include the scrollbar. +`window.innerWidth/innerHeight` includes the scrollbar. If there's a scrollbar, and it occupies some space, then these two lines show different values: ```js run @@ -29,7 +29,7 @@ alert( window.innerWidth ); // full window width alert( document.documentElement.clientWidth ); // window width minus the scrollbar ``` -In most cases we need the *available* window width: to draw or position something. That is: inside scrollbars if there are any. So we should use `documentElement.clientHeight/Width`. +In most cases, we need the *available* window width in order to draw or position something within scrollbars (if there are any), so we should use `documentElement.clientHeight/clientWidth`. ```` ```warn header="`DOCTYPE` is important" @@ -40,9 +40,9 @@ In modern HTML we should always write `DOCTYPE`. ## Width/height of the document -Theoretically, as the root document element is `documentElement.clientWidth/Height`, and it encloses all the content, we could measure document full size as `documentElement.scrollWidth/scrollHeight`. +Theoretically, as the root document element is `document.documentElement`, and it encloses all the content, we could measure the document's full size as `document.documentElement.scrollWidth/scrollHeight`. -But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Sounds like a nonsense, weird, right? +But on that element, for the whole page, these properties do not work as intended. In Chrome/Safari/Opera, if there's no scroll, then `documentElement.scrollHeight` may be even less than `documentElement.clientHeight`! Weird, right? To reliably obtain the full document height, we should take the maximum of these properties: @@ -60,11 +60,11 @@ Why so? Better don't ask. These inconsistencies come from ancient times, not a " ## Get the current scroll [#page-scroll] -DOM elements have their current scroll state in `elem.scrollLeft/scrollTop`. +DOM elements have their current scroll state in their `scrollLeft/scrollTop` properties. -For document scroll `document.documentElement.scrollLeft/Top` works in most browsers, except oldler WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. +For document scroll, `document.documentElement.scrollLeft/scrollTop` works in most browsers, except older WebKit-based ones, like Safari (bug [5991](https://bugs.webkit.org/show_bug.cgi?id=5991)), where we should use `document.body` instead of `document.documentElement`. -Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties `window.pageXOffset/pageYOffset`: +Luckily, we don't have to remember these peculiarities at all, because the scroll is available in the special properties, `window.pageXOffset/pageYOffset`: ```js run alert('Current scroll from the top: ' + window.pageYOffset); @@ -73,19 +73,25 @@ alert('Current scroll from the left: ' + window.pageXOffset); These properties are read-only. +```smart header="Also available as `window` properties `scrollX` and `scrollY`" +For historical reasons, both properties exist, but they are the same: +- `window.pageXOffset` is an alias of `window.scrollX`. +- `window.pageYOffset` is an alias of `window.scrollY`. +``` + ## Scrolling: scrollTo, scrollBy, scrollIntoView [#window-scroll] ```warn -To scroll the page from JavaScript, its DOM must be fully built. +To scroll the page with JavaScript, its DOM must be fully built. -For instance, if we try to scroll the page from the script in ``, it won't work. +For instance, if we try to scroll the page with a script in ``, it won't work. ``` Regular elements can be scrolled by changing `scrollTop/scrollLeft`. -We can do the same for the page using `document.documentElement.scrollTop/Left` (except Safari, where `document.body.scrollTop/Left` should be used instead). +We can do the same for the page using `document.documentElement.scrollTop/scrollLeft` (except Safari, where `document.body.scrollTop/Left` should be used instead). -Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). +Alternatively, there's a simpler, universal solution: special methods [window.scrollBy(x,y)](mdn:api/Window/scrollBy) and [window.scrollTo(pageX,pageY)](mdn:api/Window/scrollTo). - The method `scrollBy(x,y)` scrolls the page *relative to its current position*. For instance, `scrollBy(0,10)` scrolls the page `10px` down. @@ -106,28 +112,28 @@ These methods work for all browsers the same way. ## scrollIntoView -For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). +For completeness, let's cover one more method: [elem.scrollIntoView(top)](mdn:api/Element/scrollIntoView). The call to `elem.scrollIntoView(top)` scrolls the page to make `elem` visible. It has one argument: -- if `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element is aligned with the window top. -- if `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element is aligned with the window bottom. +- If `top=true` (that's the default), then the page will be scrolled to make `elem` appear on the top of the window. The upper edge of the element will be aligned with the window top. +- If `top=false`, then the page scrolls to make `elem` appear at the bottom. The bottom edge of the element will be aligned with the window bottom. ```online -The button below scrolls the page to make itself show at the window top: +The button below scrolls the page to position itself at the window top: -And this button scrolls the page to show it at the bottom: +And this button scrolls the page to position itself at the bottom: ``` ## Forbid the scrolling -Sometimes we need to make the document "unscrollable". For instance, when we need to cover it with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. +Sometimes we need to make the document "unscrollable". For instance, when we need to cover the page with a large message requiring immediate attention, and we want the visitor to interact with that message, not with the document. -To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will freeze on its current scroll. +To make the document unscrollable, it's enough to set `document.body.style.overflow = "hidden"`. The page will "freeze" at its current scroll position. ```online Try it: @@ -136,20 +142,20 @@ Try it: -The first button freezes the scroll, the second one resumes it. +The first button freezes the scroll, while the second one releases it. ``` -We can use the same technique to "freeze" the scroll for other elements, not just for `document.body`. +We can use the same technique to freeze the scroll for other elements, not just for `document.body`. -The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free, and the content "jumps" to fill it. +The drawback of the method is that the scrollbar disappears. If it occupied some space, then that space is now free and the content "jumps" to fill it. -That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze, and if it increased (the scrollbar disappeared) then add `padding` to `document.body` in place of the scrollbar, to keep the content width the same. +That looks a bit odd, but can be worked around if we compare `clientWidth` before and after the freeze. If it increased (the scrollbar disappeared), then add `padding` to `document.body` in place of the scrollbar to keep the content width the same. ## Summary Geometry: -- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/Height` +- Width/height of the visible part of the document (content area width/height): `document.documentElement.clientWidth/clientHeight` - Width/height of the whole document, with the scrolled out part: ```js diff --git a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg index 34187cdb6..18cd37a74 100644 --- a/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg +++ b/2-ui/1-document/10-size-and-scroll-window/document-client-width-height.svg @@ -1 +1 @@ -documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file +documentElement.clientHeightdocumentElement.clientWidth \ No newline at end of file diff --git a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md index f3547096f..4101d4915 100644 --- a/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md +++ b/2-ui/1-document/11-coordinates/1-find-point-coordinates/solution.md @@ -1,6 +1,6 @@ # Outer corners -Outer corners are basically what we get from [elem.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect). +Outer corners are basically what we get from [elem.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/DOM/element.getBoundingClientRect). Coordinates of the upper-left corner `answer1` and the bottom-right corner `answer2`: diff --git a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html index d627bee84..56c95d5ec 100644 --- a/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/3-position-at-absolute/solution.view/index.html @@ -28,8 +28,8 @@ let box = elem.getBoundingClientRect(); return { - top: box.top + pageYOffset, - left: box.left + pageXOffset + top: box.top + window.pageYOffset, + left: box.left + window.pageXOffset }; } diff --git a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html index 7e841397b..b89db3790 100644 --- a/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html +++ b/2-ui/1-document/11-coordinates/4-position-inside-absolute/solution.view/index.html @@ -26,8 +26,8 @@ let box = elem.getBoundingClientRect(); return { - top: box.top + pageYOffset, - left: box.left + pageXOffset + top: box.top + window.pageYOffset, + left: box.left + window.pageXOffset }; } diff --git a/2-ui/1-document/11-coordinates/article.md b/2-ui/1-document/11-coordinates/article.md index c71fa0fce..fc605c414 100644 --- a/2-ui/1-document/11-coordinates/article.md +++ b/2-ui/1-document/11-coordinates/article.md @@ -36,7 +36,7 @@ Additionally, there are derived properties: ```online For instance click this button to see its window coordinates: -

      +

      ``` +In the first example, the HTML attribute is used to initialize the `button.onclick`, while in the second example -- the script, that's all the difference. + **As there's only one `onclick` property, we can't assign more than one event handler.** In the example below adding a handler with JavaScript overwrites the existing handler: @@ -124,16 +124,6 @@ In the example below adding a handler with JavaScript overwrites the existing ha ``` -By the way, we can assign an existing function as a handler directly: - -```js -function sayThanks() { - alert('Thanks!'); -} - -elem.onclick = sayThanks; -``` - To remove a handler -- assign `elem.onclick = null`. ## Accessing the element: this @@ -148,9 +138,19 @@ In the code below `button` shows its contents using `this.innerHTML`: ## Possible mistakes -If you're starting to work with event -- please note some subtleties. +If you're starting to work with events -- please note some subtleties. + +We can set an existing function as a handler: + +```js +function sayThanks() { + alert('Thanks!'); +} + +elem.onclick = sayThanks; +``` -**The function should be assigned as `sayThanks`, not `sayThanks()`.** +But be careful: the function should be assigned as `sayThanks`, not `sayThanks()`. ```js // right @@ -160,7 +160,7 @@ button.onclick = sayThanks; button.onclick = sayThanks(); ``` -If we add parentheses, `sayThanks()` -- is a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work. +If we add parentheses, then `sayThanks()` becomes a function call. So the last line actually takes the *result* of the function execution, that is `undefined` (as the function returns nothing), and assigns it to `onclick`. That doesn't work. ...On the other hand, in the markup we do need the parentheses: @@ -168,21 +168,17 @@ If we add parentheses, `sayThanks()` -- is a function call. So the last line ac ``` -The difference is easy to explain. When the browser reads the attribute, it creates a handler function with *body from its content*: `sayThanks()`. +The difference is easy to explain. When the browser reads the attribute, it creates a handler function with body from the attribute content. So the markup generates this property: ```js button.onclick = function() { *!* - sayThanks(); // the attribute content + sayThanks(); // <-- the attribute content goes here */!* }; ``` -**Use functions, not strings.** - -The assignment `elem.onclick = "alert(1)"` would work too. It works for compatibility reasons, but strongly not recommended. - **Don't use `setAttribute` for handlers.** Such a call won't work: @@ -199,9 +195,9 @@ Assign a handler to `elem.onclick`, not `elem.ONCLICK`, because DOM properties a ## addEventListener -The fundamental problem of the aforementioned ways to assign handlers -- we can't assign multiple handlers to one event. +The fundamental problem of the aforementioned ways to assign handlers is that we *can't assign multiple handlers to one event*. -For instance, one part of our code wants to highlight a button on click, and another one wants to show a message. +Let's say, one part of our code wants to highlight a button on click, and another one wants to show a message on the same click. We'd like to assign two event handlers for that. But a new DOM property will overwrite the existing one: @@ -211,12 +207,12 @@ input.onclick = function() { alert(1); } input.onclick = function() { alert(2); } // replaces the previous handler ``` -Web-standard developers understood that long ago and suggested an alternative way of managing handlers using special methods `addEventListener` and `removeEventListener`. They are free of such a problem. +Developers of web standards understood that long ago and suggested an alternative way of managing handlers using the special methods `addEventListener` and `removeEventListener` which aren't bound by such constraint. The syntax to add a handler: ```js -element.addEventListener(event, handler[, options]); +element.addEventListener(event, handler, [options]); ``` `event` @@ -229,19 +225,18 @@ element.addEventListener(event, handler[, options]); : An additional optional object with properties: - `once`: if `true`, then the listener is automatically removed after it triggers. - `capture`: the phase where to handle the event, to be covered later in the chapter . For historical reasons, `options` can also be `false/true`, that's the same as `{capture: false/true}`. - - `passive`: if `true`, then the handler will not `preventDefault()`, we'll cover that later in . - + - `passive`: if `true`, then the handler will not call `preventDefault()`, we'll explain that later in . To remove the handler, use `removeEventListener`: ```js -element.removeEventListener(event, handler[, options]); +element.removeEventListener(event, handler, [options]); ``` ````warn header="Removal requires the same function" To remove a handler we should pass exactly the same function as was assigned. -That doesn't work: +This doesn't work: ```js no-beautify elem.addEventListener( "click" , () => alert('Thanks!')); @@ -249,7 +244,7 @@ elem.addEventListener( "click" , () => alert('Thanks!')); elem.removeEventListener( "click", () => alert('Thanks!')); ``` -The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter. +The handler won't be removed, because `removeEventListener` gets another function -- with the same code, but that doesn't matter, as it's a different function object. Here's the right way: @@ -266,7 +261,7 @@ input.removeEventListener("click", handler); Please note -- if we don't store the function in a variable, then we can't remove it. There's no way to "read back" handlers assigned by `addEventListener`. ```` -Multiple calls to `addEventListener` allow to add multiple handlers, like this: +Multiple calls to `addEventListener` allow it to add multiple handlers, like this: ```html run no-beautify @@ -291,47 +286,33 @@ Multiple calls to `addEventListener` allow to add multiple handlers, like this: As we can see in the example above, we can set handlers *both* using a DOM-property and `addEventListener`. But generally we use only one of these ways. ````warn header="For some events, handlers only work with `addEventListener`" -There exist events that can't be assigned via a DOM-property. Must use `addEventListener`. - -For instance, the event `transitionend` (CSS animation finished) is like that. - -Try the code below. In most browsers only the second handler works, not the first one. +There exist events that can't be assigned via a DOM-property. Only with `addEventListener`. -```html run - - - +For instance, the `DOMContentLoaded` event, that triggers when the document is loaded and the DOM has been built. - +```js +// this way it works +document.addEventListener("DOMContentLoaded", function() { + alert("DOM built"); +}); ``` +So `addEventListener` is more universal. Although, such events are an exception rather than the rule. ```` ## Event object -To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keypress", but what were the pointer coordinates? Which key was pressed? And so on. +To properly handle an event we'd want to know more about what's happened. Not just a "click" or a "keydown", but what were the pointer coordinates? Which key was pressed? And so on. When an event happens, the browser creates an *event object*, puts details into it and passes it as an argument to the handler. -Here's an example of getting mouse coordinates from the event object: +Here's an example of getting pointer coordinates from the event object: ```html run @@ -353,12 +334,12 @@ Some properties of `event` object: `event.currentTarget` : Element that handled the event. That's exactly the same as `this`, unless the handler is an arrow function, or its `this` is bound to something else, then we can get the element from `event.currentTarget`. -`event.clientX / event.clientY` -: Window-relative coordinates of the cursor, for mouse events. +`event.clientX` / `event.clientY` +: Window-relative coordinates of the cursor, for pointer events. -There are more properties. They depend on the event type, so we'll study them later when we come to different events in details. +There are more properties. Many of them depend on the event type: keyboard events have one set of properties, pointer events - another one, we'll study them later when as we move on to the details of different events. -````smart header="The event object is also accessible from HTML" +````smart header="The event object is also available in HTML handlers" If we assign a handler in HTML, we can also use the `event` object, like this: ```html autorun height=60 @@ -380,17 +361,19 @@ For instance: ``` -As we can see, when `addEventListener` receives an object as the handler, it calls `object.handleEvent(event)` in case of an event. +As we can see, when `addEventListener` receives an object as the handler, it calls `obj.handleEvent(event)` in case of an event. -We could also use a class for that: +We could also use objects of a custom class, like this: ```html run @@ -412,6 +395,7 @@ We could also use a class for that: *!* let menu = new Menu(); + elem.addEventListener('mousedown', menu); elem.addEventListener('mouseup', menu); */!* @@ -462,7 +446,7 @@ HTML attributes are used sparingly, because JavaScript in the middle of an HTML DOM properties are ok to use, but we can't assign more than one handler of the particular event. In many cases that limitation is not pressing. -The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transtionend` and `DOMContentLoaded` (to be covered). Also `addEventListener` supports objects as event handlers. In that case the method `handleEvent` is called in case of the event. +The last way is the most flexible, but it is also the longest to write. There are few events that only work with it, for instance `transitionend` and `DOMContentLoaded` (to be covered). Also `addEventListener` supports objects as event handlers. In that case the method `handleEvent` is called in case of the event. No matter how you assign the handler -- it gets an event object as the first argument. That object contains the details about what's happened. diff --git a/2-ui/2-events/02-bubbling-and-capturing/article.md b/2-ui/2-events/02-bubbling-and-capturing/article.md index 53c31eb91..2448cfa5b 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/article.md +++ b/2-ui/2-events/02-bubbling-and-capturing/article.md @@ -68,7 +68,7 @@ For instance, if we have a single handler `form.onclick`, then it can "catch" al In `form.onclick` handler: -- `this` (`=event.currentTarget`) is the `
      ` element, because the handler runs on it. +- `this` (=`event.currentTarget`) is the `` element, because the handler runs on it. - `event.target` is the actual element inside the form that was clicked. Check it out: @@ -102,7 +102,7 @@ To stop the bubbling and prevent handlers on the current element from running, t ``` ```warn header="Don't stop bubbling without a need!" -Bubbling is convenient. Don't stop it without a real need: obvious and architecturally well-thought. +Bubbling is convenient. Don't stop it without a real need: obvious and architecturally well thought out. Sometimes `event.stopPropagation()` creates hidden pitfalls that later may become problems. @@ -120,26 +120,27 @@ There's usually no real need to prevent the bubbling. A task that seemingly requ There's another phase of event processing called "capturing". It is rarely used in real code, but sometimes can be useful. -The standard [DOM Events](http://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: +The standard [DOM Events](https://www.w3.org/TR/DOM-Level-3-Events/) describes 3 phases of event propagation: 1. Capturing phase -- the event goes down to the element. 2. Target phase -- the event reached the target element. 3. Bubbling phase -- the event bubbles up from the element. -Here's the picture of a click on `` inside a table, taken from the specification: +Here's the picture, taken from the specification, of the capturing `(1)`, target `(2)` and bubbling `(3)` phases for a click event on a `` inside a table: ![](eventflow.svg) That is: for a click on `` the event first goes through the ancestors chain down to the element (capturing phase), then it reaches the target and triggers there (target phase), and then it goes up (bubbling phase), calling handlers on its way. -**Before we only talked about bubbling, because the capturing phase is rarely used. Normally it is invisible to us.** +Until now, we only talked about bubbling, because the capturing phase is rarely used. -Handlers added using `on`-property or using HTML attributes or using two-argument `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. +In fact, the capturing phase was invisible for us, because handlers added using `on`-property or using HTML attributes or using two-argument `addEventListener(event, handler)` don't know anything about capturing, they only run on the 2nd and 3rd phases. To catch an event on the capturing phase, we need to set the handler `capture` option to `true`: ```js elem.addEventListener(..., {capture: true}) + // or, just "true" is an alias to {capture: true} elem.addEventListener(..., true) ``` @@ -180,9 +181,10 @@ The code sets click handlers on *every* element in the document to see which one If you click on `

      `, then the sequence is: -1. `HTML` -> `BODY` -> `FORM` -> `DIV` (capturing phase, the first listener): -2. `P` (target phrase, triggers two times, as we've set two listeners: capturing and bubbling) -3. `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). +1. `HTML` -> `BODY` -> `FORM` -> `DIV -> P` (capturing phase, the first listener): +2. `P` -> `DIV` -> `FORM` -> `BODY` -> `HTML` (bubbling phase, the second listener). + +Please note, the `P` shows up twice, because we've set two listeners: capturing and bubbling. The target triggers at the end of the first and at the beginning of the second phase. There's a property `event.eventPhase` that tells us the number of the phase on which the event was caught. But it's rarely used, because we usually know it in the handler. @@ -190,7 +192,7 @@ There's a property `event.eventPhase` that tells us the number of the phase on w If we `addEventListener(..., true)`, then we should mention the same phase in `removeEventListener(..., true)` to correctly remove the handler. ``` -````smart header="Listeners on same element and same phase run in their set order" +````smart header="Listeners on the same element and same phase run in their set order" If we have multiple event handlers on the same phase, assigned to the same element with `addEventListener`, they run in the same order as they are created: ```js @@ -199,14 +201,20 @@ elem.addEventListener("click", e => alert(2)); ``` ```` +```smart header="The `event.stopPropagation()` during the capturing also prevents the bubbling" +The `event.stopPropagation()` method and its sibling `event.stopImmediatePropagation()` can also be called on the capturing phase. Then not only the futher capturing is stopped, but the bubbling as well. + +In other words, normally the event goes first down ("capturing") and then up ("bubbling"). But if `event.stopPropagation()` is called during the capturing phase, then the event travel stops, no bubbling will occur. +``` + ## Summary When an event happens -- the most nested element where it happens gets labeled as the "target element" (`event.target`). -- Then the event moves down from the document root to `event.target`, calling handlers assigned with `addEventListener(...., true)` on the way (`true` is a shorthand for `{capture: true}`). +- Then the event moves down from the document root to `event.target`, calling handlers assigned with `addEventListener(..., true)` on the way (`true` is a shorthand for `{capture: true}`). - Then handlers are called on the target element itself. -- Then the event bubbles up from `event.target` up to the root, calling handlers assigned using `on` and `addEventListener` without the 3rd argument or with the 3rd argument `false/{capture:false}`. +- Then the event bubbles up from `event.target` to the root, calling handlers assigned using `on`, HTML attributes and `addEventListener` without the 3rd argument or with the 3rd argument `false/{capture:false}`. Each handler can access `event` object properties: @@ -216,10 +224,10 @@ Each handler can access `event` object properties: Any event handler can stop the event by calling `event.stopPropagation()`, but that's not recommended, because we can't really be sure we won't need it above, maybe for completely different things. -The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logic behind that. +The capturing phase is used very rarely, usually we handle events on bubbling. And there's a logical explanation for that. In real world, when an accident happens, local authorities react first. They know best the area where it happened. Then higher-level authorities if needed. -The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `` may be suited for that exactly ``, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last. +The same for event handlers. The code that set the handler on a particular element knows maximum details about the element and what it does. A handler on a particular `` may be suited for that exactly ``, it knows everything about it, so it should get the chance first. Then its immediate parent also knows about the context, but a little bit less, and so on till the very top element that handles general concepts and runs the last one. Bubbling and capturing lay the foundation for "event delegation" -- an extremely powerful event handling pattern that we study in the next chapter. diff --git a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg index e089dc579..2ea88f081 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg +++ b/2-ui/2-events/02-bubbling-and-capturing/event-order-bubbling.svg @@ -1 +1 @@ -123Most deeply nested element \ No newline at end of file +123Most deeply nested element \ No newline at end of file diff --git a/2-ui/2-events/02-bubbling-and-capturing/eventflow.svg b/2-ui/2-events/02-bubbling-and-capturing/eventflow.svg index e7db9bff7..566064cd6 100644 --- a/2-ui/2-events/02-bubbling-and-capturing/eventflow.svg +++ b/2-ui/2-events/02-bubbling-and-capturing/eventflow.svg @@ -1 +1 @@ -WindowDocument<html><body><table><tbody><tr><tr><td>Shady Grove<td>Aeolian<td>Over the River,Charlie<td>DorianTargetPhase(2)CapturePhase(1)BubblingPhase(3) \ No newline at end of file +WindowDocument<html><body><table><tbody><tr><tr><td>Shady Grove<td>Aeolian<td>Over the River,Charlie<td>DorianTargetPhase(2)CapturePhase(1)BubblingPhase(3) \ No newline at end of file diff --git a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md index 73102a92b..3001b9915 100644 --- a/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md +++ b/2-ui/2-events/03-event-delegation/4-behavior-tooltip/task.md @@ -22,8 +22,10 @@ In this task we assume that all elements with `data-tooltip` have only text insi Details: +- The distance between the element and the tooltip should be `5px`. +- The tooltip should be centered relative to the element, if possible. - The tooltip should not cross window edges. Normally it should be above the element, but if the element is at the page top and there's no space for the tooltip, then below it. -- The tooltip is given in the `data-tooltip` attribute. It can be arbitrary HTML. +- The tooltip content is given in the `data-tooltip` attribute. It can be arbitrary HTML. You'll need two events here: - `mouseover` triggers when a pointer comes over an element. diff --git a/2-ui/2-events/03-event-delegation/article.md b/2-ui/2-events/03-event-delegation/article.md index 4d002116f..881183740 100644 --- a/2-ui/2-events/03-event-delegation/article.md +++ b/2-ui/2-events/03-event-delegation/article.md @@ -1,11 +1,11 @@ # Event delegation -Capturing and bubbling allow us to implement one of most powerful event handling patterns called *event delegation*. +Capturing and bubbling allow us to implement one of the most powerful event handling patterns called *event delegation*. The idea is that if we have a lot of elements handled in a similar way, then instead of assigning a handler to each of them -- we put a single handler on their common ancestor. -In the handler we get `event.target`, see where the event actually happened and handle it. +In the handler we get `event.target` to see where the event actually happened and handle it. Let's see an example -- the [Ba-Gua diagram](http://en.wikipedia.org/wiki/Ba_gua) reflecting the ancient Chinese philosophy. @@ -21,9 +21,9 @@ The HTML is like this: Bagua Chart: Direction, Element, Color, Meaning - ...Northwest... - ... - ... + Northwest
      Metal
      Silver
      Elders + ... + ... ...2 more lines of this kind... ...2 more lines of this kind... @@ -101,8 +101,8 @@ table.onclick = function(event) { Explanations: 1. The method `elem.closest(selector)` returns the nearest ancestor that matches the selector. In our case we look for `` on the way up from the source element. -2. If `event.target` is not inside any ``, then the call returns `null`, and we don't have to do anything. -3. In case of nested tables, `event.target` may be a `` lying outside of the current table. So we check if that's actually *our table's* ``. +2. If `event.target` is not inside any ``, then the call returns immediately, as there's nothing to do. +3. In case of nested tables, `event.target` may be a ``, but lying outside of the current table. So we check if that's actually *our table's* ``. 4. And, if it's so, then highlight it. As the result, we have a fast, efficient highlighting code, that doesn't care about the total number of `` in the table. @@ -121,7 +121,7 @@ The first idea may be to assign a separate handler to each button. But there's a The handler reads the attribute and executes the method. Take a look at the working example: -```html autorun height=60 run +```html autorun height=60 run untrusted

      ``` -If we're on `#parent` and then move the pointer deeper into `#child`, but we get `mouseout` on `#parent`! +If we're on `#parent` and then move the pointer deeper into `#child`, we get `mouseout` on `#parent`! ![](mouseover-to-child.svg) @@ -125,7 +125,7 @@ If there are some actions upon leaving the parent element, e.g. an animation run To avoid it, we can check `relatedTarget` in the handler and, if the mouse is still inside the element, then ignore such event. -Alternatively we can use other events: `mouseenter` и `mouseleave`, that we'll be covering now, as they don't have such problems. +Alternatively we can use other events: `mouseenter` and `mouseleave`, that we'll be covering now, as they don't have such problems. ## Events mouseenter and mouseleave @@ -145,7 +145,7 @@ When the pointer leaves an element -- `mouseleave` triggers. ```online This example is similar to the one above, but now the top element has `mouseenter/mouseleave` instead of `mouseover/mouseout`. -As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignores +As you can see, the only generated events are the ones related to moving the pointer in and out of the top element. Nothing happens when the pointer goes to the child and back. Transitions between descendants are ignored [codetabs height=340 src="https://github.com/mouseleave"] ``` @@ -195,10 +195,14 @@ Here's an example of code that accounts for all possible situations: [js src="https://github.com/mouseenter-mouseleave-delegation-2/script.js"] +Once again, the important features are: +1. It uses event delegation to handle entering/leaving of any `` inside the table. So it relies on `mouseover/out` instead of `mouseenter/leave` that don't bubble and hence allow no delegation. +2. Extra events, such as moving between descendants of `` are filtered out, so that `onEnter/Leave` runs only if the pointer leaves or enters `` as a whole. + ```online Here's the full example with all details: -[codetabs height=380 src="https://github.com/mouseenter-mouseleave-delegation-2"] +[codetabs height=460 src="https://github.com/mouseenter-mouseleave-delegation-2"] Try to move the cursor in and out of table cells and inside them. Fast or slow -- doesn't matter. Only `` as a whole is highlighted, unlike the example before. ``` diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html index e129ee6a2..eedd38716 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/index.html @@ -67,6 +67,10 @@ + + + + diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js index 27ae27b94..6a3202467 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation-2.view/script.js @@ -18,7 +18,7 @@ table.onmouseover = function(event) { // hooray! we entered a new currentElem = target; - target.style.background = 'pink'; + onEnter(currentElem); }; @@ -40,6 +40,23 @@ table.onmouseout = function(event) { } // we left the . really. - currentElem.style.background = ''; + onLeave(currentElem); currentElem = null; }; + +// any functions to handle entering/leaving an element +function onEnter(elem) { + elem.style.background = 'pink'; + + // show that in textarea + text.value += `over -> ${currentElem.tagName}.${currentElem.className}\n`; + text.scrollTop = 1e6; +} + +function onLeave(elem) { + elem.style.background = ''; + + // show that in textarea + text.value += `out <- ${elem.tagName}.${elem.className}\n`; + text.scrollTop = 1e6; +} diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js index 4700d686e..ae633ad67 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseenter-mouseleave-delegation.view/script.js @@ -1,13 +1,15 @@ table.onmouseover = function(event) { let target = event.target; target.style.background = 'pink'; - text.value += "mouseover " + target.tagName + "\n"; + + text.value += `over -> ${target.tagName}\n`; text.scrollTop = text.scrollHeight; }; table.onmouseout = function(event) { let target = event.target; target.style.background = ''; - text.value += "mouseout " + target.tagName + "\n"; + + text.value += `out <- ${target.tagName}\n`; text.scrollTop = text.scrollHeight; -}; \ No newline at end of file +}; diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg index 07830295c..6044eff17 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-bubble-nested.svg @@ -1 +1 @@ -mouseoutmouseover#parent#child \ No newline at end of file +mouseoutmouseover#parent#child \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg index 07176ba2d..22335b52e 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-from-outside.svg @@ -1 +1 @@ -#TOtargetrelatedTarget = null \ No newline at end of file +#TOtargetrelatedTarget = null \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg index 262ddf596..437f03b10 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout-over-elems.svg @@ -1 +1 @@ -#TO#FROM<DIV><DIV><DIV>mouseovermouseout \ No newline at end of file +#TO#FROM<DIV><DIV><DIV>mouseovermouseout \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg index 784f435de..1277ddff5 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-mouseout.svg @@ -1 +1 @@ -<DIV>mouseovermouseout \ No newline at end of file +<DIV>mouseovermouseout \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg index b38d76fbe..78210845b 100644 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseover-to-child.svg @@ -1 +1 @@ -mouseoutmouseover#parent#child \ No newline at end of file +mouseoutmouseover#parent#child \ No newline at end of file diff --git a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js index 6d87199c2..5752e83ae 100755 --- a/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js +++ b/2-ui/3-event-details/3-mousemove-mouseover-mouseout-mouseenter-mouseleave/mouseoverout-fast.view/script.js @@ -3,7 +3,7 @@ parent.onmouseover = parent.onmouseout = parent.onmousemove = handler; function handler(event) { let type = event.type; - while (type < 11) type += ' '; + while (type.length < 11) type += ' '; log(type + " target=" + event.target.id) return false; diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg index 515ac7f81..f5bd9f4f9 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/field.svg @@ -1 +1 @@ -(0,0)clientWidth \ No newline at end of file +(0,0)clientWidth \ No newline at end of file diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js index 1c1443a7e..10ae2eeed 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/2-drag-heroes/solution.view/soccer.js @@ -7,7 +7,7 @@ document.addEventListener('mousedown', function(event) { if (!dragElement) return; event.preventDefault(); - + dragElement.ondragstart = function() { return false; }; @@ -19,7 +19,7 @@ document.addEventListener('mousedown', function(event) { function onMouseUp(event) { finishDrag(); }; - + function onMouseMove(event) { moveAt(event.clientX, event.clientY); } @@ -31,9 +31,9 @@ document.addEventListener('mousedown', function(event) { if(isDragging) { return; } - + isDragging = true; - + document.addEventListener('mousemove', onMouseMove); element.addEventListener('mouseup', onMouseUp); @@ -50,10 +50,10 @@ document.addEventListener('mousedown', function(event) { if(!isDragging) { return; } - + isDragging = false; - dragElement.style.top = parseInt(dragElement.style.top) + pageYOffset + 'px'; + dragElement.style.top = parseInt(dragElement.style.top) + window.pageYOffset + 'px'; dragElement.style.position = 'absolute'; document.removeEventListener('mousemove', onMouseMove); @@ -113,4 +113,4 @@ document.addEventListener('mousedown', function(event) { dragElement.style.top = newY + 'px'; } -}); \ No newline at end of file +}); diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md index 721357b7a..4c928eef1 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/article.md +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/article.md @@ -1,39 +1,36 @@ # Drag'n'Drop with mouse events -Drag'n'Drop is a great interface solution. Taking something, dragging and dropping is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (drop into cart). +Drag'n'Drop is a great interface solution. Taking something and dragging and dropping it is a clear and simple way to do many things, from copying and moving documents (as in file managers) to ordering (dropping items into a cart). -In the modern HTML standard there's a [section about Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) with special events such as `dragstart`, `dragend` and so on. +In the modern HTML standard there's a [section about Drag and Drop](https://html.spec.whatwg.org/multipage/interaction.html#dnd) with special events such as `dragstart`, `dragend`, and so on. -They are interesting because they allow to solve simple tasks easily, and also allow to handle drag'n'drop of "external" files into the browser. So we can take a file in the OS file-manager and drop it into the browser window. Then JavaScript gains access to its contents. +These events allow us to support special kinds of drag'n'drop, such as handling dragging a file from OS file-manager and dropping it into the browser window. Then JavaScript can access the contents of such files. -But native Drag Events also have limitations. For instance, we can't limit dragging by a certain area. Also we can't make it "horizontal" or "vertical" only. There are other drag'n'drop tasks that can't be done using that API. +But native Drag Events also have limitations. For instance, we can't prevent dragging from a certain area. Also we can't make the dragging "horizontal" or "vertical" only. And there are many other drag'n'drop tasks that can't be done using them. Also, mobile device support for such events is very weak. -Here we'll see how to implement Drag'n'Drop using mouse events. +So here we'll see how to implement Drag'n'Drop using mouse events. ## Drag'n'Drop algorithm The basic Drag'n'Drop algorithm looks like this: -1. On `mousedown` - prepare the element for moving, if needed (maybe create a copy of it). -2. Then on `mousemove` move it by changing `left/top` and `position:absolute`. -3. On `mouseup` - perform all actions related to a finished Drag'n'Drop. +1. On `mousedown` - prepare the element for moving, if needed (maybe create a clone of it, add a class to it or whatever). +2. Then on `mousemove` move it by changing `left/top` with `position:absolute`. +3. On `mouseup` - perform all actions related to finishing the drag'n'drop. -These are the basics. Later we can extend it, for instance, by highlighting droppable (available for the drop) elements when hovering over them. +These are the basics. Later we'll see how to add other features, such as highlighting current underlying elements while we drag over them. -Here's the algorithm for drag'n'drop of a ball: +Here's the implementation of dragging a ball: ```js -ball.onmousedown = function(event) { // (1) start the process - - // (2) prepare to moving: make absolute and on top by z-index +ball.onmousedown = function(event) { + // (1) prepare to moving: make absolute and on top by z-index ball.style.position = 'absolute'; ball.style.zIndex = 1000; + // move it out of any current parents directly into body // to make it positioned relative to the body - document.body.append(ball); - // ...and put that absolutely positioned ball under the pointer - - moveAt(event.pageX, event.pageY); + document.body.append(ball); // centers the ball at (pageX, pageY) coordinates function moveAt(pageX, pageY) { @@ -41,14 +38,17 @@ ball.onmousedown = function(event) { // (1) start the process ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; } + // move our absolutely positioned ball under the pointer + moveAt(event.pageX, event.pageY); + function onMouseMove(event) { moveAt(event.pageX, event.pageY); } - // (3) move the ball on mousemove + // (2) move the ball on mousemove document.addEventListener('mousemove', onMouseMove); - // (4) drop the ball, remove unneeded handlers + // (3) drop the ball, remove unneeded handlers ball.onmouseup = function() { document.removeEventListener('mousemove', onMouseMove); ball.onmouseup = null; @@ -64,10 +64,10 @@ Here's an example in action: [iframe src="https://github.com/ball" height=230] -Try to drag'n'drop the mouse and you'll see such behavior. +Try to drag'n'drop with the mouse and you'll see such behavior. ``` -That's because the browser has its own Drag'n'Drop for images and some other elements that runs automatically and conflicts with ours. +That's because the browser has its own drag'n'drop support for images and some other elements. It runs automatically and conflicts with ours. To disable it: @@ -93,14 +93,14 @@ So we should listen on `document` to catch it. ## Correct positioning -In the examples above the ball is always moved so, that it's center is under the pointer: +In the examples above the ball is always moved so that its center is under the pointer: ```js ball.style.left = pageX - ball.offsetWidth / 2 + 'px'; ball.style.top = pageY - ball.offsetHeight / 2 + 'px'; ``` -Not bad, but there's a side-effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. +Not bad, but there's a side effect. To initiate the drag'n'drop, we can `mousedown` anywhere on the ball. But if "take" it from its edge, then the ball suddenly "jumps" to become centered under the mouse pointer. It would be better if we keep the initial shift of the element relative to the pointer. @@ -124,7 +124,7 @@ Let's update our algorithm: ```js // onmousemove - // у мяча ball стоит position:absoute + // ball has position:absolute ball.style.left = event.pageX - *!*shiftX*/!* + 'px'; ball.style.top = event.pageY - *!*shiftY*/!* + 'px'; ``` @@ -219,7 +219,7 @@ That's why the initial idea to put handlers on potential droppables doesn't work So, what to do? -There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). +There's a method called `document.elementFromPoint(clientX, clientY)`. It returns the most nested element on given window-relative coordinates (or `null` if given coordinates are out of the window). If there are multiple overlapping elements on the same coordinates, then the topmost one is returned. We can use it in any of our mouse event handlers to detect the potential droppable under the pointer, like this: @@ -276,7 +276,7 @@ function onMouseMove(event) { } ``` -In the example below when the ball is dragged over the soccer gate, the gate is highlighted. +In the example below when the ball is dragged over the soccer goal, the goal is highlighted. [codetabs height=250 src="https://github.com/ball4"] @@ -300,4 +300,4 @@ We can lay a lot on this foundation. - We can use event delegation for `mousedown/up`. A large-area event handler that checks `event.target` can manage Drag'n'Drop for hundreds of elements. - And so on. -There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to described above, so it should be easy to understand them now. Or roll our own, as you can see that's easy enough to do, sometimes easier than adapting a third-part solution. +There are frameworks that build architecture over it: `DragZone`, `Droppable`, `Draggable` and other classes. Most of them do the similar stuff to what's described above, so it should be easy to understand them now. Or roll your own, as you can see that that's easy enough to do, sometimes easier than adapting a third-party solution. diff --git a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html index 3fdd7fe76..8751c70ad 100644 --- a/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html +++ b/2-ui/3-event-details/4-mouse-drag-and-drop/ball.view/index.html @@ -13,16 +13,13 @@ + + + diff --git a/2-ui/3-event-details/6-pointer-events/ball.view/index.html b/2-ui/3-event-details/6-pointer-events/ball.view/index.html new file mode 100644 index 000000000..8bbef8f63 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/ball.view/index.html @@ -0,0 +1,30 @@ + + +

      Drag the ball.

      + + + + + + + diff --git a/2-ui/3-event-details/6-pointer-events/multitouch.view/index.html b/2-ui/3-event-details/6-pointer-events/multitouch.view/index.html new file mode 100644 index 000000000..d46e1bc16 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/multitouch.view/index.html @@ -0,0 +1,28 @@ + + + + +
      + Multi-touch here +
      + + + diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html new file mode 100644 index 000000000..781016f52 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/index.html @@ -0,0 +1,6 @@ + + + +
      +
      +
      diff --git a/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css new file mode 100644 index 000000000..9b3d3b82d --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider-html.view/style.css @@ -0,0 +1,19 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/index.html b/2-ui/3-event-details/6-pointer-events/slider.view/index.html new file mode 100644 index 000000000..b29e646a1 --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider.view/index.html @@ -0,0 +1,50 @@ + + + +
      +
      +
      + +

      Mouse over here to see the date

      + + diff --git a/2-ui/3-event-details/6-pointer-events/slider.view/style.css b/2-ui/3-event-details/6-pointer-events/slider.view/style.css new file mode 100644 index 000000000..a84cd5e7e --- /dev/null +++ b/2-ui/3-event-details/6-pointer-events/slider.view/style.css @@ -0,0 +1,20 @@ +.slider { + border-radius: 5px; + background: #E0E0E0; + background: linear-gradient(left top, #E0E0E0, #EEEEEE); + width: 310px; + height: 15px; + margin: 5px; +} + +.thumb { + touch-action: none; + width: 10px; + height: 25px; + border-radius: 3px; + position: relative; + left: 10px; + top: -5px; + background: blue; + cursor: pointer; +} diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.md rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.md diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/solution.view/index.html rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/solution.view/index.html diff --git a/2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md b/2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/2-check-sync-keydown/task.md rename to 2-ui/3-event-details/7-keyboard-events/2-check-sync-keydown/task.md diff --git a/2-ui/3-event-details/5-keyboard-events/article.md b/2-ui/3-event-details/7-keyboard-events/article.md similarity index 81% rename from 2-ui/3-event-details/5-keyboard-events/article.md rename to 2-ui/3-event-details/7-keyboard-events/article.md index 617852ccf..12fe63201 100644 --- a/2-ui/3-event-details/5-keyboard-events/article.md +++ b/2-ui/3-event-details/7-keyboard-events/article.md @@ -107,7 +107,7 @@ So, `event.code` may match a wrong character for unexpected layout. Same letters To reliably track layout-dependent characters, `event.key` may be a better way. -On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location, even if the visitor changes languages. So hotkeys that rely on it work well even in case of a language switch. +On the other hand, `event.code` has the benefit of staying always the same, bound to the physical key location. So hotkeys that rely on it work well even in case of a language switch. Do we want to handle layout-dependant keys? Then `event.key` is the way to go. @@ -139,22 +139,25 @@ For instance, the `` below expects a phone number, so it does not accept ```html autorun height=60 run ``` -Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, `key:Ctrl+V`, do not work in the input. That's a side-effect of the strict filter `checkPhoneKey`. +The `onkeydown` handler here uses `checkPhoneKey` to check for the key pressed. If it's valid (from `0..9` or one of `+-()`), then it returns `true`, otherwise `false`. -Let's relax it a little bit: +As we know, the `false` value returned from the event handler, assigned using a DOM property or an attribute, such as above, prevents the default action, so nothing appears in the `` for keys that don't pass the test. (The `true` value returned doesn't affect anything, only returning `false` matters) +Please note that special keys, such as `key:Backspace`, `key:Left`, `key:Right`, do not work in the input. That's a side effect of the strict filter `checkPhoneKey`. These keys make it return `false`. + +Let's relax the filter a little bit by allowing arrow keys `key:Left`, `key:Right` and `key:Delete`, `key:Backspace`: ```html autorun height=60 run @@ -162,7 +165,9 @@ function checkPhoneKey(key) { Now arrows and deletion works well. -...But we still can enter anything by using a mouse and right-click + Paste. So the filter is not 100% reliable. We can just let it be like that, because most of time it works. Or an alternative approach would be to track the `input` event -- it triggers after any modification. There we can check the new value and highlight/modify it when it's invalid. +Even though we have the key filter, one still can enter anything using a mouse and right-click + Paste. Mobile devices provide other means to enter values. So the filter is not 100% reliable. + +The alternative approach would be to track the `oninput` event -- it triggers *after* any modification. There we can check the new `input.value` and modify it/highlight the `` when it's invalid. Or we can use both event handlers together. ## Legacy @@ -170,6 +175,12 @@ In the past, there was a `keypress` event, and also `keyCode`, `charCode`, `whic There were so many browser incompatibilities while working with them, that developers of the specification had no way, other than deprecating all of them and creating new, modern events (described above in this chapter). The old code still works, as browsers keep supporting them, but there's totally no need to use those any more. +## Mobile Keyboards + +When using virtual/mobile keyboards, formally known as IME (Input-Method Editor), the W3C standard states that a KeyboardEvent's [`e.keyCode` should be `229`](https://www.w3.org/TR/uievents/#determine-keydown-keyup-keyCode) and [`e.key` should be `"Unidentified"`](https://www.w3.org/TR/uievents-key/#key-attr-values). + +While some of these keyboards might still use the right values for `e.key`, `e.code`, `e.keyCode`... when pressing certain keys such as arrows or backspace, there's no guarantee, so your keyboard logic might not always work on mobile devices. + ## Summary Pressing a key always generates a keyboard event, be it symbol keys or special keys like `key:Shift` or `key:Ctrl` and so on. The only exception is `key:Fn` key that sometimes presents on a laptop keyboard. There's no keyboard event for it, because it's often implemented on lower level than OS. diff --git a/2-ui/3-event-details/7-keyboard-events/german-layout.svg b/2-ui/3-event-details/7-keyboard-events/german-layout.svg new file mode 100644 index 000000000..7ac9a4008 --- /dev/null +++ b/2-ui/3-event-details/7-keyboard-events/german-layout.svg @@ -0,0 +1 @@ +StrgStrgAl tAlt GrWinWinMenu \ No newline at end of file diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html similarity index 95% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html index 401062830..a0d5a4f40 100644 --- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/index.html +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/index.html @@ -28,7 +28,7 @@ - + diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js similarity index 96% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js index 5eba24c7a..d97f7a7b5 100644 --- a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/script.js +++ b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/script.js @@ -5,6 +5,8 @@ let lastTime = Date.now(); function handle(e) { if (form.elements[e.type + 'Ignore'].checked) return; + area.scrollTop = 1e6; + let text = e.type + ' key=' + e.key + ' code=' + e.code + diff --git a/2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css b/2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css similarity index 100% rename from 2-ui/3-event-details/5-keyboard-events/keyboard-dump.view/style.css rename to 2-ui/3-event-details/7-keyboard-events/keyboard-dump.view/style.css diff --git a/2-ui/3-event-details/7-keyboard-events/us-layout.svg b/2-ui/3-event-details/7-keyboard-events/us-layout.svg new file mode 100644 index 000000000..353f225f1 --- /dev/null +++ b/2-ui/3-event-details/7-keyboard-events/us-layout.svg @@ -0,0 +1 @@ +Caps LockShiftShift \ No newline at end of file diff --git a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md index 10945ccd7..54c101193 100644 --- a/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md +++ b/2-ui/3-event-details/8-onscroll/1-endless-page/solution.md @@ -55,11 +55,11 @@ function populate() { // document bottom let windowRelativeBottom = document.documentElement.getBoundingClientRect().bottom; - // if the user scrolled far enough (<100px to the end) - if (windowRelativeBottom < document.documentElement.clientHeight + 100) { - // let's add more data - document.body.insertAdjacentHTML("beforeend", `

      Date: ${new Date()}

      `); - } + // if the user hasn't scrolled far enough (>100px to the end) + if (windowRelativeBottom > document.documentElement.clientHeight + 100) break; + + // let's add more data + document.body.insertAdjacentHTML("beforeend", `

      Date: ${new Date()}

      `); } } ``` diff --git a/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html b/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html index 5c6027a6f..9953ace66 100644 --- a/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html +++ b/2-ui/3-event-details/8-onscroll/3-load-visible-img/source.view/index.html @@ -169,38 +169,8 @@

      Neptune

      * It's enough that the top or bottom edge of the element are visible */ function isVisible(elem) { - - let coords = elem.getBoundingClientRect(); - - let windowHeight = document.documentElement.clientHeight; - - // top elem edge is visible OR bottom elem edge is visible - let topVisible = coords.top > 0 && coords.top < windowHeight; - let bottomVisible = coords.bottom < windowHeight && coords.bottom > 0; - - return topVisible || bottomVisible; - } - - /** - A variant of the test that considers the element visible if it's no more than - one page after/behind the current screen. - - function isVisible(elem) { - - let coords = elem.getBoundingClientRect(); - - let windowHeight = document.documentElement.clientHeight; - - let extendedTop = -windowHeight; - let extendedBottom = 2 * windowHeight; - - // top visible || bottom visible - let topVisible = coords.top > extendedTop && coords.top < extendedBottom; - let bottomVisible = coords.bottom < extendedBottom && coords.bottom > extendedTop; - - return topVisible || bottomVisible; + // todo: your code } - */ function showVisible() { for (let img of document.querySelectorAll('img')) { diff --git a/2-ui/3-event-details/8-onscroll/article.md b/2-ui/3-event-details/8-onscroll/article.md index 22ff8d859..734bd84c6 100644 --- a/2-ui/3-event-details/8-onscroll/article.md +++ b/2-ui/3-event-details/8-onscroll/article.md @@ -1,6 +1,6 @@ # Scrolling -The `scroll` event allows to react on a page or element scrolling. There are quite a few good things we can do here. +The `scroll` event allows reacting to a page or element scrolling. There are quite a few good things we can do here. For instance: - Show/hide additional controls or information depending on where in the document the user is. @@ -10,7 +10,7 @@ Here's a small function to show the current scroll: ```js autorun window.addEventListener('scroll', function() { - document.getElementById('showScroll').innerHTML = pageYOffset + 'px'; + document.getElementById('showScroll').innerHTML = window.pageYOffset + 'px'; }); ``` @@ -34,4 +34,4 @@ If we add an event handler to these events and `event.preventDefault()` in it, t There are many ways to initiate a scroll, so it's more reliable to use CSS, `overflow` property. -Here are few tasks that you can solve or look through to see the applications on `onscroll`. +Here are few tasks that you can solve or look through to see applications of `onscroll`. diff --git a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md index 9b218aa7f..a0e74da57 100644 --- a/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md +++ b/2-ui/4-forms-controls/1-form-elements/1-add-select-option/task.md @@ -18,3 +18,5 @@ Use JavaScript to: 1. Show the value and the text of the selected option. 2. Add an option: ``. 3. Make it selected. + +Note, if you've done everything right, your alert should show `blues`. diff --git a/2-ui/4-forms-controls/1-form-elements/article.md b/2-ui/4-forms-controls/1-form-elements/article.md index d989a5fb8..f22518d9d 100644 --- a/2-ui/4-forms-controls/1-form-elements/article.md +++ b/2-ui/4-forms-controls/1-form-elements/article.md @@ -8,11 +8,11 @@ Working with forms will be much more convenient when we learn them. Document forms are members of the special collection `document.forms`. -That's a so-called "named collection": it's both named and ordered. We can use both the name or the number in the document to get the form. +That's a so-called *"named collection"*: it's both named and ordered. We can use both the name or the number in the document to get the form. ```js no-beautify -document.forms.my - the form with name="my" -document.forms[0] - the first form in the document +document.forms.my; // the form with name="my" +document.forms[0]; // the first form in the document ``` When we have a form, then any element is available in the named collection `form.elements`. @@ -36,9 +36,9 @@ For instance: ``` -There may be multiple elements with the same name, that's often the case with radio buttons. +There may be multiple elements with the same name. This is typical with radio buttons and checkboxes. -In that case `form.elements[name]` is a collection, for instance: +In that case, `form.elements[name]` is a *collection*. For instance: ```html run height=40
      @@ -119,14 +119,13 @@ That's easy to see in an example: ``` -That's usually not a problem, because we rarely change names of form elements. +That's usually not a problem, however, because we rarely change names of form elements. ```` ## Backreference: element.form -For any element, the form is available as `element.form`. So a form references all elements, and elements -reference the form. +For any element, the form is available as `element.form`. So a form references all elements, and elements reference the form. Here's the picture: @@ -156,7 +155,7 @@ Let's talk about form controls. ### input and textarea -We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes. +We can access their value as `input.value` (string) or `input.checked` (boolean) for checkboxes and radio buttons. Like this: @@ -178,18 +177,16 @@ It stores only the HTML that was initially on the page, not the current value. A ``: -1. Find the corresponding `
      `. So, popups is not something we use everyday. @@ -15,7 +15,7 @@ Also, popups are tricky on mobile devices, that don't show multiple windows simu Still, there are tasks where popups are still used, e.g. for OAuth authorization (login with Google/Facebook/...), because: -1. A popup is a separate window with its own independent JavaScript environment. So opening a popup with a third-party non-trusted site is safe. +1. A popup is a separate window which has its own independent JavaScript environment. So opening a popup from a third-party, non-trusted site is safe. 2. It's very easy to open a popup. 3. A popup can navigate (change URL) and send messages to the opener window. @@ -38,26 +38,6 @@ button.onclick = () => { This way users are somewhat protected from unwanted popups, but the functionality is not disabled totally. -What if the popup opens from `onclick`, but after `setTimeout`? That's a bit tricky. - -Try this code: - -```js run -// open after 3 seconds -setTimeout(() => window.open('http://google.com/'), 3000); -``` - -The popup opens in Chrome, but gets blocked in Firefox. - -...If we decrease the delay, the popup works in Firefox too: - -```js run -// open after 1 seconds -setTimeout(() => window.open('http://google.com/'), 1000); -``` - -The difference is that Firefox treats a timeout of 2000ms or less are acceptable, but after it -- removes the "trust", assuming that now it's "outside of the user action". So the first one is blocked, and the second one is not. - ## window.open The syntax to open a popup is: `window.open(url, name, params)`: @@ -69,7 +49,7 @@ name : A name of the new window. Each window has a `window.name`, and here we can specify which window to use for the popup. If there's already a window with such name -- the given URL opens in it, otherwise a new window is opened. params -: The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: `width:200,height=100`. +: The configuration string for the new window. It contains settings, delimited by a comma. There must be no spaces in params, for instance: `width=200,height=100`. Settings for `params`: @@ -87,9 +67,9 @@ Settings for `params`: There is also a number of less supported browser-specific features, which are usually not used. Check window.open in MDN for examples. -## Example: a minimalistic window +## Example: a minimalistic window -Let's open a window with minimal set of features just to see which of them browser allows to disable: +Let's open a window with minimal set of features, just to see which of them browser allows to disable: ```js run let params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no, @@ -120,7 +100,7 @@ Rules for omitted settings: ## Accessing popup from window -The `open` call returns a reference to the new window. It can be used to manipulate it's properties, change location and even more. +The `open` call returns a reference to the new window. It can be used to manipulate its properties, change location and even more. In this example, we generate popup content from JavaScript: @@ -154,7 +134,7 @@ Windows may freely access content of each other only if they come from the same Otherwise, e.g. if the main window is from `site.com`, and the popup from `gmail.com`, that's impossible for user safety reasons. For the details, see chapter . ``` -## Accessing window from popup +## Accessing window from popup A popup may access the "opener" window as well using `window.opener` reference. It is `null` for all windows except popups. @@ -192,7 +172,7 @@ newWindow.onload = function() { ``` -## Scrolling and resizing +## Moving and resizing There are methods to move/resize a window: @@ -237,28 +217,30 @@ There's also `window.onscroll` event. ## Focus/blur on a window -Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. Also there are `focus/blur` events that allow to focus a window and catch the moment when the visitor switches elsewhere. +Theoretically, there are `window.focus()` and `window.blur()` methods to focus/unfocus on a window. And there are also `focus/blur` events that allow to catch the moment when the visitor focuses on a window and switches elsewhere. + +Although, in practice they are severely limited, because in the past evil pages abused them. -In the past evil pages abused those. For instance, look at this code: +For instance, look at this code: ```js run window.onblur = () => window.focus(); ``` -When a user attempts to switch out of the window (`blur`), it brings it back to focus. The intention is to "lock" the user within the `window`. +When a user attempts to switch out of the window (`window.onblur`), it brings the window back into focus. The intention is to "lock" the user within the `window`. -So, there are limitations that forbid the code like that. There are many limitations to protect the user from ads and evils pages. They depend on the browser. +So browsers had to introduce many limitations to forbid the code like that and protect the user from ads and evils pages. They depend on the browser. -For instance, a mobile browser usually ignores that call completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window. +For instance, a mobile browser usually ignores `window.focus()` completely. Also focusing doesn't work when a popup opens in a separate tab rather than a new window. -Still, there are some things that can be done. +Still, there are some use cases when such calls do work and can be useful. For instance: -- When we open a popup, it's might be a good idea to run a `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now. +- When we open a popup, it might be a good idea to run `newWindow.focus()` on it. Just in case, for some OS/browser combinations it ensures that the user is in the new window now. - If we want to track when a visitor actually uses our web-app, we can track `window.onfocus/onblur`. That allows us to suspend/resume in-page activities, animations etc. But please note that the `blur` event means that the visitor switched out from the window, but they still may observe it. The window is in the background, but still may be visible. -## Summary +## Summary Popup windows are used rarely, as there are alternatives: loading and displaying information in-page, or in iframe. @@ -268,7 +250,7 @@ If we're going to open a popup, a good practice is to inform the user about it. - Browsers block `open` calls from the code outside of user actions. Usually a notification appears, so that a user may allow them. - Browsers open a new tab by default, but if sizes are provided, then it'll be a popup window. - The popup may access the opener window using the `window.opener` property. -- The main window and the popup can freely read and modify each other if they havee the same origin. Otherwise, they can change location of each other and [exchange messages. +- The main window and the popup can freely read and modify each other if they have the same origin. Otherwise, they can change location of each other and [exchange messages](info:cross-window-communication). To close the popup: use `close()` call. Also the user may close them (just like any other windows). The `window.closed` is `true` after that. diff --git a/3-frames-and-windows/03-cross-window-communication/article.md b/3-frames-and-windows/03-cross-window-communication/article.md index 0a89521b3..4d4e320e4 100644 --- a/3-frames-and-windows/03-cross-window-communication/article.md +++ b/3-frames-and-windows/03-cross-window-communication/article.md @@ -116,6 +116,13 @@ document.domain = 'site.com'; That's all. Now they can interact without limitations. Again, that's only possible for pages with the same second-level domain. +```warn header="Deprecated, but still working" +The `document.domain` property is in the process of being removed from the [specification](https://html.spec.whatwg.org/multipage/origin.html#relaxing-the-same-origin-restriction). The cross-window messaging (explained soon below) is the suggested replacement. + +That said, as of now all browsers support it. And the support will be kept for the future, not to break old code that relies on `document.domain`. +``` + + ## Iframe: wrong document pitfall When an iframe comes from the same origin, and we may access its `document`, there's a pitfall. It's not related to cross-origin things, but important to know. @@ -263,12 +270,12 @@ The window that wants to send a message calls [postMessage](mdn:api/Window.postM Arguments: `data` -: The data to send. Can be any object, the data is cloned using the "structured cloning algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser. +: The data to send. Can be any object, the data is cloned using the "structured serialization algorithm". IE supports only strings, so we should `JSON.stringify` complex objects to support that browser. `targetOrigin` : Specifies the origin for the target window, so that only a window from the given origin will get the message. -The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read it's `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it. +The `targetOrigin` is a safety measure. Remember, if the target window comes from another origin, we can't read its `location` in the sender window. So we can't be sure which site is open in the intended window right now: the user could navigate away, and the sender window has no idea about it. Specifying `targetOrigin` ensures that the window only receives the data if it's still at the right site. Important when the data is sensitive. @@ -335,10 +342,6 @@ The full example: [codetabs src="https://github.com/postmessage" height=120] -```smart header="There's no delay" -There's totally no delay between `postMessage` and the `message` event. The event triggers synchronously, faster than `setTimeout(...,0)`. -``` - ## Summary To call methods and access the content of another window, we should first have a reference to it. diff --git a/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html b/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html index 478830610..46dd7b5cc 100644 --- a/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html +++ b/3-frames-and-windows/03-cross-window-communication/sandbox.view/index.html @@ -7,7 +7,7 @@ -
      The iframe below is has sandbox attribute.
      +
      The iframe below has the sandbox attribute.
      diff --git a/3-frames-and-windows/06-clickjacking/article.md b/3-frames-and-windows/06-clickjacking/article.md index 1daa87dd0..34d0a91ae 100644 --- a/3-frames-and-windows/06-clickjacking/article.md +++ b/3-frames-and-windows/06-clickjacking/article.md @@ -154,7 +154,7 @@ Depending on your browser, the `iframe` above is either empty or alerting you th ## Showing with disabled functionality -The `X-Frame-Options` header has a side-effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. +The `X-Frame-Options` header has a side effect. Other sites won't be able to show our page in a frame, even if they have good reasons to do so. So there are other solutions... For instance, we can "cover" the page with a `
      ` with styles `height: 100%; width: 100%;`, so that it will intercept all clicks. That `
      ` is to be removed if `window == top` or if we figure out that we don't need the protection. diff --git a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js index 2f51384ef..00c37bb94 100644 --- a/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js +++ b/4-binary/01-arraybuffer-binary-arrays/01-concat/_js.view/solution.js @@ -2,9 +2,9 @@ function concat(arrays) { // sum of individual array lengths let totalLength = arrays.reduce((acc, value) => acc + value.length, 0); - if (!arrays.length) return null; - let result = new Uint8Array(totalLength); + + if (!arrays.length) return result; // for each array - copy it over result // next array is copied right after the previous one diff --git a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg index 89ae96407..b697d6304 100644 --- a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg +++ b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-256.svg @@ -1 +1 @@ -8-bit integer256 \ No newline at end of file +8-bit integer256 \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg index c7b74cd63..8e3074fdf 100644 --- a/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg +++ b/4-binary/01-arraybuffer-binary-arrays/8bit-integer-257.svg @@ -1 +1 @@ -8-bit integer257 \ No newline at end of file +8-bit integer257 \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg index 801035ecc..b9de47de1 100644 --- a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg +++ b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-view-buffersource.svg @@ -1 +1 @@ -02134567012301new ArrayBuffer(16)ArrayBufferViewUint16Array Int16ArrayUint8Array Int8Array Uint8ClampedArrayUint32Array Int32Array Float32ArrayFloat64ArrayDataViewget/setUint8(offset) get/setFloat32(offset)...BufferSource1023456789101112131415 \ No newline at end of file +02134567012301new ArrayBuffer(16)ArrayBufferViewUint16Array Int16ArrayUint8Array Int8Array Uint8ClampedArrayUint32Array Int32Array Float32ArrayFloat64ArrayDataViewget/setUint8(offset) get/setFloat32(offset)...BufferSource1023456789101112131415 \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg index 02160e31e..b022796ad 100644 --- a/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg +++ b/4-binary/01-arraybuffer-binary-arrays/arraybuffer-views.svg @@ -1 +1 @@ -100213234567891011121314154567012301new ArrayBuffer(16)Uint16ArrayUint8ArrayUint32ArrayFloat64Array \ No newline at end of file +100213234567891011121314154567012301new ArrayBuffer(16)Uint16ArrayUint8ArrayUint32ArrayFloat64Array \ No newline at end of file diff --git a/4-binary/01-arraybuffer-binary-arrays/article.md b/4-binary/01-arraybuffer-binary-arrays/article.md index b1d0bbb0d..2827e277e 100644 --- a/4-binary/01-arraybuffer-binary-arrays/article.md +++ b/4-binary/01-arraybuffer-binary-arrays/article.md @@ -30,11 +30,11 @@ Let's eliminate a possible source of confusion. `ArrayBuffer` has nothing in com **To manipulate an `ArrayBuffer`, we need to use a "view" object.** -A view object does not store anything on it's own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`. +A view object does not store anything on its own. It's the "eyeglasses" that give an interpretation of the bytes stored in the `ArrayBuffer`. For instance: -- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values are from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". +- **`Uint8Array`** -- treats each byte in `ArrayBuffer` as a separate number, with possible values from 0 to 255 (a byte is 8-bit, so it can hold only that much). Such value is called a "8-bit unsigned integer". - **`Uint16Array`** -- treats every 2 bytes as an integer, with possible values from 0 to 65535. That's called a "16-bit unsigned integer". - **`Uint32Array`** -- treats every 4 bytes as an integer, with possible values from 0 to 4294967295. That's called a "32-bit unsigned integer". - **`Float64Array`** -- treats every 8 bytes as a floating point number with possible values from 5.0x10-324 to 1.8x10308. @@ -71,10 +71,13 @@ for(let num of view) { ## TypedArray -The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properities. +The common term for all these views (`Uint8Array`, `Uint32Array`, etc) is [TypedArray](https://tc39.github.io/ecma262/#sec-typedarray-objects). They share the same set of methods and properties. -They are much more like regular arrays: have indexes and iterable. +Please note, there's no constructor called `TypedArray`, it's just a common "umbrella" term to represent one of views over `ArrayBuffer`: `Int8Array`, `Uint8Array` and so on, the full list will soon follow. +When you see something like `new TypedArray`, it means any of `new Int8Array`, `new Uint8Array`, etc. + +Typed arrays behave like regular arrays: have indexes and are iterable. A typed array constructor (be it `Int8Array` or `Float64Array`, doesn't matter) behaves differently depending on argument types. @@ -123,9 +126,9 @@ new TypedArray(); We can create a `TypedArray` directly, without mentioning `ArrayBuffer`. But a view cannot exist without an underlying `ArrayBuffer`, so gets created automatically in all these cases except the first one (when provided). -To access the `ArrayBuffer`, there are properties: -- `arr.buffer` -- references the `ArrayBuffer`. -- `arr.byteLength` -- the length of the `ArrayBuffer`. +To access the underlying `ArrayBuffer`, there are following properties in `TypedArray`: +- `buffer` -- references the `ArrayBuffer`. +- `byteLength` -- the length of the `ArrayBuffer`. So, we can always move from one view to another: ```js @@ -206,7 +209,7 @@ These methods allow us to copy typed arrays, mix them, create new arrays from ex ## DataView -[DataView](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format. +[DataView](mdn:/JavaScript/Reference/Global_Objects/DataView) is a special super-flexible "untyped" view over `ArrayBuffer`. It allows to access the data on any offset in any format. - For typed arrays, the constructor dictates what the format is. The whole array is supposed to be uniform. The i-th number is `arr[i]`. - With `DataView` we access the data with methods like `.getUint8(i)` or `.getUint16(i)`. We choose the format at method call time instead of the construction time. @@ -232,7 +235,7 @@ let dataView = new DataView(buffer); // get 8-bit number at offset 0 alert( dataView.getUint8(0) ); // 255 -// now get 16-bit number at offset 0, it consists of 2 bytes, together iterpreted as 65535 +// now get 16-bit number at offset 0, it consists of 2 bytes, together interpreted as 65535 alert( dataView.getUint16(0) ); // 65535 (biggest 16-bit unsigned int) // get 32-bit number at offset 0 @@ -241,7 +244,7 @@ alert( dataView.getUint32(0) ); // 4294967295 (biggest 32-bit unsigned int) dataView.setUint32(0, 0); // set 4-byte number to zero, thus setting all bytes to 0 ``` -`DataView` is great when we store mixed-format data in the same buffer. E.g we store a sequence of pairs (16-bit integer, 32-bit float). Then `DataView` allows to access them easily. +`DataView` is great when we store mixed-format data in the same buffer. For example, when we store a sequence of pairs (16-bit integer, 32-bit float), `DataView` allows to access them easily. ## Summary @@ -256,7 +259,7 @@ To do almost any operation on `ArrayBuffer`, we need a view. - `Float32Array`, `Float64Array` -- for signed floating-point numbers of 32 and 64 bits. - Or a `DataView` -- the view that uses methods to specify a format, e.g. `getUint8(offset)`. -In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common discriminator". We can access it as `.buffer` and make another view if needed. +In most cases we create and operate directly on typed arrays, leaving `ArrayBuffer` under cover, as a "common denominator". We can access it as `.buffer` and make another view if needed. There are also two additional terms, that are used in descriptions of methods that operate on binary data: - `ArrayBufferView` is an umbrella term for all these kinds of views. diff --git a/4-binary/02-text-decoder/article.md b/4-binary/02-text-decoder/article.md index d9f5e8fa5..a0c80145c 100644 --- a/4-binary/02-text-decoder/article.md +++ b/4-binary/02-text-decoder/article.md @@ -2,7 +2,7 @@ What if the binary data is actually a string? For instance, we received a file with textual data. -The build-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows to read the value into an actual JavaScript string, given the buffer and the encoding. +The built-in [TextDecoder](https://encoding.spec.whatwg.org/#interface-textdecoder) object allows one to read the value into an actual JavaScript string, given the buffer and the encoding. We first need to create it: ```js @@ -12,7 +12,7 @@ let decoder = new TextDecoder([label], [options]); - **`label`** -- the encoding, `utf-8` by default, but `big5`, `windows-1251` and many other are also supported. - **`options`** -- optional object: - **`fatal`** -- boolean, if `true` then throw an exception for invalid (non-decodable) characters, otherwise (default) replace them with character `\uFFFD`. - - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order unicode mark), rarely needed. + - **`ignoreBOM`** -- boolean, if `true` then ignore BOM (an optional byte-order Unicode mark), rarely needed. ...And then decode: @@ -58,7 +58,7 @@ alert( new TextDecoder().decode(binaryString) ); // Hello The syntax is: -```js run +```js let encoder = new TextEncoder(); ``` diff --git a/4-binary/03-blob/article.md b/4-binary/03-blob/article.md index 062e1834b..fc0150577 100644 --- a/4-binary/03-blob/article.md +++ b/4-binary/03-blob/article.md @@ -55,7 +55,7 @@ This behavior is similar to JavaScript strings: we can't change a character in a ## Blob as URL -A Blob can be easily used as an URL for ``, `` or other tags, to show its contents. +A Blob can be easily used as a URL for ``, `` or other tags, to show its contents. Thanks to `type`, we can also download/upload `Blob` objects, and the `type` naturally becomes `Content-Type` in network requests. @@ -74,7 +74,7 @@ link.href = URL.createObjectURL(blob); We can also create a link dynamically in JavaScript and simulate a click by `link.click()`, then download starts automatically. -Here's the similar code that causes user to download the dynamicallly created `Blob`, without any HTML: +Here's the similar code that causes user to download the dynamically created `Blob`, without any HTML: ```js run let link = document.createElement('a'); @@ -97,11 +97,11 @@ That's what the value of `link.href` looks like: blob:https://javascript.info/1e67e00e-860d-40a5-89ae-6ab0cbee6273 ``` -The browser for each URL generated by `URL.createObjectURL` stores an the URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. +For each URL generated by `URL.createObjectURL` the browser stores a URL -> `Blob` mapping internally. So such URLs are short, but allow to access the `Blob`. -A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects an url. +A generated URL (and hence the link with it) is only valid within the current document, while it's open. And it allows to reference the `Blob` in ``, ``, basically any other object that expects a URL. -There's a side-effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. +There's a side effect though. While there's a mapping for a `Blob`, the `Blob` itself resides in the memory. The browser can't free it. The mapping is automatically cleared on document unload, so `Blob` objects are freed then. But if an app is long-living, then that doesn't happen soon. @@ -119,7 +119,7 @@ An alternative to `URL.createObjectURL` is to convert a `Blob` into a base64-enc That encoding represents binary data as a string of ultra-safe "readable" characters with ASCII-codes from 0 to 64. And what's more important -- we can use this encoding in "data-urls". -A [data url](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls. +A [data url](mdn:/http/Data_URIs) has the form `data:[][;base64],`. We can use such urls everywhere, on par with "regular" urls. For instance, here's a smiley: @@ -151,7 +151,7 @@ reader.onload = function() { }; ``` -Both ways of making an URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. +Both ways of making a URL of a `Blob` are usable. But usually `URL.createObjectURL(blob)` is simpler and faster. ```compare title-plus="URL.createObjectURL(blob)" title-minus="Blob to data url" + We need to revoke them if care about memory. @@ -166,8 +166,8 @@ We can create a `Blob` of an image, an image part, or even make a page screensho Image operations are done via `` element: -1. Draw an image (or its part) on canvas using [canvas.drawImage](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage). -2. Call canvas method [.toBlob(callback, format, quality)](https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done. +1. Draw an image (or its part) on canvas using [canvas.drawImage](mdn:/api/CanvasRenderingContext2D/drawImage). +2. Call canvas method [.toBlob(callback, format, quality)](mdn:/api/HTMLCanvasElement/toBlob) that creates a `Blob` and runs `callback` with it when done. In the example below, an image is just copied, but we could cut from it, or transform it on canvas prior to making a blob: @@ -186,7 +186,7 @@ let context = canvas.getContext('2d'); context.drawImage(img, 0, 0); // we can context.rotate(), and do many other things on canvas -// toBlob is async opereation, callback is called when done +// toBlob is async operation, callback is called when done canvas.toBlob(function(blob) { // blob ready, download it let link = document.createElement('a'); @@ -211,21 +211,44 @@ For screenshotting a page, we can use a library such as /* process the ArrayBuffer */); ``` +## From Blob to stream + +When we read and write to a blob of more than `2 GB`, the use of `arrayBuffer` becomes more memory intensive for us. At this point, we can directly convert the blob to a stream. + +A stream is a special object that allows to read from it (or write into it) portion by portion. It's outside of our scope here, but here's an example, and you can read more at . Streams are convenient for data that is suitable for processing piece-by-piece. + +The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the `Blob`. + +Then we can read from it, like this: + +```js +// get readableStream from blob +const readableStream = blob.stream(); +const stream = readableStream.getReader(); + +while (true) { + // for each iteration: value is the next blob fragment + let { done, value } = await stream.read(); + if (done) { + // no more data in the stream + console.log('all blob processed.'); + break; + } + + // do something with the data portion we've just read from the blob + console.log(value); +} +``` ## Summary @@ -235,7 +258,9 @@ That makes Blobs convenient for upload/download operations, that are so common i Methods that perform web-requests, such as [XMLHttpRequest](info:xmlhttprequest), [fetch](info:fetch) and so on, can work with `Blob` natively, as well as with other binary types. -We can easily convert betweeen `Blob` and low-level binary data types: +We can easily convert between `Blob` and low-level binary data types: + +- We can make a `Blob` from a typed array using `new Blob(...)` constructor. +- We can get back `ArrayBuffer` from a Blob using `blob.arrayBuffer()`, and then create a view over it for low-level binary processing. -- We can make a Blob from a typed array using `new Blob(...)` constructor. -- We can get back `ArrayBuffer` from a Blob using `FileReader`, and then create a view over it for low-level binary processing. +Conversion streams are very useful when we need to handle large blob. You can easily create a `ReadableStream` from a blob. The `Blob` interface's `stream()` method returns a `ReadableStream` which upon reading returns the data contained within the blob. diff --git a/4-binary/03-blob/blob.svg b/4-binary/03-blob/blob.svg index b1cf6fcc8..8f4245451 100644 --- a/4-binary/03-blob/blob.svg +++ b/4-binary/03-blob/blob.svg @@ -1 +1 @@ -image/pngblob1blob2strbuffer...typeBlobblobParts+= \ No newline at end of file +image/pngblob1blob2strbuffer...typeBlobblobParts+= \ No newline at end of file diff --git a/4-binary/04-file/article.md b/4-binary/04-file/article.md index 43c0ca583..20878b650 100644 --- a/4-binary/04-file/article.md +++ b/4-binary/04-file/article.md @@ -61,8 +61,8 @@ The main methods: The choice of `read*` method depends on which format we prefer, how we're going to use the data. -- `readAsArrayBuffer` - for binary files, to do low-level binary operations. For high-level operations, like slicing, `File` inherits from `Blob`, so we can call them directly, without reading. -- `readAsText` - for text files, when we'd like to get a string. +- `readAsArrayBuffer` -- for binary files, to do low-level binary operations. For high-level operations, like slicing, `File` inherits from `Blob`, so we can call them directly, without reading. +- `readAsText` -- for text files, when we'd like to get a string. - `readAsDataURL` -- when we'd like to use this data in `src` for `img` or another tag. There's an alternative to reading a file for that, as discussed in chapter : `URL.createObjectURL(file)`. As the reading proceeds, there are events: diff --git a/5-network/01-fetch/01-fetch-users/solution.md b/5-network/01-fetch/01-fetch-users/solution.md index b8dfb62a2..3cb88e4ea 100644 --- a/5-network/01-fetch/01-fetch-users/solution.md +++ b/5-network/01-fetch/01-fetch-users/solution.md @@ -3,7 +3,7 @@ To fetch a user we need: `fetch('https://github.com/service/https://api.github.com/users/USERNAME')`. If the response has status `200`, call `.json()` to read the JS object. -Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting arrray. +Otherwise, if a `fetch` fails, or the response has non-200 status, we just return `null` in the resulting array. So here's the code: diff --git a/5-network/01-fetch/article.md b/5-network/01-fetch/article.md index 1170042e0..4669fc451 100644 --- a/5-network/01-fetch/article.md +++ b/5-network/01-fetch/article.md @@ -1,7 +1,7 @@ # Fetch -JavaScript can send network requests to the server and load new information whenever is needed. +JavaScript can send network requests to the server and load new information whenever it's needed. For example, we can use a network request to: @@ -27,7 +27,7 @@ let promise = fetch(url, [options]) - **`url`** -- the URL to access. - **`options`** -- optional parameters: method, headers etc. -Without `options`, that is a simple GET request, downloading the contents of the `url`. +Without `options`, this is a simple GET request, downloading the contents of the `url`. The browser starts the request right away and returns a promise that the calling code should use to get the result. @@ -65,8 +65,8 @@ if (response.ok) { // if HTTP-status is 200-299 - **`response.json()`** -- parse the response as JSON, - **`response.formData()`** -- return the response as `FormData` object (explained in the [next chapter](info:formdata)), - **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), -- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representaion of binary data), -- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows to read the body chunk-by-chunk, we'll see an example later. +- **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level representation of binary data), +- additionally, `response.body` is a [ReadableStream](https://streams.spec.whatwg.org/#rs-class) object, it allows you to read the body chunk-by-chunk, we'll see an example later. For instance, let's get a JSON-object with latest commits from GitHub: @@ -89,7 +89,7 @@ fetch('https://github.com/service/https://api.github.com/repos/javascript-tutorial/en.javascript.info/commi%20%20%20.then(commits%20=%3E%20alert(commits[0].author.login));%20%60%60%60%20-To%20get%20the%20reponse%20text,%20%60await%20response.text()%60%20instead%20of%20%60.json()%60:+To%20get%20the%20response%20text,%20%60await%20response.text()%60%20instead%20of%20%60.json()%60:%20%20%60%60%60js%20run%20async%20let%20response%20=%20await%20fetch('https://api.github.com/repos/javascript-tutorial/en.javascript.info/commits'); @@ -130,6 +130,7 @@ If we've already got the response with `response.text()`, then `response.json()` ```js let text = await response.text(); // response body consumed let parsed = await response.json(); // fails (already consumed) +``` ```` ## Response headers @@ -194,7 +195,7 @@ To make a `POST` request, or a request with another method, we need to use `fetc - **`method`** -- HTTP-method, e.g. `POST`, - **`body`** -- the request body, one of: - a string (e.g. JSON-encoded), - - `FormData` object, to submit the data as `form/multipart`, + - `FormData` object, to submit the data as `multipart/form-data`, - `Blob`/`BufferSource` to send binary data, - [URLSearchParams](info:url), to submit the data in `x-www-form-urlencoded` encoding, rarely used. @@ -230,7 +231,7 @@ But, as we're going to send JSON, we use `headers` option to send `application/j We can also submit binary data with `fetch` using `Blob` or `BufferSource` objects. -In this example, there's a `` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to server: +In this example, there's a `` where we can draw by moving a mouse over it. A click on the "submit" button sends the image to the server: ```html run autorun height="90" @@ -297,13 +298,13 @@ fetch(url, options) Response properties: - `response.status` -- HTTP code of the response, -- `response.ok` -- `true` is the status is 200-299. +- `response.ok` -- `true` if the status is 200-299. - `response.headers` -- Map-like object with HTTP headers. Methods to get response body: - **`response.text()`** -- return the response as text, - **`response.json()`** -- parse the response as JSON object, -- **`response.formData()`** -- return the response as `FormData` object (form/multipart encoding, see the next chapter), +- **`response.formData()`** -- return the response as `FormData` object (`multipart/form-data` encoding, see the next chapter), - **`response.blob()`** -- return the response as [Blob](info:blob) (binary data with type), - **`response.arrayBuffer()`** -- return the response as [ArrayBuffer](info:arraybuffer-binary-arrays) (low-level binary data), diff --git a/5-network/02-formdata/article.md b/5-network/02-formdata/article.md index fb028f5c8..a73d554b1 100644 --- a/5-network/02-formdata/article.md +++ b/5-network/02-formdata/article.md @@ -12,7 +12,7 @@ let formData = new FormData([form]); If HTML `form` element is provided, it automatically captures its fields. -The special thing about `FormData` is that network methods, such as `fetch`, can accept a `FormData` object as a body. It's encoded and sent out with `Content-Type: form/multipart`. +The special thing about `FormData` is that network methods, such as `fetch`, can accept a `FormData` object as a body. It's encoded and sent out with `Content-Type: multipart/form-data`. From the server point of view, that looks like a usual form submission. @@ -47,21 +47,21 @@ As you can see, that's almost one-liner: ``` -In this example, the server code is not presented, as it's beyound our scope. The server accepts the POST request and replies "User saved". +In this example, the server code is not presented, as it's beyond our scope. The server accepts the POST request and replies "User saved". ## FormData Methods We can modify fields in `FormData` with methods: - `formData.append(name, value)` - add a form field with the given `name` and `value`, -- `formData.append(name, blob, fileName)` - add a field as if it were ``, the third argument `fileName` sets file name (not form field name), as it it were a name of the file in user's filesystem, +- `formData.append(name, blob, fileName)` - add a field as if it were ``, the third argument `fileName` sets file name (not form field name), as it were a name of the file in user's filesystem, - `formData.delete(name)` - remove the field with the given `name`, - `formData.get(name)` - get the value of the field with the given `name`, - `formData.has(name)` - if there exists a field with the given `name`, returns `true`, otherwise `false` A form is technically allowed to have many fields with the same `name`, so multiple calls to `append` add more same-named fields. -There's also method `set`, with the same syntax as `append`. The difference is that `.set` removes all fields with the given `name`, and then appends a new field. So it makes sure there's only field with such `name`, the rest is just like `append`: +There's also method `set`, with the same syntax as `append`. The difference is that `.set` removes all fields with the given `name`, and then appends a new field. So it makes sure there's only one field with such `name`, the rest is just like `append`: - `formData.set(name, value)`, - `formData.set(name, blob, fileName)`. @@ -75,13 +75,13 @@ formData.append('key2', 'value2'); // List key/value pairs for(let [name, value] of formData) { - alert(`${name} = ${value}`); // key1=value1, then key2=value2 + alert(`${name} = ${value}`); // key1 = value1, then key2 = value2 } ``` ## Sending a form with a file -The form is always sent as `Content-Type: form/multipart`, this encoding allows to send files. So, `` fields are sent also, similar to a usual form submission. +The form is always sent as `Content-Type: multipart/form-data`, this encoding allows to send files. So, `` fields are sent also, similar to a usual form submission. Here's an example with such form: @@ -168,7 +168,7 @@ The server reads form data and the file, as if it were a regular form submission [FormData](https://xhr.spec.whatwg.org/#interface-formdata) objects are used to capture HTML form and submit it using `fetch` or another network method. -We can either create `new FormData(form)` from an HTML form, or create a object without a form at all, and then append fields with methods: +We can either create `new FormData(form)` from an HTML form, or create an object without a form at all, and then append fields with methods: - `formData.append(name, value)` - `formData.append(name, blob, fileName)` diff --git a/5-network/03-fetch-progress/article.md b/5-network/03-fetch-progress/article.md index b26a8a0ce..76b05d514 100644 --- a/5-network/03-fetch-progress/article.md +++ b/5-network/03-fetch-progress/article.md @@ -5,11 +5,11 @@ The `fetch` method allows to track *download* progress. Please note: there's currently no way for `fetch` to track *upload* progress. For that purpose, please use [XMLHttpRequest](info:xmlhttprequest), we'll cover it later. -To track download progress, we can use `response.body` property. It's `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. +To track download progress, we can use `response.body` property. It's a `ReadableStream` -- a special object that provides body chunk-by-chunk, as it comes. Readable streams are described in the [Streams API](https://streams.spec.whatwg.org/#rs-class) specification. Unlike `response.text()`, `response.json()` and other methods, `response.body` gives full control over the reading process, and we can count how much is consumed at any moment. -Here's the sketch of code that reads the reponse from `response.body`: +Here's the sketch of code that reads the response from `response.body`: ```js // instead of response.json() and other methods @@ -107,6 +107,8 @@ Let's explain that step-by-step: let blob = new Blob(chunks); ``` -At we end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process. +At the end we have the result (as a string or a blob, whatever is convenient), and progress-tracking in the process. Once again, please note, that's not for *upload* progress (no way now with `fetch`), only for *download* progress. + +Also, if the size is unknown, we should check `receivedLength` in the loop and break it once it reaches a certain limit. So that the `chunks` won't overflow the memory. diff --git a/5-network/04-fetch-abort/article.md b/5-network/04-fetch-abort/article.md index 757846287..eadc5aac2 100644 --- a/5-network/04-fetch-abort/article.md +++ b/5-network/04-fetch-abort/article.md @@ -1,61 +1,81 @@ # Fetch: Abort -As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we abort a `fetch`? +As we know, `fetch` returns a promise. And JavaScript generally has no concept of "aborting" a promise. So how can we cancel an ongoing `fetch`? E.g. if the user actions on our site indicate that the `fetch` isn't needed any more. -There's a special built-in object for such purposes: `AbortController`, that can be used to abort not only `fetch`, but other asynchronous tasks as well. +There's a special built-in object for such purposes: `AbortController`. It can be used to abort not only `fetch`, but other asynchronous tasks as well. -The usage is pretty simple: +The usage is very straightforward: -- Step 1: create a controller: +## The AbortController object - ```js - let controller = new AbortController(); - ``` +Create a controller: - A controller is an extremely simple object. +```js +let controller = new AbortController(); +``` - - It has a single method `abort()`, and a single property `signal`. - - When `abort()` is called: - - `abort` event triggers on `controller.signal` - - `controller.signal.aborted` property becomes `true`. +A controller is an extremely simple object. - All parties interested to learn about `abort()` call set listeners on `controller.signal` to track it. +- It has a single method `abort()`, +- And a single property `signal` that allows to set event listeners on it. - Like this (without `fetch` yet): +When `abort()` is called: +- `controller.signal` emits the `"abort"` event. +- `controller.signal.aborted` property becomes `true`. - ```js run - let controller = new AbortController(); - let signal = controller.signal; +Generally, we have two parties in the process: +1. The one that performs a cancelable operation, it sets a listener on `controller.signal`. +2. The one that cancels: it calls `controller.abort()` when needed. - // triggers when controller.abort() is called - signal.addEventListener('abort', () => alert("abort!")); +Here's the full example (without `fetch` yet): - controller.abort(); // abort! +```js run +let controller = new AbortController(); +let signal = controller.signal; + +// The party that performs a cancelable operation +// gets the "signal" object +// and sets the listener to trigger when controller.abort() is called +signal.addEventListener('abort', () => alert("abort!")); - alert(signal.aborted); // true - ``` +// The other party, that cancels (at any point later): +controller.abort(); // abort! -- Step 2: pass the `signal` property to `fetch` option: +// The event triggers and signal.aborted becomes true +alert(signal.aborted); // true +``` - ```js - let controller = new AbortController(); - fetch(url, { - signal: controller.signal - }); - ``` +As we can see, `AbortController` is just a mean to pass `abort` events when `abort()` is called on it. - The `fetch` method knows how to work with `AbortController`, it listens to `abort` on `signal`. +We could implement the same kind of event listening in our code on our own, without the `AbortController` object. -- Step 3: to abort, call `controller.abort()`: +But what's valuable is that `fetch` knows how to work with the `AbortController` object. It's integrated in it. - ```js - controller.abort(); - ``` +## Using with fetch - We're done: `fetch` gets the event from `signal` and aborts the request. +To be able to cancel `fetch`, pass the `signal` property of an `AbortController` as a `fetch` option: -When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`: +```js +let controller = new AbortController(); +fetch(url, { + signal: controller.signal +}); +``` + +The `fetch` method knows how to work with `AbortController`. It will listen to `abort` events on `signal`. + +Now, to abort, call `controller.abort()`: + +```js +controller.abort(); +``` + +We're done: `fetch` gets the event from `signal` and aborts the request. + +When a fetch is aborted, its promise rejects with an error `AbortError`, so we should handle it, e.g. in `try..catch`. + +Here's the full example with `fetch` aborted after 1 second: ```js run async // abort in 1 second @@ -75,28 +95,31 @@ try { } ``` -**`AbortController` is scalable, it allows to cancel multiple fetches at once.** +## AbortController is scalable -For instance, here we fetch many `urls` in parallel, and the controller aborts them all: +`AbortController` is scalable. It allows to cancel multiple fetches at once. + +Here's a sketch of code that fetches many `urls` in parallel, and uses a single controller to abort them all: ```js let urls = [...]; // a list of urls to fetch in parallel let controller = new AbortController(); +// an array of fetch promises let fetchJobs = urls.map(url => fetch(url, { signal: controller.signal })); let results = await Promise.all(fetchJobs); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches ``` -If we have our own asynchronous jobs, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. +If we have our own asynchronous tasks, different from `fetch`, we can use a single `AbortController` to stop those, together with fetches. -We just need to listen to its `abort` event: +We just need to listen to its `abort` event in our tasks: ```js let urls = [...]; @@ -114,8 +137,12 @@ let fetchJobs = urls.map(url => fetch(url, { // fetches // Wait for fetches and our task in parallel let results = await Promise.all([...fetchJobs, ourJob]); -// if controller.abort() is called from elsewhere, +// if controller.abort() is called from anywhere, // it aborts all fetches and ourJob ``` -So `AbortController` is not only for `fetch`, it's a universal object to abort asynchronous tasks, and `fetch` has built-in integration with it. +## Summary + +- `AbortController` is a simple object that generates an `abort` event on its `signal` property when the `abort()` method is called (and also sets `signal.aborted` to `true`). +- `fetch` integrates with it: we pass the `signal` property as the option, and then `fetch` listens to it, so it's possible to abort the `fetch`. +- We can use `AbortController` in our code. The "call `abort()`" -> "listen to `abort` event" interaction is simple and universal. We can use it even without `fetch`. diff --git a/5-network/05-fetch-crossorigin/article.md b/5-network/05-fetch-crossorigin/article.md index f5a5f663d..4420f43c7 100644 --- a/5-network/05-fetch-crossorigin/article.md +++ b/5-network/05-fetch-crossorigin/article.md @@ -20,7 +20,7 @@ Cross-origin requests -- those sent to another domain (even a subdomain) or prot That policy is called "CORS": Cross-Origin Resource Sharing. -## Why CORS is needed? A brief history +## Why is CORS needed? A brief history CORS exists to protect the internet from evil hackers. @@ -28,7 +28,7 @@ Seriously. Let's make a very brief historical digression. **For many years a script from one site could not access the content of another site.** -That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access user's mailbox at website `gmail.com`. People felt safe. +That simple, yet powerful rule was a foundation of the internet security. E.g. an evil script from website `hacker.com` could not access the user's mailbox at website `gmail.com`. People felt safe. JavaScript also did not have any special methods to perform network requests at that time. It was a toy language to decorate a web page. @@ -44,7 +44,7 @@ One way to communicate with another server was to submit a `` there. Peopl */!* - + *!* */!* @@ -95,45 +95,45 @@ That works, and doesn't violate security, because both sides agreed to pass the After a while, networking methods appeared in browser JavaScript. -At first, cross-origin requests were forbidden. But as a result of long discussions, cross-origin requests were allowed, but any new capabilities unless require an explicit allowance by the server, expressed in special headers. +At first, cross-origin requests were forbidden. But as a result of long discussions, cross-origin requests were allowed, but with any new capabilities requiring an explicit allowance by the server, expressed in special headers. -## Simple requests +## Safe requests There are two types of cross-origin requests: -1. Simple requests. +1. Safe requests. 2. All the others. -Simple Requests are, well, simpler to make, so let's start with them. +Safe Requests are simpler to make, so let's start with them. -A [simple request](http://www.w3.org/TR/cors/#terminology) is a request that satisfies two conditions: +A request is safe if it satisfies two conditions: -1. [Simple method](http://www.w3.org/TR/cors/#simple-method): GET, POST or HEAD -2. [Simple headers](http://www.w3.org/TR/cors/#simple-header) -- the only allowed custom headers are: +1. [Safe method](https://fetch.spec.whatwg.org/#cors-safelisted-method): GET, POST or HEAD +2. [Safe headers](https://fetch.spec.whatwg.org/#cors-safelisted-request-header) -- the only allowed custom headers are: - `Accept`, - `Accept-Language`, - `Content-Language`, - `Content-Type` with the value `application/x-www-form-urlencoded`, `multipart/form-data` or `text/plain`. -Any other request is considered "non-simple". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations. +Any other request is considered "unsafe". For instance, a request with `PUT` method or with an `API-Key` HTTP-header does not fit the limitations. -**The essential difference is that a "simple request" can be made with a `` or a ` ``` -Now let's cover animation properties one by one. +Now, let's cover animation properties one by one. ## transition-property -In `transition-property` we write a list of property to animate, for instance: `left`, `margin-left`, `height`, `color`. +In `transition-property`, we write a list of properties to animate, for instance: `left`, `margin-left`, `height`, `color`. Or we could write `all`, which means "animate all properties". -Not all properties can be animated, but [many of them](http://www.w3.org/TR/css3-transitions/#animatable-properties-). The value `all` means "animate all properties". +Do note that, there are properties which can not be animated. However, [most of the generally used properties are animatable](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_animated_properties). ## transition-duration -In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](http://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`. +In `transition-duration` we can specify how long the animation should take. The time should be in [CSS time format](https://www.w3.org/TR/css3-values/#time): in seconds `s` or milliseconds `ms`. ## transition-delay -In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay: 1s`, then animation starts after 1 second after the change. +In `transition-delay` we can specify the delay *before* the animation. For instance, if `transition-delay` is `1s` and `transition-duration` is `2s`, then the animation starts 1 second after the property change and the total duration will be 2 seconds. -Negative values are also possible. Then the animation starts from the middle. For instance, if `transition-duration` is `2s`, and the delay is `-1s`, then the animation takes 1 second and starts from the half. +Negative values are also possible. Then the animation is shown immediately, but the starting point of the animation will be after given value (time). For example, if `transition-delay` is `-1s` and `transition-duration` is `2s`, then animation starts from the halfway point and total duration will be 1 second. -Here's the animation shifts numbers from `0` to `9` using CSS `translate` property: +Here the animation shifts numbers from `0` to `9` using CSS `translate` property: [codetabs src="https://github.com/digits"] @@ -108,13 +108,13 @@ In the example above JavaScript adds the class `.animate` to the element -- and stripe.classList.add('animate'); ``` -We can also start it "from the middle", from the exact number, e.g. corresponding to the current second, using the negative `transition-delay`. +We could also start it from somewhere in the middle of the transition, from an exact number, e.g. corresponding to the current second, using a negative `transition-delay`. Here if you click the digit -- it starts the animation from the current second: [codetabs src="https://github.com/digits-negative-delay"] -JavaScript does it by an extra line: +JavaScript does it with an extra line: ```js stripe.onclick = function() { @@ -129,25 +129,25 @@ stripe.onclick = function() { ## transition-timing-function -Timing function describes how the animation process is distributed along the time. Will it start slowly and then go fast or vise versa. +The timing function describes how the animation process is distributed along its timeline. Will it start slowly and then go fast, or vice versa. -That's the most complicated property from the first sight. But it becomes very simple if we devote a bit time to it. +It appears to be the most complicated property at first. But it becomes very simple if we devote a bit time to it. -That property accepts two kinds of values: a Bezier curve or steps. Let's start from the curve, as it's used more often. +That property accepts two kinds of values: a Bezier curve or steps. Let's start with the curve, as it's used more often. ### Bezier curve -The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfies the conditions: +The timing function can be set as a [Bezier curve](/bezier-curve) with 4 control points that satisfy the conditions: 1. First control point: `(0,0)`. 2. Last control point: `(1,1)`. -3. For intermediate points values of `x` must be in the interval `0..1`, `y` can be anything. +3. For intermediate points, the values of `x` must be in the interval `0..1`, `y` can be anything. The syntax for a Bezier curve in CSS: `cubic-bezier(x2, y2, x3, y3)`. Here we need to specify only 2nd and 3rd control points, because the 1st one is fixed to `(0,0)` and the 4th one is `(1,1)`. -The timing function describes how fast the animation process goes in time. +The timing function describes how fast the animation process goes. -- The `x` axis is the time: `0` -- the starting moment, `1` -- the last moment of `transition-duration`. +- The `x` axis is the time: `0` -- the start, `1` -- the end of `transition-duration`. - The `y` axis specifies the completion of the process: `0` -- the starting value of the property, `1` -- the final value. The simplest variant is when the animation goes uniformly, with the same linear speed. That can be specified by the curve `cubic-bezier(0, 0, 1, 1)`. @@ -168,7 +168,7 @@ The CSS `transition` is based on that curve: .train { left: 0; transition: left 5s cubic-bezier(0, 0, 1, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` @@ -191,13 +191,13 @@ CSS: .train { left: 0; transition: left 5s cubic-bezier(0, .5, .5, 1); - /* JavaScript sets left to 450px */ + /* click on a train sets left to 450px, thus triggering the animation */ } ``` There are several built-in curves: `linear`, `ease`, `ease-in`, `ease-out` and `ease-in-out`. -The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, we saw it already. +The `linear` is a shorthand for `cubic-bezier(0, 0, 1, 1)` -- a straight line, which we described above. Other names are shorthands for the following `cubic-bezier`: @@ -210,27 +210,27 @@ Other names are shorthands for the following `cubic-bezier`: So we could use `ease-out` for our slowing down train: - ```css .train { left: 0; transition: left 5s ease-out; - /* transition: left 5s cubic-bezier(0, .5, .5, 1); */ + /* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */ } ``` But it looks a bit differently. -**A Bezier curve can make the animation "jump out" of its range.** +**A Bezier curve can make the animation exceed its range.** -The control points on the curve can have any `y` coordinates: even negative or huge. Then the Bezier curve would also jump very low or high, making the animation go beyond its normal range. +The control points on the curve can have any `y` coordinates: even negative or huge ones. Then the Bezier curve would also extend very low or high, making the animation go beyond its normal range. In the example below the animation code is: + ```css .train { left: 100px; transition: left 5s cubic-bezier(.5, -1, .5, 2); - /* JavaScript sets left to 400px */ + /* click on a train sets left to 450px */ } ``` @@ -244,21 +244,29 @@ But if you click the train, you'll see that: [codetabs src="https://github.com/train-over"] -Why it happens -- pretty obvious if we look at the graph of the given Bezier curve: +Why it happens is pretty obvious if we look at the graph of the given Bezier curve: ![](bezier-train-over.svg) -We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made put it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. +We moved the `y` coordinate of the 2nd point below zero, and for the 3rd point we made it over `1`, so the curve goes out of the "regular" quadrant. The `y` is out of the "standard" range `0..1`. -As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property lower than the starting `left` and `y>1` -- over the final `left`. +As we know, `y` measures "the completion of the animation process". The value `y = 0` corresponds to the starting property value and `y = 1` -- the ending value. So values `y<0` move the property beyond the starting `left` and `y>1` -- past the final `left`. That's a "soft" variant for sure. If we put `y` values like `-99` and `99` then the train would jump out of the range much more. -But how to make the Bezier curve for a specific task? There are many tools. For instance, we can do it on the site . +But how do we make a Bezier curve for a specific task? There are many tools. + +- For instance, we can do it on the site . +- Browser developer tools also have special support for Bezier curves in CSS: + 1. Open the developer tools with `key:F12` (Mac: `key:Cmd+Opt+I`). + 2. Select the `Elements` tab, then pay attention to the `Styles` sub-panel at the right side. + 3. CSS properties with a word `cubic-bezier` will have an icon before this word. + 4. Click this icon to edit the curve. + ### Steps -Timing function `steps(number of steps[, start/end])` allows to split animation into steps. +The timing function `steps(number of steps[, start/end])` allows splitting an transition into multiple steps. Let's see that in an example with digits. @@ -266,7 +274,19 @@ Here's a list of digits, without any animations, just as a source: [codetabs src="https://github.com/step-list"] -We'll make the digits appear in a discrete way by making the part of the list outside of the red "window" invisible and shifting the list to the left with each step. +In the HTML, a stripe of digits is enclosed into a fixed-length `
      `: + +```html +
      +
      0123456789
      +
      +``` + +The `#digit` div has a fixed width and a border, so it looks like a red window. + +We'll make a timer: the digits will appear one by one, in a discrete way. + +To achieve that, we'll hide the `#stripe` outside of `#digit` using `overflow: hidden`, and then shift the `#stripe` to the left step-by-step. There will be 9 steps, a step-move for each digit: @@ -277,58 +297,60 @@ There will be 9 steps, a step-move for each digit: } ``` -In action: - -[codetabs src="https://github.com/step"] - The first argument of `steps(9, start)` is the number of steps. The transform will be split into 9 parts (10% each). The time interval is automatically divided into 9 parts as well, so `transition: 9s` gives us 9 seconds for the whole animation – 1 second per digit. The second argument is one of two words: `start` or `end`. -The `start` means that in the beginning of animation we need to do make the first step immediately. +The `start` means that in the beginning of animation we need to make the first step immediately. + +In action: + +[codetabs src="https://github.com/step"] -We can observe that during the animation: when we click on the digit it changes to `1` (the first step) immediately, and then changes in the beginning of the next second. +A click on the digit changes it to `1` (the first step) immediately, and then changes in the beginning of the next second. The process is progressing like this: - `0s` -- `-10%` (first change in the beginning of the 1st second, immediately) - `1s` -- `-20%` - ... -- `8s` -- `-80%` +- `8s` -- `-90%` - (the last second shows the final value). +Here, the first change was immediate because of `start` in the `steps`. + The alternative value `end` would mean that the change should be applied not in the beginning, but at the end of each second. -So the process would go like this: +So the process for `steps(9, end)` would go like this: -- `0s` -- `0` +- `0s` -- `0` (during the first second nothing changes) - `1s` -- `-10%` (first change at the end of the 1st second) - `2s` -- `-20%` - ... - `9s` -- `-90%` -Here's `step(9, end)` in action (note the pause between the first digit change): +Here's `steps(9, end)` in action (note the pause before the first digit change): [codetabs src="https://github.com/step-end"] -There are also shorthand values: +There are also some pre-defined shorthands for `steps(...)`: - `step-start` -- is the same as `steps(1, start)`. That is, the animation starts immediately and takes 1 step. So it starts and finishes immediately, as if there were no animation. - `step-end` -- the same as `steps(1, end)`: make the animation in a single step at the end of `transition-duration`. -These values are rarely used, because that's not really animation, but rather a single-step change. +These values are rarely used, as they represent not a real animation, but rather a single-step change. We mention them here for completeness. -## Event transitionend +## Event: "transitionend" -When the CSS animation finishes the `transitionend` event triggers. +When the CSS animation finishes, the `transitionend` event triggers. It is widely used to do an action after the animation is done. Also we can join animations. -For instance, the ship in the example below starts to swim there and back on click, each time farther and farther to the right: +For instance, the ship in the example below starts to sail there and back when clicked, each time farther and farther to the right: [iframe src="https://github.com/boat" height=300 edit link] -The animation is initiated by the function `go` that re-runs each time when the transition finishes and flips the direction: +The animation is initiated by the function `go` that re-runs each time the transition finishes, and flips the direction: ```js boat.onclick = function() { @@ -337,11 +359,11 @@ boat.onclick = function() { function go() { if (times % 2) { - // swim to the right + // sail to the right boat.classList.remove('back'); boat.style.marginLeft = 100 * times + 200 + 'px'; } else { - // swim to the left + // sail to the left boat.classList.add('back'); boat.style.marginLeft = 100 * times - 200 + 'px'; } @@ -357,7 +379,7 @@ boat.onclick = function() { }; ``` -The event object for `transitionend` has few specific properties: +The event object for `transitionend` has a few specific properties: `event.propertyName` : The property that has finished animating. Can be good if we animate multiple properties simultaneously. @@ -369,7 +391,7 @@ The event object for `transitionend` has few specific properties: We can join multiple simple animations together using the `@keyframes` CSS rule. -It specifies the "name" of the animation and rules: what, when and where to animate. Then using the `animation` property we attach the animation to the element and specify additional parameters for it. +It specifies the "name" of the animation and rules - what, when and where to animate. Then using the `animation` property, we can attach the animation to the element and specify additional parameters for it. Here's an example with explanations: @@ -405,11 +427,92 @@ Here's an example with explanations: There are many articles about `@keyframes` and a [detailed specification](https://drafts.csswg.org/css-animations/). -Probably you won't need `@keyframes` often, unless everything is in the constant move on your sites. +You probably won't need `@keyframes` often, unless everything is in constant motion on your sites. + +## Performance + +Most CSS properties can be animated, because most of them are numeric values. For instance, `width`, `color`, `font-size` are all numbers. When you animate them, the browser gradually changes these numbers frame by frame, creating a smooth effect. + +However, not all animations will look as smooth as you'd like, because different CSS properties cost differently to change. + +In more technical details, when there's a style change, the browser goes through 3 steps to render the new look: + +1. **Layout**: re-compute the geometry and position of each element, then +2. **Paint**: re-compute how everything should look like at their places, including background, colors, +3. **Composite**: render the final results into pixels on screen, apply CSS transforms if they exist. + +During a CSS animation, this process repeats every frame. However, CSS properties that never affect geometry or position, such as `color`, may skip the Layout step. If a `color` changes, the browser doesn't calculate any new geometry, it goes to Paint -> Composite. And there are few properties that directly go to Composite. You can find a longer list of CSS properties and which stages they trigger at . + +The calculations may take time, especially on pages with many elements and a complex layout. And the delays are actually visible on most devices, leading to "jittery", less fluid animations. + +Animations of properties that skip the Layout step are faster. It's even better if Paint is skipped too. + +The `transform` property is a great choice, because: +- CSS transforms affect the target element box as a whole (rotate, flip, stretch, shift it). +- CSS transforms never affect neighbour elements. + +...So browsers apply `transform` "on top" of existing Layout and Paint calculations, in the Composite stage. + +In other words, the browser calculates the Layout (sizes, positions), paints it with colors, backgrounds, etc at the Paint stage, and then applies `transform` to element boxes that need it. + +Changes (animations) of the `transform` property never trigger Layout and Paint steps. More than that, the browser leverages the graphics accelerator (a special chip on the CPU or graphics card) for CSS transforms, thus making them very efficient. + +Luckily, the `transform` property is very powerful. By using `transform` on an element, you could rotate and flip it, stretch and shrink it, move it around, and [much more](https://developer.mozilla.org/docs/Web/CSS/transform#syntax). So instead of `left/margin-left` properties we can use `transform: translateX(…)`, use `transform: scale` for increasing element size, etc. + +The `opacity` property also never triggers Layout (also skips Paint in Mozilla Gecko). We can use it for show/hide or fade-in/fade-out effects. + +Paring `transform` with `opacity` can usually solve most of our needs, providing fluid, good-looking animations. + +For example, here clicking on the `#boat` element adds the class with `transform: translateX(300px)` and `opacity: 0`, thus making it move `300px` to the right and disappear: + +```html run height=260 autorun no-beautify + + + + +``` + +Here's a more complex example, with `@keyframes`: + +```html run height=80 autorun no-beautify +

      click me to start / stop

      + +``` ## Summary -CSS animations allow to smoothly (or not) animate changes of one or multiple CSS properties. +CSS animations allow smoothly (or step-by-step) animated changes of one or multiple CSS properties. They are good for most animation tasks. We're also able to use JavaScript for animations, the next chapter is devoted to that. @@ -419,9 +522,11 @@ Limitations of CSS animations compared to JavaScript animations: + Simple things done simply. + Fast and lightweight for CPU. - JavaScript animations are flexible. They can implement any animation logic, like an "explosion" of an element. -- Not just property changes. We can create new elements in JavaScript for purposes of animation. +- Not just property changes. We can create new elements in JavaScript as part of the animation. ``` -The majority of animations can be implemented using CSS as described in this chapter. And `transitionend` event allows to run JavaScript after the animation, so it integrates fine with the code. +In early examples in this chapter, we animate `font-size`, `left`, `width`, `height`, etc. In real life projects, we should use `transform: scale()` and `transform: translate()` for better performance. + +The majority of animations can be implemented using CSS as described in this chapter. And the `transitionend` event allows JavaScript to be run after the animation, so it integrates fine with the code. But in the next chapter we'll do some JavaScript animations to cover more complex cases. diff --git a/7-animation/2-css-animations/bezier-linear.svg b/7-animation/2-css-animations/bezier-linear.svg index 34949d61e..0c2e970f2 100644 --- a/7-animation/2-css-animations/bezier-linear.svg +++ b/7-animation/2-css-animations/bezier-linear.svg @@ -1 +1 @@ -12 \ No newline at end of file +12 \ No newline at end of file diff --git a/7-animation/2-css-animations/bezier-train-over.svg b/7-animation/2-css-animations/bezier-train-over.svg index ff5501c43..d12d09225 100644 --- a/7-animation/2-css-animations/bezier-train-over.svg +++ b/7-animation/2-css-animations/bezier-train-over.svg @@ -1 +1 @@ -(1,1)(0,0)(0,1)(1,0)1243 \ No newline at end of file +(1,1)(0,0)(0,1)(1,0)1243 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease-in-out.svg b/7-animation/2-css-animations/ease-in-out.svg index 29ef69a11..d5c8809d8 100644 --- a/7-animation/2-css-animations/ease-in-out.svg +++ b/7-animation/2-css-animations/ease-in-out.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease-in.svg b/7-animation/2-css-animations/ease-in.svg index 0e8b797ab..38c98ecbc 100644 --- a/7-animation/2-css-animations/ease-in.svg +++ b/7-animation/2-css-animations/ease-in.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease-out.svg b/7-animation/2-css-animations/ease-out.svg index c28372870..9d22eeafd 100644 --- a/7-animation/2-css-animations/ease-out.svg +++ b/7-animation/2-css-animations/ease-out.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/ease.svg b/7-animation/2-css-animations/ease.svg index a3a1feecd..8f9d41fe8 100644 --- a/7-animation/2-css-animations/ease.svg +++ b/7-animation/2-css-animations/ease.svg @@ -1 +1 @@ -1234 \ No newline at end of file +1234 \ No newline at end of file diff --git a/7-animation/2-css-animations/train-curve.svg b/7-animation/2-css-animations/train-curve.svg index f15235990..298dacd4c 100644 --- a/7-animation/2-css-animations/train-curve.svg +++ b/7-animation/2-css-animations/train-curve.svg @@ -1 +1 @@ -1243 \ No newline at end of file +1243 \ No newline at end of file diff --git a/7-animation/3-js-animation/1-animate-ball/solution.md b/7-animation/3-js-animation/1-animate-ball/solution.md index 5d3f08eef..0dc67b8bd 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.md +++ b/7-animation/3-js-animation/1-animate-ball/solution.md @@ -2,7 +2,7 @@ To bounce we can use CSS property `top` and `position:absolute` for the ball ins The bottom coordinate of the field is `field.clientHeight`. The CSS `top` property refers to the upper edge of the ball. So it should go from `0` till `field.clientHeight - ball.clientHeight`, that's the final lowest position of the upper edge of the ball. -To to get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. +To get the "bouncing" effect we can use the timing function `bounce` in `easeOut` mode. Here's the final code for the animation: diff --git a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html index 7e031e8d1..146033cf7 100644 --- a/7-animation/3-js-animation/1-animate-ball/solution.view/index.html +++ b/7-animation/3-js-animation/1-animate-ball/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html index b246f422f..f587ff607 100644 --- a/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html +++ b/7-animation/3-js-animation/2-animate-ball-hops/solution.view/index.html @@ -21,7 +21,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/article.md b/7-animation/3-js-animation/article.md index 004954340..b85e91e21 100644 --- a/7-animation/3-js-animation/article.md +++ b/7-animation/3-js-animation/article.md @@ -77,9 +77,9 @@ setInterval(animate3, 20); These several independent redraws should be grouped together, to make the redraw easier for the browser and hence load less CPU load and look smoother. -There's one more thing to keep in mind. Sometimes when CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. +There's one more thing to keep in mind. Sometimes CPU is overloaded, or there are other reasons to redraw less often (like when the browser tab is hidden), so we really shouldn't run it every `20ms`. -But how do we know about that in JavaScript? There's a specification [Animation timing](http://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. +But how do we know about that in JavaScript? There's a specification [Animation timing](https://www.w3.org/TR/animation-timing/) that provides the function `requestAnimationFrame`. It addresses all these issues and even more. The syntax: ```js @@ -96,7 +96,7 @@ The returned value `requestId` can be used to cancel the call: cancelAnimationFrame(requestId); ``` -The `callback` gets one argument -- the time passed from the beginning of the page load in microseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now). +The `callback` gets one argument -- the time passed from the beginning of the page load in milliseconds. This time can also be obtained by calling [performance.now()](mdn:api/Performance/now). Usually `callback` runs very soon, unless the CPU is overloaded or the laptop battery is almost discharged, or there's another reason. @@ -159,7 +159,7 @@ Function `animate` accepts 3 parameters that essentially describes the animation } ``` - It's graph: + Its graph: ![](linear.svg) That's just like `transition-timing-function: linear`. There are more interesting variants shown below. @@ -227,7 +227,7 @@ See in action (click to activate): [iframe height=40 src="https://github.com/quad" link] -...Or the cubic curve or event greater `n`. Increasing the power makes it speed up faster. +...Or the cubic curve or even greater `n`. Increasing the power makes it speed up faster. Here's the graph for `progress` in the power `5`: @@ -283,7 +283,7 @@ The `bounce` function does the same, but in the reverse order: "bouncing" starts ```js function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } @@ -397,7 +397,7 @@ The effect is clearly seen if we compare the graphs of `easeIn`, `easeOut` and ` ![](circ-ease.svg) -- Red is the regular variantof `circ` (`easeIn`). +- Red is the regular variant of `circ` (`easeIn`). - Green -- `easeOut`. - Blue -- `easeInOut`. @@ -405,7 +405,7 @@ As we can see, the graph of the first half of the animation is the scaled down ` ## More interesting "draw" -Instead of moving the element we can do something else. All we need is to write the write the proper `draw`. +Instead of moving the element we can do something else. All we need is to write the proper `draw`. Here's the animated "bouncing" text typing: @@ -452,4 +452,4 @@ Surely we could improve it, add more bells and whistles, but JavaScript animatio JavaScript animations can use any timing function. We covered a lot of examples and transformations to make them even more versatile. Unlike CSS, we are not limited to Bezier curves here. -The same is about `draw`: we can animate anything, not just CSS properties. +The same is true about `draw`: we can animate anything, not just CSS properties. diff --git a/7-animation/3-js-animation/back.svg b/7-animation/3-js-animation/back.svg index 654f50fdb..fcef09ad7 100644 --- a/7-animation/3-js-animation/back.svg +++ b/7-animation/3-js-animation/back.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/bezier-linear.svg b/7-animation/3-js-animation/bezier-linear.svg index 34949d61e..0c2e970f2 100644 --- a/7-animation/3-js-animation/bezier-linear.svg +++ b/7-animation/3-js-animation/bezier-linear.svg @@ -1 +1 @@ -12 \ No newline at end of file +12 \ No newline at end of file diff --git a/7-animation/3-js-animation/bounce-easeinout.view/index.html b/7-animation/3-js-animation/bounce-easeinout.view/index.html index 837c50db1..aed3d9d08 100644 --- a/7-animation/3-js-animation/bounce-easeinout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeinout.view/index.html @@ -26,7 +26,7 @@ function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce-easeout.view/index.html b/7-animation/3-js-animation/bounce-easeout.view/index.html index e52eae8de..69dbb7ce0 100644 --- a/7-animation/3-js-animation/bounce-easeout.view/index.html +++ b/7-animation/3-js-animation/bounce-easeout.view/index.html @@ -22,7 +22,7 @@ } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/bounce-inout.svg b/7-animation/3-js-animation/bounce-inout.svg index 3a28862e0..363633abd 100644 --- a/7-animation/3-js-animation/bounce-inout.svg +++ b/7-animation/3-js-animation/bounce-inout.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/bounce.view/index.html b/7-animation/3-js-animation/bounce.view/index.html index 1be2580d9..3575ed820 100644 --- a/7-animation/3-js-animation/bounce.view/index.html +++ b/7-animation/3-js-animation/bounce.view/index.html @@ -19,7 +19,7 @@ animate({ duration: 3000, timing: function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/7-animation/3-js-animation/circ-ease.svg b/7-animation/3-js-animation/circ-ease.svg index 66db4b866..a7db9abcf 100644 --- a/7-animation/3-js-animation/circ-ease.svg +++ b/7-animation/3-js-animation/circ-ease.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/circ.svg b/7-animation/3-js-animation/circ.svg index a7de1a726..3595dd624 100644 --- a/7-animation/3-js-animation/circ.svg +++ b/7-animation/3-js-animation/circ.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/elastic.svg b/7-animation/3-js-animation/elastic.svg index 441e65f78..17f04ccde 100644 --- a/7-animation/3-js-animation/elastic.svg +++ b/7-animation/3-js-animation/elastic.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/linear.svg b/7-animation/3-js-animation/linear.svg index bfab34faf..daa753f0c 100644 --- a/7-animation/3-js-animation/linear.svg +++ b/7-animation/3-js-animation/linear.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/quad.svg b/7-animation/3-js-animation/quad.svg index 4a2798c96..25a4d0005 100644 --- a/7-animation/3-js-animation/quad.svg +++ b/7-animation/3-js-animation/quad.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/quint.svg b/7-animation/3-js-animation/quint.svg index 59d80d8c4..c879ef931 100644 --- a/7-animation/3-js-animation/quint.svg +++ b/7-animation/3-js-animation/quint.svg @@ -1 +1 @@ -011 \ No newline at end of file +011 \ No newline at end of file diff --git a/7-animation/3-js-animation/text.view/index.html b/7-animation/3-js-animation/text.view/index.html index e404fe5c4..4947e4cd4 100644 --- a/7-animation/3-js-animation/text.view/index.html +++ b/7-animation/3-js-animation/text.view/index.html @@ -29,14 +29,14 @@ timing: bounce, draw: function(progress) { let result = (to - from) * progress + from; - textArea.value = text.substr(0, Math.ceil(result)) + textArea.value = text.slice(0, Math.ceil(result)) } }); } function bounce(timeFraction) { - for (let a = 0, b = 1, result; 1; a += b, b /= 2) { + for (let a = 0, b = 1; 1; a += b, b /= 2) { if (timeFraction >= (7 - 4 * a) / 11) { return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } diff --git a/8-web-components/1-webcomponents-intro/article.md b/8-web-components/1-webcomponents-intro/article.md index f9427462b..c3522dea9 100644 --- a/8-web-components/1-webcomponents-intro/article.md +++ b/8-web-components/1-webcomponents-intro/article.md @@ -26,9 +26,9 @@ The International Space Station: ...And this thing flies, keeps humans alive in space! -How such complex devices are created? +How are such complex devices created? -Which principles we could borrow to make our development same-level reliable and scalable? Or, at least, close to it. +Which principles could we borrow to make our development same-level reliable and scalable? Or, at least, close to it? ## Component architecture @@ -57,14 +57,14 @@ Components may have subcomponents, e.g. messages may be parts of a higher-level How do we decide, what is a component? That comes from intuition, experience and common sense. Usually it's a separate visual entity that we can describe in terms of what it does and how it interacts with the page. In the case above, the page has blocks, each of them plays its own role, it's logical to make these components. A component has: -- its own JavaScript class. +- Its own JavaScript class. - DOM structure, managed solely by its class, outside code doesn't access it ("encapsulation" principle). - CSS styles, applied to the component. - API: events, class methods etc, to interact with other components. Once again, the whole "component" thing is nothing special. -There exist many frameworks and development methodologies to build them, each one with its own bells and whistles. Usually, special CSS classes and conventions are used to provide "component feel" -- CSS scoping and DOM encapsulation. +There exist many frameworks and development methodologies to build them, each with its own bells and whistles. Usually, special CSS classes and conventions are used to provide "component feel" -- CSS scoping and DOM encapsulation. "Web components" provide built-in browser capabilities for that, so we don't have to emulate them any more. diff --git a/8-web-components/1-webcomponents-intro/web-components-twitter.svg b/8-web-components/1-webcomponents-intro/web-components-twitter.svg index 534e629b9..8f59f789f 100644 --- a/8-web-components/1-webcomponents-intro/web-components-twitter.svg +++ b/8-web-components/1-webcomponents-intro/web-components-twitter.svg @@ -1 +1 @@ -1243567 \ No newline at end of file +1243567 \ No newline at end of file diff --git a/8-web-components/2-custom-elements/article.md b/8-web-components/2-custom-elements/article.md index 443731914..a84ed1192 100644 --- a/8-web-components/2-custom-elements/article.md +++ b/8-web-components/2-custom-elements/article.md @@ -115,7 +115,7 @@ customElements.define("time-formatted", TimeFormatted); // (2) > ``` -1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time. +1. The class has only one method `connectedCallback()` -- the browser calls it when `` element is added to page (or when HTML parser detects it), and it uses the built-in [Intl.DateTimeFormat](mdn:/JavaScript/Reference/Global_Objects/DateTimeFormat) data formatter, well-supported across the browsers, to show a nicely formatted time. 2. We need to register our new element by `customElements.define(tag, class)`. 3. And then we can use it everywhere. @@ -149,7 +149,7 @@ The `connectedCallback` triggers when the element is added to the document. Not In the current implementation of ``, after the element is rendered, further attribute changes don't have any effect. That's strange for an HTML element. Usually, when we change an attribute, like `a.href`, we expect the change to be immediately visible. So let's fix this. -We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for an attribute for performance reasons. +We can observe attributes by providing their list in `observedAttributes()` static getter. For such attributes, `attributeChangedCallback` is called when they are modified. It doesn't trigger for other, unlisted attributes (that's for performance reasons). Here's a new ``, that auto-updates when attributes change: @@ -320,7 +320,7 @@ For example, buttons are instances of `HTMLButtonElement`, let's build upon it. class HelloButton extends HTMLButtonElement { /* custom element methods */ } ``` -2. Provide an third argument to `customElements.define`, that specifies the tag: +2. Provide the third argument to `customElements.define`, that specifies the tag: ```js customElements.define('hello-button', HelloButton, *!*{extends: 'button'}*/!*); ``` @@ -365,7 +365,7 @@ Our new button extends the built-in one. So it keeps the same styles and standar ## References - HTML Living Standard: . -- Compatiblity: . +- Compatiblity: . ## Summary @@ -397,4 +397,4 @@ Custom elements can be of two types: /*