// * Переменные *
// $ - это допустимый символ в имени переменной
// [ ] - это массив:
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)
Кратко и информативно. Спасибо.
ОтветитьУдалитьСпасибо
ОтветитьУдалить