31
Как программировать на JavaScript и не выстрелить себе в ногу Андрей Геоня 29.12.2011

Как программировать на JavaScript и не выстрелить себе в ногу

Embed Size (px)

DESCRIPTION

 

Citation preview

Page 1: Как программировать на JavaScript и не выстрелить себе в ногу

Как программировать на JavaScript и не выстрелить

себе в ногу

Андрей Геоня29.12.2011

Page 2: Как программировать на JavaScript и не выстрелить себе в ногу

С чем имеем дело?

● ОО подход основан на прототипах;● Отсутствие пространств имен;● Отсутствие ограничения области видимости;● Отсутствие строгой типизации;● Отсутствие проверки статических элементов.

Page 3: Как программировать на JavaScript и не выстрелить себе в ногу

Прототипное программированиеfunction Human() { console.log('Human constructor called');}Human.prototype = { sayHuman: function() { alert('I am a Human!'); }, sayHello: function() { alert('Hello world!'); }}// human.__proto__ = instance of Objectvar human = new Human();

function Man() { console.log('Man constructor called');}Man.prototype = new Human();Man.prototype.sayMan = function() { alert('I am a Man!');}

function Woman() { console.log('Woman constructor called');}Woman.prototype = new Human();Woman.prototype.sayWoman = function() { alert('I am a Woman!');}

var peter = new Man(); // peter.__proto__ = instance of Humanvar olga = new Woman(); // olga.__proto__ = instance of Human

Page 4: Как программировать на JavaScript и не выстрелить себе в ногу

Интерфейсы. Классика

interface MyInterface1 { public function add($param1); public function remove($param1);}interface MyInterface2 { public function display($param1, $param2);}

class MyClass implements MyInterface1, MyInterface2 { public function add($param1) { ... } public function remove(param1) { ... } public function display($param1, $param2) { ... }}

Page 5: Как программировать на JavaScript и не выстрелить себе в ногу

Эмуляция интерфейсов в JS с помощью комментариев

/*interface MyInterface1 { function add(param1); function remove(param1);}interface MyInterface2 { function display(param1, param2);}*/

var MyClass = function() { // implements MyInterface1, MyInterface2 ... };MyClass.prototype.add = function(param1) { ... };MyClass.prototype.remove = function(param1) { ... };MyClass.prototype.display = function(param1, param2) { ... };

Page 6: Как программировать на JavaScript и не выстрелить себе в ногу

Эмуляция интерфейсов в JS с помощью простой проверки

/*interface MyInterface1 { function add(param1); function remove(param1);}interface MyInterface2 { function display(param1, param2);}*/

var MyClass = function() { this.implementsInterfaces = ['MyInterface1', 'MyInterface2']; ... };

function someClient(someInstance) { if(!implements(someInstance, 'MyInterface1', 'MyInterface2')) { throw new Error("Object does not implement a required interface."); } ...}

// Проверяет, есть ли в массиве object.implementsInterfaces нужные названия интерфейсовfunction implements(object) { ...}

Page 7: Как программировать на JavaScript и не выстрелить себе в ногу

Эмуляция интерфейсов в JS. Объекты + проверка методов

var MyInterface1 = new Interface('MyInterface1', ['add', 'remove']);var MyInterface2 = new Interface('MyInterface2', ['display']);

var MyClass = function() { // implements MyInterface1, MyInterface2 ... };

function someClient(someInstance) { // выбросит Exception, если в object отсутствуют нужные методы Interface.ensureImplements(someInstance, MyInterface1, MyInterface2)); ...}

Page 8: Как программировать на JavaScript и не выстрелить себе в ногу

Конструктор Interface

var Interface = function(name, methods) { if(arguments.length != 2) { throw new Error("Interface constructor called with " + arguments.length + "arguments, but expected exactly 2."); } this.name = name; this.methods = []; for(var i = 0, len = methods.length; i < len; i++) { if(typeof methods[i] !== 'string') { throw new Error("Interface constructor expects method names to be passed in as a string."); } this.methods.push(methods[i]); }};

Page 9: Как программировать на JavaScript и не выстрелить себе в ногу

Статический метод Interface.ensureImplementsInterface.ensureImplements = function(object) { if(arguments.length < 2) { throw new Error("Function Interface.ensureImplements called with " + arguments.length + "arguments, but expected at least 2."); } for(var i = 1, len = arguments.length; i < len; i++) { var interface = arguments[i]; if(interface.constructor !== Interface) { throw new Error("Function Interface.ensureImplements expects arguments" + "two and above to be instances of Interface."); } for(var j = 0, methodsLen = interface.methods.length; j < methodsLen; j++) { var method = interface.methods[j]; if(!object[method] || typeof object[method] !== 'function') { throw new Error("Function Interface.ensureImplements: object " + "does not implement the " + interface.name + " interface. Method " + method + " was not found."); } } }};

Page 10: Как программировать на JavaScript и не выстрелить себе в ногу

Инкапсуляция на соглашенияхvar IHuman = new Interface('Human', ['getSurname', 'setSurname', 'getName', 'setName']);

var Human = function (surname, name) { this.setSurname(surname); this.setName(name);}Human.prototype = { getSurname: function() { return this._surname; }, setSurname: function(surname) { if (!this._isset(surname)) { throw new Error('Surname is required') }; this._surname = surname; }, getName: function() { return this._name; }, setName: function(name) { this._name = name || null; }, _isset: function(s) { if (s == undefined || typeof s != 'string') { return false; } else { return true; } }}

Page 11: Как программировать на JavaScript и не выстрелить себе в ногу

ЗамыканияЗамыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.

function foo() { var a = 10; function bar() { a *= 2; return a; } return bar;}

var baz = foo(); // baz is now a reference to function barbaz(); // returns 20baz(); // returns 40baz(); // returns 80

var blat = foo(); // blat is another reference to barblat(); // returns 20

Page 12: Как программировать на JavaScript и не выстрелить себе в ногу

Инкапсуляция на замыканияхvar Human = function (newSurname, newName) { var name, surname; function isset(s) { if (s == undefined || typeof s != 'string') { return false; } else { return true; } }; // Privileged methods this.getSurname = function() { return surname; }; this.setSurname = function(newSurname) { if (!isset(newSurname)) { throw new Error('Surname is required') }; surname = newSurname; }; this.getName = function() { return name; }; this.setName = function(newName) { name = newName || null; }; // Constructor code this.setSurname(newSurname); this.setName(newName);}

Human.prototype = { // Public, non-privileged methods talk: function() { alert(this.getName() + this.getSurname()); }};

Page 13: Как программировать на JavaScript и не выстрелить себе в ногу

Статические атрибуты и методыvar Human = (function () { // Private static attribute and method var count = 0; function isset(s) { ... };

// Return the constructor return function (newSurname, newName) { // Private attributes var name, surname;

// Privileged methods this.getSurname = function() { ... }; this.setSurname = function(newSurname) { ... }; this.getName = function() { ... }; this.setName = function(newName) { ... }; // Constructor code count++; this.setSurname(newSurname); this.setName(newName); }})();

// Public static methodHuman.toUpperCase = function(s){ ... }

Human.prototype = { // Public, non-privileged methods talk: function() { ... }};

Page 14: Как программировать на JavaScript и не выстрелить себе в ногу

Константы - приватные статические атрибуты с getter-ом

var Human = (function () { // Constant (created as private static attribute) var CLASS_NAME = 'Human';

// Privileged static method this.getCLASS_NAME() { return CLASS_NAME; }

// Return the constructor return function (newSurname, newName) { ... }})();

Page 15: Как программировать на JavaScript и не выстрелить себе в ногу

Приватный объект с константами и getter-ом

var Human = (function () { // Private static attributes. var constants = { CLASS_NAME: 'Human', SOME_CONSTANT_1: 'Some value 1', SOME_CONSTANT_2: 'Some value 2' }

// Privileged static method. this.getConstant(name) { return constants[name]; }

// Return the constructor return function (newSurname, newName) { ... }})();

Page 16: Как программировать на JavaScript и не выстрелить себе в ногу

Эмуляция классического наследования

/* Class Person */function Person(name) { this.name = name;}Person.prototype.getName = function() { return this.name;}

/* Class Author */function Author(name, books) { Person.call(this, name); // Call the superclass's constructor in the scope of this this.books = books; // Add an attribute to Author}Author.prototype = new Person(); // Set up the prototype chainAuthor.prototype.constructor = Author; // Set the constructor attribute to AuthorAuthor.prototype.getBooks = function() { // Add a method to Author return this.books;}

var author1 = new Author('Jeff Six', ['Application Security for the Android Platform']);var author2 = new Author('Vandad Nahavandipoor', ['iOS 5 Programming Cookbook']);

Page 17: Как программировать на JavaScript и не выстрелить себе в ногу

Эмуляция классического наследования. Функция extendfunction extend(subClass, superClass) { var F = function() {}; F.prototype = superClass.prototype; subClass.prototype = new F(); subClass.prototype.constructor = subClass; subClass.superclass = superClass.prototype; if(superClass.prototype.constructor == Object.prototype.constructor) { superClass.prototype.constructor = superClass; }}/* Class Person */function Person(name) { this.name = name;}Person.prototype.getName = function() { return this.name;}/* Class Author. */function Author(name, books) { Author.superclass.constructor.call(this, name); this.books = books;}extend(Author, Person);Author.prototype.getBooks = function() { return this.books;};var author1 = new Author('Jeff Six', ['Application Security for the Android Platform']);var author2 = new Author('Vandad Nahavandipoor', ['iOS 5 Programming Cookbook']);

Page 18: Как программировать на JavaScript и не выстрелить себе в ногу

Инкапсуляция при эмуляции классического наследования

● В подклассе имеется доступ только к публичным и привилегированным членам родительского класса;

● Приватные члены родительского класса доступны в подклассах, а также извне только через привилегированные методы самого родительского класса;

● Если есть необходимость в protected членах, принято соглашение использовать подчеркивание, например var _myProtectedVar = null;

Page 19: Как программировать на JavaScript и не выстрелить себе в ногу

Прототипное наследование /* Clone function */ function clone(object) { function F() {} F.prototype = object; return new F; }

/* Person Prototype Object */ var Person = { name: 'default name', getName: function() { return this.name; } }

/* Author Prototype Object */ var Author = clone(Person); Author.books = []; // Default value Author.getBooks = function() { return this.books; }

var author1 = clone(Author); // Author-like object author1.name = 'Jeff Six'; author1.books = ['Application Security for the Android Platform']; var author2 = clone(Author); // Author-like object author2.name = 'Vandad Nahavandipoor'; author2.books = ['iOS 5 Programming Cookbook'];

Page 20: Как программировать на JavaScript и не выстрелить себе в ногу

Mixin/* Augment function. */function augment(receivingClass, givingClass) { for(var methodName in givingClass.prototype) { if(!receivingClass.prototype[methodName]) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } }}

/* Regular class */var Class1 = function() {};Class1.prototype = { method1: function() {}, method2: function() {}}

/* Mixin class */var Class2 = function() {};Class2.prototype = { mixinMethod1: function() { alert('mixinMethod1 called'); }, mixinMethod2: function() { alert('mixinMethod2 called'); }}; augment(Class1, Class2);var myObj = new Class1();myObj.mixinMethod1();

Page 21: Как программировать на JavaScript и не выстрелить себе в ногу

Singleton/* Basic Singleton. */var Singleton = { attribute1: true, attribute2: 10, method1: function() { ... }, method2: function(arg) { ... }};

/* Singleton with Private Members */MyNamespace.Singleton = (function() { // Private members var privateAttribute1 = false; var privateAttribute2 = [1, 2, 3]; function privateMethod1() { ... } function privateMethod2(args) { ... }

return { // Public members publicAttribute1: true, publicAttribute2: 10, publicMethod1: function() { ... }, publicMethod2: function(args) { ... } };

})();

Page 22: Как программировать на JavaScript и не выстрелить себе в ногу

"Ленивое" инстанцированиеMyNamespace.Singleton = (function() { var instance; // All of the singleton code here function constructor() { var privateAttribute1 = false; function privateMethod1() { ... } return { publicAttribute1: true, publicMethod1: function() { ... } } } return { // Public members getInstance: function() { if(!instance) { instance = constructor(); } return instance; } }})();MyNamespace.Singleton.getInstance().publicMethod1(); // Call singleton method

Page 23: Как программировать на JavaScript и не выстрелить себе в ногу

Простая Фабрикаvar CarFactory = { createCar: function(model) { var car; switch(model) { case 'bmw': car = new BMW(); break; case 'ford': car = new Ford(); break; case 'ferrari': default: car = new Ferrari(); } Interface.ensureImplements(car, Car); return car; }};

Page 24: Как программировать на JavaScript и не выстрелить себе в ногу

Фабричный метод - паттерн, порождающий классы

/* CarShop class (abstract). */var CarShop = function() {};CarShop.prototype = { sellCar: function(model) { var car = this.createCar(model); car.prepare(); car.wash(); return car; }, createCar: function(model) { throw new Error('Unsupported operation on an abstract class'); }};

● Используем абстрактный класс;● Используем метод extend: extend(FordCarShop, CarShop);● Используем при необходимости метод ensureImplements: Interface.ensureImplements

(car, Car).

Page 25: Как программировать на JavaScript и не выстрелить себе в ногу

Компоновщик. Пример использования - запоминание форм

var contactForm = new CompositeForm('contact-form', 'POST', 'contact.php');

var nameFieldset = new CompositeFieldset('name-fieldset');nameFieldset.add(new InputField('first-name', 'First Name'));nameFieldset.add(new InputField('last-name', 'Last Name'));contactForm.add(nameFieldset);

var addressFieldset = new CompositeFieldset('address-fieldset');addressFieldset.add(new InputField('address', 'Address'));addressFieldset.add(new InputField('city', 'City'));addressFieldset.add(new SelectField('state', 'State', stateArray));contactForm.add(addressFieldset);

contactForm.add(new TextareaField('comments', 'Comments'));

body.appendChild(contactForm.getElement());

addEvent(window, 'unload', contactForm.save);addEvent(window, 'load', contactForm.restore);addEvent('save-button', 'click', nameFieldset.save);addEvent('restore-button', 'click', nameFieldset.restore);

Page 26: Как программировать на JavaScript и не выстрелить себе в ногу

Декораторы функцийВыглядит это так:

function upperCaseDecorator(func) { return function() { return func.apply(this, arguments).toUpperCase(); }}

Можно применять с пользой:

var gCollection = new YMaps.GeoObjectCollection();gCollection = new TimeProfiler(gCollection); // Декорируем все методы профайлером

gCollection.add([ new YMaps.Placemark(new YMaps.GeoPoint(37.518234, 55.708937)), new YMaps.Placemark(new YMaps.GeoPoint(37.514146, 55.722294)), new YMaps.Placemark(new YMaps.GeoPoint(37.514146, 55.722225)), new YMaps.Placemark(new YMaps.GeoPoint(37.514146, 55.722236))]);gCollection.removeAll();

С помощью декоратора TimeProfiler, в консоли будет напечатано время выполнения методов add и removeAll.

Page 27: Как программировать на JavaScript и не выстрелить себе в ногу

Тренируем внимание● Расширение встроенных прототипов языка - зло;● Не забываем писать var;● Учитываем зависимость производительности от длины цепочек прототипов;● Цикл for in проходит по всей цепочке прототипов. Решение - hasOwnProperty;● JavaScript не резервирует свойство с именем hasOwnProperty, но можно делать

так: ({}).hasOwnProperty.call(obj, 'propertyName');● Обходить обычный массив с помощью for in - зло;● Ключевое слово this ссылается на контекст вызова, но его можно явно задать при

call и apply;● arguments не является наследником Array, у него нет push, pop, slice и т.д.;● Помним про высасывание определений вверх ближайшей области видимости;● Используем instanceof только собственных типов. Для стандартных типов - зло;● eval - зло: а) безопасность; б) косвенный вызов и выход из локального scope;● for(var i = 0; i < 10; i++) {

setTimeout(function() { console.log(i); }, 1000); } Числа 0-9 не будут напечатаны, так как анонимная функция сохраняет ссылку на переменную i, которая в момент вызова будет равна 10. Решение: for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); }, 1000); })(i); }

Page 28: Как программировать на JavaScript и не выстрелить себе в ногу

Сравниваем осторожно

Оператор нестрогого равенства: Оператор строгого равенства:

�"" == "0" false

0 == "" true

0 == "0" true

false == "false" false

false == "0" true

false == undefined false

false == null false

null == undefined true

" \t\r\n" == 0 true

"" === "0" false

0 === "" false

0 === "0" false

false === "false" false

false === "0" false

false === undefined false

false === null false

null === undefined false

" \t\r\n" === 0 false

Page 29: Как программировать на JavaScript и не выстрелить себе в ногу

Внимание, оператор typeof

Результат работы typeof (простите, но так исторически сложилось...):

Значение [[Class]] = Object.prototype.toString.call(myObj)

Тип

"foo" String stringnew String("foo") String object1.2 Number numbernew Number(1.2) Number objecttrue Boolean booleannew Boolean(true) Boolean objectnew Date() Date objectnew Error() Error object[1,2,3] Array objectnew Array(1, 2, 3) Array objectnew Function("") Function function/abc/g RegExp object (function в Nitro/V8)new RegExp("meow") RegExp object (function в Nitro/V8)

{} Object object

new Object() Object object

Page 30: Как программировать на JavaScript и не выстрелить себе в ногу

Почитать

● Pro JavaScript design patterns (Ross Harmes, Dustin Diaz);● JavaScript Garden: http://shamansir.github.com/JavaScript-Garden/;● MDN: https://developer.mozilla.org/en/JavaScript;● http://javascript.ru/.

Page 31: Как программировать на JavaScript и не выстрелить себе в ногу