introduce

In this article, the tenth in the advanced JavaScript in-depth series, we’ll take a closer look at object orientation, archetypes, and inheritance in JS

The body of the

1. Object-oriented definition

1.1 What is object orientation

Object orientation is an abstraction of reality

Object is a very important concept in JS, because object can encapsulate multiple related data together to better describe a thing:

For example, a cat can have attributes such as age, name, weight, color, breed, and so on

It’s much better to describe things in terms of objects than to separate things from reality into data structures in code:

  • So some programming languages are pure object-oriented programming languages, such as Java
  • When you implement anything, you abstract it into a class, and then you create a new class

Object orientation in 1.2JS

JS actually supports a variety of programming paradigms, including functional programming and object-oriented programming

  • JS objects are designed as an unordered collection of attributes, like a hash table, with keys and values combined
  • Key is an identifier name, and value can be any type or other object or function type
  • If the value is a function, we can call it a method of the object

How objects are created

Use the new

// This method is also used frequently by early adopters of JS
var person = new Object(a)// New Object() creates an empty Object
obj.name = 'alex'
obj.age = 19
obj.run = function() {}
Copy the code

Use literals

var person = {
    name: 'alex'.age: 19.run: function() {}}Copy the code

2. Operations on object properties

var obj = {
  name: 'alex'.age: 18,}// Read attributes
console.log(obj.name)
// Write attributes
obj.age = 20
// Delete attributes
delete obj.age
Copy the code

2.1 Defining a single attribute

Attribute descriptors can be used to accurately add or modify attributes of objects

The object.defineProperty () method is used to configure property descriptors

2.1.1 Object. DefineProperty

The Object.defineProperty method directly defines a new property on an Object, or modifies an existing property of an Object, and returns the Object

Object.defineProperty(obj, prop, descriptor)

Three parameters are received:

  • objThe object for which attributes are to be defined
  • propThe name or Symbol of the property to define or modify
  • descriptorThe property descriptor to define or modify

2.1.2 Classification of attribute descriptors

Attribute descriptors fall into two categories:

  • Data Properties descriptors
  • Accessor Properties (Desriptor)
configurable enumerable value writable get set
Data descriptor
Access descriptor

2.1.3 Data attribute descriptors

Data attribute descriptors have the following properties:

  • [[Configurable]]: indicates whether the attribute can passdeleteDelete the property to see if its properties can be modified, or if it can be modified to access the property descriptor
    • When we define a property directly on an object, the properties of that property[[Configurable]]fortrue
    • When we define a property through a property descriptor, the properties of the property[[Configurable]]The default isfalse
  • [[Enumerable]]: indicates whether the attribute can passfor-inorObject.keys()Return this property
    • When we define the property directly on an object, the property[[Enumerable]]fortrue
    • When we define a property through a property descriptor, the properties of the property[[Enumerable]]The default isfalse
  • [[Writable]]: indicates whether the value of an attribute can be modified
    • When we define the property directly on an object, the property[[Writable]]fortrue
    • When we define a property through a property descriptor, the properties of the property[[Writable]]The default isfalse
  • [[value]]: Value of the property, which is returned when the property is read and changed when the property is modified
    • Default to this valueundefined
var obj = {
  name: 'obj',}If it is currently a data attribute descriptor, four attribute descriptor entries can be received
Object.defineProperty(obj, 'age', {
  value: 20.// Cannot be deleted or redefined
  configurable: false.// Print and for-in and object.keys () are not accessible
  enumerable: false.// Cannot be written
  writable: false,})Copy the code

2.1.4 Accessing attribute descriptors

Data attribute descriptors have the following properties:

  • [[Configurable]]: indicates whether the attribute can passdeleteDelete the property to see if its properties can be modified, or if it can be modified to access the property descriptor
    • When we define a property directly on an object, the properties of that property[[Configurable]]fortrue
    • When we define a property through a property descriptor, the properties of the property[[Configurable]]The default isfalse
  • [[Enumerable]]: indicates whether the attribute can passfor-inorObject.keys()Return this property
    • When we define the property directly on an object, the property[[Enumerable]]fortrue
    • When we define a property through a property descriptor, the properties of the property[[Enumerable]]The default is ` false
  • [[get]]: Function to be executed when getting attributes, defaultundefined
  • [[set]]: Function to execute when setting properties, defaultundefined
var obj = {
  name: 'obj'._age: 20,}// Access property descriptors
Object.defineProperty(obj, 'age', {
  configurable: true.enumerable: true.get() {
    console.log('returns the value of age,The ${this._age}`)
    return this._age
  },
  set(newVal) {
    this._age = newVal
    console.log('capture the new value of age,${newVal}`)
  },
})

obj.age = 20 // age is change, and the new value is 20
console.log(obj.age) // Return the age value, 20
Copy the code

2.1.5 Attribute descriptors can receive summary items

Note: The default value here is when a property is defined using the property descriptor

The descriptor role The default value
configurable When this descriptor istrueCan manipulate attribute descriptors and delete from corresponding objects false
enumerable When this descriptor istrueCan appear on an enumerated property of the object (can be traversed). false
value Attributes can be values, functions, or objects undefined
writable When this descriptor istrue, the value of this attribute can be changed by the assignment operator false
get Property, which is fired when the value of the property is accessed and executes without passing any arguments, but this. The return value of this function is used as the value of the property undefined
set Property, which is fired when value is assigned to that property. This function takes an argument (that is, the new value being assigned) and passes in the this object of the assignment object undefined

2.1.6 Configuring Attribute Descriptors

// Create a property
let obj = {
  name: 'alex',}Object.defineProperty(obj, 'age', {
  value: 18.// Attributes created in this way are not enumerable by default. Enumerations need to be turned on to be iterated
  enumerable: true,})console.log(obj) // { name: 'alex', age: 18 }
Copy the code
// Case 2: an attribute is not allowed to be traversed
const obj = {
  name: 'obj'.age: 20.gender: 'Male',}Object.defineProperty(obj, 'age', {
  // Change not enumerable to false
  enumerable: false,})for (const k in obj) {
  // Age does not appear when traversing
  console.log(k, obj[k])
}
Copy the code

2.2 Defining multiple attributes at the same time

Define multiple new properties or modify existing properties directly on an Object using the object.defineProperties () method and return the Object

let obj = {
  _age: 19,}Object.defineProperties(obj, {
  name: {
    configurable: false.writable: true.enumerable: true.value: 'obj',},age: {
    configurable: false.enumerable: false.get() {
      return this._age
    },
    set(val) {
      this._age = val
    },
  },
})

console.log(obj)
Copy the code

If you use the property descriptor to define a new property, both Enumerable and 64x are true, and you just want to set the property set and GET, the different information works like this:

const obj = {
  name: 'obj'._age: 18.set age(newVal) {
    console.log('the age attribute set')
    this._age = newVal
  },
  get age() {
    console.log('get the age attribute')
    return this._age
  },
}

obj.age = 22
console.log(obj.age)
Copy the code

3. Object method supplement

3.1 Obtaining the property operator of an object

  • Object.getOwnPropertyDescriptor(object, "attr name")
  • Object.getOwnPropertyDesciptors(object)

3.2 Object extension of new attributes is prohibited

  • Object.preventExtensions(object)
  • This method will fail to add new attributes to an object (error in strict mode)
'use strict'
let obj = {
  name: 'obj',}Object.preventExtensions(obj)
obj.age = 20  / / an error
Copy the code

3.3 Sealing Objects

  • Object.seal(object)
  • Objects are not allowed to add new attributes and delete existing attributes, and property operators are not allowed to be modified
  • At its core is invocationpreventExtensionsFunction and place the property descriptorconfigurable: false
'use strict'
let obj = {
  name: 'obj',}Object.seal(obj)
obj.name = 'alex' / / normal
delete obj.name  / / an error
obj.age = 20  / / an error
Object.defineProperty(obj, 'name', {
  writable: true,})/ / an error
Copy the code

3.4 Freezing Objects

  • Object.freeze(obj)
  • It is not allowed to change the value of existing attributes, add attributes, delete attributes, or modify attribute operators
  • The core is calledsealAnd attribute descriptorswritable: false
'use strict'
let obj = {
  name: 'obj',}Object.freeze(obj)
obj.name = '123'  / / an error
delete obj.name  / / an error
obj.age = 20  / / an error
Object.defineProperty(obj, 'name', {
  writable: true,})/ / an error
Copy the code

Note: The freeze method can only freeze a level 1 attribute

'use strict'
let obj = {
  name: 'alex'.foo: {
    name: 'foo',}}Object.freeze(obj)

obj.foo.name = 'foo2'  / / is not an error
obj.name = 'obj' / / an error
Copy the code

4. Create a scheme with multiple objects

For example, if we want to create an object, all objects are based on the Person template object, and all have properties and methods such as name, age, height, etc., but they all have different values, what is the best way to create the object?

4.1 Factory Mode

We can quickly imagine one way to create objects: the factory pattern

  • The factory pattern is a very common design pattern
  • Usually we have a factory function that produces objects
'use strcit'
// createPerson is a factory function
function createPerson(name, age, height) {
  return {
    name: name,
    age: age,
    height: height,
    running: function() {
      console.log(this.name, 'is running')},eating: function() {
      console.log(this.name, 'is eating')}}}var ZhangSan = createPerson('zhangsan'.18.1.8)
var LiSi = createPerson('lisi'.25.1.88)
var WangWu = createPerson('wangwu'.19.1.75)
/ /...
Copy the code

Disadvantages of the factory model:

Cannot get the true type of the Object (any Object created through the factory schema is of type Object)

4.2 Constructors

  • Constructors, also known as constructors, are the methods we usually call when creating objects. ES6 introduces class (just syntactic sugar in this form, which it will eventually convert to)
  • In other object-oriented programming languages, a constructor is a method that exists in a class, called a constructor
  • The constructor in JS is also an ordinary function
  • If a normal function is usednewCall, the function is a constructor
function foo() {}
Foo is just a normal function
foo()
// using the new keyword foo is a constructor
// If you pass no arguments to the constructor, you can omit the parentheses, but it is not recommended
new foo()
Copy the code

The difference between new and normal calls

If a function is called by operator new, the following operations are performed:

  1. Creates a new empty object in memory
  2. Inside this object[[prototype]]Property will be changed to that constructor[[prototype]]
  3. The this inside the constructor points to the new object being created
  4. The internal code that executes the function (function body)
  5. If the constructor does not return a non-empty object, the new object is returned
var f1 = new foo()
console.log(f1)
// f1 prints foo {}, indicating that new's new object belongs to foo
Copy the code

Use constructors to create batch objects

'use strcit'
// createPerson is a factory method
function Person(name, age, height) {
  this.name = name
  this.age = age
  this.height = height
  this.running = function() {
    console.log(this.name, 'is running')}this.eating = function() {
    console.log(this.name, 'is eating')}}var ZhangSan = new Person('zhangsan'.18.1.8)
var LiSi = new Person('lisi'.25.1.88)
var WangWu = new Person('wangwu'.19.1.75)
console.log(ZhangSan, LiSi, WangWu)
// Print out that these three objects are of type Person
Copy the code

Disadvantages of constructors

Front knowledge

function foo() {
  function bar() {
    console.log('bar')}return bar
}

var f1 = foo()
var f2 = foo()

console.log(f1 === f2)  // false
Copy the code

F1 and F2 are actually two independent functions, although the function body is the same, but will occupy two memory space

function Person(name) {
  this.name = name
  this.running = function() {
    console.log(this.name, 'is running')}}var p1 = new Person('zhangsan')
var p2 = new Person('lisi')
console.log(p1.running === p2.running)  // false
Copy the code

In the example above, Person is a constructor, p1 and p2 are two instance objects, and the running method is an instance method. Obviously, the body of the instance method of both instance objects is the same, and we want to be, but it takes up two memory Spaces. It follows that the more objects you create, the more memory you will consume, but most of the functions that consume memory are the same

Running === p2.running; running == p2.running; running = p2.running;

Next, we’ll look at the solution: the Prototype

5. The prototype

5.1 Object prototype

The stereotype of an object is usually called an implicit stereotype

All objects in JS have a special built-in property [[prototype]] that points to an object.

[[Prototype]]
var obj = { name: 'alex' }
Copy the code

Early ECMA didn’t specify how to view the [[prototype]] property, and some browsers provided __proto__ attributes for objects to make it easier to view prototype objects. Do not use this property arbitrarily in a production environment, there may be some browsers that do not implement this feature

After ES5+, a method called Object.getProtoTypeof () is provided to facilitate access to prototypes in production.

var obj = { name: 'alex' }
console.log(Object.getPrototypeOf(obj))  // [Object: null prototype] {}
Copy the code

Object is an empty Object, and Object is null

conclusion

  • Every object in JS has a [[prototype]] property that points to the object’s implicit prototype **

  • To see this prototype in the early days, some browsers implemented the __proto__ attribute to see the [[prototype]] attribute

  • The later ES5 version of the ECMA specification implements the Object.getProtoTypeof () property to view [[prototype]]

5.1.1 The role of object archetypes

When we access a property of an object, the [[getter]] of the property is triggered. There are two steps:

  • View properties in the current object and use them if found
  • If they don’t find it, they follow the prototype chain

5.2 Prototype of function

A prototype of a function is called an explicit prototype

Function is also an object, for functions, it also has a __proto__ attribute (compatibility issues, only partially implemented by browsers)

The function also has an explicit prototype property: prototype (ECMA specification, no compatibility issues)

5.2.1 to look new

As mentioned above, in the process of new:

  • Inside this object[[prototype]]Property will be changed to that constructor[[prototype]]

It’s actually an internal implementation of something like this

function foo() {
   this.__proto__ = foo.prototype
}
Copy the code
// Create an instance with new
var f1 = foo()
var f2 = foo()
// f1 and F2 both have __proto__ foo. Prototype
console.log(f1.__proto__ === foo.prototype)
console.log(f2.__proto__ === foo.prototype)
Copy the code

5.2.2 Look at the picture and understand

function Person() {}

var p1 = new Person()
var p2 = new Person()
Copy the code
  • Create Person, create a function object in memory, and the Person in GO will point to that function object. In the function object, includes[[parentScope]],Function body,prototypeAnd theprototypeWill point to the prototype object of Person

  • newThe process of creatingp1andp2“, opened up in memoryp1andp2And the inside contains__proto__Property, which points to its constructor, which isPersonThe prototype object of

  • So p1.__proto__ === p2.__proto__ === Person.prototype, the process above is internal help us do

  • So when we access an attribute in an instance object, we will first look to see if we have the attribute, and if not, we will look for the prototype based on the __proto__ attribute to see if the object is in the prototype

    // Hence the operation
    p1.__proto__.name = 'alex'
    console.log(p2.name) // alex
    // However, __proto__ has compatibility issues. It is not recommended to operate directly on __proto__, but on the constructor's prototype
    Person.prototype.name = 'zhang'
    console.log(p1.name) // zhang
    console.log(p2.name) // zhang
    Copy the code

5.2.3 Properties on the function prototype

function foo() {}
console.log(foo.prototype) // Although this object is {} empty, it has many built-in properties
Copy the code

There is a constructor attribute on the stereotype

console.log(foo.prototype.constructor) // The value points to foo itself
// So there will be operations like this
console.log(foo.prototype.constructor.prototype.constructor.rototype.constructor)
Copy the code

We can also add our own attributes to the prototype

function foo() {}
foo.prototype.message = 'hello world'
foo.prototype.age = 20

// The __proto__ of all instance objects passing new foo will carry message and age
var f1 = new foo()
console.log(f1.__proto__.message)
F1. message can also be found according to the attribute lookup rule f1.message
console.log(f1.message)
Copy the code

If you add too many properties, it’s too cumbersome to use XXX. Prototype every time, so sometimes you can modify Prototype directly

foo.prototype = {
    name: 'alex'.age: 20.message: 'hello world'.run: function() {}}Copy the code

One big problem with overwriting is that, according to the ECMA specification, prototype needs to have a constructor attribute built in, but we need to modify the constructor attribute to point to a new object that doesn’t have the constrcutor attribute

foo.prototype = {
    // Don't define it directly! Because the default stereotype's Constructor property enumerable is false
    // constructor: foo,
    name: 'alex'.age: 20.message: 'hello world'.run: function() {}}// Do this in development
Object.defineProperty(foo.prototype, 'constructor', {
  configurable: true.writable: true.enumerable: false.value: foo,
})
Copy the code

5.3 Back to 4.2

In 5.1, we mentioned that every time a new instance object is created, it needs to open up memory space to store instance functions. After learning the prototype knowledge above, it is easy to come up with a solution

// The original code
function Person(name) {
  this.name = name
  this.running = function() {
    console.log(this.name, 'is running')}}var p1 = new Person('zhangsan')
var p2 = new Person('lisi')
console.log(p1.running === p2.running) // false
Copy the code
// Mount the instance method to the constructor prototype so that all instance functions of the instance object refer to the same function
function Person(name) {
  this.name = name
}
Person.prototype.running = function() {
  console.log(this.name, 'is running')}var p1 = new Person('zhangsan')
var p2 = new Person('lisi')
console.log(p1.running === p2.running) // true
Copy the code
  • Person.prototype points to the same memory space as p1.__proto__ and p2.__proto__, and a running function is mounted in Person.prototype

  • P1.__proto__ running function, which refers to Person. Prototype. running

  • Person. Prototype.running === p1.__proto__. Running === p2.__proto__

  • Easy to solve the instance function caused by excessive memory space

Question to consider: Can common attributes be included in prototypes?

The answer is clearly no

function Person(name) {
  Person.prototype.name = name
}

var p1 = new Person('zhangsan')
var p2 = new Person('lizi')

console.log(p1.name) // lisi
Copy the code

Because prototype is common, p2’s name overrides the name of the Person prototype.

Therefore, the same logic can only be placed on a common stereotype, and unique attributes need to be maintained internally

5.4 The prototype is disconnected from the instance

Redefining archetypes by literals in different places can lead to broken links

function Person(name) {
  this.name = name
}

var a1 = new Person('zhangsan')

Person.prototype = {
  running: function() {},}Object.defineProperty(Person.prototype, 'constructor', {
  configurable: true.enumerable: false.writable: true.value: Person,
})

// Create instance before overwriting prototype
__proto__ and Person.prototype do not refer to the same object
console.log(Person.prototype.running) // running
console.log(a1.running) // undefined
console.log(Person.prototype === a1.__proto__) // false
Copy the code

This is easy to understand, as long as you avoid defining instances until you redefine stereotypes

5.5 prototype chain

Pre-knowledge:

JS due to design problems, and other pure Object oriented different, all JS objects are cloned, each Object will save its prototype information, all Object root prototype Object is Object prototype Object

var obj = {
  name: 'alex'.message: '123',}console.log(obj.address)
Copy the code
  • In the previous section, we said that it is triggered when an object’s properties are accessed[[getter]]operation
  • It first looks for the property on the object and returns it
  • If you can’t find it, go to the original object’s prototype object (__proto__), if you can’t find it, go to the prototype object of the prototype object
  • Until the root prototype object for all objects is found (ObjecttheA prototype objectThe prototype of the)nullIf not found, returnundefined
  • This lookup chain is called a prototype chain (prototype chain)
  • There are many default properties and methods on objects, and all objects have these properties and methods because of the stereotype chain
var obj = {}
// 1. Create an empty object {}
// 2. Change this of the empty object to obj
__proto__ = object.prototype; // 3
// 4. Return this new object
console.log(obj.__proto__ === Object.prototype)  // true
console.log(Object.prototype.__proto__)  // null
Copy the code

The prototype Object is the top-level prototype Object

  • constructionalprototypeThe explicit stereotype points to the stereotype object of the constructor
  • Instance object__proto__The implicit stereotype points to the stereotype object of the constructor
  • Constructor prototype object__proto__An implicit stereotype refers to a prototype Object of Object
  • All objects or functions trace back to the prototype and eventually find the prototype Object of Object

Confused:

Object.__proto__ === Function.prototype // true
Copy the code
  • As in the figure above, the explicit prototype of a function points internally to the prototype Object of Object

6. Implement inheritance

function Person() {}var p1 = new Person()
Copy the code

In the above example code, how do we address Person?

  • In JS, Person is called a constructor
  • From the perspective of the developer of an object-oriented programming language, Person is a class because we can create instance object A1 through a class
  • From a programming paradigm perspective, Person is really a class

6.1 Object-oriented Features

Object-oriented has three major features: encapsulation, inheritance and polymorphism

  • Encapsulation: The process of encapsulating classes and methods into an object, as described in the previous example code
  • Inheritance: Inheritance is one of the most important aspects of object orientation, not only to reduce the amount of repetitive code, but also the premise of polymorphism (in pure object orientation)
  • Polymorphic: Different objects take on different forms during execution

6.2 packaging

function foo(name) {
  this.name = name
}

foo.prototype.running = function() {}
Copy the code

To simplify, encapsulating properties and methods into a class is the process of encapsulation

In pure object-oriented languages, encapsulation is really more about hiding implementation details and exposing only parts of an operational interface to the outside world

6.3 inheritance

  • What does inheritance do?
    • Inheritance allows us to extract duplicate code and logic into the parent class, and subclasses simply inherit from it
  • How is inheritance implemented in JS?
    • Inheritance is implemented through the mechanism of prototype chain

6.3.1 Why Inheritance is Needed

function Student(name, sno) {
  this.name = name
  this.sno = sno
}
Student.prototype.running = function() {
  console.log(this.name, ' running')
}
Student.prototype.studying = function() {
  console.log(this.name, 'studying')}function Teacher(name, title) {
  this.name = name
  this.title = title
}
Student.prototype.running = function() {
  console.log(this.name, ' running')
}
Student.prototype.teaching = function() {
  console.log(this.name, 'teaching')}Copy the code

By comparing the Student and Teacher constructors, we find that there are many duplicate codes name and running. Is it possible to have a common constructor for both?

6.4 Prototype chain implementation inheritance

// Parent class: public properties and methods
function Person() {
  this.name = 'alex'
}
Person.prototype.running = function() {
  console.log(this.name, ' running')}// Subclasses: private attributes and methods
function Student() {
  this.sno = 123
}
// Why is this necessary here?
Student.prototype = new Person()

var s1 = new Student()
s1.running() // Successfully inherit to the Person method
Copy the code

Student. Prototype = new Person() We look at the picture

  • The relationship between the Person constructor and the prototype object

  • The procedure for new Person()
    • Create an empty object
    • Point the this of the Person function to the empty object
    • Empty object[[prototype]]Point to the function’s Prototype
    • Executing function body
    • Returns the created object (where the name property exists)
  • Student.prototype = new Person()Student’s prototype object is an empty object,__proto__The prototype object pointing to Person

  • Create s1 Student instance object,s1.__proto__Access the Student’s prototype objects1.name, can not be found in this object, according to the prototype chain, go to s1’s prototype, that is, Student’s prototype object to find the name

  • accesss1.runningStudent’s prototype object (Person’s prototype object) can not be found, so go to Student’s prototype object (Person’s prototype object), find the running function, and execute it.

Disadvantages of prototype chain implementation inheritance

  • Drawback 1: When you print an S1 object, some properties are not visible (because inherited properties and methods are placed on the prototype or parent)

  • Type attribution error: s1 returns a Person type. (Since the Student prototype object does not have a constructor property, you can define one yourself.)

    Object.defineProperty(Student.prototype, 'constructor', {
      enumerable: false.configurable: true.writable: true.value: Student,
    })
    Copy the code
  • Malpractice three: the malpractice of common attribute existence

    function Person() {
      this.name = 'alex'
      this.friends = []
    }
    function Student() {
      this.sno = 123
    }
    Student.prototype = new Person()
    
    var s1 = new Student()
    var s2 = new Student()
    
    s1.friends.push('Friends of S1')
    // s1 and S2 use friends from the Student prototype object
    // So S1 affects S2, but that's not what we expect
    console.log(s2.friends) // ["s1's friend "]
    
    // What's wrong with it?
    s1.name = 's1'
    console.log(s2.name)  // alex 
    // It looks like s1 does not affect S2, but in fact, if the direct assignment is not found in this object, it creates a new property named name in this object with the value s1
    S1. name and s2.name are not the same thing
    Copy the code
  • Drawback 4: In the above code, we can’t customize without passing the parameter in the new process

6.5 Using constructors to implement inheritance

To overcome the many drawbacks of prototype-chain inheritance, the community has come up with a constructor stealing scheme

function Person(name, friends) {
  this.name = name
  this.friends = friends
}
Person.prototype.running = function() {
  return 'running is good'
}
function Student(name, friends, sno) {
  // Call the Person function implicitly via call
  When an object is new, the function body code is executed
  S1 and s2 execute the Person function, except that this is s1 and s2
  Person.call(this, name, friends)
  this.sno = sno
}
Student.prototype = new Person()

var s1 = new Student('alex'['s1'].123)
// Solve the problem four
console.log(s1) // { name: 'alex', son: 123 }
// At the same time, this situation also solves the third drawback
var s2 = new Student('zhangsan'['s2'].456)
s1.friends.push('s3')
console.log(s2.friends) // [ 's2' ]
Copy the code

Person.call(this, … Args (args, args, args, args, args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args, Args

Disadvantages of constructor inheritance

There are drawbacks to using constructors for inheritance:

  • PersonThe function is called three times (call twice, new Person() once)
  • StudentRedundant attributes in prototype objects (added when new Person())

6.6 Original inheritance

This model was proposed by Douglas Crockford (front-end master, JSON founder) in his 2006 article Prototypal Inheritance in JavaScript (using prototype Inheritance in JS)

In this article, we present a method of inheritance that is not implemented through constructors

However, this inheritance is limited to objects

var obj = {
  name: 'alex'.age: 18,}function createObject(o) {
  var obj = {}
  Object.setPrototypeOf(obj, o)
  return obj
}

// Since the setPrototype method didn't exist when Douglas proposed it, this is how he implemented it
function createObject2(o) {
  function fn() {}
  fn.prototype = o
  return new fn()
}

var newObj = createObject2(obj)

// { name: 'alex', age: 18 }
console.log(newObj.__proto__)
Copy the code

This implements an inheritance effect

// In the latest ECMA specification, Object has a default method create, which has the same effect as our own createObject
var obj = {
  name: 'alex'.age: 18,}var newObj = Object.create(obj)
// { name: 'alex', age: 18 }
console.log(newObj.__proto__)
Copy the code

6.7 Parasitic inheritance

Parasitic inheritance is an inheritance idea closely related to primary inheritance, which was also proposed and promoted by Douglas

Parasitic inheritance is a way to combine the factory pattern with primitive inheritance

Create a function that encapsulates the inheritance process, enhances the object internally in some way, and returns the object

var person = {
  name: ' '.running: function() {
    console.log('running')}},function createStudent(name) {
  var stu = Object.create(person)
  stu.name = name
  stu.studying = function() {
    console.log('studying')}return stu
}

var s1 = createStudent('alex')
var s2 = createStudent('zhangsan')
// Implement inheritance
console.log(s1.name, s2.name)
Copy the code

Of course, there are downsides to this approach:

  • This approach only works on objects
  • Every object has a public methodstudyingCreating multiple objects takes up multiple memory Spaces, but the functions in each memory space are the same

6.8 Parasitic combinatorial inheritance

Looking back at all the inheritance approaches we envisioned, using constructors to implement inheritance seems to be the ideal approach

function Person(name, age, friends) {
  this.name = name
  this.age = age
  this.friends = friends
}

Person.prototype.running = function() {
  console.log(this.name, ' running')}function Student(name, age, friends, sno) {
  Person.call(this, name, age, friends)
  this.sno = sno
}

Student.prototype = new Person()

Object.defineProperty(Student.prototype, 'constructor', {
  enumerable: false.configurabl: true.writable: true.value: Student,
})

Student.prototype.studying = function() {
  console.log(this.name, ' studying')}var s1 = new Student('alex'.18['john'].123)
s1.running()
Copy the code

In order to have a perfect inheritance scheme, we need to solve two disadvantages of borrowing constructors (also known as combinatorial inheritance) :

  • The constructor is called multiple times
  • Student’s prototype object has redundant attributes

In fact, we can overcome both of these drawbacks by parasitic combinatorial inheritance:

Student.prototype = new Person()
// the line 👆 is changed to 👇, which directly solves two drawbacks
Student.prototype = Object.create(Person.prototype)
Copy the code

Next, we can optimize, and we found that the core code for inheritance is these two

Student.prototype = Object.create(Person.prototype)

Object.defineProperty(Student.prototype, 'constructor', {
  enumerable: false.configurabl: true.writable: true.value: Student,
})
Copy the code

We can actually encapsulate it as a function

// inherit: inherit
function inheritPrototype(obj, proto) {
  obj.prototype = Object.create(proto.prototype)
  Object.defineProperty(obj.prototype, 'constructor', {
    enumerable: false.configurabl: true.writable: true.value: obj,
  })
}

inheritPrototype(Student, Person)
Copy the code

The core code

The object.create () function is relatively new, and many of the community’s older code actually uses the createObject() method we described above. Here is the core code for parasitic combinatorial inheritance

function createObject(proto) {
  function Fn() {}
  Fn.prototype = proto
  return new Fn()
}

function inheritPrototype(obj, proto) {
  obj.prototype = createObject(proto.prototype)
  Object.defineProperty(obj.prototype, 'constructor', {
    enumerable: false.configurable: true.writable: true.value: obj,
  })
}
Copy the code

Test the

function Person(name) {
  this.name = name
}

Person.prototype.greeting = function() {
  console.log(this.name, 'hello world')}function Student(name, age) {
  Person.call(this, name)
  this.age = age
}

inheritPrototype(Student, Person)

Student.prototype.studying = function() {
  console.log(this.name, this.age, 'studying')}var s1 = new Student('alex'.18)
var s2 = new Student('john'.20)

s1.greeting()  // The call succeeded
s2.studying()  // The call succeeded
Copy the code

7. Prototype supplement

Object. Create (obj, propDescriptors), creates an Object. The prototype is the first parameter passed in

var obj = {
  name: 'alex',}// The second parameter attribute descriptor is appended to the returned object
var newObj = Object.create(obj, {
  age: {
    value: 20.enumerable: true,}})// { age: 20 } { name: 'alex' }
console.log(newObj, newObj.__proto__)
Copy the code

Obj. hasOwnProperty(propName) to determine if a property is mounted on itself (not on the prototype)

console.log(newObj.hasOwnProperty('age')) // true
console.log(newObj.hasOwnProperty('name')) // false
Copy the code

The in operator to determine whether an object has access to a property (whether in a stereotype or not)

console.log(age in newObj) // true
console.log(name in newObj) // true
Copy the code

The instanceof operator checks whether the constructor’s prototype appears on the prototype chain of an instance object

function foo1() {
  this.mname = 'alex'
}

function foo2() {
  this.name = 'zhangsan'
}

function foo3() {
  this.age = 20
}

foo3.prototype = Object.create(foo1.prototype)

var f1 = new foo3()

console.log(f1 instanceof foo1) // true
console.log(f1 instanceof Object) // true
console.log(f1 instanceof foo2) // false

// The top-level constructor inherits from Object by default
console.log(foo1.prototype.__proto__)  [Object: null prototype] {}
Copy the code

Obj. IsPrototypeOf (obj), which checks if an object exists on the prototype chain of an instance object

var obj = {
  name: 'alex',}var newObj = Object.create(obj)

console.log(obj.isPrototypeOf(newObj))  // true
Copy the code

Instanceof and isPrototypeOf are used in different scenarios:

  • instanceofThe right-hand side of phi must be a function
  • isPrototypeYou want an object

8. Archetypal inheritance

The image above is one of the community’s most famous archetypal inheritance images, which we’ll take a closer look at below:

// Let's start with this code
function foo() {}

var f1 = new foo()

var o1 = new Object(a)Copy the code

As we said above, in the JS world, everything is born from the Object prototype, and all objects (functions are also objects) are cloned from the Object prototype. Object has a null prototype.

function foo() {}
Copy the code

We created a Function called foo that comes from new Function(), so foo.__proto__ = function.prototype. Function is the built-in JS constructor. Function.prototype.__proto__ points to Object.prototype

var f1 = new foo()
Copy the code

When we use the new keyword, the foo function becomes a constructor.

  • First, an empty object is created
  • Point this inside the constructor to the empty object
  • Put this empty object’s__proto__Object pointing to the constructorprototype
  • Execute the constructor body
  • Returns a new object.

At this point, the first two rows actually look like this:

At this point, we’re done with the top half and the bottom half of the top picture, and we’re going to move on to the middle part

var o1 = new Object(a)Copy the code

Using new Object() to create an Object illustrates at least two facts:

  • ObjectIs a constructor
  • Objectbynew Function()And to

The __proto__ of the Object constructor points to function. prototype

The memory space for this row looks like this

Now, when you look back at the picture, is it much clearer?

conclusion

In this article, you learned three things:

1. About object orientation

  • Know what object orientation is, and object orientation in JS

  • See two ways to create objects, using Object literals {} that are actually created using new Object()

  • Understand the concept and operation of object attribute descriptor

  • You learned about several default methods for Object. Prototype mounting

2. About prototypes

  • Object’s Prototype is implicit, and the [[Prototype]] part of the browser implements it__proto__Properties (This property is used with caution and has compatibility issues)
  • The Prototype of the function is an explicit Prototype Prototype property
  • Understand the process of new
  • Understand the prototype chain, and define the prototypeObject.prototype.__proto__ = null

3. On inheritance

  • Several inheritance modes are discussed and the defects of each inheritance mode are found
  • Combined with a variety of inheritance, write a more perfect inheritance schemeParasitic combinatorial inheritance
  • In addition to stereotypes, you learned about several methods and operators for determining stereotypes
  • Finally, finish with a classic prototype diagram to thoroughly grasp the prototype and inheritance