JavaScript summary [5] prototype, inheritance and class

Keywords: Javascript

JavaScript prototypes, inheritance, and classes

prototype

Prototype

In JavaScript, an object has a special hidden attribute [[Prototype]], which is either null or a reference to another object. When we read a missing attribute from the object, JavaScript will automatically get the attribute from the Prototype

let animal = {
  eats: true,
  sleep: true
};
let rabbit = {
  jumps: true
};

rabbit.__proto__ = animal; // (*)

// Now we can find both properties in rabbit:
alert( rabbit.eats ); // true
alert( rabbit.sleep ); // true
alert( rabbit.jumps ); // true

Prototype chain

Nearest inheritance

__ proto__ And [[Prototype]]

  • __ proto__ Is the getter/setter of [[Prototype]]
  • __ proto__ Properties are a little outdated. It exists for historical reasons. Modern programming languages suggest that we should use the function Object.getPrototypeOf/Object.setPrototypeOf instead__ proto__ get/set prototype

for... in and inheritance

let animal = {
  eats: true
};

let rabbit = {
  jumps: true,
  __proto__: animal
};

// Object.keys returns only its own key
alert(Object.keys(rabbit)); // jumps

// for..in traverses itself and the inherited keys
for(let prop in rabbit) alert(prop); // jumps, then eats

If you need to filter out the properties in the prototype, you can use hasOwnProperty to exclude them in the conditional statement body

F.prototype

  1. If F.prototype is an object, the new operator uses it to set [[Prototype]] for the new object.
// Setting Rabbit.prototype = animal literally means: "when a new Rabbit is created, assign its [[Prototype]] to animal."
let animal = {
  eats: true
};

function Rabbit(name) {
  this.name = name;
}
Rabbit.prototype = animal;
let rabbit = new Rabbit("White Rabbit"); //  rabbit.__proto__ == animal
alert( rabbit.eats ); // true
  1. Default F.prototype, constructor attribute

Every function has a "prototype" attribute, even if we don't provide it. The default "prototype" is an object with only the attribute constructor, which points to the function itself. In the above method, an enterprise announced that it did not replace the whole prototype with tiger eye stone. The correct way is to choose to add or delete the attributes of prototype, such as Rabbit.prototype.eat, or manually rebuild the constructor attribute, such as constructor: rabbit

Original prototype

  • The prototype of object.prototype is null
  • The prototypes of other objects are inherited from Object to form a prototype chain, which improves the storage efficiency
  • Basic data types also have prototypes, which are not objects. However, if we try to access their properties, the temporary wrapper object will be created through the built-in constructors String, Number and Boolean. They give us ways to manipulate strings, numbers, and Booleans, and then disappear
String.prototype.show = function() {
  alert(this);
};

"BOOM!".show(); // BOOM!

Borrow from prototype

Borrowing means that we get a method from one object and copy it to another object.

let obj = {
  0: "Hello",
  1: "world!",
  length: 2,
};

obj.join = Array.prototype.join;

alert( obj.join(',') ); // Hello,world!

The above code is valid because the internal algorithm of the built-in method join only cares about the correct index and length attribute. It does not check whether the object is a real array.

Prototype method, no proto object

Not recommended__ proto__ , The following methods are recommended instead:

  • Object.create(proto, [descriptors]) -- use the given proto as [[Prototype]] and optional attribute description to create an empty object.
  • Object.getPrototypeOf(obj) -- returns [[Prototype]] of object obj.
  • Object.setPrototypeOf(obj, proto) -- set [[Prototype]] of object obj to proto.

class

Basic grammar

class User {
  constructor(name) {
    this.name = name;
  }
  sayHi() {
    alert(this.name);
  }

}
// Usage:
let user = new User("John");
user.sayHi();

The essence of class

In JavaScript, the essence of a class is a function.

class User {
  constructor(name) { this.name = name; }
  sayHi() { alert(this.name); }
}

// Proof: User is a function
alert(typeof User); // function

The essence of the above code is

  1. Create a function named User that becomes the result of the class declaration. The code for this function comes from the constructor method (if we don't write this method, it is assumed to be empty).
  2. Store methods in classes, such as sayHi in User.prototype

Difference from function:

  1. The function created through class has a special internal attribute tag [[IsClassConstructor]]: true. Therefore, it is not exactly the same as manual creation, and you must use new to call it
  2. Class methods cannot be enumerated. Class definition sets the enumerable flag of all methods in "prototype" to false
  3. Class always uses use strict. All code in the class construct will automatically go into strict mode

Class expression

A class can be defined, passed, returned and assigned in another expression. If the class expression has a name, the name is only visible inside the class

// Named Class Expression
// (there is no such term in the specification, but it is similar to named function expressions)
let User = class MyClass {
  sayHi() {
    alert(MyClass); // The name MyClass is only visible inside the class
  }
};
new User().sayHi(); // It runs normally and displays the contents defined in MyClass
alert(MyClass); // error, MyClass is not visible externally

We can even dynamically create classes on demand:

function makeClass(phrase) {
  // Declare a class and return it
  return class {
    sayHi() {
      alert(phrase);
    }
  };
}

// Create a new class
let User = makeClass("Hello");
new User().sayHi(); // Hello

Calculation attribute name

Example of calculation method name using brackets [...]:

class User {
  ['say' + 'Hi']() { // Calculation attribute method
    alert("Hello");
  }

}

new User().sayHi();

Class field

  1. Class fields are used as follows:
class User {
  name = "John";
}

let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
  1. Make binding methods using class fields:

    Functions in JavaScript have a dynamic this. It depends on the call context. Therefore, if an object method is passed somewhere or called in another context, this will no longer be a reference to its object. This is lost in the following cases:

class Button {
  constructor(value) {
    this.value = value;
  }

  click() {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // undefined

Solution:

  • Pass a wrapper function, such as setTimeout (() = > button. Click(), 1000)
  • Bind a method to an object, for example, in a constructor
  • Use arrow function * * (recommended)**
class Button {
  constructor(value) {
    this.value = value;
  }
  click = () => {
    alert(this.value);
  }
}

let button = new Button("hello");

setTimeout(button.click, 1000); // hello

// The class field Click = () = > {...} is created based on each object. Here, there is an independent method for each Button object, and there is an internal this pointing to this object. We can pass button.click anywhere, and the value of this is always correct

Class inheritance

Syntax Child extend Parent

  1. override method

Note: the arrow function does not have super

class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed = speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stands still.`);
  }

}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // Call stop of parent class
    this.hide(); // Add function
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5
rabbit.stop(); // White Rabbit stands still. White rabbit hides!
  1. Override constructor
  • The constructor of the inheritance class must call super(...), and (/!) must be called before using this.
  • The parent constructor always uses the value of its own field, not the one that is overridden
  • You can use super.method() in a Child method to call the Parent method
  • Methods remember their classes / objects in the internal [[HomeObject]] attribute. This is how super resolves the parent method. Therefore, it is not safe to copy a method with super from one object to another.

Static properties and static methods

  1. Static method:

    You can assign a method to the class's function itself instead of the "prototype" assigned to it. Such methods are called static methods. Usually, static methods are used to implement functions that belong to this class but do not belong to any specific object of this class. Static methods are also used for database related public classes, which can be used to search / save / delete entries in the database

class User {
  static staticMethod() {
    alert(this === User);
  }
}

User.staticMethod(); // true
  1. Static properties
class Article {
  static publisher = "Levi Ding";
}

alert( Article.publisher ); // Levi Ding
// Equivalent to Article.publisher = "Levi Ding";
  1. Inherit static properties and methods

Static methods can also inherit

Private and protected properties and methods

  1. Internal and external interfaces

    • Internal interfaces are methods and properties that can be accessed through other methods of the class, but cannot be accessed externally.
    • External interfaces are methods and properties that can also be accessed from outside the class.
  2. Set protected properties:

    • Protected attributes are usually underlined_ As a prefix.
    • Read only: set as accessor property without setter

Class check

  1. instanceof operator

The instanceof operator is used to check whether an object belongs to a specific Class. It also considers inheritance. obj instanceof Class returns true if obj belongs to Class (or a derived Class of Class)

  1. toString method

For self created objects, you can use the special object property Symbol.toStringTag to customize the behavior of the toString method of the object.

let user = {
  [Symbol.toStringTag]: "User"
};

alert( {}.toString.call(user) ); // [object User]

Mixin

The easiest way to construct a mixin in JavaScript is to construct an object with practical methods so that we can easily incorporate these practical methods into the prototype of any class.

// mixin
let sayHiMixin = {
  sayHi() {
    alert(`Hello ${this.name}`);
  },
  sayBye() {
    alert(`Bye ${this.name}`);
  }
};

// Usage:
class User {
  constructor(name) {
    this.name = name;
  }
}

// Copy method
Object.assign(User.prototype, sayHiMixin);

// Now you can say hello
new User("Dude").sayHi(); // Hello Dude!

The way is to construct an object with practical methods so that we can easily incorporate these practical methods into the prototype of any class.

// mixin
let sayHiMixin = {
  sayHi() {
    alert(`Hello ${this.name}`);
  },
  sayBye() {
    alert(`Bye ${this.name}`);
  }
};

// Usage:
class User {
  constructor(name) {
    this.name = name;
  }
}

// Copy method
Object.assign(User.prototype, sayHiMixin);

// Now you can say hello
new User("Dude").sayHi(); // Hello Dude!

Posted by Canabalooza on Wed, 10 Nov 2021 18:30:36 -0800