Безопасные для областей видимости конструкторы

Безопасные для областей  видимости конструкторы в javascript
Фото с https://www.youtube.com/watch?v=soX-NGcaScE

Йо-йо! Если вы читаете мой сайт не в первый раз, то знаете, что я работаю над свой js-библиотекой stickjaw, которая занимается управлением размеров блоков. Так как у меня на неё «большие планы», то обязан заботиться о том, чтобы она была безопасной для использования и не влияла на другие js-инструкции.

Пытаясь найти способы безопасного во всех отношениях использования моей библиотеки, я наткнулся на раздел из книги которую я прочитал год назад. Теперь хочу ей с вами поделиться

Из книги Николаса Заказа «JavaScript для профессиональных веб-разработчиков»

Конструктор — это просто функция, которая вызывается с помощью оператора new. При таком способе использовать объект this внутри конструктора указывает на новый экземпляр объекта типа, например:

function Person(name, age, job){
   this.name = name;
   this.age = age;
   this.job = job;
}
var person = new Person('Nicholas', 29, 'Software Engineer');

В этом примере конструктор Person задаёт с помощью объекта this свойства name, age и job. При использовании конструктора с оператором new создаётся новый объект Preson, которому назначаются свойства. Проблема возникает, если конструктор вызывается без оператора new. Поскольку указатель this связывается со значением во время выполнения, непосредственный вызов Person() сопоставляет this c глобальным объектом (window), что приводит к случайному расширению не того объекта, например:

var persone = Person('Nichilas', 29, 'Software Engineer');
alert(window.name); // Nichilas
alert(window.age); // 29
alert(window.job); // Software Engineer

Здесь объект window расширяется тремя свойствами, предназначенными для экземпляра Person. Это происходит потому, что конструктор был вызван как обычная функция, без оператора new. Проблема возникает из-за позднего связывания объекта this, который в данном случае был сопоставлен с объектом window. Поскольку свойство name объекта window используется для идентификации объектов ссылок и фреймов, такая случайная перезапись свойства может привести и к другим ошибкам на странице. Решить проблему можно, создав безопасный для областей видимости конструктор (scope-safe constructor).

Такие конструкторы перед выполнением каких-либо изменений проверяют, являются ли объект this экземпляром правильного типа. Если нет, конструктор создаёт и возвращает новый экземпляр, например:

function Person(name, age, job){
   if(this instanceof Person){
      this.name = name;
      this.age = age;
      this.job = job;  
   } else {
      return new Person(name, age, job);
   }  
}
var person1 = new Person('Nicholas', 29, 'Software Engineer');
alert(window.name); // ''
alert(person1.name); // 'Nichilas'

var person2 = new Person('Shelby', 34, 'Ergonomist');
alert(person2.name); // 'Shelby'

Инструкция if в этом конструкторе Person проверяет, является ли объект this экземпляром Person. Если да, это означает, что был использован оператор new либо конструктор был вызван в контексте существующего экземпляра Person. В обоих случаях инициализация объекта продолжается обычным образом. Если this не является экземпляром Person, конструктор вызывается еще раз с помощью оператора new, а созданный объект возвращается из функции. В итоге вызов конструктора Person с оператором new или без него возвращает новый экземпляр Person, что предотвращает случайное задание свойств глобального объекта.

При использовании безопасных для областей видимости конструкторов нужно иметь в виду, что при этом вы фиксируете контекст, в котором может быть вызван конструктор. Если используется паттерн наследования, основанный на краже конструктора, но цепочка прототипов не применяется, то наследование может быть нарушено, например:

function Polygon(sides){
  if(this instanceof Polygon){
    this.sides = sides;
    this.getArea = function(){
      return 0;
    };
  } else {
    return new Polygon(sides);
  }
}
function Rectangle(width, height){
	Polygon.call(this, 2);
	this.width = width;
	this.height = height;
	this.getArea = function(){
		return  this.width * this.height;
	}
}
var rect = new Rectangle(5, 10);
alert(rect.sides) // undefined

В этом коде конструктор Polygon безопасен для области видимости, а конструктор Rectangle — нет. При создании экземпляра Rectangle , он должен унаследовать свойство sides от типа Polygon, благодаря вызову Polygon.call(). Однако из-за того что конструктор Polygon, поэтому создаётся и возвращается новый объект Polygon. Объект this в конструкторе Rectangle не расширяется, а значение, возвращенное методом Polygone.call(), не используется, так что у созданного объекта Rectangle нет свойcтва sides.

Эта проблема не возникает, если с паттерном «Кража» конструктора используется цепочка прототипов или паразитное комбинированное наследование. Рассмотрим пример:

function Rectangle(width, height){
	Polygon.call(this, 2);
	this.width = width;
	this.height = height;
	this.getArea = function(){
		return this.width * this.height;
	}
}
Rectangle.prototype = new Polygon();

var rect = new Rectangle(5, 10);
alert(rect.sides)

В этом коде экземпляр Rectangle является также экземпляром типа Polygone, так что метод Polygon.call() работает надлежащим образом, добавляя свойство sides к экземпляру Rectangle.

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