Understanding object

MDN: Object-oriented programming is a programming pattern that creates real-world models in an abstract manner. It uses previously established paradigms, including modularity, polymorphism, and encapsulation techniques.

Any object is unique, regardless of its own state

We describe objects in terms of states

The change in state is the behavior

Three characteristics of object orientation

  • Encapsulation – Encapsulation, reuse, decoupling, cohesion (Describe architecture)
  • Inheritance — Class Base object-oriented
  • Polymorphism – describes the degree of dynamism

Class vs Prototype

Class (Class)

  • Classes are a common way of describing objects
  • There are two main schools of categorization and categorization
  • For classification, inherit C++
  • A computer language that uses the idea of classification, single inheritance. And there will be a base class Object

Prototype

  • Archetypes are a way of describing objects that is closer to human primal cognition
  • We are not trying to categorize things rigorously, but rather to describe them in terms of similarity
  • Any object merely needs to describe itself as different from the prototype

JavaScript object

Ecma-262 defines an object as an unordered collection of attributes

Each property or method of an object is identified by a name that maps to a value

You can think of an ECMAScript object as a hash table, where the contents are a set of name/value pairs that can be data or functions

Attribute types

Ecma-262 uses a number of internal features to describe the characteristics of attributes. These features are defined by the specification that implements the engine for JavaScript. Therefore, developers cannot access these features directly in JavaScript

There are two types of attributes: data attributes and accessor attributes

Data attributes

Data properties contain a location to hold data values. Values are read from and written to this location.

Data attributes have four properties that describe their behavior

  • [[Configurable]]: indicates whether the attribute can passdeleteDelete and redefine, whether its properties can be modified, and whether it can be changed to accessor properties. By default, this property is for all properties directly defined on an objecttrue
  • [[Enumerable]]: indicates whether the attribute can passfor-inLoop back. By default, this property is for all properties directly defined on an objecttrue
  • [[Writable]]: indicates whether the value of an attribute can be modified. By default, this property is for all properties directly defined on an objecttrue
  • [[value]]: contains the actual value of the attribute
let person = {
  name: 'andy'
}
Copy the code

The [[Enumerable]], [[Writable]] and [[Writable] feature are all set to true, and the [[Value]] feature is set to the specified Value

Here, we create an object with a property named name and assign the value ‘Andy’. This means that the [[value]] feature will be set to ‘Andy’, and any subsequent changes to this value will be saved there

If you want to modify the default properties of properties, you must use the Object.defineProperty(obj, prop, Descriptor) method

This method takes three arguments

  • obj: The object for which attributes are to be defined
  • prop: The name or of the property to be defined or modifiedSymbol
  • descriptor: Property descriptor to define or modify
let person = {}
Object.defineProperty(person, 'name', {
  writable: false.configurable: false.enumerable: false.value: 'andy'
})
Copy the code

Note:

  • When settingwritablefalseThe value of this attribute cannot be modified. Attempts to modify this property are ignored in non-strict mode, and errors are thrown in strict mode
  • When settingconfigurablefalse, the property cannot be removed from the object. When trying to call this propertydeleteDelete, which is ignored in non-strict mode and throws an error in strict mode.
  • Once a property is defined as unconfigurable, it cannot be changed back to configurable and called againObject.defineProperty()And modify any nonwritableAttributes cause errors
  • In the callObject.defineProperty()When,configurable,enumerablewritableIf not specified, the default value isfalse

Accessor properties

Contains a getter function and a setter function, but these are not required.

When an accessor property is read, the getter function is called, whose responsibility is to return a valid value.

When the accessor property is written, the setters are called and the new values are passed in, and this function must decide what changes to make to the data.

Accessor properties have four properties that describe their behavior

  • [[Configurable]]: indicates whether the attribute can passdeleteDelete and redefine, whether its properties can be modified, and whether it can be changed to accessor properties. By default, this property is for all properties directly defined on an objecttrue
  • [[Enumerable]]: indicates whether the attribute can passfor-inLoop back. By default, this property is for all properties directly defined on an objecttrue
  • [[Get]]: Gets the function, called when the property is read. The default value isundefined
  • [[Set]]: Sets the function, called when the property is read. The default value isundefined

Accessor properties cannot be defined directly; object.defineProperty () must be used

let perosn = {
  _age: 18
}

Object.defineProperty(person, 'age', {
  get() {
    return this._age
  }
  set(newValue) {
  	if(newValue > 18) {
      this._age = newValue
    }
	}
})
Copy the code

Defining multiple properties

ECMAScript provides the object.defineProperties (obj, props) method to define multiple properties at once with multiple descriptors

Receives two parameters:

  • obj: The object whose properties are to be defined or modified
  • props: The object whose enumerable properties or modified property descriptors are to be defined

Read properties of properties

ECMAScript provides Object. GetOwnPropertyDescriptor (obj, prop) method, you can set the attributes of the descriptor

Receives two parameters:

  • obj: Indicates the target object to be searched
  • prop: Name of an attribute in the target object

The return value:

Returns the property descriptor object if the specified property exists on the object, otherwise undefined

ECMAScript 2017 added Object. GetOwnPropertyDescriptors (obj) method, is used to capture all its attributes of an Object descriptor

Receives a parameter:

  • obj: arbitrary object

The return value:

Descriptor for all of the specified object’s own properties, or returns an empty object if there are none

This method will actually call Object on each has its own properties. The getOwnPropertyDescriptor () and return them in a new Object

Some extensions to Object

Object.assign

ECMAScript6 provides object. assign(target,… Sources) method that allocates the values of all enumerable properties from one or more source objects to the target object.

Parameters:

  • target: Target object
  • sources: the source object

The return value:

The target object

Note:

If an attribute in the target object has the same key, the attribute is overwritten by an attribute in the source object; Properties of subsequent source objects similarly override properties of previous source objects

Object. The assign method will only copy the source Object has its own (Object. The hasOwnProperty returns true), and can be enumerated (Object) propertyIsEnumbeable returns true) attributes to the target Object

Properties of type Stirng and Symbol are copied

This method uses [[Get]] on the source object to Get the value of the property, and then uses [[Set]] on the target object to Set the value of the property

Object.assign() actually performs a shallow copy of each source Object

If an error occurs during assignment, the operation terminates and exits, throwing an error; Object.assign() has no concept of “rolling back” previous assignments, so it is a method that does its best and may only do a partial copy

let dest = {}
let src = { a: 'foo' }
let result = Object.assign(dest, src)

console.log(dest === result) // true
console.log(dest ! == src)// true
console.log(result) // {a: "foo"}
Copy the code
Object.is

The object.is () method determines whether two values are the same

Object.is(+0.0) // true
Object.is(-0.0) // false
Object.is(NaN.NaN) // true
Copy the code
// Polyfill

if (!Object.is) {
  Object.is = function (x, y) {
    if (x === y) {
      returnx ! = =0 || 1 / x === 1 / y
    } else {
      returnx ! == x && y ! == y } } }Copy the code

Create an object

Objects can be easily created using Object constructors or Object literals, but there is an obvious downside: creating multiple objects with the same interface requires a lot of repetitive code

ES6 did not formally support object-oriented constructs, such as classes and inheritance. We can use archetypal inheritance to simulate the same behavior (not recommended)

ES6 starts to officially support classes and inheritance. The classes in ES6 are designed to fully cover the prototype-based inheritance pattern designed by the previous specification. For all intents and purposes, however, ES6 classes are just syntactic sugar that encapsulates ES5.1 constructors plus stereotype inheritance

The factory pattern

Used to abstract the process of creating a particular object

🌰 :

function createPerson(name, age) {
  let o = new Object()

  o.name = name
  o.age = age
  o.sayName = function () {
    console.log(this.name)
  }

  return o
}

let person = createPerson('Andy'.18)
Copy the code

While the problem of creating multiple similar objects is solved, the problem of object identification (what type the newly created object is) is not solved

Constructor pattern

Constructors in ECMAScript are used to create objects of a specific type

🌰 :

function Person(name, age) {
  this.name = name
  this.age = age
  this.sayName = function () {
    console.log(this.name)
  }
}

let person = new Person('Andy'.18)
Copy the code

In this example, the Person() constructor replaces the createPerson() factory function. In fact, the internal code is basically the same, with the following differences:

  • Create objects without showing them
  • Properties and methods are directly assigned tothis
  • There is noreturn

To create an instance of Person, use the new operator. Calling the constructor in this way does the following:

  1. Create a new object in memory
  2. Inside this new object[[Prototype]]Property assigned to the constructorprototypeattribute
  3. Inside the constructorthisIs assigned to this new object (i.ethisPoint to new object)
  4. Code that points inside the constructor (adding attributes to a new object)
  5. If the constructor returns a non-empty object, that object is returned; Otherwise, the newly created object is returned

Constructors do not have to be written as function declarations; A function expression assigned to a variable may also work:

let Person = function(name, age) {
  // ...
}
Copy the code

When instantiating, if no arguments are passed, the parenthesis after the constructor is omitted. Whenever we have the new operator, we can call the corresponding constructor:

function Person() {
	this.name = 'Andy'
  this.sayName = function() {
    console.log(this.name)
  }
}

let p1 = new Person()
let p2 = new Person
Copy the code

1. Constructors are functions

The only difference between a constructor and a normal function is how it is called

Any function that uses only the constructor called with the new operator is a normal function

2. Constructor problems

The methods defined are created on each instance

In the example above, p1 and p2 both have methods named sayName(), but they are not the same Function instance

Functions in ECMAScript are objects, so each time a function is defined, an object is initialized

function Person() {
  this.name = 'Andy'
  this.sayName = new Function('console.log(this.name)') // Logical equivalence
}
Copy the code
console.log(p1.sayName == p2.sayName) // false
Copy the code

There is no need to define two different instances of Function to do the same thing. And this can defer the binding of functions to objects until runtime

To solve this problem, move the function definition outside the constructor:

function Person() {
  this.name = 'Andy'
  this.sayName = sayName
}

function sayName() {
  console.log(this.name)
}
Copy the code

Thus, P1 and P2 share the sayName() function defined on the global scope. The problem of function duplication with the same logic is solved, but the global scope is also messed up. If you need more than one method, you define more than one function in the global scope. Can result in code that doesn’t cluster well for custom type references. This problem can be solved by prototyping patterns

The prototype pattern

Each function creates a Prototype property, which is an object containing properties and methods that should be shared by a particular referenced instance

In effect, this object is a prototype of the object created by calling the constructor

function Person() { }

Person.prototype.name = 'Andy'
Person.prototype.sayName = function () {
  console.log(this.name)
}

let p1 = new Person
p1.sayName() // "Andy"

let p2 = new Person()
p2.sayName() // "Andy"

console.log(p1.sayName === p2.sayName) // true
Copy the code

1. Understand the prototype

Whenever a function is created, a Prototype attribute (pointing to the prototype object) is created for that function according to certain rules

By default, all stereotype objects automatically get a property called constructor that refers back to the constructor associated with them

You can add additional properties and methods to the prototype object

Person.prototype.constructor === Person // true
Copy the code

When you customize a constructor, the stereotype Object acquires only the constructor attribute by default; all other methods inherit from Object

Each time the constructor is called to create a new instance, the inner [[prototype]] pointer of that instance is assigned to the constructor’s prototype object

There is no standard way to access the [[Prototype]] feature in scripts; Firfox, Safari, and Chrome expose the __proto__ property on each object, which allows access to the object’s prototype

Key point: There is a direct connection between the instance and the constructor stereotype, but not between the instance and the constructor

Function Person() {} * let Person = function() {} */
function Person() {}/** * after the declaration, the constructor has a prototype object associated with it */
console.log(typeof Person.prototype)
console.log(Person.prototype)
/ / {
// constructor: f Person(),
// __proto__: Object
// }

The /** * constructor has a prototype property that references its prototype object. The prototype object also has a constructor property that references the constructor
console.log(Person.prototype.constructor === Person) // true

/** * Normal prototype chain will terminate at the prototype of Object * Object prototype is null */
console.log(Person.prototype.__proto__ === Object.prototype) // true
console.log(Person.prototype.__proto__.constructor === Object) // true
console.log(Person.prototype.__proto__.__proto__ === null) // true

let person1 = new Person
let person2 = new Person()

/** * instances are linked to Prototype objects via __proto__, which actually points to the hidden feature [[Prototype]] ** constructors are linked to Prototype objects via Prototype ** instances are not directly related to constructors, but to Prototype objects */
console.log(person1.__proto__ === Person.prototype) // true
console.log(person1.__proto__.constructor === Person) // true

/** * An instance created by the same constructor, sharing the same prototype object */
console.log(person1.__proto__ === person2.__proto__) // true

/** * instanceof checks if the instance's prototype chain contains the specified constructor's prototype */
console.log(person1 instanceof Person) // true
console.log(person1 instanceof Object) // true
console.log(Person.prototype instanceof Object) // true
Copy the code

While not all implementations expose [[Prototype]], you can use the isPrototypeOf() method to determine this relationship between two objects. In essence, isPrototype() compares whether the [[Prototype]] of the passed argument points to the object on which it was called

Person.prototype.isPrototypeOf(person1) // true
Person.prototype.isPrototypeOf(person2) // true
Copy the code

The ECMAScript Object type has a method object.getProtoTypeof () that returns the parameter’s internal property [[Prototype]] value

Object.getPrototypeOf(person1) === Person.prototype // true
Copy the code

The Object type also has a setPrototypeOf() method that writes a new value to the instance’s private property [[Prototype]]. This overrides the stereotype inheritance of an object:

let biped = {
  numLegs: 2
}

let person = {
  name: 'Matt'
}

Object.setPrototypeOf(person, biped)

console.log(person.numLegs) / / 2
console.log(Object.getPrototypeOf(person) === biped) // true
Copy the code

Note: Object.setPrototypeof () can seriously affect code performance. The Mozilla documentation states that:

In all browsers and JavaScript engines, the effects of changing inheritance relationships are subtle and profound. This effect goes beyond simply executing object.setPrototypeof () statements, but involves all code that accesses any [[Prototype]] modified objects.

To avoid the potential performance degradation caused by using Object.setPrototypeof (), you can create a new Object with the specified prototype by using Object.create() :

let biped = {
  numLegs: 2
}

let person = Object.create(biped)

console.log(person.numLegs) / / 2
console.log(Object.getPrototypeOf(person) === biped) // true
Copy the code

2. Prototype level

When an instance property is read, the property is first searched on the instance; If it is not found, the prototype of the search instance is inherited

Instances can read values on prototype objects

Instances cannot override these values

If you add a property on the instance with the same name as the property on the prototype object, the property will be created on the instance, which will shadow the property on the prototype object

With the DELETE service, attributes on the instance can be completely removed, allowing the identifier resolution process to continue searching the prototype object

function Person() {}

Person.prototype.name = 'Nicholas'
Person.prototype.age = 18
Person.prototype.sayName = function() {
  console.log(this.name)
}

let person1 = new Person
let person2 = new Person

person1.name = 'Andy'
console.log(person1.name) // "Andy"
console.log(person2.name) // "Nicholas"

delete person1.name
console.log(person1.name) // "Nicholas"
Copy the code

The hasOwnProperty() method is used to determine whether a property is on an instance or a prototype object

This method inherits from Object and returns true if the property exists on the instance of the Object calling it

function Person() {}

Person.prototype.name = 'Nicholas'
Person.prototype.age = 18
Person.prototype.sayName = function() {
  console.log(this.name)
}

let person1 = new Person
let person2 = new Person
console.log(person1.hasOwnProperty('name')) // false

person1.name = 'Andy'
console.log(person1.name) // "Andy"
console.log(person1.hasOwnProperty('name')) // true

console.log(person2.name) // "Nicholas"
console.log(person2.hasOwnProperty('name')) // false

delete person1.name
console.log(person1.name) // "Nicholas"
console.log(person1.hasOwnProperty('name')) // false
Copy the code

3. Stereotypes and in operators

The in operator can be used in two ways: alone and through for-in loops

When used alone, the IN operator returns true when the specified property is accessible through the object, whether on the instance or the stereotype

function Person() {}

Person.prototype.name = 'Nicholas'
Person.prototype.age = 18
Person.prototype.sayName = function() {
  console.log(this.name)
}

let person1 = new Person
let person2 = new Person
console.log(person1.hasOwnProperty('name')) // false
console.log('name' in  person1) // true

person1.name = 'Andy'
console.log(person1.name) // "Andy"
console.log(person1.hasOwnProperty('name')) // true
console.log('name' in  person1) // true

console.log(person2.name) // "Nicholas"
console.log(person2.hasOwnProperty('name')) // false
console.log('name' in  person2) // true

delete person1.name
console.log(person1.name) // "Nicholas"
console.log(person1.hasOwnProperty('name')) // false
console.log('name' in  person1) // true
Copy the code

If you want to determine if a property exists on a stereotype, right?

function hasPrototypeProperty(obj, key) {
  return! obj.hasOwnProperty(key) && (keyin obj)
}
Copy the code

The in operator returns true whenever the object is accessible, whereas hasOwnProperty() returns true only if the property exists on the instance. Therefore, as long as the in operator returns true and hasOwnProperty() returns false, the property is a stereotype property

If you use the IN operator in a for-in loop, any property that can be accessed by an object and that can be enumerated ([[Enumerable]] is true) is returned, including instance and stereotype properties

You can use the object.keys () method to get all enumerable instance properties on an Object

Through Object. GetOwnPropertyNames () method to get all the instance attributes

function Person() {}

Person.prototype.name = 'Nicholas'
Person.prototype.age = 18
Person.prototype.sayName = function() {
  console.log(this.name)
}

let keys = Object.keys(Person.prototype)
console.log(keys) // ["name", "age", "sayName"]
let p1 = new Person
p1.name = "Andy"
p1.age = 20
let p1keys = Object.keys(p1)
console.log(p1keys) // ["name", "age"]

let allKeys = Object.getOwnPropertyNames(Person.prototype)
console.log(allKeys) // ["constructor", "name", "age", "sayName"]
Copy the code

After ECMAScript6 new Symbol (Symbol) type, accordingly increase the Object. The getOwnPropertySymbols () method

let k1 = Symbol('k1')
let k2 = Symbol('k2')

let o = {
  [k1]: 'k1',
  [k2]: 'k2',}console.log(Object.getOwnPropertySymbols(o)) // [Symbol(k1), Symbol(k2)]
Copy the code

4. Attribute enumeration order

The enumeration order of for-in and Object,keys() is indeterminate, depending on the JavaScript engine

Object. GetOwnPropertyNames (), Object. GetOwnPropertySymbols () and the Object, the assign () enumeration sequence is certain. Enumerates numeric keys in ascending order, and then enumerates strings and symbolic keys in insertion order

5. Prototype-related grammar

ECMAScript 2017 adds two static methods for converting object content to a serialized — and more importantly, iterable — format

Object.values() returns an array of Object values

Object.entries Return an array of key/value pairs

const obj = {
  foo: 'bar'.baz: 2.qux: {}}console.log(Object.values(obj)) // ["bar", 2, {}]
console.log(Object.entries(obj)) // [["foo", "bar"], ["baz", 2], ["qux", {}]]
Copy the code

Non-string attributes are converted to string output.

These two methods perform a shallow copy of the object

const obj = {
  qux: {}}console.log(Object.values(obj)[0] === obj.qux) // true
console.log(Object.entries(obj)[0] [1] === obj.qux) // true
Copy the code

Symbolic attributes are ignored

const k = Symbol(a)const obj = {
  [k]: 'foo'
}

console.log(Object.values(obj)) / / []
console.log(Object.entries(obj)) / / []
Copy the code

6. Prototype dynamics

Because the search for values from the stereotype is dynamic, any changes made to the stereotype object are reflected in the instance, even if the instance already exists before modifying the stereotype

The link between the instance and the stereotype is simply a pointer, not a saved copy

Rewriting the entire stereotype disconnects the original stereotype from the constructor, but the instance still references the original stereotype

The instance has only Pointers to the stereotype, not to the constructor

function Person() {}

let p = new Person()

Person.prototype = {
  constructor: Person,
  name: 'Andy'.age: 18.sayName() {
    console.log(this.name)
  }
}

p.sayName() // Uncaught TypeError: p.sayName is not a function
Copy the code

Instances created after overriding a stereotype on a constructor reference the new stereotype. Instances created before this point will still reference the original prototype

7. Native object prototypes

All constructors of native reference types (Object, Array, String, and so on) define instance methods on stereotypes

All default method references can be obtained from a prototype of a native object, and new methods can be defined for instances of a native type. Native object stereotypes can be modified in the same way as custom object stereotypes

Note: Although this can be done, it is not recommended to modify native object prototypes in a reproduction environment. This is likely to lead to misunderstandings and naming conflicts. It is also possible to accidentally override native methods. The recommended approach is to create a custom class that inherits the native type

8. Prototype problems

Weakening the ability to pass initialization parameters to constructors causes all instances to default to the same attribute values. While this can be inconvenient, it’s not the biggest problem with prototyping. The main problem with prototyping stems from its shared nature.

All attributes on the stereotype are shared between instances, which is appropriate for functions. The real problem comes from the attributes that contain the reference values

function Person() {}

Person.prototype = {
  constructor: Person,
  name: 'Andy'.age: 18.friends: ['Lyn'.'Mike'].sayName() {
    console.log(this.name)
  }
}

let person1 = new Person()
let person2 = new Person()

person1.friends.push('Tony')

console.log(person1.friends) // ["Lyn", "Mike", "Tony"]
console.log(person2.friends) // ["Lyn", "Mike", "Tony"]
console.log(person1.friends === person2.friends) // true
Copy the code

Handwriting implementation new

function newFunc(. args) {
  // Take the first argument of the args array, the target constructor
  const constructor = args.shift() // Create an empty object and make it inherit from the constructorprototypeAttributes / /obj.__proto__= = =constructor.prototype
  const obj = Object.create(constructor.prototype// The constructor returns the resultapplyMake the inside of the constructorthisPoint to theobj
  const result = constructor.apply(obj, args) // If the result is a non-empty object, it is returned directly, otherwise it is returnedobj
  return (typeof result === 'object'&& result ! = =null)?result : obj
}
Copy the code

inheritance

Prototype chain

Ecma-262 defines the stereotype chain as the main inheritance of ECMAScript. The basic idea is to inherit properties and methods of multiple reference types through stereotypes

Constructor, prototype, and instance relationships:

  • Each constructor has a stereotype object, and the stereotype has a property that points back to the constructor

  • The instance has an internal pointer to the prototype

What if the stereotype is an instance of another type?

That means that the stereotype itself has an internal pointer to another stereotype, which in turn has a pointer to another constructor. This creates a chain of stereotypes between the instance and the stereotype

🌰 :

function SuperType() {
  this.property = true
}

SuperType.prototype.getSuperValue = function() {
  return this.property
}

function SubType() {
  this.subproperty = false
}

/ / inherit the SuperType
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {
  return this.subproperty
}

let instance = new SubType()
console.log(instance.getSuperValue()) // true
Copy the code

Prototype chains extend the prototype search mechanism. We know that when we read a property on an instance, we first search for that property on the instance; If not, the prototype of the search instance is inherited; After inheritance is implemented through the prototype chain, the search can then inherit up, searching the prototype of the prototype. In this case, the call to instance.getsuperValue () goes through a three-step search: instance, subtype.prototype, and supertype.prototype before finding the method in the last step

1. Default prototype

By default, all reference types are inherited from Object, which is also implemented through the stereotype chain.

The default prototype of any function is an instance of Object, which means that the instance has an internal pointer to Object.prototype

2. Relationship between prototype and instance

The relationship between prototypes and instances can be determined in two ways

  • instanceofOperator: if the corresponding constructor appears in the stereotype chain of an instanceinstanceofreturntrue
instance instanceof Object // true
instance instanceof SuperType // true
instance instanceof SubType // true
Copy the code
  • isPrototypeOf()Method: This method can be called by each stereotype in the stereotype chain, and returns if the stereotype is included in the stereotype chaintrue
Object.prototype.isPrototypeOf(instance) // true
SuperType.prototype.isPrototypeOf(instance) // true
SubType.prototype.isPrototypeOf(instance) // true
Copy the code
  • Creating a prototype as an object literal breaks the previous prototype chain because it overwrites it
function SuperType() {
  this.property = true
}

SuperType.prototype.getSuperValue = function() {
  return this.property
}

function SubType() {
  this.subproperty = false
}

/ / inherit the SuperType
SubType.prototype = new SuperType()

SubType.prototype.getSubValue = function () {
  return this.subproperty
}

SubType.prototype = {
  getSubValue() {
    return this.subproperty;
  },

  someOtherMethod() {
    return false; }}let instance = new SubType()
console.log(instance.getSuperValue()) // Uncaught TypeError: instance.getSuperValue is not a function
Copy the code

3. Prototype chain problems

  • The main problem occurs when a stereotype contains reference values that are shared between all instances
function SuperType() {
  this.colors = ['red'.'green'.'blue']}function SubType() {}

SubType.prototype = new SuperType()

let instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ["red", "green", "blue", "black"]

let instance2 = new SubType()
console.log(instance2.colors) // ["red", "green", "blue", "black"]
Copy the code
  • A subtype cannot take arguments to the parent type’s constructor when instantiated

Classical inheritance (embeded constructors)

Call the superclass constructor in the subclass constructor

Functions are simple objects that execute code in a particular context, so you can use the apply() and call() methods to execute constructors in the newly created object context

function SuperType() {
  this.colors = ['red'.'green'.'blue']}function SubType() {
  / / inherit the SuperType
  SuperType.call(this)}let instance1 = new SubType()
instance1.colors.push('black')
console.log(instance1.colors) // ["red", "green", "blue", "black"]

let instance2 = new SubType()
console.log(instance2.colors) // ["red", "green", "blue"]
Copy the code

1. Pass parameters

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

function SubType() {
  SuperType.call(this.'Andy')
  this.age = 18
}

let instance = new SubType
console.log(instance.name) // "Andy"
console.log(instance.age) / / 18
Copy the code

2. The problem of classical inheritance

  • Methods must be defined in constructors, so functions cannot be reused
  • Subclasses do not have access to methods defined on superclass prototypes, so all types can only use the constructor pattern

Combination of inheritance

A combination of stereotype chain and classical inheritance: use stereotype chain to inherit properties and methods on the stereotype, and use stolen constructors to inherit instance properties.

This allows methods to be defined on prototypes for reuse and allows each instance to have its own attributes.

function SuperType(name) {
  this.name = name,
  this.colors = ['red'.'green'.'blue']
}

SuperType.prototype.sayName = function() {
  console.log(this.name)
}

function SubType(name, age) {
  // Inherit attributes
  SuperType.call(this, name)
  this.age = age
}

// Inheritance method
SubType.prototype = new SuperType()

SubType.prototype.sayAge = function() {
  console.log(this.age)
}

let instance1 = new SubType('Nicholas'.20)
instance1.colors.push('black')
console.log(instance1.colors) // ["red", "green", "blue", "black"]
instance1.sayName() // Nicholas
instance1.sayAge() / / 20

let instance2 = new SubType('Andy'.18)
console.log(instance2.colors) // ["red", "green", "blue"]
instance2.sayName() // Andy
instance2.sayAge() / / 18
Copy the code

Composite inheritance makes up for the deficiency of prototype chain and classical inheritance and is the most used inheritance pattern in JavaScript. Composite inheritance also preserves the ability of the Instanceof operator and isPrototypeOf() methods to recognize composite objects

Primary inheritance

function object(o) {
  function F() {}
  F.prototype = o
  return new F()
}
Copy the code

The object method essentially makes a shallow copy of the incoming object

let person = {
  name: 'Nicholas'.friends: ['Shelby'.'Court'.'Van']}let person1 = object(person)
person1.name = 'Greg'
person1.friends.push('Rob')

let person2 = object(person)
person2.name = 'Linda'
person2.friends.push('Barbie')

console.log(person.friends) // ["Shelby", "Court", "Van", "Rob", "Barbie"]
Copy the code

ECMAScript 5 normalizes the concept of type-inheritance by adding the object.create () method

let person = {
  name: 'Nicholas'.friends: ['Shelby'.'Court'.'Van']}let person1 = Object.create(person, {
  name: {
    value: 'Greg'}})console.log(person1.name) // Greg
Copy the code

Note: Reference values contained in attributes are always shared between related objects, just as with the stereotype pattern

Parasitic inheritance

The idea behind this is similar to the parasitic constructor and factory patterns: Create a function that implements inheritance, enhances the object in some way, and then returns the object.

function createAnother(original) {
  // let clone = object(original)
  let clone = Object.create(original)
  clone.sayHi = function() {
    console.log('hi')}return clone
}
Copy the code

The object() function is not required for parasitic inheritance, and any function that returns a new object can be used here

Note: Adding functions to objects through parasitic inheritance can make them difficult to reuse

Parasitic combinatorial inheritance

Combinatorial inheritance has efficiency problems: the superclass constructor is always called twice; One is called when a subclass stereotype is created and the other is called in a subtype constructor.

function SuperType(name) {
  this.name = name
  this.colors = ['red'.'green'.'blue']
}

SuperType.prototype.sayName = function() {
  console.log(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name) // Call SuperType() for the second time

  this.age = age
}

SubType.prototype = new SuperType() // Call SuperType() for the first time
SubType.prototype.constructor = SubType
SubType.prototype.sayAge = function() {
  console.log(this.age)
}
Copy the code

Parasitic combinatorial inheritance inherits attributes by stealing constructors and using hybrid prototype chain inheritance

function inheritPrototype(subType, superType) {
  let prototype = Object.create(superType.prototype)
  prototype.constructor = subType
  subType.prototype = prototype
}
Copy the code

The inheritPrototype() function implements the core logic of parasitic inheritance.

This function takes two arguments: a subclass constructor and a superclass constructor;

The first step is to create a copy of the superclass prototype

Set the constructor property for the returned Prototype object to fix the default constructor loss caused by rewriting the prototype

Finally, the newly created object is assigned to the prototype of the subtype

Change the above combination inheritance:

function SuperType(name) {
  this.name = name
  this.colors = ['red'.'green'.'blue']
}

SuperType.prototype.sayName = function() {
  console.log(this.name)
}

function SubType(name, age) {
  SuperType.call(this, name)

  this.age = age
}

inheritPrototype(SubType, SuperType)

SubType.prototype.sayAge = function() {
  console.log(this.age)
}
Copy the code

class

Using ECMAScript 5 features to simulate class-like behavior has its own problems

ECMAScript 6 introduces the class keyword with the ability to formally define classes

Classes are the new foundational syntactic sugar in ECMAScript that appears to support formal class inheritance, but the concepts of stereotypes and constructors are still behind them

The class definition

Class declarations and class expressions

/ / the class declaration
class Animal {}

// Class expression
const Animal = class {}
Copy the code

Difference from function:

  • Class definitions cannot be promoted
  • Functions are scoped by functions and classes are scoped by blocks

The composition of the class

  • Constructor method
  • Instance methods
  • Access to functions
  • Set the function
  • Static class method

None of this is necessary; The code in the class definition is executed in strict mode

// Empty class definition
class Foo {}

// There are constructors
class Bar {
  constructor(){}}// There are fetch functions
class Baz {
  get myBaz() {}}// There are static methods
class Qux {
  static myQux(){}}Copy the code

The name of the class expression is optional

The name string of the class expression can be obtained through the name attribute

This identifier cannot be accessed outside the scope of a class expression

let Person = class PersonName {
  identify() {
    console.log(Person.name, PersonName.name)
  }
}

let p = new Person()

p.identify() // PersonName PersonName

console.log(Person.name) // PersonName
console.log(PersonName) // Uncaught ReferenceError: PersonName is not defined
Copy the code

Class constructor

The constructor keyword represents the class’s constructor; Tells the interpreter to call this function when using the new operator to create a new instance of a class

1. The instantiation

Calling the class constructor with new does the following:

  1. Create a new object in memory
  2. Inside this new object[[Prototype]]Property assigned to the constructorprototypeattribute
  3. Inside the constructorthisIs assigned to this new object (i.ethisPoint to new object)
  4. Execute the code inside the constructor (add attributes to the new object)
  5. If the constructor returns a non-empty object, that object is returned; otherwise, a new object created previously is returned

Difference with constructors:

Class constructors must be called using the new operator; A normal constructor that does not call new takes the global this as its internal object; Calling a class constructor without using new throws an error

Instances, stereotypes, and class members

1. Instance member

A “own” property added to the newly created instance (this)

Each instance corresponds to a unique member object

2. Prototype methods and accessors

A method defined in a class block

class Person {
  constructor() {
    // Everything added to this will exist on different instances
    this.locate = () = > console.log('instance')}locate() {
    // Defined on the prototype object of the class
    console.log('prototype')}}Copy the code

Methods can be defined in class constructors or in class blocks, not primitive values or objects

Class definitions support getting and setting accessors

class Person {
  set name(newName) {
    this._name = newName
  }

  get name() {
    return this._name
  }
}
Copy the code

Static class methods

Use the static keyword

Static members can only have one per class

In static members, this refers to the class itself

class Person {
  constructor() {
    // Everything added to this will exist on different instances
    this.locate = () = > console.log('instance')}locate() {
    // Defined on the prototype object of the class
    console.log('prototype')}// Defined on the class itself
  static locate() {
    console.log('class'.this)}}Copy the code

Static class methods are great for instance factories:

class Person {
  constructor(name) {
    this._name = name
  }

  sayName() {
    console.log(this._name)
  }

  static create() {
    return new Person('Andy')}}console.log(Person.create())
Copy the code

4. Non-function archetypes and class members

Class definitions do not support adding member data to stereotypes or classes, but you can do so manually outside the class definition

The class definition does not show support for adding data members because it is an anti-pattern to add mutable (modifiable) data members to shared targets (stereotypes and classes). In general, an object instance should own the data referenced through this alone.

5. Iterator and generator methods

The class definition syntax supports defining generator methods on both prototypes and classes themselves

inheritance

1. Base inheritance

Using the extends keyword, you can inherit any object that has [[Construct]] and a stereotype

class Vehicle {
  identifyPrototype(id) {
    console.log(id, this)}static identifyClass(id) {
    console.log(id, this)}}class Bus extends Vehicle {}

let v = new Vehicle()
let b = new Bus()

b.identifyPrototype('bus') // bus Bus {}
v.identifyPrototype('vehicle') // vehicle Vehicle {}

Bus.identifyClass('bus') // bus class Bus extends Vehicle {}
Vehicle.identifyClass('vehicle') // vehicle class Vehicle {}
Copy the code

The extends keyword also supports class expressions let Bus = class extends Vehicle {}

2. Constructors, HomeObject, and super()

super()

Methods of derived classes can reference their archetypes through the super keyword.

Using super in the class constructor calls the superclass constructor

class Vehicle {
  constructor() {
    this.hasEngine = true}}class Bus extends Vehicle {
  constructor() {
    // Do not refer to this before calling super(), otherwise a ReferenceError will be raised

    super(a)// equivalent to super.constructor()

    console.log(this instanceof Vehicle) // true
    console.log(this) // Bus { hasEngine: true }}}new Bus()
Copy the code

Static methods defined on inherited classes can be called via super in static methods:

class Vehicle {
  static identify() {
    console.log('vehicle')}}class Bus extends Vehicle {
  static identify() {
    super.identify()
  }
}

Bus.identify() // vehicle
Copy the code

ES6 adds an internal property [[HomeObject]] to class constructors and static methods, which is a pointer to the object that defines the method. This pointer is automatically assigned and can only be accessed within the JavaScript engine. Super is always defined as a prototype for [[HomeObject]]

A few things to note when using super:

  • superCan only be used in constructors and static methods of derived classes
class Vehicle {
  constructor() {
    super(a)// Uncaught SyntaxError: 'super' keyword unexpected here}}Copy the code
  • Can’t be applied alonesuperThe keyword
class Vehicle {}

class Bus extends Vehicle {
  constructor() {
    console.log(super) // Uncaught SyntaxError: 'super' keyword unexpected here}}Copy the code
  • callsuperThe parent constructor is called and the returned instance is assigned tothis
class Vehicle {}

class Bus extends Vehicle {
  constructor() {
    super(a)console.log(this instanceof Vehicle)
  }
}

new Bus() // true
Copy the code
  • super()Behaves like calling a constructor. If you need to pass parameters to the parent constructor, you need to pass them manually
class Vehicle {
  constructor(licensePlate) {
    this.licensePlate = licensePlate
  }
}

class Bus extends Vehicle {
  constructor(licensePlate) {
    super(licensePlate)
  }
}

console.log(new Bus('A2333')) // Bus {licensePlate: "A2333"}
Copy the code
  • If the class constructor is not defined, it is called when the derived class is instantiatedsuper(), and all arguments passed to the derived class
class Vehicle {
  constructor(licensePlate) {
    this.licensePlate = licensePlate
  }
}

class Bus extends Vehicle {}

console.log(new Bus('A2333')) // Bus {licensePlate: "A2333"}
Copy the code
  • In a class constructor, cannot be called againsuper()Before quotingthis
class Vehicle {}

class Bus extends Vehicle {
  constructor() {
    console.log(this)}}new Bus() // Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
Copy the code
  • If the definition class constructor is displayed in a derived class, it must either be called in itsuper()Or you must return an object within it
class Vehicle {}

class Car extends Vehicle {}

class Bus extends Vehicle {
  constructor() {
    super()}}class Van extends Vehicle {
  constructor() {
    return{}}}console.log(new Car()) // Car {}
console.log(new Bus()) // Bus {}
console.log(new Van()) / / {}
Copy the code

Abstract base classes

ECMAScript does not specifically support the syntax for abstract base classes. Instantiation of abstract base classes can be prevented by detecting whether new.target (which saves classes or functions called by the new keyword) is an abstract base class at instantiation time:

class Vehicle {
  constructor() {
    console.log(new.target)
    if(new.target === Vehicle) {
      throw new Error('Vehicle cannot be directly instantiated')}}}class Bus extends Vehicle {}

new Bus() // class Bus extends Vehicle {}
new Vehicle() // class Vehicle()
// Uncaught Error: Vehicle cannot be directly instantiated
Copy the code