How to add new method to existing object? - javascript

I tried this but it doesn't work:
const robot = {
name: 'Marble',
model: '5F',
age: 1,
};
robot.prototype.sayHello = function(){return `Hello I am ${this.model}`}
I get error: Uncaught TypeError: Cannot set properties of undefined (setting 'sayHello')

It's the same way you add any other value to an existing object.
You add it to the object.
robot.sayHello = function ...
The prototype property is used on constructor functions / classes to add to all instances of the class.
class Robot {
constructor(name, model, age) {
this.name = name;
this.model = model;
this.age = age;
}
}
const robot = new Robot('Marble', '5F', 1);
const robot2 = new Robot('ZYX', '6F', 5);
Robot.prototype.sayHello = function(){return `Hello I am ${this.model}`}
console.log(robot.sayHello());
console.log(robot2.sayHello());
… although modifying a class after you create it isn't a great idea. Now we have class syntax we can build classes in one go.

Related

How to use constructors as a prototype chain?

Suppose that I have a javascript constructor:
function Person(name) {
this.name = name;
this.hello = function () { return "It's a-me, " + name + "!"; };
}
the Person "type" has a convenient method, hello that I would like to re-use on another type Student. I would like for a Student to have the following structure:
function Student(name) {
this.name = name;
this.hello = function () { return "It's a-me, " + name + "!"; };
this.books = [];
}
One option is to use the code for Student as-is above. This is sub-optimal for the usual reasons, such as that if I want it to mirror the Person type, then I have to manually keep their code in sync. Anyway, this is not good.
A second option (from this answer) is to do something like:
function Student(name) {
Person.call(this, name);
this.books = [];
}
When I mario = new Student("mario") I get the following:
Object { name: "mario", hello: hello(), books: [] }
I've successfully achieved the inheritance that I wanted, but this has the unfortunate property of placing all of the desired properties into my object. Notably, for example, there is a "hello" property on mario. It would be nice if that "hello" property could be looked up in the prototype chain.
How can I neatly create a prototype chain given the relevant object constructors?
When you create an object with new, the this value of your constructor function is set to the object, and that object's prototype is set to the prototype of the constructor Function being called.
That's why your properties are currently being added to the created object.
function Student {
this.name = name
}
const student = new Student('John')
// is (almost) equivalent to the following
const student = {}
student.name = 'John'
But if you want to add properties to the prototype instead, so that you can use inheritance, then in ES5 Javascript you can do so by assigning properties directly to the prototype of your constructor function.
function Person(name) {
this.name = name;
}
// Person is a function
// Its prototype is an instance of Object, and it has no properties
// i.e. something like Person.prototype = new Object()
Person.prototype.hello = function() {
return 'It is I, ' + this.name
}
// Now Person.prototype has one property, "hello", which is a function.
function Student(name) {
Person.call(this, name)
this.books = [];
}
// Student is a function
// Its prototype is also an instance of Object with no properties
// the following is the magic line
Student.prototype = Object.create(Person.prototype)
// We replace the prototype of the Student function with a new object, but
// Object.create() allows us to set the prototype to an existing object, in this case Person.prototype,
// Person.prototype is itself an instance of Object, and we previously
// added the "hello" function to it as a property.
const student = new Student('John')
// So what happens here?
// First a new object is created, and its prototype is set to Student.prototype
// Then we call Person.call(this)
// Which executes the body of the Person function
// So you get the properties on the object itself through the body of the Person and Student functions
// And you get the shared functionality through the prototype chain of instance -> Student.prototype -> Person.prototype -> Object.prototype
Hope that helps!
You can use prototyping method or class sugar method as you want.
Here is a simple example :
function Student(name) {
this.name = name;
this.books = [];
}
Student.prototype.hello = function(){
return "It's a-me, " + this.name + "!";
}
Student.prototype.addBook = function(book){
this.books.push(book);
}
Student.prototype.getBooks = function(){
return this.books;
}
let mario = new Student("Mario");
console.log(mario.hello());
mario.addBook("prototyping");
mario.addBook("chain");
console.log(mario.getBooks());
class Person {
constructor(name) {
this.name = name;
this.books = [];
}
hello(){
return "It's a-me, " + this.name + "!";
}
addBook(book){
this.books.push(book);
}
getBooks(){
return this.books;
}
}
let luigi = new Person("Luigi");
console.log(luigi.hello());
luigi.addBook("classSugar");
luigi.addBook("classType");
console.log(luigi.getBooks());
For longer chains use Object.assign, here is an example of making a GradStudent that is both a Student and a Person and has the personality of a Comedian and also has the properties and methods of a 4th class GameCharacter:
(function() {
//Person
function Person(name) {
this.name = name;
this.helloString = "Hello my name is "
}
Person.prototype.name = "Bob";
Person.prototype.hello = function() {
return this.helloString + this.name;
};
//Student
function Student(name, books) {
Person.call(this, name);
this.books = books;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.books = ["math","reading"];
//Comedian
function Comedian(name) {
Person.call(this,name);
};
Comedian.prototype = Object.create(Person.prototype);
Comedian.prototype.constructor = Comedian;
Comedian.prototype.hello = function() {
return "I don't know what my parents where thinking when they name me Squat, just kidding, my name is " + this.name;
};
//GameCharacter
function GameCharacter(power) {
this.power = power;
};
GameCharacter.prototype = new Object();
GameCharacter.prototype.constructor = GameCharacter;
GameCharacter.prototype.gainPower = function(power) {
this.power += ", "+power;
};
GameCharacter.prototype.statePower = function() {
return this.power;
};
//GradStudent
function GradStudent(name, books, degree) {
Comedian.call(this, name);
Student.call(this,name,books);
GameCharacter.call(this, "jumping");
this.degree = degree;
this.gainPower("flying");
}
GradStudent.prototype = Object.create(Student.prototype);
Object.assign(GradStudent.prototype, Comedian.prototype, GameCharacter.prototype);
GradStudent.prototype.constructor = GradStudent;
var gradStudent = new GradStudent("Bill",["C", "C++", "JavaScript"], "B.S.");
console.log(gradStudent.hello() + " I have a " + gradStudent.degree +" I am studying " + gradStudent.books.toString() + ". \n In a game I play my power's are "+ gradStudent.statePower() + ". \n Is gradStudent also a Student? " + (gradStudent instanceof Student) + "" );
var otherStudent = new Student("Jennifer" ,["english", "science"]);
console.log(gradStudent.books.toString() + " " + otherStudent.books.toString());
})();
GradStudent is an instance of Student, it's a type of Student, and can also do all the things Comedian and GameCharacter does. The value of Object.assign is that kind of multiple inheritance.
I can accomplish such a thing with the following:
function Student(name) {
Object.setPrototypeOf(this, new Person(name));
this.books = [];
}
However, I'm not familiar enough with javascript to know what possible problems might arise with this solution. Coming from other OO style languages, it feels weird for the prototype of mario to be an actual instance of a Person, but I suppose everything in js is an instance, in some sense, so this might just be bias on my part.

JS inheritance - MDN article

given MDN JS Inheritance article, we have these lines
My question is, why use Object.create and not just Person.prototype?
I understand the need to link prototypes.
But here is console example rendering the call to Object.create in fact not connecting the inherited methods:
Why is that? is it mistake in the article?
Teacher.prototype = Person.prototype
That sets the prototye of the teachers to the same object as the persons prototype. So if you change that:
Teacher.prototype.hi = () => alert("hi");
Then that exists both on teachers and persons:
new Person().hi();
Thats not what you want when creating a subclass. If you do
Teacher.prototype = Object.create( Person.prototype );
You create a new object that inherits the persons prototype. Now the properties do not exist on the object itself, but they are inherited. That getOwnPropertyNames returns nothing does not mean that the properties are not inherited but the opposite: They just don't exist on the object itself, but on its parent.
new Teacher().greeting(); // works :)
The problem with Teacher.prototype = Person.prototype is that then, there is no actual inheritence going on - both prototypes will reference the same object. If you proceed to add a function to Teacher's prototype, for example getClassTaught(), that will mutate Person.prototype, which should not have that method.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() { return this.name; };
function Teacher(name, className) {
this.name = name;
this.className = className;
}
Teacher.prototype = Person.prototype;
Teacher.prototype.getClassTaught = function() { return this.className; };
const person = new Person();
console.log('getClassTaught' in person);
You also wouldn't be able to shadow Person functions without replacing them entirely. For example, if there's a greeting() function on Person.prototype, and you assign another greeting() function to Teacher.prototype, you'll be overwriting the function on Person.prototype - other persons calling greeting() may not work anymore, because the function is now Teacher-specific, rather than Person-generic.
function Person(name) {
this.name = name;
}
Person.prototype.getName = function() { return this.name; };
Person.prototype.greeting = function() { return 'Hi, I am ' + this.name; };
function Teacher(name, className) {
this.name = name;
this.className = className;
}
Teacher.prototype = Person.prototype;
Person.prototype.greeting = function() { return 'Hi, I am Teacher ' + this.name; };
const person = new Person('Bob');
console.log(person.greeting());
getOwnPropertyNames only shows you the property names directly on the object itself - it does not show inherited property names. When you use Object.create(Person.prototype), greeting is inherited from the Person prototype; it's not directly on Teacher.prototype, so it doesn't show up in getOwnPropertyNames.

Error when trying to work with nested objects with constructor

I'm getting the following error when trying to create a nested object using a constructor:
Uncaught TypeError: Cannot read property 'data' of undefined
Here's my code:
function Car(name){
this.name.data.CarName = name;
this.show = function(){document.getElementById("fill").innerHTML = toyota.name.data.CarName;};
}
var toyota;
function functionadd(){
toyota = new Car("Toyota");
toyota.show();
}
I have looked this up everywhere and can't seem to find an answer and am wondering if I'm making some stupid mistake.
Any help is fantastic, thanks :)
It's because object name is undefined and there is also no property data on it, so you have to initialize this.name and this.name.data before adding CarName:
function Car(name){
this.name = {};
this.name.data = {};
this.name.data.CarName = name;
this.show = function(){document.getElementById("fill").innerHTML = toyota.name.data.CarName;};
}
It's shorter with object litteral:
function Car(name){
this.name = { data: { CarName: name } };
this.show = function(){document.getElementById("fill").innerHTML = toyota.name.data.CarName;};
}

javascript prototype propertie hookup

var People = function()
{
this.FirstName = "John";
this.LastName = "Doer";
}
var Employee = function(o)
{
this.DateHired = "June 30";
this.prototype = o;
}
var obj = new People();
var employee1 = new Employee(obj);
print(employee1.DateHired);
print(employee1.FirstName);
Output:
June 30
I expected the output should be:
June 30
Jonh
but it is not what I expected. So could someone explain me what is wrong with the above code?
You've set a property called prototype on the instance created by new Employee, which doesn't do anything to hook up a prototype chain. The prototype property goes on the function Employee. That property is then used to set the [[Prototype]] internal property of objects created via new Employee.
So assuming you want Employee to be a subclass of People, you'd do it like this:
// The singular is "Person", so I'll use that instead of People
var Person = function() {
this.FirstName = "John";
this.LastName = "Doer";
};
var Employee = function() {
// Give `Person` a chance to do its initialization of the instance
Person.call(this/*, possible, arguments, here*/);
// Now carry on with the `Employee` initialization
this.DateHired = "June 30";
};
// Make Employee a subclass of Person by
// 1. Giving `Employee.prototype` a new object whose [[Prototype]] is
// `Person.prototype`
// 2. Ensuring that `Employee.prototype`'s `constructor` property points
// back to Employee.
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
// Usage
var employee1 = new Employee();
console.log(employee1.DateHired);
console.log(employee1.FirstName);
However, here at the end of 2016, we don't need to play those games anymore: We can use the class syntax of ES2015 (transpiling if we need to deploy the code to older browsers), which handles the plumbing for us:
class Person {
constructor() {
this.FirstName = "John";
this.LastName = "Doer";
}
}
class Employee extends Person {
constructor() {
// Give `Person` its chance to initialize the instance
super();
// Now carry on with the `Employee` initialization
this.DateHired = "June 30";
}
}
// Usage
const employee1 = new Employee();
console.log(employee1.DateHired);
console.log(employee1.FirstName);
Inheritance works as below
var People = function()
{
this.FirstName = "John";
this.LastName = "Doer";
}
var Employee = function()
{
this.DateHired = "June 30";
//this.prototype = o;
People.call(this);
}
Employee.prototype=Object.create(People.prototype);
var employee1 = new Employee();
console.log(employee1.DateHired);
console.log(employee1.FirstName);
When you say:
this.prototype = o;
You are essentially creating a property named "prototype" and giving it a value.
Javascript assumes that you are trying to store values in a variable named "prototype".
If you typed:
print(employee1.prototype.FirstName);
You would get
Output: John

How to define a non-extensible javascript object

I'd like to, if possible, define a javascript object that has a few properties along with getters/setters for those properties, but I don't want others to be able to add new properties to objects without extending the object definition (similar to how one would define a class in Java/C#). Is this possible to do with javascript?
You can use the "preventExtensions" method.
var obj = { foo: 'a' };
Object.preventExtensions(obj);
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions
In the following way, you can freeze the instances of the objects, but leave open to inheriting classes to add their own properties:
function Animal(name, action) {
this.name = name;
this.action = action;
if (this.constructor === Animal) {
Object.freeze(this);
}
}
var dog = new Animal('rover', 'bark')
dog.run = function(){console.log('I\'m running!')} // throws type error
function Dog(name, action, bark) {
Animal.call(this, name, action)
this.bark = bark // Animal not frozen since constructor is different
Object.freeze(this)
}
var puppy = new Dog('sparky', 'run', 'woof')
puppy.isTrained = false; // throws type error
See here: http://www.2ality.com/2013/06/freezing-instances.html
You can use Object.seal(obj):
const obj = Object.seal({
a: 1,
b: "hello"
})
obj.c = "world" // silently fails

Categories

Resources