Inheritance that must be known in object orientation

As a programmer living on JS. We have to understand one thing.

JavaScript is the purest object-oriented language

There is no such thing as a Class in JS, and the classes we see in ES6 are modeled on prototypes. Also called [[analog class]]. Much purer than Java,C plus anything!! 🐶

Why do we need inheritance

const kitty = {
	say: function() {
		console.log(Meow, meow, meow.)}}const tiger = {
	say: function(value) {
		console.log('Whoop')}}const lion = {
	say: function(value) {
		console.log(Ow ow ow!)}}Copy the code

Each of the above objects has a say property, written three times. At this point, if we all need to add some judgments to say, we need to add them to each object. So we need a way to make all three objects reference the same method. Implement code reuse.

const Felidae = function({value}) {
	this.value = value
	this.say = function() {
		console.log(this.value)
	}
}  const kitty = new Felidae({value: Meow, meow, meow. })
const tiget = new Felidae({value: 'Whoop' })
const lion = new Felidae({value: Ow ow ow! })
Copy the code

That way we don’t have to write say for every object.

At this point I need to get Felidae to have some common approach to other families and how do I do that?

const Animal = function() {}
animal.prototype.run = function() {
	console.log('I can run')}const Felidae = function() {}
Felidae.prototype = new Animal()

const Kitty = new Felidae()
conosle.log(kitty.run()) // 'I can run'
Copy the code

This is achieved by the assignment of [[prototype]].

What happens when kitty.run ()

The run method does not exist on Kitty objects. But we can call it directly. What happens? To understand this process, we need some prior knowledge of [[object accessor properties]] and [[object property descriptors]].

When almost any object is created, there is a hidden property called prototype [[prototype]]. While we are in the process of kitty.run (). [[prototype]] is the default [[Get]] procedure that looks for its own attributes first, and if it doesn’t, looks for [[prototype]]. Until you find it, or know Object. Prototye.

The end of the [[prototype]] chain is Object.prototype

The word is almost accurate, and objects that should be created by object.create (null) are ‘clean ‘. Try printing it out in your controller.

The default procedure for [[Get]] is this, what about the procedure for [[Put]]?

Kitty.color = ‘white’ masking attribute concept occurs

Following the previous code, assign color to the Kittiy object.

Kitty.color = 'white'
console.log(Kitty.color) // 'white'
Copy the code

When Kittiy is assigned the color attribute, the [[Get]] operation will still be performed. If Kittiy has the color attribute, it will be assigned directly. If not, look up [[prototype chain]]. If there is none, Kitty will be assigned color comfort. But if there is a color attribute on the stereotype chain, the masking attribute occurs by default.

So shielding properties, that is, the lower the property name and [[prototype chain]] under the condition of the above attributes are name repetition, at the bottom of this attribute will give on the [[prototype chain]] to blocked, exist on both sides. If you print it, it will print out the value of the underlying property.

But this is the default case, if the following code occurs, it will be said.

const Felidae = function() {}
Object.defineProperties(Felidae .prototype, {
   color: {
	 value: 'black',}})const Kitty = new Felidae()
Kitty.color = 'white'
console.log(Kitty.color) // 'black'
Copy the code

Explicitly using [[object property descriptor]] defaults to false except value

The above code does not generate a masking attribute and does not change the value of color. Not surprisingly, the following also does not generate a masking attribute:


const Felidae = function() {}
Object.defineProperties(Felidae .prototype, {
   color: {
	set: function() {}}})const Kitty = new Felidae()
Kitty.color = 'white'
console.log(Kitty.color) // undefined
Copy the code

From this we can conclude that there are three things that can happen when we assign a value to an object (from [JavaScript you don’t know]]):

  1. If a normal data access property named color exists at the top of the [[prototype]] chain and is not marked as writable (writable: false), add a color property directly to KittyShielding properties.
  2. If color exists on the [[prototype]] chain, but it is marked as (writable: true,). There is no way to modify existing properties or create masking properties on Kitty. In ES5 strict mode, an error is even reported directly.
  3. If [[prototype]] chain has color and set. Set. color will definitely be called and will not be added to Kitty and will not redefine the color setter.

Inheritance based on archetypes

JS does not exist classes, but prototype-based inheritance, also known as prototype inheritance. Inheritance, in its traditional sense, means the operation of copying. So many people call JS object inheritance just like the relationship between father and son in reality, DNA inheritance and so on. Such a statement is not entirely wrong, it is nonsense.

Js inheritance does not have a copy operation, but to produce a certain relationship between two objects, they are not parent and child relationship. [[JavaScript you don’t know]] uses the word delegate to call the relationship between them. It’s more like an employment relationship, a peer relationship, so to speak.

Change the direction of this to implement inheritance

function Animate() {
	this.color = 'red'
}

function Kitty() {
	Animate.apply(this)}const kitty = new Kitty()
console.log(kitty.color) // 'red'
Copy the code

Kitty has the Animate constructor property. Nice. Appears to have completed inheritance. There was a fatal problem, however. Kitty could not inherit the Animate prototype.

function Animate() {
	this.color = 'red'
}
Animate.prototype = {
	brand: 'benz'
}

function Kitty() {
	Animate.apply(this)}const kitty = new Kitty()
console.log(kitty.color) // 'red'
console.log(kitty.brand) // undefiend
Copy the code

Stereotype association new a constructor to implement inheritance

We use the following code to get what the figure looks like:

const Felidae = function() {}
Felidae.prototype = {
	run: function() {
		console.log('I can run')}}const Kitty = new Felidae()
conosle.log(kitty.run()) // 'I can run'
const lion = new Felidae()

const FelineSubfamily = function() {}
FelineSubfamily.prototype = new Felidae()

const tiger = new FelineSubfamily() 
Copy the code

Kitty, Lion and Tiger seem to have no direct relationship, but they all indirectly point to Felidae. Prototype. It’s not like Java or Dart or something like that. They’re all separate entities once they pass new, or instantiated. Nothing to do with each other.

JS archetypal inheritance is the opposite of Java. An instance of ‘instantiated’ in JS can even change properties above [[prototype]].

const Felidae = function() {
	this.option = {
		color: 'white'}}const FelineSubfamily = function() {}
FelineSubfamily.prototype = new Felidae()

const Kitty = new FelineSubfamily() 
Kitty.option.color = 'mother zha'

const Lino = new FelineSubfamily()

console.log(Kitty.option.color) // 'what?'
console.log(Lino.option.color) // 'I'm sorry'
Copy the code

This example is so typical that the so-called ‘child’ changes the properties of the ‘parent’. [[reference type]]. Can primitive types be changed?

const Felidae = function() {
	this.number = 1
}

const FelineSubfamily = function() {}
FelineSubfamily.prototype = new Felidae()

const Kitty = new FelineSubfamily() 
Kitty.number++
const Lino = new FelineSubfamily()

console.log(Kitty.number) / / 2
console.log(Lino.number) / / 1
Copy the code

The results show no change. What’s going on here? To explain this process, go back to [[what happened when #Kitty ran]]. Number = kitty.number + 1 where a number attribute is directly assigned to Kitty. Assignment is from right to left. Kitty.number found the number above the prototype chain in [[Get]], so 1 + 1 returns 2. In this case, number is the default [[object’s property descriptor]]. It is not read-only, so it assigns the number attribute directly to Kitty.

From this we can know that the hidden trouble of JS inheritance is too big. It also makes it clear that object inheritance is not a copy operation. To reduce that risk. We usually assign attributes to [[prototype]] :

const Felidae = function() {}
Felidae.prototype = {
	color: 'red',}const FelineSubfamily = function() {}
FelineSubfamily.prototype = new Felidae()

const Kitty = new FelineSubfamily() 
Kitty.color = 'white'

const Lino = new FelineSubfamily()

console.log(Kitty.color) / / 'white'
console.log(Lino.color) // 'red'
Copy the code

This eliminates the need for instantiated objects to modify properties above the constructor directly by accessing member properties. The ‘brothers’ are also not changed unexpectedly. It seems that object inheritance is already secure. In fact, it is not safe;

functino Car() {
  this.brand = 'benz'
}
Car.prototype = {
  brand: 'Mazda'.intro: function() {
    console.log(this.brand)
  }
}

car.intro() // 'benz'
car.__proto__.intor() // 'Mazda'
Copy the code

This example also has an explanation for [[This points to the problem]]

We can still access the prototype object through __proto__k. You can still modify things on the prototype chain with __proto__.

function Test() {}
Test.prototype = {
  color: 'red'
}


function A() {}
A.prototype = new Test()

const a = new A()

a.__proto__.color = 'green'
console.log(a.color) // 'green'

const b = new A()
console.log(b.color) // 'green'
Copy the code

Create an intermediate constructor to implement inheritance (enterprise-level approach)

function Test() {}
Test.prototype = {
  color: 'red'
}


function Buffer() {}
Buffer.prototype = Test.prototype
const buffer = new Buffer()

function A() {}
A.prototype = buffer 
A.prototype.color = 'green'

const a = new A()
console.log(a.color) // 'green'

const b = new Test()
console.log(b.color) // 'red'
Copy the code

It is easy to change the properties of the Test prototype by inheriting the object with an intermediate Buffer.

Creating a Buffer each time is cumbersome, so encapsulate it

function inherit(Target, Origin) {
	function Buffer(){}
	Buffer.prototype = Origin.prototye
	Target.prototype = new Buffer()
	// Normalize the constructor data
	Target.prototype.constructor = Target
	Target.prototype.super_class = Origin
}
Copy the code

Since then, the object’s archetypal inheritance is relatively stable. Even if a buffered function is added, the child can still modify the parent’s attributes. It is still possible to change anything on the prototype chain by obj.__proto__.__proto__. XXX.

It also needs to work with [[object property descriptor]] to be stable. Make properties on the prototype chain read-only and unconfigurable.

Object archetypal inheritance is so awkward that it doesn’t work. Not to mention the concept of class-based polymorphism in traditional object orientation. It’s hard. So React came hook, vue came 3.0. Both provide certain concepts of functional programming. Replace object inheritance by combining functions. 🤓