loading...

Mastering Hard Parts of JavaScript: Prototype & Class II

internettradie profile image Ryan Ameri ・4 min read

Using Object.create()

Exercise 2

Inside personStore object, create a property greet where the value is a function that logs "hello".

const personStore = {
  // add code here
};

personStore.greet(); // -> Logs 'hello'

Solution 2

const personStore = {
  greet: function someName() {
    console.log("hello");
  },
};

Here greet is just a property on an object. It's value happens to be a function (that does something). We can access that property like accessing any other property of an object, using the dot notation personStore.greet. And since it's a function, we can run the function by adding the parentheses so personStore.greet() works and runs the function that we have defined.

It turns out the name of this function someName is not important since it's assigned to the property greet and so someName is never directly used. So we can turn it into an anonymous function:

const personStore = {
  greet: function () {
    console.log("hello");
  },
};

And later on in ES6, they introduced a new more concise syntax, which allows us to write this example as:

const personStore = {
  greet() {
    console.log("hello");
  },
};

All three solution shown here do exactly the same thing.

Exercise 3

Create a function personFromPersonStore that takes as input a name and an age. When called, the function will create person objects using the Object.create method on the personStore object.

function personFromPersonStore(name, age) {
  // add code here
}

const sandra = personFromPersonStore("Sandra", 26);
console.log(sandra.name);
// -> Logs 'Sandra'
console.log(sandra.age);
//-> Logs 26
sandra.greet();
//-> Logs 'hello'

Solution 3

function personFromPersonStore(name, age) {
  const person = Object.create(personStore);
  person.name = name;
  person.age = age;
  return person;
}

The important thing to note here is that Object.create(), irrespective of the argument passed to it, always returns an empty object. So initially, person is an empty object with no properties.

Every object has a hidden [[prototype]] property (bad name!). Simply put, when you call a property on an object, the JS engine first checks to see if the object has that property. If it can't find such a property, it will look at its [[prototype]] property to see which Object it descends from. const person = Object.create(personStore) tells the JS engine to create a new empty object and return it and call it person, but if we call a property of person and person doesn't have that property, look up to personStore and see if it has that property.

So when sandra.name is called, the JS engine looks at the sandra object to see if it has a name property. It does, so its value is returned. Next when sandra.age is called, the JS engine looks at the sandra object to see if it has an age property. It does, so its value is returned. Next, sandra.greet() is called. The JS engine looks at the object sandra to see if it has a greet property. It does not. Instead of failing, the JS engine then looks at sandra's hidden [[prototype]] property which points to personStore, so it then goes to see if personStore has a greet property. It does, so that function is invoked.

This basically achieves what class-based object oriented languages call inheritance, without using any classes. This is a gross simplification of JavaScript's prototypal architecture, for more information I've found this chapter of the javascript.info very helpful.

Exercise 4

Without editing the code you've already written, add an introduce method to the personStore object that logs "Hi, my name is [name]".

// add code here
sandra.introduce();
// -> Logs 'Hi, my name is Sandra'

Solution 4

personStore.introduce = function () {
  console.log(`Hi, my name is ${this.name}`);
};

We can add methods to our personStore at a later time. And the dynamic nature of JS means that even though sandra was created before we created this method, it still gets to use it. Here we also see our first introduction to the this keyword. In this context, this is always the value of the object to the left of the the function being invoked. So here when introduce() is called, the object to its left is sandra, so this gets assigned to sandra. Now when in the body of the method this.name is equal to sandra.name so its value is returned.

Do not use arrow functions when declaring methods on an object. In arrow functions this is lexically scoped, so in this example, if we had declared

personStore.introduce = () => {
  console.log(`Hi, my name is ${this.name}`);
};

Here this would not refer to sandra. It would look to see if the function introduce has a this, it doesn't. So it would look at its outside scope to see if it has a this. Here the outside scope is the global memory or window, which does have an object called this so it checks to see if that object has a name property. It doesn't, so undefined is returned.

Understanding the prototype chain, what JavaScript's lexical scoping means, and how arrow functions change the behaviour of this are perhaps the most challenging parts of JavaScript for someone new to the language. It requires practice, but it will make sense in the end.

Posted on by:

internettradie profile

Ryan Ameri

@internettradie

Re-discovering frontend development. Professional translator & interpreter. Amateur powerlifter. BLM. He/him/his

Discussion

pic
Editor guide