4 | js @ ФМИ
5 | My Awesome Presentation
6 |
7 |
33 |
34 |
35 |
45 |
47 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/lectures/00-intro.markdown:
--------------------------------------------------------------------------------
1 | # JavaScript @ ФМИ
2 |
3 | ### 2015/2016
4 |
5 | ---
6 |
7 | # JavaScript като цяло
8 |
9 | * Client side (демек в браузъра)
10 | * Server side (демек не в браузъра)
11 | * На някои странни места (tessel.io)
12 |
13 | ---
14 | # Ще си говорим за
15 |
16 | * Програмиране с JavaScript генерално
17 | * Какво е асинхронен код и защо го ползваме
18 | * Какво (не) харесваме в асинхронния код
19 | * Как да се справим с проблемите
20 | * Каква е разликата между server и client side JavaScript
21 | * ФУНКЦИОНАЛНО ПРОГРАМИРАНЕ!!!
22 |
23 | ---
24 |
25 | # Бегъл план
26 |
27 | * Основните неща в езика
28 | * Променливи
29 | * Функции
30 | * Callbacks
31 | * Събития
32 | * По-интересни js специфични теми
33 | * ООП (без класове)
34 | * Прототипи
35 | * Наследяване
36 |
37 | ---
38 |
39 | class: center
40 | # За да е по-лесно за всички
41 |
42 | ## Node first
43 |
44 | ## Browser later
45 |
46 | ---
47 |
48 | # Защото
49 |
50 | * Консистентност
51 | * Тестването с node е значително по-просто от тестването в браузър.
52 | * Не искаме да рискуваме изказвания като „ама то може и в браузъра“
53 |
54 | ---
55 |
56 | # Лекции
57 |
58 | * Веднъж в седмицата
59 | * Три часа
60 | * Присъствието очевидно не е задължително
61 | * Но със сигурност помага много
62 |
63 | ???
64 |
65 | * Кои дни
66 | * От колко часа
67 | * Кои зали
68 |
69 | ---
70 |
71 | # ~Упражнения
72 |
73 | ** < see previous slide > **
74 |
75 | ???
76 |
77 | * Кои дни
78 | * От колко часа
79 | * Кои зали
80 |
81 | ---
82 |
83 | # Домашни
84 |
85 | * Планираме да са ~ веднъж на две седмици (но сме реалисти)
86 | * Обикновено срокът ще е една седмица
87 | * Ще се тестват автоматично
88 | * „Ама то sum е почти същото като summ“
89 | * „Е добре де, връщам String, а не Number, ама е правилно“
90 | * …
91 | * Очевидно ще формират част от оценката
92 |
93 | ???
94 |
95 | * Колко често(ни се иска)
96 | * Как ще се проверяват
97 | * Каква тежест
98 |
99 | ---
100 |
101 | # Проекти
102 |
103 | * Не може да завършвате курса без проект
104 | * Няма проблем да е web, с back-end на друг език(ruby/go/perl/…)
105 | * Много харесваме идеята за нещо, което е тотално несвързано с web
106 | * Никога не е твърде рано да започнете
107 | * Трябва да одобрим идеята
108 |
109 | По-лесно е да научите технология, ако я ползвате за реализирането на нещо, което ви е интересно
110 |
111 | ???
112 |
113 | * Добре е да се почнат възможно най-рано
114 | * Добрата идея помага да научиш нужната технология
115 | * Каква тежест ще имат?
116 |
117 | ---
118 |
119 | # Тестове
120 |
121 | * Два на брой
122 | * В средата на семестъра
123 | * В края на семестъра/през сесията
124 | * В moodle
125 | * Може да изпуснете един
126 | * Може да поправите някой през сесията
127 | * НЕ РАЗЧИТАЙТЕ ТВЪРДЕ МНОГО НА ПОСЛЕДНИТЕ ДВЕ
128 |
129 | ---
130 |
131 | # Дните
132 |
133 | Ще се постараем да не сме толкова досадни. Да заспивате след курса и да се събуждате, за да дойдете на курса определено не е най-прекрасното преживяване. За нас също.
134 |
135 | ---
136 |
137 | # And now for something completely different …
138 |
139 | ---
140 | class: center
141 | # UNIX
142 | 
143 |
144 | ???
145 |
146 | Горещо препоръчваме ползването на UNIX-like среди.
147 |
148 | ---
149 |
150 | # Но все пак
151 |
152 | В [download секцията](http://nodejs.org/download) на сайта на nodejs има msi инсталатори.
153 |
154 | ---
155 |
156 | # nvm/n
157 |
158 | По ред причини глобалното инсталиране на node за dev цели не е твърде удобен подход.
159 |
160 | Двата варианта, които предлагаме са [nvm](github.com/creationix/nvm) и [n](https://github.com/visionmedia/n).
161 |
162 | ---
163 |
164 | # REPL demo
165 |
166 | ---
167 |
168 | # Скриптове
169 |
170 | Пишем програмата си във файл. След това подаваме името на файла като аргумент на `node` командата и по този начин го изпълняваме.
171 |
172 | Изненадани сте, нали?
173 |
174 | ```sh
175 | $ cat hi.js
176 | function sayHi(name) {
177 | console.log('Hello, ' + name);
178 | }
179 | sayHi('Pencho');
180 |
181 | $ node hi.js
182 | Hello, Pencho
183 | ```
184 |
--------------------------------------------------------------------------------
/lectures/01-variables-functions.markdown:
--------------------------------------------------------------------------------
1 | # Variables, functions, linting
2 |
3 | ---
4 | ## Книжки
5 |
6 | * JavaScript - The Good Parts /David Crockford/
7 |
8 | ---
9 |
10 | # Променливи
11 |
12 | ```javascript
13 | > a = 5 // BAD
14 | 5
15 | > b = 7; // BAD
16 | 7
17 | > var c = 43 // meeeeeeeh... aaaalmost
18 | undefined
19 | > var d = 42; // GOOD
20 | undefined
21 | ```
22 |
23 | ---
24 |
25 | # Оценяване
26 |
27 | ```javascript
28 | > var a = 20
29 | undefined
30 | > a
31 | 20
32 | > var b = '20'
33 | undefined
34 | > a == b
35 | true
36 | > a === b
37 | false
38 | ```
39 |
40 | * Познатото ни двойно равно (==) ще сравно стойността обръщайки типовете до подходящ (според вируталната машина) тип
41 | * Ето защо проверката за еквивалентност по тип и стойност в JS се прави с === (тройно равно)
42 | * Макар и незначителна тази разлика може да доведе до неочаквани и най-често неприятни резултати
43 |
44 | ---
45 |
46 | # Типове „неща“
47 | ## Числа
48 |
49 | ```javascript
50 | > var lowBoundary = 9000;
51 | undefined
52 | > typeof lowBoundary
53 | 'number'
54 | > var pi = 3.14;
55 | undefined
56 | > typeof pi
57 | 'number'
58 | ```
59 | * Всичко има тип
60 | * `undefined` е ключова дума в езика
61 | * За да проверим дали нещо е дефинирано или не... :
62 |
63 | ```javascript
64 | > typeof notDefinedVar === 'undefined'
65 | true
66 | ```
67 |
68 | може и така:
69 |
70 | ```javascript
71 | > notDefinedVar === undefined
72 | true
73 | ```
74 |
75 | ---
76 |
77 | # Типове „неща“
78 | ## Низове
79 |
80 | ```javascript
81 | > var name = 'Pen\ncho';
82 | ```
83 |
84 | Съвсем същото като
85 |
86 | ```javascript
87 | > var name = "Pen\ncho";
88 | ```
89 |
90 | ---
91 |
92 | # Типове „неща“
93 | ## Списъци
94 |
95 | ```javascript
96 | > var team = ['Joro', 'Minko', 'Evgeni'];
97 | undefined
98 | > team.length
99 | 3
100 | > team[0]
101 | 'Joro'
102 | > team[1]
103 | 'Minko'
104 | > team[-1]
105 | undefined
106 | ```
107 | * Списъците както всичко друго - са обекти.
108 | * Можем да ги създадем с new или директно с `[]` (квадратни скоби)
109 | * Добавянето на елементи НЕ предполага явно 'разщиряване на размера'
110 | * Списъците са динамични и се оразмеряват според броя елементи
111 |
112 | ---
113 | # Типове „неща“
114 | ## Обекти
115 |
116 | ```javascript
117 | > var panda = {name: 'Стамат', age: 12, cuteness: 9000.001 };
118 | undefined
119 | > panda
120 | { name: 'Стамат',
121 | age: 12,
122 | cuteness: 9000.001 }
123 | > panda.name
124 | 'Стамат'
125 | > panda.weight = 30
126 | 30
127 | > panda.class
128 | undefined
129 | > panda['cuteness']
130 | 9000.001
131 | ```
132 |
133 | * Обект/асоциативен списък са едно и също в javascript се оказва
134 | че всички атрибути на даден обект са обект на autovivification ...
135 | * ...в момента на първото присвояване на стойност!!!
136 | * ...но ако не е било присвоено нищо на даден атрибут, първото му
137 | прочитане връща undefined
138 | * т.е. -> при добавяне на атрибут се самосъздава елемента без да е нужно
139 | неговото експлицитно (по някакъв начин) предефиниране.
140 |
141 | ---
142 | # Функции
143 |
144 | ```javascript
145 | function sayHi(name) {
146 | console.log('Hello, ' + name);
147 | }
148 | ```
149 |
150 | ```javascript
151 | function sumTwoThings(a, b) {
152 | return a + b;
153 | };
154 | ```
155 |
156 | ```javascript
157 | function sumAllTheThings () {
158 | var result = arguments[0];
159 | for(var i = 1; i < arguments.length; ++i) {
160 | result += arguments[i];
161 | }
162 |
163 | return result;
164 | }
165 | ```
166 |
167 | * Дефиницията на функциите е като в C и Java
168 | * Но в JavaScript дефиниция на функция води до
169 | създаване на екземпляр на обект от типа `Function`
170 | * т.е. всичи функции са обекти, но за това повече после...
171 |
172 | ```javascript
173 | var sumTwoThings = function (a, b) {
174 | return a + b;
175 | };
176 | ```
177 |
178 | # Асинхронни функции
179 | ```javascript
180 | setTimeout(function () {
181 | console.log('Hello There');
182 | }, 1000);
183 | ```
184 |
185 | * Ще се изпълни след хиляда мили секунди
186 |
187 |
188 | ```javascript
189 | for (var i = 0; i < 5; i++) {
190 | setTimeout(function () {
191 | console.log(i);
192 | }, 1000);
193 | }
194 | > 5
195 | 5
196 | 5
197 | 5
198 | 5
199 | ```
200 |
201 | * Иска да изпечата **i**, но докато е минала една секунда цикълът отдавна е стигнал вече до 5
202 | * Асинхронна операция (не е в нормалния поток на изпълнение)
203 | * Изпълнява се от Event Loop модела на Javascript
204 |
205 | ---
206 | ## `for` е кофти
207 |
208 | * Не, няма смисъл да спорим
209 | * Повече по този въпрос после
210 | * Има няколко други механизъма за итериране, които са в духа на JavaScript
211 |
212 | ---
213 |
214 | # По-подробно за списъци
215 |
216 | ## Методи
217 |
218 | ```javascript
219 | > Object.getOwnPropertyNames(Object.getPrototypeOf(a))
220 | [ 'length',
221 | 'constructor',
222 | 'toString',
223 | 'toLocaleString',
224 | 'join',
225 | 'pop',
226 | 'push',
227 | 'concat',
228 | 'reverse',
229 | 'shift',
230 | 'unshift',
231 | 'slice',
232 | 'splice',
233 | 'sort',
234 | 'filter',
235 | 'forEach',
236 | 'some',
237 | 'every',
238 | 'map',
239 | 'indexOf',
240 | 'lastIndexOf',
241 | 'reduce',
242 | 'reduceRight' ]
243 | ```
244 |
245 | ---
246 | ### `for` е гаден
247 |
248 | ```javascript
249 | var albums = ['Lateralus', '10,000 days', 'Ænima'];
250 | albums.forEach(function (album) {
251 | console.log(album + ' is an album by Tool');
252 | });
253 | ```
254 |
255 | ---
256 | # filter
257 |
258 | ```javascript
259 | > albums.filter(function (album) {
260 | return album.charAt(0) === 'Æ';
261 | });
262 | ['Ænima']
263 | ```
264 |
265 | ---
266 | # map
267 | ```javascript
268 | > albums.map(function (album) {
269 | return album.toLowerCase();
270 | });
271 | ['lateralus', '10,000 days', 'ænima']
272 | ```
273 |
274 | ---
275 | # push/pop
276 |
277 | ```javascript
278 | > albums.push('Undertow');
279 | 4
280 | > albums
281 | ['Lateralus', '10,000 days', 'Ænima', 'Undertow']
282 | > albums.pop();
283 | 'Undertow'
284 | albums
285 | ['Lateralus', '10,000 days', 'Ænima']
286 | ```
287 |
288 | ---
289 | # `Array`
290 |
291 | ```javascript
292 | > var bands = new Array(10);
293 | undefined
294 | > bands
295 | [ , , , , , , , , , ]
296 | ```
297 |
298 | ---
299 | # `Array`
300 |
301 | ```javascript
302 | > var things = new Array(10, 'asd');
303 | undefined
304 | > things
305 | [ 10, 'asd' ]
306 | ```
307 |
308 | ---
309 | # shift/unshift
310 |
311 | Абсолютно същото, но в началото на списъка, а не в края
312 |
313 | ---
314 | # Прости структури от данни
315 |
316 | * Сам по себе си е списък
317 | * `pop`/`push` ⇨ стек
318 | * `unshift`/`pop` или `push`/`shift` ⇨ опашка
319 |
320 | ---
321 |
322 | # Нехомогенни
323 |
324 | ```javascript
325 | > things = [42, 'brie', {species: 'unicorn', пробабилитъ: '0.000000001'}]
326 | ```
327 |
328 |
329 | ---
330 | # Сложност
331 |
332 | Сложността на операциите върху `Array` обекти най-вероятно не е каквато очаквате. За това има [много добро обяснение](http://stackoverflow.com/questions/11514308/big-o-of-javascript-arrays#answer-11535121).
333 |
334 | **TL;DR** Списъците са обекти, обектите са хешове.
335 | ---
336 | # Javascript runtime (v8)
337 |
338 | ---
339 | # Какво представлява?
340 | 
341 | ---
342 | # Stacking
343 |
344 | javascript
345 | function f(b){
346 | var a = 12;
347 | return a+b+35;
348 | }
349 |
350 |
351 | function g(x){
352 | var m = 4;
353 | return f(m*x); //f се слага в stack
354 | }
355 |
356 | g(21); //g се слага в stack
357 |
358 |
359 | * Когато имаме function call push-ваме в stack-а.
360 | * Когато функцияна приключи pop-ваме от stack-а.
361 |
362 | ---
363 | #Javascript is single threaded
364 | ---
365 | # Какво се случва, когато възникне event докато Stack-a е пълен?
366 | ---
367 | 
368 | * Koгато stack-а Е ПРАЗЕН Event loop-а чете от TaskQueue-то
369 | и ако има task го push-ва в stack-а.
370 | ---
371 |
372 | ## Pushing tasks
373 |
374 | * В browser-а съобщения се добавят, когато възникне някакъв event и
375 | имаме event handler за него. (webAPIs -> addEventListener)
376 |
377 | * Също setTimeout(), process.nextTick() и др.
378 |
379 | javascript
380 | setTimeout(function(){ alert(1); }, 1000) //Изпълни се след минимум 1 сек
381 |
382 |
383 | setTimeout е функция на браузъра (Web APIs). Когато я извикаме browser-а пускай timer и след изтичането му
384 | callback-а се push-ва в TaskQueue. Подобно е и когано правим ajax requests.
385 |
386 |
387 | javascript
388 | setTimeout(function(){ alert(1); }, 0)
389 | // Може да си мислим като - TaskQueue.push(function(){ alert(1); })
390 |
391 |
392 | * Важно е да пешем non-blocking code!
393 |
394 | * Трябва да се внимава с for, while, forEach защото са синхронни.
395 |
396 | * Render Queue също зависи от javascript.
397 | Не можем да рендерираме, когато имаме функции в stack-а.Render Queue има
398 | по-високо priority от callback-овете, които слагаме в TaskQueue.
399 | ---
400 | ### Blocking the event loop
401 |
402 | javascript
403 | var fs = require('fs'); //require the module for file system ops
404 |
405 | var file = fs.readFileSync('./myBigFile'); //synchronously read file
406 |
407 |
408 | Това блокира thread-а докато целия файл не бъде прочетен.
409 |
410 | Решението е fs.readFile(filepath, callback), което чете файла
411 | на малки парчета (chunks) и след това извиква
412 | callback-а с прочетения файл като аргумент.
413 |
414 |
415 | ---
416 | # За разнообразие и по нужда
417 | ### linter-и(мъхясване?)
418 |
419 | * jslint/jshint
420 | * Интегрират се с всяка разумна среда
421 |
422 | ***
423 |
424 | * vim :
425 | [чрез syntastic](https://github.com/scrooloose/syntastic)
426 | * emacs :
427 | [чрез flycheck](http://www.emacswiki.org/emacs/Flycheck)
428 | * sublime text :
429 | [sublime-jslint](http://opensourcehacker.com/2013/04/12/jslint-integration-for-sublime-text-2/)
430 | * ако не сте си писали редактора сами най-вероятно има разумен начин да подкарате linter с него.
431 |
432 |
--------------------------------------------------------------------------------
/lectures/02-modules.markdown:
--------------------------------------------------------------------------------
1 | # js @ фми
2 |
3 | ---
4 |
5 | # Modules (в кратце)
6 |
7 | Разделяме кода в отделни файлове с цел по-добра изолация на отделните парчета от програмата ни.
8 |
9 | Получаваме по-лесен за поддръжка код, с по-лесно подменими компоненти(стига да се постараем да спазваме консистентни интерфейси между отделните парчета).
10 |
11 | ---
12 |
13 | # require
14 |
15 | Вградена функция в nodejs, с която „поискваме“ модули.
16 |
17 | ```javascript
18 | var fs = require('fs');
19 | ```
20 |
21 | Получаваме обект, отговарящ на модула, който сме `require`-нали.
22 |
23 | ---
24 |
25 | # module.exports
26 |
27 | Във всеки файл, който изпълняваме с node имаме достъп до `module` обекта. Това е обект, чрез който можем да достъпваме функционалност свързана с текущия модул.
28 |
29 | След време ще си поговорим по-подробно за това, за сега **don't overthink it!**
30 |
31 | ---
32 | # Домашни/задачки
33 |
34 | Обещахме!
35 |
36 | Като добри хора ще (се постараем много сериозно да) спазим обещанието си.
37 |
38 | ---
39 | # Домашни/задачки
40 |
41 | В тоя ред на мисли всяко домашно, което предавате ще трябва да спазва някаква форма.
42 |
43 | ```javascript
44 | module.exports = {
45 | fib: function (n) {
46 | …
47 | },
48 | …
49 | }
50 | ```
51 |
52 | Така си гарантираме, че имаме хубав интерфейс, през който да тестваме решенията ви.
53 |
54 | ---
55 | # Домашни/задачки
56 |
57 | [В github (ще) има задачка]().
58 |
59 | Живейте с мисълта, че имате срок до следващия петък.
60 |
61 | Скоро ще се появи обяснение как да предавате решенията си.
62 |
63 | ---
64 | # но първо
65 |
66 | ---
67 | # List quirks
68 |
69 | ## `for … in …`
70 |
71 | Най-вероятно не прави това, което очаквате. Ако знаехте какво точно прави, по-скоро бихте го избягвали.
72 |
73 | ---
74 | # List quirks
75 |
76 | ## `for … in …`
77 |
78 | ### python
79 | ```python
80 | a = ['brie', 42, 'bacon', 'cheddar', []]
81 | for thing in a:
82 | print thing
83 |
84 | brie
85 | 42
86 | bacon
87 | cheddar
88 | []
89 | ```
90 |
91 | ---
92 | # List quirks
93 |
94 | ## `for … in …`
95 |
96 | ### javascript
97 | ```javascript
98 | var a = ['brie', 42, 'bacon', 'cheddar', []]
99 | for (thing in a) {
100 | console.log(thing);
101 | }
102 |
103 | 0
104 | 1
105 | 2
106 | 3
107 | 4
108 | ```
109 |
110 | ---
111 | # List quirks
112 |
113 | ## `for … in …`
114 |
115 | ### javascript
116 | ```javascript
117 | var a = ['brie', 42, 'bacon', 'cheddar', []]
118 | for (thing in a) {
119 | console.log(a[thing]);
120 | }
121 | brie
122 | 42
123 | bacon
124 | cheddar
125 | []
126 | ```
127 |
128 | ---
129 | # List quirks
130 |
131 | ## `for … in …`
132 |
133 | ### javascript
134 | ```javascript
135 | var a = ['brie', 42, 'bacon', 'cheddar', []]
136 | a.foo = 'bar'
137 | for (thing in a) {
138 | console.log(a[thing]);
139 | }
140 | brie
141 | 42
142 | bacon
143 | cheddar
144 | []
145 | bar
146 | ```
147 |
148 | ---
149 | # List quirks
150 | ## `for … in …`
151 |
152 | [В MDN пише ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in):
153 | > …
154 | >
155 | > A for...in loop iterates over the properties of an object in an arbitrary order
156 | >
157 | > …
158 |
159 | ---
160 | # List quirks
161 | ## `for … in …`
162 |
163 | За да сте информирани казваме изрично:
164 |
165 | Списъци се обхождат **винаги и само** с `forEach`.
166 |
167 | ---
168 | # List quirks
169 | ## `in`
170 |
171 | Същия аналог
172 |
173 | ### python
174 | ```python
175 | > a = [4, 5, 42, 'chunky']
176 | > 'chunky' in a
177 | True
178 | ```
179 |
180 | ### javascript
181 | ```javascript
182 | > var a = [4, 5, 42, 'chunky']
183 | > 'chunky' in a
184 | false
185 | ```
186 |
187 | ---
188 | # List quirks
189 | ## `in`
190 |
191 | Проверка дали нещо принадлежи на списък се прави с `indexOf`.
192 |
193 | ```javascript
194 | var a = [4, 5, 42, 'chunky']
195 | a.indexOf('chunky') > -1
196 | true
197 | ```
198 |
199 | ---
200 | # List quirks
201 |
202 | `Array` в javascript не е свързан списък, или последователни „клетки“ от паметта, или друга структура, която се използва често за представяне на масиви/списъци.
203 |
204 | `Array` е „javascript обект“, което означава, че се държи по-скоро като dict/Hash/HashMap/etc. от колкото като линейна или свързана структура.
205 |
206 | ---
207 | # `global`
208 |
209 | Обекта, в който се пазят всички глобални имена за текущия процес.
210 |
211 | ---
212 | # `typeof`
213 |
214 | Задължителното „ако идвате от php“
215 |
--------------------------------------------------------------------------------
/lectures/03-01-moar-functional.markdown:
--------------------------------------------------------------------------------
1 | # bind
2 |
3 | `bind` задава нов `this` на дадена функция и „забранява“ замяната му
4 |
5 | ```javascript
6 | var getThisValue = function () {
7 | return this.value;
8 | };
9 | var thing = { value: 42 },
10 | otherThing = { value: 73 };
11 | ```
12 |
13 | ```javascript
14 | > getThisValue.bind(thing)();
15 | 42
16 | > getThisValue.bind(thing).call(otherThing)
17 | 42
18 | ```
19 |
20 | ---
21 |
22 | # Функционални забавления
23 |
24 | В javascript липсват някои дребни удобства, които идват „безплатно“ в повечето други популярни езици. Пример за това е `range`. В много езици има възможност код като `range(10, 45)` да създаде обект, който да представлява нещо итеруемо, обхождащо стойностите от 10 до 45. Същото можем да постигнем с прости функционални похвати и в javascript:
25 |
26 | ```javascript
27 | function range(start, end, stopIteration) {
28 | return function () {
29 | if (start > end) {
30 | return stopIteration;
31 | } else {
32 | return start++;
33 | }
34 | }
35 | }
36 | ```
37 |
38 | ---
39 |
40 | След това можем да използваме `range` функцията по следния начин:
41 |
42 | ```javascript
43 | var stopIteration = {},
44 | next = range(42, 73, stopIteration),
45 | item;
46 |
47 | while((item = next()) !== stopIteration) {
48 | console.log(item);
49 | }
50 | ```
51 |
52 | ---
53 |
54 | # `===` и `!==`
55 |
56 | `===` и `!==` са **строги** проверки. Това ще рече, че за стойности от тип `string` и `number` не се прави type coercion.
57 |
58 | * `5 === '5'` се оценява до `false`
59 | * `6 !== '6'` се оценява до `true`.
60 | * в javascript няма предефиниране на оператори, така че `{} == {}` винаги ще бъде `false`, тъй като това са два различни обекта.
61 | * но поради имплицитния type coercion `{} == '[object Object]'` се оценява до `true`
62 |
63 | ⇨ правим проверката с `!==`, а не с `!=`
64 |
65 | ---
66 |
67 | # Алтернативи
68 |
69 | * Вместо проверка за `stopIteration` да „хвърлим“ грешка
70 | * Да получим тялото на цикъла като функция
71 |
72 | ```javascript
73 | function range(start, end, stopIteration) {
74 | return function () {
75 | if (start > end) {
76 | throw new Error('RANGE OVER');
77 | } else {
78 | return start++;
79 | }
80 | }
81 | }
82 | ```
83 |
84 | Обаче:
85 | * Все още не знаем какво точно е `Error`
86 | * Поведението му може да ни изненада неприятно
87 | * В javascript „хвърлянето“ на грешки не е на почит, защото не се държи възпитано при асинхронен код
88 |
89 | ---
90 |
91 | # Задачка
92 | ### `function iterator(array, stopIteration)`
93 |
94 | Прави същото като `range`, но обхождайки подадения **Array-like** обект.
95 |
96 | `function iterator(array, stopIteration)`, която прави същото като `range`, но обхождайки подадения **Array-like** обект.
97 |
98 | ---
99 |
100 | # Задачка
101 | ### `rangeMap`
102 |
103 |
104 | ```javascript
105 | > var doubled = rangeMap(1, 3, function (item) { return item + item });
106 | > console.log(doubled)
107 | [2, 4, 6]
108 | ```
109 | Алтернативна имплементация на `range`, която не очаква `stopIteration` обект, а се държи като `map`, очаквайки функция, която да изпълни върху всички елементи.
110 |
--------------------------------------------------------------------------------
/lectures/04-01-moar-modules.markdown:
--------------------------------------------------------------------------------
1 | # Modules (в малко повече детайл)
2 |
3 | Разделяме кода в отделни файлове с цел по-добра изолация на отделните парчета от програмата ни.
4 |
5 | Спазваме DRY (Don't repeat yourself) принципа - модулите са удобен начин за преизползване на код.
6 |
7 | Възползваме се от ["стандартната библиотека"](https://nodejs.org/api/) на Node.js и [огромната база](http://npmjs.com/) от допълнителни модули.
8 |
9 | ---
10 |
11 | # Собствен модул - библиотека
12 |
13 | ```javascript
14 | // file math.js
15 | function add(x, y) {
16 | return x + y;
17 | }
18 |
19 | module.exports = {
20 | add: add
21 | };
22 |
23 | ```
24 |
25 | ```javascript
26 | // file index.js
27 | var math = require('./math');
28 | console.log(math.add(4, 6));
29 | ```
30 |
31 | ---
32 |
33 | # Собствен модул - клас
34 |
35 | ```javascript
36 | // file my_class.js
37 | var MyClass = function() {
38 | this.member = 42;
39 | }
40 |
41 | module.exports = MyClass;
42 |
43 | ```
44 |
45 | ```javascript
46 | // file index.js
47 | var myClass = require('./my_class');
48 | var instance = new myClass();
49 |
50 | console.log(instance.member);
51 | ```
52 |
53 | ---
54 |
55 | # Модули от "стандартната библиотека"
56 |
57 | ```javascript
58 | var fs = require('fs');
59 |
60 | // `__dirname` връща пътя към текущия модул
61 | var fileName = __dirname + '/out.txt';
62 | fs.writeFileSync(fileName, "This will go in the file");
63 | ```
64 |
65 | Документация - в страничката на [съответния модул](https://nodejs.org/api/fs.html).
66 |
67 | ---
68 |
69 | # Външни модули
70 |
71 | Инсталираме с `npm install <име>` в основната папка на нашето приложение:
72 |
73 | ```bash
74 | # в терминала
75 | npm install uuid
76 | ```
77 |
78 | ```javascript
79 | // в някой сорс файл
80 |
81 | var uuid = require('uuid');
82 | console.log(uuid.v1());
83 | ```
84 |
85 | Документация - в страничка на модула в NPM repository-то, в случая [uuid](https://www.npmjs.com/package/uuid).
86 |
87 | ---
88 |
89 | # Пътища за зареждане
90 |
91 | Забележете require-ването само по име на модула - Node търси за файл/папка с това име в подпапка **node_modules** на нашата основна папка. Ако не намери, продължава да търси **node_modules** нагоре по дървото с папки.
92 |
93 | ```bash
94 | /home/modder/some_project/node_modules/uuid
95 | /home/modder/node_modules/uuid
96 | /home/node_modules/uuid
97 | /node_modules/uuid
98 | ```
99 |
100 | Повече информация за начина, по който Node търси модули - [тук](https://nodejs.org/api/modules.html#modules_loading_from_node_modules_folders).
101 |
--------------------------------------------------------------------------------
/lectures/05-01-es2015.markdown:
--------------------------------------------------------------------------------
1 | # ES2015
2 |
3 | ### 2015/2016
4 |
5 | ---
6 | # ES2015
7 |
8 | Последната финализирана версия на ECMAScript. Преди няколко месеца беше известен като ES6, но поради желанието на TC39 да прави годишни итерации с цел разширяване на езика, бе преименуван на ES2015.
9 |
10 | ---
11 |
12 | # ES2015 включва неща като...
13 |
14 | - Block lexical scope
15 | - Classes
16 | - Promises
17 | - Generators
18 | - Iterators
19 | - Enhanced object literals
20 | - Destructuring
21 | - Arrow functions
22 |
23 | ---
24 |
25 | # Преди да се забавляваме с ES2015...
26 | ## нека обърнем внимание на ES5...
27 |
28 | ---
29 |
30 | # Property descriptors
31 |
32 | ---
33 |
34 | ```javascript
35 | Object.defineProperty(obj, 'propName', descriptor);
36 | ```
37 |
38 | ---
39 |
40 | ## Стойности по подразбиране
41 |
42 | ```javascript
43 | var descriptor = {
44 | configurable: false,
45 | enumerable: false,
46 | value: undefined,
47 | writable: false,
48 | get: undefined,
49 | set: undefined
50 | };
51 | ```
52 |
53 | ---
54 |
55 | ## Семантика
56 |
57 | - `configurable` - при стойност `true` даденото поле на обекта може да бъде изтрито и/или преконфигурирано (т.е. можем да променим дескриптора)
58 | - `enumerable` - при стойност `true` даденото поле може да бъде енумерирано (например, обходено с `for...in` или получено през `Object.keys`)
59 | - `value` - стойност на полето
60 | - `writable` - при `true` стойността на полете може да бъде променяна с оператор `=`
61 | - `get` - връща стойността на полето (не може да живее заедно с `value`)
62 | - `set` - задава стойност на полето (не може да живее заедно с `value`)
63 |
64 | ---
65 |
66 | # Immutable обекти
67 |
68 | Immutable е обект, който не може да бъде променян след създаването си.
69 |
70 | ---
71 |
72 | ## Защо immutable data rulez?
73 |
74 | - Конкурентно програмиране (няма проблеми с конкурентно модифициране на данни)
75 | - Няма промяна в състоянието (не могат да възникнат бъгове от неконсистентно състояние)
76 |
77 | ---
78 |
79 | # `Object.freeze` & `Object.seal`
80 |
81 | ---
82 |
83 | ## `Object.seal`
84 |
85 | Забранява добавянето на нови полета към даден обект. Прави всички налични полета на обекта `non-configurable`
86 |
87 | ```javascript
88 | var foo = { bar: 42 };
89 | Object.seal(foo);
90 |
91 | foo.bar = 43;
92 | console.log(foo.bar); // 43
93 |
94 | foo.baz = 1.618;
95 | console.log(foo.baz); // undefined
96 | ```
97 | ---
98 |
99 | ## `Object.freeze`
100 |
101 | Забранява добавянето и изтриването на съществуващи полета на даден обект. Прави полетата на обекта `non-writable` & `non-configurable`.
102 |
103 | ```javascript
104 | var foo = { bar: 42 };
105 | Object.freeze(foo);
106 |
107 | foo.bar = 43;
108 | console.log(foo.bar); // 42
109 |
110 | foo.baz = 1.618;
111 | console.log(foo.baz); // undefined
112 | ```
113 | ---
114 |
115 | ## С други думи:
116 |
117 | ```javascript
118 | var foo = { bar : 42 };
119 | Object.getOwnPropertyDescriptor(foo, 'bar');
120 | // Object { value: 42, writable: true, enumerable: true, configurable: true }
121 |
122 | Object.freeze(foo);
123 | // Object { value: 42, writable: false, enumerable: true, configurable: false }
124 | ```
125 |
126 | ---
127 |
128 | ## Какво ще се случи:
129 |
130 | ```javascript
131 | var foo = { bar: { baz: 42 } };
132 |
133 | Object.freeze(foo);
134 | foo.bar = { foobar: 12 };
135 | foo.bar.baz = 1.618;
136 |
137 | console.log(foo.bar);
138 | console.log(foo.bar.baz);
139 | ```
140 | ---
141 |
142 | # Strict mode
143 |
144 | ```javascript
145 | 'use strict';
146 |
147 | // Now you're in strict mode...
148 | ```
149 |
150 | ---
151 |
152 | ## Какво означава да си в strict mode...
153 |
154 | Имаме редица ограничения, които са създадении с цел:
155 |
156 | - Избягване на често допускани грешки
157 | - Оптимизация на кода, който пишем от виртуалната машина
158 | - По-лесна миграция към нови стандарти
159 |
160 | ---
161 |
162 | Забранено е дефинирането на променливи без ключова дума, която експлицитно задава това (`var`, `let`):
163 |
164 | ```javascript
165 | 'use strict';
166 |
167 | foo = 42;
168 | ```
169 | ---
170 |
171 | ## Strict mode
172 | ### не ви позволява да нарушавате предварително зададени правила...
173 |
174 | ```javascript
175 | 'use strict';
176 | var foo = { bar: 42 };
177 | Object.freeze(foo);
178 | foo.bar = 43; // thorws
179 | ```
180 | ---
181 |
182 | ## Аргументи с еднакво име...
183 |
184 | ```javascript
185 | function foo(a, a, b) {
186 | return a + b;
187 | }
188 | // Argument name clash in strict mode
189 |
190 | ```
191 | ---
192 | ## Какъв мислите, че ще бъде резултатът от:
193 |
194 | ```javascript
195 | function f(a) {
196 | a = 1.618;
197 | return [a, arguments[0]];
198 | }
199 | console.log(f(42));
200 | ```
201 | ---
202 | ## Strict mode оправя това...
203 |
204 | ```javascript
205 | function f(a) {
206 | a = 1.618;
207 | return [a, arguments[0]];
208 | }
209 | console.log(f(42)); // [1.168, 42]
210 | ```
211 | ---
212 | ## Без use strict...
213 |
214 | ```javascript
215 | function a() {
216 | return this;
217 | }
218 | console.log(this); // ?
219 | ```
220 | ---
221 | ## С 'use strict'...
222 |
223 | ```javascript
224 | function a() {
225 | 'use strict';
226 | return this;
227 | }
228 | console.log(this); // undefined
229 | ```
230 | ---
231 |
232 | ## Без повече `fn.caller` & `fn.arguments`
233 |
234 | ```javascript
235 | function foo() {
236 | console.log(foo.arguments); // []
237 | console.log(foo.caller); // undefined
238 | }
239 |
240 | foo();
241 | ```
242 |
243 | ###...но с `use strict`...
244 |
245 | ```javascript
246 | function foo() {
247 | console.log(foo.arguments);
248 | console.log(foo.caller);
249 | }
250 |
251 | foo();
252 | // 'caller' and 'arguments' are restricted function properties
253 | ```
254 | ---
255 |
256 | # ES2015!
257 |
258 | ---
259 |
260 | # TCO (tail call optimization)
261 |
262 | ---
263 |
264 | ## Опашъчна рекурсия
265 |
266 | 
267 |
268 | ### Как да я оптимизираме?
269 |
270 | ---
271 |
272 | ## Оптимизацията е валидна за всички tail calls:
273 |
274 | ```javascript
275 | function id(a) {
276 | return a;
277 | }
278 | function sum(a, b) {
279 | return id(a + b);
280 | }
281 | function addOne(a) {
282 | return sum(a, 1);
283 | }
284 |
285 | addOne(41) // 42
286 | ```
287 |
288 | ### Call stack
289 |
290 | ```
291 | +---------+
292 | | id |
293 | +---------+
294 | | sum |
295 | +---------+
296 | | addOne |
297 | +---------+
298 | ```
299 | ---
300 |
301 | ## Защо `use strict` забранява `fn.caller`?
302 |
303 | ---
304 |
305 | # Block lexical scope
306 |
307 | ---
308 |
309 | ## Какво знаем...
310 |
311 | ```javascript
312 | if (true) {
313 | var answer = 42;
314 | if (answer === 42) {
315 | console.log("You got the answer: " + answer);
316 | }
317 | }
318 | if (true) {
319 | console.log(answer); // 42
320 | }
321 | ```
322 | ---
323 |
324 | # Block lexical scope
325 |
326 | ```javascript
327 | if (true) {
328 | let answer = 42;
329 | if (answer === 42) {
330 | console.log("You got the answer: " + answer);
331 | }
332 | }
333 | if (true) {
334 | console.log(answer); // dead kittie
335 | }
336 | ```
337 | ---
338 |
339 | # Enhanced object literals
340 |
341 | ---
342 |
343 | ## Как дефинирахме object literals в миналото...
344 |
345 | ```javascript
346 | var bar = 42;
347 | var talk = function (words) { ... };
348 | var foo = {
349 | bar: bar,
350 | talk: talk,
351 | walk: function (distance) {
352 | // do stuff
353 | }
354 | };
355 | ```
356 |
357 | ## Как го правим сега?
358 |
359 | ```javascript
360 | var bar = 42;
361 | var talk = function (words) { ... };
362 | var foo = {
363 | bar,
364 | talk,
365 | walk(distance) {
366 | // do stuff
367 | }
368 | };
369 | ```
370 | ---
371 |
372 | ## Изчисляеми полета
373 |
374 | ```javascript
375 | var obj = {
376 | [\`guessWhatI'llBe${Math.random()}\`]: 42
377 | };
378 | ```
379 |
380 | ---
381 |
382 | # Destructuring
383 |
384 | ---
385 |
386 | ## Как извличахме полетата на обект преди?
387 |
388 | ```javascript
389 | function foobar(config) {
390 | var name = config.name;
391 | var age = config.age;
392 | var gitHubHandle = config.socialMedia.gitHubHandle;
393 | // ...do stuff
394 | }
395 | ```
396 |
397 | ---
398 |
399 | ## Как го правим сега...
400 |
401 | ```javascript
402 | function foobar(config) {
403 | var { name, age, socialmedia: { githubHandle } } = config;
404 | // ...do stuff
405 | }
406 | ```
407 | ## или...
408 |
409 | ```javascript
410 | function foobar({ name, age, socialmedia: { githubHandle } }) {
411 | // ...do stuff
412 | }
413 | ```
414 | ---
415 |
416 | # Arrow functions
417 |
418 | ---
419 |
420 | ## Функции от по-висок ред (super quick recap)
421 |
422 | ```javascript
423 | var sum = [1, 2, 3].reduce(function (a, c) {
424 | return a + c;
425 | }, 0);
426 |
427 | var sorted = obj.sort(function (a, b) {
428 | var ageDif = a.age - b.age;
429 | if (ageDif !== 0) {
430 | return ageDif;
431 | } else {
432 | return a.height - b.height;
433 | }
434 | });
435 | ```
436 | ---
437 |
438 | ## High-order arrow functions
439 |
440 | ```javascript
441 | var sum = [1, 2, 3].reduce((a, c) => a + c, 0);
442 | var sorted = obj.sort((a, b) => {
443 | let ageDif = a.age - b.age;
444 | if (ageDif !== 0) {
445 | return ageDif;
446 | } else {
447 | return a.height - b.height;
448 | }
449 | });
450 | ```
451 | ---
452 |
453 | ## Запазват `this` на заобикалящия ги код!
454 |
455 | ```javascript
456 | function MyHero() {
457 | var self = this;
458 | setTimeout(function () {
459 | this.startSuperPowers();
460 | }, 1000);
461 | }
462 | new MyHero();
463 | ```
464 |
465 | ```javascript
466 | function MyHero() {
467 | var self = this;
468 | setTimeout(() => {
469 | this.startSuperPowers();
470 | }, 1000);
471 | }
472 | new MyHero();
473 | ```
474 | ---
475 | # Класове
476 | ---
477 |
478 | ## Защо са ни нужни?
479 | ### ...много хора ги мразят...
480 |
481 | - Оптимизации от виртуалната машина
482 | - По-кратък learning curve
483 | - Повечето популярни езици за програмиране ги поддържат
484 | - Добре известна семантика
485 | - Те са просто syntax sugar
486 |
487 | ---
488 | ## Синтаксис:
489 |
490 | ```javascript
491 | class Person {
492 | static totalPeople = 0;
493 | constructor(name) {
494 | Person.totalPeople += 1;
495 | this._name = name;
496 | }
497 | set name(name) {
498 | this._name = name;
499 | }
500 | get name() {
501 | return this._name;
502 | }
503 | }
504 | ```
505 | ---
506 | ## Транслира се до нещо от рода на:
507 |
508 | ```javascript
509 | var Person = (function () {
510 | _createClass(Person, null, [{
511 | key: "totalPeople",
512 | value: 0,
513 | enumerable: true
514 | }]);
515 | function Person(name) {
516 | _classCallCheck(this, Person);
517 | Person.totalPeople += 1;
518 | this._name = name;
519 | }
520 | _createClass(Person, [{
521 | key: "name",
522 | set: function set(name) {
523 | this._name = name;
524 | },
525 | get: function get() {
526 | return this._name;
527 | }
528 | }]);
529 | return Person;
530 | })();
531 | ```
532 | ---
533 | ## Следните особености:
534 |
535 | - Не съществува преди изпълнение на кода за неговата дефиниция
536 | - Има само 1 конструктор (защо?)
537 | - Всички методи са добавени към прототипа и са `nonenumerable`
538 | - `set` и `get` се използват за създаване на setters и getters
539 | - Няма модификатори за видимост (към момента)
540 | - Всички статични полета се добавят като полета на конструкторната функция
541 | ---
542 |
543 | ## Extends...
544 |
545 | ```javascript
546 | class Person { ... }
547 |
548 | class Developer extends Person {
549 | constructor(name, langs) {
550 | super(name);
551 | this.languages = langs;
552 | }
553 | talk() {
554 | return super.talk() + \` and knows ${this.languages.join(', ')}\`;
555 | }
556 | // ...
557 | }
558 | ```
559 | ---
560 | # Promises
561 | ---
562 |
563 | ## Какво е callback hell?
564 |
565 | 
566 |
567 | ---
568 |
569 | ## Задача...
570 |
571 | Искаме да заредим всички потребители, които са зададени в JSON файл.
572 |
573 | Във файла имаме списък с JSON файлове, които представляват отделните потребители.
574 |
575 | Имаме дефинирана функция `getJSON(file, cb)`. `cb` ще бъде изпълнен с резултатът от заявката, когато тя приключи.
576 |
577 | ---
578 |
579 | ## Решение с callbacks
580 |
581 | ```javascript
582 | function loadUsers() {
583 | getJSON('users.json', function (users) {
584 | console.log(users.group);
585 | users.users_list.forEach(function (u) {
586 | getJSON(u, function (user) {
587 | console.log(user);
588 | }, function (error) {
589 | console.error(error);
590 | });
591 | });
592 | }, function (error) {
593 | console.error(error);
594 | });
595 | }
596 | ```
597 | ---
598 | ## Решение с promises
599 |
600 | Вместо функцията да приема callback като аргумент може да връща обект, които да има метод, който приема функция и да връща обект от същия тип. Тази функция ще бъде извикана веднъж, когато асинхронното действие бъде приключено.
601 |
602 | ---
603 |
604 | ## Promises
605 |
606 | ```javascript
607 | function loadUsers() {
608 | getJSON('users.json')
609 | .then(function (data) {
610 | console.log(data.group);
611 | Promise.all(data.users_list.map(getJSON))
612 | .then(function (users) {
613 | console.log(users);
614 | });
615 | });
616 | }
617 | ```
618 | ---
619 | ## Нека го разгледаме стъпка по стъпка...
620 |
621 | ### Стъпка 1
622 |
623 | ```javascript
624 | function loadUsers() {
625 | getJSON('users.json')
626 | .then(function (data) {
627 | // ...
628 | // Ще бъде извикана, когато имаме
629 | // получен отговор от заявката: GET users.json
630 | // ...
631 | });
632 | }
633 | ```
634 |
635 | ---
636 |
637 | ### Стъпка 2
638 |
639 | ```javascript
640 | // ...
641 | .then(function (data) {
642 | // Ще извика функцията getJSON с всеки един от файловете
643 | // които сме получили като резултат от заявката до
644 | // users.json, намиращи се в users_list
645 | Promise.all(data.users_list.map(getJSON))
646 | });
647 | // ...
648 | ```
649 | ---
650 |
651 | ### Стъпка 3
652 |
653 | ```javascript
654 | // На Promise.all ще подадем масив от обекти от тип Promise
655 | // т.е. ще имат then метод, към който можем да
656 | // закачим callback.
657 | Promise.all(data.users_list.map(getJSON))
658 | ```
659 | ---
660 |
661 | ### Стъпка 4
662 | ```javascript
663 | // Promise.all ще върне нов обект от тип Promise
664 | // чийто callback ще бъде извикан, веднъж когато
665 | // всички Promise обекти от масива са получили стойност.
666 | Promise.all(data.users_list.map(getJSON))
667 | .then(function (users) {
668 | // масив с всички резултати, с които
669 | // callback на promises от масива върнат
670 | // от map са извикани
671 | });
672 | ```
673 | ---
674 |
675 | ## Promises не са нова идея...
676 |
677 | > The future and/or promise constructs were first implemented in programming languages such as MultiLisp and Act 1.
678 |
679 | ### Multilisp
680 |
681 | > It was designed by Robert H. Halstead in the early 1980s
682 | ---
683 |
684 | ## Chaining promises
685 |
686 | ```javascript
687 | asyncFunction()
688 | // ще бъде извикана веднъж, когато
689 | // изпълнението на asyncFunction е довело до резултат
690 | .then(result => {
691 | return anotherAsyncFunction(result);
692 | })
693 | // Ще бъде извикана веднъж, когато promise
694 | // върнат от anotherAsyncFunction има стойност
695 | // (т.е. когато изпълнението на асинхронната функция
696 | // anotherAsyncFunction е довело до резултат)
697 | .then(anotherResult => {
698 | return thirdAsyncFunction(anotherResult);
699 | })
700 | // ...
701 | .then(result => {
702 | // final result
703 | });
704 | ```
705 | ---
706 |
707 | # Генератори
708 |
709 | ---
710 |
711 | ## Генераторът е функция, която връща множество резултати
712 |
713 | ```javascript
714 | function *countTo(n) {
715 | for (let i = 0; i < n; i += 1) {
716 | yield i;
717 | }
718 | }
719 |
720 | let generator = countTo(3);
721 | generator.next(); // { done: false, value: 0 }
722 | generator.next(); // { done: false, value: 1 }
723 | generator.next(); // { done: false, value: 2 }
724 | generator.next(); // { done: true, value: undefined }
725 | ```
726 |
727 | ---
728 |
729 | ## Подаване на стойност на генератора:
730 |
731 | ```javascript
732 | function *countTo(n) {
733 | for (let i = 0; i < n; i += 1) {
734 | let isEven = yield i;
735 | console.log(i, (isEven) ? 'is' : 'isn\'t', 'even.');
736 | }
737 | }
738 |
739 | let generator = countTo(3);
740 | let res = generator.next();
741 | res = generator.next(!(res.value % 2));
742 | res = generator.next(!(res.value % 2));
743 | res = generator.next(!(res.value % 2));
744 |
745 | ```
746 | ---
747 |
748 | ## По време на следващото упражнение ще си поиграе(те|м) с Promises & Generators
749 |
750 | ---
751 |
752 | # Итератори
753 |
754 | ---
755 |
756 | ## Дефиниция на итератор
757 |
758 | ```javascript
759 | var foo = {};
760 | foo[Symbol.iterator] = function * () {
761 | for (var i = 0; i < 5; i += 1) {
762 | yield i;
763 | }
764 | };
765 | ```
766 |
767 | ...и можем да го обходим с:
768 |
769 | ```javascript
770 | for (var val of foo) {
771 | console.log(val);
772 | }
773 | ```
774 | ---
775 |
776 | ## Ресурси
777 |
778 | - [Immutable.js](https://facebook.github.io/immutable-js/)
779 | - [A generator-based sequence generator and utility.](https://github.com/nmn/Grunge)
780 | - [Минимална имплементация на promises](https://gist.github.com/mgechev/afbf6a5802b8fa55d79c)
781 | - [IterJS](https://github.com/abozhilov/IterJS)
782 |
--------------------------------------------------------------------------------
/lectures/05-02-promises-and-generators-short.markdown:
--------------------------------------------------------------------------------
1 | ### Continuation Passing
2 |
3 | ```javascript
4 | fs.readFile(inputFile, function(err, data) {
5 | if (err) return res.status(500).send(err);
6 | process1(data, function(err, data) {
7 | if (err) return res.status(500).send(err);
8 | process2(data, function(err, data) {
9 | if (err) return res.status(500).send(err);
10 | process3(data, function(err, data) {
11 | if (err) return res.status(500).send(err);
12 | fs.writeFile(outputFile, data, function(err) {
13 | if (err) return res.status(500).send(err);
14 | res.status(200).send('processed successfully using callback hell');
15 | });
16 | });
17 | });
18 | });
19 | });
20 | ```
21 | ---
22 | ###Named Continuation Passing
23 |
24 | ```javascript
25 | fs.readFile(inputFile, onReadFile);
26 |
27 | function onReadFile(err, data) {
28 | if (err) return res.status(500).send(err)
29 | process1(data, onProcess1)
30 | }
31 |
32 | function onProcess1(err, data) {
33 | if (err) return res.status(500).send(err)
34 | process2(data, onProcess2)
35 | }
36 |
37 | function onProcess2(err, data) {
38 | if (err) return res.status(500).send(err)
39 | process3(data, onProcess3)
40 | }
41 |
42 | function onProcess3(err, data) {
43 | if (err) return res.status(500).send(err)
44 | fs.writeFile(outputFile, data, onWriteFile)
45 | }
46 |
47 | function onWriteFile(err) {
48 | if (err) return res.status(500).send(err)
49 | res.status(200).send('processed successfully using callback hell')
50 | }
51 | ```
52 | ---
53 |
54 | #Promises ES2015 (ES6)
55 |
56 | ---
57 | ###Какво са promises?
58 | Promise е обект който се използва за отлагане.
59 |
60 | Той представлява операция, която все още не е завършила, но се очаква да завърши.
61 |
62 | ```javascript
63 | new Promise(executor);
64 | new Promise(function(resolve, reject) { ... });
65 | ```
66 |
67 | Този конструктор взима като аргумент функция с два аргумента: resolve, reject.
68 |
69 | Тези два аргумента са функции като едната се използва когато искаме да си изпълним обешанието, а другата когато искаме да го отхвърлим.
70 |
71 | Като използваме обещания постигаме синхронност на асинхронните операции.
72 |
73 | ---
74 |
75 | ###States
76 |
77 | Всяко обещание има 3 състояния:
78 |
79 | - pending (initial state)
80 | - fulfilled
81 | - rejected
82 |
83 | ---
84 | ###Example
85 |
86 | ```javascript
87 | function httpGet(url) {
88 | return new Promise(
89 | function (resolve, reject) {
90 | var request = new XMLHttpRequest();
91 | request.onreadystatechange = function () {
92 | if (this.status === 200) {
93 | // Success
94 | resolve(this.response);
95 | } else {
96 | // Something went wrong (404 etc.)
97 | reject(new Error(this.statusText));
98 | }
99 | }
100 | request.onerror = function () {
101 | reject(new Error(
102 | 'XMLHttpRequest Error: '+this.statusText));
103 | };
104 | request.open('GET', url);
105 | request.send();
106 | });
107 | }
108 |
109 | ```
110 |
111 | ---
112 |
113 | ###Example
114 | ```javascript
115 | httpGet('http://example.com/file.txt')
116 | .then(
117 | function (value) {
118 | console.log('Contents: ' + value);
119 | },
120 | function (reason) {
121 | console.error('Something went wrong', reason);
122 | });
123 | ```
124 | ---
125 | ###Chaining (using then)
126 |
127 | ```javascript
128 |
129 | asyncFunc()
130 | .then(function (value1) {
131 | return 123;
132 | })
133 | .then(function (value2) {
134 | console.log(value2); // 123
135 | });
136 | ```
137 | ---
138 |
139 | ### Error handling
140 |
141 | ```javascript
142 |
143 | asyncFunc1()
144 | .then(asyncFunc2)
145 | .then(asyncFunc3)
146 | .catch(function (reason) {
147 | // Something went wrong above
148 | });
149 | ```
150 | ---
151 | ###Composition (map via Promise.all)
152 |
153 | ```javascript
154 | var fileUrls = [
155 | 'http://example.com/file1.txt',
156 | 'http://example.com/file2.txt'
157 | ];
158 | var promisedTexts = fileUrls.map(httpGet);
159 |
160 | Promise.all(promisedTexts)
161 | .then(function (texts) {
162 | texts.forEach(function (text) {
163 | console.log(text);
164 | });
165 | })
166 | .catch(function (reason) {
167 | // Receives first rejection among the promises
168 | });
169 | ```
170 | ---
171 | ###Workflow
172 |
173 | ---
174 |
175 | #Итериране на обекти
176 |
177 | В ES5 нямаме много възможности да използваме някакви операции върху обекти и за това използвахме
178 | библиотеки като lodash и underscore.
179 |
180 | За радост в ES2015 това не е така. Вече имаме Iterators.
181 |
182 | ---
183 |
184 | #Какво е Iterator?
185 |
186 | ---
187 |
188 | Iterator е design pattern, който ни дава начин да достъпваме последователно елементи на някакъв контейнер (обект).
189 |
190 | Това значи, че можем да използваме един и същ интерфейс за итериране независимо от репрезентацията на обекта.
191 |
192 |
193 |
194 |
195 | В езици като C#, Java за да можем за инерираме по обект трябва да наследяваме някакъв интерфейс или да extend-нем някакъв клас, но в JS не е така.
196 |
197 | В JS за да бъдеш итератор трябва да изглеждаш като такъв! Това означава, че можем да използваме итератори
198 | върху всеки един обект.
199 |
200 | ---
201 | ###Какво трябва да направим за да можем да итерираме по даден обект?
202 |
203 | За да можем да итерираме по обект, той или някой от обектите от прототипната му верига трябва да има свойството Symbol.iterator
204 |
205 | [Symbol.iterator] е функция без аргументи, която връща обект, който има имплементиран метод - next
206 |
207 | next трябва да е функция, която не взима аргументи и връща обект с две свойства: done(boolean) и value
208 |
209 |
210 | Обекти които имат [Symbol.iterator]
211 |
212 |
213 | ---
214 | ###Пример
215 |
216 | ```javascript
217 | var someString = "hi";
218 |
219 | var iterator = someString[Symbol.iterator]();
220 |
221 | iterator.next(); // { value: "h", done: false }
222 | iterator.next(); // { value: "i", done: false }
223 | iterator.next(); // { value: undefined, done: true }
224 |
225 | var arr = ['a', 'b', 'c'];
226 | iterator = arr[Symbol.iterator]();
227 | iter.next() //{ value: 'a', done: false }
228 | iter.next() //{ value: 'b', done: false }
229 | iter.next() //{ value: 'c', done: false }
230 | iter.next() //{ value: undefined, done: true }
231 |
232 | ```
233 | ---
234 | ###Как да си пишем custom iterables
235 |
236 | ```javascript
237 | let iterable = {
238 | [Symbol.iterator]() {
239 | let step = 0;
240 | let iterator = {
241 | next() {
242 | if (step <= 2) {
243 | step++;
244 | }
245 | switch (step) {
246 | case 1:
247 | return { value: 'hello', done: false };
248 | case 2:
249 | return { value: 'world', done: false };
250 | default:
251 | return { value: undefined, done: true };
252 | }
253 | }
254 | };
255 | return iterator;
256 | }
257 | };
258 |
259 | for (let x of iterable) {
260 | console.log(x);
261 | }
262 | // Output:
263 | // hello
264 | // world
265 | ```
266 | ---
267 |
268 | for ... of е от ES2015 и работи с Iterator-и
269 | ```javascript
270 | for (variable of object) {
271 | statement
272 | }
273 | ```
274 |
275 | Някои вътрешни конструкции като spread оператора използват итератори
276 | ```javascript
277 | [...someString] // ["h", "i"]
278 | ```
279 | ---
280 |
281 | #Generators
282 |
283 | ---
284 |
285 | ###Какво са генераторите
286 |
287 | Генераторите са функциии с множество крайни точки.
288 |
289 | Чрез ключовата дума yield ние можем да замразим изпълнението на нашата генератор функция след което можем да продължим изпълнението и.
290 |
291 | ```javascript
292 | function* fibonacci() {
293 | var a = 0, b = 1, c = 0;
294 |
295 | while(true){
296 | yield a; //<-- suspend the generator function
297 | c = a, a = b, b = c + b;
298 | }
299 | }
300 |
301 |
302 | var seq = fibonacci(); //<--- call the generator function and to get an iterator object
303 |
304 | //След извикване на next получаваме обект { value: 0, done: false };
305 | console.log(seq.next().value); // 0
306 | console.log(seq.next().value); // 1
307 | console.log(seq.next().value); // 1
308 | console.log(seq.next().value); // 2
309 | console.log(seq.next().value); // 3
310 | console.log(seq.next().value); // 5
311 | ```
312 | ---
313 |
314 |
315 |
316 |
Това ли е всичко? ... Ко? Не!
317 |
318 | ---
319 |
320 | ###Можем да пращаме стойности на генеранорите си.
321 | ```javascript
322 | function* powerGenerator() {
323 | var result = Math.pow(yield 'a', yield 'b');
324 | return result;
325 | }
326 |
327 | var g = powerGenerator();
328 | console.log(g.next().value);
329 | console.log(g.next(10).value);
330 | console.log(g.next(2).value); // <--- what's the FINAL result?
331 | ```
332 | Ако извикаме още един път next получаваме обект
333 |
334 | { value: undefined, done: true };
335 | ---
336 |
337 |
338 |
339 |
Можем ли да използваме генератори за работа с асинхронните си функции?
340 | ---
341 | #Example
342 | ```javascript
343 | sync(function* (resume){
344 | try{
345 | var profile = yield _get('/profile', resume);
346 | console.log('Hello ' + profile.name);
347 | }catch(err){
348 | console.log(err);
349 | }
350 | });
351 |
352 | function sync(gen){ //<-- our sync function takes a generator
353 | var iteratorObj = null;
354 |
355 | function resume(err, result){
356 | if(err) return iteratorObj.throw(err);
357 | iteratorObj.next(result);
358 | }
359 |
360 | iteratorObj = gen(resume);
361 | iteratorObj.next();
362 | }
363 |
364 | ```
365 | ---
366 | # Може и още по-добре ...
367 | ```javascript
368 |
369 | function _get(val, callback) {
370 | setTimeout(function(){
371 | callback(null, val);
372 | }, val);
373 | }
374 |
375 | function _post(url, data, callback) {
376 | setTimeout(function(){
377 | callback(null, url + ': data received');
378 | }, 1000);
379 | }
380 |
381 | sync(function* (resume) {
382 | try{
383 | var responses = yield [_get(2000, resume()), _get(300, resume())];
384 | console.log(responses);
385 | var resp = yield _post('/myData', responses, resume());
386 | console.log(resp);
387 | }
388 | catch(err){
389 | console.log(err);
390 | }
391 | });
392 | ```
393 | ---
394 | ```javascript
395 | function sync(gen) {
396 | var returnedValues = [], operationCounter = 0, returnedValuesCounter = 0;
397 |
398 | var reset = function(){
399 | returnedValues = [];
400 | operationCounter = 0;
401 | returnedValuesCounter = 0;
402 | };
403 |
404 | var check = function() {
405 | if (returnedValuesCounter == operationCounter) {
406 | var result = null;
407 | if (operationCounter == 1) result = returnedValues[0];
408 | else result = returnedValues;
409 | reset();
410 | iterable.next(result);
411 | }
412 | };
413 |
414 | var resume = function() {
415 | var slot = operationCounter++;
416 |
417 | return function(err, returnedValue) {
418 | if (err) iterable.throw(err);
419 | returnedValuesCounter++;
420 | returnedValues[slot] = returnedValue;
421 | check();
422 | };
423 | };
424 |
425 | var iterable = gen(resume);
426 | iterable.next();
427 | }
428 | ```
429 | ---
430 |
431 | class: middle, center
432 | [Още за генератори](https://strongloop.com/strongblog/how-to-generators-node-js-yield-use-cases/)
--------------------------------------------------------------------------------
/lectures/06-node-js.markdown:
--------------------------------------------------------------------------------
1 | class: middle, center
2 |
3 | # Какво е V8?
4 |
5 | ---
6 |
7 | class: middle, center
8 | ### V8 is a Javascript engine designed by Google
9 |
10 | Преди v8 повечето JS engine-и бяха просто интерпретатори и не бяха бързи.
11 |
12 | ---
13 | class: middle, center
14 |
15 | #Защо е проблем да се направи бърз JS enigine?
16 |
17 | ---
18 |
19 | ### JS е много динамичен език
20 |
21 | * Обектите са хеш таблици и няма нищо статично по тях.
22 |
23 | * Можем да добавяме и махаме свойства към обектите когато си искаме.
24 |
25 | * Имаме Prototype Chains, които можем да модифицираме в run time.
26 |
27 | * Можем да променяме контекста (call, apply, bind).
28 |
29 | ---
30 | ### Когато говорим за големи уеб приложения са ни необходими
31 |
32 | * Бързо да достъпваме свойствата на обектите.
33 |
34 | * Бързи function calls.
35 |
36 | * И мн добър memory management.
37 |
38 | ---
39 | ### Тези 3 неща се решават от V8 чрез:
40 |
41 | * Hidden classes.
42 |
43 | * Inline caching or polymorphic inline cache (used with the JIT compiler)
44 |
45 | С тези две неща Google постигат бързо достъпване на свойства на обектите и бързи function calls
46 |
47 | * Generational Garbage Collector (memory pools holding objects of different ages)
48 |
49 | С това се постига добър memory management
50 | ---
51 | ### Hidden classes
52 |
53 |
54 | ---
55 | ### Какво ще стане тук?
56 | ```javascript
57 | var p1 = {
58 | x: 2,
59 | y: 5
60 | };
61 |
62 | var p2 = {
63 | x: 2,
64 | y: 5
65 | };
66 |
67 | var p3 = {
68 | y: 5,
69 | x: 7
70 | }
71 | ```
72 | ---
73 | ### Arrays
74 | * Скритите класове на масивите следят типовете на елементите
75 | * Ако един масив е пълен с doubles той се upgrade-ва (unboxed) към fast double
76 | * Невнимателно манипулиране на масивите може да причини оптимизационни проблеми (unboxing - boxing)
77 |
78 | ```javascript
79 | var a = new Array();
80 | a[0] = 77; // Allocates
81 | a[1] = 88;
82 | a[2] = 0.5; // Allocates, converts
83 | a[3] = true; // Allocates, converts
84 |
85 | var a = [77, 88, 0.5, true]; //Is better
86 | ```
87 | ---
88 | ### Arrays
89 |
90 | * Не пре-алокирайте големи масиви (>=100K елементи).
91 |
92 | * Ако знаем колко елемента ще се съдържат в масива е хубаво да пре-алокираме.
93 |
94 | * Не е хубаво да трием елементи. (Отнася се и за обекти).
95 |
96 | * За масиви с различни типове трябва да използваме Array literal.
97 | ---
98 | class: middle, center
99 |
100 | ##### Обектните литерали, които имат една и съща структура също използват едни и същи скрити класове.
101 |
102 | * За това е хубаво когато правим нови обекти да слагаме в тях всички property-та, които те ще съдържат.
103 | ---
104 | ###Какво прави v8?
105 |
106 | * v8 компилира Javascript директно до машинни инструкции (unoptimized native code) преди изпълнението му.
107 | (Няма никакво интерпретиране и bytecode)
108 |
109 | * Функциите не се компилират докато не бъдат извикани, което е хубаво защото виртуалната машина не губи време за големи библиотеки
110 |
111 | * След компилирането се пуска profiler, който избира "hot" функции, които да бъдат оптимизирани от Crankshaft (optimization compiler)
112 |
113 | ---
114 | class: middle, centeri
115 | ###Какво е NodeJS?
116 | ---
117 | class: middle, center
118 | ####Open-source, cross-platform runtime environment for developing server-side web applications.
119 |
120 |
121 |
122 | * libuv - high performance, cross-platform evented I/O library
123 |
124 | * js/c++ - Node екосистемата npm (node package manager) е най-голямата open-source екосистема от библиотеки в света.
125 |
126 | ---
127 | ###Малко история
128 | * Node e създаден 2009 от Ryan Dahl и други програмисти от Joyent.
129 |
130 | * 2011 година излиза npm
131 |
132 | * 2012 Ryan Dahl се оттегля от проекта
133 |
134 | * 2014 поради вътрешен конфликт излиза форк на node с името io.js
135 |
136 | * 2015 node and io.js merge
137 |
138 | ---
139 | ###Runtime
140 |
141 | * v8 event loop се грижи за javascript и C++ модулите, които работят в main thread-а.
142 |
143 | * Ако нещо работи извън main thread-а libev и libuv го обработват в thread pool. libuv прави връзката с main thread-а.
144 |
145 |
146 | ---
147 | ###Конвенции
148 |
149 | ```javascript
150 |
151 | var callback = function(err, data) {
152 | //DO STUFF
153 | };
154 |
155 | saveData(myData, callback);
156 |
157 | ```
158 |
159 | * callback функцията е винаги последният аргумент, който се подава на асинхронната функция.
160 |
161 | * Първата стойност, която се подава на callback функцията е винаги error.
162 | ---
163 | ###Modules
164 |
165 | ```javascript
166 |
167 | var fs = require('fs'); //default module
168 | var myModule = require('../app_modules/myModule'); //custom module
169 |
170 | myModule.foo(); // > this is foo!
171 | ```
172 | Някои други default модули са:
173 |
174 | * http
175 | * crypto
176 | * os
177 | * path
178 |
179 | Тук може да откриете всички: https://nodejs.org/api/
180 |
181 | ---
182 | ###Как да пишем собствени модули:
183 |
184 | ```javascript
185 |
186 | module.exports = {
187 | foo: function() {
188 | return 'this is foo!';
189 | },
190 | bar: function() {
191 | return 'this is bar';
192 | }
193 | };
194 |
195 | ```
196 |
197 | ```javascript
198 |
199 | module.exports.foo = function() {
200 | return 'this is foo!';
201 | };
202 |
203 | module.exports.bar = function() {
204 | return 'this is bar!';
205 | };
206 | ```
207 | ---
208 | ###NPM
209 |
210 | * Когато започнем да правим нов проект започваме с npm init за да си създадем package.json, който съдържа полезна информация за проекта ни.
211 |
212 | ```javascript
213 | npm install module_name
214 | ```
215 |
216 | * При добавяне на нов модул към проекта е хубаво да използвамe npm install module_name --save за да запишем този модул към dependencies в package.json.
217 |
218 | * Свалените модули се запазват в папка node_modules, която трябва да добавям в .gitignore за да не я commit-ваме към source control-а.
219 |
220 | * При сваляне на приложението от github посто трябва да напишем npm install или накратко npm i за да се свалят всички dependency-та от package.json.
221 |
222 | * Понякога искаме модулите да се свалят глобално, за да можем да ги използваме навсякъде. Тогава използваме npm install module_name -g.
223 |
224 | ---
225 | ###Events
226 |
227 | * Събитията ни дават друг начин, по който можем да пишем асинхронни операции.
228 |
229 | ```javascript
230 | //Callback example
231 | getUserData(param1, function(err, results) {
232 | //Do stuff
233 | });
234 |
235 |
236 |
237 | //Event example
238 | var userData = getUserData(param1);
239 |
240 | //Subscribe to the data event
241 | userData.on('data', function(data) {
242 | //Do stuff
243 | });
244 |
245 | //Subscribe to the item event
246 | userData.on('item', function(item) {
247 | //Do stuff
248 | });
249 | ```
250 | Тук казваме, че всеки път когато възникне евент с име data, трябва да се изпълни тази функция. Ако искаме да се изпълни само веднъж, използваме once.
251 |
252 | ---
253 | ###Custom Event Emitters (1)
254 |
255 | ```javascript
256 | var EventEmitter = require('events').EventEmitter;
257 |
258 | var emitter = new EventEmitter();
259 |
260 | module.exports.getUserData = function() {
261 | setTimeout(function(){
262 | //Publish the item event after at least 5sec
263 | emitter.emit('item', { id: 123, name: 'ivan' });
264 | }, 5000);
265 | return emitter;
266 | };
267 | ```
268 |
269 | ---
270 | ###Custom Event Emitters (2)
271 | ```javascript
272 | var EventEmitter = require('events').EventEmitter;
273 | var util = require('util');
274 |
275 | function CustomEmitter() {
276 | EventEmitter.call(this);
277 | };
278 |
279 | CustomEmitter.prototype.connect = function() {
280 | var self = this;
281 | setTimeout(function(){
282 | self.emit('connected', { name: 'ivan', age: 19 });
283 | }, 5000);
284 | };
285 |
286 | util.inherits(CustomEmitter, EventEmitter);
287 | module.exports = CustomEmitter;
288 | ```
289 | ```javascript
290 | var CustomEmitter = require('./CustomEmitter');
291 |
292 | var cm = new CustomEmitter();
293 |
294 | cm.on('connected', function(data) {
295 | console.log(data);
296 | });
297 |
298 | cm.connect();
299 | ```
300 | ---
301 | ###Streams
302 |
303 | * Потоците разширяват EventEmitter.
304 |
305 | * Те представляват абстракция за менажиране на data flow. (Network traffic, File I/O ...)
306 |
307 | * Може да правим 3 типа потоци: Readable, Writable, Transform (Readable and Writable)
308 |
309 | * Можем да pipe-ваме Readable потоците към Writable (подобно на unix command pipeing)
310 | ---
311 | ###Readable Streams
312 |
313 |
314 |
event 'readable' (a chunk of data can be read)
315 |
event 'data' (get chunk of data asap)
316 |
event 'end' (no more data to read)
317 |
event 'error'
318 |
event 'close' (when all resources are closed)
319 |
read([size]) (pull data from internal buffer)
320 |
setEncoding(encoding)
321 |
pause()
322 |
isPaused()
323 |
pipe(WritableStream)
324 |
unpipe()
325 |
unshift() (unshift onto read queue)
326 |
327 |
328 | ---
329 | ### Writable Streams
330 |
331 |
332 |
event 'finish'
333 |
event 'drain' (begin writing more data after read returned false)
334 |
event 'error'
335 |
event 'pipe'
336 |
event 'unpipe'
337 |
write(chunk)
338 |
cork() (force buffering of all writes)
339 |
uncork() (flush all buffered data since cork call)
340 |
end() (call this when we don't need more data)
341 |
setDefaultEncoding(encoding)
342 |
343 |
344 | ---
345 | ###Example (1)
346 | ```javascript
347 | var request = require('request');
348 | var arr = [];
349 |
350 | var rs = request('http://www.google.com/');
351 | rs.on('data', function(chunk) {
352 | arr.push(chunk);
353 | });
354 |
355 | rs.on('end', function() {
356 | var result = arr.toString();
357 | result = result.replace(/div/g,'span');
358 | var newBuffer = new Buffer(result);
359 |
360 | //Send new buffer somewhere ...
361 |
362 | console.log(result);
363 | });
364 | ```
365 | ---
366 | ###Example (2)
367 |
368 | ```javascript
369 | var fs = require('fs');
370 | var file = [];
371 |
372 | var readStream = fs.createReadStream('path/to/file');
373 |
374 | readStream.on('data', function(chunk) {
375 | file.push(chunk); //chunk is a buffer
376 | });
377 |
378 | readStream.on('end', function() {
379 | console.log(file.toString());
380 | });
381 |
382 | var writeStream = fs.createWriteStream('path/to/otherfile');
383 |
384 | readStream.pipe(writeStream);
385 | ```
386 | ---
387 | ### Example (3)
388 |
389 | ```javascript
390 | var fs = require('fs');
391 |
392 | var writeStream = fs.createWriteStream('./test.txt');
393 |
394 | writeStream.on('finish', function() {
395 | console.log('all done ...');
396 | });
397 |
398 | writeStream.write('Hello');
399 | writeStream.write(' ');
400 | writeStream.write('World!');
401 |
402 | writeStream.end();
403 | ```
404 | ---
405 |
406 | ### Example (4)
407 | ```javascript
408 | var fs = require('fs');
409 | var zlib = require('zlib');
410 | var request = require('request');
411 |
412 | var writeStream = fs.createWriteStream('path/to/file');
413 | //zlib.createGzip() is a Transform stream
414 | request('http://google.com/').pipe(zlib.createGzip()).pipe(writeStream);
415 | ```
416 | ---
417 | ### Custom Streams
418 |
419 | ```javascript
420 | var stream = require('stream');
421 | var util = require('util');
422 |
423 | function EchoStream () {
424 | stream.Writable.call(this);
425 | };
426 |
427 | util.inherits(EchoStream, stream.Writable);
428 |
429 | EchoStream.prototype._write = function (chunk, encoding, done) {
430 | console.log(chunk.toString());
431 | done();
432 | }
433 |
434 | var myStream = new EchoStream();
435 | process.stdin.pipe(myStream);
436 | ```
437 | * Потоците обикновенно работят с buffers.
438 | * ObjectMode: true ни позволява да работим с JS обекти, вместо с буфери.
439 | * Ако ни трябва Readable или Transform stream трябва съответно да реализираме _read или _transform методи вместо _write
440 |
441 | ---
442 | ##### Transform Stream Example
443 | ```javascript
444 | var stream = require('stream');
445 | var fs = require('fs');
446 | var liner = new stream.Transform( { objectMode: true } );
447 |
448 | liner._transform = function (chunk, encoding, done) {
449 | var data = chunk.toString();
450 | if (this._lastLineData) data = this._lastLineData + data;
451 |
452 | var lines = data.split('\n');
453 | this._lastLineData = lines.pop();
454 |
455 | lines.forEach(this.push.bind(this));
456 | done();
457 | };
458 |
459 | liner._flush = function (done) {
460 | if (this._lastLineData) this.push(this._lastLineData);
461 | this._lastLineData = null;
462 | done();
463 | };
464 |
465 | var source = fs.createReadStream('path/to/file');
466 | source.pipe(liner);
467 |
468 | liner.on('readable', function () {
469 | var line = null;
470 | while((line = liner.read())){
471 | console.log(line);
472 | }
473 | });
474 | ```
475 | ---
476 | ### Readable Stream Example
477 |
478 | ```javascript
479 | var util = require('util');
480 | var Readable = require('stream').Readable;
481 |
482 | var MyStream = function(options) {
483 | Readable.call(this, options); // pass through the options to the Readable constructor
484 | this.counter = 1000;
485 | };
486 |
487 | util.inherits(MyStream, Readable); // inherit the prototype methods
488 |
489 | MyStream.prototype._read = function(n) {
490 | this.push('foobar');
491 | if (this.counter-- === 0) { // stop the stream
492 | this.push(null);
493 | }
494 | };
495 |
496 | var mystream = new MyStream();
497 | mystream.pipe(process.stdout);
498 | ```
499 |
500 | ---
501 | class: middle, center
502 | [More about Streams](https://github.com/substack/stream-handbook)
503 | ---
504 | ### Buffers
505 | Когато четем от файлове или работим с мрежата ние получаваме сурова (raw) памет, която е извън v8 heap-а. Buffer-ът представлява mapping (handle) между тази памет и JS Array.
506 | v8 няма контрол върху тази памет, за това този mapping, който се създава, позволява на libuv да деалокира суровата памет след като Garbage Collector-а (GC) премахне нашия buffer.
507 |
508 | Buffer-ите могат да бъдат гадни!
509 |
510 | Нека си представим, че pre-alloc-ираме голямо парче памет (fs.readFile без encoding) и след това разбием тази памет на малки парчета към конкретни JS обекти. Какво ще се случи с нашата памет, ако без да искаме оставим референции към някои от тези обекти?
511 |
512 | Използването на slice в Node върху буфери не е добра идея, защото може да доведе до memory leaks. Slice не прави копие на array, а създава пойнтъри към паметта, която държи array-я.
513 | За това е по-добре е да си създадем изцяло нов Buffer.
514 |
515 | Да разгледаме също какво се случва в Stream Example(1).
516 | ---
517 | ### Process
518 |
519 | * process обектът е достъпен навсякъде в нашето node приложение и разширява Event Emitter.
520 |
521 | * има полезни референции към:
522 | - process.stdin, process.stdout, process.stderr (Streams)
523 |
524 | - process.argv, process.pid, process.title, process.cwd() и др (Attrbutes)
525 |
526 | - process.abort(), process.kill(pid) (methods)
527 |
528 | - 'exit', 'error' + POSIX signal events (events)
529 |
530 | ---
531 | ### Using Node as a client
532 |
533 | Вече разгледахме request, а сега нека видим http
534 | ```javascript
535 | var http = request('http');
536 | var options = {
537 | host: 'www.google.com',
538 | port: 80,
539 | path: '/',
540 | method: 'GET'
541 | };
542 |
543 | var req = http.request(options, function(resp){
544 | console.log(resp.statusCode);
545 | resp.pipe(process.stdout);
546 | });
547 | //req is a WritableStream
548 | //resp is a ReadableStream
549 |
550 | req.end(); //close the writable stream
551 |
552 | //We can use get instead
553 | http.get('www.google.com', function(res){
554 | res.pipe(process.stdout);
555 | });
556 | ```
557 |
558 | ---
559 | ### Using Node as web server Example(1)
560 |
561 | ```javascript
562 | var http = require('http');
563 | var server = http.createServer(function(req, res){
564 | //req and res are streams
565 | //process request
566 | });
567 |
568 | server.listen(8081, function() {
569 | console.log('Server is listening on 8081 ...');
570 | });
571 | ```
572 | ---
573 | ```javascript
574 | var http = require("http"),
575 | url = require("url"),
576 | path = require("path"),
577 | fs = require("fs"),
578 | port = process.argv[2] || 8888;
579 |
580 | http.createServer(function(request, response) {
581 | var uri = url.parse(request.url).pathname
582 | , filename = path.join(process.cwd(), uri);
583 |
584 | fs.exists(filename, function(exists) {
585 | if(!exists) {
586 | response.writeHead(404, {"Content-Type": "text/plain"});
587 | response.write("404 Not Found\n");
588 | response.end();
589 | return;
590 | }
591 | if (fs.statSync(filename).isDirectory()) filename += '/index.html';
592 | fs.readFile(filename, "binary", function(err, file) {
593 | if(err) {
594 | response.writeHead(500, {"Content-Type": "text/plain"});
595 | response.write(err + "\n");
596 | response.end();
597 | return;
598 | }
599 | response.writeHead(200);
600 | response.write(file, "binary");
601 | response.end();
602 | });
603 | });
604 | }).listen(parseInt(port, 10));
605 | ```
--------------------------------------------------------------------------------
/lectures/07-express/expressServer/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = require('./router');
3 | var fs = require('fs');
4 | var bodyParser = require('body-parser');
5 | var busboy = require('connect-busboy');
6 | var app = express();
7 | var port = 8888;
8 |
9 | app.use(bodyParser.json());
10 | app.use(bodyParser.urlencoded({ extended: true }));
11 | app.use('/api/books', router);
12 |
13 | app.post('/upload', busboy(), function(req, res) {
14 | if(!req.busboy) return res.status(500).send('Incorrect post headers!');
15 | req.busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
16 | var ws = fs.createWriteStream('./uploads/'+ filename);
17 | ws.on('finish', function() {
18 | console.log('File saved ...');
19 | res.status(200).end();
20 | });
21 | file.pipe(ws);
22 | });
23 | req.pipe(req.busboy);
24 | });
25 |
26 | //Serve static content
27 | app.use(express.static(__dirname + '/static'));
28 |
29 | app.listen(port, function() {
30 | console.log('Server listening on ' + port + ' ...');
31 | });
--------------------------------------------------------------------------------
/lectures/07-express/expressServer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "07-express",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "= <=>",
10 | "license": "ISC",
11 | "dependencies": {
12 | "body-parser": "^1.14.1",
13 | "connect-busboy": "0.0.2",
14 | "express": "^4.13.3"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lectures/07-express/expressServer/router.js:
--------------------------------------------------------------------------------
1 | var expressRouter = require('express').Router();
2 | var fs = require('fs');
3 |
4 | var books = [{
5 | id: '1',
6 | author: 'None',
7 | name: 'book1'
8 | },{
9 | id: '2',
10 | author: 'None1',
11 | name: 'book2'
12 | },{
13 | id: '3',
14 | author: 'None',
15 | name: 'book3'
16 | }];
17 |
18 | expressRouter.get('/', function(req, res) {
19 | var filter = req.query;
20 | if(Object.keys(filter).length === 0) return res.json(books);
21 | var result = books.filter(function(book) {
22 | var flag = true;
23 | for(var prop in filter) {
24 | flag = (filter[prop] === book[prop]);
25 | }
26 | return flag;
27 | });
28 | res.json(result);
29 | });
30 |
31 | expressRouter.get('/:bookId', function(req, res) {
32 | var book = books[req.params.bookId];
33 | if(!book) res.status(404).send('Book not found!');
34 | res.json(book);
35 | });
36 |
37 | expressRouter.post('/', function(req, res) {
38 | books.push(req.body);
39 | res.status(201).end();
40 | });
41 |
42 | module.exports = expressRouter;
43 |
--------------------------------------------------------------------------------
/lectures/07-express/expressServer/static/index.html:
--------------------------------------------------------------------------------
1 | Hello world!
2 |
--------------------------------------------------------------------------------
/lectures/07-express/generator.js:
--------------------------------------------------------------------------------
1 | function readFile(filename, callback) {
2 | setTimeout(function() {
3 | callback(null, 'File ' + filename + ' contents');
4 | }, 5000);
5 | }
6 |
7 | function* f(maxN) {
8 | while(maxN < 5) {
9 | var x = yield readFile('alabala', niakva);
10 | console.log(x);
11 | maxN++;
12 | //return 1000;
13 | }
14 | }
15 |
16 | function niakva(error, result) {
17 | //console.log(result);
18 | iter.next(result);
19 | }
20 |
21 |
22 | var iter = f(1);
23 | console.log(iter.next()); //kick start
24 | //console.log(iter.next(500));
25 |
--------------------------------------------------------------------------------
/lectures/07-express/httpServer/app.js:
--------------------------------------------------------------------------------
1 | var http = require('http');
2 | var port = 8888;
3 |
4 | var server = http.createServer(function(req, res) {
5 | var allData = [];
6 |
7 | req.on('data', function(chunk) {
8 | allData.push(chunk);
9 | });
10 |
11 | req.on('end', function() {
12 | var host = allData.toString();
13 | var request = http.request({ host: host, path: '/' }, function(req) {
14 | var htmlData = [];
15 | req.on('data', function(htmlChunk) {
16 | htmlData.push(htmlChunk);
17 | });
18 | req.on('end', function() {
19 | res.setHeader('Status code', 200);
20 | res.write(htmlData.toString());
21 | res.end();
22 | });
23 | });
24 | request.end();
25 | });
26 | });
27 |
28 | server.listen(port, function(){
29 | console.log('Server listening on ' + port);
30 | });
--------------------------------------------------------------------------------
/lectures/08-dom-intro-and-events.markdown:
--------------------------------------------------------------------------------
1 | # Съдържание
2 |
3 | * DOM
4 | * DOM vs HTML
5 | * Типове данни
6 | * Селектиране на елементи
7 | * Добавяне на събития
8 | * addEventListener
9 | * eventObject
10 | * bubbling
11 | * capturing
12 | * on.\*
13 | * Премахване на събития
14 | * Memory leaks при обработката на събития
15 | ---
16 |
17 | # Document Object Model
18 |
19 | API за работа с HTML и XML
20 |
21 | Можем да си мислим за HTML като за сериализирана версия на DOM.
22 |
23 | ---
24 |
25 | # DOM, типове елементи
26 |
27 | - `document` - коренът на DOM дървото
28 | - `Element` - елемент в DOM дървото, който има дъщерни елементи
29 | - `NodeList` - списък с DOM обекти, които могат да бъдат както елементи, така и `text`
30 | - `Text` - DOM обект, който съдържа само текст, не можа да съдържа елементи
31 |
32 | ---
33 |
34 | ## Достъп до елементи в DOM дървото
35 |
36 | - `document.createElement(tag_name)` - създава нов елемент
37 | - `element.childNodes` - достъп до всички дъщерни елементи на даден елемент
38 | - върнатият резултат е от тип `NodeList`, не е масив
39 | - `element.children` - връща всички дъщерни елементи от тип `Element`
40 | - `element.appendChild(child)` - добавя дъщерен елемент на даден елемент
41 | - `element.removeChild(child)` - изтрива дъщерен елемент от даден елемент
42 |
43 | ---
44 |
45 | # Атрибути vs полета
46 |
47 | в HTML можем да зададем атрибути на различните елементи:
48 |
49 | ```html
50 |
51 | ```
52 |
53 | След като браузърът обработи HTML ще създаде `HTMLInputElement`, който ще има различни полета:
54 |
55 | - checked
56 | - type
57 | - disabled
58 | - value
59 | - ...
60 |
61 | ---
62 |
63 | # Работа с атрибути
64 |
65 | - `element.setAttribute(attr_name, value)`
66 | - `element.getAttribute(attr_name)`
67 | - `element.removeAttribute(attr_name)`
68 |
69 | ---
70 |
71 | # Селектиране на елементи
72 |
73 | - `document.getElementById(id)` - селектира елемент по уникален идентификатор
74 | - `element.getElementsByTagName(tag_name)` - селектира елементи по име на таг
75 | - `element.getElementsByClassName(class_name)` - селектира елементи по име на клас
76 | - `element.querySelectorAll(selector)` - селектира всички елементи, които отговарят на зададения селектор
77 | - `element.querySelector(selector)` - селектира първият елемент, които отговаря на зададения селектор
78 |
79 | ---
80 |
81 | # CSS селектори
82 |
83 | - `element` - селектира всички елементи с име `element`
84 | - `#element-id` - селектира елемента с идентификатор `element-id`
85 | - `.class-name` - селектира всички елемент, които използват клас с име `class-name`
86 | - `[attribute="value"]` - селектира всички елементи, които имат атрибут `attribute` със стойност `value`
87 | - `parentSelector childSelector` - селектира всички елемент, които съвпадат със селектора `childSelector` и се намират в елементи съвпадащи със селектора `parentSelector`
88 |
89 | ---
90 |
91 | # Примери
92 |
93 | ```javascript
94 | document.querySelectorAll('#foo');
95 |
96 | document.querySelector('.foo-bar');
97 |
98 | document.querySelectorAll('content.container [title="foobar"]:nth-child(2)');
99 | ```
100 | ---
101 |
102 | # Shadow DOM
103 |
104 | Част от стандарта на WebComponents. Позволява ни по-добра енкапсулация.
105 |
106 | - Можем да създадем т.нар. "shadow root", който е корен на поддърво, което се третира по други правила от браузъра
107 | - Създаденото поддърво е независимо откъм стилове
108 |
109 | ---
110 |
111 | # Shadow DOM
112 |
113 | ```html
114 |
115 |
120 |
121 |
Hello, !
122 |
123 |
124 |
Foobar
125 | ```
126 |
127 |
128 | ```javascript
129 | let root = document.querySelector('#foo').createShadowRoot();
130 | let clone = document.importNode(document.querySelector('#greeting').content, true);
131 | root.appendChild(clone);
132 | ```
133 |
134 | ---
135 |
136 | # Template елемента
137 |
138 | Позволява да добавим markup в документа без той да бъде обработван от браузъра.
139 |
140 | Не е нужно да използваме `` тагове за добавяне на темплейти онлайн (както се случваше с Backbone и Handlebars).
141 |
142 | ---
143 |
144 | # Content елемента
145 |
146 | Позволява да правим "content projection". В случая чрез този елемент, можем да проектираме съдържание от light DOM в shadow DOM.
147 |
148 | ---
149 |
150 | # Асинхронна обработка на код в браузъра
151 |
152 | - `addEventListener` - Добавяне на код обслужващ събития
153 | - `removeEventListener` - Изтриване на код обслужващ събития
154 | - `setTimeout` - Добавяне на код, който да бъде изпълнен след определен интервал
155 | - `clearTimeout`
156 | - `setInterval` - Добавяне на код, който да бъде изпълняван на регулярни интервали от време
157 | - `clearInterval`
158 | - `XMLHttpRequest`
159 | - `fetch`
160 | - `Promise`
161 | - `requestAnimationFrame`
162 | - `WebSocket`
163 | - `WebWorkers`
164 | - и други
165 |
166 | ---
167 |
168 | # DOM събития
169 |
170 | Обработката на събития в DOM ни помага да реагираме при настъпването на определени "условия".
171 | ---
172 | # Примери за събития
173 |
174 | * _mouseenter_ - курсорът на мишката бива преместен върху даден DOM елемент
175 | * _click_ - натискане на левия бутон на мишката при преместен курсор върху даден DOM елемент
176 | * _keydown_ - натискане на бутон на клавиатурата при фокус на даден елемент
177 | * _load_ - при зареждане на даден елемент (изображение, iframe, и други)
178 | * и други...
179 | ---
180 | # Обработка на събития
181 |
182 | W3C стандартизира метода `addEventListener` на Node (`Node.prototype.addEventListener`),
183 | който се използва за добавяне на функции, които обработват събития от определен (зададен) тип, към даден елемент.
184 |
185 | (заб: тук под Node нямаме предвид Node.JS, а класът Node в DOM)
186 |
187 | ---
188 | # addEventListener
189 |
190 | `element.addEventListener(EVENT_NAME, HANDLER, CAPTURING)`
191 |
192 | - `EVENT_NAME` - задава име на събитие, което искаме да обработваме с
193 | - `HANDLER` - callback, който е отговорен за обработката на събитието
194 | - `CAPTURING` - задава начина на "propagation" на събитията
195 |
196 | ---
197 |
198 | # addEventListener
199 |
200 | `addEventListener` ни позволява да добавяме няколко callback-а за едно
201 | и също събитие към даден елемент.
202 |
203 | ---
204 |
205 | # addEventListener
206 |
207 | Пример:
208 |
209 | ```javascript
210 | document.body.addEventListener('click', function (e) {
211 | console.log('The user just clicked onto the body');
212 | }, false);
213 | ```
214 | ---
215 |
216 | # event oбекта
217 |
218 | Всеки callback получава като аргумент event обект. Някои от полетата на този event обект са:
219 |
220 | * offsetY - Задава или връща y-координатата на курсора на мишката спрямо горния-ляв ъгъл на
221 | позициониран родителски елемент (ancestor).
222 | * offsetY - Задава или връща x-координатата на курсора на мишката спрямо горния-ляв ъгъл на
223 | позициониран родителски елемент (ancestor).
224 | * metaKey - Булева стойност индикираща дали meta бутонът на клавиатурата е натистнат
225 | * altKey - Булева стойност индикираща дали alt бутонът на клавиатурата е натистнат
226 | * shiftKey - Булева стойност индикираща дали shift бутонът на клавиатурата е натистнат
227 | * ctrlKey - Булева стойност индикираща дали ctrl бутонът на клавиатурата е натистнат
228 |
229 | ---
230 |
231 | * clientY - Връща y позицията на курсора по време на събитието спрямо горния десен ъгъл на window.
232 | * clientX - Връща x позицията на курсора по време на събитието спрямо горния десен ъгъл на window.
233 | * pageY - Връща y позицията на курсора по време на събитието спрямо document.
234 | * pageX - Връща x позицията на курсора по време на събитието спрямо document.
235 | * which - Връща unicode символ или номер на бутона на мишката, който е довел до събитието.
236 | * keyCode - Връща идентификатор на бутона на клавиатурата, който е натистнат.
237 | * cancelBubble - Задава дали искаме да прекратим bubbling на събитието.
238 | * currentTarget - Връща текущия елемент, за който даденият еvent handler е извикан.
239 | * target - Връща елемента, който е довел до задействане на събитието (източника).
240 | * type - Връща типа на събитието (click, mousedown...).
241 | ---
242 |
243 | # Bubbling & Capturing
244 |
245 | 
246 |
247 | ---
248 |
249 | # Bubbling
250 |
251 | Стандартният начин за "propagation" на събитията е bottom-up - от child елемент към parent елемент:
252 |
253 | ```html
254 |
255 |
256 |
257 | ```
258 |
259 | ```javascript
260 | document.getElementById('parent').addEventListener('click', function () {
261 | console.log('parent');
262 | });
263 | document.getElementById('child').addEventListener('click', function () {
264 | console.log('child');
265 | });
266 | ```
267 |
268 | ---
269 |
270 | # Capturing
271 |
272 | Алтернативен начин за обработка на събития е т.нар. capturing, където обработката се извършва в обратен ред:
273 |
274 | ```html
275 |
276 |
277 |
278 | ```
279 |
280 | ```javascript
281 | document.getElementById('parent').addEventListener('click', function () {
282 | console.log('parent');
283 | }, true);
284 | document.getElementById('child').addEventListener('click', function () {
285 | console.log('child');
286 | }, true);
287 | ```
288 | ---
289 |
290 | # on\*
291 |
292 | Друг начин за добавяне на събития е чрез полетата на елементите `onEVENT_NAME`:
293 |
294 | ```
295 | document.body.onclick = function (e) {
296 | console.log('Click on the body');
297 | };
298 |
299 | ```
300 |
301 | Недостатък
302 |
303 | При него може да се добавя само еднин event handler, който евентуално да делегира изпълнението на
304 | други методи.
305 |
306 | Предимство
307 |
308 | Като възможно *предимство* може да се отбележи факта, че имаме директен достъп до event handler без предварително да сме запазили референция към него.
309 |
310 | По-добре не използвайте този начин за добавяне на event handlers.
311 |
312 | ---
313 |
314 | # Премахване на събития
315 |
316 | Премахването на събития се осъществява с метода: `removeEventListener`.
317 | Той приема като аргумент име на събитие и callback, който да бъде премахнат.
318 |
319 | **Метода трябва да бъде извикан с двата параметъра**, в противен случай
320 | handler(s) няма да бъдат премахнат(и).
321 |
322 | ```javascript
323 |
324 | var handler = function (e) {
325 | //...
326 | };
327 | el.addEventListener('click', handler);
328 |
329 | //...
330 |
331 | el.removeEventListener('click', handler);
332 | ```
333 |
334 | ---
335 |
336 | # Премахване на събития
337 |
338 | В случая, в който добавяме event handler от вида `onEVENT_NAME` премахването на даден handler може да се
339 | осъществи като:
340 |
341 | ```javasctipt
342 | document.body.onclick = function () {};
343 | //...
344 | document.body.onclick = undefined;
345 | ```
346 |
347 | ---
348 |
349 | # Memory leaks
350 |
351 | Много честа причина за memory leaks в JavaScript са именно забравени еvent handlers.
352 |
353 | Заради създадения closure променливите от външните scope-ове на event handlers не могат да бъдат
354 | премахнати, което води до невъзможност паметта да бъде освободена.
355 |
356 |
--------------------------------------------------------------------------------
/lectures/09-websockets.markdown:
--------------------------------------------------------------------------------
1 | class: center,middle
2 | #websockets
3 |
4 | ---
5 | .center[
6 | #pros
7 | ]
8 | .left-column[
9 | #socket
10 | * Живее „безкрайно“
11 | * Изисква относително малко meta информация
12 | * Комуникацията тече постоянно в двете посоки
13 | ]
14 |
15 | .right-column[
16 | #long polling
17 | * Дава механизъм за оторизация
18 | * Така или иначе имаме работещ web сървър
19 | * Имаме добре дефинирана последователност request ⇨ response
20 | ]
21 |
22 | ---
23 |
24 | .center[
25 | #cons
26 | ]
27 | .left-column[
28 | #socket
29 | * Low level операци ⇨ ред security забележки
30 | * Нужно е сами да имплементираме протоколи
31 | ]
32 |
33 | .right-column[
34 | #long polling
35 | * Твърде много повтаряща се meta информация
36 | * Всяка заявка е отваряне на нов socket
37 | ]
38 |
39 | ---
40 | class: center,middle
41 | ###DECISIONS
42 | ##DECISIONS
43 | #DECISIONS
44 |
45 | ---
46 | class: center,middle
47 | 
48 |
49 | ---
50 | #WebSockets
51 |
52 | В крайна сметка една HTTP заявка се реализира чрез отваряне на TCP socket. Браузъра обаче затваря този socket след като получи response(при HTTP/1.1 може да направим няколко заявки по един socket)
53 |
54 | WebSocket-ите са разширение на HTTP/1.1, което позволява да кажем, че искаме този socket да остане отворен неопределено време и по него да реализираме двупосочна комуникация между клиента и сървъра.
55 |
56 | Това се случва, когато в HTTP заявката ни има header `Connection: Upgrade`
57 |
58 | ---
59 | #WebSocket-и в клиента
60 | ```
61 | WebSocket WebSocket(
62 | in DOMString url,
63 | in optional DOMString protocols
64 | );
65 |
66 | WebSocket WebSocket(
67 | in DOMString url,
68 | in optional DOMString[] protocols
69 | );
70 | ```
71 |
72 | ---
73 | #WebSocket-и в клиента
74 | ```javascript
75 | var socket = new WebSocket("ws://www.example.com/socketserver");
76 |
77 | socket.onopen = function(event) {
78 | console.log('connected!');
79 | socket.send('Very first transmission on this socket');
80 | };
81 |
82 | socket.onmessage = function(event) {
83 | console.log('The server sent', event.data, 'through the socket');
84 | };
85 | ```
86 |
87 | ---
88 | #WebSocket-и в клиента
89 |
90 | Едно извикване на `send` в клиента предизвиква точно един `message` event в сървъра и обратното.
91 |
92 | ---
93 | #Typed arrays
94 |
95 | В някои случаи имаме нужда от данни в по-суров вид. Такива случаи могат да бъдат комуникация по websocket или пък комуникация с webworker.
96 | Изпращането на инстанции на `Number` през websocket, или пък някаква форма на странно кодиране на числа в `String` би било доста неефективно.
97 |
98 | ---
99 | #Typed arrays
100 |
101 | За целта javascript ни предоставя `ArrayBuffer` обекти, които представляват буквално „парче памет“. Дължината им се измерва в байтове. Не можем директно да манипулираме елементите на един `ArrayBuffer`. За целта използваме типизирани масиви, които предоставят `DataView`, през което да достъпваме данните в него.
102 |
103 | ---
104 | #Typed arrays
105 |
106 | * `Int8Array`
107 | * `Int16Array`
108 | * `Int32Array`
109 | * `Uint8Array`
110 | * `Uint8CLampedArray`
111 | * `Uint16Array`
112 | * `Uint32Array`
113 | * `Float32Array`
114 | * `Float64Array`
115 |
116 | ---
117 | #Typed arrays
118 |
119 | 
120 |
121 | ---
122 | #Typed arrays = WebSockets
123 |
124 | ```javascript
125 | var array = Int8Array(3);
126 | array[0] = 13;
127 | array[1] = 42;
128 | array[2] = 66; //666 > 255 :(
129 | socket.binaryType = 'arraybuffer';
130 | socket.send(array);
131 | ```
132 |
133 | ---
134 | #socket.io
135 | И все пак светът е страшен, може пък да попаднем в среда, където няма websocket-и.
136 |
137 | Според [Can I Use](http://caniuse.com/#search=websockets) около 82% от интернет потребителите ползват браузър поддържащ WebSocket-и. За някои проекти това число може да е малко.
138 |
139 | Отделно в случаите когато не се интересуваме толкова много от пренасяне на binary данни, а предпочитаме по-лесна комуникация с json [socket.io](http://socket.io/) е прекрасно решение.
140 |
141 | Съществуват servers имплементации за node, python, ruby, go, java, C#(WTF?!) etc…
142 |
143 | ---
144 | #socket.io
145 | ##благини
146 | Автоматичен fallback до различни транспорти според средата, в която се изпълнява кода.
147 |
148 | * WebSockets
149 | * AJAX long polling
150 | * JSONP
151 | * HTTP Streaming
152 | * Flash sockets(евентуално с допълнителна конфигурация)
153 |
154 | ---
155 | #socket.io
156 | ##благини
157 |
158 | Имаме `EventEmitter` интерфейс(`on` метод) и можем да се закачаме за произволни event-и. Доста по-добре от един единствен `onmessage`.
159 |
160 | ```javascript
161 | var socket = io('http://localhost');
162 | socket.on('someeventwemadeup', function(dataр) {
163 | console.log('We got that custom event we just made up!');
164 | });
165 | ```
166 | --
167 | Пращаме/получаваме директно js обекти.
168 | ```javascript
169 | socket.send({ it: 'is', a: 'real', javascript: 'object'});
170 | ```
171 |
172 | ---
173 | #socket.io
174 | ##namespaces
175 | Всеки `socket.io` обект може да принадлежи на определен namespace. Това позволява separation of concerns, като едно съобщение може да бъде пращано само по определени socket-и, а не по всички.
176 |
177 | ```javascript
178 | var socket = io('http://localhost/stats');
179 | ```
180 |
181 | ```javascript
182 | io.emit('data', '/stats');
183 | ```
184 |
185 | Това ще рече, че socket.io сървъра слуша на `http://localhost`, а нашия сокет обект ще бъде в `/stats` namespace-а.
186 |
187 | ---
188 | #socket.io
189 | ##channels
190 |
191 | Когато един socket.io обект се свърже към определен namespace, той не може да го промени. Можем обаче да групираме socket-ите в канали и да променяме каналите, в които се намират в runtime.
192 |
193 | ```javascript
194 | socket.join('a channel');
195 |
196 | …
197 |
198 | socket.leave('a channel');
199 | ```
200 |
201 | ```javascript
202 | io.to('a channel').emit('data');
203 | ```
204 |
205 | ---
206 | class: center,middle
207 | ###VNC имплементацията на Минко е [в джитхюб](https://github.com/FMIjs/js-lectures-2014/tree/master/tasks/08/vnc)
208 |
--------------------------------------------------------------------------------
/lectures/12-d3.markdown:
--------------------------------------------------------------------------------
1 | class: center,middle
2 | #d3.js
3 |
4 | .center[
5 | data driven documents
6 | ]
7 | ---
8 | .center[
9 | #data driven documents
10 | ]
11 | .left-column[
12 | * реактивна библиотека
13 |
14 | * на крактко: съпоставя множества от структурирани данни с SVG множества
15 |
16 | * идва с иструменти като
17 | - подсистема за избиране на елементи
18 | - скали
19 | - механизъм за различаване на разликите при промяна на данните
20 |
21 | * вероятно най-съществената библиотека за работа с уеб графика
22 |
23 | * един от най-важните инструментариуми наред с след jQuery
24 |
25 | ]
26 |
27 | ---
28 |
29 | ```JS
30 |
31 | var dataset = [20, 5, 10, 0, 50];
32 |
33 | d3.select('body')
34 | .selectAll('p') // selection
35 | .data(dataset) // data binding
36 | .enter()
37 | .append('p') // dom manipulation
38 | .attr('class', 'paragraph') // static property
39 | .text(function(d, i) { // dynamic property
40 | return i + ': my value is ' + d;
41 | })
42 | .style('font-size', function(d) {
43 | return (d / 2 + 25) + 'px'; // maps to [25, 50]
44 | });
45 |
46 | ```
47 |
48 | [https://scottcheng.github.io/d3js-101/#/programming-pattern]
49 |
50 | ---
51 |
52 | .center[
53 | #data/enter/exit
54 |
55 | * data - обновяваме данните и в следствие на тях...:
56 |
57 | * enter - новите елементи, които ще цъфнат
58 |
59 | * exit - елементите, които си отиват
60 |
61 |
62 |
63 |
64 | [http://bost.ocks.org/mike/join/]
65 |
66 | ]
67 |
68 | ---
69 |
70 | .center[
71 | #data joins
72 |
73 | * ^^^ така се нарича съпоставянето на елементи и данни
74 |
75 | * вместо да казваме на D3 как да направи нещо му казваме какво искаме
76 |
77 | * тоест -> пишем декларативен код...
78 |
79 | * ...и работим с хипероперации в/у изброими(подредени) мулти-множества
80 |
81 | ```JS
82 | /* add many circles according to data */
83 |
84 | let circle = svg.selectAll("circle")
85 | .data(data)
86 |
87 | circle.exit().remove();
88 |
89 | circle.enter().append("circle")
90 | .attr("r", 2.5);
91 |
92 | circle
93 | .attr("cx", function(d) { return d.x; })
94 | .attr("cy", function(d) { return d.y; });
95 |
96 | ```
97 |
98 | ]
99 |
100 | ---
101 |
102 | .center[
103 | #скали
104 |
105 | * функции, които изобразяват данни в накакъв друг домейн,
106 |
107 | * ...запазвайки пропорциите.
108 |
109 | * промяната на скалите няма да предизвика автоматична промяна на на гафикaта
110 |
111 |
112 | ```JS
113 |
114 | var x = d3.time.scale()
115 | .range([10, 280])
116 |
117 | ```
118 | ]
119 | ---
120 |
121 | .center[
122 |
123 | #range / domain
124 |
125 | * range - обемът на пиксели от екрана, които искаме да ... заангажираме
126 | * ***Внимание - в d3 величните за y са обърнати. нулата е в горната част на екрана.***
127 | * domain - областта на допустимите велични за входните данни
128 |
129 | #видове скали
130 |
131 | * Quantitative Scales - за непрекъснати входни данни като числени велиничи
132 | * Линейни
133 | - d3.scale.linear
134 | - d3.scale.identity
135 | - d3.scale.sqrt
136 | - d3.scale.pow
137 | - d3.scale.log
138 | * Дискретни
139 | - d3.scale.quantize
140 | - d3.scale.qunatile
141 | * Прагови
142 | - d3.scale.threshold
143 |
144 | * Ordinal Scales - за дискретни данни като имена, категории, цветове
145 |
146 | * Time Scales - за времеви области/домени
147 |
148 | ]
149 |
150 | ---
151 |
152 | .center[
153 | #transition
154 | ]
155 | .left-column[
156 | * може да се анимират промените по елементите
157 | * анимацията може да се контролира
158 | ]
159 |
160 | ---
161 |
162 | * A Technical Intro - A Technical Intro
163 | * D3@HN - https://hn.algolia.com/?query=d3
164 | ]
165 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "js2015-slides",
3 | "version": "0.0.1",
4 | "description": "slides for the javascript course at FMI 2015/2016",
5 | "main": "compile.js",
6 | "repository": "https://github.com/FMIjs/js-lectures-2015",
7 | "scripts": {},
8 | "author": "",
9 | "license": "MIT",
10 | "dependencies": {
11 | "cpr": "^1.0.0",
12 | "hogan": "^1.0.2",
13 | "q": "^1.0.1"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tasks/01/README.md:
--------------------------------------------------------------------------------
1 | 1. Напишете функция `fib(n)`, която по зададен параметър `n`, връща `n`-тото число на Фибоначи. **Не с рекурсия.**
2 | 2. Нека an и an-1 са две последователни числа на Фибоначи. При `n -> ∞` an / an-1 -> φ. Напишете функция `phiEstimation(n)`, която по зададен параметър `n`, връща приближение на числото `φ` (the golden ratio).
3 | 3. Напишете функция `reverseWordsOrderInString`, която по зададен низ връща нов низ с думите от оригиналния низ в обратен ред. Пример:
4 |
5 | 'Foo bar baz'
6 | 'baz bar Foo'
7 |
8 | 4. Напишете функция `reverseWordsInString`, която по зададен низ връща нов низ с думите от входния низ с думи в противоположна посока. Пример:
9 |
10 | 'Foo bar baz'
11 | 'ooF rab zab'
12 |
13 | 5. Напишете функция `findNthNumber(n, arr)`, която по зададен параметър `n`, намира `n`-тото по големина число(или `undefined` ако няма толкова) в даден масив `arr`.
14 | - сортирайте масива и намерете `n`-тото най-голямо число.
15 | - използвайте partition от quick sort и рекурсивно подредете масива.
16 |
17 | 6. `median(arr)` - Намерете [медианата](http://bg.wikipedia.org/wiki/%D0%9C%D0%B5%D0%B4%D0%B8%D0%B0%D0%BD%D0%B0_(%D1%81%D1%82%D0%B0%D1%82%D0%B8%D1%81%D1%82%D0%B8%D0%BA%D0%B0)) на несортиран масив с `n` елемента.
18 |
19 | 7. Напишете функция `setBits(m, n, i, j)`, която приема като аргументи числата `m`, `n`, `i`, `j`. Функцията задава битовете на `m` в диапазона `i`, `j` със стойност `n`, т.е.:
20 |
21 | ```text
22 | Вход:
23 | m = 10000010
24 | n = 11010
25 | i = 2
26 | j = 5
27 | Резултат:
28 | 10110110
29 | ```
30 |
31 | Както входните параметри, така и резултатът трябва да бъдат числа в десетична бройна система т.е.:
32 |
33 | ```
34 | setBits(130, 26, 2, 5); //182
35 | ```
36 |
--------------------------------------------------------------------------------
/tasks/02/README.md:
--------------------------------------------------------------------------------
1 | # 02. Функционално Програмиране с Javascript - Задачи
2 |
3 | 1. Направете функция която която приема произволен брой аргументи и връща сумата от квадратите на нечетните аргументи (числа, а не по индекси).
4 |
5 | ```javascript
6 | sumSquaresOfOddArgs(1, 2, 3, 4, 5, 6)
7 | > 35
8 | ```
9 |
10 | 2. Направете функция sum която взима като аргументи 2 числа но при подадено само едно връща нова функция (взимаща 1) която ще сумира с първия аргумент.
11 |
12 | ```javascript
13 | var partialSum = sum(5);
14 | partialSum
15 | > [Function]
16 |
17 | partialSum(10)
18 | > 15
19 |
20 | sum(5)(2)
21 | > 7
22 | ```
23 |
24 | 3. Направете функция curry взимаща друга функция (а) като аргумент и връща а със способност да и се подават частично аргументи
25 |
26 | ```javascript
27 | var curried = curry(function (a, b, c, d) {
28 | return a + b + c;
29 | });
30 |
31 | curried(6, 6);
32 | > [Function]
33 |
34 |
35 | curried(6, 6)(6, 6);
36 | > 18
37 |
38 | ```
39 |
40 | 4. Направете функция sequence която взима като аргументи произволен брой функции и връща нова функция която прилага всички тези функции върху своя аргумент.
41 |
42 | ```javascript
43 | var number = sequence()
44 | var toNumber = sequence(parseFloat, Math.round);
45 |
46 | // първо прилага parseFloat а после Math.round
47 | toNumber('66.7')
48 | > 67
49 | ```
50 |
51 | 5. Направете функция compose която работи като sequence но изпълнява аргументите (функциите) в обратен ред.
52 |
53 | ```javascript
54 | var number = compose(Math.round, parseFloat);
55 | number('66.6')
56 | > 67
57 |
58 | ```
59 |
60 | 6. Направете функция cons (двойка) която взима като аргументи два елемента.cons връща функция която взима като аргумент функция която се изпълнява върху двойката а и b.
61 |
62 | ```javascript
63 | cons(a, b)
64 | > [Function]
65 | ```
66 | 7. Направете функция car(глава) която взима като аргумент вече създадена двойка от cons, прилагайки му функция която връща първия елемент на двойката (а).
67 |
68 | ```javascript
69 | var pair = cons(7, 8);
70 | car(pair)
71 | > 7
72 | ```
73 |
74 | 8. Направете функция cdr(опашка) която взима като аргумент вече създадена двойка(cons) и му прилага функция която връща втория елемент на двойката (b).
75 |
76 | ```javascript
77 | var list = cons(7, cons(8, 9));
78 | var tail = cdr(list)
79 | tail
80 | > [Function]
81 | cdr(tail)
82 | > 9
83 | ```
84 |
85 | 9. Направете функция forEach която взима аргументи списък от двойки и функция която да се изпълни върху всеки елемент от списъка.
86 |
87 | ```javascript
88 | var log = console.log.bind(console);
89 | var list = cons(1, cons(2, cons(3, cons(4, 5))));
90 | forEach(list, log);
91 |
92 | > 1
93 | 2
94 | 3
95 | 4
96 | 5
97 | ```
98 |
99 | 10. Направете функция map която взима като аргументи свързан списък от двойки и функция f.
100 | map връща нов списък като на всеки елемент има приложена функцията f.
101 |
102 | ```javascript
103 | var list = cons(1, cons(2, cons(3, cons(4, 5))));
104 | var mapped = map(list, function (el) {
105 | return el * el;
106 | });
107 |
108 | forEach(mapped, log);
109 | > 1
110 | 4
111 | 9
112 | 16
113 | 25
114 | ```
115 |
116 |
117 | 11. Направете функция filter която взима като аргументи списък от двойки и предикатна функция и връща нов списък само с елементите за които тази предикатна функция връща true.
118 |
119 | ```javascript
120 | var list = cons(1, cons(2, cons(3, cons(4, 5))));
121 | var filtered = filter(list, function (e) {
122 | return e % 2 === 0;
123 | });
124 |
125 | forEach(filtered, log);
126 | > 2
127 | 4
128 | ```
129 |
130 | 12. Направете функция reduce която взима като аргументи списък от двойки, функция на два аргумента и първоначална стойност. Функцията която е като втори аргумент акумулира всички елементи върху началната стойност
131 |
132 | ```javascript
133 | var list = cons(1, cons(2, cons(3, cons(4, 5))));
134 |
135 | var sum = reduce(list, function (accumulation, current) {
136 | return accumulation + current;
137 | }, 0);
138 |
139 | sum
140 | > 15
141 |
142 | var product = reduce(list, function (accum, curr) {
143 | return accum * curr;
144 | }, 1);
145 |
146 | product
147 | > 120
148 | ```
--------------------------------------------------------------------------------
/tasks/02/more-examples/complement.js:
--------------------------------------------------------------------------------
1 | function complement(predicate) {
2 | return function () {
3 | return !predicate.apply(null, arguments)
4 | };
5 | }
6 |
7 | // example usage
8 |
9 | function isCool(person) {
10 | return person.isCool === true;
11 | }
12 |
13 | var isNotCool = complement(isCool);
14 |
15 | var people = [{
16 | name: 'Ivan'
17 | isCool: true
18 | }, {
19 | name: 'Pesho',
20 | isCool: false
21 | }, {
22 | name: 'Dragan',
23 | isCool: false
24 | }];
25 |
26 | var coolPeople = people.filter(isCool);
27 | var notCoolPeople = people.filter(complement(isCool));
--------------------------------------------------------------------------------
/tasks/02/solutions/cons-car-cdr.js:
--------------------------------------------------------------------------------
1 |
2 | function cons(a, b) {
3 | return (executor) => executor(a, b);
4 | }
5 |
6 | function car(c) {
7 | return c((a, b) => a);
8 | }
9 |
10 | function cdr(c) {
11 | return c((a, b) => b);
12 | }
13 |
--------------------------------------------------------------------------------
/tasks/02/solutions/curry.js:
--------------------------------------------------------------------------------
1 | var curry = function(fn) {
2 | return function(){
3 | var outerArgs = [].slice.call(arguments, 0);
4 | if (outerArgs.length < fn.length) {
5 | return function() {
6 | var innerArgs = [].slice.call(arguments, 0);
7 | var totalArgsLength = outerArgs.length + innerArgs.length;
8 | if (totalArgsLength < fn.length) {
9 | return fn.apply(this, outerArgs.concat(innerArgs).concat([].slice.call(arguments, 0)));
10 | }
11 | return fn.apply(this, outerArgs.concat(innerArgs));
12 | }
13 | }
14 | return fn.apply(this, outerArgs);
15 | }
16 | }
17 |
18 | var curry = function (fn) {
19 | var arity = fn.length;
20 | return function g() {
21 | var args = [].slice.call(arguments);
22 | if (args.length >= arity) {
23 | return fn.apply(null, args);
24 | } else {
25 | return function () {
26 | var innerArgs = [].slice.call(arguments);
27 | return g.apply(null, args.concat(innerArgs));
28 | }
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/tasks/02/solutions/map-filt-red-native.js:
--------------------------------------------------------------------------------
1 | /**
2 | * this implementation works on a list with a last element always a list or nothing
3 | * cons(1, cons(2, cons(3, cons(4, cons(5)))))
4 | */
5 |
6 | function map(c, f) {
7 | return typeof c === 'function' && cons(f(car(c)), map(cdr(c), f));
8 | }
9 |
10 | function filter(c, f) {
11 | if (typeof c !== 'function') {
12 | return undefined;
13 | }
14 | if (f(car(c))) {
15 | return cons(car(c), filter(cdr(c), f));
16 | }
17 | return filter(cdr(c), f);
18 | }
19 |
20 | function forEach(c, f) {
21 | if (typeof c === 'function' && car(c)) {
22 | f(car(c));
23 | forEach(cdr(c), f);
24 | }
25 | }
26 |
27 | function reduce(c, fn, init) {
28 | return typeof c === 'function' ? reduce(cdr(c), fn, fn(init, car(c))) : init;
29 | }
--------------------------------------------------------------------------------
/tasks/02/solutions/map-filt-red-with-pair.js:
--------------------------------------------------------------------------------
1 | /**
2 | * this implementation works on a list which can have last element
3 | * different than a list
4 | * cons(1, cons(2, cons(3, cons(4, cons(5, 6)))))
5 | */
6 |
7 | function applyFunction(fn) {
8 | var args = [].slice.call(arguments, 1);
9 | args.forEach(fn);
10 | }
11 |
12 | function isPair(c) {
13 | return typeof c === 'function' && typeof cdr(c) !== 'function' &&
14 | typeof cdr(c) !== 'undefined';
15 | }
16 |
17 | function forEach(c, f) {
18 | if (typeof c === 'function' && typeof (cdr(c)) === 'function') {
19 | f(car(c));
20 | forEach(cdr(c), f);
21 | } else if (isPair(c)) {
22 | applyFunction(f, car(c), cdr(c));
23 | } else {
24 | f(car(c));
25 | }
26 | }
27 |
28 | function print(list) {
29 | forEach(list, function (el) {
30 | console.log(el);
31 | });
32 | };
33 |
34 | function map(c, fn) {
35 | if (typeof c === 'function' && typeof cdr(c) === 'function') {
36 | return cons(fn(car(c)), map(cdr(c), fn));
37 | } else if (isPair(c)) {
38 | return cons(fn(car(c)), fn(cdr(c)));
39 | }
40 | }
41 |
42 | function filter(c, pred) {
43 | if (typeof c !== 'function') {
44 | return undefined;
45 | }
46 | if (isPair(c)) {
47 | var args = [car(c), cdr(c)].filter(pred);
48 | return args.length > 0 ? cons.apply(null, args) : undefined;
49 | }
50 | if (pred(car(c))) {
51 | return cons(car(c), filter(cdr(c), pred));
52 | }
53 | return filter(cdr(c), pred);
54 | }
55 |
56 | function reduce(c, fn, init) {
57 | if (typeof c !== 'function') {
58 | return init;
59 | } else if (isPair(c)) {
60 | return car(c) + cdr(c) + init;
61 | }
62 | return reduce(cdr(c), fn, fn(init, car(c)));
63 | }
--------------------------------------------------------------------------------
/tasks/02/solutions/sequence-and-compose.js:
--------------------------------------------------------------------------------
1 | function sequence() {
2 | var funcs = [].slice.call(arguments);
3 | return function (arg) {
4 | return funcs.reduce(function (accum, fn) {
5 | return fn(accum);
6 | }, arg);
7 | };
8 | }
9 |
10 | function compose() {
11 | var funcs = [].slice.call(arguments);
12 | return function (arg) {
13 | return funcs.reduceRight(function (accum, fn) {
14 | return fn(accum);
15 | }, arg);
16 | };
17 | }
--------------------------------------------------------------------------------
/tasks/02/solutions/sum.js:
--------------------------------------------------------------------------------
1 | function sum(a, b) {
2 | if (typeof b === 'undefined') {
3 | return function (c) {
4 | return a + c;
5 | };
6 | } else {
7 | return a + b;
8 | }
9 | }
10 | var addTen = sum(10);
11 |
12 | addTen(10)
13 | // > 20
--------------------------------------------------------------------------------
/tasks/02/solutions/sumSquaresOfArgs.js:
--------------------------------------------------------------------------------
1 | function sumSquaresOfOddArgs() {
2 | return [].call(arguments)
3 | .filter(function (el) {
4 | return el % 2 === 1;
5 | })
6 | .map(function (el) {
7 | return el * el;
8 | })
9 | .reduce(function (accumulation, current) {
10 | return accumulation + current;
11 | }, 0);
12 | }
13 |
14 |
15 | function mapAndFilter() {
16 | return [].map.call(arguments, function (a) {
17 | return a * a;
18 | }).filter(function (el) {
19 | return el % 2 === 0;
20 | });
21 | }
--------------------------------------------------------------------------------
/tasks/03/README.md:
--------------------------------------------------------------------------------
1 | I. Разширете прототипа на Array с функция myMap, която прилага функция към всеки елемент от масива и връща нов списък с резултата.
2 |
3 | ```javascript
4 | [1,2,3,4,5,6,7,8,9].myMap(function(el){ return el + 10; }); // -> [11,12,13,14,15,16,17,18,19]
5 | ```
6 |
7 | II. Напишете конструктор Vehicle, който взима един аргумент mileage, което е private свойство на обекта. Vehicle има следните методи: getMileage и toString.
8 | getMilage връща стойността private свойството, а toString връща string: This Vehicle Mileage is (mileage e обект с единствено свойство val!)
9 |
10 | Напишете конструктор Car, който взима два аргумента: brand и consumption. Car има 3 метода:
11 | - drive(miles), който прибавя miles към mileage.val
12 | - getBrand(), който връща марката
13 | - getConsumption(), който връща consumption
14 | - toString(), който връща string съдържащ + резултата от извикването на toString на "super" класа.
15 |
16 | При създаването на нов обект от Car mileage.val трябва да бъде 0.
17 |
18 | ```javascript
19 |
20 | var c1 = new Car('honda', 5);
21 | c1.getMileage(); // -> 0
22 | c1.toString(); // -> honda 5 This Vehicle mileage is 0
23 | c1.drive(1000);
24 | c1.getMileage(); // -> 1000
25 | c1.toString(); // -> honda 5 This Vehicle mileage is 1000
26 |
27 | ```
28 | Какво ще се случи, ако mileage не e обект, а е променлива?
29 |
30 | III. Напишете конструктор Point, който взима 2 аргумента - x и y. Point има метод: toString, който връща - x, y. Напишете конструктор Point3D, който наследява
31 | всички свойства на Point и има допълнителен аргумент z. Направете прототипно наследяване муждy Point3D и Point.
32 | Напишете toString метод към прототипа на Point3D, който връща x, y, z като използва Point.toString за да върне x, y.
33 | Напишете метод toArray към прототипа на Point, който връща координатите в списък. За Point3D toArray НЕ трябва бъде имплементиран, но ако бъде извикан той трябва да върне [x, y, z]
34 |
35 | IV. Създайте библиотека FMIjs, която обработва списъци от числа и съдържа :
36 | 1) BinaryHeap - клас с име MinHeap, чийто прототип има за прототип Array.prototype.
37 | 2) heapSort - метод, който връща сортиран списък.
38 |
39 | MinHeap съдържа методи:
40 | - parentIndex
41 | - leftIndex
42 | - rightIndex
43 | - parentElement
44 | - leftElement
45 | - rightElement
46 | - getMinimum
47 | - isEmpty
48 | - insert (използвайте push, за слагане на елемент в списъка. Методът връща heap-а като string)
49 | - bubbleUp
50 | - bubbleDown
51 |
52 | и конструктор, който взима един аргумент - списък. Ако няма подаден списък или е подедено нещо друго да се създава празен heap.
53 |
54 | (При създаване на повече от един heap методите на MinHeap трябва да се създадат само веднъж !)
55 |
56 | Обяснете следния ефект:
57 | ```javascript
58 | var b = new FMIjs.BinaryHeap();
59 | b // -> []
60 | b[0] = 1000;
61 | b // -> []
62 | b[0] // -> 1000
63 | b.insert(5);
64 | b // -> [5]
65 | b[0] // -> 5
66 | ```
67 | ```javascript
68 |
69 | var arr = [1,2,6,9,2,4,9,87,36,21,1];
70 | FMIjs.heapSort(arr); // -> [1,1,2,2,4,6,9,9,21,36,87]
71 |
72 | var b = FMIjs.BinaryHeap();
73 | b.insert(5);
74 | b.insert(10);
75 | b.insert(1);
76 |
77 | b.getMinimum(); //-> 1
78 | b.getMinimum(); //-> 5
79 | b.getMinimum(); //-> 10
80 |
81 | ```
82 |
--------------------------------------------------------------------------------
/tasks/03/homework/README.md:
--------------------------------------------------------------------------------
1 | # Observers
2 |
3 | ## Directory structure
4 |
5 | ```
6 | .
7 | ├── index.js
8 | ├── lib
9 | │ ├── observables
10 | │ │ ├── Observable.js
11 | │ │ └── PostsCollection.js
12 | │ └── observers
13 | │ ├── LogObserver.js
14 | │ ├── MailObserver.js
15 | │ └── Observer.js
16 | ├── logs
17 | ├── package.json
18 | └── public
19 | ├── index.html
20 | └── src
21 | └── main.js
22 | ```
23 |
24 | Run `npm install` in the root of the bootstrap directory in order to install all node dependencies.
25 |
26 | Run `node index.js` in order to start the express application. When you open the URL [http://localhost:3000](http://localhost:3000), you should see HTML page with text input, text area and a button.
27 |
28 | Take a look at the implementation of the constructor function `Observable`.
29 | Review the "interface" `Observer`.
30 |
31 | Extend the constructor function `Observable` in `PostsCollection` and implement the method `addPost`.
32 |
33 | ### Implement the "interface" of `Observer` in `LogObserver` and `MailObserver`.
34 |
35 | When the method update of `LogObserver` is being called it should create a new file with name `Date.now() + '.txt'` and content: "[current date] Title Content".
36 | When the method update of `MailObserver` is being called it should send a new email message with subject the title of the post and content, the post's content.
37 |
38 | Instructions about the required APIs are available in `LogObserver.js` and `MailObserver.js`.
39 |
40 | Inside `index.js` create new instances of the both observers and set the appropriate configuration. Add the observers to a new object, instance of `PostsCollection`.
41 |
42 | If the implementation is correct, when you add new post through the web interface or `addPost` method, the application should create new file in the `logs` directory and email with the title of the article should be sent, to the destination you've set.
43 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/index.js:
--------------------------------------------------------------------------------
1 | /* global require, __dirname, console */
2 |
3 | var MailObserver = require('./lib/observers/MailObserver'),
4 | LogObserver = require('./lib/observers/LogObserver'),
5 | logConfig = { path: __dirname + '/logs' },
6 | mailerConfig = { from: 'Foo Bar ', to: '' },
7 | PostsCollection = require('./lib/observables/PostsCollection'),
8 | posts = new PostsCollection(),
9 | express = require('express'),
10 | bodyParser = require('body-parser'),
11 | app = express();
12 |
13 | // Create and attach observers
14 |
15 | app.use(express.static(__dirname + '/public'));
16 | app.use(bodyParser.json());
17 |
18 | app.post('/post', function (req, res) {
19 | 'use strict';
20 | posts.addPost(req.body.title, req.body.content);
21 | res.status(200);
22 | res.end();
23 | });
24 |
25 | app.listen(3000);
26 |
27 | console.log('Listening on port 3000...');
28 |
29 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/lib/observables/Observable.js:
--------------------------------------------------------------------------------
1 | /* global module */
2 |
3 | function Observable() {
4 | 'use strict';
5 | this.observers = [];
6 | }
7 |
8 | Observable.prototype.addObserver = function (ob) {
9 | 'use strict';
10 | this.observers.push(ob);
11 | };
12 |
13 | Observable.prototype.removeObserver = function (ob) {
14 | 'use strict';
15 | this.observers.splice(this.observers.indexOf(ob), 1);
16 | };
17 |
18 | Observable.prototype.update = function () {
19 | 'use strict';
20 | var args = arguments;
21 | this.observers.forEach(function (ob) {
22 | ob.update.apply(ob, args);
23 | });
24 | };
25 |
26 | module.exports = Observable;
27 |
28 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/lib/observables/PostsCollection.js:
--------------------------------------------------------------------------------
1 | /* global require, module */
2 |
3 | var Observable = require('./Observable');
4 |
5 | // Extend Observable
6 | function PostsCollection() {
7 | 'use strict';
8 | this.posts = [];
9 | }
10 |
11 | PostsCollection.prototype.addPost = function (title, content) {
12 | 'use strict';
13 | // Add post and notify about the change
14 | };
15 |
16 | module.exports = PostsCollection;
17 |
18 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/lib/observers/LogObserver.js:
--------------------------------------------------------------------------------
1 | /* global module, require */
2 |
3 | var Observer = require('./Observer'),
4 | // use fs.writeFileSync(fileName, content)
5 | fs = require('fs');
6 |
7 | function LogObserver(config) {
8 | 'use strict';
9 | this.config = config;
10 | }
11 |
12 | LogObserver.prototype = Object.create(Observer.prototype);
13 |
14 | LogObserver.prototype.update = function (title, data) {
15 | 'use strict';
16 | // Implement the method
17 | };
18 |
19 | module.exports = LogObserver;
20 |
21 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/lib/observers/MailObserver.js:
--------------------------------------------------------------------------------
1 | /* global require, module */
2 |
3 | var Observer = require('./Observer'),
4 | // more information about the API at
5 | // http://www.nodemailer.com/docs/usage-example
6 | mail = require('nodemailer').mail;
7 |
8 | function MailObserver(config) {
9 | 'use strict';
10 | this.config = config;
11 | }
12 |
13 | MailObserver.prototype = Object.create(Observer.prototype);
14 |
15 | MailObserver.prototype.update = function (title, data) {
16 | 'use strict';
17 | // Implement the method
18 | };
19 |
20 | module.exports = MailObserver;
21 |
22 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/lib/observers/Observer.js:
--------------------------------------------------------------------------------
1 | /* global module */
2 |
3 | function Observer() {}
4 |
5 | Observer.prototype.update = function () {
6 | 'use strict';
7 | throw new Error('Not implemented');
8 | };
9 |
10 | module.exports = Observer;
11 |
12 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "observer",
3 | "version": "0.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "mgechev",
10 | "license": "MIT",
11 | "dependencies": {
12 | "express": "~4.4.1",
13 | "nodemailer": "~0.6.5",
14 | "body-parser": "~1.3.0"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/tasks/03/homework/bootstrap/public/src/main.js:
--------------------------------------------------------------------------------
1 | /* global document,
2 | location, XMLHttpRequest, JSON */
3 |
4 | (function () {
5 |
6 | 'use strict';
7 |
8 | function $(id) {
9 | return document.getElementById(id);
10 | }
11 |
12 | function postArticle(title, content) {
13 | var xhr = new XMLHttpRequest(),
14 | post = { title: title, content: content };
15 | xhr.open('post', location.protocol + '//' + location.host + '/post', true);
16 | xhr.setRequestHeader('Content-type', 'application/json');
17 | xhr.send(JSON.stringify(post));
18 | }
19 |
20 | $('send-btn').onclick = function () {
21 | postArticle($('title').value, $('content').value);
22 | };
23 | }());
24 |
--------------------------------------------------------------------------------
/tasks/03/solutions.js:
--------------------------------------------------------------------------------
1 | //1.
2 | Array.prototype.myMap = function(operation){
3 | var result = [];
4 | this.forEach(function(elem){
5 | result.push(operation(elem));
6 | });
7 | return result;
8 | };
9 |
10 | [1,2,3,4,5,6,7,8,9].myMap(function(el){ return el + 10; }); // -> [11,12,13,14,15,16,17,18,19]
11 |
12 |
13 |
14 | //2.
15 | function Point(x, y){
16 | this.x = x;
17 | this.y = y;
18 | }
19 |
20 | Point.prototype.toArray = function(){
21 | var self = this;
22 | var result = [];
23 | Object.keys(this).forEach(function(key){
24 | var prop = self[key];
25 | if(prop instanceof Function) return;
26 | result.push(prop);
27 | });
28 | return result;
29 | };
30 |
31 | function Point3D(x, y, z){
32 | Point.call(this, x, y);
33 | this.z = z;
34 | }
35 |
36 | Point3D.prototype = Object.create(Point.prototype);
37 |
38 | var p = new Point(1,2);
39 | var p1 = new Point3D(1,2,3);
40 | console.log(p.toArray());
41 | console.log(p1.toArray());
42 |
43 |
44 |
45 | //3
46 | function Vehicle(mileage){
47 |
48 | this.getMileage = function(){
49 | return mileage.val;
50 | };
51 |
52 | this.tostring = function(){
53 | return 'This Vehicle mileage is ' + mileage.val;
54 | };
55 |
56 | }
57 |
58 |
59 | function Car(brand, consumption){
60 | var mileage = { val: 0 };
61 | Vehicle.call(this, mileage);
62 | var superToString = this.tostring;
63 |
64 | this.drive = function(miles){
65 | mileage.val += miles;
66 | };
67 |
68 | this.getBrand = function(){
69 | return brand;
70 | };
71 |
72 | this.getConsumption = function(){
73 | return consumption;
74 | };
75 |
76 | this.tostring = function(){
77 | return brand + ' ' + consumption + ' ' + superToString();
78 | };
79 | }
80 |
81 | var c1 = new Car('honda', 5);
82 | console.log(c1.getMileage()); // -> 0
83 | console.log(c1.tostring()); // -> honda 5 This Vehicle mileage is 0
84 | c1.drive(1000);
85 | console.log(c1.getMileage()); // -> 1000
86 | console.log(c1.tostring()); // -> honda 5 This Vehicle mileage is 1000
87 |
88 |
89 |
90 | //4
91 | var FMIjs = (function(){
92 |
93 | var BinaryHeap = function(list){
94 | var self = this;
95 | if(!(list instanceof Array)) return this; //or use Array.isArray(list)
96 | list.forEach(function(el){
97 | self.insert(el);
98 | });
99 | };
100 |
101 | BinaryHeap.prototype = Object.create(Array.prototype);
102 |
103 | BinaryHeap.prototype.parentIndex = function(i){
104 | return i === 0 ? 0 : Math.ceil(i/2) - 1;
105 | };
106 |
107 | BinaryHeap.prototype.leftIndex = function(i){
108 | return (2 * i) + 1;
109 | };
110 |
111 | BinaryHeap.prototype.rightIndex = function(i){
112 | return (2 * i) + 2;
113 | };
114 |
115 | BinaryHeap.prototype.parentElement = function(i){
116 | return this[this.parentIndex(i)];
117 | };
118 |
119 |
120 | BinaryHeap.prototype.leftElement = function(i){
121 | return this[this.leftIndex(i)];
122 | };
123 |
124 | BinaryHeap.prototype.rightElement = function(i){
125 | return this[this.rightIndex(i)];
126 | };
127 |
128 | BinaryHeap.prototype.getMinimum = function(){
129 | var lastIndex = this.length - 1;
130 | this.swap(0, lastIndex);
131 | var result = this.pop();
132 | this.bubbleDown();
133 | return result;
134 | };
135 |
136 | BinaryHeap.prototype.isEmpty = function(){
137 | return this.length === 0;
138 | };
139 |
140 | BinaryHeap.prototype.insert = function(value){
141 | this.push(value);
142 | this.bubbleUp();
143 | return this.toString();
144 | };
145 |
146 | BinaryHeap.prototype.swap = function(firstIndex, secondIndex){
147 | var tmp = this[firstIndex];
148 | this[firstIndex] = this[secondIndex];
149 | this[secondIndex] = tmp;
150 | };
151 |
152 | BinaryHeap.prototype.bubbleUp = function(){
153 | var index = this.length - 1;
154 | while(this.parentElement(index) > this[index]){
155 | var parentIndex = this.parentIndex(index);
156 | this.swap(index, parentIndex);
157 | index = parentIndex;
158 | }
159 | };
160 |
161 | BinaryHeap.prototype.bubbleDown = function(){
162 | var index = 0;
163 | var rightValue = null;
164 | var leftIndex = null;
165 | var currentValue = null;
166 | while(this.leftElement(index)){
167 | leftValue = this.leftElement(index);
168 | rightValue = this.rightElement(index);
169 | currentValue = this[index];
170 | if(currentValue > leftValue || currentValue > rightValue){
171 | if(leftValue > rightValue){
172 | this.swap(this.rightIndex(index), index);
173 | index = this.rightIndex(index);
174 | }else{
175 | this.swap(this.leftIndex(index), index);
176 | index = this.leftIndex(index);
177 | }
178 | }else{
179 | break;
180 | }
181 | }
182 | };
183 |
184 | var heapSort = function(list){
185 | var heap = new BinaryHeap(list);
186 | var result = [];
187 | while(heap.length){
188 | result.push(heap.getMinimum());
189 | }
190 | return result;
191 | };
192 | return {
193 | BinaryHeap: BinaryHeap,
194 | heapSort: heapSort
195 | };
196 | }());
197 |
198 | var arr = [1,2,6,9,2,4,9,87,36,21,1];
199 | console.log(FMIjs.heapSort(arr));
200 |
201 | var b = new FMIjs.BinaryHeap();
202 | b.insert(5);
203 | b.insert(10);
204 | b.insert(1);
205 |
206 | console.log(b.getMinimum()); //-> 1
207 |
--------------------------------------------------------------------------------
/tasks/04/README.md:
--------------------------------------------------------------------------------
1 | # ES2015 Задачи
2 |
3 | ## Async Execution and Promises
4 |
5 | 1. Направете функция която изчаква 1000ms след което записва във файл произволен стринг. След това изчаква още 1000ms и записва в друг файл отново произволен стринг. След още 1000ms изчакване отваря първия файл, отново изчаква 1000ms, отваря втория файл, изчаква отново 1000ms и накрая записва в трети файл конкатениран резултатa от четенето на двата предходни файла.
6 | Използвайте функции [writeFile](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback) и [readFile](https://nodejs.org/api/fs.html#fs_fs_readfile_file_options_callback) и _не използвайте_ Promises.
7 |
8 | 1. Направете предната задача използвайки Promises
9 |
10 | 1. Използвайте примера за httpGet oт първия час и напишете генератор на произволни имена на класове за браузъра. За целта вземете имена на класове от [ClassNamer](http://www.classnamer.com/) - подайте http request към адрес http://www.classnamer.com/index.txt?generator=generic.
11 |
12 | 1. Напишете функция simulateDocs, която ще ви помогне да симулирате, че сте написали документация за кода си. Тя трябва да използва генератора от предната задача, за да получи произволни имена на класове и при последващи извиквания да връща стрингoве:
13 | simulateDocs() -> "ClassName1 extends ClassName2"
14 | simulateDocs() -> "ClassName1 extends ClassName2 extends ClassName3"
15 | ...
16 | ClassName1,..N са върнати от генератора имена и при всяко следващо извикване функцията simulateDocs върнатият стринг е продължение на този от предходното извикване. При повече от 5 извиквания функцията трябва да спре да връща резултат.
17 |
18 |
--------------------------------------------------------------------------------
/tasks/04/solutions/task1.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | function getRandomString() {
4 | return Math.random().toString(36).substring(7);
5 | }
6 |
7 | function handleError(err) {
8 | console.log(err);
9 | }
10 |
11 | function wait1000(err, callback) {
12 | if(err) return handleError(err);
13 | console.log('simulating async operation...');
14 | setTimeout(function(){
15 | callback(null);
16 | }, 1000);
17 | }
18 |
19 | function writeFile(err, data, callback) {
20 | if(err) return handleError(err);
21 | fs.writeFile(data.path, data.string, callback);
22 | }
23 |
24 | function readFile(err, data, callback) {
25 | if(err) return handleError(err);
26 | fs.readFile(data.path, callback);
27 | }
28 |
29 | function doStuff(){
30 | var processPath = process.cwd();
31 | var FILE1_PATH = processPath + '/file1';
32 | var FILE2_PATH = processPath + '/file2';
33 | var FILE3_PATH = processPath + '/file3';
34 |
35 | wait1000(null, function() {
36 | var randString = getRandomString();
37 | writeFile(null, { path: FILE1_PATH, string: randString } , function(err) {
38 | wait1000(err, function(err) {
39 | randString = getRandomString();
40 | writeFile(err, { path: FILE2_PATH, string: randString }, function(err) {
41 | wait1000(err, function(err) {
42 | readFile(err, { path: FILE1_PATH }, function(err, content1) {
43 | wait1000(err, function(err){
44 | readFile(err, { path: FILE2_PATH }, function(err, content2) {
45 | wait1000(err, function(err) {
46 | writeFile(err, { path: FILE3_PATH, string: content1 + content2 }, function(err) {
47 | if(err) return handleError(err);
48 | console.log('This is so ugly ... ');
49 | });
50 | });
51 | });
52 | });
53 | });
54 | });
55 | });
56 | });
57 | });
58 | });
59 | }
60 |
61 | doStuff();
--------------------------------------------------------------------------------
/tasks/04/solutions/task2-es6.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var promisify = require('es6-promisify');
3 | var _readFile = promisify(fs.readFile);
4 | var _writeFile = promisify(fs.writeFile);
5 | var text1;
6 |
7 | function wait(time) {
8 | return new Promise(res => setTimeout(res, time));
9 | }
10 |
11 | wait(1000)
12 | .then(() => _writeFile('hello.txt', 'text of file I', 'utf8'))
13 | .then(() => wait(1000))
14 | .then(() => _writeFile('hello2.txt', 'text of file II', 'utf8'))
15 | .then(() => wait(1000))
16 | .then(() => _readFile('hello.txt'))
17 | .then((txt) => text1 = txt)
18 | .then(() => wait(1000))
19 | .then(() => _readFile('hello2.txt'))
20 | .then((text2) => _writeFile('result.txt', `${text1} ${text2}`, 'utf8'))
21 | .catch((err) => console.log(err));
--------------------------------------------------------------------------------
/tasks/04/solutions/task2.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | //In this solution we can create a function promisify which takes a function as an argument
4 | //and retuns a promise but I will stick to the more generic way.
5 | //PS. On github there are modules that provide this functionality.
6 |
7 | //Because we dont have an async operation here the better solution is to return a resolved Promise
8 | //instead of creating a new promise like readFile and writeFile and resolve it inside.
9 | function getRandomString(data) {
10 | data = data || {}; //if data is undefined or null assign an empry object
11 | data.string = Math.random().toString(36).substring(7);
12 | return Promise.resolve(data);
13 | }
14 |
15 | function handleError(err) {
16 | console.log(err);
17 | }
18 |
19 | function wait1000(data) {
20 | return new Promise(function(resolve, reject){
21 | console.log('simulating async operation...');
22 |
23 | //Its always a good idea to name our functions for debuging purposes.
24 | //This way in the stack trace we can see the function name instad of anonymous function.
25 | setTimeout(function asyncOperation(){
26 | resolve(data);
27 | }, 1000);
28 | });
29 | }
30 |
31 | //I will curry the writeFile and readFile Functions so I can pass the necessary paths when I chain the promises.
32 | function writeFile(path) {
33 | return function writeFilePromise(data) {
34 | return new Promise(function(resolve, reject){
35 | fs.writeFile(path, data.string, function(err){
36 | if(err) return reject(err);
37 | resolve(data);
38 | });
39 | });
40 | };
41 | }
42 |
43 | function concatContents(data){
44 | if(!data.contents) throw new Error('File contents not found!');
45 | data.string = data.contents.join('');
46 | return Promise.resolve(data);
47 | }
48 |
49 | //We will store the contents of the files in an array - contents.
50 | function readFile(path) {
51 | return function readFilePromise(data) {
52 | return new Promise(function(resolve, reject) {
53 | fs.readFile(path, function(err, content) {
54 | if(err) return reject(err);
55 | data = data || {};
56 | if(!data.contents) data.contents = [];
57 | data.contents.push(content);
58 | resolve(data);
59 | });
60 | });
61 | };
62 | }
63 |
64 | function doStuff() {
65 | var processPath = process.cwd();
66 | var FILE1_PATH = processPath + '/file1';
67 | var FILE2_PATH = processPath + '/file2';
68 | var FILE3_PATH = processPath + '/file3';
69 | var data = {
70 | string : '',
71 | contents: []
72 | };
73 |
74 | wait1000(data)
75 | .then(getRandomString) //Then expects a function as arguments .then(onFulfilled, onRejected).
76 | .then(writeFile(FILE1_PATH)) //Because readFile and writeFile are curried we can pass an argument.
77 | .then(wait1000)
78 | .then(getRandomString)
79 | .then(writeFile(FILE2_PATH))
80 | .then(wait1000)
81 | .then(readFile(FILE1_PATH))
82 | .then(wait1000)
83 | .then(readFile(FILE2_PATH))
84 | .then(wait1000)
85 | .then(concatContents)
86 | .then(writeFile(FILE3_PATH))
87 | .catch(handleError) //handleError will catch an error from any of the above functions.
88 | .then(function finalMessage(data) {
89 | console.log('Thats much better...');
90 | });
91 | }
92 |
93 | doStuff();
94 |
--------------------------------------------------------------------------------
/tasks/05/01.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var Writable = require('stream').Writable;
4 |
5 |
6 | class FileReader extends Writable {
7 | constructor() {
8 | super();
9 | this.buffer = [];
10 | console.log(this);
11 | }
12 |
13 | _write()
14 | }
15 |
16 |
17 | let fr = new FileReader();
18 | console.log(fr);
19 |
--------------------------------------------------------------------------------
/tasks/05/README.md:
--------------------------------------------------------------------------------
1 | # ES2015 Задачи
2 |
3 | ## NodeJS Streams, Events, Http
4 |
5 | 1. Да се напише конструктор функция **FileReader**, която има properties:
6 |
7 | ```
8 | lastLine (число)
9 | buffer (масив)
10 | ... и каквито други са ви необходими
11 | ```
12 |
13 | и [разширява](https://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor) [stream.Writable](https://nodejs.org/api/stream.html#stream_class_stream_writable).
14 |
15 | При идване на данни към потока те трябва да се convert-нат към String, който се разцепва спрямо '\n'.
16 | Ако последния елемент на стринга, не е '\n' и все още потокът не е затворен трябва да запомним (в `lastLine`)
17 | последния елемен от масива (не е цял ред). На следващото писане трябва да го конкатенираме с
18 | новополучените данни. Всички останали редове се слагат във вътрешния масив `buffer`. Четенето на нови
19 | данни от страна на потока трябва да става, когато масивът `buffer` е празен.
20 |
21 | **[stream.Readable](https://nodejs.org/api/stream.html#stream_class_stream_readable)** (прочетен файл, мрежов източник, пр.) -----*data*-----> **FileReader** (наследник на stream.Writable, имплементира [write](https://nodejs.org/api/stream.html#stream_writable_write_chunk_encoding_callback))
22 |
23 | FileReader също трябва да има метод `fetchLine`, който извършва асинхронна операция, която трябва да
24 | се реализира със `setTimeout`. Този метод трябва да хвърля евент `next-line` с поредния елемент от
25 | масива `buffer`, а ако той не съществува и все още потокът не е затворен трябва да се прочете нов
26 | chunk от данни и да се повтори същата операция. Ако потокът е затворен значи сме приключили с
27 | четенето на данни и можем да хвърлим евента с `null`.
28 |
29 | Въпрос: Защо трябва `fetchLine` да е асинхронен и да използваме `setTimeout` вместо [`process.nextTick`](https://nodejs.org/api/process.html#process_process_nexttick_callback_arg)?
30 |
31 | ------
32 |
33 | 2. Да се реализира конструктор функция с името **RoundRobin**, която взима като аргументи масив streams от FileReader-и. RoundRobin разширява EventEmitter и има properties:
34 |
35 | ```
36 | streams,
37 | currentLine (масив),
38 | generator
39 | ... и каквито други са ви необходими.
40 | ```
41 |
42 | Обектите създадени с тази функция трябва да имат и метод start, който предизвиква стартирането на генератора.
43 |
44 | Ако имаме n на брой файла трябва към всеки един от тях да има отворен [fs.ReadStream](https://nodejs.org/api/fs.html#fs_class_fs_readstream), който е pipe-нат
45 | към FileReader. Искаме да обикаляме по всеки един от тези FileReader-и и да взимаме по един ред.
46 | След първото преминаване по всички потоци резултатът намиращ се в currentLine (масив с N елемента в случая) трябва да изглежда така:
47 |
48 | ```
49 | ["file1-Line1", "file2-Line1", ... , "fileN-Line1"]
50 | ```
51 |
52 | След създаването на string от вида:
53 |
54 | ```
55 | file1-Line1 file2-Line1 ... fileN-Line1
56 | ```
57 |
58 | искаме да хвърлим евент `new-line` с получения резултат и да продължим нататък.
59 | Процесът спира, когато всички неща в `currentLine` са `null` и трябва да хвърлим евент `finish`.
60 | (Някои файлове може да са по-дълги от други)
61 |
62 | ------
63 |
64 | 3. Да се създаде http сървър работещ на порт, който сме подали при стартирането на скрипта. Когато дойде request искаме да вземем pathname-а (пример http://localhost/file1/file2/file3 - pathname е /file1/file2/file3) и да го разбием на '/'. В зависимост, колко файла има зададени в url-то и дали ги има на файловата система искаме да използваме нещата от задача 1 и 2 за да върнем резултат от вида:
65 |
66 | ```text
67 | file1-Line1 file2-Line1 file3-Line1
68 | file1-Line2 file2-Line2 file3-Line2
69 | ...
70 | ```
71 |
72 | Когато получим request, който иска от сървъра да му върне favicon трябва да върнем 404 със съобщение "File not found".
73 |
--------------------------------------------------------------------------------
/tasks/05/solutions/asyncgen.js:
--------------------------------------------------------------------------------
1 | // runner for async shit
2 | var run = function(generator) {
3 | var gi;
4 | var util = function(asyncFun) {
5 | console.log('use the util to have a reference to thyself in async.');
6 | asyncFun(gi);
7 | }
8 |
9 | gi = generator(util);
10 | gi.next(); // kick/start the generator
11 | }
12 |
13 | // main program
14 | run( function *(util) {
15 |
16 | util(function(main) {
17 | setTimeout(function () {
18 | console.log('first async completed.');
19 | main.next();
20 | });
21 | });
22 | yield;
23 | console.log('do something after first async');
24 | /* do some more shit */
25 |
26 | yield util(function(main) {
27 | setTimeout(function () {
28 | console.log('seocnd async completed.');
29 | main.next();
30 | });
31 | });
32 |
33 | console.log('do something after first async');
34 | /* do some more shit */
35 | })
36 |
37 |
38 |
--------------------------------------------------------------------------------
/tasks/05/solutions/simpleGenerator.js:
--------------------------------------------------------------------------------
1 | function countFive(gen) {
2 | var n = 0;
3 | var nextNumber = function() {
4 | setTimeout(function() {
5 | if(n < 6) {
6 | return iterable.next(n++);//go to yield with n++
7 | }
8 | },0);
9 | return undefined;
10 | };
11 | var iterable = gen(nextNumber);
12 | iterable.next(); //kick start
13 | }
14 |
15 | countFive(function* (nextNumber) {
16 | while(true){
17 | var num = yield nextNumber();
18 | console.log(num);
19 | }
20 | })
--------------------------------------------------------------------------------
/tasks/05/solutions/task1.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 |
3 | //Resource for writable stream: https://nodejs.org/api/stream.html#stream_class_stream_writable
4 | var WritableStream = require('stream').Writable;
5 |
6 | function FileReader() {
7 | WritableStream.call(this);
8 | this.lastDone = null;
9 | this.lastLine = null;
10 | this.buffer = [];
11 | this.hasFinished = false;
12 | var self = this;
13 |
14 | //Add callback to the finish event and when its called set the property hasFinished to true.
15 | //We can use self._writableState.finished instad of doing this but we have to know about it.
16 | this.on('finish', function() {
17 | self.hasFinished = true;
18 | });
19 | }
20 |
21 | util.inherits(FileReader, WritableStream);
22 |
23 | FileReader.prototype.fetchLine = function() {
24 | var self = this;
25 |
26 | //If stream's not finished and we don't have a nextLine we must let some data to be written to the stream
27 | //and try to fetch the line again. We use setTimeout because if we use process.nextTick the callback
28 | //will be executed before the _write is called (its executed before any I/O operations)
29 | //Resource:
30 | //https://nodejs.org/api/process.html#process_process_nexttick_callback_arg
31 | setTimeout(function() {
32 | var nextLine = self.buffer[0];
33 | self.buffer = self.buffer.slice(1); //create new array without the first element
34 | if(!nextLine) {
35 | if(!self.hasFinished) {
36 | //if there is a done callback and stream is not closed
37 | //then exec done so data can be written into the stream
38 | if(self.lastDone && !self.hasFinished) self.lastDone();
39 | self.fetchLine();
40 | } else {
41 | //If stream is closed then send null
42 | self.emit('next-line', null);
43 | }
44 | return; //Don't execute line 46
45 | }
46 | self.emit('next-line', nextLine);
47 | }, 0);
48 | };
49 |
50 | FileReader.prototype._write = function(chunk, encoding, done) {
51 | var chunkString = chunk.toString();
52 |
53 | //Check if lastLine is set. If so append it to the current result.
54 | if(this.lastLine) {
55 | chunkString = this.lastLine + chunkString;
56 | this.lastLine = null;
57 | }
58 |
59 | var chunkLines = chunkString.split('\n'); //Split the lines
60 |
61 | //If the last symbol is not a \n we don't have the full line so we must save the
62 | //last element and append next chunkString to it
63 | if(chunkString.slice(chunkString.length - 1) !== '\n' && this.hasFinished) {
64 | this.lastLine = chunkLines.pop();
65 | }
66 | this.buffer = this.buffer.concat(chunkLines);
67 |
68 | //Keep a reference to the done call so we can execute the function later when the
69 | //buffer array is empty
70 | this.lastDone = done;
71 | };
72 |
73 | module.exports = FileReader;
74 |
--------------------------------------------------------------------------------
/tasks/06/README.md:
--------------------------------------------------------------------------------
1 | # Express.js Server
2 |
3 | * Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications.
4 |
5 | [API Reference](http://expressjs.com/api.html)
6 |
7 |
8 | ## Task 1 - Restful API
9 |
10 | * `GET /all_books` - returns all the books for all the users
11 | * `GET /books/:bookId` - returns the data for a book by a given bookId
12 | * `POST /book` - expects user, author, title, descriptionText and rate in the body data. Creates a new book and returns a bookId, which should be unique for every book!
13 | * `GET /all_users` - returns all the registered users.
14 | * `GET /all_authors` - returns all the authors for the books we have in our library.
15 | * `POST /register` - expects user as an argument. Creates a new user and returns a key for that user. If the user exists just returns a 409 response code.
16 | * `DELETE /book` - expects bookId as an argument. Deletes the bookId if the book exists, otherwise return a 403 response code.
17 |
18 | * `GET /all_books?:someField=:someVal` - A dynamic filter API endpoint which returns all books by a passed fiter properties
19 | ```
20 | /all_books?rate=7
21 | /all_books?title=The%20Great%20Gatsby
22 | ```
23 |
24 | ### Testing
25 | ```
26 | curl -H "Content-Type: application/json" --data '{"user": "billy", "author": "Steinbeck",... }' http://localhost:8080/
27 | ```
28 | ## Task 2 - Async each Function
29 | ### Направете функция forEach, която приема като аргументи:
30 | * масив от елементи
31 | * итератор функция, която приема аргументи
32 | * пореден елемент от масива
33 | * callback фунцкия, която да извика, когато е приключил с обработката на елемента от масива
34 | * callback функция, приемаща единствен аргумент грешка. Тя ще се извика, когато възникне грешка или всички други callbacks са приключили
35 |
36 | Идеята на вашата функция forEach e да осигури паралелна "обработка" на елементи в масива чрез итератор функцията (втория си аргумент)
37 |
38 | Пример за употребата:
39 |
40 | ```javascript
41 | forEach(openFiles, function (file, next) {
42 |
43 | // Асинхронна обработка на елемента от масива
44 | console.log('Processing file ' + file);
45 | fs.readFile(file, function (err, file) {
46 | if (err) {
47 | next(err);
48 | }
49 | console.log(file);
50 | next();
51 | });
52 |
53 | }, function (err) {
54 | // Ако при обработката на елементите се хвърли грешка, тя ще се намира в аргумента err
55 | if (err) {
56 | console.log('A file failed to process', err);
57 | } else {
58 | console.log('All files have been processed successfully');
59 | }
60 | });
61 | ```
--------------------------------------------------------------------------------
/tasks/07/README.md:
--------------------------------------------------------------------------------
1 | # Maze traversal
2 |
3 | 1. Отворете следната връзка http://plnkr.co/edit/HFdwuA1B48eG01Rn1IGY
4 | 2. На базата на API описан в plnkr, в `script.js`, реализирайте:
5 | - `drawMaze(maze, parentNode)` - метод, който рисува лабиринта в елемента `parentNode`.
6 | - `traverseMaze(maze, start, target, strategy)` - метод, който на базата на подадените аргументи открива път в лабиринта и го рисува на екрана.
7 | - `maze` - матрица, която представлява дадения лабиринт.
8 | - `start` - начална точка от вида `[row, col]`.
9 | - `target` - стойност на целта (в зададения като пример масив е 2).
10 | - `strategy` - генератор, който обхожда матрицата.
11 | - Генераторът връща следните стойности за `value`:
12 | - `current` - текущата клетка в лабиринта.
13 | - `neighbors` - съседите на текущата клетка.
14 |
15 |
16 | # Query selector
17 |
18 | 1. Имплементирайте query selector (polyfill за `document.querySelectorAll`). Вашата имплементация трябва да поддържа следните опрерации:
19 | - `*` - селектира всички елементи.
20 | - `tagName` - селектира всички елементи с име `tagName`.
21 | - `#id` - селектира всички елементи с id със стойност `id`.
22 | - `.className` - селектира всички елементи, които имат добавен клас със стойност `className`.
23 | - `parentSelector childSelector` - селектира всички преки и непреки наследници, които удовлетворяват `childSelector`, на всички елементи, които са селектирани от `parentSelector`.
24 | - `parentSelector>childSelector` - селектира всички преки наследници, които удовлетворяват `childSelector`, на всички елементи, които са селектирани от `parentSelector`.
--------------------------------------------------------------------------------
/tasks/08/wsChat/app.js:
--------------------------------------------------------------------------------
1 | var port = 8888,
2 | app = require('express')(),
3 | http = require('http').Server(app),
4 | io = require('socket.io')(http),
5 | counter = 0;
6 |
7 | io.on('connection', function(socket) {
8 | socket.username = socket.handshake.query.username || 'user' + (++counter);
9 | socket.emit('username', socket.username);
10 |
11 | socket.on('message', function(msg) {
12 | console.log('new message from ' + socket.username + ': ' + msg);
13 | this.broadcast.emit('message', { text: msg, username: this.username });
14 | });
15 |
16 | socket.on('disconnect', function() {
17 | console.log('user ' + this.username + ' disconnected');
18 | counter--;
19 | });
20 | });
21 |
22 | http.listen(port, function() {
23 | console.log('listening on ' + port);
24 | });
--------------------------------------------------------------------------------
/tasks/08/wsChat/client.js:
--------------------------------------------------------------------------------
1 | var username = process.argv[2],
2 | query = username ? { query: "username=" + username } : undefined,
3 | socket = require('socket.io-client')('http://localhost:8888', query),
4 | keypress = require('keypress'),
5 | line = '';
6 |
7 | keypress(process.stdin);
8 |
9 | socket.on('connect', function() {
10 | console.log('connected ...');
11 | });
12 |
13 | socket.on('disconnect', function() {
14 | console.log('disconnected');
15 | });
16 |
17 | socket.on('username', function(data){
18 | username = data;
19 | console.log('username is ' + username);
20 | });
21 |
22 | socket.on('message', function(message) {
23 | process.stdout.clearLine();
24 | process.stdout.cursorTo(0);
25 | process.stdout.write(message.username + ': ' + message.text + '\n');
26 | if(line) process.stdout.write(line);
27 | });
28 |
29 | process.stdin.on('keypress', function (ch, key) {
30 | if (key && key.ctrl && key.name == 'c') {
31 | process.exit();
32 | } else if(key && key.name === 'return') {
33 | socket.emit('message', line);
34 | process.stdout.write('\n');
35 | line = '';
36 | } else if(key && key.name === 'backspace') {
37 | process.stdout.clearLine();
38 | process.stdout.cursorTo(0);
39 | line = line.slice(0 , -1);
40 | process.stdout.write(line);
41 | } else {
42 | if(ch === undefined) return;
43 | line += ch;
44 | process.stdout.write(ch);
45 | }
46 | });
47 |
48 | process.stdin.setRawMode(true);
49 | process.stdin.resume();
50 |
--------------------------------------------------------------------------------
/tasks/08/wsChat/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ws-socket",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "= <=>",
10 | "license": "ISC",
11 | "dependencies": {
12 | "express": "^4.13.3",
13 | "keypress": "^0.2.1",
14 | "socket.io": "^1.3.7",
15 | "socket.io-client": "^1.3.7"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tasks/09/README.md:
--------------------------------------------------------------------------------
1 | # Lightweight AngularJS implementation
2 |
3 | In this exercise we are going to create a lightweight implementation of AngularJS.
4 |
5 | Our framework is going to support directives, two-way data-binding, services, controllers...and even more!
6 |
7 | ## Provider
8 |
9 | 0. Create object literal called `Provider`. The provider object should has the following public methods:
10 | * `get` - used for getting services.
11 | * `directive` - used for defining directives.
12 | * `controller` - used for defining controllers.
13 | * `service` - used for defining services.
14 | * `annotate` - used for getting array of the names of the dependencies of given "provider" (i.e. service, controller or directive).
15 | * `invoke` - used for invoking services (i.e. resolving their dependencies and calling the factory method).
16 | * `Provider` should have properties called `DIRECTIVES_SUFFIX` and `CONTROLLERS_SUFFIX` with values "Directive" and "Controller".
17 |
18 | 0. Define property of type object, which is called `_providers`
19 |
20 | 0. Define a property called `_cache`. It should contains a property with value `new Scope` and key `rootScope` (we are going to implement the `Scope` in later section, for now you can comment the statement).
21 |
22 | 0. Define a method called `annotate`, which accepts a function and returns an array of its arguments' names.
23 |
24 | 0. Add method called `_register`. `_register` should accept two arguments - `name` (name of the provider) and `fn` (factory method of the provider). It should add new property of the `_providers` hash map with key the first argument passed to the method and value `fn`..
25 |
26 | 0. Define a method called `get`. It should accept arguments called `name` (name of the provider) and `locals` (hash with local dependencies, the keys in the hash are the names of the dependencies and its values are the actual dependencies). `get` should return service with name `name`, if it is already cached (i.e. property of `this._cache`), otherwise it should call the factory method of the service (`this._provider[name]`) with the method `this.invoke` and cache the result. Do not forget to pass the local dependencies to `this.invoke`.
27 |
28 | 0. Define method called `invoke`. It should accept two arguments - `fn` (factory method) and `locals` (local dependencies). Using `annotate` and `get` resolve all dependencies of the current factory method (`fn`) and invoke the factory method. Return the result of the invocation. Note that the dependencies could be located both in `locals` hash and
29 |
30 | 0. Add methods called `directive`, `controller` and `service`. They should accept two arguments - `name` (name of the provider) and `fn` (factory method). They should call `_register` with appropriate name for the provider (i.e. with special suffix for `directive` and `controller` - `DIRECTIVES_SUFFIX`, `CONTROLLERS_SUFFIX`) and the factory method, which is passed as second argument.
31 |
32 |
33 | ## Scope
34 |
35 | 0. Define a constructor function called `Scope`. It should initialize the properties:
36 | * `$$watchers` - an empty array.
37 | * `$$children` - an empty array.
38 | * `$parent` - it should accept the value of a parameter called `parent` passed to the constructor function.
39 | * `$id` - it should accept the value of a parameter called `id` passed to the constructor function or `0` if the passed parameter is `undefined`.
40 | * `Scope` should have a "static" property called `counter`, with initial value `0`.
41 | 0. Define method called `$eval`. It should **evaluate expressions in the context of the current scope**. The expressions, which would be evaluated are:
42 | * Method invocation (i.e. `foo()`)
43 | * Function invocation, i.e. the value of the expression passed to `$eval` will be a function, which should be invoked in the context of the scope.
44 | * Get property value (i.e. `bar`).
45 |
46 | 0. Add method called `$watch` to the prototype of the `Scope` function. It should accept two arguments `exp` and `fn`. It should add new object to the `$$watchers` array. The properties of the new object should be called `exp`, `fn` and `last`. The first two properties should accept the values of the arguments passed to `$watch`, `last` should be set to be equals to **cloned** (`Utils.clone`) value of the value result from evaluation of the expression (`exp`).
47 |
48 | 0. Add method called `$new` - a factory method for creating new scopes. The created scope should inherit prototypically from `this` and its `$id` should be equals to `Scope.counter + 1` (do not forget to increment `Scope.counter`). Add the created scope to the `$$children` array and return it.
49 |
50 | 0. Define method called `$destroy`. It should remove the current scope from the `$$children` array of its parent.
51 |
52 | 0. Define method called `$digest`. Inside the body of the method a loop should iterate over the watchers (`$$watchers`) until all watchers are "clean" (i.e. their current value is equals to their last value - `Utils.equals`). In the end of the method invocation it should be called recursively for all children of the method. If any of the values of the watchers is found dirty, the `fn` (i.e. the observer associated with the current watcher), should be invoked with arguments:
53 | * the current value of the watcher expression
54 | * the previous value of the watcher expression
55 |
56 |
57 | ## DOMCompiler
58 |
59 | 0. Define an object literal called `DOMCompiler`, which has the following public interface:
60 | * `bootstrap` - method responsible for doing the initial parsing of the DOM tree.
61 | * `compile` - method, which accepts a DOM element and scope and compiles the subtree, which has root the passed element, in the context of the passed scope.
62 |
63 | 0. `bootstrap` accepts a single argument - the root element of your application (for example `document.body`) and should invoke `compile` with the `$rootScope` and the root element of your application.
64 |
65 | 0. `compile`, should apply the `link` function of all directives found on the current element and after that should invoke itself recursively with all children elements of the current element. Each registered directive must have two properties:
66 | * `scope` - a boolean property, which indicates that the current directive requires new scope to be created. **NOTE** that no more than one new scope per directive should be created (you should implement this restriction in the `DOMCompiler`).
67 | * `link` - a link function, which is responsible for encapsulating the directive's logic.
68 |
69 | **NOTE** Do not use third party libraries for the implementation of the `DOMCompiler`. For getting all children of given element use: `el.children`. For getting all attributes of given element use `el.attributes`. The returned collection from `el.attributes` will be of type `NamedNodeMap`, which means that you may need to cast it into an array, if necessary. You can access the name of the attribute by: `attr.name` and its value by `attr.value`.
70 |
71 | ## ngl-bind
72 |
73 | Define a directive called `ngl-bind`. When applied to given DOM element as attribute, it should accept an expression as value. When the value of the expression is being changed it should update the content of the element according to the new value of the expression. As initial value of the DOM element `ngl-bind` should set the initial value of the evaluation of the expression.
74 |
75 | ## ngl-model
76 |
77 | Define a directive called `ngl-model`. When applied to given DOM input element as attribute, it should accept name of property of the scope associated with the directive. When the value of the scope's property is being changed this should reflect on the value of the input, once the value of the input is being changed by user interaction this should reflect on the value of the property (i.e. it should implement two-way data-binding).
78 |
79 | ## ngl-controller
80 |
81 | Define a directive called `ngl-controller`. When applied to given DOM element as attribute, as value it should accept name of controller. The link function of this directive should create new controller with name the value of the attribute. The controller should be invoked with local dependencies hash containing property called `$scope` and value the new scope passed to the directive's link function (i.e. the directive should require creation of new scope).
82 |
83 | ## ngl-repeat
84 |
85 | Create new directive called `ngl-repeat`. When applied to given DOM element as attribute it should accept expression in the format `item in items`, as value. When the scope associated with the directive has collection called `items` the directive should iterate over the items in the collection and reference each item in the collection as `item`. The current item should be accessible via the property `item`. For each item in the collection should be created new scope with property called `item`.
86 |
87 | Example:
88 |
89 | When compiled in the context of the scope:
90 |
91 | ```JavaScript
92 | $scope.items = [1, 2, 3];
93 | ```
94 | the result of the compilation of the following markup:
95 |
96 | ```HTML
97 |