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()
- Create the temporary variable this and bind this to the body of the People function
- Execute People.prototype.constructor = People
- Execute this. _proto_ = People. prototype
- Executing self-definitions in the body of People functions
- Returns the newly created object