JavaScript. OOP (in russian)
-
Upload
mikhail-davydov -
Category
Technology
-
view
261 -
download
0
Transcript of JavaScript. OOP (in russian)
Михаил Давыдов Разработчик JavaScript
JavaScript ООП
3
JavaScript ООП
• Нет классов – Но можно эмулировать их
• Есть прототипы • Есть наследование на прототипах
– Делегирующее прототипное наследование
• Все можно менять во время работы – Цепочку наследования можно менять – Прототипы можно менять – На классах так сделать нельзя
• Можно изменить прототипы базовых "классов"
4
Сказка о мутантах
5
Сказка о мутантах
• В далекой-далекой галактике • Нет привычного нам наследования • Есть телепатическое наследование
– "Телегенез"
• Действующие лица: – Дедушка – Отец – Сын
6
Структура мутанта
Мутант
"Телепатические" гены
Собственные гены
Движение генов
7
Все зеленые
Color
Дед Отец Сын
8
Дед: хочу стать синим!
Color
Дед Отец Сын
9
Все посинели
Color
Дед Отец Сын
10
Отец: верну-ка я цвет
Color Color
Дед Отец Сын
11
Дед синий, отец и сын зеленые
Color Color
Дед Отец Сын
12
Сын: хочу быть черным
Color
Color Color
Дед Отец Сын
13
Мутанты и JavaScript
Size, Age
Color
Объект
Свойства прототипа
Собственные свойства
Делегирование
Цепочка прототипов
14
Собственные свойства и прототип
• Собственные свойства • Свойства прототипа • Любой объект имеет ссылку на прототип
– И примитив также* – Имеет с рождения – По умолчанию – Object.prototype
• Делегирование – Мы можем пользоваться функциями прототипа не имея собственных
• Цепочка прототипов – Каждый прототип это тот же объект – Который также может иметь прототип – У прототипа прототипа также может быть прототип
Вызывает функцию как конструктор Строит цепочку прототипов
Оператор new
16
Работа оператора new
• new(Constructor, arguments):*!• Получает на вход 2 операнда
– Функция должна иметь свойство prototype
• Создает временный объект (obj) • Добавляет свойство __proto__
– obj.__proto__ = Constructor.prototype
• Вызывает конструктор над объектом – Constructor.apply(obj, arguments)
• Конструктор вернул примитив. Результат obj • Иначе то, что вернул конструктор
17
new Smth() может вернуть не инстанс Smth!
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 () {}}
Подмена инстанса
19
It isn't JavaScript bug, it is feature!
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 в коде
21
Во многих браузерах __proto__ скрытое свойство. Менять и получать нельзя!
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"
23
Оператор new используется для построения цепочек прототипов
Цепочка прототипов это способ наследования в JavaScript
25
Цепочка прототипов
// Конструктор Grandfather var Grandfather = function () {}; Grandfather.prototype.color = 'green';
// Конструктор Father var Father = function () {}; typeof Father.prototype === "object";
// Для цепочки нам нужно получить вот это Father.prototype = { __proto__: Grandfather.prototype };
26
Строим цепочку прототипов явно
// Конструктор Father var Father = function () {}; Father.prototype = new Grandfather();
// Как помним, это аналогично: Father.prototype = { __proto__: Grandfather.prototype };
27
Не забываем! __proto__ лучше установить явно – через оператор new
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"]
Пример с мутантами
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"]
Пример с мутантами
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"]
Пример с мутантами
31
Цепочка прототипов: Grandfather
g __proto__ object
Grandfather.prototype color blue __proto__ object
Object.prototype __proto__ null
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
33
34
А что, если в конструкторе alert(), а если он добавляет свойства?
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();
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();
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); // Наследуем
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()
Оператор "точка" и []
Получение свойств
Используют цепочку прототипов и собственные свойства
41
Оператор "точка" и []
• getProperty(obj, name): *!• Ищет в собственных свойствах • Нет? – ищем в цепочке прототипов • Пока __proto__ !== null • Не нашли – возвращаем undefined
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;
Оператор "точка" и [] в коде
43
Цепочка прототипов объекта s s color black __proto__ object
Son.prototype __proto__ object
Grandfather.prototype color blue __proto__ object
Object.prototype __proto__ null
Участок цепочки Father пропущен
44
Храните функции в прототипе, а данные в собственных свойствах
Оператор instanceof
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
47
Оператор instanceof использует цепочку прототипов
48
Оператор instanceof
• instanceof(obj, Constructor):Boolean!• Использует цепочку прототипов • Рекурсивно проверяет равенство Constructor.prototype === obj.__proto__!
• До того пока __proto__ !== null – вернет false
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 в коде
50
Цепочка прототипов объекта s s color black __proto__ object
Son.prototype __proto__ object
Grandfather.prototype color blue __proto__ object
Object.prototype __proto__ null
Участок цепочки Father пропущен
51
var s = new Son(); s instanceof Array === false; // ОК! Grandfather.prototype.__proto__ = Array.prototype; s instanceof Array === true; // WAT???
Оператор instanceof
52
var s = new Son(); s instanceof Array === false; // ОК! Grandfather.prototype.__proto__ = Array.prototype; s instanceof Array === true; // WAT???
Оператор instanceof
Вызов конструктора родителя Вызов метода родителя
Вызов метода родителя
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'; };
Вызов метода родителя
Классы?
ECMAScript 6 class
Библиотеки для эмуляции классов
Трансляция в JavaScript
56
Находится в стадии черновика спецификации
class Grandfather { constructor () {} public color 'blue'; } class Father extends Grandfather { constructor () {} public color 'green'; } var f = new Father();
ECMAScript 6 class
58
В JavaScript нет и не будет классов. Слово class – синтаксический сахар.
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
Есть несколько библиотек…
Библиотеки для классов
61
Таких библиотек over 9000
62
Каждый программист на JavaScript должен написать свою реализацию классов ©
63
Библиотеки для классов
• Mootools • Klass • JSClas • …
Over 9000 http://habrahabr.ru/post/132698/#comment_4404597
64
Mootools
var Grandfather = new Class({ initialize: function () { } }); var Father = new Class({ Extends: Grandfather, initialize: function () { } });
Все они выглядят примерно так
65
В JavaScript нет и не будет классов. new Class – для удобства разработчика.
Пишем на одном языке, где есть классы, а затем переделываем в JavaScript!
Трансляция в JS
67
Много языков транслируется в JS
• CoffeeScript • Dart • TypeScript • Processing • Python, Delphi, Ruby, C++ (LLVM)
68
Зачем транслируют?
• Не знают JavaScript и его особенностей • Удобно писать на 1м языке
– Python, Ruby
• Синтаксический сахар – CoffeeScript, Dart, TypeScript
• Очень долго переписывать – Программы на C++
69
Проблемы трансляции
• Может быть крайне не оптимальна – Тормоза и лаги – Много костылей
• На выходе плохо читаемый код – Сделан роботами для роботов
• Отлаживать в любом случае JavaScript
70
Лучше применять трансляцию в JavaScript только в крайнем случае!
Изменение базовых классов
Да, их также можно менять!
String.prototype
Array.prototype
Number.prototype
…
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
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
74
Прототипы базовых классов изменяем только в крайнем случае или для polyfill!
75
JavaScript ООП
• Нет классов – есть прототипы • Прототипное наследование • Цепочка прототипов
– inherits, Object.create()
• __proto__ и prototype • Оператор new • Оператор instanceof • Оператор точка и [] • Много библиотек для классов • Трансляция в JavaScript – крайний случай
Основы и заблуждения насчет JavaScript
http://clck.ru/0zjHr