Заголовок
Правильный JavaScript
Будь спартанцем!
WebStandardsDays, 2011 ©
z, ? | toggle help (this) |
space, → | next slide |
shift-space, ← | previous slide |
d | toggle debug mode |
## <ret> | go to slide # |
c, t | table of contents (vi) |
f | toggle footer |
r | reload slides |
n | toggle notes |
p | run preshow |
Заголовок
WebStandardsDays, 2011 ©
1. В блоге пишу о разном-упоротом. 2. Можете обзывать так друзей 3. Написал добрую половину жаваскрипта для фотохостинга Nikon, использую его в работе и just for fun лет шесть. И ещё я пишу на Java, но не говорите никому об этом.
Poolparty smile is the property of Skype Limited ©; http://www.patwreck.com/wordpress/wp-content/uploads/2010/04/13d68eccc211e88c0867f1c6a0ef92e986147a71_s.gif
(но не в ущерб шоу и смыслу)
подождать
подождать
( Любое ООП )
подождать 5 сек
( Только два слова; У вас пять секунд на размышление )
Вот что об этом думают японцы:
– Очень плохо всё с ООП в JavaScript, уважаемый гайдзин!
Image source: http://lostintransit.org/archives/000772.html. In fact, these men are thinking not about JavaScript, I suppose
веб-разработчик имеется в виду
В школе нас учили наследованию...
http://tsdsu.co.uk/files/diploma-and-graduation-hat.jpeg
По сути наследование – почти то же, что классификация [БЫСТРО ПЕРЕКЛЮЧИТЬ]
http://img3.sencha.com/files/learn/Ext2-Container-hierarchy.gif
Ой, это картинка из другого доклада. Давайте возьмём живой пример
http://www.genery.com/sites/all/themes/gen2/images/screen/pushkin.png
Это немного необычная классификация
Здесь всё банально, рыбки от рыбок, кошки от кошек, лошади от лошадей – и всё это животные. Обычно создаются более сложные иерархии и абстракции
These Emoji are the property of Apple Inc. ©
Наследование Кошки и Собаки от класса Животного:
function Animal(type) {
this.type = type;
}
Animal.prototype.meet = function(other) {
return this.type + ' meets ' + other.type + '.'
}
function Cat() {
Cat.superclass.constructor.apply(this, 'Cat');
}
var F = function() { }
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat
Cat.superclass = Animal.prototype
function Dog() {
Dog.superclass.constructor.apply(this, 'Dog');
}
var F = function() { }
F.prototype = Animal.prototype
Dog.prototype = new F()
Dog.prototype.constructor = Dog
Dog.superclass = Animal.prototype
var cat = new Cat('Cat');
var dog = new Animal('Dog');
cat.react(dog);
> Cat meets Dog
Плохо, потому что не то. Американцы бы сказали Oh My GOD!
(хоть и можно обернуть в функцию)
(タコ猫 / オクトケツ)
Octocat is drawn by David OReilly
В природе такое встречается редко, но вот когда мы разрабатываем сложную систему, у нас часто появляются сложные компоненты
(ドッギダック)
http://api.ning.com/files/XWzZwWqhuPRomBff4H4uwRgDFRP5xMX2YaYZ-eBmoTPYEZfIZddJ4HwYt1-0Ca9CZ6Ph1dkR5-ipAYpSAERdjZTBXA-oRL5L/dogduck.jpg
Может...
(Есть больше одного способа сделать это)
Object.create - нововведённая функция, есть не везде. Намеренно использую маленькую букву - это экземпляры
var petya = Object.create(null);
// или Object.create();
petya.name = 'Петя';
petya.greet = function()
{ console.log('Я – ' + this.name); }
var vasya = Object.create(petya);
// NB: копируются ссылки!
vasya.name = 'Вася';
vasya.greet();
> 'Я – Вася'
http://www.christofle.com/files/products/02415003-1-612-0-dinner-fork.png; http://t2.gstatic.com/images?q=tbn:ANd9GcSkHf8gTj0iRpiZzl3Sdhxwi0gYgd8shxUhelDw7OkPoZfB3fp26HZKx4pd
(UNIX, GitHub, ...)
Фабрика кошек (но не осьмикошек, мы не планируем их наследовать, не будем преумножать сущности). Маруся - тёплое ламповое имя для кошки. Буэ – франзуское.
var cat = function(cname) {
return {
// новое для каждого клона
name: cname || 'Маруся',
// новая для каждого клона
identify: function() {
console.log('Я – ' + this.name); }
};
}
var octocat = new cat('Буэ');
octocat.identify();
> 'Я – Буэ'
Чуть более правильный вариант
var catProto = {
name: 'Маруся', // одно на всех
identify: function() { // одна на всех
console.log('Я – ' + this.name); }
};
var cat = function(cname) { this.name = cname; };
cat.prototype = catProto;
// "Создать по прототипу"
var octocat = new cat('Буэ');
octocat.identify();
> 'Я – Буэ'
Если мы всё же решили наследовать осьмикошек. Псевдоклассическое наследование. Присвоение prototype.constructor
не влияет на instanceof
, это заблуждение. Это исправление затёртого в предыдущей строке свойства constructor
, Свойство constructor
копируется между всеми экземплярами, созданными от данной функции.
function cat(name) { // у каждого
this.name = name || 'Маруся'; // новое у каждого
this.kittens = [];
}
cat.prototype = { // статика
react: function(who) { // общая для клонов ф-ция
console.log(this.name +
' реагирует на ' + who); }
};
function octocat() {}
octocat.prototype = new cat(); // клонируем...
octocat.prototype.constructor = octocat;// можно без
// ...и изменяем
octocat.prototype.name = 'Буэ'; // общее свойство
octocat.prototype.tentacles = 6;
var superoctocat = new octocat(); // клонируем...
superoctocat.tentacles = 8; // ...и изменяем
superoctocat instanceof cat;
> true
superoctocat.name // всех клонов зовут "Буэ"
> 'Буэ'
function cat(name) { // у каждого
this.name = name || 'Маруся'; // новое у каждого
this.kittens = [];
}
cat.prototype = { // статика
react: function(who) { // общая для клонов ф-ция
console.log(this.name +
' реагирует на ' + who); }
};
function octocat() {}
octocat.prototype = new cat(); // клонируем...
octocat.prototype.constructor = octocat;
var boo = new octocat();
boo.kittens
> []
var woo = new octocat();
woo.kittens.push('Гоша');
woo.kittens
> ["Гоша"]
boo.kittens
> ["Гоша"] // Oooopps!!!
Передаём имя через конструкторы. Совсем правильный вариант.
function cat(name) { // у каждого
this.name = name || 'Маруся'; // новое у каждого
this.kittens = []; // новый массив у кажд. клона
}
cat.prototype = { // статика
react: function(who) { // общая для клонов ф-ция
console.log(this.name +
' reacts on ' + who); }
};
function octocat(name) { // функция создания клона
cat.call(this,name || 'Буэ'); // <---- !!! ВАЖНО
this.tentacles = 6; // новое св-во у каждого
}
octocat.prototype = new cat('Буэ'); // клон-шаблон
octocat.prototype.global = ...; // общая
var superoctocat = new octocat('Чак');
superoctocat.tentacles = 8;
superoctocat instanceof cat;
> true
superoctocat.name
> 'Чак'
var superoctocat = new octocat('Чак');
cat
, общий для всех экземпляров octocat
octocat()
()superoctocat
superoctocat [instance of octocat]
{ name: 'Чак' }
octocat.prototype [instance of cat]
{ name: 'Буэ' }
cat.prototype
{ react: ... }
Object.prototype
{ toString: ... /* и т.д. */ }
Modified from http://bonsaiden.github.com/JavaScript-Garden/#object.prototype
Для работы с клонированием нужны:
prototype
у функции – именно этот объектnew
с этой функцией и получи клонПрограммисты очень не хотят использовать имя родительского класса и заводят свои обёртки
Сложности начинаются, когда оказывается нужен super
:
octocat.prototype.react=function(who){
cat.prototype.react.call(this, who);
// вспомните self
console.log('Перехвачено в octocat');
}
super
не имеет смысла, если мы клонируемsuper
?Именно прикручивание ООП внесло в JS много костылей. Не забывайте оператор new
и вам не нужно будет костылей
Вы спросите почему?.. Редко нужно создавать/копировать сложные объекты
Польза отказа от методов в пользу функций:
class SomeClass:
def method(self, param1, param2):
. . .
(обратите внимание на self)
Без IDE можно забыть интерфейс, нужно их все держать в памяти, лишние классы, делать интерфейс для каждого отдельного метода довольно странно, обычно это наборы методов
public interface CanMeow { // МожетМяукать
void meow(); // мяукнуть
}
public interface IsCat extends CanMeow {
// ЯвляетсяКошкой
void pullTail(); // дёрнутьХвост
}
public void pullTail(IsCat cat) { ... }
public void doMeow(CanMeow meowable) { ... }
Осьмикошка может мяукать, но у неё нет хвоста!
Можно даже не проверять тип: при вызове выбросится исключение. Кстати, хотел использовать собоутку для утиной типизации, но забыл
function doMeow(mayBeCanMeow) { // можетУмеетМяукать
if (!mayBeCanMeow.meow) {
// mayBeCanMeow.meow !== undefined
throw new Error('Не умеет мяукать :(');
}
mayBeCanMeow.meow();
}
doMeow({}); // Не умеет мяукать!
doMeow(petya); // Не умеет мяукать!
doMeow({'meow': function()
{ console.log('Мяу!'); } }); // Мяу!
doMeow(octocat); // Мяу!
doMeow(elephantWhoCanMeow);
// слонУмеющийМяукать: Мяу!
function pullTail(possiblyCat) { // можетКошка
if (!possiblyCat.tail) {
// possiblyCat.tail !== undefined
throw new Error('Нет хвоста :(');
}
console.log((possiblyCat.name
? withTail.name
: 'Неизвестный') +
' кричит «Ай!»');
}
pullTail({}); // Нет хвоста!
pullTail(petya); // Нет хвоста!
pullTail(musya); // Муся кричит «Ай!»
pullTail(elephant); // Слонишка кричит «Ай!»
pullTail({'tail': 1 }); // Неизвестный кричит «Ай!»
pullTail(octocat); // Нет хвоста!
Легко создать новый объект
var myChain = { head: null };
var divOne = { type: 'div', 'next': null };
var divTwo = { type: 'table', 'next': null };
divOne.next = divTwo;
myChain.head = divOne;
function walk(chain, func) {
var cursor = chain.head;
while (cursor != null) {
func(cursor);
cursor = cursor.next;
}
}
walk(myChain, function(elm)
> 'div' { console.log(elm.type) } );
> 'table'
Легко создать новый объект
var divOne = { type: 'div', 'next': null };
var divTwo = { type: 'table', 'next': null };
divOne.next = divTwo;
function walk(chain, func) {
var cursor = chain.head;
while (cursor != null) {
func(cursor);
cursor = cursor.next;
}
}
function init_chain(head) {
return { 'head': head };
}
walk(init_chain(divOne), function(elm)
> 'div' { console.log(elm.type) } );
> 'table'
По названиям параметров не всегда понятно, что от них ожидается
(помечайте описанные где-либо в документации типы или ожидания)
Про монады рассказывать не буду
maths.js
:
var privateFunc = function(...) { ... };
exports.state = { '...': ...,
'...': ... };
exports.add = function(...) { ... };
exports.mul = function(...) { ... };
exports.sqrt = function(...) { ... };
exports.max = function(...) { ... };
my.js
:
var maths = require('maths');
var res = maths.add(...);
external.js
:
var state = require('maths').state;
нет require, не асинхронный путь
Эмуляция в браузере:
var maths = {};
. . .
maths.add = function(...) {...};
maths.mul = function(...) {...};
if (typeof module === "object") {
module.exports = maths;
} else if (typeof window === "object") {
window.maths = maths;
} else {
throw new Error('Не могу найти библиотеку maths ' +
'(объекты "module" или "window"' +
'не найдены).');
}
Если много модулей – используйте Require.js
Пространства имён
var namespace = namespace || {};
(function( o ){
o.foo = "foo";
o.bar = function(){
return "bar";
};
var private = function() { ... };
})(namespace);
console.log(namespace);
Добавляет свойства кнопки к любому объекту
Кнопка:
var asButton = function() {
this.hover = function(bool) {
bool ? this.classes.push('hover')
: this.classes.remove('hover');
};
this.press = function(bool) {
bool ? this.classes.push('pressed')
: this.classes.remove('pressed');
};
this.fire = function() {
return this.action();
};
return this;
};
Добавляет свойства овала к любому объекту
Овал:
var asOval = function(options) {
this.area = function() {
return Math.PI * this.longRadius
* this.shortRadius;
};
this.ratio = function() {
return this.longRadius/this.shortRadius;
};
this.grow = function() {
this.shortRadius += (options.growBy/this.ratio());
this.longRadius += options.growBy;
};
this.shrink = function() {
this.shortRadius -= (options.shrinkBy/this.ratio());
this.longRadius -= options.shrinkBy;
};
return this;
}
Овальная кнопка:
var OvalButton = function(longRadius, shortRadius,
label, action) {
this.longRadius = longRadius;
this.shortRadius = shortRadius;
this.label = label;
this.action = action;
};
при вызове функций as...
функции клонируются, это не страшно, их можно кэшировать, подробности в литературе
Смешиваем:
asButton.call(OvalButton.prototype);
asOval.call(OvalButton.prototype,
{growBy: 2, shrinkBy: 2});
var myButton = new OvalButton(3, 2,
'Нажми!', function() {alert('Он нажал!')});
myButton.area(); //18.84955592153876
myButton.grow();
myButton.area(); //52.35987755982988
myButton.fire(); //'Он нажал!'
http://javascriptweblog.wordpress.com/2011/05/31/a-fresh-look-at-javascript-mixins/
Что применять?
(Если вы пишете документацию к функциям)
// (other:Number[, yet_another:Number]) → Number
// Returns the sum of the object's value with
// the given Number
function add(other, yet_another) {
return this.value + other + (yet_another || 0)
}
(Библиотеки не стоят двух однострочных функций)
(ООП нужно вовсе не всегда)
позволяет не думать о непривычных вещах
2011 ©