ECMAScript и все-все-все

Материал из YourcmcWiki
Перейти к: навигация, поиск
Автор

Виталий Филиппов
Дополнительный нижний колонтитул

ECMAScript и все-все-все

Содержание

ECMAScript — ассемблер будущего,
бэкенд, фронтенд и все-все-все @@ %%

(Об эволюции и фичах JavaScript)

ES6.jpg

Виталий Филиппов, 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 типизация @@ %%

script_kiddies_demotivator.jpeg

Холивар!!! @@

  • Шутки в сторону: тема серьёзная
  • Популярных динамических языков много
    можно пытаться это отрицать, но таки удобно
  • Статические… хде они? Java? (C# не в счёт, винда)
  • Компилируемые новые есть: D, Rust, Go, Vala, Swift
    но кто на них пишет-то?

Все хотят одного @@

  • Типизация — не необходимость, как раньше, а лишь один из способов проверки
    что ещё можно проверять?
    Rust: borrow checker
    функциональщина: «purity» checker
    Java: checked exceptions
  • При этом
    auto уже даже в C++
    тайпчекер (частично) уже даже в PHP (+ Hack)

Проверки — хорошо, но @@ %%

414874_paranojya.jpg

Почему 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 (LLVM B3)

four_tier_performance.png

Ключевые слова о том, как это всё устроено @@

  • Какой бывает JIT?
    method-based jit (JVM)
    tracing jit (PyPy, TraceMonkey)
  • Девиртуализация
  • Ускорение поиска в хеше (Lua)
  • OSR (On-Stack Replace)

Отступление: PyPy @@ %%

Трассирующий JIT-компилятор для Python, очень медленный

Рисует множество Мандельброта при сборке

2c32601f8747c5e93becc08270fa5127.png

LLVM @@

LLVM (http://llvm.org), ранее «Low Level Virtual Machine»

  • Набор библиотек для построения компиляторов/интерпретаторов
  • Модульный
    исходник → фронтенд (ЯП) → LLVM IR (SSA)
    IR → оптимизатор LLVM → IR
    IR → бэкенд → машинный код
  • А также сами компиляторы (в первую очередь C/C++/ObjC: Clang)
  • На LLVM сделаны компилятор шейдеров Radeon и OpenCL

LLVMCompiler1.png

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 (фронтенды)

Nginx-logo.png

(плюс 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 @@

Можно писать и на префорке @@ %%

Но…

«Хорошо быть девочкой в розовом пальто,
можно и не девочкой, но уже не то»

Обзор языка @@ %%

Типы данных @@

Типы данных (что может вернуть 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 ()
  • C-подобный switch:
  • break, continue
  • try { throw 'x'; } catch(e) {} finally {}

Интересные конструкции @@

  • a == b (мягкое сравнение) и a === b (точное сравнение)
    "" == false, 0 == false, "1" == true, 1 == true, "5" == 5, null == undefined
  • 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 = ... } ) )();