Js Drip #7 Опасность при использовании "this" в конструкторах
Опубликовано Вт. 03 Февраль 2015 в Translations
Вольный перевод Dealing with the Dangers of this
in Constructors
В прошлой статье мы говорили, что опасно вызова конструктор без new
. А теперь расскажем почему.
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
// Безопасный вызов
var red = new Color(255, 0, 0);
// Опасный вызов
var blue = Color(0, 0, 255);
Когда конструктор вызывается с ключевым словом new
, значение this
внутри конструктора указывает на новый объект,
который создается. Но когда конструктор вызывается как обычная функция значение this
конструктора соответствует
значению this
любой другой функции. И хорошо, если это будет глобальный объект (window
в браузере).
Вот иллюстрация проблемы:
// Глобальная переменная
r = "Rodent Of Unusual Size";
function Color(r, g, b) {
this.r = r;
this.g = g;
this.b = b;
}
// Опасный вызов
// `this` указывает на глобальный объект
var blue = Color(0, 0, 255);
// выведет: 0
console.log(r);
// Выведет: undefined
console.log(blue);
В примере вышел есть глобальная переменная r
. Или, если посмотреть под другим углом, то глобальный объект содержит
свойство r
. Когда конструктор Color
вызывается без new
, this
конструктора указывает на глобальный объект
(в большинстве случаев). Таким образом функция-конструктор просто переписывает глобальную переменную r
, когда задает
свойство объекта blue
.
Кроме того, т.к. конструктор Color
был вызван как обычная функция, то он не вернул автоматически новый объект,
поэтому значение blue
равно undefined
.
Думаю, ты представляешь, как может быть "весело" отлаживать такой баг. Как можно избежать этой проблемы? На самом деле решение крайне простое:
// Глобальная перменная
r = "Rodent Of Unusual Size";
function Color(r, g, b) {
// Проверяем, что `this` это
// объект `Color`.
if (this instanceof Color) {
this.r = r;
this.g = g;
this.b = b;
} else {
// Если это не так, то принудительно вызываем
// конструктор правильно.
return new Color(r, g, b);
}
}
// Опасный вызов
// Сейчас `this` указывает на глобальный объект.
var blue = Color(0, 0, 255);
// Выведет: "Rodent Of Unusual Size"
console.log(r);
// Выведет: Color {r: 0, g: 0, b: 255}
console.log(blue);
В обновленном конструкторе Color
, мы сначала проверяем, что this
указывает на образец Color
. Это корректно,
потому что ключевое слово new
всегда создает новый объект-образец конструктор прежде, чем функция-конструктор начнет
работать.
Если this
указывает не на образец Color
, то конструктор быз вызван не правильно, а значит пропускаем логику
конструктора и вернем в качестве результат корректно вызванный конструктор.
Вот такой способ избежать опасных вызовов функции-конструктора и защититься от перезаписи свойств глобальный объектов.
Конечно, используя такой способ разработчики могут привыкнуть к вызову конструктора без new
, что плохо.
Если ты хочешь просто заставить их использовать new
, то можно вместо корректно вызова конструктора вызывать ошибку:
function Color(r, g, b) {
// Проверяем, что `this`указывает
// на образец `Color`.
if (this instanceof Color) {
this.r = r;
this.g = g;
this.b = b;
} else {
// Если это не так, то вызываем ошибку.
throw new Error("`Color` invoked without `new`");
}
}
Теперь ты знаешь как обезопасить свои конструкторы от вызова без ключевого слова new
.