«И давно вы занимаетесь программизмом?»
JavaScriptECMAScript
Сравнительный анализ
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.">"; }
$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';
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)
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
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") }
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;
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 ;
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
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); }
Имхо вполне нейтральненько.
(неприятных рефлексов вызывать не должен)
1995—1997
1998
2000
2004
Совместимость браузеров ещё плохая, так что
На ЛОРе до сих пор шутят, что «Java тормозит», а что ж тогда JS?
LLVM (http://llvm.org), ранее «Low Level Virtual Machine»
Пример 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
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; }
("event loop")
Все писали сетевой сервер на C? :)
Обычный (блокирующий) ввод/вывод:
Неблокирующий ввод/вывод:
Кроме HTTP-запросов клиентов ещё есть:
Почему бы всё это не обрабатывать в одном цикле?
⇒ Это и будет «событийная машина»
Почти везде опциональная сущность:
Но…
«Хорошо быть девочкой в розовом пальто,
можно и не девочкой, но уже не то»
и приколы
Типы данных (что может вернуть typeof):
var a = 123;
Новый синтаксис ES6: let/const.
let a = 123; const b = 'hello';
Функции — объекты первого класса в 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);
function TreeGrid() { // конструктор. тут есть this } TreeGrid.prototype.method = function() { // метод класса. тут есть this console.log(this); } var obj = new TreeGrid(); obj.method();
(Очень простая альтернатива классам)
Для наследования в prototype нужно присвоить объект, __proto__ которого ссылается на базовый класс.
function CalendarGrid() { TreeGrid.call(this, arguments); // "super()" } CalendarGrid.prototype = Object.create(TreeGrid.prototype); CalendarGrid.prototype.constructor = CalendarGrid;
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(); // то же самое вручную
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); }); };
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 локальны в функциях! 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 = ... } ) )();
Он же ES2015. А также ES2016, ES2017
Уже сказал про:
ES6
var b = "key"; var a = { [b+"_value"]: "v2" }; // выражение в []
ES5
var b = "key2"; var a = { key: "value" }; // ключи - только литералы, можно без кавычек a[b] = 'v2'; // динамические ключи только так
«Деструктивное присваивание»
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; } }
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.
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 всё равно их поддерживает (и транслирует в генераторы).
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 }).
yield gen.p(<PROMISE>);
Но в итоге, конечно, все перейдут на Promise.
// 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, у меня нет. Но гибче, да.
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);
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 }
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); }
Наиболее актуальны CommonJS и ES6.
ИМХО — самый краткий и удобный синтаксис.
const pg = require('pg'); const gen = require('gen-thread'); module.exports.method = function() { /*...*/ }; // или даже: module.exports = MyClass; function MyClass() { } /*...*/
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 } });
(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 } }));
Пользоваться им не просто, а очень просто:
Самих модулей столько, что аж страшно.
Как писать на ES6, если он не везде поддерживается?
Ответ — Babel!
В JS транслируют всё, что можно и нельзя
Актуальное:
nodejs debug app.js; Есть REPL
for (var i = 0; i < 5; i++) { debugger; // брейкпоинт console.log(i); } console.log("done");
$ 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
Ну, тут проблем нет совсем
В целом очень похожи.
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"
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'; }
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; }
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'));
// 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 — ВЕРХ рас***дяйства
Ибо РАСПОЛАГАЕТ к плохому коду:
И те, и другие — по сути, решают задачу шаблонизации на JS.
<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>
Даёт писать шаблоны прямо на JS! Удобно и безопасно.
Например, цикл:
Angular 2 continues to put «JS» into HTML. React puts «HTML» into JS.
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> } });
Клон Opera Mail на React и node.js
?