Хотя многие здесь говорят, что нет лучшего способа создания объектов, существует разумное объяснение, почему существует так много способов создания объектов в JavaScript, начиная с 2019 года, и это связано с прогрессом JavaScript над различные версии релизов EcmaScript, начиная с 1997 года.
До ECMAScript 5 существовало только два способа создания объектов: функция конструктора или литеральная запись (лучшая альтернатива new Object ()). С помощью нотации функции конструктора вы создаете объект, который может быть создан в нескольких экземплярах (с новым ключевым словом), в то время как литеральная нотация доставляет один объект, например одиночный.
// constructor function
function Person() {};
// literal notation
var Person = {};
Независимо от используемого вами метода, объекты JavaScript являются просто свойствами пар ключ-значение:
// Method 1: dot notation
obj.firstName = 'Bob';
// Method 2: bracket notation. With bracket notation, you can use invalid characters for a javascript identifier.
obj['lastName'] = 'Smith';
// Method 3: Object.defineProperty
Object.defineProperty(obj, 'firstName', {
value: 'Bob',
writable: true,
configurable: true,
enumerable: false
})
// Method 4: Object.defineProperties
Object.defineProperties(obj, {
firstName: {
value: 'Bob',
writable: true
},
lastName: {
value: 'Smith',
writable: false
}
});
В ранних версиях JavaScript единственным реальным способом имитации наследования на основе классов было использование функций конструктора. функция конструктора - это специальная функция, которая вызывается с ключевым словом «new». По соглашению, идентификатор функции пишется с заглавной буквы, но не требуется. Внутри конструктора мы ссылаемся на ключевое слово this, чтобы добавить свойства к объекту, который неявно создается функцией конструктора. Функция конструктора неявно возвращает новый объект с заполненными свойствами обратно вызывающей функции неявно, если только вы явно не используете ключевое слово return и не возвращаете что-то еще.
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.sayName = function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var bob = new Person("Bob", "Smith");
bob instanceOf Person // true
Проблема с методом sayName. Как правило, в языках программирования на основе объектно-ориентированных классов вы используете классы как фабрики для создания объектов. Каждый объект будет иметь свои собственные переменные экземпляра, но у него будет указатель на методы, определенные в проекте класса. К сожалению, при использовании функции конструктора JavaScript каждый раз, когда она вызывается, она определяет новое свойство sayName для вновь создаваемого объекта. Таким образом, у каждого объекта будет свое уникальное свойство sayName. Это потребляет больше ресурсов памяти.
В дополнение к увеличению ресурсов памяти, определение методов внутри функции конструктора исключает возможность наследования. Опять же, метод будет определен как свойство вновь создаваемого объекта, а не другого объекта, поэтому наследование не может работать как. Следовательно, JavaScript предоставляет цепочку прототипов как форму наследования, что делает JavaScript языком прототипов.
Если у вас есть родитель и родитель имеет много общих свойств дочернего, тогда дочерний объект должен наследовать эти свойства. До ES5 это было выполнено следующим образом:
function Parent(eyeColor, hairColor) {
this.eyeColor = eyeColor;
this.hairColor = hairColor;
}
Parent.prototype.getEyeColor = function() {
console.log('has ' + this.eyeColor);
}
Parent.prototype.getHairColor = function() {
console.log('has ' + this.hairColor);
}
function Child(firstName, lastName) {
Parent.call(this, arguments[2], arguments[3]);
this.firstName = firstName;
this.lastName = lastName;
}
Child.prototype = Parent.prototype;
var child = new Child('Bob', 'Smith', 'blue', 'blonde');
child.getEyeColor(); // has blue eyes
child.getHairColor(); // has blonde hair
То, как мы использовали вышеописанную цепочку прототипов, имеет причуду. Поскольку прототип является действующей ссылкой, изменяя свойство одного объекта в цепочке прототипов, вы также изменили бы то же свойство другого объекта. Очевидно, что изменение наследуемого метода ребенка не должно изменять метод родителя. Object.create решил эту проблему, используя полифилл. Таким образом, с помощью Object.create вы можете безопасно изменять дочернее свойство в цепочке прототипов, не затрагивая то же свойство родителя в цепочке прототипов.
ECMAScript 5 представил Object.create для решения вышеупомянутой ошибки в функции конструктора для создания объекта. Метод Object.create () СОЗДАЕТ новый объект, используя существующий объект в качестве прототипа вновь созданного объекта. Поскольку новый объект создан, у вас больше не возникает проблема, когда изменение дочернего свойства в цепочке прототипов изменит ссылку родителя на это свойство в цепочке.
var bobSmith = {
firstName: "Bob",
lastName: "Smith",
sayName: function(){
return "My name is " + this.firstName + " " + this.lastName;
}
}
var janeSmith = Object.create(bobSmith, {
firstName : { value: "Jane" }
})
console.log(bobSmith.sayName()); // My name is Bob Smith
console.log(janeSmith.sayName()); // My name is Jane Smith
janeSmith.__proto__ == bobSmith; // true
janeSmith instanceof bobSmith; // Uncaught TypeError: Right-hand side of 'instanceof' is not callable. Error occurs because bobSmith is not a constructor function.
До ES6 здесь использовался общий шаблон для создания конструкторов функций и Object.create:
const View = function(element){
this.element = element;
}
View.prototype = {
getElement: function(){
this.element
}
}
const SubView = function(element){
View.call(this, element);
}
SubView.prototype = Object.create(View.prototype);
Теперь Object.create в сочетании с функциями конструктора широко используются для создания и наследования объектов в JavaScript. Тем не менее, ES6 представил концепцию классов, которые в основном являются синтаксическим сахаром по сравнению с существующим наследованием на основе прототипов JavaScript. Синтаксис класса не вводит новую объектно-ориентированную модель наследования в JavaScript. Таким образом, JavaScript остается прототипом языка.
Классы ES6 значительно упрощают наследование. Нам больше не нужно вручную копировать функции-прототипы родительского класса и сбрасывать конструктор дочернего класса.
// create parent class
class Person {
constructor (name) {
this.name = name;
}
}
// create child class and extend our parent class
class Boy extends Person {
constructor (name, color) {
// invoke our parent constructor function passing in any required parameters
super(name);
this.favoriteColor = color;
}
}
const boy = new Boy('bob', 'blue')
boy.favoriteColor; // blue
В целом, эти 5 различных стратегий создания объектов в JavaScript совпали с развитием стандарта EcmaScript.