Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

Михаил Давыдов Разработчик JavaScript JavaScript ООП

Slide 3

Slide 3 text

3 JavaScript ООП •  Нет классов –  Но можно эмулировать их •  Есть прототипы •  Есть наследование на прототипах –  Делегирующее прототипное наследование •  Все можно менять во время работы –  Цепочку наследования можно менять –  Прототипы можно менять –  На классах так сделать нельзя •  Можно изменить прототипы базовых "классов"

Slide 4

Slide 4 text

4 Сказка о мутантах

Slide 5

Slide 5 text

5 Сказка о мутантах •  В далекой-далекой галактике •  Нет привычного нам наследования •  Есть телепатическое наследование –  "Телегенез" •  Действующие лица: –  Дедушка –  Отец –  Сын

Slide 6

Slide 6 text

6 Структура мутанта Мутант "Телепатические" гены Собственные гены Движение генов

Slide 7

Slide 7 text

7 Все зеленые Color   Дед Отец Сын

Slide 8

Slide 8 text

8 Дед: хочу стать синим! Color   Дед Отец Сын

Slide 9

Slide 9 text

9 Все посинели Color   Дед Отец Сын

Slide 10

Slide 10 text

10 Отец: верну-ка я цвет Color   Color   Дед Отец Сын

Slide 11

Slide 11 text

11 Дед синий, отец и сын зеленые Color   Color   Дед Отец Сын

Slide 12

Slide 12 text

12 Сын: хочу быть черным Color   Color   Color   Дед Отец Сын

Slide 13

Slide 13 text

13 Мутанты и JavaScript Size,   Age   Color   Объект Свойства прототипа Собственные свойства Делегирование Цепочка прототипов

Slide 14

Slide 14 text

14 Собственные свойства и прототип •  Собственные свойства •  Свойства прототипа •  Любой объект имеет ссылку на прототип –  И примитив также* –  Имеет с рождения –  По умолчанию – Object.prototype •  Делегирование –  Мы можем пользоваться функциями прототипа не имея собственных •  Цепочка прототипов –  Каждый прототип это тот же объект –  Который также может иметь прототип –  У прототипа прототипа также может быть прототип

Slide 15

Slide 15 text

Вызывает функцию как конструктор Строит цепочку прототипов Оператор new

Slide 16

Slide 16 text

16 Работа оператора new •  new(Constructor, arguments):*! •  Получает на вход 2 операнда –  Функция должна иметь свойство prototype •  Создает временный объект (obj) •  Добавляет свойство __proto__ –  obj.__proto__ = Constructor.prototype •  Вызывает конструктор над объектом –  Constructor.apply(obj, arguments) •  Конструктор вернул примитив. Результат obj •  Иначе то, что вернул конструктор

Slide 17

Slide 17 text

17 new Smth() может вернуть не инстанс Smth!

Slide 18

Slide 18 text

18 function Constructor() { // no body } new Constructor() instanceof Constructor === true; // OK // Подмена результата function Constructor () { return {}; // <<< } new Constructor() instanceof Constructor === false; // <<< // Аналогично function Constructor() {return []} function Constructor() {return function () {}} Подмена инстанса

Slide 19

Slide 19 text

19 It isn't JavaScript bug, it is feature!

Slide 20

Slide 20 text

20 function myNew(Constructor, args) { if (typeof Constructor !== "function") { throw new TypeError(); } if (typeof Constructor.prototype === "undefined") { throw new TypeError(); } var obj = { __proto__: Constructor.prototype }; var result = Constructor.apply(obj, args); if (typeof result === "object" && result !== null || typeof result === "function") { return result; } return obj; } Оператор new в коде

Slide 21

Slide 21 text

21 Во многих браузерах __proto__ скрытое свойство. Менять и получать нельзя!

Slide 22

Slide 22 text

22 // Конструктор Grandfather var Grandfather = function () {}; Grandfather.prototype.color = 'green'; Пример new var gf = new Grandfather(); gf.color; // "green" // Это аналогично var gf = { __proto__: Grandfather.prototype }; gf.color; // "green"

Slide 23

Slide 23 text

23 Оператор new используется для построения цепочек прототипов

Slide 24

Slide 24 text

Цепочка прототипов это способ наследования в JavaScript

Slide 25

Slide 25 text

25 Цепочка прототипов // Конструктор Grandfather var Grandfather = function () {}; Grandfather.prototype.color = 'green'; // Конструктор Father var Father = function () {}; typeof Father.prototype === "object"; // Для цепочки нам нужно получить вот это Father.prototype = { __proto__: Grandfather.prototype };

Slide 26

Slide 26 text

26 Строим цепочку прототипов явно // Конструктор Father var Father = function () {}; Father.prototype = new Grandfather(); // Как помним, это аналогично: Father.prototype = { __proto__: Grandfather.prototype };

Slide 27

Slide 27 text

27 Не забываем! __proto__ лучше установить явно – через оператор new

Slide 28

Slide 28 text

28 var Grandfather = function () {}; // Конструктор Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; // Конструктор Father Father.prototype = new Grandfather(); // Наследуем var Son = function () {}; // Конструктор Son Son.prototype = new Father(); // Наследуем var g = new Grandfather(); // Экземпляр "класса" Grandfather var f = new Father(); // Экземпляр "класса" Father var s = new Son(); // Экземпляр "класса" Son // Изначально все зеленые console.log([g.color, f.color, s.color]); // ["green", "green", "green"] Пример с мутантами

Slide 29

Slide 29 text

29 // Дед решил поменять свой цвет и цвет потомства Grandfather.prototype.color = 'blue'; // Все синие console.log([g.color, f.color, s.color]); // ["blue", "blue", "blue"] // Отец решил все вернуть для себя и своего потомства Father.prototype.color = 'green'; // Хотя мог исделать и так: // Grandfather.prototype.color = 'green'; // Цвет вернулся console.log([g.color, f.color, s.color]); // ["blue", "green", "green"] Пример с мутантами

Slide 30

Slide 30 text

30 // Смысла нет Grandfather.prototype.color = 'blue'; console.log([g.color, f.color, s.color]); // ["blue", "green", "green"] // Сын решил поменял только собственное свойство s.color = 'black'; console.log([g.color, f.color, s.color]); // ["blue", "green", "black"] var SonsSon = function () {}; // Конструктор SonsSon SonsSon.prototype = new Son(); // Наследуем var ss = new SonsSon(); // Экземпляр "класса" SonsSon console.log([g.color, f.color, s.color, ss.color]); // ["blue", "green", "black", "green"] Пример с мутантами

Slide 31

Slide 31 text

31 Цепочка прототипов: Grandfather g __proto__ object Grandfather.prototype color blue __proto__ object Object.prototype __proto__ null

Slide 32

Slide 32 text

32 В конце экземпляр Son будет таким. Hell Mess… var s = { color: 'black', // Поменял только собственное свойство __proto__: { // Son.prototype __proto__: { // Father.prototype color: 'green', // Отец решил вернуть цвет __proto__: { // Grandfather.prototype color: 'blue', // Дед решил поменять цвет __proto__: { // Object.prototype // Много разных свойств __proto__: null } } } } }; Цепочка прототипов: Son

Slide 33

Slide 33 text

33

Slide 34

Slide 34 text

34 А что, если в конструкторе alert(), а если он добавляет свойства?

Slide 35

Slide 35 text

35 alert('Mua-ha-ha') // Конструктор Grandfather var Grandfather = function () { alert('Mua-ha-ha'); return ["Mua-ha-ha!"]; }; Grandfather.prototype.color = 'green'; // Конструктор Father var Father = function () {}; Father.prototype = new Grandfather();

Slide 36

Slide 36 text

36 alert('Mua-ha-ha') // Конструктор Grandfather var Grandfather = function () { alert('Mua-ha-ha'); return "Mua-ha-ha!"; }; Grandfather.prototype.color = 'green'; // Конструктор Father var Father = function () {}; Father.prototype = new Grandfather();

Slide 37

Slide 37 text

37 Используется для чистого наследования цепочки прототипов new – это просто средство подмешать prototype function inherits(Constructor, SuperConstructor) { var F = function () {}; // Временный, чистый конструктор // Сохраняем ссылку F.prototype = SuperConstructor.prototype; // Применяем __proto__ = prototype Constructor.prototype = new F(); } Функция inherits или подобная var Grandfather = function () {}; // Конструктор Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; // Конструктор Father // Father.prototype = new Grandfather(); inherits(Father, Grandfather); // Наследуем

Slide 38

Slide 38 text

38 Есть еще один вариант использовать Object.create(); Только ECMAScript 5 var Grandfather = function () {}; // Конструктор Grandfather Grandfather.prototype.color = 'green'; var Father = function () {}; // Конструктор Father // Father.prototype = new Grandfather(); // Что она делает - понятно Father.prototype = Object.create(Grandfather.prototype); // Полная и абсолютно честная версия Father.prototype = Object.create(Grandfather.prototype, { constructor: { value: Father, enumerable: false, writable: true, configurable: true } }); ECMAScript 5 – Object.create()

Slide 39

Slide 39 text

Оператор "точка" и [] Получение свойств

Slide 40

Slide 40 text

Используют цепочку прототипов и собственные свойства

Slide 41

Slide 41 text

41 Оператор "точка" и [] •  getProperty(obj, name): *! •  Ищет в собственных свойствах •  Нет? – ищем в цепочке прототипов •  Пока __proto__ !== null •  Не нашли – возвращаем undefined

Slide 42

Slide 42 text

42 function getProperty(obj, name) { // Ищем в собственных if (obj.hasOwnProperty(name)) { return obj[name]; } // Ищем рекурсивно в цепочке прототипов else if (obj.__proto__ !== null) { return getProperty(obj.__proto__, name); } // Не нашли else { return undefined; } } // Пример getProperty(s, "color") === s.color; Оператор "точка" и [] в коде

Slide 43

Slide 43 text

43 Цепочка прототипов объекта s s color black __proto__ object Son.prototype __proto__ object Grandfather.prototype color blue __proto__ object Object.prototype __proto__ null Участок цепочки Father пропущен

Slide 44

Slide 44 text

44 Храните функции в прототипе, а данные в собственных свойствах

Slide 45

Slide 45 text

Оператор instanceof

Slide 46

Slide 46 text

46 var u = new Grandfather(); var f = new Father(); var s = new Son(); s instanceof Son === true; // OK s instanceof Father === true; // OK? s instanceof Grandfather === true; // OK?? // Неужели множественное наследование??? s instanceof Object === true; // WAT??? s instanceof Array === false; // ОК! Оператор instanceof

Slide 47

Slide 47 text

47 Оператор instanceof использует цепочку прототипов

Slide 48

Slide 48 text

48 Оператор instanceof •  instanceof(obj, Constructor):Boolean! •  Использует цепочку прототипов •  Рекурсивно проверяет равенство Constructor.prototype === obj.__proto__! •  До того пока __proto__ !== null – вернет false

Slide 49

Slide 49 text

49 function isInstanceOf(obj, Сonstructor) { // Нашли if (obj.__proto__ === Сonstructor.prototype) { return true; } // Ищем дальше рекурсивно else if (obj.__proto__ !== null) { return isInstanceOf(obj.__proto__, Сonstructor); } // Не нашли else { return false; } } // Пример isInstanceOf(s, Father) === s instanceof Father; Оператор instanceof в коде

Slide 50

Slide 50 text

50 Цепочка прототипов объекта s s color black __proto__ object Son.prototype __proto__ object Grandfather.prototype color blue __proto__ object Object.prototype __proto__ null Участок цепочки Father пропущен

Slide 51

Slide 51 text

51 var s = new Son(); s instanceof Array === false; // ОК! Grandfather.prototype.__proto__ = Array.prototype; s instanceof Array === true; // WAT??? Оператор instanceof

Slide 52

Slide 52 text

52 var s = new Son(); s instanceof Array === false; // ОК! Grandfather.prototype.__proto__ = Array.prototype; s instanceof Array === true; // WAT??? Оператор instanceof

Slide 53

Slide 53 text

Вызов конструктора родителя Вызов метода родителя Вызов метода родителя

Slide 54

Slide 54 text

54 var Grandfather = function (name) { this.name; }; Grandfather.prototype.hello = function () { return 'I am ' + this.name; }; var Father = function (name) { Grandfather.call(this, name); }; Father.prototype.hello = function () { return Grandfather.prototype.hello.call(this) + ' – Father'; }; Вызов метода родителя

Slide 55

Slide 55 text

Классы? ECMAScript 6 class Библиотеки для эмуляции классов Трансляция в JavaScript

Slide 56

Slide 56 text

56 Находится в стадии черновика спецификации class Grandfather { constructor () {} public color 'blue'; } class Father extends Grandfather { constructor () {} public color 'green'; } var f = new Father(); ECMAScript 6 class

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

58 В JavaScript нет и не будет классов. Слово class – синтаксический сахар.

Slide 59

Slide 59 text

59 Все это в конечном итоге будет цепочкой прототипов. Вот такой: var Grandfather = function () {}; // Конструктор Grandfather Grandfather.prototype.color = 'blue'; var Father = function () {}; // Конструктор Father Father.prototype = Object.create(Grandfather.prototype); Father.prototype.color = 'green'; ECMAScript 6 class

Slide 60

Slide 60 text

Есть несколько библиотек… Библиотеки для классов

Slide 61

Slide 61 text

61 Таких библиотек over 9000

Slide 62

Slide 62 text

62 Каждый программист на JavaScript должен написать свою реализацию классов ©

Slide 63

Slide 63 text

63 Библиотеки для классов •  Mootools •  Klass •  JSClas •  … Over 9000 http://habrahabr.ru/post/132698/#comment_4404597

Slide 64

Slide 64 text

64 Mootools var Grandfather = new Class({ initialize: function () { } }); var Father = new Class({ Extends: Grandfather, initialize: function () { } }); Все они выглядят примерно так

Slide 65

Slide 65 text

65 В JavaScript нет и не будет классов. new Class – для удобства разработчика.

Slide 66

Slide 66 text

Пишем на одном языке, где есть классы, а затем переделываем в JavaScript! Трансляция в JS

Slide 67

Slide 67 text

67 Много языков транслируется в JS •  CoffeeScript •  Dart •  TypeScript •  Processing •  Python, Delphi, Ruby, C++ (LLVM)

Slide 68

Slide 68 text

68 Зачем транслируют? •  Не знают JavaScript и его особенностей •  Удобно писать на 1м языке –  Python, Ruby •  Синтаксический сахар –  CoffeeScript, Dart, TypeScript •  Очень долго переписывать –  Программы на C++

Slide 69

Slide 69 text

69 Проблемы трансляции •  Может быть крайне не оптимальна –  Тормоза и лаги –  Много костылей •  На выходе плохо читаемый код –  Сделан роботами для роботов •  Отлаживать в любом случае JavaScript

Slide 70

Slide 70 text

70 Лучше применять трансляцию в JavaScript только в крайнем случае!

Slide 71

Slide 71 text

Изменение базовых классов Да, их также можно менять! String.prototype Array.prototype Number.prototype …

Slide 72

Slide 72 text

72 // Чтобы не зацепить хорошие браузеры if (!Array.prototype.indexOf) { Array.prototype.indexOf = function (searchElement) { for (var i = 0; i < this.length; i++) { if (this[i] === searchElement) { return i; } } return -1; }; } [1, 2, 3, 4].indexOf(3); // 2 Polyfill для Array#indexOf Внимание! Это не полная реализация – не используйте ее! Все, что влезло в слайд. Array indexOf method http://clck.ru/3mm5x

Slide 73

Slide 73 text

73 Number.prototype.times = function (callback) { for (var i = 0; i < this; i++) { callback(i); } }; // Пример (10).times(function (index) { console.log(index); }); // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 Number#times

Slide 74

Slide 74 text

74 Прототипы базовых классов изменяем только в крайнем случае или для polyfill!

Slide 75

Slide 75 text

75 JavaScript ООП •  Нет классов – есть прототипы •  Прототипное наследование •  Цепочка прототипов –  inherits, Object.create() •  __proto__ и prototype •  Оператор new •  Оператор instanceof •  Оператор точка и [] •  Много библиотек для классов •  Трансляция в JavaScript – крайний случай

Slide 76

Slide 76 text

Основы и заблуждения насчет JavaScript http://clck.ru/0zjHr

Slide 77

Slide 77 text

77 Михаил Давыдов Разработчик JavaScript azproduction@yandex-team.ru azproduction Спасибо