“Code Tailor “provides technology related information and a series of basic articles for front-end developers. Follow the wechat public account” Rookie of Xiaohe Mountain “to get the latest articles.

preface

Before we get started, we want to let you know that this article is a summary of the “objects, Classes, and Object-oriented programming” section of the JavaScript language. If you already know the following, you can skip this section and jump right into the exercises

  • The basic construction of an object
  • Object declaration and usage
  • class
  • Object structure assignment
  • inheritance
  • Packaging object

If you are a little bit forgotten about some parts, 👇🏻 is ready for you!

Summary to summarize

Ecma-262 defines an object as an unordered collection of attributes. Strictly speaking, this means that an object is a set of values in no particular order. Each property or method of an object is identified by a name that maps to a value. For this reason (and for other reasons not yet discussed), 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.

The basic construction of an object

The usual way to create a custom Object is to create a new instance of Object and then add properties and methods to it, as shown in the following example:

let person = new Object()
person.name = 'XHS-rookies'
person.age = 18
person.job = 'Software Engineer'
person.sayName = function () {
  console.log(this.name)
}
Copy the code

This example creates an object named Person with three attributes (name, age, and job) and a method (sayName()). The sayName() method displays the value of this.name, which is resolved to Person.name. Early JavaScript developers used this approach frequently to create new objects. Over the years, object literals became more popular. The previous example could have been written like this if it had used object literals:

let person = {
  name: 'XHS-rookies'.age: 18.job: 'Software Engineer'.sayName() {
    console.log(this.name)
  },
}
Copy the code

The Person object in this example is equivalent to the person object in the previous example, with the same properties and methods. These attributes have their own characteristics, and these characteristics determine how they behave in JavaScript.

Object declaration and usage

Looking at the past releases of the ECMAScript specification, each version seems to have unexpected features. ECMAScript 5.1 does not formally support object-oriented structures such as classes or inheritance. However, as we’ll see in the following sections, the same behavior can be successfully simulated by clever use of prototypical inheritance. ECMAScript 6 officially supports 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

The factory pattern is a well-known design pattern widely used in software engineering to abstract the process of creating specific objects. The following example shows one way to create an object based on a specific interface:

function createPerson(name, age, job) {
  let o = new Object()
  o.name = name
  o.age = age
  o.job = job
  o.sayName = function () {
    console.log(this.name)
  }
  return o
}
let person1 = createPerson('XHS-rookies'.18.'Software Engineer')
let person2 = createPerson('XHS-boos'.18.'Teacher')
Copy the code

Here, the function createPerson() takes three parameters from which an object containing The Person information is built. This function can be called multiple times with different arguments, each time returning an object containing three properties and one method. While this factory pattern solves the problem of creating multiple similar objects, it does not solve the problem of object identification (what type the newly created object is).

Constructor pattern

Constructors in ECMAScript are used to create objects of a specific type. Native constructors, such as Object and Array, can be used directly by the runtime in the execution environment. You can also customize constructors that define properties and methods for your own object types in the form of functions. For example, the previous example using the constructor pattern could be written as follows:

function Person(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
    console.log(this.name)
  }
}
let person1 = new Person('XHS-rookies'.18.'Software Engineer')
let person2 = new Person('XHS-boos'.18.'Teacher')
person1.sayName() // XHS-rookies
person2.sayName() // XHS-boos
Copy the code

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

  • No object is explicitly created.

  • Properties and methods are assigned directly to this.

  • There is no return.

Also, notice that the function name Person is capitalized. By convention, constructor names begin with a capital letter, and non-constructors begin with a lowercase letter. This is borrowed from object-oriented programming languages and helps distinguish between constructed and ordinary functions in ECMAScript. After all, ECMAScript constructors are functions that create objects.

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

Create a new object in memory.

(2) The [[Prototype]] property inside this new object is assigned to the constructor’s Prototype property.

(3) This inside the constructor is assigned to the new object (this refers to the new object).

Execute the code inside the constructor (add attributes to the new object).

(5) If the constructor returns a non-empty object, return that object; Otherwise, the newly created object is returned.

At the end of the previous example, person1 and person2 hold separate instances of Person. Both objects have a constructor attribute pointing to Person, as shown below:

console.log(person1.constructor == Person) // true
console.log(person2.constructor == Person) // true
Copy the code

Constructor is originally used to identify object types. However, the instanceof operator is generally considered a more reliable way to determine the type of an object. Each Object in the previous example is an instanceof Object, as well as an instanceof Person, as shown in the result of calling the instanceof operator below:

console.log(person1 instanceof Object) // true
console.log(person1 instanceof Person) // true
console.log(person2 instanceof Object) // true
console.log(person2 instanceof Person) // true
Copy the code

Defining custom constructors ensures that instances are identified as specific types, which is a big advantage over the factory pattern. In this example, person1 and person2 are also considered instances of Object because all custom objects inherit from Object (more on this later). Constructors need not be written as function declarations. Function expressions assigned to variables can also represent constructors:

let Person = function (name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = function () {
    console.log(this.name)
  }
}
let person1 = new Person('XHS-rookies'.18.'Software Engineer')
let person2 = new Person('XHS-boos'.18.'Teacher')
person1.sayName() // XHS-rookies
person2.sayName() // XHS-boos
console.log(person1 instanceof Object) // true
console.log(person1 instanceof Person) // true
console.log(person2 instanceof Object) // true
console.log(person2 instanceof Person) // true
Copy the code

When instantiating, if you do not want to pass parameters, the parentheses after the constructor are optional. Whenever we have the new operator, we can call the corresponding constructor:

function Person() {
  this.name = 'rookies'
  this.sayName = function () {
    console.log(this.name)
  }
}
let person1 = new Person()
let person2 = new Person()
person1.sayName() // rookies
person2.sayName() // rookies
console.log(person1 instanceof Object) // true
console.log(person1 instanceof Person) // true
console.log(person2 instanceof Object) // true
console.log(person2 instanceof Person) // true
Copy the code

1. Constructors are functions

The only difference between a constructor and a normal function is how it is called. In addition, constructors are functions. There is no special syntax for defining a function as a constructor. Any function called with the new operator is a constructor, and any function called without the new operator is a normal function. For example, the Person() defined in the previous example could be called like this:

// as a constructor
let person = new Person('XHS-rookies'.18.'Software Engineer')
person.sayName() // "XHS-rookies"
// called as a function
Person('XHS-boos'.18.'Teacher') // Add to the window object
window.sayName() // "XHS-boos"
// call in the scope of another object
let o = new Object()
Person.call(o, 'XHS-sunshineboy'.25.'Nurse')
o.sayName() // "XHS-sunshineboy"
Copy the code

This example begins by showing a typical constructor call that creates a new object using the new operator. Then there is the normal function invocation, which does not call Person() with the new operator and results in properties and methods being added to the window object. Remember here that in cases where a function is called without explicitly setting this (that is, no method is called as an object, or no call is made with call()/apply()), this always refers to the Global object (which in browsers is the Window object). So after the above call, there is a sayName() method on the window object that returns “Greg”. The last invocation shown is to call a function through call() (or apply()), specifying a specific object as a scope. The call here specifies object O as the this value inside Person(), so all attributes and sayName() methods are added to object O after executing the function code.

2. Constructor problems

Constructors, while useful, are not without their problems. The main problem with constructors is that they define methods that are created on each instance. So person1 and person2 are sayName() methods for the previous example, but they are not the same Function instance. As we know, functions in ECMAScript are objects, so each time a function is defined, an object is initialized. Logically, the constructor actually looks like this:

function Person(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = new Function('console.log(this.name)') // Logical equivalence
}
Copy the code

Understanding the constructor this way makes it clear that each Person instance will have its own Function instance to display the Name property. Of course, creating functions this way brings different scope chains and identifier resolution. But the mechanism for creating new Function instances is the same. Therefore, functions on different instances are not equal even though they have the same name, as follows:

console.log(person1.sayName == person2.sayName) // false
Copy the code

There is no need to define two different instances of Function because they both do the same thing. Furthermore, this can defer the binding of functions to objects until runtime. To solve this problem, move the function definition outside the constructor:

function Person(name, age, job) {
  this.name = name
  this.age = age
  this.job = job
  this.sayName = sayName
}
function sayName() {
  console.log(this.name)
}
let person1 = new Person('XHS-rookies'.18.'Software Engineer')
let person2 = new Person('XHS-boos'.18.'Teacher')
person1.sayName() // XHS-rookies
person2.sayName() // XHS-boos
Copy the code

Here, sayName() is defined outside the constructor. Inside the constructor, the sayName attribute is equal to the global sayName() function. Because this time the sayName attribute contains only a pointer to an external function, person1 and person2 share the sayName() function defined on the global scope. This solves the problem of repeating functions defined with the same logic, but it also messes up the global scope because that function can really only be called on one object. If the object requires multiple methods, define multiple functions in the global scope. This results in code that doesn’t cluster well for custom type references. This new 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 instances of a particular reference type. In effect, this object is a prototype of the object created by calling the constructor. The advantage of using a stereotype object is that the properties and methods defined on it can be shared by the object instance. Values that were originally assigned directly to object instances in constructors can be assigned directly to their prototypes, as follows:

function Person() {}
Person.prototype.name = 'XHS-rookies'
Person.prototype.age = 18
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
  console.log(this.name)
}
let person1 = new Person()
person1.sayName() // "XHS-rookies"
let person2 = new Person()
person2.sayName() // "XHS-rookies"
console.log(person1.sayName == person2.sayName) // true
Copy the code

Function expressions can also be used:

let Person = function () {}
Person.prototype.name = 'XHS-rookies'
Person.prototype.age = 18
Person.prototype.job = 'Software Engineer'
Person.prototype.sayName = function () {
  console.log(this.name)
}
let person1 = new Person()
person1.sayName() // "XHS-rookies"
let person2 = new Person()
person2.sayName() // "XHS-rookies"
console.log(person1.sayName == person2.sayName) // true
Copy the code

Here, all the attributes and the sayName() method are added directly to the Person prototype property; there is nothing in the constructor body. But after this definition, the new object created by calling the constructor still has the corresponding properties and methods. Unlike the constructor pattern, the properties and methods defined using this stereotype pattern are shared by all instances. Thus person1 and person2 access the same attributes and the same sayName() function. To understand this process, you must understand the nature of stereotypes in ECMAScript. (See Object Stereotypes for more on ECMAScript stereotypes.)

Other prototype syntax

Readers may have noticed that in the previous example, person. prototype was rewritten every time an attribute or method was defined. To reduce code redundancy and visually encapsulate prototype functionality, it is common practice to rewrite a prototype directly from an object literal containing all properties and methods, as shown in the following example:

function Person() {}
Person.prototype = {
  name: 'XHS-rookies'.age: 18.job: 'Software Engineer'.sayName() {
    console.log(this.name)
  },
}
Copy the code

In this example, Person.prototype is set to equal a new object created from an object literal. The end result is the same, except for one problem: after this rewriting, the Constructor property of Person.prototype no longer points to Person. When a function is created, its Prototype object is also created and its constructor property is automatically assigned. This overrides the default Prototype Object, so its constructor property also points to a completely different new Object (Object constructor) than the original constructor. While the instanceof operator can still reliably return values, we can no longer rely on the constructor attribute to identify types, as shown in the following example:

let friend = new Person()
console.log(friend instanceof Object) // true
console.log(friend instanceof Person) // true
console.log(friend.constructor == Person) // false
console.log(friend.constructor == Object) // true
Copy the code

Here, instanceof still returns true for both Object and Person. But the constructor property now equals Object instead of Person. If constructor’s value is important, you can set its value specifically when rewriting the prototype object as follows:

function Person() {}
Person.prototype = {
  constructor: Person,
  name: 'XHS-rookies'.age: 18.job: 'Software Engineer'.sayName() {
    console.log(this.name)
  },
}
Copy the code

This code purposely includes the constructor attribute and sets it to Person, ensuring that the attribute still contains the appropriate value. Note, however, that restoring the constructor property this way creates an attribute that [[Enumerable]] is true. The native constructor property is not enumerable by default. Therefore, if you are using an ECMAScript compliant JavaScript engine, you might instead define the constructor property using the object.defineProperty () method:

function Person() {}
Person.prototype = {
  name: 'XHS-rookies'.age: 18.job: 'Software Engineer'.sayName() {
    console.log(this.name)
  },
}
// Restore the constructor attribute
Object.defineProperty(Person.prototype, 'constructor', {
  enumerable: false.value: Person,
})
Copy the code

class

The previous sections provided an in-depth look at how to simulate class-like behavior using only ECMAScript 5 features. As you can see, each strategy has its own problems and compromises. Because of this, the code that implements inheritance can be very verbose and confusing.

To address these issues, the new class keyword introduced in ECMAScript 6 has the ability to formally define classes. Classes are the new basic syntactic sugar structure in ECMAScript, so you might not be comfortable with them at first. While the ECMAScript 6 class may appear to support formal object-oriented programming, the concepts of stereotypes and constructors are still behind it.

The class definition

Like function types, there are two main ways to define classes: class declarations and class expressions. Both methods use the class keyword in parentheses:

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

Like function expressions, class expressions cannot be referenced until they are evaluated. However, unlike function definitions, while function declarations can be promoted, class definitions cannot:

console.log(FunctionExpression) // undefined

var FunctionExpression = function () {}
console.log(FunctionExpression) // function() {}
console.log(FunctionDeclaration) // FunctionDeclaration() {}

function FunctionDeclaration() {}
console.log(FunctionDeclaration) // FunctionDeclaration() {}
console.log(ClassExpression) // undefined

var ClassExpression = class {}
console.log(ClassExpression) // class {}
console.log(ClassDeclaration) // ReferenceError: ClassDeclaration is not defined

class ClassDeclaration {}
console.log(ClassDeclaration) // class ClassDeclaration {}
Copy the code

Another difference with function declarations is that functions are scoped by functions, while classes are scoped by blocks:

{
  function FunctionDeclaration() {}
  class ClassDeclaration {}}console.log(FunctionDeclaration) // FunctionDeclaration() {}
console.log(ClassDeclaration) // ReferenceError: ClassDeclaration is not defined
Copy the code

The composition of the class

Classes can contain constructor methods, instance methods, get functions, set functions, and static class methods, but none of these are required. An empty class definition is still valid. By default, the code in a class definition is executed in strict mode.

As with function constructors, most programming styles recommend capitalizing the first letter of the class name to distinguish it from instance Foo created through it (e.g., class Foo {}) :

// Empty class definition, valid
class Foo {}
// class with constructor, valid
class Bar {
  constructor(){}}// The class that gets the function is valid
class Baz {
  get myBaz() {}}// Class with static methods, valid
class Qux {
  static myQux(){}}Copy the code

The name of the class expression is optional. After assigning a class expression to a variable, the name string of the class expression can be obtained through the name attribute. This identifier cannot be accessed outside the scope of the 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) // ReferenceError: PersonName is not defined
Copy the code

Class constructor

The constructor keyword is used to create class constructors within the class definition block. The method name constructor tells the interpreter to call this function when using the new operator to create a new instance of a class. The definition of a constructor is not required, and not defining a constructor is equivalent to defining a constructor as an empty function.

instantiation

Instantiating a Person with the new operator is equivalent to calling its constructor with new. The only appreciable difference is that the JavaScript interpreter knows that using new and classes means instantiation should be done using the constructor function. Calling the class constructor with new does the following.

Create a new object in memory.

(2) The [[Prototype]] pointer inside the new object is assigned to the constructor’s Prototype property.

(3) This inside the constructor is assigned to the new object (this refers to the new object).

Execute the code inside the constructor (add attributes to the new object).

(5) If the constructor returns a non-empty object, return that object; Otherwise, the newly created object is returned.

Consider the following example:

class Animal {}
class Person {
  constructor() {
    console.log('person ctor')}}class Vegetable {
  constructor() {
    this.color = 'orange'}}let a = new Animal()
let p = new Person() // person ctor
let v = new Vegetable()
console.log(v.color) // orange
Copy the code

The arguments passed in when the class is instantiated are used as arguments to the constructor. If no arguments are required, the parentheses following the class name are optional:

class Person {
  constructor(name) {
    console.log(arguments.length)
    this.name = name || null}}let p1 = new Person() / / 0
console.log(p1.name) // null
let p2 = new Person() / / 0
console.log(p2.name) // null
let p3 = new Person('Jake') / / 1
console.log(p3.name) // Jake
Copy the code

By default, the class constructor returns this object after execution. The object returned by the constructor is used as the instantiated object, and if there is no reference to the newly created this object, it is destroyed. However, if you return an object other than this, the object will not be associated with the class through the Instanceof operator because the object’s prototype pointer has not been modified.

class Person {
  constructor(override) {
    this.foo = 'foo'
    if (override) {
      return {
        bar: 'bar',}}}}let p1 = new Person(),
  p2 = new Person(true)
console.log(p1) // Person{ foo: 'foo' }
console.log(p1 instanceof Person) // true
console.log(p2) // { bar: 'bar' }
console.log(p2 instanceof Person) // false
Copy the code

The main difference between class constructors and constructors is that class constructors must be called using the new operator. A normal constructor without a new call takes the global this (usually window) as its internal object. If we forget to use new when calling the class constructor, we will throw an error:

function Person() {}
class Animal {}
// Build instance window as this
let p = Person()
let a = Animal()
// TypeError: class constructor Animal cannot be invoked without 'new'
Copy the code

There is nothing special about a class constructor; once instantiated, it becomes a normal instance method (but as a class constructor, you still use the new call). Therefore, it can be referenced on the instance after instantiation:

class Person {}
// Create a new instance with class
let p1 = new Person()
p1.constructor()
// TypeError: Class constructor Person cannot be invoked without 'new'
// Create a new instance with a reference to the class constructor
let p2 = new p1.constructor()
Copy the code

Instances, stereotypes, and class members

The syntax of a class makes it very easy to define members that should exist on instances, members that should exist on stereotypes, and members that should exist on the class itself.

1. Instance member

The class constructor is executed each time the class identifier is called through new. Inside this function, you can add “own” attributes to the newly created instance (this). There are no restrictions on what attributes to add. In addition, you can continue to add new members to the instance after the constructor completes execution.

Each instance corresponds to a unique member object, which means that none of the members are shared on the stereotype:

class Person {
  constructor() {
    // This example starts by defining a string using the object wrapper type
    // To test the equality of the two objects below
    this.name = new String('xhs-rookies')
    this.sayName = () = > console.log(this.name)
    this.nicknames = ['xhs-rookies'.'J-Dog']}}let p1 = new Person(),
  p2 = new Person()
p1.sayName() // xhs-rookies
p2.sayName() // xhs-rookies
console.log(p1.name === p2.name) // false
console.log(p1.sayName === p2.sayName) // false
console.log(p1.nicknames === p2.nicknames) // false
p1.name = p1.nicknames[0]
p2.name = p2.nicknames[1]
p1.sayName() // xhs-rookies
p2.sayName() // J-Dog
Copy the code

2. Prototype methods and accessors

To share methods between instances, the class definition syntax treats methods defined in a class block as prototype methods.

class Person {
  constructor() {
    // Everything added to this will exist on different instances
    this.locate = () = > console.log('instance')}// Everything defined in a class block is defined in the prototype of the class
  locate() {
    console.log('prototype')}}let p = new Person()
p.locate() // instance
Person.prototype.locate() // prototype
Copy the code

You can define a method in a class constructor or in a class block, but you cannot add primitive values or objects to the prototype as member data in a class block:

class Person {
  name: 'xhs-rookies'
}
// Uncaught SyntaxError: Unexpected token
Copy the code

Class methods are equivalent to object properties, so they can use strings, symbols, or computed values as keys:

const symbolKey = Symbol('symbolKey')
class Person {
  stringKey() {
    console.log('invoked stringKey')
  }
  [symbolKey]() {
    console.log('invoked symbolKey')} ['computed' + 'Key'] () {console.log('invoked computedKey')}}let p = new Person()
p.stringKey() // invoked stringKey
p[symbolKey]() // invoked symbolKey
p.computedKey() // invoked computedKey
Copy the code

Class definitions also support getting and setting accessors. The syntax and behavior are the same as normal objects:

class Person {
  set name(newName) {
    this.name_ = newName
  }
  get name() {
    return this.name_
  }
}
let p = new Person()
p.name = 'xhs-rookies'
console.log(p.name) // xhs-rookies
Copy the code

Static class methods

Static methods can be defined on a class. These methods are typically used to perform operations that are not instance-specific and do not require the existence of an instance of the class. Like prototype members, static members can only have one on each class. Static class members are prefixed with the static keyword in the class definition. In static members, this refers to the class itself. All other conventions are the same as for prototype members:

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

Static class methods are great for instance factories:

class Person {
  constructor(age) {
    this.age_ = age
  }
  sayAge() {
    console.log(this.age_)
  }
  static create() {
    Create and return a Person instance using a random age
    return new Person(Math.floor(Math.random() * 100))}}console.log(Person.create()) // Person { age_: ... }
Copy the code

4. Non-function archetypes and class members

While the class definition does not explicitly support adding member data to a stereotype or class, it is possible to add it manually outside the class definition:

class Person {
  sayName() {
    console.log(`${Person.greeting} The ${this.name}`)}}// Define data members on the class
Person.greeting = 'My name is'
// Define data members on the prototype
Person.prototype.name = 'xhs-rookies'
let p = new Person()
p.sayName() // My name is xhs-rookies
Copy the code

Note that adding data members is not explicitly supported in the class definition because it is an anti-pattern to add mutable (modifiable) data members to shared objects (stereotypes and classes). In general, an object instance should have its own data referenced through this (note that the use of this varies slightly from case to case; see this-MDN for more on this).

Iterator and generator methods

Class definition syntax supports defining generator methods on both prototypes and classes themselves:

class Person {
  // Define generator methods on prototypes
  *createNicknameIterator() {
    yield 'xhs-Jack'
    yield 'xhs-Jake'
    yield 'xhs-J-Dog'
  }
  // Define generator methods on the class
  static *createJobIterator() {
    yield 'xhs-Butcher'
    yield 'xhs-Baker'
    yield 'xhs-Candlestick maker'}}let jobIter = Person.createJobIterator()
console.log(jobIter.next().value) // xhs-Butcher
console.log(jobIter.next().value) // xhs-Baker
console.log(jobIter.next().value) // xhs-Candlestick maker
let p = new Person()
let nicknameIter = p.createNicknameIterator()
console.log(nicknameIter.next().value) // xhs-Jack
console.log(nicknameIter.next().value) // xhs-Jake
console.log(nicknameIter.next().value) // xhs-J-Dog
Copy the code

Because generator methods are supported, class instances can be made iterable by adding a default iterator:

class Person {
  constructor() {
    this.nicknames = ['xhs-Jack'.'xhs-Jake'.'xhs-J-Dog'[]} *Symbol.iterator]() {
    yield* this.nicknames.entries()
  }
}
let p = new Person()
for (let [idx, nickname] of p) {
  console.log(nickname)
}
// xhs-Jack
// xhs-Jake
// xhs-J-Dog
// We can also return only iterator instances:
class Person {
  constructor() {
    this.nicknames = ['xhs-Jack'.'xhs-Jake'.'xhs-J-Dog']} [Symbol.iterator]() {
    return this.nicknames.entries()
  }
}
let p = new Person()
for (let [idx, nickname] of p) {
  console.log(nickname)
}
// xhs-Jack
// xhs-Jake
// xhs-J-Dog
Copy the code

Object destructuring assignment

ECMAScript 6 adds object destructuring syntax that enables one or more assignment operations using nested data in a single statement. Simply put, object deconstruction is the assignment of object attributes using structures that match the object. The following example shows two equivalent pieces of code, first destructed without objects:

// Do not use object destructuring
let person = {
  name: 'xhs-Matt'.age: 18,}let personName = person.name,
  personAge = person.age
console.log(personName) // xhs-Matt
console.log(personAge) / / 18
Copy the code

Then, it is deconstructed using objects:

// Use object destructuring
let person = {
  name: 'xhs-Matt'.age: 18,}let { name: personName, age: personAge } = person
console.log(personName) // xhs-Matt
console.log(personAge) / / 18
Copy the code

Destructuring allows you to declare multiple variables and perform multiple assignments simultaneously in a structure that resembles an object literal. If you want the variable to use the name of the property directly, you can use shorthand syntax, such as:

let person = {
  name: 'xhs-Matt'.age: 18,}let { name, age } = person
console.log(name) // xhs-Matt
console.log(age) / / 18
Copy the code

The cases where deconstruction fails and object deconstruction can specify some default values are detailed in our deconstruction assignment article, which we won’t go into more detail in objects.

inheritance

Much of the earlier part of this chapter discussed how to implement inheritance using ES5’s mechanisms. One of the best new ECMAScript 6 features is native support for class inheritance. Although class inheritance uses the new syntax, it still uses the stereotype chain behind it.

Inheritance based

ES6 classes support single inheritance. Using the extends keyword, you can inherit any object that has [[Construct]] and a stereotype. For the most part, this means that you can inherit not only from a class, but also from ordinary constructors (to remain backward compatible) :

class Vehicle {}
/ / a derived class
class Bus extends Vehicle {}
let b = new Bus()
console.log(b instanceof Bus) // true
console.log(b instanceof Vehicle) // true
function Person() {}
// Inherit the normal constructor
class Engineer extends Person {}
let e = new Engineer()
console.log(e instanceof Engineer) // true
console.log(e instanceof Person) // true
Copy the code

Derived classes access the class and methods defined on the stereotype through the stereotype chain. The value of this reflects the instance or class calling the corresponding method:

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 {}
Vehicle.identifyClass('vehicle') // vehicle, class Vehicle {}
Copy the code

Note: The extends keyword can also be used in class expressions, so let Bar = class extends Foo {} is a valid syntax.

Constructors, HomeObject, and super()

Methods of derived classes can reference their archetypes through the super keyword. This keyword can only be used in derived classes, and only inside class constructors, instance methods, and static methods. 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

Note: 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]].

There are a few things to note when using super

  • superOnly used in derived class constructors and static methods.
class Vehicle {
  constructor() {
    super(a)// SyntaxError: 'super' keyword unexpected}}Copy the code
  • Cannot be quoted separatelysuperKeyword, which is used either to call a constructor or to reference a static method.
class Vehicle {}
class Bus extends Vehicle {
  constructor() {
    console.log(super)
    // SyntaxError: 'super' keyword unexpected here}}Copy the code
  • callsuper()The 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('1337H4X')) // Bus { licensePlate: '1337H4X' }
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('1337H4X')) // Bus { licensePlate: '1337H4X' }
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()
// ReferenceError: Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
Copy the code
  • If a constructor is explicitly defined 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

Packaging object

Raw value wrapper type

To facilitate manipulation of raw values, ECMAScript provides three special reference types: Boolean, Number, and String. These types have the same characteristics as the other reference types described in this chapter, but also have special behavior corresponding to their respective primitive types. Every time a method or property of a primitive value is used, an object of the corresponding primitive wrapper type is created in the background, exposing the various methods that operate on the primitive value. Consider the following example:

let s1 = 'xhs-rookies'
let s2 = s1.substring(2)
Copy the code

In this case, s1 is a variable containing a string, which is a raw value. The second line immediately calls the substring() method on S1 and stores the result in S2. We know that primitive values themselves are not objects, so logically there should be no methods. And in fact this example works exactly as expected. This is because there is a lot of processing going on in the background to make this happen. Specifically, when the second line accesses S1, it does so in read mode, that is, it reads the value that the variable holds from memory. Any time a string value is accessed in read mode, the following three steps are performed in the background:

(1) Create a String instance;

(2) call a specific method on the instance;

(3) Destroy the instance.

Think of these steps as executing the following three lines of ECMAScript code:

let s1 = new String('xhs-rookies')
let s2 = s1.substring(2)
s1 = null
Copy the code

This behavior allows the original value to own the behavior of the object. For booleans and numeric values, the above three steps also happen in the background, but with Boolean and Number wrapped types. The main difference between reference types and raw value wrapper types is the life cycle of the object. After a reference type is instantiated with new, the resulting instance is destroyed when it leaves scope, and the automatically created raw value wrapper object exists only for the execution of the line of code that accessed it. This means that attributes and methods cannot be added to raw values at run time. Take the following example:

let s1 = 'xhs-rookies'
s1.color = 'red'
console.log(s1.color) // undefined
Copy the code

The second line here attempts to add a color attribute to the string s1. However, when the third line of code accesses the color property, it is gone. The reason is that the second line of code creates a temporary String object when it runs, and when the third line of code executes, the object has already been destroyed. In fact, the third line of code creates its own String object here, but this object has no color attribute.

The Boolean, Number, and String constructors can be explicitly used to create primitive value wrapper objects. However, you should do this only when you really need them; otherwise, it’s easy to confuse the developer with whether they are raw values or reference values. Calling Typeof on an instance of a primitive value wrapper type returns “object”, and all primitive value wrapper objects are converted to a Boolean value of true.

In addition, the Object constructor, as a factory method, can return an instance of the corresponding primitive value wrapper type based on the type of the value passed in. Such as:

let obj = new Object('xhs-rookies')
console.log(obj instanceof String) // true
Copy the code

If the Object is passed a String, an instance of String is created. If it is a numeric value, an instance of Number is created. Boolean values give you an instance of Boolean.

Note that calling a primitive value wrapper type constructor with new is not the same as calling a transformation function of the same name. Such as:

let value = '18'
let number = Number(value) // Transition function
console.log(typeof number) // "number"
let obj = new Number(value) // constructor
console.log(typeof obj) // "object"
Copy the code

In this example, the variable number holds a raw value of 25, while the variable obj holds an instance of number.

Although explicitly creating instances of primitive value wrapper types is not recommended, they are important for the ability to manipulate primitive values. Each raw value wrapper type has a corresponding set of methods to facilitate data manipulation.

The title self-test

One: All objects have prototypes.

  • A:
  • B: wrong
Answer

Answer: * * * * B

All objects except base objects have prototypes. Basic objects have access to some methods and attributes, such as.toString. That’s why you can use built-in JavaScript methods! All of these methods are available on the prototype. Although JavaScript cannot find these methods directly on objects, JavaScript will find them along the prototype chain so that you can use them.


Two: Which of the following will have side effects on the object Person?

const person = {
  name: 'Lydia Hallie'.address: {
    street: '100 Main St',}}Object.freeze(person)
Copy the code
  • A: person.name = "Evan Bacon"
  • B: delete person.address
  • C: person.address.street = "101 Main St"
  • D: person.pet = { name: "Mara" }
Answer

Answer: C

Freeze An Object using the object. freeze method. Attributes cannot be added, modified, or deleted.

However, it only shallow freezes the object, meaning that only the immediate properties in the object are frozen. If the property is another object, as in the case of Address, the property in address is not frozen and can still be modified.


Three: which constructor to use for successful inheritanceDogThe class?

class Dog {
  constructor(name) {
    this.name = name
  }
}

class Labrador extends Dog {
  / / 1
  constructor(name, size) {
    this.size = size
  }
  / / 2
  constructor(name, size) {
    super(name)
    this.size = size
  }
  / / 3
  constructor(size) {
    super(name)
    this.size = size
  }
  / / 4
  constructor(name, size) {
    this.name = name
    this.size = size
  }
}
Copy the code
  • A: 1
  • B: 2
  • C: 3
  • D: 4
Answer

Answer: B

In subclasses, the this keyword cannot be accessed until super is called. If it does, it will throw a ReferenceError: 1 and 4 will raise a ReferenceError.

With the super keyword, the constructor of the parent class needs to be called with the given arguments. The constructor of the parent class takes the name argument, so we need to pass name to super.

The Labrador class receives two arguments, name because it inherits Dog and size as additional attributes of the Labrador class, both of which need to be passed to the Labrador constructor, so constructor 2 is used to do this correctly.

JavaScript series of objects, we end here, thank you for your support to the author! Your attention and praise will be the strongest motivation for us to move forward! Thank you!