Показаны сообщения с ярлыком объекты. Показать все сообщения
Показаны сообщения с ярлыком объекты. Показать все сообщения

пятница, 10 октября 2014 г.

JavaScript: objects from scratch.

Из стандарта Ecma-Script:

ES - язык, основанный на объектах (object-based) и любая программа на нем представляет собой скопление взаимодействующих объектов.
Каждый объект представляет собой набор свойств, а каждое свойство, в свою очередь, имеет ряд атрибутов, определяющих как это свойство может быть использовано (например, быть доступным для чтения).
Свойство объекта также должно быть объектом (или функцией как разновидностью объекта или же литералом). 

В JavaScript объекты можно создавать разными способами. Наиболее употребимые из них - вызов функции-конструктора с помощью оператора new (при этом создастся пустой объект с дополнительными свойствами заданными в конструкторе):

var obj = new fnConstructor(value);

или же используя т.н. "синтаксический сахар", создавая пустой объект при помощи присваивания {}.

var obj = {};

В действительности объект obj не будет пустым, он имеет целый ряд предустановленных свойств и методов, которые получает любой объект, создаваемый стандартными способами. Для того, чтобы создать действительно пустой объект, не содержащий ни одного свойства и метода, нужно использовать следующий вызов:

var obj = Object.create(null);

Вновьсозданному объекту можно назначить свойства при помощи следующего вызова:

Object.defineProperty(object, name, config);

где object - ссылка на объект, name - строковый литерал, содержащий имя назначаемого свойства, а config - объект, содержащий ряд атрибутов свойств объекта (т.н. дескриптор):

value: '42' - предустановленное значение настраиваемого свойства, по умолчанию - undefined. Если свойство должно являться методом (содержать некоторый код), то его определение выглядит как value: function() { ... }

writable: true - возможность записывать значения в свойства (false дает readonly свойства). По умолчанию установлено в false. Исключение TypeError генерируется при записи в readonly свойство объекта только в strict mode.

enumerable: true - свойство объекта будет доступно при переборе с помощью конструкции (for key in obj) или вызова Object.keys(). В любом случае вызов Object.getOwnPropertyNames(obj) покажет даже такие "скрытые" свойства. По умолчанию свойство устанавливается в false.

configurable: true - свойство объекта можно переопределять или удалять. Будучи раз установленным в false свойство переопределению уже не поддаётся (кроме изменения параметра writable на false), а любые попытки изменить значения параметров value, enumerable, configurable, writable (на true), get/set приведут к исключению TypeError даже в обычном режиме (а попытка удалить свойство в strict mode).

get/set: эти свойства требуют указания функций геттера и сеттера, которые будут автоматически вызываться при попытке считать или установить значение (при этом устанавливаемое значение будет передаваться функции сеттеру в качестве параметра). С помощью этих свойств можно реализовать протоколирование считываний-назначений или какую-либо сложную обработку получаемых значений, приведение их к определенному типу, а также изменить свойства на функции при изменении кода с необходимостью сохранения API (например, если вместо хранения возраста в объекте начала храниться дата рождения, возврат возраста можно сделать как геттер-функцию от даты рождения).
Свойства get/set нельзя использовать одновременно со свойствами value и writeable (вызовет TypeError даже в обычном режиме).

Наряду с методом Object.defineProperty() можно использовать Object.defineProperties() для установки целого ряда свойств в одном вызове. Важно помнить, что при вызове defineProperty() свойства writeable, enumerable, configurable по умолчанию установлены в false, тогда как при создании свойства при помощи конструкции:

obj.prop = value;

эти свойства установлены в true.

defineProperty() можно вызывать из конструктора (в качестве ссылки на объект предпочтительно использовать this), а для улучшения читаемости кода можно использовать заготовки типа:

propWEC = { writeable: true, enumerable: true, configurable: true };

Свойства можно назначать при объявлении объекта в форме литерала, однако при этом устанавливается ряд умолчаний:

var a = { 
  name: 'Mikhail', 
  surname: 'Ivanov, 
  get fullName() { return name + surname },
  set fullName(newname) { 
                  var names = newname.trim().split(/\s+/); 
                  this.name = names[0] || '';
                  this.surname = names[1] || ''; }
}

Для каждого свойства объекта происходит упрощенное назначение name: value, при этом модификаторы writable, enumerable, configurable устанавливаются в true. А в качестве геттеров и сеттеров можно определить только анонимные функции, для предопределенных придётся использовать вызов defineProperty().

Для получения набора атрибутов, которые имеет то или иное свойство объекта используется вызов getOwnPropertyDescriptor(obj, 'property');

Помимо этого объект, определенный через {} получает в качестве предка Object.prototype, с которым наследует ряд стандартных свойств объекта JavaScript (типа toString() и т.п.).

Литералы могут быть объявлены только как часть выражения. В противном случае возможна трактовка их как блочных выражений с меткой:

{ a: 10 }   // Метка а
({ a: 10 }) // Объект со свойством a.

Свойства, для которых в дескрипторе атрибут configurable установлен в true, можно удалять

delete a.name;
console.log(a.name); // > undefined

Для каждого объекта набор присущих ему свойств можно получить тремя способами (см. описание атрибута enumerable).


среда, 8 октября 2014 г.

Функция как объект

JavaScript придерживается идеологии: "все - объект". В этом отношении функции мало чем отличаются от других объектов - они так же могут иметь собственные методы и свойства.

function f() {};
f.x = 10;
f.y = function() { console.log (f.x) };

f.y(); // > 10

Функцию можно создать при помощи конструктора Function:

var f = new Function(a, 'console.log(a)');

В качестве параметра вновьсоздаваемой функции передают набор формальных параметров, а в последнем (строкового типа) должен содержаться код функции для выполнения.

Код выше эквивалентен обычному объявлению функции:

function f(a) { console.log(a) }

В отличие от всех других объектов объект типа function имеет скрытое свойство [[Call]], которое и содержит выполняемый код функции. Выполнение происходит когда имя функции (или функциональное выражение) сопровождается парой скобок (возможно с передаваемыми параметрами):

f(10);
или
function f(a){ console.log(a) }(10);

В результате "превратить" обычный объект в функцию или создать функцию путем объявления объекта через фигурные скобки не удастся - только через непосредственное объявление или через конструктор Function (второй способ используется достаточно редко).

Каждая объявленная в глобальной области видимости функция является методом объекта global (как и каждая объявленная в ней переменная - ее свойством). По сути методы являются исполняемыми свойствами. А в JavaScript код функции - недоступное для модификации исполняемое свойство функции как объекта.




суббота, 27 сентября 2014 г.

Методы, this

При создании объекта можно задать его методы:

var itchy = {
     color: "black",
     tail: true,
     voice: function { console.log("Meow!"); }
}

Методы можно вызывать двумя способами:

itchy.voice();  // > Meow!
itchy["voice"]; // > Meow!

Метод может создаваться и позже при помощи присваивания (обратить внимание на синтаксис):

var itchy = {
     color: "black",
     tail: true
}

itchy.voice = function() { console.log("Meow!"); };
itchy.voice();    // Meow!
itchy['voice'](); // Meow!

При необходимости обращаться к свойствам и методам объекта, нужно вызывать их по имени объекта и метода:

var itchy.whatColor = function() { console.log(color); }; 
// > SyntaxError.

var itchy.whatColor = function() { console.log(itchy.color); };
itchy.whatColor(); // > black

Но при удалении оригинальной переменной возникнет ошибка:

var scratchy = itchy;
itchy = null;
scratchy.whatColor(); // > TypeError

Для предотвращения таких ошибок нужно использовать this.
Ключевое слово this, использованное в методе объекта, ссылается на текущий объект. Можно обращаться к свойствам и методам объекта изнутри:

var itchy.whatColor = function { console.log(itchy.color); };
var scratchy = itchy;
itchy = null;
srcatchy.whatColor(); // > black

Некоторые объекты позволяют вызывать свои методы цепочкой:

maze.up().up().left().down().right();

Для этого каждый метод должен возвращать this:

maze = {
    up: function() { console.log("up!"); return this; },
    down: function() { console.log("down!"); return this; },
    left: function() { console.log("left!"); return this; },
    right: function() { console.log("right!"); return this; },
}

this возвращает ссылку на объект, являющийся текущим контекстом вызова.

Текущий объект не является фиксированным и зависит от контекста вызова функции. Он может передаваться в функцию различными способами.

1. Неявно через вызов метода object.method(...);         //this = object
2. Неявно через вызов new   new constructor(...); //this = новый объект
3. Явно через call         function.call(object, ...); //this = object
4. Явно через apply        function.apply(object, ...);//this = object

Если ни один из этих способов не использован, то this указывает на Global Object для браузера - window для Node.js - global.

В случае 1 один и тот же метод-функция может быть присвоен разным объектам.

var itchy = { color : "black" };
var scratchy = { color : "white" };

var c = function() { console.log(this.color); };

itchy.whatColor = c;
scratchy.whatColor = c;

При вызове метода whatColor this для каждого объекта будет свой.

itchy.whatColor();
scratchy.whatColor();


В случае 2 конструктор может быть вызван и как обычная функция (без new). При этом this в обычном режиме будет ссылаться на глобальный объект (window, global), а в strict mode будет равен undefined.


Функции call и apply являются стандартными методами объекта типа function. Они позволяют (разово) выполнить произвольные функции в контексте объекта, указанного в качестве первого аргумента методов call и apply. Функция будет выполнена как метод объекта, даже если у объекта уже есть метод с таким именем. Назначения функции постоянным методом объекта как в случае с присваиванием не происходит.

var obj = { 
    prop: "value", 
    method: function() { console.log ("Do something!"); } 
};

var method = function() { console.log("Do something else!") };

obj.method();     // Do something!
method.call(obj); // Do something else!
obj.method();     // Do something!


Разница между call() и apply() заключается в обработке аргументов. Первым аргументом для обоих методов является объект, в контексте которого должны быть исполнена функция для которых вызывается метод. Далее call() принимает последовательность аргументов, а apply() - массив из аргументов.

func.call(context, arg1, arg2, ..., argN);
func.apply(context, [arg1, arg2, ..., argN);


Если первый аргумент call/apply равен null или undefined, то функция выполнится в контексте глобального объекта (с this равным window или global) в обычном режиме и с this равным null или undefined в strict mode.

При помощи apply можно сделать универсальную переадресацию вызова:

function f(a, b) {
    g.apply(this, arguments);
}

Функция g вызывается в том же контексте, что и f и с теми же аргументами. Код будет таким же для любого количества аргументов f.

Объекты в JavaScript: Конструкторы


Для создания любого объекта можно использовать функцию конструктор вида:

function Cat(color, tail) {
    this._color = color;
    this._tail  = tail;
}

Фактически, конструктор является инициализатором свойств объекта. 
И затем создать конструктор при помощи оператора new.

var scratchy = new Cat("black", true);

Во время создания объекта контекст this устанавливается на созданный объект, а сам он возвращается в результате выполнения new, если не указано конкретное возвращаемое значение.

scratchy instanceof Cat; // > true

Методы объекта создаются через прототипы:

Cat.prototype.describe = function() {
    return this.color + " cat " + 
           (this._tail ? "with" : "without") + " tail";
}

Если забыть оператор new при создании объекта, то Cat выполнится как обычная функция, возвращающая значение undefined (return отсутствует) и устанавливающая значения _color и _tail для глобального объекта. Чтобы этого не произошло, конструктор желательно создавать с директивой "use strict"; которая выбросит исключение при попытке установки свойств глобального объекта.

Что можно предпринять против "забывчивости"?

1. Создание объекта можно перенести внутрь конструктора и тогда не понадобится использовать new при объявлении - это произойдет в коде конструктора:

function Cat(color, tail) {
    "use strict";

    if (this instanceof Cat) {
        this._color = color;
        this._tail = tail;
    } else {
        return new Cat(color, tail)
}

При вызове без new выполнение пойдёт сначала по ветви else, а потом уже с вновьсозданным объектом по ветви if