3

1. There are no real classes in JS!

JavaScript is different from class-oriented languages in that it does not have classes as an abstract model for objects. JavaScript only has objects, and there is no real class . JS just takes advantage of a special feature of the function-all functions will have a public and non-enumerable attribute by default named prototype, which will point to another object to simulate the behavior of the class.

It should be noted that if the built-in bind function is used to generate a hard-bound function, the function has no prototype attribute, and the prototype of the target function will replace the prototype of the hard-bound function. instanceof or new on such a function is equivalent to directly using the target function.
function Animal() {};
console.log(Animal.prototype);// {}

In JS, new Animal() looks like it instantiates the Animal class, but in fact it is not.

const a = new Animal();
console.log(Object.getPrototypeOf(a) === Animal.prototype);// true

In a class-oriented language, a class can be copied (or instantiated) multiple times. Instantiating a class means "copying the behavior of the class into a physical object", and this process is repeated for each new instance.

But in JS, there is no similar copy mechanism. You cannot create multiple instances of a class, you can only create multiple objects. Their " prototype " related to the same object. However, by default, copying is not performed, so these objects will not completely lose contact, they are related to each other. new Animal() will generate a new object (we call it a ), the internal link of this new object " prototype " associated with the Animal.prototype object. We did not initialize a class. In fact, we did not copy any behavior from the "class" to an object, just let the two objects relate to each other.

2. What is the constructor in JS?

function Animal() {};
const a = new Animal();

There is no real class in JS, but when I see these two lines of code, I still think that Animal is a class. Why is this? In my opinion, one reason lies in the presence of the new operator. In class-oriented languages, the new operator is required. Another reason is that in new Animal() in, Animal the Called particularly such like is called when the class is instantiated class called by the constructor .

But in fact, Animal is no different from other functions in your program. The function itself is not a constructor, but when we add the new keyword in front of the ordinary function call, it will turn the function call into a "constructor call" ( new will hijack all ordinary functions And call it in the form of a constructed object).

Simply put, in JS, "constructor" can be interpreted as a function called using the new operator. However, what we need to know is that functions in JS are not constructors, only when new is used, the function call will become constructor call .

3. "Class-oriented" in JS

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

const dog = new Animal('dog');
const cat = new Animal('cat');

dog.sayName(); // dog
cat.sayName(); // cat
console.log(dog.constructor); //  [Function: Animal]
  1. this.name = name adds a name attribute to each object through the implicit binding of , which is a bit like a data value encapsulated by a class instance.
  2. Animal.prototype.sayName = ... will add a property (function) to the Animal.prototype During the creation process, dog and cat ), the internal link of this new object " prototype " will be associated with Animal.prototype . When dog and cat can not be found sayName , it will Animal.prototype found on.

    Note that, dog.constructor pointing Animal function, so dog of constructor property, seems to represent the dog by whom constructed. But in fact, this is only seem so because dog itself does not constructor property, constructor property and sayName , same as Animal.prototype property, this property and dog (or cat no connection between).

    For Animal.prototype , constructor is only Animal function is declared (it is not enumerable, but it can be changed),

    Animal.prototype.constructor = 'animal';
    console.log(dog.constructor); // 'animal'

    When changing the Animal.prototype , the orientation of the constructor attribute also becomes confusing. This is because the fish attribute does not exist on constructor , so constructor Animal.prototype (that is, {} ) is searched, but {} also does not have the constructor attribute, so it will continue to find Object.prototype . This object has the constructor , which points to the built-in function Object

    Animal.prototype = {};
    const fish = new Animal();
    console.log(fish.constructor); // [Function: Object]

    Of course, we can manually specify the constructor attribute.

    Animal.prototype = {};
    Object.defineProperty(Animal.prototype, 'constructor', {
     enumerable: false, // 不可枚举
     writable: true,
     configurable: true,
     value: Animal, // 让 constructor 指向 Animal
    });
    const fish = new Animal();
    console.log(fish.constructor); // [Function: Animal]
    

    All in all, the constructor is just a common attribute that may be changed. The reference of dog.constructor

4. Inheritance

The prototype style of inherits :

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

function Dog(name, color) {
    Animal.call(this, name);
    this.color = color;
}
// 创建了一个新的 Dog.prototype 对象并关联到 Animal.prototype
Dog.prototype = Object.create(Animal.prototype);
//Object.setPrototypeOf( Dog.prototype, Animal.prototype )
// 注意!现在 Dog.prototype.constructor 的指向已经变为了Animal
Dog.prototype.sayName = function () {
    console.log('重写sayName');
    //显式多态,调用Animal.prototype.sayName
    Animal.prototype.sayName.call(this);
};

const teddy = new Dog('泰迪', '棕色');
teddy.sayName(); // 重写sayName 泰迪

When declaring Dog , like all functions, Dog will have a prototype attribute pointing to the default object (assuming the object name is originObj ), but originObj not what we want Foo.prototype . So we created a new object and associated this new object to Foo.prototype , discarding the default object originObj . The above code is implemented Object.create Of course, it can also be achieved by ES6's Object.setPrototypeOf , Object.setPrototypeOf( Dog.prototype, Animal.prototype ) , this function is to modify originObj , rather than abandon originObj , and therefore through Object.setPrototypeOf , Dog.prototype.constructor point does not change.

We can use instanceof to check the relationship between teddy and Dog or Animal

console.log(teddy instanceof Animal);

instanceof left is a ordinary objects A , the right side is a function B , the operator checks B.prototype exists in A of " the prototype " chain.

If you want to check two ordinary objects relationship between, you can use isPrototypeOf

console.log(Animal.prototype.isPrototypeOf(teddy));

5.Class syntax in ES6

You might think that ES6's class syntax introduces a new "class" mechanism to JS, but that's not the case. The class is basically just a syntactic sugar of the " prototype "

class Animal {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}

class Dog extends Animal {
    constructor(name, color) {
        super(name);
        this.color = color;
    }
    sayName() {
        console.log('重写sayName');
        //相对多态
        super.sayName();
    }
}

const teddy = new Dog('泰迪', 'brown');
teddy.sayName();

In addition to the better syntax, what other problems does ES6 solve?

  1. No longer quote the messy prototype .
  2. Dog directly "inherited" Animal , no longer need to pass Object.create to replace
    For the prototype object, there is no need to set __proto__ or Object.setPrototypeOf .
  3. Relative polymorphism can be achieved by super , so that any method can refer to the party with the same name in the upper layer of the prototype chain
    Law. No need to use explicit polymorphism, Animal.prototype.sayName.call(this);
  4. class literal syntax cannot declare attributes (only methods can be declared) . It looks like this is a restriction, but it will exclude
    Drop many bad situations, otherwise, the "instance" at the end of the prototype chain may be accidentally obtained
    Properties elsewhere (these properties are implicitly "shared" by all "instances"). So, the class syntax is actually
    Can help you avoid making mistakes .
  5. You can extends , and even the built-in object (sub)type, such as
    Array or RegExp. Without the class ..extends syntax, it is very difficult to achieve this.

Note that, super and this different, super not dynamic binding. The testObj.sayName below does not point to its current " prototype " object testParen , but points to Animal .

const testObj = {
 name: 'test',
 sayName: Dog.prototype.sayName,
};
const testParen = {
 sayName() {
     console.log('testParen');
 },
};
Object.setPrototypeOf(testObj, testParen);
testObj.sayName();//重写sayName test

forceddd
271 声望912 粉丝

一名前端爱好者。