renovations
This commit is contained in:
parent
c7d4c7e3ff
commit
e1948130f6
170 changed files with 1496 additions and 1161 deletions
|
@ -4,16 +4,17 @@
|
|||
|
||||
Гораздо позже появилось [объектно-ориентированное программирование](http://ru.wikipedia.org/wiki/%D0%9E%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5) (ООП), которое позволяет группировать функции и данные в единой сущности -- "объекте".
|
||||
|
||||
Например, "пользователь", "меню", "компонент интерфейса"...
|
||||
|
||||
**Чтобы ООП-подход "работал", объект должен представлять собой законченную, интуитивно понятную сущность.**
|
||||
При объектно-ориентированной разработке мы описываем происходящее на уровне объектов, которые создаются, меняют свои свойства, взаимодействуют друг с другом и (в случае браузера) со страницей, в общем, живут.
|
||||
|
||||
Например, "пользователь", "меню", "компонент интерфейса"... При объектно-ориентированном подходе каждый объект должен представлять собой интуитивно понятную сущность, у которой есть методы и данные.
|
||||
|
||||
[warn header="ООП -- это не просто объекты"]
|
||||
В JavaScript объекты часто используются просто как коллекции.
|
||||
|
||||
Например, встроенный объект [Math](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Math) содержит функции (`Math.sin`, `Math.pow`, ...) и данные (константа `Math.PI`).
|
||||
|
||||
При таком использовании объектов мы не можем сказать, что "применён объектно-ориентированный подход". В частности, никакую "единую сущность" `Math` из себя не представляет.
|
||||
При таком использовании объектов мы не можем сказать, что "применён объектно-ориентированный подход". В частности, никакую "единую сущность" `Math` из себя не представляет, это просто коллекция независимых функций с общим префиксом `Math`.
|
||||
[/warn]
|
||||
|
||||
|
||||
|
@ -35,9 +36,9 @@ vasya.sayHi(); // пользователь умеет говор
|
|||
|
||||
Здесь мы видим ярко выраженную сущность -- `User` (посетитель).
|
||||
|
||||
**При объектно-ориентированной разработке мы описываем происходящее на уровне объектов, которые создаются, меняют свои свойства, взаимодействуют друг с другом и (в случае браузера) со страницей, в общем, живут.**
|
||||
ООП -- это наука о том, как делать правильную архитектуру. У неё есть свои принципы, например [SOLID](https://ru.wikipedia.org/wiki/SOLID_%28%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82%D0%BD%D0%BE-%D0%BE%D1%80%D0%B8%D0%B5%D0%BD%D1%82%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D0%B5%29).
|
||||
|
||||
ООП -- это наука о том, как делать правильную архитектуру. У неё есть свои принципы, по ним пишут книги, к примеру:
|
||||
По приёмам объектно-ориентированной разработки пишут книги, к примеру:
|
||||
|
||||
<ul>
|
||||
<li><a href="http://www.ozon.ru/context/detail/id/3905587/?partner=iliakan">Объектно-ориентированный анализ и проектирование с примерами приложений.</a>
|
||||
|
@ -46,4 +47,4 @@ vasya.sayHi(); // пользователь умеет говор
|
|||
<i>Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес.</i></li>
|
||||
</ul>
|
||||
|
||||
Далее мы поговорим подробно как про ООП, так и об основных принципах, которых нужно придерживаться.
|
||||
Здесь мы не имеем возможности углубиться в теорию ООП, но основных "китов", на которых оно стоит, потрогаем за усы вдумчиво и конкретно.
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
## Внутренний и внешний интерфейс
|
||||
|
||||
В программировании есть чёткое разграничение методов и свойств объекта на две группы:
|
||||
В программировании мы будем разделять методы и свойства объекта на две группы:
|
||||
|
||||
<ul>
|
||||
<li>*Внутренний интерфейс* -- это свойства и методы, доступ к которым может быть осуществлен только из других методов объекта, их также называют "приватными" (есть и другие термины, встретим их далее).</li>
|
||||
|
@ -46,27 +46,13 @@
|
|||
|
||||
Но снаружи кофеварка закрыта специальным кожухом, чтобы никто к ним не подобрался. Детали скрыты и недоступны. Виден лишь внешний интерфейс.
|
||||
|
||||
**Все, что нужно для пользования объектом -- это внешний интерфейс.**
|
||||
|
||||
О внутреннем пользователю вообще знать не обязательно.
|
||||
|
||||
[smart header="Между приватным и публичным"]
|
||||
Приватные свойства полностью закрыты для доступа снаружи, а публичные -- наоборот, полностью открыты. Это крайности, между которыми бывают промежуточные варианты.
|
||||
|
||||
<ul>
|
||||
<li>В языке С++ можно открыть доступ к приватным переменным одного класса -- другому, объявив его "дружественным".</li>
|
||||
<li>В языке Java можно объявлять переменные, которые доступны всем классам внутри "пакета".</li>
|
||||
<li>Между объектами можно организовать "наследование" и сделать свойства открытыми только для "наследников", такой вариант доступа называют "защищённым".</li>
|
||||
</ul>
|
||||
|
||||
В этом учебнике будем изучать наследование и защищённые свойства, но позже, а пока сосредоточимся на приватном и публичном доступе... И, конечно, использовать мы будем JavaScript :)
|
||||
[/smart]
|
||||
Получив объект, всё, что нужно для пользования им -- это знать внешний интерфейс. О внутреннем же знать вообще не обязательно.
|
||||
|
||||
Это были общие слова по теории программирования.
|
||||
|
||||
Далее мы реализуем кофеварку на JavaScript с приватными и публичными свойствами. В кофеварке много деталей, мы конечно, не будем моделировать каждый винтик, а сосредоточимся на основных приёмах разработки.
|
||||
|
||||
### Шаг 1: публичное и приватное свойство
|
||||
## Шаг 1: публичное и приватное свойство
|
||||
|
||||
Конструктор кофеварок будет называться `CoffeeMachine`.
|
||||
|
||||
|
@ -85,28 +71,28 @@ var coffeeMachine = new CoffeeMachine(100);
|
|||
coffeeMachine.waterAmount = 200;
|
||||
```
|
||||
|
||||
**Локальные переменные, включая параметры конструктора, являются приватными свойствами.**
|
||||
**Локальные переменные, включая параметры конструктора, можно считать приватными свойствами.**
|
||||
|
||||
В примере выше это `power` -- мощность кофеварки, которая указывается при создании и далее будет использована для расчёта времени кипячения.
|
||||
|
||||
К локальным переменным конструктора нельзя обратиться снаружи, но они доступны внутри самого конструктора.
|
||||
|
||||
**Свойства, записанные в `this`, являются публичными.**
|
||||
**Свойства, записанные в `this`, можно считать публичными.**
|
||||
|
||||
Здесь свойство `waterAmount` записано в объект, а значит -- доступно для модификации снаружи. Можно доливать и выливать воду в любом количестве.
|
||||
|
||||
[smart header="Вопрос терминологии"]
|
||||
Может возникнуть вопрос -- почему я назвал `power` "приватным свойством", ведь это локальная *переменная*, а никакое не *свойство* объекта?
|
||||
Далее мы будем называть `power` как "локальной переменной", так и "приватным свойством" объекта.
|
||||
|
||||
Здесь небольшой конфликт терминологий.
|
||||
Это, смотря, с какой стороны посмотреть.
|
||||
|
||||
Термины "приватное свойство/метод", "публичное свойство/метод" относятся к общей теории ООП. А их конкретная реализация в языке программирования может быть различной.
|
||||
|
||||
Здесь ООП-принцип "приватного свойства" реализован через локальные переменные, поэтому и "локальная переменная" и "приватное свойство" -- правильные термины, в зависимости от того, с какой точки зрения посмотреть -- кода или архитектуры ООП.
|
||||
Здесь ООП-принцип "приватного свойства" реализован через локальные переменные, поэтому и "локальная переменная" и "приватное свойство" -- правильные термины, в зависимости от того, с какой точки зрения взглянуть -- кода или архитектуры ООП.
|
||||
[/smart]
|
||||
|
||||
|
||||
### Шаг 2: публичный и приватный методы
|
||||
## Шаг 2: публичный и приватный методы
|
||||
|
||||
Добавим публичный метод `run`, запускающий кофеварку, а также вспомогательные внутренние методы `getBoilTime` и `onReady`:
|
||||
|
||||
|
@ -141,11 +127,11 @@ coffeeMachine.waterAmount = 200;
|
|||
coffeeMachine.run();
|
||||
```
|
||||
|
||||
**Приватные методы, такие как `onReady`, `getBoilTime` объявляются как вложенные функции.**
|
||||
Приватные методы, такие как `onReady`, `getBoilTime` могут быть объявлены как вложенные функции.
|
||||
|
||||
В результате естественным образом получается, что доступ к ним (через замыкание) имеют только другие функции, объявленные в том же конструкторе.
|
||||
|
||||
### Шаг 3: константа
|
||||
## Шаг 3: константа
|
||||
|
||||
Для расчёта времени на кипячение воды используется формула `c*m*ΔT / power`, где:
|
||||
<ul>
|
||||
|
@ -171,7 +157,7 @@ function CoffeeMachine(power) {
|
|||
|
||||
// расчёт времени для кипячения
|
||||
function getBoilTime() {
|
||||
return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power;
|
||||
return this.waterAmount * WATER_HEAT_CAPACITY * 80 / power; // ошибка!
|
||||
}
|
||||
*/!*
|
||||
|
||||
|
@ -181,7 +167,7 @@ function CoffeeMachine(power) {
|
|||
}
|
||||
|
||||
this.run = function() {
|
||||
setTimeout(onReady, getBoilTime());
|
||||
setTimeout(onReady, getBoilTime());
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -194,9 +180,9 @@ coffeeMachine.run();
|
|||
|
||||
Удельная теплоёмкость `WATER_HEAT_CAPACITY` выделена большими буквами, так как это константа.
|
||||
|
||||
**Внимание, при запуске кода выше в методе `getBoilTime` будет ошибка. Как вы думаете, почему?**
|
||||
Внимание, при запуске кода выше в методе `getBoilTime` будет ошибка. Как вы думаете, почему?
|
||||
|
||||
### Шаг 4: доступ к объекту из внутреннего метода
|
||||
## Шаг 4: доступ к объекту из внутреннего метода
|
||||
|
||||
Внутренний метод вызывается так: `getBoilTime()`. А чему при этом равен `this`?... Как вы наверняка помните, в современном стандарте он будет `undefined` (в старом -- `window`), из-за этого при чтении `this.waterAmount` возникнет ошибка!
|
||||
|
||||
|
@ -311,11 +297,11 @@ coffeeMachine.run();
|
|||
|
||||
Теперь `getBoilTime` получает `self` из замыкания.
|
||||
|
||||
**Конечно, чтобы это работало, мы не должны изменять `self`, а все приватные методы, которые хотят привязаться к текущему объекту, должны использовать внутри себя `self` вместо `this`.**
|
||||
**Конечно, чтобы это работало, мы не должны изменять `self`, а все приватные методы, которые хотят иметь доступ к текущему объекту, должны использовать внутри себя `self` вместо `this`.**
|
||||
|
||||
Вместо `self` можно использовать любое другое имя переменной, например `var me = this`.
|
||||
|
||||
## Что нам даст разделение доступов?
|
||||
## Итого
|
||||
|
||||
Итак, мы сделали кофеварку с публичными и приватными методами и заставили их корректно работать.
|
||||
|
||||
|
|
|
@ -153,5 +153,14 @@ alert( coffeeMachine.waterAmount() ); // 450
|
|||
|
||||
Единый геттер-сеттер используется реже, чем две отдельные функции, но в некоторых JavaScript-библиотеках, например [jQuery](http://jquery.com) и [D3](http://d3js.org) подобный подход принят на уровне концепта.
|
||||
|
||||
## Задачи
|
||||
## Итого
|
||||
|
||||
<ul>
|
||||
<li>Для большего контроля над присвоением и чтением значения, вместо свойства делают "функцию-геттер" и "функцию-сеттер", геттер возвращает значение, сеттер -- устанавливает.</li>
|
||||
<li>Если свойство предназначено только для чтения, то может быть только геттер, только для записи -- только сеттер.</li>
|
||||
<li>В качестве альтернативы паре геттер/сеттер применяют единую функцию, которая без аргументов ведёт себя как геттер, а с аргументом -- как сеттер.</li>
|
||||
</ul>
|
||||
|
||||
Также можно организовать геттеры/сеттеры для свойства, не меняя структуры кода, через [дескрипторы свойств](/descriptors-getters-setters).
|
||||
|
||||
|
||||
|
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function CoffeeMachine(power, capacity) {
|
||||
var waterAmount = 0;
|
||||
|
||||
Object.defineProperty(this, "waterAmount", {
|
||||
|
||||
get: function() {
|
||||
return waterAmount;
|
||||
},
|
||||
|
||||
set: function(amount) {
|
||||
if (amount > capacity) {
|
||||
throw new Error("Нельзя залить больше, чем " + capacity);
|
||||
}
|
||||
|
||||
waterAmount = amount;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var coffeeMachine = new CoffeeMachine(1000, 300);
|
||||
coffeeMachine.waterAmount = 500;
|
||||
```
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
# Заменить свойство на встроенные геттеры/сеттеры
|
||||
|
||||
[importance 5]
|
||||
|
||||
Вам попал в руки код кофеварки, который использует свойство `this.waterAmount` для хранения количества воды:
|
||||
|
||||
```js
|
||||
function CoffeeMachine(power, capacity) {
|
||||
// количество воды в кофеварке
|
||||
this.waterAmount = 0;
|
||||
}
|
||||
|
||||
// создать кофеварку
|
||||
var coffeeMachine = new CoffeeMachine(1000, 300);
|
||||
|
||||
// залить воды
|
||||
coffeeMachine.waterAmount = 500;
|
||||
```
|
||||
|
||||
Задача -- сделать так, чтобы при присвоении `coffeeMachine.waterAmount = 500` выдавалась ошибка, если значение больше `capacity` (в примере выше `300`).
|
||||
|
||||
Для этого реализуйте `waterAmount` через геттер и сеттер, который будет проверять корректность установки. Используйте для этого `Object.defineProperty`.
|
|
@ -1,407 +0,0 @@
|
|||
# Дескрипторы, геттеры и сеттеры свойств
|
||||
|
||||
В этой главе мы рассмотрим возможности, которые позволяют очень гибко и мощно управлять всеми свойствами объекта, включая их аспекты -- изменяемость, видимость в цикле `for..in` и даже "невидимые" геттеры-сеттеры.
|
||||
|
||||
Они поддерживаются всеми современными браузерами, но не IE8-. Точнее говоря, они поддерживаются даже в IE8, но не для всех объектов, а только для DOM-объектов (они используются при работе со страницей, это сейчас вне нашего рассмотрения).
|
||||
|
||||
Большая часть этих методов, в частности, работа с дескрипторами, не задействуется в других главах учебника для обеспечения совместимости с IE8-, но во вспомогательных скриптах -- библиотеках для тестирования, сборки, а также для сервера Node.JS они используются достаточно активно.
|
||||
|
||||
[cut]
|
||||
## Дескрипторы в примерах
|
||||
|
||||
Основной метод для управления свойствами -- [Object.defineProperty](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperty).
|
||||
|
||||
Он позволяет определить свойство путём задания "дескриптора" -- описания, включающего в себя ряд важных внутренних параметров.
|
||||
|
||||
Синтаксис:
|
||||
|
||||
```js
|
||||
Object.defineProperty(obj, prop, descriptor)
|
||||
```
|
||||
|
||||
Аргументы:
|
||||
<dl>
|
||||
<dt>`obj`</dt>
|
||||
<dd>Объект, в котором объявляется свойство.</dd>
|
||||
<dt>`prop`</dt>
|
||||
<dd>Имя свойства, которое нужно объявить или модифицировать.</dd>
|
||||
<dt>`descriptor`</dt>
|
||||
<dd>Дескриптор -- объект, который описывает поведение свойства. В нём могут быть следующие поля:
|
||||
|
||||
<dl>
|
||||
<dt>`value`</dt>
|
||||
<dd>Значение свойства, по умолчанию `undefined`</dd>
|
||||
<dt>`writable`</dt>
|
||||
<dd>Значение свойства можно менять, если `true`. По умолчанию `false`.</dd>
|
||||
<dt>`configurable`</dt>
|
||||
<dd>Если `true`, то свойство можно удалять, а также менять его в дальнейшем при помощи `defineProperty`. По умолчанию `false`.</dd>
|
||||
<dt>`enumerable`</dt>
|
||||
<dd>Если `true`, то свойство будет участвовать в переборе `for..in`. По умолчанию `false`.</dd>
|
||||
<dt>`get`</dt>
|
||||
<dd>Функция, которая возвращает значение свойства. По умолчанию `undefined`.</dd>
|
||||
<dt>`set`</dt>
|
||||
<dd>Функция, которая записывает значение свойства. По умолчанию `undefined`.</dd>
|
||||
</dl>
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
Чтобы избежать конфликта, запрещено одновременно указывать значение `value` и функции `get/set`. Либо значение, либо функции для его чтения-записи, одно из двух. Также запрещено и не имеет смысла указывать `writable` при наличии `get/set`-функций.
|
||||
|
||||
Далее мы подробно разберём эти свойства на примерах.
|
||||
|
||||
### Пример: обычное свойство
|
||||
|
||||
Обычное свойство добавить очень просто.
|
||||
|
||||
Два таких вызова работают одинаково:
|
||||
|
||||
```js
|
||||
var user = {};
|
||||
|
||||
// 1. простое присваивание
|
||||
user.name = "Вася";
|
||||
|
||||
// 2. указание значения через дескриптор
|
||||
Object.defineProperty(user, "name", { value: "Вася" });
|
||||
```
|
||||
|
||||
### Пример: свойство-константа
|
||||
|
||||
Для того, чтобы сделать свойство неизменяемым, добавим ему флаги `writable` и `configurable`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
*!*
|
||||
"use strict";
|
||||
*/!*
|
||||
|
||||
var user = {};
|
||||
|
||||
Object.defineProperty(user, "name", {
|
||||
value: "Вася",
|
||||
writable: false, // запретить присвоение "user.name="
|
||||
configurable: false // запретить удаление "delete user.name"
|
||||
});
|
||||
|
||||
// Теперь попытаемся изменить это свойство.
|
||||
|
||||
// в strict mode присвоение "user.name=" вызовет ошибку
|
||||
*!*
|
||||
user.name = "Петя";
|
||||
*/!*
|
||||
```
|
||||
|
||||
**Заметим, что ошибки при попытке изменения такого свойства произойдут только при `use strict`.**
|
||||
|
||||
Без `use strict` операция записи "молча" не сработает.
|
||||
|
||||
### Пример: свойство, скрытое для for..in
|
||||
|
||||
Встроенный метод `toString`, как и большинство встроенных методов, не участвует в цикле `for..in`. Это удобно, так как обычно такое свойство является "служебным".
|
||||
|
||||
К сожалению, свойство `toString`, объявленное обычным способом, будет видно в цикле `for..in`, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = {
|
||||
name: "Вася",
|
||||
toString: function() { return this.name; }
|
||||
};
|
||||
|
||||
*!*
|
||||
for(var key in user) alert(key); // name, toString
|
||||
*/!*
|
||||
```
|
||||
|
||||
`Object.defineProperty` может помочь исключить `toString` из списка итерации. Достаточно поставить ему флаг `enumerable: false`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = {
|
||||
name: "Вася",
|
||||
toString: function() { return this.name; }
|
||||
};
|
||||
|
||||
*!*
|
||||
Object.defineProperty(user, "toString", {enumerable: false});
|
||||
|
||||
for(var key in user) alert(key); // name
|
||||
*/!*
|
||||
```
|
||||
|
||||
Обратим внимание, вызов `defineProperty` не перезаписал свойство, а просто модифицировал настройки у существующего `toString`.
|
||||
|
||||
### Пример: свойство как функция-геттер
|
||||
|
||||
Дескриптор позволяет задать свойство, которое на самом деле работает как функция. Для этого в нём нужно указать эту функцию в `get`.
|
||||
|
||||
Например, у объекта `user` есть обычные свойства: имя `firstName` и фамилия `surname`.
|
||||
|
||||
Создадим свойство `fullName`, которое на самом деле является функцией:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = {
|
||||
firstName: "Вася",
|
||||
surname: "Петров"
|
||||
}
|
||||
|
||||
Object.defineProperty(user, "fullName", {
|
||||
*!*get*/!*: function() {
|
||||
return this.firstName + ' ' + this.surname;
|
||||
}
|
||||
});
|
||||
|
||||
*!*
|
||||
alert(user.fullName); // Вася Петров
|
||||
*/!*
|
||||
```
|
||||
|
||||
**Обратим внимание, снаружи это обычное свойство `user.fullName`.**
|
||||
|
||||
Лишь в описании указывается, что на самом деле его значение возвращается функцией.
|
||||
|
||||
### Пример: свойство геттер-сеттер
|
||||
|
||||
Также можно указать функцию, которая используется для записи значения, при помощи дескриптора `set`.
|
||||
|
||||
Например, добавим возможность присвоения `user.fullName` к примеру выше:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = {
|
||||
firstName: "Вася",
|
||||
surname: "Петров"
|
||||
}
|
||||
|
||||
Object.defineProperty(user, "fullName", {
|
||||
|
||||
get: function() {
|
||||
return this.firstName + ' ' + this.surname;
|
||||
},
|
||||
|
||||
*!*
|
||||
set: function(value) {
|
||||
var split = value.split(' ');
|
||||
this.firstName = split[0];
|
||||
this.surname = split[1];
|
||||
}
|
||||
*/!*
|
||||
});
|
||||
|
||||
*!*
|
||||
user.fullName = "Петя Иванов";
|
||||
*/!*
|
||||
alert(user.firstName); // Петя
|
||||
alert(user.surname); // Иванов
|
||||
```
|
||||
|
||||
## Геттеры и сеттеры в литералах
|
||||
|
||||
Если мы создаём объект при помощи синтаксиса `{ ... }`, то задать геттеры/сеттеры можно прямо в его определении.
|
||||
|
||||
Для этого используется особый синтаксис: `get свойство` или `set свойство`.
|
||||
|
||||
Например, ниже объявлен геттер-сеттер `fullName`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = {
|
||||
firstName: "Вася",
|
||||
surname: "Петров",
|
||||
|
||||
*!*
|
||||
get fullName() {
|
||||
*/!*
|
||||
return this.firstName + ' ' + this.surname;
|
||||
},
|
||||
|
||||
*!*
|
||||
set fullName(value) {
|
||||
*/!*
|
||||
var split = value.split(' ');
|
||||
this.firstName = split[0];
|
||||
this.surname = split[1];
|
||||
}
|
||||
};
|
||||
|
||||
*!*
|
||||
alert(user.fullName); // Вася Петров (из геттера)
|
||||
|
||||
user.fullName = "Петя Иванов";
|
||||
alert(user.firstName); // Петя (поставил сеттер)
|
||||
alert(user.surname); // Иванов (поставил сеттер)
|
||||
*/!*
|
||||
```
|
||||
|
||||
## Да здравствуют геттеры и сеттеры!
|
||||
|
||||
Казалось бы, зачем нам назначать геттеры и сеттеры через всякие хитрые вызовы? Можно же сделать функции `getFullName`, `setFullName`...
|
||||
|
||||
**Основной бонус -- возможность получить контроль над свойством в любой момент!**
|
||||
|
||||
В начале разработки мы можем использовать обычные свойства, например у `User` будет имя `name` и возраст `age`:
|
||||
|
||||
```js
|
||||
function User(name, age) {
|
||||
this.name = name;
|
||||
this.age = age;
|
||||
}
|
||||
|
||||
var pete = new User("Петя", 25);
|
||||
|
||||
alert(pete.age); // 25
|
||||
```
|
||||
|
||||
**С обычными свойствами в коде меньше букв, они удобны.**
|
||||
|
||||
...Но рано или поздно может наступить расплата!
|
||||
|
||||
Например, когда написано много кода, который использует эти свойства, формат данных изменился и теперь вместо возраста `age` хранится дата рождения `birthday`:
|
||||
|
||||
```js
|
||||
function User(name, birthday) {
|
||||
this.name = name;
|
||||
this.birthday = birthday;
|
||||
}
|
||||
|
||||
var pete = new User("Петя", new Date(1987, 6, 1));
|
||||
```
|
||||
|
||||
Что теперь делать со старым кодом, который выводит свойство `age`?
|
||||
|
||||
Можно, конечно, найти все места и поправить их, но это долго, а иногда и невозможно, скажем, если вы взаимодействуете со сторонней библиотекой, код в которой -- чужой и влезать в него нежелательно.
|
||||
|
||||
Геттеры позволяют обойти проблему легко и непринуждённо.
|
||||
|
||||
Просто добавляем геттер `age`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
function User(name, birthday) {
|
||||
this.name = name;
|
||||
this.birthday = birthday;
|
||||
|
||||
*!*
|
||||
Object.defineProperty(this, "age", {
|
||||
get: function() {
|
||||
var todayYear = new Date().getFullYear();
|
||||
return todayYear - this.birthday.getFullYear();
|
||||
}
|
||||
});
|
||||
*/!*
|
||||
}
|
||||
|
||||
var pete = new User("Петя", new Date(1987, 6, 1));
|
||||
|
||||
alert(pete.age); // получает возраст из даты рождения
|
||||
```
|
||||
|
||||
**Таким образом, `defineProperty` позволяет нам использовать обычные свойства и, при необходимости, в любой момент заменить их на функции, сохраняя совместимость внешнего интерфейса.**
|
||||
|
||||
## Другие методы работы со свойствами
|
||||
|
||||
<dl>
|
||||
<dt>[Object.defineProperties(obj, descriptors)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/defineProperties)</dt>
|
||||
<dd>Позволяет объявить несколько свойств сразу:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var user = {}
|
||||
|
||||
Object.defineProperties(user, {
|
||||
*!*
|
||||
firstName: {
|
||||
*/!*
|
||||
value: "Петя"
|
||||
},
|
||||
|
||||
*!*
|
||||
surname: {
|
||||
*/!*
|
||||
value: "Иванов"
|
||||
},
|
||||
|
||||
*!*
|
||||
fullName: {
|
||||
*/!*
|
||||
get: function() {
|
||||
return this.firstName + ' ' + this.surname;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
alert( user.fullName ); // Петя Иванов
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>[Object.keys(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys), [Object.getOwnPropertyNames(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyNames)</dt>
|
||||
<dd>Возвращают массив -- список свойств объекта.
|
||||
|
||||
При этом `Object.keys` возвращает только `enumerable`-свойства, а `Object.getOwnPropertyNames` -- все:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var obj = {
|
||||
a: 1,
|
||||
b: 2,
|
||||
internal: 3
|
||||
};
|
||||
|
||||
Object.defineProperty(obj, "internal", {enumerable: false});
|
||||
|
||||
*!*
|
||||
alert( Object.keys(obj) ); // a,b
|
||||
alert( Object.getOwnPropertyNames(obj) ); // a, internal, b
|
||||
*/!*
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>[Object.getOwnPropertyDescriptor(prop)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/getOwnPropertyDescriptor)</dt>
|
||||
<dd>Возвращает дескриптор для свойства с `prop`.
|
||||
|
||||
Полученный дескриптор можно изменить и использовать `defineProperty` для сохранения изменений, например:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var obj = { test: 5 };
|
||||
*!*
|
||||
var descriptor = Object.getOwnPropertyDescriptor(obj, 'test');
|
||||
*/!*
|
||||
|
||||
*!*
|
||||
// заменим value на геттер, для этого...
|
||||
*/!*
|
||||
delete descriptor.value; // ..нужно убрать value/writable
|
||||
delete descriptor.writable;
|
||||
descriptor.get = function() { // и поставить get
|
||||
alert("Preved :)");
|
||||
};
|
||||
|
||||
*!*
|
||||
// поставим новое свойство вместо старого
|
||||
*/!*
|
||||
|
||||
// если не удалить - defineProperty объединит старый дескриптор с новым
|
||||
delete obj.test;
|
||||
|
||||
Object.defineProperty(obj, 'test', descriptor);
|
||||
|
||||
obj.test; // Preved :)
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
...И несколько методов, которые используются очень редко:
|
||||
<dl>
|
||||
<dt>[Object.preventExtensions(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal)</dt>
|
||||
<dd>Запрещает добавление свойств в объект.</dd>
|
||||
<dt>[Object.seal(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/seal)</dt>
|
||||
<dd>Запрещает добавление и удаление свойств, все текущие свойства делает `configurable: false`.</dd>
|
||||
<dt>[Object.freeze(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/freeze)</dt>
|
||||
<dd>Запрещает добавление, удаление и изменение свойств, все текущие свойства делает `configurable: false, writable: false`.</dd>
|
||||
<dt>[Object.isExtensible(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isExtensible), [Object.isSealed(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isSealed), [Object.isFrozen(obj)](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/isFrozen)</dt>
|
||||
<dd>Возвращают `true`, если на объекте были вызваны методы `Object.preventExtensions/seal/freeze`.</dd>
|
||||
</dl>
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue