Как переопределить функцию при создании нового объекта в прототипальном наследовании?

Из этот пост в блоге у нас есть этот пример прототипного наследования в JavaScript:

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        alert(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        alert(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'}
});

var female = Object.create(human, {
    gender: {value: 'Female'}
});

var david = Object.create(male, {
    name: {value: 'David'},
    planetOfBirth: {value: 'Mars'}
});

var jane = Object.create(female, {
    name: {value: 'Jane'}
});

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

Теперь мне интересно, как один правильно "переопределить", например, функцию sayPlanet?

Я пробовал вот так:

jane.sayPlanet = function(){
    console.log("something different");
};

и это работает.

Однако я также пробовал это следующим образом:

var jane = Object.create(female, {
    name: {value: 'Jane'},

    sayPlanet: function(){
        console.log("something different");
    }
});

но я получаю ошибку типа.

Мои вопросы:

  • Как добавить функцию sayPlanet в Object.create?
  • это вообще "хороший способ" или есть лучший способ (лучшая практика)?

изменить Я понял, как я могу добавить sayPlanet внутри Object.create:

sayPlanet: {
    value: function(){
        console.log("something different");
    }
}

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

edit # 2: Как указал Махавир ниже, это ужасный пример, потому что, как оказалось, вы не можете (исправить меня, если я ошибаюсь) измените name jane, когда он был Object.create d.

edit # 3: (человек, человек, это приведет меня к определенному объекту, где люди носят белые пальто). Поскольку @WhiteHat указывает ниже, действительно вы можете установить свойство name для обновления следующим образом:

var jane = Object.create(female, {
    name: {
        value: 'Jane',
        writable: true
    }
});

а затем вы можете сделать jane.name="Jane v2.0";.

Я буду честным здесь, люди - у меня нет подсказки относительно того, в каком направлении идти, по-видимому, так много вариантов. И только сегодня я читаю Эрика Эллиота https://medium.com/javascript-scene/the-two-pillars-of-javascript-ee6f3281e7f3, и теперь я не знаю, что думать больше, потому что он продолжает утверждать, что люди на ES6 не совсем верно: O. Мех, я думаю, мне придется снова взглянуть на книгу Крокфордов, решиться на "один путь" и посмотреть, как далеко она меня ведет.

Ответ 1

Использование Object.create, безусловно, является допустимым подходом, но сам пример кажется мне немного ошибочным в отношении внутренней работы Object.create. Сообщение в блоге действительно помогает описать различные способы создания объектов в javascript, но я не думаю, что пример для Object.create дает хорошее представление о том, как он работает, что больше похоже на подход new/constructor чем может показаться.

Object.create позволяет создать объект на основе prototype, но без constructor. Это означает, что prototype chain созданного объекта не зависит от constructor (поэтому его проще проследить, этот прототип, связанный через конструктор, не очень прост или легко следовать). Но Object.create все еще создает prototype chain, таким же образом new делает.

Итак, в вашем примере, когда вы определяете name в human, например, здесь:

var human = {
    name: '',

И затем, когда вы создаете jane:

var jane = Object.create(female, {
    name: {value: 'Jane'}

Вы действительно не назначаете значение name property, которое вы определили в human. Фактически вы добавляете свойство к Джейн. Но human.name все еще является свойством в prototype chain of jane. Он работает, потому что javascript будет следовать цепочке прототипов, чтобы найти первое свойство соответствия, но human.name все еще каким-то образом связано с jane.

Смотрите здесь: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

То же самое происходит, если вы используете конструктор:

var Person = function(gender, name) {
     this.name = name;
}

var newPerson = new Person();

И что же касается функции sayPlanet.

Это действительный подход, но может привести к странному поведению. Например, вы можете решить modifiy sayPlanet для всех людей, назначив его следующим образом:

human.sayPlanet = function(){console.log('new sayPlanet')}

Это будет работать для всех humans, кроме тех, для которых вы предоставили свой собственный sayPlanet свой собственный. Что в вашем случае может быть ожидаемым результатом. Но все же вы должны убедиться, что sayPlanet действительно должен быть свойством человека.

С gender он применяется в human и в male и female. Поэтому изменение human.gender не будет работать ни на одном. Но он все еще является свойством human, который немного запутан, когда вы хотите работать с этими объектами. У вас в основном есть свойство, которое определено, которое доступно для записи, но что когда изменение не имеет никакого эффекта. В основном это указывает на то, какое свойство вам нужно добавить к вашим экземплярам людей или где-то в цепочке прототипов. Опять же, похоже, что он используется много, но когда объясняется с помощью подобных примеров, он каким-то образом создает впечатление, что Object.create просто объединяет свойства, но это не то, что он делает.

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

Если вы хотите использовать prototypes, вы можете использовать подход new/constructor или Object.create. Но Object.create создаст цепочку прототипов так же, как это делает new, она просто избавится от конструкторов.

Небольшой пример того, как Object.create и new разделяют некоторые виды поведения:

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        console.log(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
    gender: {value: 'Male'}
});

var female = Object.create(human, {
    gender: {value: 'Female'}
});


var david = Object.create(male, {
    name: {value: 'David'},
    planetOfBirth: {value: 'Mars', configurable: true}
});

var jane = Object.create(female, {
    name: {value: 'Jane'},
    sayPlanet: {value: function(){
        console.log("something different");
        }, writable: true, enumerable: true, configurable: true
    }
});

var Male = function(name){ // in this case the real constructor is female or male. Name is the only property that 'needs' to be initialized
    this.name = name;
    this.planetOfBirth = 'Jupiter';
}

Male.prototype = Object.create(male);

var john = new Male('John')

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // Jane was born on Earth

john.sayGender(); // John says my gender is Female
john.sayPlanet(); // John was born on Earth


delete david.planetOfBirth; //just to show how human properties will still be in the chain even if you delete them
delete john.name; // It true also if you use new. 
delete jane.sayPlanet;

console.log('\n','----after deleting properties----');

david.sayPlanet();
jane.sayPlanet();
john.sayGender();

human.planetOfBirth = 'Venus'; // This will apply to all humans, even the ones already created
                             
console.log('\n','----after assigning planetOfBirth on human----');

david.sayPlanet();
jane.sayPlanet();
john.sayPlanet(); // John still has planetOfBirth as its own property, since it wasn't deleted

delete john.planetOfBirth;

console.log('\n','----after deleting john planetOfBirth----');

john.sayPlanet();  // But it still there

Ответ 2

Лучшей практикой, как я узнал, является определение разрешений на запись на уровне прототипа объекта. Я изучил эту технику из прочтения Адди Османи JavaScript Design Patterns, очень авторитетного и открытого источника в Интернете: http://addyosmani.com/resources/essentialjsdesignpatterns/book/

Пожалуйста, проверьте свойство sayPlanet в приведенном ниже примере. Имейте в виду, вам не нужно устанавливать все другие свойства "writeable" в false, я только сделал это в своем примере кода, чтобы проиллюстрировать эту точку. Такой подход обеспечивает большую гибкость и возможность повторного использования, чем другие, все еще действительные, подходы. Здесь вы можете заметить, что это синтаксис Object.defineProperties в прототипе.

var human = {
  name: {
    value: '',
    writable: false //only to illustrate default behavior not needed
  },
  gender: {
    value: '',
    writable: false //only to illustrate default behavior not needed
  },
  planetOfBirth: {
    value: "Earth",
    configurable: false //only to illustrate default behavior not needed
  }

};

//here we define function properties at prototype level

Object.defineProperty(human, 'sayGender', {
  value: function() {
    alert(this.name + ' says my gender is ' + this.gender);
  },
  writable: false
});

Object.defineProperty(human, 'sayPlanet', {
  value: function() {
    alert(this.name + ' was born on ' + this.planetOfBirth);
  },
  writable: true
});

//end definition of function properties

var male = Object.create(human, {
  gender: {
    value: 'Male'
  }
});

var female = Object.create(human, {
  gender: {
    value: 'Female'
  }
});

var david = Object.create(male, {
  name: {
    value: 'David'
  },
  planetOfBirth: {
    value: 'Mars'
  }
});

var jane = Object.create(female, {
  name: {
    value: 'Jane'
  }
});

//define the writable sayPlanet function for Jane
jane.sayPlanet = function() {
  alert("something different");
};

//test cases

//call say gender before attempting to ovverride
david.sayGender(); // David says my gender is Male

//attempt to override the sayGender function for david 
david.sayGender = function() {
  alert("I overrode me!")
};
//I tried and failed to change an unwritable fucntion
david.sayGender(); //David maintains that my gender is Male

david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // something different

Ответ 3

Из MDN...

Второй аргумент Object.create вызывает propertiesObject.

Раздел синтаксиса Object.defineProperties описывает его лучше всего, см. реквизиты.

Короче говоря, второй аргумент, переданный в Object.create, должен иметь перечислимые свойства, каждый с одним или несколькими из следующих ключей...

конфигурируемый
перечислимы
значение
записываемый
получить
set

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

Из примера в вашем вопросе, чтобы изменить значение после создания объекта, просто добавьте writable: true.

var human = {
    name: '',
    gender: '',
    planetOfBirth: 'Earth',
    sayGender: function () {
        console.log(this.name + ' says my gender is ' + this.gender);
    },
    sayPlanet: function () {
        console.log(this.name + ' was born on ' + this.planetOfBirth);
    }
};

var male = Object.create(human, {
  gender: {
    value: 'Male',
    writable: true
  }
});

var david = Object.create(male, {
  name: {
    value: 'David',
    writable: true
  },
  planetOfBirth: {
    value: 'Mars',
    writable: false  //only born once
  }
});

david.sayGender();
david.sayPlanet();

david.gender = 'Female';
david.name = 'Caitlyn';
david.planetOfBirth = 'Venus';  //(will not work, not writable)

david.sayGender();
david.sayPlanet();

Ответ 4

Очень плохой способ реализовать наследование, я действительно плохо себя чувствую в этом примере.

Что делать, если вы хотите изменить какое-то значение, скажем, "planetOfBirth/gender" позже, когда вы создали объект.

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

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

//Following the same example: lets understand this like a real life problem
//Creates human objects
var human = {
  name: '',
  gender: '',
  planetOfBirth: 'Earth',
  sayGender: function () {
      console.log(this.name + ' says my gender is ' + this.gender);
  },
  sayPlanet: function () {
      console.log(this.name + ' was born on ' + this.planetOfBirth);
  }
};

//Creates Person constructor to use later to create Male/Female objects like David/Jane
var Person = function(gender, name) {
 this.gender = gender;
 this.name = name;
};

//Assigning the human as the prototype of Person constructor
Person.prototype = Object.create(human);

//Setter function to change the planet of birth of a person
Person.prototype.setPlanetOfBirth = function(planetOfBirth){
  this.planetOfBirth = planetOfBirth;
};

//Creating david object using Person constructor
var david = new Person('Male', 'David');
//change the planet of birth for David as Mars
david.setPlanetOfBirth('Mars');

//Creating jane object using Person constructor
var jane = new Person('Female', 'Jane');

//A object specific function the will only available to Jane to use
jane.sayPlanet = function(){
 console.log("something different");
};

david.sayGender(); // David says my gender is Male
david.sayPlanet(); // David was born on Mars

jane.sayGender(); // Jane says my gender is Female
jane.sayPlanet(); // something different

Ответ 5

1 - он должен быть как ниже блока кода:

var jane = Object.create(Female.prototype, {
    name: {value: 'Jane'},

    sayPlanet:{ value : function(){ alert("something different");}}
});

2 - это решение хорошо, и вы можете увидеть эту ссылку

Ответ 6

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

var human = {
  sayPlanet:function(){
    // old sayPlanet
  }
}

function Person(name, sayPlanet)
{
  this.name = name;
  this.sayPlanet = sayPlanet;
}

Person.prototype = human;

// use your constructor function
var david = new Person('david', function(){/*new sayPlanet*/});

Существует также class .. extends, предложенный в стандарте ES6, но вы должны помнить, что это синтаксический сахар над существующим прототипом javascript наследование, и в javascript все еще нет родных классов.