ECMAScript и все-все-все — различия между версиями
м |
|||
Строка 611: | Строка 611: | ||
</source> | </source> | ||
− | * '''var''' локальны в рамках функции или Global Scope (в | + | * '''var''' локальны в рамках функции или Global Scope (в браузере — window) |
* '''let''' и '''const''' локальны, как и положено, в рамках блока | * '''let''' и '''const''' локальны, как и положено, в рамках блока | ||
Строка 686: | Строка 686: | ||
* У прототипа может быть свой прототип ⇒ наследование | * У прототипа может быть свой прототип ⇒ наследование | ||
* {{mag|object.__proto__}} — прототип этого объекта (класс, по сути) | * {{mag|object.__proto__}} — прототип этого объекта (класс, по сути) | ||
+ | *: {{gray|невидим в IE <= 8; можно object.constructor.prototype}} | ||
* {{mag|function.prototype}} — прототип, создаваемый этой функцией как конструктором | * {{mag|function.prototype}} — прототип, создаваемый этой функцией как конструктором | ||
Строка 704: | Строка 705: | ||
=== this @@ === | === this @@ === | ||
− | * this передаётся отдельно, как | + | * this передаётся отдельно, как «контекст вызова» |
− | * this можно | + | * this можно «подменить» через '''function.apply(obj, arguments)''' / '''function.call(obj, arg1, arg2, …)''' |
* обычные функции/замыкания '''не помнят this''' (!) | * обычные функции/замыкания '''не помнят this''' (!) | ||
* arrow functions '''помнят''' this | * arrow functions '''помнят''' this | ||
− | * this можно | + | * this можно «запомнить» через function.bind() {{gray|(IE9+ / FF4+)}} |
*: также через function.bind() можно сделать '''карринг''' | *: также через function.bind() можно сделать '''карринг''' | ||
*: {{gray|(ну или явно создав замыкание)}} | *: {{gray|(ну или явно создав замыкание)}} | ||
− | === | + | === this — примеры @@ === |
<source lang="javascript"> | <source lang="javascript"> | ||
Строка 774: | Строка 775: | ||
* a == b (мягкое сравнение) и a === b (точное сравнение) | * a == b (мягкое сравнение) и a === b (точное сравнение) | ||
*: <tt><nowiki>"" == false, 0 == false, "1" == true, 1 == true, "5" == 5, null == undefined</nowiki></tt> | *: <tt><nowiki>"" == false, 0 == false, "1" == true, 1 == true, "5" == 5, null == undefined</nowiki></tt> | ||
+ | * regexp literals: <tt>var re = /<html[^<>]*>/i;</nowiki> | ||
* break label, continue label | * break label, continue label | ||
* Цикл по ключам: <tt><nowiki>for (var i in obj) {}</nowiki></tt> | * Цикл по ключам: <tt><nowiki>for (var i in obj) {}</nowiki></tt> | ||
Строка 848: | Строка 850: | ||
// Эквивалентно A.prototype.m = ( ( function(){} ) ( function() { B.prototype.m = ... } ) )(); | // Эквивалентно A.prototype.m = ( ( function(){} ) ( function() { B.prototype.m = ... } ) )(); | ||
</source> | </source> | ||
+ | |||
+ | == <span style="font-family: Consolas; background: #f1dd56; padding: 5px; font-style: normal">ES6</span> @@ %% == | ||
+ | |||
+ | Он же ES2015. А также ES2016, ES2017 | ||
+ | |||
+ | http://es6-features.org/, https://babeljs.io/repl/ | ||
+ | |||
+ | == Фичи ES6 @@ == | ||
+ | |||
+ | Уже сказал про: | ||
+ | * let/const | ||
+ | * arrow functions | ||
+ | * for .. of | ||
+ | |||
+ | == Функции, локальные в блоках @@ == | ||
+ | |||
+ | (Block-scoped functions) | ||
+ | |||
+ | <source lang="javascript"> | ||
+ | { | ||
+ | function foo () { return 1 } | ||
+ | foo() === 1 | ||
+ | { | ||
+ | function foo () { return 2 } | ||
+ | foo() === 2 | ||
+ | } | ||
+ | foo() === 1 | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | == Вычислимые свойства объектов @@ == | ||
+ | |||
+ | 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: 3 } = obj; // 3 - значение по умолчанию | ||
+ | |||
+ | // можно в параметрах функции! | ||
+ | [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ].reduce( | ||
+ | (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> | ||
+ | |||
+ | == Итераторы для 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> | ||
+ | |||
+ | == Классы (!) @@ == | ||
+ | |||
+ | «Наконец-то», скажете вы. | ||
+ | |||
+ | <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> | ||
+ | |||
+ | == super и 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(). | ||
[[Категория:VitaliPrivate]] | [[Категория:VitaliPrivate]] |
Версия 20:00, 10 октября 2016
- Автор
- Виталий Филиппов
- Дополнительный нижний колонтитул
- ECMAScript и все-все-все
Содержание
- 1 ECMAScript — ассемблер будущего, бэкенд, фронтенд и все-все-все @@ %%
- 2 О своих предпочтениях @@
- 3 О чём доклад? @@
- 4 Что такое JS? @@
- 5 Скриптота vs типизация @@ %%
- 6 Холивар!!! @@
- 7 Все хотят одного @@
- 8 Проверки — хорошо, но @@ %%
- 9 Почему JS? @@
- 10 Синтаксис @@ %%
- 11 Perl @@
- 12 PHP @@
- 13 Python @@
- 14 Ruby @@
- 15 Go @@
- 16 Erlang @@
- 17 OCaml O_O @@
- 18 Lua @@
- 19 JS @@
- 20 Синтаксис JS @@ %%
- 21 История JS @@
- 22 Дремучий период @@
- 23 Появление AJAX @@
- 24 Современное развитие @@
- 25 Производительность @@
- 26 Вычислительный бенчмарк @@
- 27 Почему V8 такой быстрый? @@
- 28 Ключевые слова о том, как это всё устроено @@
- 29 Отступление: PyPy @@ %%
- 30 LLVM @@
- 31 SSA @@
- 32 V8 JIT @@
- 33 Производительность @@
- 34 I/O bound, а не CPU bound @@
- 35 Событийные машины! @@ %%
- 36 Как вообще обрабатываются соединения клиентов? @@
- 37 Событийная машина @@
- 38 Так работает Nginx… @@ %%
- 39 А если пойти дальше? @@
- 40 Событийные машины @@
- 41 Событийная машина node.js @@
- 42 Можно писать и на префорке @@ %%
- 43 Обзор языка @@ %%
- 43.1 Типы данных @@
- 43.2 Переменные @@
- 43.3 Функции @@
- 43.4 Замыкания @@
- 43.5 Прототипы (голый/старый JS) @@
- 43.6 Прототипы @@
- 43.7 Наследование (голый JS) @@
- 43.8 this @@
- 43.9 this — примеры @@
- 43.10 this и замыкания @@
- 43.11 Стандартные конструкции @@
- 43.12 Интересные конструкции @@
- 43.13 Приколы: приведение типов @@
- 43.14 Приколы: var @@
- 43.15 Приколы: неоднозначный разбор @@
- 44 ES6 @@ %%
- 45 Фичи ES6 @@
- 46 Функции, локальные в блоках @@
- 47 Вычислимые свойства объектов @@
- 48 Destructuring @@
- 49 Оператор распаковки @@
- 50 Упрощённые названия ключей @@
- 51 Итераторы для for .. of @@
- 52 Классы (!) @@
- 53 super и property @@
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, 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 быстрая, и он быстрый. Мамонты видать шутят
- Но он же интерпретируемый?
- НЕТ! Интерпретируемых языков уже вообще нет.
- Ну, разве что bash…
Вычислительный бенчмарк @@
- C++ (g++ 5.3.1) -O2 = 1.124s
- java8 = 1.428s
- PyPy (tracing jit) = 1.72s
- node.js = 2s
- java7 = ~2.5s
- PHP 7 = 6.7s
- Python 3.5 = 19s, 2.7 = 21s
- Perl = 25s
- PHP 5.6 = 69s :))))) ахаха, прекрати
Почему 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
Производительность @@
- Итого, 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;</nowiki>
- break label, continue label
- Цикл по ключам: <tt>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
Функции, локальные в блоках @@
(Block-scoped functions)
{ function foo () { return 1 } foo() === 1 { function foo () { return 2 } foo() === 2 } foo() === 1 }
Вычислимые свойства объектов @@
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: 3 } = obj; // 3 - значение по умолчанию // можно в параметрах функции! [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ].reduce( (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'; } };
Итераторы для 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); }
Классы (!) @@
«Наконец-то», скажете вы.
class Shape { constructor (id, x, y) { this.id = id; this.move(x, y); } move (x, y) { this.x = x; this.y = y; } }
super и 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().