понедельник, 7 ноября 2016 г.

1.12 Объекты и конструкторы

(Из книги "Говоря на языке JavaScript").

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

Отдельный объект

Как у всех значений, у объекта есть свойства. Вы можете просто рассматривать объект как набор свойств, каждое из которых представляет собой пару ключ/значение. Ключ это строка, а значение может быть любого типа из доступных в JavaScript.

Вы можете создавать объекты непосредственно с помощью соответствующего литерала:
 
'use strict';
var jane = {
    name: 'Jane',

    describe: function () {
        return 'Person named '+this.name;
    }
};
 
Только что описанный объект имеет свойства name и describe. Можно считывать (“get”) и записывать (“set”) свойства:
 
jane.name                  // Содержит 'Jane' (get).
jane.name = 'John';        // set
jane.newProperty = 'abc';  // Новое свойство создается автоматически.
 
Свойства, содержащие в качестве своих значений функции. как, например describe в данном примере, называются методами. Они используют ключевое слово this чтобы указывать на объект, внутри которого находятся:
 
jane.describe()  // Вызов метода, выведет 'Person named John'.
jane.name = 'Jane';
jane.describe()  // 'Person named Jane'.
 
Оператор in позволяет убедиться, что то или иное свойство существует внутри объекта:
 
'newProperty' in jane // Вернет true.
'foo' in jane         // Вернет false.
 
Если вы пытаетесь обратиться к свойству, которое не существует, вы получите значение  undefined. Таким образом, предыдущие две проверки могут быть выполнены так:
 
jane.newProperty !== undefined // Вернет true.
jane.foo !== undefined         // Вернет false.
 
Оператор delete удаляет свойство:
 
delete jane.newProperty // true
'newProperty' in jane   // false

Произвольные ключи для свойств

Ключ для свойства представляет собой строку. Мы вызываем значение по ключу с помощью оператора-точки. Но это возможно лишь в случаях, когда ключ соответствует требованиям, предъявляемым к идентификаторам (см. Идентификаторы и имена переменных). Если вы хотите в качестве ключей использовать произвольные строки, вам следует заключать их в кавычки при описании объекта и использовать квадратные скобки при чтении и записи его свойств:
 
var obj = { 'not an identifier': 123 };
obj['not an identifier'] // 123
obj['not an identifier'] = 456;
 
Квадратные скобки позволяют вам также вычислять ключи "налету":
 
var obj = { hello: 'world' };
var x = 'hello';

obj[x]          // 'world'
obj['hel'+'lo'] // 'world'

Извлечение методов

Если вы извлекаете метод, он теряет свою связь с объектом. Это уже не метод, а просто функция, и слово this внутри нее приобретает значение undefined (в строгом режиме). В качестве примера давайте вернемся к прежнему объекту jane:
 
'use strict';
var jane = {
    name: 'Jane',

    describe: function () {
        return 'Person named '+this.name;
    }
};
 
Допустим, мы хотим извлечь метод  describe из jane, поместить его в переменную func и вызвать. К сожалению, так не получится:
 
var func = jane.describe;
func() // Выведет TypeError: Cannot read property 'name' of undefined.
 
Решение заключается в использовании метода bind(),  который есть у каждой функции (функция, как сказано ранее, это разновидность объекта и сама может обладать методами). bind() создает новую функцию, внутри которой  this всегда имеет заданное при связывании значение:
 
var func2 = jane.describe.bind(jane);
func2() // Выведет 'Person named Jane'.

Функции внутри методов

Каждая функция располагает своим собственным указателем  this. Это неудобно, если вы помещаете функцию внутрь метода, потому что из такой функции вам становится недоступен this самого метода. Вот пример, с вызовом  forEach для перебора элементов массива:
 
var jane = {
    name: 'Jane',
    friends: [ 'Tarzan', 'Cheeta' ],
    logHiToFriends: function () {
        'use strict';
        this.friends.forEach(function (friend) {
            // `this` is undefined here
            console.log(this.name+' says hi to '+friend);
        });
    }
}
 
Вызов функции logHiToFriends порождает ошибку:
 
jane.logHiToFriends() // Выведет TypeError: Cannot read property 'name' of undefined.
 
Давайте рассмотрим два способа устранить эту неприятность. Во-первых, мы можем сохранить this в другой переменной:
 
logHiToFriends: function () {
    'use strict';
    var that = this;
    this.friends.forEach(function (friend) {
        console.log(that.name+' says hi to '+friend);
    });
}
 
Второй путь - воспользоваться тем, что у forEach есть второй параметр, который позволяет вам предоставлять ссылку на this:
 
logHiToFriends: function () {
    'use strict';
    this.friends.forEach(function (friend) {
        console.log(this.name+' says hi to '+friend);
    }, this);
}
 
Выражения-функции часто используются в качестве аргументов при вызове других функций в JavaScript. Всегда будьте осторожны при использовании this внутри таких функций.

Конструкторы как фабрики объектов

До сих пор вам могло казаться, что объекты JavaScript это лишь соответствия строк и значений, просто некие словари. Тем не менее, объекты в JavaScript поддерживают еще и возможность, делающую их подлинными объектно-ориентированными инструментами: наследование. Здесь мы не будем подробно останавливаться на том, как оно работает в  JavaScript, но дадим простой паттерн, который может стать отправной точкой. Подробности можно узнать из  Главы 17.

Помимо своей классической роли, функции и методы могут быть в JavaScript еще и конструкторами, фабриками объектов. В таких случаях их вызывают с оператором new. Конструкторы, грубо говоря, являются аналогами классов других языков программирования. Традиционно имена функций-конструкторов начинаются с заглавной буквы. Например:
 
// Set up instance data
function Point(x, y) {
    this.x = x;
    this.y = y;
}
// Methods
Point.prototype.dist = function () {
    return Math.sqrt(this.x*this.x + this.y*this.y);
};
 
Мы видим, что конструктор состоит из двух частей. Первая, функция Point, устанавливает свойства порождаемого объекта-экземпляра. Вторая (Point.prototype) описывает метод объекта. Первая часть (данные) специфична для каждого создаваемого объекта, вторая - одинакова для всех.

Чтобы использовать Point, мы запускаем его с оператором new:
 
var p = new Point(3, 5);
p.x // 3
p.dist() // 5.830951894845301

p является экземпляром Point:

p instanceof Point // true

Комментариев нет:

Отправить комментарий