Должны ли функции быть привязаны к объекту или его прототипу? - PullRequest
5 голосов
/ 26 февраля 2020

Я изучаю javascript, и я задаюсь вопросом о концепции функций и прототипов внутри объекта.

Мое понимание концепции

Как я понимаю это значит, что функции должны быть присоединены к прототипу объекта, чтобы выделить меньше памяти.

Например (если то, что я сказал, верно), обе версии будут выполнять ту же работу, но вторая версия будет выделять меньше памяти:

var collectionOfObjects = [];
for(var i=0; i<1000; i++){
    collectionOfObjects.push({id: 2, sayHi: function(){console .log('hi')}})
}
//vs
function Foobar(id){
    this.id = id || 0;
}
Foobar.prototype.sayHi = function(){
    console.log('hi');
}
var otherCollection = [];
for(var i=0; i<1000; i++){
    otherCollection.push(new Foobar());
}

Вопрос

//this attaches sayHi function to the object instead of its prototype
var foobar = {
    id: 0,
    sayHi: function(){
        console.log('Hi');
    }
}

Поскольку я не должен использовать __proto__, как мне прикрепить функцию sayHi к прототипу foobar вместо объекта foobar? Я предполагаю, что добавление sayHi к Object.prototype не является хорошим решением, так как добавило бы функцию sayHi к каждому объекту.

Ответы [ 3 ]

3 голосов
/ 28 февраля 2020

Цепочка прототипов настраивается с помощью ключевого слова new, class или Object.create(). В любом случае, он устанавливается при создании объекта.

Итак, вы уже знаете один способ - добавить функции в прототип функции-конструктора и создать экземпляр с помощью new.

Современный способ - использовать class , но вы специально упомянули здесь унаследованный код, поэтому я собираюсь предположить, что он не подходит для целей этого обсуждения.

Последний способ - использовать Object.create(), который дает вам объект с некоторым прототипом:

var myProto = {
  // There is only one of this function, ever.
  sayHi: function() {
    console.log('hi');
  }
}

var otherCollection = [];
for(var i=0; i<1000; i++){
    var obj = Object.create(myProto)
    obj.id = i // or other custom setup.
    otherCollection.push(obj);
}

Тем не менее, в современных javascript движках, имеющих уникальные функции для каждого объекта, нет ничего особенного. Я работал над некоторыми довольно большими javascript приложениями и использованием памяти, поскольку количество экземпляров функций никогда не было узким местом производительности вообще.

В современном React с функциональными перехватчиками, например, это быть невероятно громоздким (если не невозможным в некоторых случаях), чтобы избежать создания сотен новых функций на каждом рендере. Он спроектирован таким образом, и все еще работает очень хорошо. И если это не работает хорошо, это никогда не происходит из-за слишком большого количества функций.

3 голосов
/ 28 февраля 2020

Вы понимаете, верно. Объекты, созданные с нотацией obj = { ... }, наследуются от Object.prototype, и изменение их прототипа с помощью __proto__ или его современного эквивалента Object.setPrototypeOf является особенно медленным во всех реализациях JavaScript. Но есть и другой способ установить прототип объекта при создании.

Функция Object.create(proto) позволяет вам создать объект, который наследуется от proto, поэтому вам не нужно использовать запутанный Foobar.prototype синтаксис и магический оператор new.

let Foobar = {
  id: 0,
  sayHi: function() {
    console.log('Hi');
  }
};

let obj = Object.create(Foobar);
obj.id = 1;
obj.sayHi(); // Hi

Если бы у вас было дюжина свойств для установки на obj, необходимость присваивать каждое свойство вручную после создания объекта было бы немного неубедительным, но вы для этого можно использовать изящную функцию Object.assign:

let Elephant = {
  name: 'an elephant',
  ears: 'boring',
  sayHi: function() {
    console.log(`Hi, I am ${this.name} and my ears are ${this.ears}.`);
  }
};

let dumbo = Object.assign(Object.create(Elephant), {
  name: 'Dumbo',
  ears: 'AWESOME'
});

dumbo.sayHi(); // Hi, I am Dumbo and my ears are AWESOME.

Если вы хотите создать группу объектов и дать им все те же 3 метода, вы можете заменить это:

let t = { name: "Tom", eat, sleep, repeat };
let g = { name: "Garfield", eat, sleep, repeat };

с этим:

let protoCat = { eat, sleep, repeat };
let t = Object.assign(Object.create(protoCat), { name: "Tom" });
let g = Object.assign(Object.create(protoCat), { name: "Garfield });

Однако было бы более практично иметь вместо этого некоторую функцию конструктора. Использование Object.assign для одного свойства немного излишне, поэтому я упросту следующий пример:

function cat(name) {
    const c = Object.create(protoCat);
    c.name = name;
    return c;
}

let t = cat("Tom");
let t = cat("Garfield");

Обратите внимание, что вы не вызываете cat с new, так как cat создает свой собственный объект. Если вы попытаетесь вызвать cat с оператором new, это приведет к созданию объекта, который наследуется от пустого cat.prototype, но этот объект в конечном итоге будет отброшен, поскольку cat имеет возвращаемое значение.

ОБНОВЛЕНИЕ: Хотя мой ответ дает довольно полное объяснение того, как работает prototype, я чувствую, что не смог указать, что вам вообще не нужно его использовать. Вы могли бы очень хорошо просто назначать методы каждый раз.

function cat(name) {
    return { name, eat, sleep, repeat };
}

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

JavaScript любит обрабатывать объекты как данные, подобно lisp, и если вы просто притворяетесь, что у них есть классы, вы можете в конечном итоге вы хотите, чтобы вы использовали другой язык.

2 голосов
/ 28 февраля 2020

Как сказал Майк в комментариях, псевдоклассическое наследование осуждается классами в ES2015 (ES6) и более поздних версиях, поэтому Foobar.prototype следует отбросить.

Вы выделяете меньше памяти, повторно используя определение функции вместо определения функции для каждого объекта, который вы создаете. Это можно сделать с помощью Footbar.prototype, простых прототипов, классов или простого определения функции без наследования. Как обычно, JavaScript предоставляет множество способов достижения той же цели, и вам решать, какой из них вы выберете.

Ваш вопрос уже содержит псевдоклассический способ сделать это, вы также можете использовать class или простой прототип:

var footbarProptotype = {
    sayHi: function() {
        console.log('Hi');
    }
};

var footbar = Object.create(footbarProptotype);

footbar.sayHi()

И вы можете повторно использовать определение функции без использования какого-либо наследования

function sayHi(){
    console.log('hi');
}

var collectionOfObjects = [];

for(var i=0; i<1000; i++){
    collectionOfObjects.push({ sayHi: sayHi });
}

Оптимизация памяти происходит только после определения функции только один раз и повторное использование этого определения каждый раз, когда вы можете.

...