$30 off During Our Annual Pro Sale. View Details »

КРиПИ - JavaScript ООП

КРиПИ - JavaScript ООП

Mikhail Davydov

November 02, 2012
Tweet

More Decks by Mikhail Davydov

Other Decks in Education

Transcript

  1. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. 6
    Структура мутанта
    Мутант

    "Телепатические"

    гены

    Собственные

    гены

    Движение генов

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  13. 13
    Мутанты и JavaScript
    Size,  
    Age  
    Color  
    Объект

    Свойства

    прототипа

    Собственные

    свойства

    Делегирование

    Цепочка прототипов

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  18. 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 () {}}
    Подмена инстанса

    View Slide

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

    View Slide

  20. 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 в коде

    View Slide

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

    View Slide

  22. 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"

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. 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"]
    Пример с мутантами

    View Slide

  29. 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"]
    Пример с мутантами

    View Slide

  30. 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"]
    Пример с мутантами

    View Slide

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

    View Slide

  32. 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

    View Slide

  33. 33

    View Slide

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

    View Slide

  35. 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();

    View Slide

  36. 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();

    View Slide

  37. 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); // Наследуем

    View Slide

  38. 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()

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  42. 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;
    Оператор "точка" и [] в коде

    View Slide

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

    View Slide

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

    View Slide

  45. Оператор instanceof

    View Slide

  46. 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

    View Slide

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

    View Slide

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

    View Slide

  49. 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 в коде

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  54. 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';
    };
    Вызов метода родителя

    View Slide

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

    View Slide

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

    View Slide

  57. View Slide

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

    View Slide

  59. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  72. 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

    View Slide

  73. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  77. 77
    Михаил Давыдов
    Разработчик JavaScript
    [email protected]
    azproduction
    Спасибо

    View Slide