// * Переменные * // $ - это допустимый символ в имени переменной // [ ] - это массив: 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) { // arguments.callee return x == 1 ? 1 : x + arguments.callee(x - 1); }; // а можно и так: var factorial = function f(x) { // локальное имя позволяет сослаться на себя изнутри 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 arr[$i] = function(x) { // в x копируется значение i return function() { alert(x*x); }; } ($i); // вызов свежесозданной функции } return arr; } // Функции можно добавить свойство: function Zeppelin() { } Zeppelin.$counter = 0; // используя свойства получаем аналог статических переменных внутри функций: function Zeppelin() { var $self = arguments.callee; $self.$counter++; alert($self.$counter); } // * ООП * // Функция создаёт объект, если вызывается с new. В этом случае она является // конструктором объекта function Lizard() { this.$color = 'green'; } var $speedy = new Lizard(); // Lizard - конструктор для $speedy alert($speedy.$color); // => 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
суббота, 12 июня 2010 г.
Сжато и без литья воды о JavaScript
Конспект - результат осмысления языка. Естественно, пользовался чужими наработками, местами прямо копировал себе код. На авторство не претендую, писал в первую очередь для приведения собственных знаний в порядок.
Подписаться на:
Комментарии к сообщению (Atom)
Кратко и информативно. Спасибо.
ОтветитьУдалитьСпасибо
ОтветитьУдалить