3. 3
JavaScript ООП
• Нет классов
– Но можно эмулировать их
• Есть прототипы
• Есть наследование на прототипах
– Делегирующее прототипное наследование
• Все можно менять во время работы
– Цепочку наследования можно менять
– Прототипы можно менять
– На классах так сделать нельзя
• Можно изменить прототипы базовых
"классов"
5. 5
Сказка о мутантах
• В далекой-далекой галактике
• Нет привычного нам наследования
• Есть телепатическое наследование
– "Телегенез"
• Действующие лица:
– Дедушка
– Отец
– Сын
13. 13
Мутанты и JavaScript
Size,
Age
Color
Объект
Свойства
прототипа
Собственные
свойства
Делегирование
Цепочка прототипов
14. 14
Собственные свойства и прототип
• Собственные свойства
• Свойства прототипа
• Любой объект имеет ссылку на прототип
– И примитив также*
– Имеет с рождения
– По умолчанию – Object.prototype
• Делегирование
– Мы можем пользоваться функциями прототипа не имея собственных
• Цепочка прототипов
– Каждый прототип это тот же объект
– Который также может иметь прототип
– У прототипа прототипа также может быть прототип
16. 16
Работа оператора new
• new(Constructor, arguments):*!
• Получает на вход 2 операнда
– Функция должна иметь свойство prototype
• Создает временный объект (obj)
• Добавляет свойство __proto__
– obj.__proto__ = Constructor.prototype
• Вызывает конструктор над объектом
– Constructor.apply(obj, arguments)
• Конструктор вернул примитив. Результат obj
• Иначе то, что вернул конструктор
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 в коде
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"
25. 25
Цепочка прототипов
// Конструктор Grandfather
var Grandfather = function () {};
Grandfather.prototype.color = 'green';
// Конструктор Father
var Father = function () {};
typeof Father.prototype === "object";
// Для цепочки нам нужно получить вот это
Father.prototype = {
__proto__: Grandfather.prototype
};
26. 26
Строим цепочку прототипов явно
// Конструктор Father
var Father = function () {};
Father.prototype = new Grandfather();
// Как помним, это аналогично:
Father.prototype = {
__proto__: Grandfather.prototype
};
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"]
Пример с мутантами
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"]
Пример с мутантами
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"]
Пример с мутантами
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
34. 34
А что, если в конструкторе
alert(), а если он добавляет
свойства?
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();
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();
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); // Наследуем
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()
41. 41
Оператор "точка" и []
• getProperty(obj, name): *!
• Ищет в собственных свойствах
• Нет? – ищем в цепочке прототипов
• Пока __proto__ !== null
• Не нашли – возвращаем undefined
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;
Оператор "точка" и [] в коде
43. 43
Цепочка прототипов объекта s
s
color black
__proto__ object
Son.prototype
__proto__ object
Grandfather.prototype
color blue
__proto__ object
Object.prototype
__proto__ null
Участок цепочки Father пропущен
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
48. 48
Оператор instanceof
• instanceof(obj, Constructor):Boolean!
• Использует цепочку прототипов
• Рекурсивно проверяет равенство
Constructor.prototype === obj.__proto__!
• До того пока __proto__ !== null – вернет false
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 в коде
50. 50
Цепочка прототипов объекта s
s
color black
__proto__ object
Son.prototype
__proto__ object
Grandfather.prototype
color blue
__proto__ object
Object.prototype
__proto__ null
Участок цепочки Father пропущен
51. 51
var s = new Son();
s instanceof Array === false; // ОК!
Grandfather.prototype.__proto__ =
Array.prototype;
s instanceof Array === true; // WAT???
Оператор instanceof
52. 52
var s = new Son();
s instanceof Array === false; // ОК!
Grandfather.prototype.__proto__ =
Array.prototype;
s instanceof Array === true; // WAT???
Оператор instanceof
56. 56
Находится в стадии черновика спецификации
class Grandfather {
constructor () {}
public color 'blue';
}
class Father extends Grandfather {
constructor () {}
public color 'green';
}
var f = new Father();
ECMAScript 6 class
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
63. 63
Библиотеки для классов
• Mootools
• Klass
• JSClas
• …
Over 9000 http://habrahabr.ru/post/132698/#comment_4404597
64. 64
Mootools
var Grandfather = new Class({
initialize: function () {
}
});
var Father = new Class({
Extends: Grandfather,
initialize: function () {
}
});
Все они выглядят примерно так
65. 65
В JavaScript нет и не будет
классов. new Class – для
удобства разработчика.
66. Пишем на одном языке, где есть
классы, а затем переделываем в
JavaScript!
Трансляция в JS
67. 67
Много языков транслируется в JS
• CoffeeScript
• Dart
• TypeScript
• Processing
• Python, Delphi, Ruby, C++ (LLVM)
68. 68
Зачем транслируют?
• Не знают JavaScript и его особенностей
• Удобно писать на 1м языке
– Python, Ruby
• Синтаксический сахар
– CoffeeScript, Dart, TypeScript
• Очень долго переписывать
– Программы на C++
69. 69
Проблемы трансляции
• Может быть крайне не оптимальна
– Тормоза и лаги
– Много костылей
• На выходе плохо читаемый код
– Сделан роботами для роботов
• Отлаживать в любом случае JavaScript
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
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
75. 75
JavaScript ООП
• Нет классов – есть прототипы
• Прототипное наследование
• Цепочка прототипов
– inherits, Object.create()
• __proto__ и prototype
• Оператор new
• Оператор instanceof
• Оператор точка и []
• Много библиотек для классов
• Трансляция в JavaScript – крайний случай