// * Переменные * // $ это допустимый символ в имени переменной // [] это массив: var $data = [1, 2, 7, '1sdf']; // {} это хэш var $hash = { a : 1, b : 'ZOO', c : function() { } }; // Переменные объявленные с использованием var имеют локальную // область действия, без - глобальную (в браузере принадлежат // объекту window) // * Функции * // Функция: function foo($str) { alert($str); } // Функция как переменная: var $bar = function() { foo('Hello'); }; // объект справа от знака равенства является анонимной функцией // А также, функции в JavaScript являются объектами первого класса: var $buzz = function($lambda) { return $lambda(); }; $buzz($bar); // В действительности запись: function foo() { } // является сокращённой записью: foo = function() { }; // что является сокращением: foo = new Function("arg1", "arg2", "body"); // Внутри функции можно получить ссылку на саму себя так var factorial = function(x) { return x == 1 ? 1 : x + factorial(x - 1); // используя имя функции }; // можно и так: var factorial = function(x) { return x == 1 ? 1 : x + arguments.callee(x - 1); // arguments.callee }; // а можно и так: var factorial = function f(x) { // f позволяет сослаться на себя изнутри return x == 1 ? 1 : x + f(x - 1); }; // JavaScript поддерживает замыкания: function outer() { var x = 7; // лексическая переменная захватывается замыканием var $closure = function() { alert(x); }; // на замыканиях основана инкапсуляция данных } // Каждое выполнение функции хранит все переменные в специальном объекте // [[scope]] недоступном для программиста. При создании вложенной функции // её передаётся ссылка на внешний [[scope]]. Замыкание, это когда внешний // [[scope]] остаётся жить после завершения внешней функции. JavaScript // всегда хранит внешние [[scope]] до завершения работы внутренних функций, // чтобы внутренние функции могли найти переменные, если они не найдены // в собственных [[scope]]. Все функции в JavaScript являются замыканиями! // Переменные попадают в замыкание по ссылке, а не по значению, // поэтому в отличии от перла function create() { var $arr = []; for(var $i=0; $i<100; $i++) { $arr[i] = function() { alert(i * i); }; // => 10000 } return $arr; } // этот код вернёт массив функций показывающих одно и тоже значение (100*100) // поэтому в подобных случаях делают двойное замыкание: function create() { var $arr = []; for (var $i=1; $i green // this отличается от this в C#, C++, e.t.c // В JavaScript this привязывается к контексту вызова // есть 4 случая: // 1, если функция вызывается через new как конструктор объекта: // this указывает на создаваемый объект function Lizard() { this.$color = 'green'; } // 2. Если функция запущена как метод объекта, this будет ссылкой на этот объект var $lazy = { $speed : 10 }; // это хэш var $jazz = function() { alert(this.$speed); }; // это функция $lazy.showSpeed = $jazz; // а это объекту создали меод showSpeed $lazy.showSpeed(); // => 30 // 3. Если функция вызывается через call или apply, this указывает на объект // переданный в call / apply function sum(a, b) { this.c = a + b; } var obj = {}; sum.call(obj, 1, 2); // obj.c = 3 sum.apply(obj, [1, 2]); // obj.c = 3 // 4. в просто вызове функции, this становится равным глобальному объекту // в браузере это windows // Инкапсуляция // Инициализацию данных предпочтительно размещать в конструкторе var MyClass = function() { this.public = function() { }; // доступное снаружи свойство var private = function() { }; // закрытое свойство }; var $obj = new MyClass(); $obj.public(); // корректно $obj.private(); // ошибка // Наследование // ООП построено на наследовании объектов, а не классов. Для понимания наследования // надо знать, что такое прототип. Прототип - это объект ассоциированный с // функцией-конструктором объекта. Сама функция не использует ассоциированный прототип. // Но созданные функцией объекты содержат заданные в прототипе свойства: function Rock() { } Rock.prototype = [1, 5, 13]; // прототипом сейчас является массив var $guitar = new Rock(); alert($guitar[2]); // => 13 var Blizzard = function() { }; Blizzard.prototype = { Z : 7, PI : 3.14159 }; var diablo = new Blizzard(); // Blizzard это функция-конструктор alert(Blizzard.PI); // => "undefined" alert(diablo.Z); // => 7 // Прототип это скрытая ссылка [[prototype]] недоступная программисту, принадлежащая // созданному объекту. Значением этой ссылки является доступное для программиста // свойство prototype у функции-конструктора в момент вызова new. // Важным свойством прототипа является то, что если вызывается какое-то свойство // объекта и оно отсутствует у самого объекта, начинается рекурсивный поиск в // в объекте на который ссылается прототип. Соответственно, присвоив прототипу // базовый объект, получаем наследование: var Car = function() { this.speed = 120; }; var Supercar = function() { }; Supercar.prototype = new Car; // Здесь мы сделали Supercar наследником Car var $ford = new Supercar(); alert($ford.speed); // => 120 // Указанный способ реализации наследования имеет два недостатка: // 1 - необходимость создания базового объекта, 2 - вытекающий из первого, // необходимость учёта функцией-конструктором вариант своего использования // только для назначения прототипа. // Гибкость концепции прототипов позволяет неколькими способами реализовать // наследование, ниже элегантный, но далеко не тривиальный для понимания способ // реализовать наследование, лишённый указанных выше недостатков: function extend(Child, Parent) { var F = function() { }; F.prototype = Parent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.super = Parent.prototype; } function Money(summ) { this.$ammount = summ; } function Dollar() { // вызов конструктора родителя с передачей ему аргументов Dollar.super.constructor.call(this, arguments[0]); // аргументы можно передавать как угодно: массивом arguments // либо набором переменных, разделённых запятой - всё это // придёт в базовый конструктор, в данном случае в Money( ... ) } extend(Dollar, Money); var $money = new Money(1000); var $dollar = new Dollar(3000); alert($money.$ammount); // => 1000 alert($dollar.$ammount); // => 3000 // Альтернативный способ создания объектов и наследования, основанный // на добавление нужных методов "наследника" в "базовый класс": function Base() { this.$E = 2.718281828; return { compute : function($arg) { return $arg * $E; } }; } // Потомок function Derived() { var self = Base(); // <- здесь можно было бы передать аргументы // в конструктор "базового класса" self.constructor = arguments.callee; self.compute = function($arg) { // это пример перекрытия метода в "наследнике" return $arg * $E * $E; }; return self; } var $d = new Derived; $d.compute(7); // => 51.7233 // Различие способов состоит в том, что во втором способе теряется // функциональность оператора instanceоf, который проверяет // принадлежность объекта проходя по цепочке его прототипов. // Это далеко не единственные способы организации наследования // в JavaScript, существует масса различных комбинаций указанных подходов // Пара слов о манипуляции с obj.constructor // Function.prototype.constructor или object.constructor // это ссылка на конструктор объекта, т.е. на функцию вызванную // с оператором new. object.constructor устанавливается исxодя // из Function.prototype.constructor на момент вызова new. // // Для каждой функции свойство prototype.constructor должно указывать // на саму функцию, тогда создаваемые объекты будут иметь правильное свойство // constructor. Если свойство prototype для функции переназначается // на новый объект, надо обязательно установить и свойство constructor: function NewYork() { } NewYork.prototype = { population : 10000000, crime : 'high' }; NewYork.prototype.constructor = NewYork; // при установке прототипа функции через new этого делать не надо var City = function() { }; City.prototype = new NewYork; // автоматически конструктор // установится ссылкой на NewYork // Статические члены function Figure() { } Figure.prototype = { active : true, coords : [0, 0] }; Figure.prototype.constructor = Figure; var fig1 = new Figure(); var fig2 = new Figure(); fig1.active = false; fig1.coords[0] = 10; alert(fig2.active); // => true alert(fig2.coords[0]); // => 10 (!) // причина изменения coords[0] у второго объекта (fig2), в том, что при // изменении элемента массива coords первым объектом (fig1), сначала // был произведён поиск массива для доступа к его элементу внутри объекта // fig1, массив не был найден и далее был произведён поиск в прототипе, // где он и был найден. Элемeнты находящиеся в прототипе функции разделяются // всеми созданными функцией объектами. Причина же неизменности переменной // active у fig2 заключается в том, что при присвоении свойству в объекте, // если свойство отсутствует, оно просто создаётся. // fig1.active = false; создало свойство в fig1, // alert(fig2.active); вывело свойство из прототипа. // Итак, все нестатические члены классов желательно создавать в конструкторе // а все статические члены, обращение к которым является не присваиванием, т.е. // всё, что не является примитивными типами, размещаетса в прототипе: function Square() { this.color = 'green'; this.active = true; this.coords = [0, 0]; } Square.prototype = { getSomeSharedFlag : function() { return true; }, someSharedHash : { foo : 1, bar : 2 } }; Square.prototype.constructor = Square; // Полиморфизм // Полиморфизм в JavaScript обеспечивается перегрузкой методов в механизме наследования: function Ship() { this.getVolume = function() { return 1000; }; } function LargeShip() { this.getVolume = function() { return 100000; }; } LargeShip.prototype = new Ship; function count(ship) { alert(ship.getVolume()); } var ship1 = new Ship; var ship2 = new LargeShip; count( ship1 ); // => 1000 count( ship2 ); // => 100000