В то время как многие люди здесь говорят, что нет лучшего способа создания объектов, существует разумное объяснение, почему существует так много способов создания объектов в 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.