JavaScript does not have the concept of classes, mainly through the prototype chain to achieve inheritance. In general, inheritance means copying operations, but JavaScript by default does not copy the properties of an object. Instead, JavaScript simply creates an association (prototype object pointer) between two objects so that one object can delegate to the other object’s properties and functions, so instead of being called inheritance, A commission would be more accurate.

The prototype

When we create a new object instance, we can directly access toString, valueOf, and other native methods without doing anything. So where do these methods come from? The answer is prototyping.

When the console prints an empty object, we can see that there are many methods that have been “initialized” to mount on the built-in __proto__ object. This built-in __proto__ is a pointer to the prototype object that is automatically created (explicitly or implicitly) when a new reference object is created and mounted to the new instance. When we try to access a property/method on an instance object, we return the instance property/method if it is present on the instance object, and if not, we look for the corresponding property/method on the prototype object pointed to by __proto__. This is why methods like toString and valueOf that try to access empty objects are still accessible, and JavaScript is officially based on inheritance in this way.

The constructor

If an instance’s __proto__ is just a pointer to the prototype object, it means that the prototype object was created before then. When was the prototype object created? This brings us to the concept of constructors.

A constructor is simply a function that can use the new keyword to create an instance object.

// A normal function
function person () {}

// The first letter of a function is usually capitalized
function Person () {}
const person = new Person();
Copy the code

The prototype object is created precisely when the constructor is declared. When the constructor is declared, the prototype object is created along with it and then mounted to the constructor’s Prototype property:

When a prototype object is created, a constructor property is automatically generated, pointing to the constructor that created it. So the relationship between the two is very closely related.

If you are careful, you may notice that the prototype object also has its own __proto__, which is not surprising, after all, everything is an object. The __proto__ of the prototype Object refers to Object.prototype. Object.prototype.__proto__ exists? It doesn’t exist. If you print it, it will be null. This also proves that Object is the origin of data types in JavaScript.

At this point, we have a general understanding of the relationship between the prototype and the constructor, which can be represented by a graph:

Prototype chain

Having said prototypes, we can talk about prototype chains, which are easy to explain if we understand the prototype mechanism. In fact, in the diagram above, the __proto__ chain is called the prototype chain.

How prototype chains work: The reason prototype chains are so important is that they determine how inheritance is implemented in JavaScript. When we access a property, the look-up mechanism is as follows:

  • Access object instance properties, return if yes, pass if no__proto__Go to its prototype object.
  • If the prototype object is found, it returns. If the prototype object is not found, it continues to search through its __proto__.
  • Keep finding them, floor by floorObject.prototype, returns if the target attribute is found, and returns if it cannotundefinedI will not look down because I am looking down__proto__isnull.

From the above explanation, we should be able to understand the prototype object for the instance generated by the constructor. If everything in JavaScript is an object, the constructor must be an object. If an object is __proto__, what is the __proto__ of the constructor?

We can print it out and have a look:

Now remember that all functions can be created using the new Function() method.

This also proves that all functions are instances of Function. Hold on, there seems to be something wrong, then Function.__proto__ is…

Function generates itself. In fact, we don’t need to understand it, because as a JS built-in objects, Function object when you haven’t generated script file already exists, where to call themselves, this stuff is similar to the metaphysics of “tao” and “heaven and earth”, you can show them who is generated, age grade are not born with the life of the sun is not out of heaven and earth… Forget it, in the downward pull will be written xiuxian =. =

Function.__proto__ = Function. Prototype

  • To be consistent with other functions
  • To illustrate a relationship, let’s say that all functions areFunctionThe instance.
  • Functions are callablecall bindThese built-in apis, written this way is a good way to ensure that the function instances can use these apis.

Note:

There are a few things to note about prototypes, prototype chains, and constructors:

  • __proto__Nonstandard attributes. If you want to access the prototype of an object, use the new ones in ES6Reflect.getPrototypeOforObject.getPrototypeOf()Method, not directobj.__proto__Because a nonstandard attribute means that the attribute may be modified or removed directly in the future. The same is true when changing the prototype of an objectES6To provide theReflect.setPrototypeOfObject.setPrototypeOf.
let target = {};
let newProto = {};
Reflect.getPrototypeOf(target) === newProto; // false
Reflect.setPrototypeOf(target, newProto);
Reflect.getPrototypeOf(target) === newProto; // true
Copy the code
  • The function will haveprototype, in addition toFunction.prototype.bind()Outside.
  • Objects will have__proto__, in addition toObject.prototypeIn fact, it also exists, but isnull).
  • All functions are created from Function, that is, their__proto__Will be equal toFunction.prototype.
  • Function.prototypeIs equal to theFunction.__proto__

The prototype of pollution

Prototype contamination is when an attacker somehow modifies the prototype of a JavaScript object.

What does that mean? It’s pretty simple. If we take the Object. The prototype. ToString change like this:

Object.prototype.toString = function () {alert('Prototype contamination')};
let obj = {};
obj.toString();
Copy the code

So when we run this code the browser will raise an alert, the object’s native toString method has been overwritten, and all objects will be affected when toString is called.

How could anyone be stupid enough to write such code in source code, you might say, shooting yourself in the foot? True, no one would write this in the source code, but an attacker could use prototype contamination to launch an attack, either through forms or by modifying the content of a request. Consider the following:

'use strict';
 
const express = require('express');
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser');
const path = require('path');
 
const isObject = obj= > obj && obj.constructor && obj.constructor === Object;
 
function merge(a, b) {
    for (var attr in b) {
        if (isObject(a[attr]) && isObject(b[attr])) {
            merge(a[attr], b[attr]);
        } else{ a[attr] = b[attr]; }}return a
}
 
function clone(a) {
    return merge({}, a);
}
 
// Constants
const PORT = 8080;
const HOST = '0.0.0.0';
const admin = {};
 
// App
const app = express();
app.use(bodyParser.json())
app.use(cookieParser());
 
app.use('/', express.static(path.join(__dirname, 'views')));
app.post('/signup', (req, res) => {
    var body = JSON.parse(JSON.stringify(req.body));
    var copybody = clone(body)
    if (copybody.name) {
        res.cookie('name', copybody.name).json({
            "done": "cookie set"
        });
    } else {
        res.json({
            "error": "cookie not set"}}})); app.get('/getFlag', (req, res) => {
    varа dmin =JSON.parse(JSON.stringify(req.cookies))
    if(admin. а dmin = =1) {
        res.send("hackim19{}");
    } else {
        res.send("You are not authorized"); }}); app.listen(PORT, HOST);console.log(`Running on http://${HOST}:${PORT}`);
Copy the code

If the above code fragment exists in the server, the attacker only needs to set the cookie to {__proto__: {admin: 1}} to complete the system intrusion.

Solutions to prototype contamination

Before we look at the solution to prototype contamination, let’s take a look at the lodash team’s previous approach to prototype contamination:

The code is simple; whenever it encounters sensitive words such as constructor or __proto__, it simply exits execution. This is of course an effective way to prevent prototype contamination, but there are other methods as well:

  1. useObject.create(null), method to create a prototype asnullSo that no extension to the prototype will take effect:
const obj = Object.create(null);
obj.__proto__ = { hack: 'Properties of contaminated archetypes' };
console.log(obj); / / = > {}
console.log(obj.hack); // => undefined
Copy the code
  1. Freeze (obj) to freeze the specified Object so that its properties cannot be modified and it becomes an unextensible Object:

    Object.freeze(Object.prototype);
    
    Object.prototype.toString = 'evil';
    
    console.log(Object.prototype.toString);
    ƒ toString() {[native code]}
    Copy the code
  2. Build a JSON schema and filter sensitive key names through the JSON Schema when parsing user input.

  3. Avoid unsafe recursive merges. This is similar to the LoDash fix, which improves the security of merge operations by skipping sensitive key names.

inheritance

Finally, we can talk about inheritance. Let’s take a look at the concept of inheritance and see what baidu says:

Inheritance is a concept of object-oriented software technology. It is the three basic characteristics of object-oriented, together with polymorphism and encapsulation. Inheritance can cause subclasses to have attributes and methods of their parent class, redefine, append attributes and methods, and so on.

This paragraph for programmers, this explanation is relatively easy to understand. Scrolling down, I came across an important description:

The creation of subclasses can add new data, new functions, can inherit all functions of the parent class, but can not selectively inherit some functions of the parent class. Inheritance is a relationship between classes, not between objects.

Now, this is kind of awkward, where do you get classes in JavaScript, only objects. Wouldn’t it be impossible to achieve pure inheritance? Hence the opening sentence: rather than inheritance, delegation is more accurate.

But JavaScript is very flexible, and that flexibility gives it a lot of drawbacks as well as some amazing advantages. It doesn’t matter that there is no native class providing inheritance. We can implement inheritance in JavaScript in more diverse ways, such as object.assign:

let person = { name: null.age: null };
let man = Object.assign({}, person, { name: 'John'.age: 23 });
console.log(man);  // => { name: 'John', age: 23 }
Copy the code

Using call and apply:

let person = {
    name: null.sayName: function () {
        console.log(this.name);
    },
    sayAge: function () {
        console.log(this.age); }};let man = { name: 'Man'.age: 23 };
person.sayName.call(man); // => Man
person.sayAge.apply(man); / / = > 23
Copy the code

We can even use deep-copy objects to do things like inheritance… There are many ways to implement inheritance in JS, but looking at the code above, it is not difficult to find some problems:

  • Encapsulation is not strong, too messy, writing up is very inconvenient.
  • There is no way to tell where the child objects are inherited from.

Is there a way around these problems? We can use the most common method of inheritance in JavaScript: prototype inheritance

Prototype chain inheritance

Prototype chain inheritance is to connect object instances through the prototype chain. When accessing an attribute of the target object, the object instances can be searched along the prototype chain, so as to achieve the effect similar to inheritance.

/ / parent class
function SuperType (colors = ['red'.'blue'.'green']) {
    this.colors = colors;
}

/ / subclass
function SubType () {}
// Inherits the parent class
SubType.prototype = new SuperType();
// Pointing the constructor attribute back to SubType in this way changes the constructor attribute to a traversable one
SubType.prototype.constructor = SubType;

let superInstance1 = new SuperType(['yellow'.'pink']);
let subInstance1 = new SubType();
let subInstance2 = new SubType();
superInstance1.colors; // => ['yellow', 'pink']
subInstance1.colors; // => ['red', 'blue', 'green']
subInstance2.colors; // => ['red', 'blue', 'green']
subInstance1.colors.push('black');
subInstance1.colors; // => ['red', 'blue', 'green', 'black']
subInstance2.colors; // => ['red', 'blue', 'green', 'black']
Copy the code

The above code uses the most basic prototype chain inheritance so that the subclass can inherit the attributes of the parent class. The key step of ** prototype inheritance is to associate the subclass prototype with the parent class prototype so that the prototype chain can be connected. ** here is to directly point the subclass prototype to the parent class instance to complete the association.

The above is an initial state of prototype inheritance. If we analyze the above code, we will find that there are still problems:

  1. When creating a subclass instance, you cannot pass arguments to the constructor of a supertype.
  2. This creates subclass prototypes that contain instance attributes of the parent class, causing problems with synchronous modification of reference type attributes.

Combination of inheritance

Composite inheritance solves both of these problems by using call to call the superclass constructor in the subclass constructor:

// Combinatorial inheritance implementation

function Parent(value) {
    this.value = value;
}

Parent.prototype.getValue = function() {
    console.log(this.value);
}

function Child(value) {
    Parent.call(this, value)
}

Child.prototype = new Parent();

const child = new Child(1)
child.getValue();
child instanceof Parent;
Copy the code

However, there is a problem: the constructor of the superclass is called twice (once when the subclass prototype is created and again when the subclass instance is created), resulting in the subclass prototype having the superclass instance properties, which wastes memory.

Parasitic combinatorial inheritance

In view of the defects of combinatorial inheritance, “parasitic combinatorial inheritance” has been evolved: use Object.create(Parent. Prototype) to create a new prototype Object to give subclasses to solve the defects of combinatorial inheritance:

// Parasitic combination inheritance implementation

function Parent(value) {
    this.value = value;
}

Parent.prototype.getValue = function() {
    console.log(this.value);
}

function Child(value) {
    Parent.call(this, value)
}

Child.prototype = Object.create(Parent.prototype, {
    constructor: {
        value: Child,
        enumerable: false.// This property cannot be enumerated
        writable: true.// This property can be overwritten
        configurable: true // You can delete the property by delete}})const child = new Child(1)
child.getValue();
child instanceof Parent;
Copy the code

The mode of parasitic combination inheritance is now recognized in the industry more reliable JS inheritance mode, ES6 class inheritance after Babel escape, the bottom is also using the way of parasitic combination inheritance.

Judgment of inheritance

When we use stereotype chain inheritance, how do we determine the relationship between object instances and target types?

instanceof

We can use instanceof to determine whether there is an inheritance relationship between the two. Instanceof literally means whether xx is an instanceof XXX. Return true if so, false otherwise:

function Parent () {}
function Child () {}
Child.prototype = new Parent();
let parent = new Parent();
let child = new Child();

parent instanceof Parent; // => true
child instanceof Child; // => true
child instanceof Parent; // => true
child instanceof Object; // => true
Copy the code

Instanceof is essentially a prototype chain lookup to determine inheritance, so it can only be used to determine reference types, not basic types. We can manually implement a simplified version of instanceof:

function _instanceof (obj, Constructor) {
    if (typeofobj ! = ='object' || obj == null) return false;
    let construProto = Constructor.prototype;
    let objProto = obj.__proto__;
    while(objProto ! =null) {
        if (objProto === construProto) return true;
        objProto = objProto.__proto__;
    }
    return false;
}
Copy the code

Object.prototype.isPrototypeOf(obj)

Still can use the Object. The prototype. IsPrototypeOf indirectly judge inheritance relationships, this method is used to determine whether an Object exists in another Object on the prototype chain:

function Foo() {}
function Bar() {}
function Baz() {}

Bar.prototype = Object.create(Foo.prototype);
Baz.prototype = Object.create(Bar.prototype);

var baz = new Baz();

console.log(Baz.prototype.isPrototypeOf(baz)); // true
console.log(Bar.prototype.isPrototypeOf(baz)); // true
console.log(Foo.prototype.isPrototypeOf(baz)); // true
console.log(Object.prototype.isPrototypeOf(baz)); // true
Copy the code

This article has been included in the front-end interview Guide column

Relevant reference

  • JavaScript goes from prototype to prototype chain
  • Latest: what you Have to know about JavaScript behind Lodash’s serious security Flaw
  • The way of the front-end interview

Previous content recommended

  1. Thoroughly understand throttling and anti-shaking
  2. [Basic] Principles and applications of HTTP and TCP/IP protocols
  3. 【 practical 】 WebPack4 + EJS + Express takes you through a multi-page application project architecture
  4. Event Loop in browser
  5. Interviewer: Tell me about the execution context
  6. Interviewer: Talk about scopes and closures
  7. Interviewer: Talk about modularity in JS
  8. Interviewer: Talk about modularity in JS