ECMAScript и все-все-все — различия между версиями
м (→Destructuring @@) |
|||
(не показана одна промежуточная версия этого же участника) | |||
Строка 1: | Строка 1: | ||
− | <slideshow title="" style=" | + | <slideshow title="" style="yellnobook" scaled="true" font="Roboto, Segoe UI, cursive" headingmark="@@" centermark="%%" incmark="++" footer=""> |
− | ; subfooter: <span style="font-family: Consolas; background: #f1dd56; padding: 5px; font-style: normal">ECMAScript и все-все-все</span> | + | ; subfooter: <span style="font-family: Consolas; background: #f1dd56; padding: 5px; font-style: normal; color: black">ECMAScript и все-все-все</span> |
</slideshow> | </slideshow> | ||
+ | ; PDF-версия: [[Media:ES6 and everyone.pdf|ES6 and everyone.pdf]] | ||
== ECMAScript — ассемблер будущего,<br /> бэкенд, фронтенд и все-все-все @@ %% == | == ECMAScript — ассемблер будущего,<br /> бэкенд, фронтенд и все-все-все @@ %% == | ||
Строка 44: | Строка 45: | ||
== Скриптота vs типизация @@ %% == | == Скриптота vs типизация @@ %% == | ||
− | + | [[File:Script_kiddies_demotivator.jpeg]] | |
− | == Холивар!!! @@ == | + | === Холивар!!! @@ === |
* Шутки в сторону: тема серьёзная | * Шутки в сторону: тема серьёзная | ||
Строка 55: | Строка 56: | ||
*: {{gray|''но кто на них пишет-то?''}} | *: {{gray|''но кто на них пишет-то?''}} | ||
− | == Все хотят одного @@ == | + | === Все хотят одного @@ === |
* Типизация — не необходимость, как раньше, а лишь один из способов проверки | * Типизация — не необходимость, как раньше, а лишь один из способов проверки | ||
*: что ещё можно проверять? | *: что ещё можно проверять? | ||
− | *: Rust | + | *: Rust: borrow checker |
− | *: функциональщина | + | *: функциональщина: «purity» checker |
+ | *: Java: checked exceptions | ||
* При этом | * При этом | ||
*: auto уже даже в C++ | *: auto уже даже в C++ | ||
*: тайпчекер (частично) уже даже в PHP (+ Hack) | *: тайпчекер (частично) уже даже в PHP (+ Hack) | ||
− | == Проверки — хорошо, но @@ %% == | + | === Проверки — хорошо, но @@ %% === |
− | + | [[Файл:Paranoia.jpg]] | |
== Почему JS? @@ == | == Почему JS? @@ == | ||
Строка 77: | Строка 79: | ||
* Мощное сообщество и развитие | * Мощное сообщество и развитие | ||
* Удобный пакетный менеджер npm | * Удобный пакетный менеджер npm | ||
− | * Есть тайпчекеры: TypeScript, Dart… | + | * Есть тайпчекеры: TypeScript, Flow, Dart… |
== Синтаксис @@ %% == | == Синтаксис @@ %% == | ||
− | == Perl @@ == | + | {{gray|Сравнительный анализ}} |
+ | |||
+ | === Perl @@ === | ||
<code-perl> | <code-perl> | ||
Строка 106: | Строка 110: | ||
* Спецсимволы захватили мир | * Спецсимволы захватили мир | ||
+ | * Репутация «write-only», развитие умерло | ||
− | == PHP @@ == | + | === PHP @@ === |
<code-php> | <code-php> | ||
Строка 126: | Строка 131: | ||
* Что за $$$$$? | * Что за $$$$$? | ||
+ | * Репутация г**нокода | ||
− | == Python @@ == | + | === Python @@ === |
<code-python> | <code-python> | ||
Строка 147: | Строка 153: | ||
* Пробелы меняют смысл?!!!! | * Пробелы меняют смысл?!!!! | ||
− | == Ruby @@ == | + | === Ruby @@ === |
<source lang="ruby"> | <source lang="ruby"> | ||
Строка 171: | Строка 177: | ||
* projects — переменная? Фигвам. Метод без аргументов.) | * projects — переменная? Фигвам. Метод без аргументов.) | ||
+ | * чем он лучше хотя бы питона? | ||
− | == Go @@ == | + | === Go @@ === |
<source lang="go"> | <source lang="go"> | ||
Строка 197: | Строка 204: | ||
* Что за смайлики <tt><nowiki>:= <- & *</nowiki></tt>? Где мои скобочки? | * Что за смайлики <tt><nowiki>:= <- & *</nowiki></tt>? Где мои скобочки? | ||
− | == Erlang @@ == | + | === Erlang @@ === |
<source lang="erlang"> | <source lang="erlang"> | ||
Строка 225: | Строка 232: | ||
* Ой-ой-ой… | * Ой-ой-ой… | ||
− | * Классов нет, есть процессы. Но сетевой, | + | * Специфичен. Классов нет, есть процессы. Но сетевой, да! |
− | == OCaml O_O @@ == | + | === OCaml O_O @@ === |
<source lang="ocaml"> | <source lang="ocaml"> | ||
Строка 257: | Строка 264: | ||
</source> | </source> | ||
+ | * «вроде что-то древнее» | ||
* ПОЛИЗ?!!!! | * ПОЛИЗ?!!!! | ||
− | == JS @@ == | + | === Lua @@ === |
+ | |||
+ | <source lang="lua"> | ||
+ | function Encoder.put(self, chunk) | ||
+ | if self.buffersize < 2 then | ||
+ | coroutine.yield(chunk) | ||
+ | else | ||
+ | if #self.buffer + #chunk > self.buffersize then | ||
+ | local written = 0 | ||
+ | local fbuffer = self.buffersize - #self.buffer | ||
+ | |||
+ | coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer)) | ||
+ | written = fbuffer | ||
+ | |||
+ | while #chunk - written > self.buffersize do | ||
+ | fbuffer = written + self.buffersize | ||
+ | coroutine.yield(chunk:sub(written + 1, fbuffer)) | ||
+ | written = fbuffer | ||
+ | end | ||
+ | |||
+ | self.buffer = chunk:sub(written + 1) | ||
+ | else | ||
+ | self.buffer = self.buffer .. chunk | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | </source> | ||
+ | |||
+ | * Для полноты картины | ||
+ | |||
+ | === JS @@ === | ||
<source lang="javascript"> | <source lang="javascript"> | ||
Строка 292: | Строка 330: | ||
</source> | </source> | ||
− | == Синтаксис JS @@ %% == | + | === Синтаксис JS @@ %% === |
Имхо вполне нейтральненько. | Имхо вполне нейтральненько. | ||
Строка 304: | Строка 342: | ||
* 2008+: шустрота и современный период | * 2008+: шустрота и современный период | ||
− | == Дремучий период @@ == | + | === Дремучий период @@ === |
1995—1997 | 1995—1997 | ||
Строка 320: | Строка 358: | ||
* pre-AJAX: JSONP, невидимый iframe | * pre-AJAX: JSONP, невидимый iframe | ||
− | == Появление AJAX @@ == | + | === Появление AJAX @@ === |
2004 | 2004 | ||
Строка 331: | Строка 369: | ||
* 2006 — jQuery | * 2006 — jQuery | ||
− | == Современное развитие @@ == | + | === Современное развитие @@ === |
* 2008 — Google V8 | * 2008 — Google V8 | ||
Строка 339: | Строка 377: | ||
* 2012 — Angular 1.0 | * 2012 — Angular 1.0 | ||
* 2013 — React | * 2013 — React | ||
+ | * {{mag|Где-то тут же изоморфность}} | ||
* 2015 — ES6 принят | * 2015 — ES6 принят | ||
Строка 344: | Строка 383: | ||
На ЛОРе до сих пор шутят, что «Java тормозит», а что ж тогда JS? | На ЛОРе до сих пор шутят, что «Java тормозит», а что ж тогда JS? | ||
− | * А ничего — он быстрый. | + | * А ничего — и Java быстрая, и он быстрый. {{gray|Мамонты видать шутят}} |
+ | *: {{mag|из скриптоты node.js быстрее всех}} | ||
* Но он же интерпретируемый? | * Но он же интерпретируемый? | ||
*: НЕТ! '''Интерпретируемых''' языков уже вообще нет. | *: НЕТ! '''Интерпретируемых''' языков уже вообще нет. | ||
*: {{gray|Ну, разве что bash…}} | *: {{gray|Ну, разве что bash…}} | ||
− | == Вычислительный [https://blog.famzah.net/2016/02/09/cpp-vs-python-vs-perl-vs-php-performance-benchmark-2016/ бенчмарк] @@ == | + | === Вычислительный [https://blog.famzah.net/2016/02/09/cpp-vs-python-vs-perl-vs-php-performance-benchmark-2016/ бенчмарк] @@ === |
− | * C++ (g++ | + | * https://github.com/famzah/langs-performance, время на 10 итераций (i386) |
− | * java8 = | + | * C++ (g++ 6.1.1) -O2 = {{green|0.92s}} |
− | * PyPy (tracing jit) = 1. | + | * java8 = {{green|0.92s}}; java6 = {{green|1s}} {{gray|''(ручные массивы)''}} |
− | * '''node.js = | + | * PyPy (tracing jit) = 1.25s |
− | + | * Rust 1.12 = {{green|0.85s}}! Go 1.7 = 1.14s | |
+ | * '''node.js 4.6 = 1.35s''' + [https://blog.famzah.net/2016/09/10/cpp-vs-python-vs-php-vs-java-vs-others-performance-benchmark-2016-q3/#comment-21850 баг]; nodejs 0.10 = 2.6s | ||
* PHP 7 = 6.7s | * PHP 7 = 6.7s | ||
− | * Python 3.5 = | + | * Ruby 2.3 = 14.3s, Python 3.5 = 17s, 2.7 = 20s |
− | * Perl = | + | * Perl = 24s |
− | * '''PHP 5.6 = | + | * '''PHP 5.6 = 42.5s''' :))))) {{mag|ахаха, прекрати}} |
− | == Почему V8 такой быстрый? @@ == | + | ==== Разница между 64 и 32 бит: i386 @@ ==== |
+ | |||
+ | <plot> | ||
+ | binwidth=0.5 | ||
+ | set yrange [0:13] | ||
+ | set xrange [0:55] | ||
+ | set ytics ('Rust 1.11' 1, 'C++' 2, 'Java 8' 3, 'PyPy' 4, 'Go' 5, 'node.js' 6, 'PHP 7' 7, 'Ruby 2.3' 8, 'Python 3.5' 9, 'Python 2.7' 10, 'Perl' 11, 'PHP 5.6' 12) | ||
+ | set grid xtics | ||
+ | set mxtics 1 | ||
+ | set style fill solid 1.0 noborder | ||
+ | set boxwidth 0.7 relative | ||
+ | plot 'lang.dat' using 2:1:(0.0):2:(($1)-(binwidth/2.0)): (($1)+(binwidth/2.0)) with boxxyerrorbars title 'x = Быстрее PHP 5.6 в x раз' | ||
+ | DATASET lang | ||
+ | 1.0 49.9 | ||
+ | 2.0 46.08 | ||
+ | 3.0 46.38 | ||
+ | 4.0 34.98 | ||
+ | 5.0 37.36 | ||
+ | 6.0 31.77 | ||
+ | 7.0 6.3 | ||
+ | 8.0 2.98 | ||
+ | 9.0 2.49 | ||
+ | 10.0 2.12 | ||
+ | 11.0 1.76 | ||
+ | 12.0 1.00 | ||
+ | ENDDATASET | ||
+ | </plot> | ||
+ | |||
+ | ==== Разница между 64 и 32 бит: amd64 @@ ==== | ||
+ | |||
+ | <plot> | ||
+ | binwidth=0.5 | ||
+ | set yrange [0:13] | ||
+ | set xrange [0:55] | ||
+ | set ytics ('Rust 1.12' 1, 'C++' 2, 'Java 8' 3, 'PyPy' 4, 'Go' 5, 'node.js' 6, 'PHP 7' 7, 'Ruby 2.3' 8, 'Python 3.5' 9, 'Python 2.7' 10, 'Perl' 11, 'PHP 5.6' 12) | ||
+ | set grid xtics | ||
+ | set mxtics 1 | ||
+ | set style fill solid 1.0 noborder | ||
+ | set boxwidth 0.7 relative | ||
+ | plot 'lang.dat' using 2:1:(0.0):2:(($1)-(binwidth/2.0)): (($1)+(binwidth/2.0)) with boxxyerrorbars title 'x = Быстрее PHP 5.6 в x раз' | ||
+ | DATASET lang | ||
+ | 1.0 50.95 | ||
+ | 2.0 47.88 | ||
+ | 3.0 43.53 | ||
+ | 4.0 29.54 | ||
+ | 5.0 28.65 | ||
+ | 6.0 27.18 | ||
+ | 7.0 8.53 | ||
+ | 8.0 4.62 | ||
+ | 9.0 3.18 | ||
+ | 10.0 2.59 | ||
+ | 11.0 2.22 | ||
+ | 12.0 1.00 | ||
+ | ENDDATASET | ||
+ | </plot> | ||
+ | |||
+ | === Почему V8 такой быстрый? @@ === | ||
Потому, что 4-слойный JIT! | Потому, что 4-слойный JIT! | ||
Строка 371: | Строка 468: | ||
* 4 слой — [https://webkit.org/blog/3362/introducing-the-webkit-ftl-jit/ FTL JIT] (<s>LLVM</s> [https://webkit.org/blog/5852/introducing-the-b3-jit-compiler/ B3]) | * 4 слой — [https://webkit.org/blog/3362/introducing-the-webkit-ftl-jit/ FTL JIT] (<s>LLVM</s> [https://webkit.org/blog/5852/introducing-the-b3-jit-compiler/ B3]) | ||
− | https://webkit.org/blog-files/ftl-jit/four_tier_performance.png | + | [[File:FTL_JIT_performance.png]] [https://webkit.org/blog-files/ftl-jit/four_tier_performance.png] |
− | == Ключевые слова о том, как это всё устроено @@ == | + | === Ключевые слова о том, как это всё устроено @@ === |
* Какой бывает JIT? | * Какой бывает JIT? | ||
Строка 382: | Строка 479: | ||
* OSR (On-Stack Replace) | * OSR (On-Stack Replace) | ||
− | == Отступление: PyPy @@ %% == | + | === Отступление: PyPy @@ %% === |
Трассирующий JIT-компилятор для Python, очень медленный | Трассирующий JIT-компилятор для Python, очень медленный | ||
Строка 388: | Строка 485: | ||
Рисует множество Мандельброта при сборке | Рисует множество Мандельброта при сборке | ||
− | + | [[File:PyPy_mandel.png]] | |
− | == LLVM @@ == | + | === LLVM @@ === |
LLVM (http://llvm.org), ранее «Low Level Virtual Machine» | LLVM (http://llvm.org), ранее «Low Level Virtual Machine» | ||
Строка 401: | Строка 498: | ||
* {{gray|На LLVM сделаны компилятор шейдеров Radeon и OpenCL}} | * {{gray|На LLVM сделаны компилятор шейдеров Radeon и OpenCL}} | ||
− | + | [[File:LLVMCompiler1.png]] | |
{{----}} | {{----}} | ||
− | == SSA @@ == | + | === SSA @@ === |
Пример LLVM IR: | Пример LLVM IR: | ||
Строка 427: | Строка 524: | ||
</pre> | </pre> | ||
− | == V8 JIT @@ == | + | === V8 JIT @@ === |
* Первый раз функции запускаются в LLInt | * Первый раз функции запускаются в LLInt | ||
Строка 440: | Строка 537: | ||
*: {{green|→ OSR в LLVM/B3 JIT}} | *: {{green|→ OSR в LLVM/B3 JIT}} | ||
− | == | + | === Мало? @@ === |
+ | |||
+ | [http://asmjs.org/ asm.js] ([http://kripken.github.io/mloc_emscripten_talk/ презентация]) | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function strlen(ptr) { // calculate length of C string | ||
+ | ptr = ptr|0; | ||
+ | var curr = 0; | ||
+ | curr = ptr; | ||
+ | while (MEM8[curr]|0 != 0) { | ||
+ | curr = (curr + 1)|0; | ||
+ | } | ||
+ | return (curr - ptr)|0; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | [[File:asmjs-bench.png|600px]] | ||
+ | |||
+ | === Производительность — итого @@ === | ||
* Итого, V8 — «смешанный» JIT | * Итого, V8 — «смешанный» JIT | ||
Строка 446: | Строка 561: | ||
* Чакра Наделлы тоже очень похожа на V8 | * Чакра Наделлы тоже очень похожа на V8 | ||
* Постоянная битва :) https://arewefastyet.com/ | * Постоянная битва :) https://arewefastyet.com/ | ||
− | * {{red|Но язык | + | * {{red|Но язык — ещё не всё!}} Ещё есть: |
*: В браузере — {{green|DOM}} (медленный в старых Firefox при формально быстром JS) | *: В браузере — {{green|DOM}} (медленный в старых Firefox при формально быстром JS) | ||
*: На сервере — {{green|ввод/вывод}} | *: На сервере — {{green|ввод/вывод}} | ||
Строка 461: | Строка 576: | ||
* ⇒ что же делать?.. | * ⇒ что же делать?.. | ||
− | == Событийные машины! @@ %% == | + | === Событийные машины! @@ %% === |
{{gray|("event loop")}} | {{gray|("event loop")}} | ||
− | == Как вообще обрабатываются соединения клиентов? @@ == | + | === Как вообще обрабатываются соединения клиентов? @@ === |
{{green|Все писали сетевой сервер на C? :)}} | {{green|Все писали сетевой сервер на C? :)}} | ||
Строка 478: | Строка 593: | ||
*: ''«Проблема C10K» {{gray|(обработать 10000 соединений на 1 сервере)}}'' | *: ''«Проблема C10K» {{gray|(обработать 10000 соединений на 1 сервере)}}'' | ||
− | == Событийная машина @@ == | + | === Событийная машина @@ === |
{{green|Неблокирующий ввод/вывод:}} | {{green|Неблокирующий ввод/вывод:}} | ||
Строка 489: | Строка 604: | ||
* '''всё в один поток''' {{gray|(или в несколько по числу ядер CPU)}} | * '''всё в один поток''' {{gray|(или в несколько по числу ядер CPU)}} | ||
− | == Так работает Nginx… @@ %% == | + | === Так работает Nginx… @@ %% === |
…и весь современный Web (фронтенды) | …и весь современный Web (фронтенды) | ||
Строка 497: | Строка 612: | ||
{{gray|(плюс zero-copy и раздача статики через sendfile())}} | {{gray|(плюс zero-copy и раздача статики через sendfile())}} | ||
− | == А если пойти дальше? @@ == | + | === А если пойти дальше? @@ === |
Кроме HTTP-запросов клиентов ещё есть: | Кроме HTTP-запросов клиентов ещё есть: | ||
Строка 510: | Строка 625: | ||
{{mag|⇒ Это и будет «событийная машина»}} | {{mag|⇒ Это и будет «событийная машина»}} | ||
− | == Событийные машины @@ == | + | === Событийные машины @@ === |
Почти везде опциональная сущность: | Почти везде опциональная сущность: | ||
Строка 521: | Строка 636: | ||
* PHP: [https://github.com/kakserpom/phpdaemon kak.serpom.po.yaitsam/phpdaemon] | * PHP: [https://github.com/kakserpom/phpdaemon kak.serpom.po.yaitsam/phpdaemon] | ||
*: {{gray|почти никто не использует}} | *: {{gray|почти никто не использует}} | ||
+ | * Go: goroutine, {{red|но не совсем}} | ||
+ | * Lua+nginx: ngx_lua/cosocket | ||
* Механизмы разные в разных ОС ⇒ '''libevent''', '''libev''' | * Механизмы разные в разных ОС ⇒ '''libevent''', '''libev''' | ||
− | == Событийная машина node.js @@ == | + | === Событийная машина node.js @@ === |
* Отличительная черта: '''вообще нет блокирующего I/O''' | * Отличительная черта: '''вообще нет блокирующего I/O''' | ||
Строка 529: | Строка 646: | ||
* Реально работает: [https://habrahabr.ru/post/123154/ тест 1M соединений на 1 сервере] | * Реально работает: [https://habrahabr.ru/post/123154/ тест 1M соединений на 1 сервере] | ||
* Кроме {{green|node.js}} такое есть только в {{mag|Erlang}} | * Кроме {{green|node.js}} такое есть только в {{mag|Erlang}} | ||
− | *: и, | + | *: 1M соединений переваривает даже успешнее — [https://blog.whatsapp.com/196/1-million-is-so-2011 на продакшне у WhatsApp] |
− | *: {{gray|но... | + | *: и gc быстрый (кучи отдельные) |
+ | *: {{gray|но… erlang. свои минусы и [https://habrahabr.ru/post/183150/ начинает хотеться монад]}} | ||
+ | * [http://blog.virtan.com/2012/07/million-rps-battle.html Million RPS Battle] | ||
+ | |||
+ | === Можно писать и на префорке @@ %% === | ||
+ | |||
+ | Но… | ||
+ | |||
+ | <div style="background: #ff0080; color: white; font-family: Consolas, Droid Sans, monospace; padding: 10px; display: inline-block; text-align: center !important"> | ||
+ | «Хорошо быть девочкой в розовом пальто,<br /> | ||
+ | можно и не девочкой, но уже не то» | ||
+ | </div> | ||
+ | |||
+ | == Обзор языка @@ %% == | ||
+ | |||
+ | {{gray|и приколы}} | ||
+ | |||
+ | === Типы данных @@ === | ||
+ | |||
+ | Типы данных (что может вернуть typeof): | ||
+ | * '''null''' {{gray|(не NULL, всё регистрозависимо)}} | ||
+ | * '''undefined''' {{gray|("не определено")}} | ||
+ | * '''number''': <tt><nowiki>1, −100500.05, NaN</nowiki></tt> | ||
+ | * '''boolean''': <tt><nowiki>true, false</nowiki></tt> | ||
+ | * '''string''': <tt><nowiki>"hello1", 'hello2'</nowiki></tt> {{gray|(всегда Unicode)}} | ||
+ | * {{gray|'''symbol''' — для «скрытых полей» (ES6)}} | ||
+ | * всё остальное — '''object''' | ||
+ | ** хеш — объект дефолтного типа: <tt><nowiki>{ key: "value" }</nowiki></tt> | ||
+ | ** массив — разновидность объекта (класс Array): <tt style="white-space: nowrap"><nowiki>[ "value1", 2, 3, "value3" ]</nowiki></tt> | ||
+ | ** функции — тоже объекты, их typeof = '''function''' | ||
+ | |||
+ | === Переменные @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | var a = 123; | ||
+ | </source> | ||
+ | |||
+ | Новый синтаксис ES6: let/const. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | let a = 123; | ||
+ | const b = 'hello'; | ||
+ | </source> | ||
+ | |||
+ | * '''var''' локальны в рамках функции или Global Scope (в браузере — window) | ||
+ | * '''let''' и '''const''' локальны, как и положено, в рамках блока | ||
+ | |||
+ | === Функции @@ === | ||
+ | |||
+ | Функции — объекты первого класса в JS. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function f(arg) | ||
+ | { | ||
+ | return arg; | ||
+ | } | ||
+ | var f = function(a) | ||
+ | { | ||
+ | return a*a; | ||
+ | }; | ||
+ | </source> | ||
+ | |||
+ | Новый синтаксис ES6: arrow functions | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | var f = a => a*a; | ||
+ | var g = (a, b) => a*b; | ||
+ | </source> | ||
+ | |||
+ | === Замыкания @@ === | ||
+ | |||
+ | Функции в JS являются замыканиями («замыкаются» на текущую область видимости): | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function a(arg) | ||
+ | { | ||
+ | var sum = 0; | ||
+ | var f = function(x) { sum += x; }; | ||
+ | var g = function() { return sum; }; | ||
+ | return [ f, g ]; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | Вызывать функцию можно с любым числом аргументов, пропущенные будут undefined, лишние — в arguments. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function a() | ||
+ | { | ||
+ | console.log(arguments[1]); // "abc" | ||
+ | } | ||
+ | a(1, "abc", 3, 4); | ||
+ | </source> | ||
+ | |||
+ | === Прототипы (голый/старый JS) @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function TreeGrid() | ||
+ | { | ||
+ | // конструктор. тут есть this | ||
+ | } | ||
+ | TreeGrid.prototype.method = function() | ||
+ | { | ||
+ | // метод класса. тут есть this | ||
+ | console.log(this); | ||
+ | } | ||
+ | |||
+ | var obj = new TreeGrid(); | ||
+ | obj.method(); | ||
+ | </source> | ||
+ | |||
+ | === Прототипы @@ === | ||
+ | |||
+ | {{gray|(Очень простая альтернатива классам)}} | ||
+ | |||
+ | * {{blue|''Объекты «создаются из функций»''}} | ||
+ | * У каждого объекта есть прототип | ||
+ | * Прототип — тоже объект, в нём функции и «свойства по умолчанию» | ||
+ | * У прототипа может быть свой прототип ⇒ наследование | ||
+ | * {{mag|object.__proto__}} — прототип этого объекта (класс, по сути) | ||
+ | *: {{gray|невидим в IE <= 8; можно object.constructor.prototype}} | ||
+ | * {{mag|function.prototype}} — прототип, создаваемый этой функцией как конструктором | ||
+ | |||
+ | === Наследование (голый JS) @@ === | ||
+ | |||
+ | Для наследования в {{mag|prototype}} нужно присвоить объект, {{mag|__proto__}} которого ссылается на базовый класс. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function CalendarGrid() | ||
+ | { | ||
+ | TreeGrid.call(this, arguments); // "super()" | ||
+ | } | ||
+ | |||
+ | CalendarGrid.prototype = Object.create(TreeGrid.prototype); | ||
+ | CalendarGrid.prototype.constructor = CalendarGrid; | ||
+ | </source> | ||
+ | |||
+ | === this @@ === | ||
+ | |||
+ | * this передаётся отдельно, как «контекст вызова» | ||
+ | * this можно «подменить» через '''function.apply(obj, arguments)''' / '''function.call(obj, arg1, arg2, …)''' | ||
+ | * обычные функции/замыкания '''не помнят this''' (!) | ||
+ | * arrow functions '''помнят''' this | ||
+ | * this можно «запомнить» через function.bind() {{gray|(IE9+ / FF4+)}} | ||
+ | *: также через function.bind() можно сделать '''карринг''' | ||
+ | *: {{gray|(ну или явно создав замыкание)}} | ||
+ | |||
+ | === this — примеры @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | var obj = new TreeGrid(); | ||
+ | obj.method(); // this = obj | ||
+ | |||
+ | var f = obj.method; | ||
+ | f(); // this = window | ||
+ | |||
+ | f.apply(obj); // this снова = obj | ||
+ | |||
+ | f = obj.method.bind(obj); | ||
+ | f(); // f привязана к obj (this = obj) | ||
+ | |||
+ | f = obj.method.bind(obj, "hello"); // карринг | ||
+ | f("arg2"); // эквивалентно obj.method("hello", "arg2"); | ||
+ | |||
+ | f = obj.method; | ||
+ | f = function() { return f.apply(obj, arguments); } | ||
+ | f(); // то же самое вручную | ||
+ | </source> | ||
+ | |||
+ | === this и замыкания @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | TreeGrid.prototype.method = (arg1, arg2) => | ||
+ | { | ||
+ | // Так делать нельзя, this всегда = контексту, в котором описан метод (обычно window) | ||
+ | console.log(this); | ||
+ | }; | ||
+ | |||
+ | TreeGrid.prototype.setListener = function() | ||
+ | { | ||
+ | // Так делать нельзя, this будет = контексту вызова (кликнутому HTML-элементу) | ||
+ | this.table.addEventListener('click', this.handleClick); | ||
+ | // И так тоже делать нельзя | ||
+ | this.table.addEventListener('click', function(event) | ||
+ | { | ||
+ | this.handleClick(event); | ||
+ | }); | ||
+ | // А вот так можно (оп-па!) | ||
+ | this.table.addEventListener('click', (event) => this.handleClick(event)); | ||
+ | // Ну и вот так тоже можно | ||
+ | var self = this; | ||
+ | this.table.addEventListener('click', function(event) { self.handleClick(event); }); | ||
+ | }; | ||
+ | </source> | ||
+ | |||
+ | === Стандартные конструкции @@ === | ||
+ | |||
+ | * <tt><nowiki>if () {} else if () {} else {}</nowiki></tt> (тело можно однострочное) | ||
+ | * C-подобный for: <tt><nowiki>for (var i = 0; i < 100; i++) {}</nowiki></tt> | ||
+ | * <tt><nowiki>while () {}</nowiki></tt> | ||
+ | * <tt><nowiki>do {} while ()</nowiki></tt> | ||
+ | * break, continue | ||
+ | * C-подобный switch: <tt><nowiki>switch (x) { case 1: case 2: break; default: alert('x'); }</nowiki></tt> | ||
+ | * <tt><nowiki>try { throw 'x'; } catch(e) {} finally {}</nowiki></tt> | ||
+ | |||
+ | === Интересные конструкции @@ === | ||
+ | |||
+ | * a == b (мягкое сравнение) и a === b (точное сравнение) | ||
+ | *: <tt><nowiki>"" == false, 0 == false, "1" == true, 1 == true, "5" == 5, null == undefined</nowiki></tt> | ||
+ | * regexp literals: <tt><nowiki>var re = /<html[^<>]*>/i;</nowiki></tt> | ||
+ | * break label, continue label | ||
+ | * Цикл по {{blue|ключам}}: <tt><nowiki>for (var i </nowiki>{{blue|in}}<nowiki> obj) {}</nowiki></tt> | ||
+ | * Цикл по {{green|значениям (ES6)}}: <tt><nowiki>for (var i </nowiki>{{green|of}}<nowiki> obj) {}</nowiki></tt> | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function a(a1, a2) | ||
+ | { | ||
+ | label: | ||
+ | for (var i of a1) | ||
+ | for (var j of a2) | ||
+ | if (!a2[j]) | ||
+ | continue label; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === Приколы: приведение типов @@ === | ||
+ | |||
+ | Приколов в JS немного, но они есть. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | // "+" переопределён для строк | ||
+ | console.log("100"+1 == "1001"); | ||
+ | console.log("100"-1 == 99); // можно приводить к числу через a-0 или 1*a | ||
+ | |||
+ | // в ключах хеша true, false, null и undefined превращаются | ||
+ | // в строки "true", "false", "null" и "undefined" | ||
+ | // имхо, в PHP (true="1", false="", null="") сделано логичней | ||
+ | var a = {}; | ||
+ | a[false] = 123; | ||
+ | console.log(a["false"] == 123); | ||
+ | </source> | ||
+ | |||
+ | === Приколы: var @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | // var локальны в функциях! | ||
+ | function f() | ||
+ | { | ||
+ | for (var i = 0; i < 100; i++) | ||
+ | { | ||
+ | // через 0.5сек 100 раз напечатает "100" | ||
+ | setTimeout(function() { console.log(i); }, 500); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // и определяются как будто в начале! | ||
+ | var a = 42; | ||
+ | function foo() { alert(typeof a); var a = 10; } | ||
+ | foo(); // --> не 42, а undefined! | ||
+ | </source> | ||
+ | |||
+ | === Приколы: неоднозначный разбор @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | // эта функция, увы, вернёт undefined ({} станет блоком, а key: меткой) | ||
+ | // хеши начинать строго с той же строки! | ||
+ | function fn() | ||
+ | { | ||
+ | return | ||
+ | { | ||
+ | key: "value" | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | // (function() {})() - определение + вызов функции, типично, чтобы не засорять контекст | ||
+ | // точки с запятой опциональны, но лучше их ставить. иначе: | ||
+ | A.prototype.m = function() | ||
+ | { | ||
+ | } /* вот тут нужна ; */ | ||
+ | |||
+ | (function() { B.prototype.m = ... })(); | ||
+ | |||
+ | // Эквивалентно A.prototype.m = ( ( function(){} ) ( function() { B.prototype.m = ... } ) )(); | ||
+ | </source> | ||
+ | |||
+ | == <span style="font-family: Consolas; background: #f1dd56; padding: 10px 10px 0 10px; font-style: normal">ES6</span> @@ %% == | ||
+ | |||
+ | Он же ES2015. А также ES2016, ES2017 | ||
+ | |||
+ | http://es6-features.org/, https://babeljs.io/repl/ | ||
+ | |||
+ | === Фичи ES6 @@ === | ||
+ | |||
+ | Уже сказал про: | ||
+ | * let/const | ||
+ | * arrow functions | ||
+ | * for .. of | ||
+ | |||
+ | === Вычислимые свойства объектов @@ === | ||
+ | |||
+ | ES6 | ||
+ | <source lang="javascript"> | ||
+ | var b = "key"; | ||
+ | var a = { [b+"_value"]: "v2" }; // выражение в [] | ||
+ | </source> | ||
+ | |||
+ | ES5 | ||
+ | <source lang="javascript"> | ||
+ | var b = "key2"; | ||
+ | var a = { key: "value" }; // ключи - только литералы, можно без кавычек | ||
+ | a[b] = 'v2'; // динамические ключи только так | ||
+ | </source> | ||
+ | |||
+ | === Destructuring @@ === | ||
+ | |||
+ | «Деструктивное присваивание» | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | var obj = { key: 'abc', value: [ 1, 2 ] }; | ||
+ | var { key: k, value: [ a, b ], other: o = 3 } = obj; // 3 - значение по умолчанию | ||
+ | |||
+ | // можно в параметрах функции! | ||
+ | [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ] | ||
+ | .reduce(function(obj, [ k, v ]) { obj[k] = v; return obj; }, {}); | ||
+ | </source> | ||
+ | |||
+ | === Оператор распаковки @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function f(...args) {} | ||
+ | |||
+ | f(...iterable); | ||
+ | |||
+ | // Кстати, в ES5 нельзя сделать apply конструктору. А тут: | ||
+ | new Object(...args); | ||
+ | |||
+ | // Распаковка массива | ||
+ | let arr = [ ...iterable, 4, 5, 6 ]; | ||
+ | |||
+ | // слияние объектов. у каждого фреймфорка было своё - $.extend, Ext.extend, Object.assign | ||
+ | let merge_obj = { ...obj1, ...obj2, key: "value" }; | ||
+ | |||
+ | // Распаковка в массив | ||
+ | [ a, b, ...other ] = [ 1, 2, 3, 4, 5 ]; | ||
+ | </source> | ||
+ | |||
+ | === Упрощённые названия ключей @@ === | ||
+ | |||
+ | ES6 | ||
+ | <source lang="javascript"> | ||
+ | obj = { x, y, a() { return 'abc'; } }; | ||
+ | </source> | ||
+ | |||
+ | ES5 | ||
+ | <source lang="javascript"> | ||
+ | obj = { x: x, y: y, a: function a() { return 'abc'; } }; | ||
+ | </source> | ||
+ | |||
+ | === Классы (!) @@ === | ||
+ | |||
+ | «Наконец-то», скажете вы. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | class Shape | ||
+ | { | ||
+ | constructor (id, x, y) | ||
+ | { | ||
+ | this.id = id; | ||
+ | this.move(x, y); | ||
+ | } | ||
+ | move (x, y) | ||
+ | { | ||
+ | this.x = x; | ||
+ | this.y = y; | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === Наследование и property @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | class Rectangle extends Shape | ||
+ | { | ||
+ | constructor (id, x, y, width, height) | ||
+ | { | ||
+ | super(id, x, y); | ||
+ | this._width = width; | ||
+ | this._height = height; | ||
+ | } | ||
+ | static default() | ||
+ | { | ||
+ | return new Rectangle("default", 0, 0, 100, 200); | ||
+ | } | ||
+ | set width(width) { this._width = width; } | ||
+ | get width() { return this._width; } | ||
+ | get area() { return this._width*this._height; } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | Нюанс: IE8+, так как при трансляции в ES5 требуют Object.defineProperty(). | ||
+ | |||
+ | === Генераторы @@ === | ||
+ | |||
+ | Функции, из которых можно выйти и вернуться. {{mag|Пожалуй, самая крутая из всех доработок!}} | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function* a() | ||
+ | { | ||
+ | try | ||
+ | { | ||
+ | var v = yield 1; | ||
+ | if (v < 0) yield 2; | ||
+ | else yield 3; | ||
+ | } | ||
+ | catch (e) { yield "error"; } | ||
+ | return "final"; | ||
+ | } | ||
+ | |||
+ | var b = a(); // объект GeneratorFunction. функция ещё не начала выполняться | ||
+ | b.next(); // вернёт { value: 1, done: false } | ||
+ | b.next(-5); // передаст -5 внутрь генератора. и вернёт { value: 2, done: false } | ||
+ | b.throw(new Error("x")); // передаст исключение внутрь генератора. и вернёт { value: "error", done: false} | ||
+ | b.next(); // вернёт { value: "final", done: true } | ||
+ | </source> | ||
+ | |||
+ | ==== Чем генераторы круты? @@ ==== | ||
+ | |||
+ | {{mag|Они дают убрать лестницу колбэков!}} Пример лестницы (node-postgres): | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | const pg = require('pg'); | ||
+ | function makeQueries(callback) | ||
+ | { | ||
+ | var client = new pg.Client(); | ||
+ | client.connect(function(err) { | ||
+ | if (err) throw err; | ||
+ | client.query('SELECT $1::text as name', ['brianc'], function (err, result) { | ||
+ | if (err) throw err; | ||
+ | client.end(function (err) { | ||
+ | if (err) throw err; | ||
+ | callback(result); | ||
+ | }); | ||
+ | }); | ||
+ | }); | ||
+ | } | ||
+ | makeQueries(function(result) { console.log(result.rows[0]); }); | ||
+ | </source> | ||
+ | |||
+ | ==== А теперь с генераторами @@ ==== | ||
+ | |||
+ | Генератор можно приостановить. Пишем обёртку и получаем coroutine: | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | const gen = require('gen-thread'); | ||
+ | const pg = require('pg'); | ||
+ | |||
+ | function* makeQueries() | ||
+ | { | ||
+ | var client = new pg.Client(); | ||
+ | // yield подождёт, пока не выполнится автоматически созданный колбэк gen.ef() | ||
+ | yield client.connect(gen.ef()); | ||
+ | var result = (yield client.query('SELECT $1::text as name', ['brianc'], gen.ef()))[0]; | ||
+ | yield client.end(gen.ef()); | ||
+ | return result; | ||
+ | } | ||
+ | |||
+ | gen.run(makeQueries(), function(result) { console.log(result.rows[0]); }); | ||
+ | </source> | ||
+ | |||
+ | === Промисы и async/await @@ === | ||
+ | |||
+ | То же, но стандартно — делается через промисы и async/await. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function sleep(millis) | ||
+ | { | ||
+ | return new Promise(function(resolve, reject) { | ||
+ | setTimeout(resolve, millis); | ||
+ | }); | ||
+ | } | ||
+ | async function f() | ||
+ | { | ||
+ | await sleep(500); | ||
+ | await sleep(1000); | ||
+ | } | ||
+ | // эквивалентно цепочке промисов: | ||
+ | function f() | ||
+ | { | ||
+ | return sleep(500).then(result => sleep(1000)); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | {{blue|Это уже не ES6 (2015), а 2016-2017; но Babel всё равно их поддерживает (и транслирует в генераторы).}} | ||
+ | |||
+ | ==== Поддержка Promise @@ ==== | ||
+ | |||
+ | API с колбэками надо оборачивать. Это нетрудно, но надо знать, куда ставить колбэк: | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function wrap(fn, ...args) | ||
+ | { | ||
+ | return new Promise(function(resolve, reject) | ||
+ | { | ||
+ | try { fn(resolve, ...args); } | ||
+ | catch (e) { reject(e); } | ||
+ | }); | ||
+ | } | ||
+ | async function test() | ||
+ | { | ||
+ | await wrap(setTimeout, 500); | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | ==== Нюансы с исключениями @@ ==== | ||
+ | |||
+ | {{red|Нюанс 1: Promise'ы nodejs глотают исключения}} | ||
+ | |||
+ | Решение — [http://bluebirdjs.com Bluebird], он бросает '''Unhandled rejection error''' | ||
+ | |||
+ | {{red|Нюанс 2: У асинхронных исключений нет вменяемого стека.}} | ||
+ | |||
+ | Стеки в духе: | ||
+ | <pre> | ||
+ | at Connection.parseE (node_modules/pg/lib/connection.js:554:11) | ||
+ | at Connection.parseMessage (node_modules/pg/lib/connection.js:381:17) | ||
+ | at Socket.<anonymous> (node_modules/pg/lib/connection.js:117:22) | ||
+ | at emitOne (events.js:77:13) | ||
+ | at Socket.emit (events.js:169:7) | ||
+ | at readableAddChunk (_stream_readable.js:146:16) | ||
+ | at Socket.Readable.push (_stream_readable.js:110:10) | ||
+ | at TCP.onread (net.js:523:20) | ||
+ | </pre> | ||
+ | |||
+ | Решение — опять-таки Bluebird + <tt><nowiki>Promise.config({ longStackTraces: true })</nowiki></tt>. | ||
+ | |||
+ | ==== gen-thread @@ ==== | ||
+ | |||
+ | * Ну, или забить пока на промисы | ||
+ | * Юзать генераторы и мой [https://github.com/vitalif/gen-thread gen-thread] (в промисы он тоже умеет) | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | yield gen.p(<PROMISE>); | ||
+ | </source> | ||
+ | |||
+ | ''Но в итоге, конечно, все перейдут на Promise.'' | ||
+ | |||
+ | === Модули ES6 @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | // lib/math.js | ||
+ | export function sum (x, y) { return x + y }; | ||
+ | export var pi = 3.141593; | ||
+ | // lib/mathplusplus.js | ||
+ | export * from "lib/math"; | ||
+ | export var e = 2.71828182846; | ||
+ | export default (x) => Math.exp(x); | ||
+ | // someApp.js | ||
+ | import exp, { pi, e } from "lib/mathplusplus"; | ||
+ | console.log("e^{π} = " + exp(pi)); | ||
+ | </source> | ||
+ | |||
+ | {{gray|Оговорка: чёткой уверенности, что это лучше CommonJS, у меня нет. Но гибче, да.}} | ||
+ | |||
+ | === Template strings @@ === | ||
+ | |||
+ | PHP-подобная строковая интерполяция. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | let name = "John"; | ||
+ | `Hello, ${name}`; | ||
+ | </source> | ||
+ | |||
+ | Кастомная интерполяция (для DSL, видимо): | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | get`http://example.com/foo?bar=${bar + baz}&quux=${quux}`; | ||
+ | // эквивалентно: | ||
+ | get([ "http://example.com/foo?bar=", "&quux=", "" ], bar + baz, quux); | ||
+ | </source> | ||
+ | |||
+ | === Proxy @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | let target = { foo: "Welcome, foo" }; | ||
+ | let proxy = new Proxy(target, | ||
+ | { | ||
+ | get (receiver, name) | ||
+ | { | ||
+ | return name in receiver ? receiver[name] : `Hello, ${name}`; | ||
+ | } | ||
+ | }); | ||
+ | proxy.foo === "Welcome, foo"; | ||
+ | proxy.world === "Hello, world"; | ||
+ | </source> | ||
+ | |||
+ | === Функции, локальные в блоках @@ === | ||
+ | |||
+ | (Block-scoped functions) | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | { | ||
+ | function foo () { return 1 } | ||
+ | foo() === 1 | ||
+ | { | ||
+ | function foo () { return 2 } | ||
+ | foo() === 2 | ||
+ | } | ||
+ | foo() === 1 | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === Итераторы для for .. of @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | let fibonacci = { | ||
+ | [Symbol.iterator]() { | ||
+ | let pre = 0, cur = 1; | ||
+ | return { | ||
+ | next() { | ||
+ | [ pre, cur ] = [ cur, pre + cur ]; | ||
+ | return { done: false, value: cur }; | ||
+ | } | ||
+ | }; | ||
+ | } | ||
+ | } | ||
+ | for (let n of fibonacci) { if (n > 1000) break; console.log(n); } | ||
+ | </source> | ||
+ | |||
+ | == Обзор инструментов @@ == | ||
+ | |||
+ | * Пакетные менеджеры | ||
+ | * Трансляторы | ||
+ | * Сборка, преобразования и минификация | ||
+ | * Отладка, профилировка | ||
+ | * Редакторы, IDE | ||
+ | |||
+ | === Системы модулей @@ === | ||
+ | |||
+ | * {{red|AMD}} (RequireJS): Asynchronous Module Definition | ||
+ | * {{green|CommonJS}} (node.js) | ||
+ | * {{red|UMD}} (AMD+CommonJS, поддерживается и там, и там) | ||
+ | * {{green|Модули ES6}} (уже рассказал) | ||
+ | |||
+ | Наиболее актуальны CommonJS и ES6. | ||
+ | |||
+ | ==== CommonJS @@ ==== | ||
+ | |||
+ | ИМХО — самый краткий и удобный синтаксис. | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | const pg = require('pg'); | ||
+ | const gen = require('gen-thread'); | ||
+ | |||
+ | module.exports.method = function() { /*...*/ }; | ||
+ | |||
+ | // или даже: | ||
+ | module.exports = MyClass; | ||
+ | |||
+ | function MyClass() | ||
+ | { | ||
+ | } | ||
+ | /*...*/ | ||
+ | </source> | ||
+ | |||
+ | ==== AMD @@ ==== | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | define(['jquery', 'underscore'], function ($, _) { | ||
+ | // methods | ||
+ | function a(){}; // private because it's not returned (see below) | ||
+ | function b(){}; // public because it's returned | ||
+ | function c(){}; // public because it's returned | ||
+ | |||
+ | // exposed public methods | ||
+ | return { | ||
+ | b: b, | ||
+ | c: c | ||
+ | } | ||
+ | }); | ||
+ | </source> | ||
+ | |||
+ | ==== UMD @@ ==== | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | (function (root, factory) { | ||
+ | if (typeof define === 'function' && define.amd) | ||
+ | define(['jquery', 'underscore'], factory); | ||
+ | else if (typeof exports === 'object') | ||
+ | module.exports = factory(require('jquery'), require('underscore')); | ||
+ | else | ||
+ | root.returnExports = factory(root.jQuery, root._); | ||
+ | }(this, function ($, _) { | ||
+ | function a(){}; // private because it's not returned (see below) | ||
+ | function b(){}; // public because it's returned | ||
+ | function c(){}; // public because it's returned | ||
+ | return { | ||
+ | b: b, | ||
+ | c: c | ||
+ | } | ||
+ | })); | ||
+ | </source> | ||
+ | |||
+ | === Пакетный менеджер [https://www.npmjs.com/ NPM] @@ === | ||
+ | |||
+ | * Использует систему модулей CommonJS | ||
+ | * Ставит зависимости '''рекурсивно''' (node_modules/module1/node_modules/module2/…) | ||
+ | * Можно их упростить: '''npm dedup''' | ||
+ | |||
+ | Пользоваться им не просто, а очень просто: | ||
+ | * Установить модуль: {{green|npm install [-g] [--save] [--save-dev] module}} | ||
+ | * Создать package.json для проекта: {{green|npm init}} | ||
+ | * Зарегистрироваться/залогиниться: {{green|npm login}} | ||
+ | * Опубликовать свой модуль: {{green|npm publish}} | ||
+ | |||
+ | {{mag|Самих модулей столько, что аж страшно.}} | ||
+ | |||
+ | === <s>[https://bower.io/ Bower]</s> @@ === | ||
+ | |||
+ | * {{red|Ещё один пакетный менеджер}} | ||
+ | * Ставится из npm O_o | ||
+ | * Команды примерно те же: install, init… те же semver’ы | ||
+ | * {{red|Основные отличия — AMD и плоское дерево зависимостей…}} | ||
+ | *: {{green|то есть как будто вы просто сказали npm dedup}} | ||
+ | *: {{gray|только свалится, если что-то несовместимо по semver}} | ||
+ | |||
+ | : ⇒ Нафиг-нафиг. | ||
+ | |||
+ | === Transpiler: Babel/Bublé @@ === | ||
+ | |||
+ | Как писать на ES6, если он не везде поддерживается? | ||
+ | |||
+ | {{green|Ответ — Babel!}} | ||
+ | |||
+ | * REPL: https://babeljs.io/repl/ | ||
+ | * Командная строка: npm install babel-cli (babel-node) | ||
+ | * browserify: babelify | ||
+ | |||
+ | ''Transpiler — транслятор JS в JS. Другие: [https://buble.surge.sh/ Bublé], [https://github.com/google/traceur-compiler Traceur].'' | ||
+ | |||
+ | === Трансляторы @@ === | ||
+ | |||
+ | {{red|В JS транслируют всё, что можно и нельзя}} | ||
+ | * [https://github.com/jashkenas/coffeescript/wiki/list-of-languages-that-compile-to-js List of languages that compile to JS] | ||
+ | * Тайпчекеры: [https://www.typescriptlang.org/ TypeScript], [https://flowtype.org/ Flow], [https://www.dartlang.org/ Dart] (язык) | ||
+ | * Kotlin, Ceylon, Scala.js, ClojureScript | ||
+ | * Непосредственно Java: [http://teavm.org/ TeaVM] | ||
+ | * '''C++ (O_O)''': [http://emscripten.org/ Emscripten] (LLVM → JS), [http://mandreel.com/ Mandreel] | ||
+ | * Python: Pyjamas, Transcrypt; Ruby: Opal | ||
+ | * OCaml: Ocsigen/Bucklescript | ||
+ | * Haskell: ghcjs, haste | ||
+ | * {{gray|И много чего ещё}} | ||
+ | |||
+ | === Сборка, упаковка и минификация @@ === | ||
+ | |||
+ | Актуальное: | ||
+ | * {{mag|browserify}} — упаковка npm-модулей (CommonJS) для браузера | ||
+ | * {{mag|webpack}} — универсальная система сборки (AMD / CommonJS / ES6 модулей) + CSS + картинки + whatever | ||
+ | * {{mag|stealjs}} — оптимизатор js для многостраничных сайтов | ||
+ | * [http://eslint.org/ eslint] — проверка на ошибки и стиль | ||
+ | * [http://gulpjs.com/ gulp] — запускатор скриптов сборки | ||
+ | * uglifyjs — обфускатор | ||
+ | |||
+ | === Чуть старее @@ === | ||
+ | |||
+ | * grunt — запускатор скриптов сборки (часто grunt+bower) | ||
+ | * bower — пакетный менеджер (рассказал выше) | ||
+ | * requirejs — система загрузки и сборки AMD модулей | ||
+ | * [http://jshint.com/ jshint] — проверка на ошибки и стиль | ||
+ | * yui-compressor, closure compiler — обфускаторы | ||
+ | * [http://rollupjs.org/ rollup] — система сборки для ES6 модулей (используется реже) | ||
+ | |||
+ | === IDE, редакторы, отладка @@ === | ||
+ | |||
+ | * {{blue|Netbeans}} | ||
+ | *: отладчик встроен | ||
+ | *: {{blue|(!)}} умеет живое обновление кода при отладке | ||
+ | * {{mag|Atom}} от GitHub | ||
+ | *: сам написан на node.js + webkit (Electron) | ||
+ | *: напоминает Sublime | ||
+ | *: модульный; отладчик — отдельный пакет | ||
+ | * {{green|Visual Studio Code}} | ||
+ | *: мелкий и мягкий форк Atom’а | ||
+ | *: встроен отладчик и поддержка typescript и C# | ||
+ | |||
+ | === Отладка из консоли @@ === | ||
+ | |||
+ | <tt>nodejs debug app.js;</tt> Есть REPL | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | for (var i = 0; i < 5; i++) | ||
+ | { | ||
+ | debugger; // брейкпоинт | ||
+ | console.log(i); | ||
+ | } | ||
+ | console.log("done"); | ||
+ | </source> | ||
+ | |||
+ | === Отладка из [https://github.com/node-inspector/node-inspector node-inspector] @@ === | ||
+ | |||
+ | {{blue|$ npm install -g node-inspector; node-debug app.js}} | ||
+ | Node Inspector is now available from http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858 | ||
+ | Debugging `app.js` | ||
+ | |||
+ | Debugger listening on port 5858 | ||
+ | |||
+ | [[File:Node_inspector_ss.png|600px]] | ||
+ | |||
+ | === Браузерная часть @@ === | ||
+ | |||
+ | {{green|Ну, тут проблем нет совсем}} | ||
+ | * F12 даже в IE | ||
+ | * watchify | ||
+ | * есть фокусы с live reload (react/redux) | ||
+ | * chrome + netbeans connector | ||
+ | |||
+ | === Профилировка @@ === | ||
+ | |||
+ | * node --prof, node --prof-process | ||
+ | * Профилировка памяти: [https://github.com/bnoordhuis/node-heapdump heapdump] | ||
+ | * Flamegraph: [https://github.com/davidmarkclements/0x 0x] [https://github.com/davidmarkclements/0x/raw/master/demo.gif анимация]<br /> [[File:0xFlamegraph.png|300px]] | ||
+ | * [http://stackoverflow.com/questions/1911015/how-do-i-debug-node-js-applications/16512303#16512303 Ещё заметки] | ||
+ | |||
+ | === Тестирование @@ === | ||
+ | |||
+ | * Для браузера: [https://karma-runner.github.io/ Karma] | ||
+ | *: {{gray|Запускает обычные тесты, но в открытом приложении в браузере}} | ||
+ | * В целом: [https://mochajs.org/ Mocha], Chai, Sinon | ||
+ | |||
+ | === Тайпчекеры: TypeScript, Flow @@ === | ||
+ | |||
+ | В целом {{green|очень похожи}}. | ||
+ | * У {{blue|Flow}} лучше вывод типов | ||
+ | * У {{mag|TypeScript}} есть public/private/protected | ||
+ | * У {{blue|Flow}} есть Nullable типы (и он автоматически проверяет на null) | ||
+ | * TypeScript — {{mag|над}}множество JS | ||
+ | * Flow — {{blue|под}}множество JS | ||
+ | |||
+ | === TS null @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function foo(num: number) { | ||
+ | if (num > 10) { | ||
+ | return 'cool'; | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | // cool | ||
+ | const result: string = foo(100); | ||
+ | console.log(result.toString()); | ||
+ | </source> | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | // still cool? | ||
+ | console.log(foo(1).toString()); | ||
+ | // error at runtime: "Cannot read property 'toString' of undefined" | ||
+ | </source> | ||
+ | |||
+ | === Flow null @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | function foo(num: number) { | ||
+ | if (num > 10) | ||
+ | return 'cool'; | ||
+ | } | ||
+ | |||
+ | // error: call of method `toString`. | ||
+ | // Method cannot be called on possibly null value | ||
+ | console.log(foo(100).toString()); | ||
+ | |||
+ | // error: return undefined. This type is incompatible with string | ||
+ | function foo(num: number): string { | ||
+ | if (num > 10) | ||
+ | return 'cool'; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === TS vs Flow: generics @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | class Animal { | ||
+ | name: string; | ||
+ | constructor(name: string) { | ||
+ | this.name = name; | ||
+ | } | ||
+ | } | ||
+ | class Dog extends Animal { | ||
+ | // just to make this different from cat | ||
+ | goodBoyFactor: number; | ||
+ | } | ||
+ | class Cat extends Animal { | ||
+ | purrFactor: number; | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | === TS vs Flow: присваивание элементов @@ === | ||
+ | |||
+ | OK в обоих | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | let cats: Array<Cat> = []; // только коты | ||
+ | let animals: Array<Animal> = []; // только животные | ||
+ | |||
+ | // неа, не кот | ||
+ | cats.push(10); | ||
+ | |||
+ | // неа, не кот | ||
+ | cats.push(new Animal('Fido')); | ||
+ | |||
+ | // круто, кот | ||
+ | cats.push(new Cat('Purry')); | ||
+ | |||
+ | // круто, кот - тоже животное | ||
+ | animals.push(new Cat('Purry')); | ||
+ | </source> | ||
+ | |||
+ | === TS vs Flow: присваивание коллекций @@ === | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | // error TS2322: Type 'Animal[]' is not assignable to type 'Cat[]'. | ||
+ | // Type 'Animal' is not assignable to type 'Cat'. | ||
+ | // Property 'purrFactor' is missing in type 'Animal'. | ||
+ | //cats = animals; | ||
+ | |||
+ | // во Flow не пройдёт | ||
+ | // опа, в TS работает. но теперь animals небезопасны | ||
+ | animals = cats; | ||
+ | |||
+ | // потому что вот это тоже проходит без ошибки | ||
+ | animals.push(new Dog('Brutus')); | ||
+ | animals.push(new Animal('Twinky')); | ||
+ | |||
+ | // упс | ||
+ | cats.forEach(cat => console.log(`Cat: ${cat.name}`)); | ||
+ | // Cat: Purry | ||
+ | // Cat: Brutus | ||
+ | // Cat: Twinky | ||
+ | </source> | ||
+ | |||
+ | == Обзор фреймворков @@ == | ||
+ | |||
+ | * Клиентские фреймворки «старые» | ||
+ | * Клиентские фреймворки «новые» | ||
+ | * Попытки мобильной разработки | ||
+ | * Попытки игровых фреймворков | ||
+ | |||
+ | === «Старые» @@ === | ||
+ | |||
+ | Заслуживают упоминания: | ||
+ | * {{blue|jQuery}} (фиг сдохнет, а надо бы) | ||
+ | * {{green|ExtJS}} (тоже фиг сдохнет) | ||
+ | * {{red|R.I.P}}: Yahoo UI, PrototypeJS | ||
+ | * По-моему, сдыхает: Dojo | ||
+ | |||
+ | ==== jQuery @@ ==== | ||
+ | |||
+ | {{blue|«Меня зовут Мистер Свинья!»}} | ||
+ | |||
+ | ''Если скриптота — рас***дяйство, то jQuery — {{red|ВЕРХ}} рас***дяйства'' | ||
+ | |||
+ | Ибо РАСПОЛАГАЕТ к плохому коду: | ||
+ | * Всё в кучу: объект-бог с кучей хелперов $ {{mag|и такие же модули}} {{gray|(например, DataTables)}} | ||
+ | * <tt><nowiki>$.extend(), $.ajax(), $("<html>"), $(".class")</nowiki></tt> | ||
+ | * Селекторы / операции над множествами: <tt><nowiki>$(".class").each(e => $(e).hide())</nowiki></tt> | ||
+ | *: {{red|если элементов не найдётся, no problem, ничего не сдохнет}} | ||
+ | * Страшные контроллеры, гвоздями прибитые к UI | ||
+ | * Отсутствие возможностей оптимизации при динамической сборке UI | ||
+ | *: {{gray|Пример — WikiEditor}} | ||
+ | |||
+ | ==== ExtJS @@ ==== | ||
+ | |||
+ | * Desktop-like фреймворк, {{green|умеет кучу всего}} | ||
+ | * Фундаментально {{red|тормозноват}} | ||
+ | * Со стилизацией большие трудности | ||
+ | *: {{gray|1=Ну хоть тема в ExtJS >= 4 стала норм, можно смотреть без рвотных позывов}} | ||
+ | * По моему опыту — писать на нём НЕ проще и НЕ быстрее, чем на HTML+JS | ||
+ | * Data binding есть, но ограниченный — только свойств (а не иерархии) компонентов | ||
+ | |||
+ | === «Новые» (компонентные) @@ === | ||
+ | |||
+ | * Data binding | ||
+ | *: <s>Knockout, Ember, Backbone</s> | ||
+ | *: {{gray|упомянем для истории; неактуальны}} | ||
+ | *: {{green|AngularJS 1, 2}} | ||
+ | * Virtual DOM | ||
+ | *: {{green|React}} | ||
+ | |||
+ | И те, и другие — по сути, {{mag|решают задачу шаблонизации на JS}}. | ||
+ | |||
+ | === AngularJS 1 @@ === | ||
+ | |||
+ | * Dirty checking | ||
+ | * Все биндинги 2-way | ||
+ | * Поэтому медленный | ||
+ | * Есть компоненты («директивы») и контроллеры | ||
+ | * {{blue|На этом уже ''можно'' писать}} | ||
+ | |||
+ | ==== Пример @@ ==== | ||
+ | |||
+ | <div></div> | ||
+ | <div '''ng-controller="LoanCalculator"'''> | ||
+ | <div> | ||
+ | Дата выдачи: <input name="" type="text" '''ng-model="props.start"''' /><br /> | ||
+ | Сумма: <input name="" type="text" '''ng-model="props.total"''' /><br /> | ||
+ | Процент: <input name="" type="text" '''ng-model="props.percent"''' /><br /> | ||
+ | Срок: <input name="" type="text" '''ng-model="props.months"''' /> месяцев<br /> | ||
+ | Штраф за просрочку: <input name="" type="text" '''ng-model="props.fine"''' /><br /> | ||
+ | Пеня % годовых на просрочку: <input name="" type="text" '''ng-model="props.penaltyPercent"''' /><br /> | ||
+ | <input type="button" value="Рассчитать" '''ng-click="recalc()"''' /> | ||
+ | <input type="button" value="Сбросить" '''ng-click="clear()"''' /> | ||
+ | </div> | ||
+ | <table '''ng-if="payments.length"''' border="0"> | ||
+ | <tr> | ||
+ | <th>Дата</th> | ||
+ | <th>Сумма</th> | ||
+ | <th>Комментарий</th> | ||
+ | </tr> | ||
+ | <tr '''ng-repeat="payment in payments"'''> | ||
+ | <td><input name="" type="text" '''ng-model="payment.date" ng-change="clear_from($index+1)"''' /></td> | ||
+ | <td><input name="" type="text" '''ng-model="payment.total" ng-change="clear_from($index+1)"''' /></td> | ||
+ | <td>{{payment.text}}</td> | ||
+ | </tr> | ||
+ | <tr '''ng-if="clean"'''> | ||
+ | <th>Всего</th> | ||
+ | <td>{{sum}}</td> | ||
+ | <td>{{outsums}}</td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | </div> | ||
+ | |||
+ | === Angular 2 @@ === | ||
+ | |||
+ | * Пофиксили скорость, разделив 1-way и 2-way биндинги | ||
+ | * Перешли на TypeScript ''{{gray|и всех агитируют}}'' | ||
+ | * Обновились в целом | ||
+ | * {{green|Больше «искаропки»}} | ||
+ | * {{red|Но зато тяжелее. Зачем-то тянет за собой RxJS…}} | ||
+ | |||
+ | === React: почему я за React? @@ === | ||
+ | |||
+ | * {{mag|JSX}}: на первый взгляд странно, на самом деле — круто и удобно! | ||
+ | * '''Строго однонаправленный''' поток данных | ||
+ | *: {{gray|(2-way binding — как чуть сложнее, так проблема)}} | ||
+ | * {{green|Правильная компонентность}} | ||
+ | *: Убирает все сомнения в том, MV-что у нас — MVVM, MVC, M-V-VC, M-V-VC-VM… | ||
+ | *: Контроллер занимается тем, чем и должен: работой с данными | ||
+ | * Легковесный, простой, изящный. Учится за 1 вечер. Не принуждает к дополнительным компонентам. | ||
+ | |||
+ | ==== Почему JSX — круто? @@ ==== | ||
+ | |||
+ | Даёт писать шаблоны прямо на JS! Удобно и безопасно. | ||
+ | |||
+ | Например, цикл: | ||
+ | * Ember: {{red|<nowiki>{{# each}}</nowiki>}} | ||
+ | * Angular 1: {{red|ng-repeat}} | ||
+ | * Angular 2: {{red|ngFor}} | ||
+ | * Knockout: {{red|<nowiki>data-bind="foreach"</nowiki>}} | ||
+ | * React: {{blue|ПРОСТО ИСПОЛЬЗУЙТЕ JS :)}} (обычный for или Array.map()) | ||
+ | |||
+ | <blockquote>Angular 2 continues to put «JS» into HTML. React puts «HTML» into JS.</blockquote> | ||
+ | |||
+ | ==== JSX @@ ==== | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | var MessageInList = React.createClass({ | ||
+ | msgClasses: { unread: 'unread', unseen: 'unseen', answered: 'replied', flagged: 'pinned', sent: 'sent' }, | ||
+ | render: function() | ||
+ | { | ||
+ | var msg = this.props.msg; | ||
+ | return <div data-i={this.props.i} className={'message'+ | ||
+ | (msg.body_text || msg.body_html ? '' : ' unloaded')+ | ||
+ | (msg.flags.map(c => (this.msgClasses[c] ? ' '+this.msgClasses[c] : '')).join(''))+ | ||
+ | (this.props.selected ? ' selected' : '')+ | ||
+ | (msg.thread && this.props.threads ? ' thread0' : '')} onMouseDown={this.props.onClick}> | ||
+ | |||
+ | <div className="icon" style={{ width: (20+10*(msg.level||0)), backgroundPosition: (10*(msg.level||0))+'px 7px' }}></div> | ||
+ | <div className="subject" style={{ paddingLeft: (20+10*(msg.level||0)) }}>{msg.subject}</div> | ||
+ | {msg.thread && this.props.threads ? <div className={'expand'+(msg.collapsed ? '' : ' collapse')}></div> : null} | ||
+ | <div className="bullet"></div> | ||
+ | <div className="from" style={{ left: (21+10*(msg.level||0)) }}> | ||
+ | {(msg.props.sent ? 'To '+(msg.props.to[0][0]||msg.props.to[0][1]) : (msg.props.from ? msg.props.from[0]||msg.props.from[1] : ''))} | ||
+ | </div> | ||
+ | <div className="size">{Util.formatBytes(msg.size||0)}</div> | ||
+ | <div className="attach" style={msg.props.attachments && msg.props.attachments.length ? null : { display: 'none' }}></div> | ||
+ | <div className="time">{Util.formatDate(msg.time)}</div> | ||
+ | |||
+ | </div> | ||
+ | } | ||
+ | }); | ||
+ | </source> | ||
+ | |||
+ | ==== JSX и Virtual DOM @@ ==== | ||
+ | |||
+ | * Как применять изменения быстро? | ||
+ | *: ''(DOM — относительно медленный)'' | ||
+ | * Храним {{mag|копию DOM}} в виде json | ||
+ | * После render() сравниваем | ||
+ | * Изменения применяем {{red|к реальному DOM}} | ||
+ | : {{green|⇒ Быстро и при этом гибко}} | ||
+ | |||
+ | ==== [https://facebook.github.io/react/docs/component-specs.html Компоненты React] @@ ==== | ||
+ | |||
+ | * Обязательный метод ОДИН: {{blue|render()}} | ||
+ | * props и state {{gray|(XML-атрибуты и внутреннее состояние)}} | ||
+ | * children {{gray|(тело элемента)}} | ||
+ | * getDefaultProps(), getInitialState() | ||
+ | * setState(). {{red|нет setProps() и setChildren()!}} {{gray|(получает при render родителя)}} | ||
+ | * componentWillMount(), componentDidMount(), componentWillUnmount() | ||
+ | * componentWillReceiveProps(p), shouldComponentUpdate(p, s) | ||
+ | * componentWillUpdate(p, s), componentDidUpdate(p, s) | ||
+ | * propTypes, mixins, statics | ||
+ | |||
+ | ==== А вот почему это красиво @@ ==== | ||
+ | |||
+ | ExtJS. Класс «панель». {{red|Где тут делать инициализацию?}} | ||
+ | |||
+ | [[File:Extjs-events.png|400px]] | ||
+ | |||
+ | {{red|beforerender? render? afterrender? afterlayout? added? add? show?}} {{gray|спойлер: нигде}} | ||
+ | |||
+ | ==== Библиотеки для React @@ ==== | ||
+ | |||
+ | * Контроллер: Flux | ||
+ | * Или {{mag|Redux}} (2 кб!) | ||
+ | * react-router | ||
+ | * react-router-redux | ||
+ | * HTTP? superagent | ||
+ | * [https://facebook.github.io/draft-js/ Draft.js] {{gray|(rich editor)}} | ||
+ | |||
+ | ==== О Redux @@ ==== | ||
+ | |||
+ | * «State container» | ||
+ | * {{green|Однонаправленный поток данных:}} | ||
+ | *: Store → Компоненты → Действия → Диспетчер → Store | ||
+ | * Компоненты «привязываются» к данным в Store | ||
+ | * И к вызовам действий | ||
+ | * Действие = reducer: {{mag|(Состояние, Действие) → (НовоеСостояние)}}. | ||
+ | : {{gray|→ Очень чёткое отделение контроллера}} | ||
+ | |||
+ | === Как писать на React @@ === | ||
+ | |||
+ | [https://facebook.github.io/react/docs/thinking-in-react.html Thinking in React] | ||
+ | # Набросать (или взять у дизайнеров) HTML-макет | ||
+ | # Выделить компоненты | ||
+ | #: Помним, что компонент — ''единица рендера'' | ||
+ | # Сделать статическую версию на React, всё через props | ||
+ | # Выделить состояние, определить владельца состояния | ||
+ | #: Можно уже брать Flux/Redux | ||
+ | #: Presentational vs Controller | ||
+ | # Научить менять состояние | ||
+ | |||
+ | === Прочее @@ === | ||
+ | |||
+ | * Мобильное | ||
+ | ** React Native: {{gray|Virtual DOM над нативными компонентами}} | ||
+ | ** NativeScript: {{gray|Тоже JS в отдельном потоке и свой XML-язык описания UI}} | ||
+ | ** {{green|Уверен, появятся ещё}} | ||
+ | * https://html5gameengine.com/ | ||
+ | * WebGL: SceneJS, OSG.JS, Blend4Web | ||
+ | |||
+ | == Демонстрация LikeOpera @@ %% == | ||
+ | |||
+ | {{gray|Клон Opera Mail на React и node.js}} | ||
+ | |||
+ | == Вопросы @@ %% == | ||
− | + | ? |
Текущая версия на 14:49, 9 февраля 2017
- Автор
- Виталий Филиппов
- Дополнительный нижний колонтитул
- ECMAScript и все-все-все
- PDF-версия
- ES6 and everyone.pdf
Содержание
- 1 ECMAScript — ассемблер будущего, бэкенд, фронтенд и все-все-все @@ %%
- 2 О своих предпочтениях @@
- 3 О чём доклад? @@
- 4 Что такое JS? @@
- 5 Скриптота vs типизация @@ %%
- 6 Почему JS? @@
- 7 Синтаксис @@ %%
- 8 История JS @@
- 9 Производительность @@
- 10 I/O bound, а не CPU bound @@
- 11 Обзор языка @@ %%
- 11.1 Типы данных @@
- 11.2 Переменные @@
- 11.3 Функции @@
- 11.4 Замыкания @@
- 11.5 Прототипы (голый/старый JS) @@
- 11.6 Прототипы @@
- 11.7 Наследование (голый JS) @@
- 11.8 this @@
- 11.9 this — примеры @@
- 11.10 this и замыкания @@
- 11.11 Стандартные конструкции @@
- 11.12 Интересные конструкции @@
- 11.13 Приколы: приведение типов @@
- 11.14 Приколы: var @@
- 11.15 Приколы: неоднозначный разбор @@
- 12 ES6 @@ %%
- 12.1 Фичи ES6 @@
- 12.2 Вычислимые свойства объектов @@
- 12.3 Destructuring @@
- 12.4 Оператор распаковки @@
- 12.5 Упрощённые названия ключей @@
- 12.6 Классы (!) @@
- 12.7 Наследование и property @@
- 12.8 Генераторы @@
- 12.9 Промисы и async/await @@
- 12.10 Модули ES6 @@
- 12.11 Template strings @@
- 12.12 Proxy @@
- 12.13 Функции, локальные в блоках @@
- 12.14 Итераторы для for .. of @@
- 13 Обзор инструментов @@
- 13.1 Системы модулей @@
- 13.2 Пакетный менеджер NPM @@
- 13.3 Bower @@
- 13.4 Transpiler: Babel/Bublé @@
- 13.5 Трансляторы @@
- 13.6 Сборка, упаковка и минификация @@
- 13.7 Чуть старее @@
- 13.8 IDE, редакторы, отладка @@
- 13.9 Отладка из консоли @@
- 13.10 Отладка из node-inspector @@
- 13.11 Браузерная часть @@
- 13.12 Профилировка @@
- 13.13 Тестирование @@
- 13.14 Тайпчекеры: TypeScript, Flow @@
- 13.15 TS null @@
- 13.16 Flow null @@
- 13.17 TS vs Flow: generics @@
- 13.18 TS vs Flow: присваивание элементов @@
- 13.19 TS vs Flow: присваивание коллекций @@
- 14 Обзор фреймворков @@
- 15 Демонстрация LikeOpera @@ %%
- 16 Вопросы @@ %%
ECMAScript — ассемблер будущего,
бэкенд, фронтенд и все-все-все @@ %%
(Об эволюции и фичах JavaScript)
Виталий Филиппов, CUSTIS
О своих предпочтениях @@
«И давно вы занимаетесь программизмом?»
- Начинал лет в 11 с C/C++ (Turbo C / C++Builder)
- Потом открыл для себя Linux, свободный софт…
- главное читать логи :)
- …LAMP (Perl/PHP), HTML и JS
- Теперь полюбил серверный JS (nodejs)
О чём доклад? @@
- Почему JS?
- История JavaScript
- Обзор языка, производительность
- Обзор выдумок
- (фреймворки, системы сборки и т.п)
- Немного демо
Что такое JS? @@
JavaScriptECMAScript
- От Java только часть названия
- Скриптота! (динамический язык)
- Объект/массив/скаляр (JSON)
- Прототипы, замыкания, колбэки, нет многопоточности
- ES — язык. А ещё есть окружение (DOM, BOM)
- Куча новых фич (ES2015-2016-2017)
- Браузерный — Chrome (V8), Firefox (SpiderMonkey) и даже IE (ChakraCore)
- Серверный — node.js (V8)
Скриптота vs типизация @@ %%
Холивар!!! @@
- Шутки в сторону: тема серьёзная
- Популярных динамических языков много
- можно пытаться это отрицать, но таки удобно
- Статические… хде они? Java? (C# не в счёт, винда)
- Компилируемые новые есть: D, Rust, Go, Vala, Swift
- но кто на них пишет-то?
Все хотят одного @@
- Типизация — не необходимость, как раньше, а лишь один из способов проверки
- что ещё можно проверять?
- Rust: borrow checker
- функциональщина: «purity» checker
- Java: checked exceptions
- При этом
- auto уже даже в C++
- тайпчекер (частично) уже даже в PHP (+ Hack)
Проверки — хорошо, но @@ %%
Почему JS? @@
- Нейтральный C-подобный синтаксис
- Быстрый! (как вычислительно, так и для I/O)
- Событийная машина node.js — не пустой звук! (1M соединений на одном сервере)
- Всегда нужен в браузерах (а веб нынче даже 1С)
- Мощное сообщество и развитие
- Удобный пакетный менеджер npm
- Есть тайпчекеры: TypeScript, Flow, Dart…
Синтаксис @@ %%
Сравнительный анализ
Perl @@
sub _skip_attrs { my ($tag, $attrs) = @_; $tag = lc $tag; return "<$tag>" if $tag =~ m!^/!so; my ($enclosed) = $attrs =~ m!/$!so ? ' /' : ''; $attrs = { $attrs =~ /([^\s=]+)=([^\s=\'\"]+|\"[^\"]*\"|\'[^\']*\')/gso }; my $new = {}; for (qw(name id class style title)) { $new->{$_} = $attrs->{$_} if $attrs->{$_}; } my %l = (a => 'href', blockquote => 'cite', q => 'cite'); if ($attrs->{$l{$tag}} && $attrs->{$l{$tag}} !~ /^[\"\']?javascript/iso) { $new->{$l{$tag}} = $attrs->{$l{$tag}}; } return "<$tag".join("", map { " $_=".$new->{$_} } keys %$new).$enclosed.">"; }
- Спецсимволы захватили мир
- Репутация «write-only», развитие умерло
PHP @@
$isExact = []; foreach ([ 'line' => 'l', 'cfo' => 'cc' ] as $k => $t) { if (!isset($specified[$k.'_id']) && !isset($specified[$k.'_id_exact']) && !isset($groups[$k]) && !isset($groups[$k.'_all'])) $isExact[] = "$posAlias.${k}_id IS NULL"; elseif ($lastgrp == $k.'all') $isExact[] = "$posAlias.${k}_id=$t.id"; } foreach ([ 'party', 'account', 'paytype' ] as $k) if (!isset($specified[$k.'_id']) && !isset($groups[$k])) $isExact[] = "$posAlias.${k}_id IS NULL"; return implode(' AND ', $isExact) ?: '1=1';
- Что за $$$$$?
- Репутация г**нокода
Python @@
class FileCache: def __init__(self, dir): self.dir = dir if not os.path.isdir(dir): os.mkdir(dir) def fn(self, key): key = re.sub('([^a-zA-Z0-9_\-]+)', lambda x: binascii.hexlify(x.group(1)), key) return self.dir+'/'+key def clean(self): t = time.time() for fn in os.listdir(self.dir): if t > os.stat(self.dir+'/'+fn).st_mtime: os.unlink(self.dir+'/'+fn)
- Пробелы меняют смысл?!!!!
Ruby @@
module Gitlab class SearchResults attr_reader :current_user, :query def objects(scope, page = nil) case scope when 'projects' projects.page(page).per(per_page) when 'issues' issues.page(page).per(per_page) when 'merge_requests' merge_requests.page(page).per(per_page) when 'milestones' milestones.page(page).per(per_page) else Kaminari.paginate_array([]).page(page).per(per_page) end end
- projects — переменная? Фигвам. Метод без аргументов.)
- чем он лучше хотя бы питона?
Go @@
func TestChannelStoreSave(t *testing.T) { Setup() teamId := model.NewId() o1 := model.Channel{} o1.TeamId = teamId o1.DisplayName = "Name" o1.Name = "a" + model.NewId() + "b" o1.Type = model.CHANNEL_OPEN if err := (<-store.Channel().Save(&o1)).Err; err != nil { t.Fatal("couldn't save item", err) } if err := (<-store.Channel().Save(&o1)).Err; err == nil { t.Fatal("shouldn't be able to update from save") }
- Что за смайлики := <- & *? Где мои скобочки?
Erlang @@
iq_handler(From, _To, #iq{type=set, lang = Lang, sub_el = #xmlel{name = Operation} = SubEl} = IQ, CC)-> ?DEBUG("carbons IQ received: ~p", [IQ]), {U, S, R} = jid:tolower(From), Result = case Operation of <<"enable">>-> ?INFO_MSG("carbons enabled for user ~s@~s/~s", [U,S,R]), enable(S,U,R,CC); <<"disable">>-> ?INFO_MSG("carbons disabled for user ~s@~s/~s", [U,S,R]), disable(S, U, R) end, case Result of ok -> ?DEBUG("carbons IQ result: ok", []), IQ#iq{type=result, sub_el=[]}; {error,_Error} -> ?ERROR_MSG("Error enabling / disabling carbons: ~p", [Result]), Txt = <<"Database failure">>, IQ#iq{type=error,sub_el = [SubEl, ?ERRT_INTERNAL_SERVER_ERROR(Lang, Txt)]} end;
- Ой-ой-ой…
- Специфичен. Классов нет, есть процессы. Но сетевой, да!
OCaml O_O @@
let log_client_info c sock = let buf = Buffer.create 100 in let date = BasicSocket.date_of_int (last_time ()) in Printf.bprintf buf "%-12s(%d):%d -> %-30s[%-14s %-20s] connected for %5d secs %-10s bw %5d/%-5d %-6s %2d/%-2d reqs " (Date.simple date) (nb_sockets ()) (client_num c) ( let s = c.client_name in let len = String.length s in if len > 30 then String.sub s 0 30 else s) (brand_to_string c.client_brand) (match c.client_kind with Indirect_address _ | Invalid_address _ -> "LowID" | Direct_address (ip,port) -> Printf.sprintf "%s:%d" (Ip.to_string ip) port) (last_time () - c.client_connect_time) (if c.client_rank > 0 then Printf.sprintf "rank %d" c.client_rank else "") (nwritten sock) (nread sock) (if c.client_banned then "banned" else "") c.client_requests_received c.client_requests_sent ;
- «вроде что-то древнее»
- ПОЛИЗ?!!!!
Lua @@
function Encoder.put(self, chunk) if self.buffersize < 2 then coroutine.yield(chunk) else if #self.buffer + #chunk > self.buffersize then local written = 0 local fbuffer = self.buffersize - #self.buffer coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer)) written = fbuffer while #chunk - written > self.buffersize do fbuffer = written + self.buffersize coroutine.yield(chunk:sub(written + 1, fbuffer)) written = fbuffer end self.buffer = chunk:sub(written + 1) else self.buffer = self.buffer .. chunk end end end
- Для полноты картины
JS @@
TreeGridNode.prototype.setChildren = function(isLeaf, newChildren) { if (!this.tr) this.grid.tbody.innerHTML = ''; else { var tr = this.tr[this.tr.length-1]; while (tr.nextSibling && tr.nextSibling._node.level > this.level) this.grid.tbody.removeChild(tr.nextSibling); if (this.leaf != isLeaf) { if (isLeaf) { this.tr[0].cells[0].firstChild.className = 'collapser collapser-inactive'; removeListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler()); } else { this.tr[0].cells[0].firstChild.className = this.collapsed ? 'collapser collapser-collapsed' : 'collapser collapser-expanded'; addListener(this.tr[0].cells[0].firstChild, 'click', this._getToggleHandler()); } } } this.leaf = isLeaf; this.children = []; this.childrenByKey = {}; this.addChildren(newChildren); }
Синтаксис JS @@ %%
Имхо вполне нейтральненько.
(неприятных рефлексов вызывать не должен)
История JS @@
- 1995—2004: дремучий лес с партизанами
- 2004—2008: появление AJAX
- 2008+: шустрота и современный период
Дремучий период @@
1995—1997
- Создание и начальная стандартизация
- DOM ещё нет, только «DOM level 0»
- document.forms, document.images
1998
- DOM level 1 (DHTML)
- document.getElementById, все элементы — объекты
2000
- DOM level 2
- События и более-менее современный вид объектов
- pre-AJAX: JSONP, невидимый iframe
Появление AJAX @@
2004
- Firefox 1.0
- XmlHttpRequest
- Первое SPA — Gmail, впервые упомянут термин «AJAX»
- Начало конца дремучего периода JS, как языка для всплывающих баннеров
Совместимость браузеров ещё плохая, так что
- 2006 — jQuery
Современное развитие @@
- 2008 — Google V8
- 2009 — IE8 (M$ очнулся)
- 2009 — node.js (2011 — v0.6)
- 2011 — начало работы над ES6
- 2012 — Angular 1.0
- 2013 — React
- Где-то тут же изоморфность
- 2015 — ES6 принят
Производительность @@
На ЛОРе до сих пор шутят, что «Java тормозит», а что ж тогда JS?
- А ничего — и Java быстрая, и он быстрый. Мамонты видать шутят
- из скриптоты node.js быстрее всех
- Но он же интерпретируемый?
- НЕТ! Интерпретируемых языков уже вообще нет.
- Ну, разве что bash…
Вычислительный бенчмарк @@
- https://github.com/famzah/langs-performance, время на 10 итераций (i386)
- C++ (g++ 6.1.1) -O2 = 0.92s
- java8 = 0.92s; java6 = 1s (ручные массивы)
- PyPy (tracing jit) = 1.25s
- Rust 1.12 = 0.85s! Go 1.7 = 1.14s
- node.js 4.6 = 1.35s + баг; nodejs 0.10 = 2.6s
- PHP 7 = 6.7s
- Ruby 2.3 = 14.3s, Python 3.5 = 17s, 2.7 = 20s
- Perl = 24s
- PHP 5.6 = 42.5s :))))) ахаха, прекрати
Разница между 64 и 32 бит: i386 @@
Разница между 64 и 32 бит: amd64 @@
Почему V8 такой быстрый? @@
Потому, что 4-слойный JIT!
- Как уже сказано, интерпретируемых языков нет.
- 1 слой — LLInt, интерпретатор байткода (быстрый старт)
- 2 слой — Baseline JIT
- 3 слой — DFG (Data Flow Graph) JIT
- здесь появляется типизация
- 4 слой — FTL JIT (
LLVMB3)
Ключевые слова о том, как это всё устроено @@
- Какой бывает JIT?
- method-based jit (JVM)
- tracing jit (PyPy, TraceMonkey)
- Девиртуализация
- Ускорение поиска в хеше (Lua)
- OSR (On-Stack Replace)
Отступление: PyPy @@ %%
Трассирующий JIT-компилятор для Python, очень медленный
Рисует множество Мандельброта при сборке
LLVM @@
LLVM (http://llvm.org), ранее «Low Level Virtual Machine»
- Набор библиотек для построения компиляторов/интерпретаторов
- Модульный
- исходник → фронтенд (ЯП) → LLVM IR (SSA)
- IR → оптимизатор LLVM → IR
- IR → бэкенд → машинный код
- А также сами компиляторы (в первую очередь C/C++/ObjC: Clang)
- На LLVM сделаны компилятор шейдеров Radeon и OpenCL
SSA @@
Пример LLVM IR:
store i32 1, i32* %e, align 4 br label %4 ; <label>:4 ; preds = %29, %0 %5 = load i32* %a, align 4 %6 = load i32* %b, align 4 %7 = add nsw i32 %5, %6 store i32 %7, i32* %c, align 4 %8 = load i32* %c, align 4 %9 = load i32* %a, align 4 %10 = sub nsw i32 %8, %9 store i32 %10, i32* %d, align 4 %11 = load i32* %d, align 4 %12 = icmp ne i32 %11, 0 br i1 %12, label %13, label %14 ; <label>:13 ; preds = %4 br label %20
V8 JIT @@
- Первый раз функции запускаются в LLInt
- 6 вызовов либо 100 повторов строки
- → OSR в Baseline JIT
- C*66 вызовов либо C*1000 повторов строки
- → OSR в DFG JIT
- C ~ 1, больше для больших функций
- Нарушение type guard в DFG
- → OSR обратно в Baseline JIT
- C*6666 вызовов либо C*100000 повторов строки
- → OSR в LLVM/B3 JIT
Мало? @@
function strlen(ptr) { // calculate length of C string ptr = ptr|0; var curr = 0; curr = ptr; while (MEM8[curr]|0 != 0) { curr = (curr + 1)|0; } return (curr - ptr)|0; }
Производительность — итого @@
- Итого, V8 — «смешанный» JIT
- В Firefox — тоже всё шустро
- Чакра Наделлы тоже очень похожа на V8
- Постоянная битва :) https://arewefastyet.com/
- Но язык — ещё не всё! Ещё есть:
- В браузере — DOM (медленный в старых Firefox при формально быстром JS)
- На сервере — ввод/вывод
I/O bound, а не CPU bound @@
- Типичные веб- и бизнес- приложения сетевые
- «получить из одного места (базы, кэша) и переложить в другое / отдать клиенту»
- nb: все наши веб-фреймворки ни фига не делают :)
- nb: иначе PHP-сайтики были бы неюзабельны
- Иногда — очень сетевые (C10K / C1M)
- чисто русский термин «хайлоад»
- 10gbit ethernet уже среди нас
- ⇒ что же делать?..
Событийные машины! @@ %%
("event loop")
Как вообще обрабатываются соединения клиентов? @@
Все писали сетевой сервер на C? :)
Обычный (блокирующий) ввод/вывод:
- forking: socket(), accept(), fork(), дочерний процесс работает с клиентом
- threading: socket(), accept(), создаём поток, дочерний работает с клиентом
- prefork / thread pool: создаём N процессов/потоков заранее
- Потоки и процессы — объекты ОС
- разница только в изоляции памяти
- 1000 потоков — уже тяжело (context switch)
- «Проблема C10K» (обработать 10000 соединений на 1 сервере)
Событийная машина @@
Неблокирующий ввод/вывод:
- socket()
- select() / poll() / epoll / kqueue
- говорим ОС: «разбуди, когда в любом из сокетов что-то произойдёт»
- сокет пока один (слушающий)
- новое соединение => добавляем в select и спим дальше
- кто-то прислал запрос => читаем, быстро обрабатываем, отвечаем, спим дальше
- всё в один поток (или в несколько по числу ядер CPU)
Так работает Nginx… @@ %%
…и весь современный Web (фронтенды)
(плюс zero-copy и раздача статики через sendfile())
А если пойти дальше? @@
Кроме HTTP-запросов клиентов ещё есть:
- СУБД/кэши
- Файлы
- REST сервисы
- Websocket’ы
- Сетевые серверы (IMAP?)
Почему бы всё это не обрабатывать в одном цикле?
⇒ Это и будет «событийная машина»
Событийные машины @@
Почти везде опциональная сущность:
- Python: Twisted
- более-менее популярен
- Java: Vert.x, Webbit
- мало кто использует, ынтырпрайз же, а тут хипстота какая-то
- да, внутри wildfly тоже event loop — но только для HTTP
- в JEE — потоки во все поля
- PHP: kak.serpom.po.yaitsam/phpdaemon
- почти никто не использует
- Go: goroutine, но не совсем
- Lua+nginx: ngx_lua/cosocket
- Механизмы разные в разных ОС ⇒ libevent, libev
Событийная машина node.js @@
- Отличительная черта: вообще нет блокирующего I/O
- тут-то колбэки JS и становятся преимуществом
- Реально работает: тест 1M соединений на 1 сервере
- Кроме node.js такое есть только в Erlang
- 1M соединений переваривает даже успешнее — на продакшне у WhatsApp
- и gc быстрый (кучи отдельные)
- но… erlang. свои минусы и начинает хотеться монад
- Million RPS Battle
Можно писать и на префорке @@ %%
Но…
«Хорошо быть девочкой в розовом пальто,
можно и не девочкой, но уже не то»
Обзор языка @@ %%
и приколы
Типы данных @@
Типы данных (что может вернуть typeof):
- null (не NULL, всё регистрозависимо)
- undefined ("не определено")
- number: 1, −100500.05, NaN
- boolean: true, false
- string: "hello1", 'hello2' (всегда Unicode)
- symbol — для «скрытых полей» (ES6)
- всё остальное — object
- хеш — объект дефолтного типа: { key: "value" }
- массив — разновидность объекта (класс Array): [ "value1", 2, 3, "value3" ]
- функции — тоже объекты, их typeof = function
Переменные @@
var a = 123;
Новый синтаксис ES6: let/const.
let a = 123; const b = 'hello';
- var локальны в рамках функции или Global Scope (в браузере — window)
- let и const локальны, как и положено, в рамках блока
Функции @@
Функции — объекты первого класса в JS.
function f(arg) { return arg; } var f = function(a) { return a*a; };
Новый синтаксис ES6: arrow functions
var f = a => a*a; var g = (a, b) => a*b;
Замыкания @@
Функции в JS являются замыканиями («замыкаются» на текущую область видимости):
function a(arg) { var sum = 0; var f = function(x) { sum += x; }; var g = function() { return sum; }; return [ f, g ]; }
Вызывать функцию можно с любым числом аргументов, пропущенные будут undefined, лишние — в arguments.
function a() { console.log(arguments[1]); // "abc" } a(1, "abc", 3, 4);
Прототипы (голый/старый JS) @@
function TreeGrid() { // конструктор. тут есть this } TreeGrid.prototype.method = function() { // метод класса. тут есть this console.log(this); } var obj = new TreeGrid(); obj.method();
Прототипы @@
(Очень простая альтернатива классам)
- Объекты «создаются из функций»
- У каждого объекта есть прототип
- Прототип — тоже объект, в нём функции и «свойства по умолчанию»
- У прототипа может быть свой прототип ⇒ наследование
- object.__proto__ — прототип этого объекта (класс, по сути)
- function.prototype — прототип, создаваемый этой функцией как конструктором
Наследование (голый JS) @@
Для наследования в prototype нужно присвоить объект, __proto__ которого ссылается на базовый класс.
function CalendarGrid() { TreeGrid.call(this, arguments); // "super()" } CalendarGrid.prototype = Object.create(TreeGrid.prototype); CalendarGrid.prototype.constructor = CalendarGrid;
this @@
- this передаётся отдельно, как «контекст вызова»
- this можно «подменить» через function.apply(obj, arguments) / function.call(obj, arg1, arg2, …)
- обычные функции/замыкания не помнят this (!)
- arrow functions помнят this
- this можно «запомнить» через function.bind() (IE9+ / FF4+)
- также через function.bind() можно сделать карринг
- (ну или явно создав замыкание)
this — примеры @@
var obj = new TreeGrid(); obj.method(); // this = obj var f = obj.method; f(); // this = window f.apply(obj); // this снова = obj f = obj.method.bind(obj); f(); // f привязана к obj (this = obj) f = obj.method.bind(obj, "hello"); // карринг f("arg2"); // эквивалентно obj.method("hello", "arg2"); f = obj.method; f = function() { return f.apply(obj, arguments); } f(); // то же самое вручную
this и замыкания @@
TreeGrid.prototype.method = (arg1, arg2) => { // Так делать нельзя, this всегда = контексту, в котором описан метод (обычно window) console.log(this); }; TreeGrid.prototype.setListener = function() { // Так делать нельзя, this будет = контексту вызова (кликнутому HTML-элементу) this.table.addEventListener('click', this.handleClick); // И так тоже делать нельзя this.table.addEventListener('click', function(event) { this.handleClick(event); }); // А вот так можно (оп-па!) this.table.addEventListener('click', (event) => this.handleClick(event)); // Ну и вот так тоже можно var self = this; this.table.addEventListener('click', function(event) { self.handleClick(event); }); };
Стандартные конструкции @@
- if () {} else if () {} else {} (тело можно однострочное)
- C-подобный for: for (var i = 0; i < 100; i++) {}
- while () {}
- do {} while ()
- break, continue
- C-подобный switch: switch (x) { case 1: case 2: break; default: alert('x'); }
- try { throw 'x'; } catch(e) {} finally {}
Интересные конструкции @@
- a == b (мягкое сравнение) и a === b (точное сравнение)
- "" == false, 0 == false, "1" == true, 1 == true, "5" == 5, null == undefined
- regexp literals: var re = /<html[^<>]*>/i;
- break label, continue label
- Цикл по ключам: for (var i in obj) {}
- Цикл по значениям (ES6): for (var i of obj) {}
function a(a1, a2) { label: for (var i of a1) for (var j of a2) if (!a2[j]) continue label; }
Приколы: приведение типов @@
Приколов в JS немного, но они есть.
// "+" переопределён для строк console.log("100"+1 == "1001"); console.log("100"-1 == 99); // можно приводить к числу через a-0 или 1*a // в ключах хеша true, false, null и undefined превращаются // в строки "true", "false", "null" и "undefined" // имхо, в PHP (true="1", false="", null="") сделано логичней var a = {}; a[false] = 123; console.log(a["false"] == 123);
Приколы: var @@
// var локальны в функциях! function f() { for (var i = 0; i < 100; i++) { // через 0.5сек 100 раз напечатает "100" setTimeout(function() { console.log(i); }, 500); } } // и определяются как будто в начале! var a = 42; function foo() { alert(typeof a); var a = 10; } foo(); // --> не 42, а undefined!
Приколы: неоднозначный разбор @@
// эта функция, увы, вернёт undefined ({} станет блоком, а key: меткой) // хеши начинать строго с той же строки! function fn() { return { key: "value" }; } // (function() {})() - определение + вызов функции, типично, чтобы не засорять контекст // точки с запятой опциональны, но лучше их ставить. иначе: A.prototype.m = function() { } /* вот тут нужна ; */ (function() { B.prototype.m = ... })(); // Эквивалентно A.prototype.m = ( ( function(){} ) ( function() { B.prototype.m = ... } ) )();
ES6 @@ %%
Он же ES2015. А также ES2016, ES2017
http://es6-features.org/, https://babeljs.io/repl/
Фичи ES6 @@
Уже сказал про:
- let/const
- arrow functions
- for .. of
Вычислимые свойства объектов @@
ES6
var b = "key"; var a = { [b+"_value"]: "v2" }; // выражение в []
ES5
var b = "key2"; var a = { key: "value" }; // ключи - только литералы, можно без кавычек a[b] = 'v2'; // динамические ключи только так
Destructuring @@
«Деструктивное присваивание»
var obj = { key: 'abc', value: [ 1, 2 ] }; var { key: k, value: [ a, b ], other: o = 3 } = obj; // 3 - значение по умолчанию // можно в параметрах функции! [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ] .reduce(function(obj, [ k, v ]) { obj[k] = v; return obj; }, {});
Оператор распаковки @@
function f(...args) {} f(...iterable); // Кстати, в ES5 нельзя сделать apply конструктору. А тут: new Object(...args); // Распаковка массива let arr = [ ...iterable, 4, 5, 6 ]; // слияние объектов. у каждого фреймфорка было своё - $.extend, Ext.extend, Object.assign let merge_obj = { ...obj1, ...obj2, key: "value" }; // Распаковка в массив [ a, b, ...other ] = [ 1, 2, 3, 4, 5 ];
Упрощённые названия ключей @@
ES6
obj = { x, y, a() { return 'abc'; } };
ES5
obj = { x: x, y: y, a: function a() { return 'abc'; } };
Классы (!) @@
«Наконец-то», скажете вы.
class Shape { constructor (id, x, y) { this.id = id; this.move(x, y); } move (x, y) { this.x = x; this.y = y; } }
Наследование и property @@
class Rectangle extends Shape { constructor (id, x, y, width, height) { super(id, x, y); this._width = width; this._height = height; } static default() { return new Rectangle("default", 0, 0, 100, 200); } set width(width) { this._width = width; } get width() { return this._width; } get area() { return this._width*this._height; } }
Нюанс: IE8+, так как при трансляции в ES5 требуют Object.defineProperty().
Генераторы @@
Функции, из которых можно выйти и вернуться. Пожалуй, самая крутая из всех доработок!
function* a() { try { var v = yield 1; if (v < 0) yield 2; else yield 3; } catch (e) { yield "error"; } return "final"; } var b = a(); // объект GeneratorFunction. функция ещё не начала выполняться b.next(); // вернёт { value: 1, done: false } b.next(-5); // передаст -5 внутрь генератора. и вернёт { value: 2, done: false } b.throw(new Error("x")); // передаст исключение внутрь генератора. и вернёт { value: "error", done: false} b.next(); // вернёт { value: "final", done: true }
Чем генераторы круты? @@
Они дают убрать лестницу колбэков! Пример лестницы (node-postgres):
const pg = require('pg'); function makeQueries(callback) { var client = new pg.Client(); client.connect(function(err) { if (err) throw err; client.query('SELECT $1::text as name', ['brianc'], function (err, result) { if (err) throw err; client.end(function (err) { if (err) throw err; callback(result); }); }); }); } makeQueries(function(result) { console.log(result.rows[0]); });
А теперь с генераторами @@
Генератор можно приостановить. Пишем обёртку и получаем coroutine:
const gen = require('gen-thread'); const pg = require('pg'); function* makeQueries() { var client = new pg.Client(); // yield подождёт, пока не выполнится автоматически созданный колбэк gen.ef() yield client.connect(gen.ef()); var result = (yield client.query('SELECT $1::text as name', ['brianc'], gen.ef()))[0]; yield client.end(gen.ef()); return result; } gen.run(makeQueries(), function(result) { console.log(result.rows[0]); });
Промисы и async/await @@
То же, но стандартно — делается через промисы и async/await.
function sleep(millis) { return new Promise(function(resolve, reject) { setTimeout(resolve, millis); }); } async function f() { await sleep(500); await sleep(1000); } // эквивалентно цепочке промисов: function f() { return sleep(500).then(result => sleep(1000)); }
Это уже не ES6 (2015), а 2016-2017; но Babel всё равно их поддерживает (и транслирует в генераторы).
Поддержка Promise @@
API с колбэками надо оборачивать. Это нетрудно, но надо знать, куда ставить колбэк:
function wrap(fn, ...args) { return new Promise(function(resolve, reject) { try { fn(resolve, ...args); } catch (e) { reject(e); } }); } async function test() { await wrap(setTimeout, 500); }
Нюансы с исключениями @@
Нюанс 1: Promise'ы nodejs глотают исключения
Решение — Bluebird, он бросает Unhandled rejection error
Нюанс 2: У асинхронных исключений нет вменяемого стека.
Стеки в духе:
at Connection.parseE (node_modules/pg/lib/connection.js:554:11) at Connection.parseMessage (node_modules/pg/lib/connection.js:381:17) at Socket.<anonymous> (node_modules/pg/lib/connection.js:117:22) at emitOne (events.js:77:13) at Socket.emit (events.js:169:7) at readableAddChunk (_stream_readable.js:146:16) at Socket.Readable.push (_stream_readable.js:110:10) at TCP.onread (net.js:523:20)
Решение — опять-таки Bluebird + Promise.config({ longStackTraces: true }).
gen-thread @@
- Ну, или забить пока на промисы
- Юзать генераторы и мой gen-thread (в промисы он тоже умеет)
yield gen.p(<PROMISE>);
Но в итоге, конечно, все перейдут на Promise.
Модули ES6 @@
// lib/math.js export function sum (x, y) { return x + y }; export var pi = 3.141593; // lib/mathplusplus.js export * from "lib/math"; export var e = 2.71828182846; export default (x) => Math.exp(x); // someApp.js import exp, { pi, e } from "lib/mathplusplus"; console.log("e^{π} = " + exp(pi));
Оговорка: чёткой уверенности, что это лучше CommonJS, у меня нет. Но гибче, да.
Template strings @@
PHP-подобная строковая интерполяция.
let name = "John"; `Hello, ${name}`;
Кастомная интерполяция (для DSL, видимо):
get`http://example.com/foo?bar=${bar + baz}&quux=${quux}`; // эквивалентно: get([ "http://example.com/foo?bar=", "&quux=", "" ], bar + baz, quux);
Proxy @@
let target = { foo: "Welcome, foo" }; let proxy = new Proxy(target, { get (receiver, name) { return name in receiver ? receiver[name] : `Hello, ${name}`; } }); proxy.foo === "Welcome, foo"; proxy.world === "Hello, world";
Функции, локальные в блоках @@
(Block-scoped functions)
{ function foo () { return 1 } foo() === 1 { function foo () { return 2 } foo() === 2 } foo() === 1 }
Итераторы для for .. of @@
let fibonacci = { [Symbol.iterator]() { let pre = 0, cur = 1; return { next() { [ pre, cur ] = [ cur, pre + cur ]; return { done: false, value: cur }; } }; } } for (let n of fibonacci) { if (n > 1000) break; console.log(n); }
Обзор инструментов @@
- Пакетные менеджеры
- Трансляторы
- Сборка, преобразования и минификация
- Отладка, профилировка
- Редакторы, IDE
Системы модулей @@
- AMD (RequireJS): Asynchronous Module Definition
- CommonJS (node.js)
- UMD (AMD+CommonJS, поддерживается и там, и там)
- Модули ES6 (уже рассказал)
Наиболее актуальны CommonJS и ES6.
CommonJS @@
ИМХО — самый краткий и удобный синтаксис.
const pg = require('pg'); const gen = require('gen-thread'); module.exports.method = function() { /*...*/ }; // или даже: module.exports = MyClass; function MyClass() { } /*...*/
AMD @@
define(['jquery', 'underscore'], function ($, _) { // methods function a(){}; // private because it's not returned (see below) function b(){}; // public because it's returned function c(){}; // public because it's returned // exposed public methods return { b: b, c: c } });
UMD @@
(function (root, factory) { if (typeof define === 'function' && define.amd) define(['jquery', 'underscore'], factory); else if (typeof exports === 'object') module.exports = factory(require('jquery'), require('underscore')); else root.returnExports = factory(root.jQuery, root._); }(this, function ($, _) { function a(){}; // private because it's not returned (see below) function b(){}; // public because it's returned function c(){}; // public because it's returned return { b: b, c: c } }));
Пакетный менеджер NPM @@
- Использует систему модулей CommonJS
- Ставит зависимости рекурсивно (node_modules/module1/node_modules/module2/…)
- Можно их упростить: npm dedup
Пользоваться им не просто, а очень просто:
- Установить модуль: npm install [-g] [--save] [--save-dev] module
- Создать package.json для проекта: npm init
- Зарегистрироваться/залогиниться: npm login
- Опубликовать свой модуль: npm publish
Самих модулей столько, что аж страшно.
Bower @@
- Ещё один пакетный менеджер
- Ставится из npm O_o
- Команды примерно те же: install, init… те же semver’ы
- Основные отличия — AMD и плоское дерево зависимостей…
- то есть как будто вы просто сказали npm dedup
- только свалится, если что-то несовместимо по semver
- ⇒ Нафиг-нафиг.
Transpiler: Babel/Bublé @@
Как писать на ES6, если он не везде поддерживается?
Ответ — Babel!
- REPL: https://babeljs.io/repl/
- Командная строка: npm install babel-cli (babel-node)
- browserify: babelify
Transpiler — транслятор JS в JS. Другие: Bublé, Traceur.
Трансляторы @@
В JS транслируют всё, что можно и нельзя
- List of languages that compile to JS
- Тайпчекеры: TypeScript, Flow, Dart (язык)
- Kotlin, Ceylon, Scala.js, ClojureScript
- Непосредственно Java: TeaVM
- C++ (O_O): Emscripten (LLVM → JS), Mandreel
- Python: Pyjamas, Transcrypt; Ruby: Opal
- OCaml: Ocsigen/Bucklescript
- Haskell: ghcjs, haste
- И много чего ещё
Сборка, упаковка и минификация @@
Актуальное:
- browserify — упаковка npm-модулей (CommonJS) для браузера
- webpack — универсальная система сборки (AMD / CommonJS / ES6 модулей) + CSS + картинки + whatever
- stealjs — оптимизатор js для многостраничных сайтов
- eslint — проверка на ошибки и стиль
- gulp — запускатор скриптов сборки
- uglifyjs — обфускатор
Чуть старее @@
- grunt — запускатор скриптов сборки (часто grunt+bower)
- bower — пакетный менеджер (рассказал выше)
- requirejs — система загрузки и сборки AMD модулей
- jshint — проверка на ошибки и стиль
- yui-compressor, closure compiler — обфускаторы
- rollup — система сборки для ES6 модулей (используется реже)
IDE, редакторы, отладка @@
- Netbeans
- отладчик встроен
- (!) умеет живое обновление кода при отладке
- Atom от GitHub
- сам написан на node.js + webkit (Electron)
- напоминает Sublime
- модульный; отладчик — отдельный пакет
- Visual Studio Code
- мелкий и мягкий форк Atom’а
- встроен отладчик и поддержка typescript и C#
Отладка из консоли @@
nodejs debug app.js; Есть REPL
for (var i = 0; i < 5; i++) { debugger; // брейкпоинт console.log(i); } console.log("done");
Отладка из node-inspector @@
$ npm install -g node-inspector; node-debug app.js Node Inspector is now available from http://127.0.0.1:8080/?ws=127.0.0.1:8080&port=5858 Debugging `app.js` Debugger listening on port 5858
Браузерная часть @@
Ну, тут проблем нет совсем
- F12 даже в IE
- watchify
- есть фокусы с live reload (react/redux)
- chrome + netbeans connector
Профилировка @@
- node --prof, node --prof-process
- Профилировка памяти: heapdump
- Flamegraph: 0x анимация
- Ещё заметки
Тестирование @@
- Для браузера: Karma
- Запускает обычные тесты, но в открытом приложении в браузере
- В целом: Mocha, Chai, Sinon
Тайпчекеры: TypeScript, Flow @@
В целом очень похожи.
- У Flow лучше вывод типов
- У TypeScript есть public/private/protected
- У Flow есть Nullable типы (и он автоматически проверяет на null)
- TypeScript — надмножество JS
- Flow — подмножество JS
TS null @@
function foo(num: number) { if (num > 10) { return 'cool'; } }
// cool const result: string = foo(100); console.log(result.toString());
// still cool? console.log(foo(1).toString()); // error at runtime: "Cannot read property 'toString' of undefined"
Flow null @@
function foo(num: number) { if (num > 10) return 'cool'; } // error: call of method `toString`. // Method cannot be called on possibly null value console.log(foo(100).toString()); // error: return undefined. This type is incompatible with string function foo(num: number): string { if (num > 10) return 'cool'; }
TS vs Flow: generics @@
class Animal { name: string; constructor(name: string) { this.name = name; } } class Dog extends Animal { // just to make this different from cat goodBoyFactor: number; } class Cat extends Animal { purrFactor: number; }
TS vs Flow: присваивание элементов @@
OK в обоих
let cats: Array<Cat> = []; // только коты let animals: Array<Animal> = []; // только животные // неа, не кот cats.push(10); // неа, не кот cats.push(new Animal('Fido')); // круто, кот cats.push(new Cat('Purry')); // круто, кот - тоже животное animals.push(new Cat('Purry'));
TS vs Flow: присваивание коллекций @@
// error TS2322: Type 'Animal[]' is not assignable to type 'Cat[]'. // Type 'Animal' is not assignable to type 'Cat'. // Property 'purrFactor' is missing in type 'Animal'. //cats = animals; // во Flow не пройдёт // опа, в TS работает. но теперь animals небезопасны animals = cats; // потому что вот это тоже проходит без ошибки animals.push(new Dog('Brutus')); animals.push(new Animal('Twinky')); // упс cats.forEach(cat => console.log(`Cat: ${cat.name}`)); // Cat: Purry // Cat: Brutus // Cat: Twinky
Обзор фреймворков @@
- Клиентские фреймворки «старые»
- Клиентские фреймворки «новые»
- Попытки мобильной разработки
- Попытки игровых фреймворков
«Старые» @@
Заслуживают упоминания:
- jQuery (фиг сдохнет, а надо бы)
- ExtJS (тоже фиг сдохнет)
- R.I.P: Yahoo UI, PrototypeJS
- По-моему, сдыхает: Dojo
jQuery @@
«Меня зовут Мистер Свинья!»
Если скриптота — рас***дяйство, то jQuery — ВЕРХ рас***дяйства
Ибо РАСПОЛАГАЕТ к плохому коду:
- Всё в кучу: объект-бог с кучей хелперов $ и такие же модули (например, DataTables)
- $.extend(), $.ajax(), $("<html>"), $(".class")
- Селекторы / операции над множествами: $(".class").each(e => $(e).hide())
- если элементов не найдётся, no problem, ничего не сдохнет
- Страшные контроллеры, гвоздями прибитые к UI
- Отсутствие возможностей оптимизации при динамической сборке UI
- Пример — WikiEditor
ExtJS @@
- Desktop-like фреймворк, умеет кучу всего
- Фундаментально тормозноват
- Со стилизацией большие трудности
- Ну хоть тема в ExtJS >= 4 стала норм, можно смотреть без рвотных позывов
- По моему опыту — писать на нём НЕ проще и НЕ быстрее, чем на HTML+JS
- Data binding есть, но ограниченный — только свойств (а не иерархии) компонентов
«Новые» (компонентные) @@
- Data binding
-
Knockout, Ember, Backbone - упомянем для истории; неактуальны
- AngularJS 1, 2
-
- Virtual DOM
- React
И те, и другие — по сути, решают задачу шаблонизации на JS.
AngularJS 1 @@
- Dirty checking
- Все биндинги 2-way
- Поэтому медленный
- Есть компоненты («директивы») и контроллеры
- На этом уже можно писать
Пример @@
<div ng-controller="LoanCalculator"> <div> Дата выдачи: <input name="" type="text" ng-model="props.start" /><br /> Сумма: <input name="" type="text" ng-model="props.total" /><br /> Процент: <input name="" type="text" ng-model="props.percent" /><br /> Срок: <input name="" type="text" ng-model="props.months" /> месяцев<br /> Штраф за просрочку: <input name="" type="text" ng-model="props.fine" /><br /> Пеня % годовых на просрочку: <input name="" type="text" ng-model="props.penaltyPercent" /><br /> <input type="button" value="Рассчитать" ng-click="recalc()" /> <input type="button" value="Сбросить" ng-click="clear()" /> </div> <table ng-if="payments.length" border="0"> <tr> <th>Дата</th> <th>Сумма</th> <th>Комментарий</th> </tr> <tr ng-repeat="payment in payments"> <td><input name="" type="text" ng-model="payment.date" ng-change="clear_from($index+1)" /></td> <td><input name="" type="text" ng-model="payment.total" ng-change="clear_from($index+1)" /></td> <td>{{payment.text}}</td> </tr> <tr ng-if="clean"> <th>Всего</th> <td>{{sum}}</td> <td>{{outsums}}</td> </tr> </table> </div>
Angular 2 @@
- Пофиксили скорость, разделив 1-way и 2-way биндинги
- Перешли на TypeScript и всех агитируют
- Обновились в целом
- Больше «искаропки»
- Но зато тяжелее. Зачем-то тянет за собой RxJS…
React: почему я за React? @@
- JSX: на первый взгляд странно, на самом деле — круто и удобно!
- Строго однонаправленный поток данных
- (2-way binding — как чуть сложнее, так проблема)
- Правильная компонентность
- Убирает все сомнения в том, MV-что у нас — MVVM, MVC, M-V-VC, M-V-VC-VM…
- Контроллер занимается тем, чем и должен: работой с данными
- Легковесный, простой, изящный. Учится за 1 вечер. Не принуждает к дополнительным компонентам.
Почему JSX — круто? @@
Даёт писать шаблоны прямо на JS! Удобно и безопасно.
Например, цикл:
- Ember: {{# each}}
- Angular 1: ng-repeat
- Angular 2: ngFor
- Knockout: data-bind="foreach"
- React: ПРОСТО ИСПОЛЬЗУЙТЕ JS :) (обычный for или Array.map())
Angular 2 continues to put «JS» into HTML. React puts «HTML» into JS.
JSX @@
var MessageInList = React.createClass({ msgClasses: { unread: 'unread', unseen: 'unseen', answered: 'replied', flagged: 'pinned', sent: 'sent' }, render: function() { var msg = this.props.msg; return <div data-i={this.props.i} className={'message'+ (msg.body_text || msg.body_html ? '' : ' unloaded')+ (msg.flags.map(c => (this.msgClasses[c] ? ' '+this.msgClasses[c] : '')).join(''))+ (this.props.selected ? ' selected' : '')+ (msg.thread && this.props.threads ? ' thread0' : '')} onMouseDown={this.props.onClick}> <div className="icon" style={{ width: (20+10*(msg.level||0)), backgroundPosition: (10*(msg.level||0))+'px 7px' }}></div> <div className="subject" style={{ paddingLeft: (20+10*(msg.level||0)) }}>{msg.subject}</div> {msg.thread && this.props.threads ? <div className={'expand'+(msg.collapsed ? '' : ' collapse')}></div> : null} <div className="bullet"></div> <div className="from" style={{ left: (21+10*(msg.level||0)) }}> {(msg.props.sent ? 'To '+(msg.props.to[0][0]||msg.props.to[0][1]) : (msg.props.from ? msg.props.from[0]||msg.props.from[1] : ''))} </div> <div className="size">{Util.formatBytes(msg.size||0)}</div> <div className="attach" style={msg.props.attachments && msg.props.attachments.length ? null : { display: 'none' }}></div> <div className="time">{Util.formatDate(msg.time)}</div> </div> } });
JSX и Virtual DOM @@
- Как применять изменения быстро?
- (DOM — относительно медленный)
- Храним копию DOM в виде json
- После render() сравниваем
- Изменения применяем к реальному DOM
- ⇒ Быстро и при этом гибко
Компоненты React @@
- Обязательный метод ОДИН: render()
- props и state (XML-атрибуты и внутреннее состояние)
- children (тело элемента)
- getDefaultProps(), getInitialState()
- setState(). нет setProps() и setChildren()! (получает при render родителя)
- componentWillMount(), componentDidMount(), componentWillUnmount()
- componentWillReceiveProps(p), shouldComponentUpdate(p, s)
- componentWillUpdate(p, s), componentDidUpdate(p, s)
- propTypes, mixins, statics
А вот почему это красиво @@
ExtJS. Класс «панель». Где тут делать инициализацию?
beforerender? render? afterrender? afterlayout? added? add? show? спойлер: нигде
Библиотеки для React @@
- Контроллер: Flux
- Или Redux (2 кб!)
- react-router
- react-router-redux
- HTTP? superagent
- Draft.js (rich editor)
О Redux @@
- «State container»
- Однонаправленный поток данных:
- Store → Компоненты → Действия → Диспетчер → Store
- Компоненты «привязываются» к данным в Store
- И к вызовам действий
- Действие = reducer: (Состояние, Действие) → (НовоеСостояние).
- → Очень чёткое отделение контроллера
Как писать на React @@
- Набросать (или взять у дизайнеров) HTML-макет
- Выделить компоненты
- Помним, что компонент — единица рендера
- Сделать статическую версию на React, всё через props
- Выделить состояние, определить владельца состояния
- Можно уже брать Flux/Redux
- Presentational vs Controller
- Научить менять состояние
Прочее @@
- Мобильное
- React Native: Virtual DOM над нативными компонентами
- NativeScript: Тоже JS в отдельном потоке и свой XML-язык описания UI
- Уверен, появятся ещё
- https://html5gameengine.com/
- WebGL: SceneJS, OSG.JS, Blend4Web
Демонстрация LikeOpera @@ %%
Клон Opera Mail на React и node.js
Вопросы @@ %%
?