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

  • 1,315 views
Uploaded on

 

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
1,315
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
10
Comments
0
Likes
1

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Как программировать наJavaScript и не выстрелить себе в ногу Андрей Геоня 29.12.2011
  • 2. С чем имеем дело?● ОО подход основан на прототипах;● Отсутствие пространств имен;● Отсутствие ограничения области видимости;● Отсутствие строгой типизации;● Отсутствие проверки статических элементов.
  • 3. Прототипное программирование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
  • 4. Интерфейсы. Классика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) { ... }}
  • 5. Эмуляция интерфейсов в 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) { ... };
  • 6. Эмуляция интерфейсов в 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) { ...}
  • 7. Эмуляция интерфейсов в 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)); ...}
  • 8. Конструктор Interfacevar 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]); }};
  • 9. Статический метод 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."); } } }};
  • 10. Инкапсуляция на соглашениях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; } }}
  • 11. ЗамыканияЗамыкание — это особый вид функции. Она определена в теле другой функции и создаётсякаждый раз во время её выполнения. При этом вложенная внутренняя функция содержит ссылкина локальные переменные внешней функции. Каждый раз при выполнении внешней функциипроисходит создание нового экземпляра внутренней функции, с новыми ссылками на переменныевнешней функции.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 80var blat = foo(); // blat is another reference to barblat(); // returns 20
  • 12. Инкапсуляция на замыканиях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()); }};
  • 13. Статические атрибуты и методы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() { ... }};
  • 14. Константы - приватные статические атрибуты с 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) { ... }})();
  • 15. Приватный объект с константами и 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) { ... }})();
  • 16. Эмуляция классического наследования/* 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 superclasss 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]);
  • 17. Эмуляция классического наследования. Функция 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]);
  • 18. Инкапсуляция при эмуляции классического наследования● В подклассе имеется доступ только к публичным и привилегированным членам родительского класса;● Приватные члены родительского класса доступны в подклассах, а также извне только через привилегированные методы самого родительского класса;● Если есть необходимость в protected членах, принято соглашение использовать подчеркивание, например var _myProtectedVar = null;
  • 19. Прототипное наследование/* 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 valueAuthor.getBooks = function() { return this.books;}var author1 = clone(Author); // Author-like objectauthor1.name = Jeff Six;author1.books = [Application Security for the Android Platform];var author2 = clone(Author); // Author-like objectauthor2.name = Vandad Nahavandipoor;author2.books = [iOS 5 Programming Cookbook];
  • 20. 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();
  • 21. Singleton/* Basic Singleton. */ /* Singleton with Private Members */var Singleton = { MyNamespace.Singleton = (function() { attribute1: true, attribute2: 10, // Private members method1: function() { var privateAttribute1 = false; ... var privateAttribute2 = [1, 2, 3]; }, function privateMethod1() { method2: function(arg) { ... ... } } function privateMethod2(args) {}; ... } return { // Public members publicAttribute1: true, publicAttribute2: 10, publicMethod1: function() { ... }, publicMethod2: function(args) { ... } }; })();
  • 22. "Ленивое" инстанцирование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
  • 23. Простая Фабрика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; }};
  • 24. Фабричный метод - паттерн, порождающий классы/* 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).
  • 25. Компоновщик. Пример использования - запоминание форм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);
  • 26. Декораторы функцийВыглядит это так: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.
  • 27. Тренируем внимание ● Расширение встроенных прототипов языка - зло; ● Не забываем писать 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); }
  • 28. Сравниваем осторожноОператор нестрогого равенства: Оператор строгого равенства:"" == "0" false "" === "0" false0 == "" true 0 === "" false0 == "0" true 0 === "0" falsefalse == "false" false false === "false" falsefalse == "0" true false === "0" falsefalse == undefined false false === undefined falsefalse == null false false === null falsenull == undefined true null === undefined false" trn" == 0 true " trn" === 0 false
  • 29. Внимание, оператор 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 objectnew Object() Object object
  • 30. Почитать● 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/.