О себе Александр Кучеренко • В DataArt 1,5 месяца • Занимаюсь программированием 7+ лет • За это время писал на: PHP, JavaScript, ActionScript3, C#, Java(Android), Objective-C Поговорим о JavaScript 1. Объекты в JavaScript, лямбды, замыкания. 2. Deferred & Promises. 3. Библиотека Q. С чего начиналось С чего все начиналось: • Ajax • Web 2.0 • jQuery Что сейчас • Полноценные веб приложения (GoogleDoc, eyeOs, FirefoxOS, ChromeOS) • Мобильные приложения (PhoneGap, jQuery Mobile, Sencha Touch) • Серверный JavaScript (Node JS) • Инструменты (MongoDB, JSON, NPM, Bower, Grunt) • Тестирование: Buster.js, Karma, TestSwarm, JsTestDriver, YUI Yeti, Jasmine, QUnit, Sinon • 3D графика WebGL Базовые типы Элементарные • Number • Boolean • String • Null • undefined Объекты String Array Date Function … Number • Float64 - 8 байт • console.log(0.1+0.2) > 0.3 ? > 0.30000000000000004 • BCMath for JavaScript • 0.1234.toFixed(2) = 0.12 Number • При операциях с Number - никогда не происходят ошибки. Зато могут быть возвращены специальные значения • 1/0 = Number.POSITIVE_INFINITY (плюс бесконечность) • -1/0 = Number.NEGATIVE_INFINITY (минус бесконечность) • Положительная бесконечность Number.POSITIVE_INFINITY больше любого Number, и даже больше самой себя. Number • NaN - особый результат • Любая математическая операция с NaN дает NaN: – NaN + 1 = NaN • NaN не равен сам себе: – NaN == NaN // false • Можно проверить с помощью функции isNaN: – isNaN(NaN) // true String • Строки в javascript - полностью юникодные • Кавычки двойные и одинарные работают одинаково • Можно указывать юникодные символы через \uXXXX: • Встроены регулярные выражения, методы replace/match: "превед медвед".replace(/(.*?)\s(.*)/, "$2, $1!") // => медвед, превед! Boolean • False – – – – – – false null undefined “” 0 Number.NaN • True – все остальное – “0” (В PHP FALSE) – “false” undefined • При попытке доступа к глобальной переменной undefined (если она не изменена). • Неявный возврат из функции при отсутствии в ней оператора return. • Из операторов return, которые ничего не возвращают. • В результате поиска несуществующего свойства у объекта (и доступа к нему). • Параметры, которые не были переданы в функцию явно. • При доступе ко всему, чьим значением является undefined. undefined • undefined — это тип с единственным возможным значением: undefined • Не являясь константой, она не является и ключевым словом. Можно с лёгкостью переопределить // Happy debugging suckers ... undefined = true; (function(something, foo, undefined) { // в локальной области видимости `undefined` // снова ссылается на правильное значене. })('Hello World', 42); NULL • Используется во внутренних механизмах JavaScript (например для определения конца цепочки прототипов за счёт присваивания Foo.prototype = null) Объекты • В JavaScript всё ведет себя, как объект, лишь за двумя исключениями — NULL и UNDEFINED. • Почему у примитивов можно вызвать методы? Объекты и примитивы var number = 2; // Можно вызвать метод console.log(number.toString()); // -> 2 // Попробуем задать свойство number.newProperty = 3; console.log(number.newProperty); // -> undefined // Если нельзя, но сильно хочется, то - можно Number.prototype.newProperty = 3; console.log(number.newProperty); // -> 3 Function • Функции в JavaScript тоже являются объектами Пример №1: foo(); // сработает, т.к. функция будет создана до выполнения кода function foo() {} Пример №2: foo; // 'undefined' foo(); // вызовет TypeError var foo = function() {}; Область видимости Хотя JavaScript нормально понимает синтаксис двух фигурных скобок, окружающих блок, он не поддерживает блочную область видимости; всё что остаётся на этот случай в языке — область видимости функций. Пример №1: function test() { // область видимости for(var i = 0; i < 10; i++) { // не область видимости // считаем } console.log(i); // 10 } Как работает this Различают ровно пять вариантов того, к чему привязывается this в языке. 1. Глобальная область видимости Когда мы используем this в глобальной области, она будет просто ссылаться на глобальный объект. 2. Вызов функции foo(); // Тут this также ссылается на глобальный объект 3. Вызов метода test.foo(); // Тут this также ссылается на глобальный объект. 4. Вызов конструктора new foo(); Если перед вызовом функции присутствует ключевое слово new то данная функция будет действовать как конструктор. Внутри такой функции this будет указывать на новосозданный Object. Как работает this 5. Переопределение this Переопределить this можно с помощью методов apply, call и bind Пример №1: function foo(a, b, c) {} var bar = {}; foo.apply(bar, [1, 2, 3]); // массив развернётся в a = 1, b = 2, c = 3 foo.call(bar, 1, 2, 3); // аналогично Как работает this Пример самой распространённой ошибки: Foo.method = function() { function test() { // this ??? } test(); } Как работает this Пример самой распространённой ошибки: Foo.method = function() { function test() { // this ссылается на глобальный объект } test(); } Как работает this Достойно выходим из ситуации: Foo.method = function() { var that = this; function test() { // Здесь используем that вместо this } test(); } Как работает this А что если… var test = someObject.methodTest; test(); Как работает this А что если… var test = someObject.methodTest; test(); Следуя первому правилу test вызывается как обычная функция; следовательно this внутри него больше не ссылается на someObject Хотя позднее связывание this на первый взгляд может показаться плохой идеей, но на самом деле именно благодаря этому работает наследование прототипов. Замыкания Замыкание в JavaScript: function outerFn(myArg) { var myVar = 42; function innerFn() { // имеет доступ к myVar и myArg } } Замыкания Замыкание в PHP: function getNumber() { $id = 42; return function() use ($id){ return $id*2; }; } Замыкания Классика: for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); } , 1000);} Замыкания • Каждое выполнение функции хранит все переменные в специальном объекте с кодовым именем [[scope]], который нельзя получить в явном виде, но он есть • Каждый вызов var... - всего лишь создает новое свойство этого объекта, а любое упоминание переменной - первым делом ищется в свойствах этого объекта Замыкания Обычное выполнение функции без замыкания: function sum(x,y) { // неявно создался объект [[scope]] ... // в [[scope]] записалось свойство z var z; // нашли переменную в [[scope]], [[scope]].z = x+y z = x+y; // нашли переменную в [[scope]], return [[scope]].z return z; // функция закончилась, // [[scope]] никому больше не нужен и умирает вместе с z } Замыкания Замыкание - это когда объект локальных переменных [[scope]] внешней функции остается жить после ее завершения. Внутренняя функция может обратиться к нему в любой момент и получить переменную внешней функции. Замыкания Выполнение функции с замыканием: function addHideHandler(sourceId, targetId) { // создан объект [[scope]] со свойствами sourceId, targetId // записать в [[scope]] свойство sourceNode var sourceNode = document.getElementById(sourceId); // записать в [[scope]] свойство handler var handler = function() { var targetNode = document.getElementById(targetId); targetNode.style.display = 'none'; }; sourceNode.onclick = handler; // функция закончила выполнение // (***) и тут - самое интересное! } Замыкания При запуске функции все происходит стандартно: 1. 2. 3. создается [[scope]] туда записываются локальные переменные внутренняя функция получает ссылку на [[scope]] Но в самом конце - внутренняя функция присваивается sourceNode.onclick. Внешняя функция закончила свою работу, но внутренняя - может запуститься когда-нибудь потом. Интерпретатор javascript не проводит анализ - понадобятся ли внутренней функции переменные из внешней, и какие переменные могут быть нужны. Вместо этого он просто оставляет весь [[scope]] внешней функции в живых. Чтобы когда внутренняя функция запустится, если она вдруг не найдет какую-либо переменную в своем [[scope]] - она могла обратиться к [[scope]] внешней функции и нашла бы ее там. Замыкания Пример: function makeShout() { var phrase = "Превед!”; var shout = function() { alert(phrase); } phrase = "Готово!"; return shout; } shout = makeShout(); shout(); // что выдаст? Замыкания Внутри makeShout() Пример: function makeShout() { var phrase = "Превед!”; 2. В [[scope]] пишется: phrase="Превед!” var shout = function() { alert(phrase); } 3. В [[scope]] пишется: shout=..функция.. phrase = "Готово!"; return shout; } shout = makeShout(); shout(); 1. создается [[scope]] 4. При создании функция shout получает ссылку на [[scope]] внешней функции 5. [[scope]].phrase меняется на новое значение "Готово!” Замыкания При запуске shout() Пример: function makeShout() { 1. Создается свой собственный объект [[scope2]] var phrase = "Превед!”; var shout = function() { alert(phrase); } 2. Ищется phrase в [[scope2]] - не найден 3. Ищется phrase в [[scope]] внешней функции найдено значение "Готово!” phrase = "Готово!"; return shout; 4. alert("Готово!") } shout = makeShout(); shout(); То есть, внутренняя функция получает последнее значение внешних переменных. Замыкания Счетчик: function makeCounter() { var numberOfCalls = 0; return function() { return ++numberOfCalls; } } var counter1 = makeCounter(); console.log(counter1()); // -> 1 console.log(counter1()); // -> 2 console.log(counter1()); // -> 3 var counter2 = makeCounter(); console.log(counter2()); // -> 1 console.log(counter2()); // -> 2 lambda-функции VS анонимные функции • Согласно wiki синонимы • Есть подозрение что замешан python lambda х, у: х+у Почему асинхронность это сложно? Callback hell in action getUser(params["id"], function(err,user) { if(!user) return res.send(404); getProfile(user, function(err,profile) { if(err) return res.send(500); if(profile) { update(user,profile,params,function(err) { if(err) return res.send(500); res.send(profile); }) } else { create(user,params,function(err,profile) { if(err) return res.send(500); res.send(profile); }) } }) }); Это можно поправить getUser(params["id"],gotUser); function gotUser(err,user) { if(!user) return res.send(404); // don't inline closure, use bind getProfile(user,saveProfile.bind(this,user)); } function saveProfile(user,err,profile) { if(err) return res.send(500); if(profile) { update(user,profile,params,saved); } else { create(user,params,saved); } } Выполнение параллельных задач var updatedA = false; var updatedB = false; updateA(function() { if(updatedB) done() updatedA = true; }) updateB(function() { if(updatedA) done() updatedB = true; }) Когда задач много var files = [a,b,c,d]; var filtered = []; var done = 0; files.forEach(function(file){ something(file, function(passed) { if(!passed) return; filtered.push(f); done += 1; if(done == files.length) cb(); }) }); Deferred and Promises Deferred — это отложенный результат, который станет известен через некоторое время. Deferred and Promises readFile("file.txt", function (err, result) { // continue here… }); // becomes var promiseForResult = readFile("file.txt"); Deferred and Promises Инструкция function readFile(fileName){ 1. Создать Deferred объект 2. При завершении асинхронного метода перевести Deferred объект в нужное состояние 3. Передать Deferred.promise объект из текущей функции куда-то, где его состояние будут отслеживать. var deferred = Q.defer(); readFileAsync(fileName, function(error, result){ if(error){ deferred.reject(error); }else{ deferred.resolve(result); } }); return deferred.promise; } Почему при возврате из функции передается не сам Deferred объект, а именно Promise? Deferred and Promises • Deferred объект — это всего лишь хранилище состояния асинхронной функции. Таких состояний обычно несколько: – – – pending — ожидание завершения процесса rejected — процесс закончен падением resolved — процесс закончен успешно • Кроме того у Deferred объекта есть ряд методов, которые могут менять его состояние. Например, метод .resolve(). • По состоянию Deferred объекта мы можем судить, закончен ли процесс, состояние которого мы отслеживаем. Deferred and Promises • Обработчики выполняются последовательно • Если обработчик «выполнения»/«ошибки» добавляется к уже «выполненному»/«отменённому» объекту, то он будет вызван немедленно. • Повторный вызов resolve/reject не приводт к изменению состояния и повторному вызову обработчиков, а просто игнорируется (и не важно, что было вызвано до этого resolve() или reject()). • Хотя promises появились в jQuery 1.5 лучше их не юзать Библиотека Q • Установка: npm, bower, NuGet • Модули для: Node.js, CommonJS, AMD, microjs • Понимает промисы: jQuery, Dojo, When.js, WinJS • Множество сторонних модулей • mongoose-q, mongo-q Библиотека Q • .then, .done, .catch(fail), .finally(fin) readFile("file.txt") .then(function(contentOfFile){ // Do something with content }) .catch(function(error){ // Handle error }) .finally(function(){ // Call always }) .done(); Библиотека Q • .then – выполняем последовательно readFile("file.txt") .then(function(contentOfFile){ // Do something with contentOfFile return readFile("file2.txt") }) .then(unction(contentOfFile2){ // Do something with contentOfFile2 return readFile("file3.txt") }) .catch(function(error){ // Error handle }) .done() Библиотека Q • .then – выполняем последовательно readFile("file.txt") .then(readFile("file2.txt")) .then(readFile("file3.txt")) .catch(function(error){ // Error handle for file, file2 and file3 }) .then(readFile("file4.txt")) .then(readFile("file5.txt")) .catch(function(error){ // Error handle for file4 and file5 }) .done() Библиотека Q • .when, .all – выполняем параллельно Q.when(readFile("file.txt"), readFile("file2.txt")) .catch() .done() Q.all([readFile("file.txt"), readFile("file2.txt”]) .catch() .done() Библиотека Q • .spread Q.all([readFile("file.txt"), readFile("file2.txt”]) .spread(function(contentOfFile, contentOfFile2){ // Do something with contentOfFile1-2 }) .catch() .done() getUsername() .then(function (username) { return [username, getUser(username)]; }) .spread(function (username, user) { // do something }) .catch() .done(); Библиотека Q Sequences var funcs = [foo, bar, baz, qux]; funcs.reduce(Q.when, Q()); Q in Node.js // 1 Q.nfcall(FS.readFile, "foo.txt", "utf-8"); // 2 var readFile = Q.denodeify(FS.readFile); return readFile("foo.txt", "utf-8"); // 3 var deferred = Q.defer(); FS.readFile("foo.txt", "utf-8", deferred.makeNodeResolver()); return deferred.promise; Q and jQuery Q(jQuery.ajax("foobar.html”)) .then(function (data) { // on success }) .catch() .done() Вопросы Ссылочки Полезно почитать: • http://bonsaiden.github.io/JavaScript-Garden/ NO MORE PYRAMIDS • http://timruffles.github.io/reject-js-2013-talk/#/ Lib Q • https://github.com/kriskowal/q Мой Github: • https://github.com/AlexTiTanium