What did new do in JavaScript, and how did it relate to the prototype chain?

Posted by greencoin on Sat, 18 May 2019 19:07:21 +0200

Original text: https://legacy.ofcrab.com/press/javascript-new.html

If we talk about JavaScript's new in an object-oriented way, it's still hard to understand. We can understand it in another direction.

You humans

I am a programmer and a person. I may:

  • There's a loud name.
  • Born on a certain day
  • It's a man.
  • I can walk.
  • I can still run.
  • Still hopping
  • Able to speak
  • I can also write code.

So, in JavaScript, we might express me as follows:

const me = {
  name: 'Pan Banxian, a bearded farmer and co-worker',
  birth: '1988-08-08',
  sex: 'male',
  walk: function (speed, direction, duration) {
    // Long time to walk in direction at speed
  },
  run: function (speed, direction, duration) {
    // Like running, speed
  },
  jump: function (high, direction, angle) {
    // Jump high from angle to direction
  },
  speak: function (letters) {
    // Speak letters
  },
  coding: function (language, line) {
    // What about writing programs?
  }
}

You humans

Of course, I can't be the only programmer in the world, let alone me. Like our small company, there are seven or eight hundred people. It seems that all these people's data are stored in the database:

name sex birth
Pan Tao male 1988-08-08
superb male 1985-08-09
Soft spring rain male 1999-08-08

We query the above records from the database, and in JavaScript they may be represented as a two-dimensional data, and then create these three people, possibly as follows:

const people = DB.query()
// people = [['Pan Tao','male','1988-08-08'], [...], [...]]
for (let i = 0; i < people.length; i++) {
  let [name, sex, birth] = people[i]
  people[i] = {
    name,
    sex,
    birth,
    walk: function () {},
    run: function () {},
    jump: function () {},
    speak: function () {},
    coding: function () {}
  }
}

Duplicate resource usage

As you have found above, to create three objects, walk, run, jump, speak, coding, these five things can do (methods), in fact, the same way, but we repeated to describe how to do, in fact, it takes up a lot of resources, so we may like to improve the following:

const walk = function walk () {}
const run = function run () {}
const jump = function jump () {}
const speak = function speak () {}
const coding = function coding () {}

for (let i = 0; i < people.length; i++) {
  let [name, sex, birth] = people[i]
  people[i] = {
    name,
    sex,
    birth,
    walk,
    run,
    jump,
    speak,
    coding
  }
}

Different people share the same resources (methods)

But there are more than humans in the world.

Yes, compared with other creatures in the world, the number of human beings is worth mentioning. If like the above, all kinds of things that different species can do will have to define different functions. Peristalsis is certainly not what human beings will do, but many other creatures will do it. So, for the convenience of code management, we put all the things that human beings can do in one pair. In this case, it would be equivalent to having a namespace and would no longer conflict with other species.

const whatPeopleCanDo = {
    walk: function () {},
    run: function () {},
    jump: function () {},
    speak: function () {},
    coding: function () {}
}
for (let i = 0; i < people.length; i++) {
  let [name, sex, birth] = people[i]
  people[i] = {
    name,
    sex,
    birth,
    ...whatPeopleCanDo
  }
}

prototype

However, some people may not know how much their sex information is, others may not know how much their birth is, but we hope that when we create this person, we can give some initial data to the unknown data, so what People CanDo can not fully express a person, we can improve:

const peopleLike = {
    name: '',
    sex: 'unknown',
    birth: '',
    walk: function () {},
    run: function () {},
    jump: function () {},
    speak: function () {},
    coding: function () {}
}
for (let i = 0; i < people.length; i++) {
  let [name, sex, birth] = people[i]
  people[i] = {
    ...peopleLike,
    name: name || peopleLike.name,
    sex: sex || peopleLike.sex,
    birth: birth || peopleLike.birth
  }
}

In this way, we can add some default values for unknown attributes. We call peopleLike the prototype. It shows what attributes a species like human has and what it can do.

Prototype chain

Although the above version is much better than the original version, there is still a lot of room for improvement. Now let's change it as follows:

const peoplePrototype = {
    name: '',
    sex: 'unknown',
    birth: '',
    walk: function () {},
    run: function () {},
    jump: function () {},
    speak: function () {},
    coding: function () {}
}
for (let i = 0; i < people.length; i++) {
  let [name, sex, birth] = people[i]
  people[i] = {
    name: name || peoplePrototype.name,
    sex: sex || peoplePrototype.sex,
    birth: birth || peoplePrototype.birth,
    __proto__: peoplePrototype
  }
}

Instead of binding all the methods in the human prototype to a person, we specify, as above, a special field _proto_: My prototype is the object of the peoplePrototype, and we have a rule: If you want to request a method from me that you don't have on me, go to my prototype and find it, if I do. If there is no prototype on it, go to the prototype of my prototype and look for it until there is no higher prototype somewhere.

The people object created as above has its own properties, but when we access the people.speak() method, we actually access people. proto. Speak (), which is our rule.

More Elegant Creation of New Human Beings

We can't always write an object by ourselves when we need to create a new person, just like the above, and then specify its prototype manually. So we can create a function to generate human:

const peoplePrototype = {
    name: '',
    sex: 'unknown',
    birth: '',
    walk: function () {},
    run: function () {},
    jump: function () {},
    speak: function () {},
    coding: function () {}
}
const makePeople = function makePeople(name, sex, birth) {
  let people = {}
  people.name = name || peoplePrototype.name
  people.sex = sex || peoplePrototype.sex
  people.birth = birth || peoplePrototype.birth
  people.__proto__ = peoplePrototype
  return people
}

people = people.map(makePeople)

Now we just need to introduce the makePeople function to create new people anytime, anywhere.

A slightly more elegant improvement

Obviously, this is not the best way to define a prototype and a prototype object. We can merge the two together, so we can have the following implementations:

const People = function People (name, sex, birth) {
  let people = {}
  people.name = name || People.prototype.name
  people.sex = sex || People.prototype.sex
  people.birth = birth || People.prototype.birth
  people.__proto__ = People.prototype
  return people
}

People.prototype = {
    name: '',
    sex: 'unknown',
    birth: '',
    walk: function () {},
    run: function () {},
    jump: function () {},
    speak: function () {},
    coding: function () {}
}

We call directly the function that created human beings People. This function has an attribute called prototype, which indicates what the prototype of the object created with this function is and what the function does before. Create temporary objects, set the properties of objects, bind the prototype, and then return.

The magic of this

In addition to human beings, there are other animals, such as Tiger, Fish and so on. In the above way, we will create temporary objects with different tiger or fish names in Tiger() or Fish() functions. This is too troublesome. We can call all the objects created by this function "this object", that is, this object. We don't care whether people are ghosts or ghosts. All temporary objects are called thisObject or, more simply, this.

const People = function People (name, sex, birth) {
  let this = {}
  this.name = name || People.prototype.name
  this.sex = sex || People.prototype.sex
  this.birth = birth || People.prototype.birth
  this.__proto__ = People.prototype
  return this
}

Of course, the above paragraph of code is problematic, just assume the same, is this feasible?

new

So far, we have found the evolution of the whole code. It's time to introduce this new. What does it do? It is followed by a function similar to the People above, indicating that I need to create an instance of People. Its invention is to solve all the repetitive things above. With new, we do not need to define a temporary object every time. In the context of new, we automatically create a temporary variable this in the body of the People function. That means the object to be created. At the same time, for instance created with new, prototype of creating function is automatically bound as prototype, and a constructor function for People is automatically created to indicate what the prototype creating function is. So we can change it to the following:

const People = function People (name, sex, birth) {
  this.name = name || People.prototype.name
  this.sex = sex || People.prototype.sex
  this.birth = birth || People.prototype.birth
}

People.prototype.name = ''
People.prototype.sex = 'unknown'
People.prototype.birth = ''
People.prototype.walk = function () {}
People.prototype.run = function () {}
People.prototype.jump = function () {}
People.prototype.speak = function () {}
People.prototype.coding = function () {}

people = people.map(p => new People(...p))

summary

What on earth did new do? When new People()

  1. Create the temporary variable this and bind this to the body of the People function
  2. Execute People.prototype.constructor = People
  3. Execute this. _proto_ = People. prototype
  4. Executing self-definitions in the body of People functions
  5. Returns the newly created object

Topics: Javascript Database Spring Attribute