es6 类,继承,原型链_揭开ES6类和原型继承的神秘面纱

es6 类,继承,原型链

In the early history of the JavaScript language, a cloud of animosity formed over the lack of a proper syntax for defining classes like in most object oriented languages. It wasn’t until the ES6 spec release in 2015 that the class keyword was introduced; it is described as syntactical sugar over the existing prototype-based inheritance.

在JavaScript语言的早期历史中,由于缺乏像大多数面向对象的语言中那样定义类的正确语法,形成了仇恨云。 直到2015年ES6规范发布,才引入了class关键字。 它被描述为现有基于原型的继承上的语法糖。

At its most basic level, the class keyword in ES6 is equivalent to a constructor function definition that conforms to prototype-based inheritance. It may seem redundant that a new keyword was introduced to wrap an already existing feature but it leads to readable code and lays the foundation upon which future object oriented features can be built.

在最基本的层次上, ES6class关键字等效于构造函数定义,该定义符合基于原型的继承。 引入一个新的关键字来包装一个已经存在的功能似乎是多余的,但是它导致了可读的代码,并为将来的面向对象功能的构建奠定了基础。

Before ES6, if we had to create a blueprint ( class ) for creating many objects of the same type, we’d use a constructor function like this:

ES6之前,如果必须创建一个蓝图(类)来创建许多相同类型的对象,则可以使用如下构造函数:

function Animal (name, fierce) {
  Object.defineProperty(this, 'name', {
    get: function() { return name; }
  });

  Object.defineProperty(this, 'fierce', {
    get: function() { return fierce; }
  });
}

Animal.prototype.toString = function() {
  return 'A' + ' ' + ( this.fierce ? 'fierce' : 'tame' ) + ' ' + this.name;
}

This is a simple object constructor that represents a blueprint for creating instances of the Animal class. We have defined two read-only own properties and a custom toString method on the constructor function. We can now create an Animal instance with the new keyword:

这是一个简单的对象构造函数,代表用于创建Animal类实例的蓝图。 我们在构造函数上定义了两个只读的own属性和一个自定义的toString方法。 现在,我们可以使用new关键字创建Animal实例:

var Lion = new Animal('Lion', true);
console.log(Lion.toString()); // "A fierce Lion"

Great! It works as expected. We can rewrite the code using the ES6 class for a concise version:

大! 它按预期工作。 我们可以使用ES6类重写代码以获得简洁的版本:

class Animal {

  constructor(name, fierce){
      this._name = name;
      this._fierce = fierce;
  }

  get name() {
    return this._name;
  }

  get fierce() {
    return ` This animal is ${ this._fierce ? 'fierce' : 'tame' }`;
  }

  toString() {
    return `This is a ${ this._fierce ? 'fierce' : 'tame' } ${this._name}`;
  }

}

Let’s create an instance of the Animal class with the new keyword as we did before:

让我们像以前一样用new关键字创建Animal类的实例:

let Lion = new Animal('Lion', true);

    console.log(Lion.fierce); 

    console.log(Lion.toString())

Defining classes in ES6 is very straightforward and feels more natural in an object oriented sense than the previous simulation using object constructors. Let’s take an in-depth look at the ES6 class by exploring some of its attributes and ramifications.

ES6定义类非常简单,与以前使用对象构造函数进行的模拟相比,在面向对象的意义上感觉更自然。 让我们通过探索ES6类的一些属性和后果来深入研究它。

定义班级 ( Defining Classes )

Making a transition from using the older object constructors to the newer ES6 classes shouldn’t be difficult at all since the class keyword is just a special function and exhibits expected function behavior. For example, just like a function, a class can be defined by either a declaration or an expression, where the latter can be named or unnamed.

从使用较旧的对象构造函数过渡到较新的ES6类应该一点也不困难,因为class关键字只是一个特殊的function并且表现出预期的功能行为。 例如,就像一个函数一样,可以通过声明或表达式来定义class ,在声明或表达式中可以命名或不命名。

类声明 ( Class Declarations )

A class declaration is defined with the class keyword and followed by the name of the class.

class关键字定义一个类声明,后跟class的名称。

class Animal {}

We already used the class declaration when we wrote the ES6 version of the Animal constructor function :

在编写Animal构造函数的ES6版本时,我们已经使用了class声明:

class Animal {
      constructor(name, fierce){
        this._name = name;
        this._fierce = fierce;
      }
      }

类表达式 ( Class Expressions )

A class expression allows for a bit more flexibility; a class may be named or unnamed, however, when a class expression is named, the name attribute becomes a local property on the class’ body and can be accessed using the .name property.

类表达式允许更多的灵活性。 一个类可以被命名或不被命名,但是,当一个类表达式被命名时,name属性成为该类主体上的局部属性,可以使用.name属性进行访问。

An unnamed class expression skips the name after the class keyword:

未命名的类表达式会跳过class关键字之后的名称:

// unnamed
    let animal = class {}

A named class expression, on the other hand includes the name:

另一方面,命名的类表达式包含名称:

// named
    let animal = class Animal {}

When comparing the object constructor to the ES6 class, it is worthy of note that unlike the object constructor that can be accessed before it’s scope is defined because of hoisting, the class can’t and isn’t hoisted.

在将对象构造函数与ES6 class进行比较时,值得注意的是,与由于吊起而在定义作用域之前可以对其进行访问的对象构造函数不同, class不能也不能被吊起

While this may seem like a major limitation on ES6 classes, it doesn’t have to be; good ES6 practice demands that if any function must mutate an instance of a class then it can be defined anywhere in the program but should be invoked only after the class itself has been defined.

尽管这似乎是对ES6类的主要限制,但不一定如此。 良好的ES6惯例要求,如果任何函数必须更改类的实例,则可以在程序中的任何位置定义它,但只有在定义class本身之后才能调用它。

类的构造函数和主体 ( Constructor and Body of a Class )

After defining a class using any of the two stated methods, the curly brackets {} should hold class members, such as instance variables, methods or constructor; the code within the curly brackets make up the body of the class.

使用上述两种方法中的任何一种定义类后,大括号{}应该包含类成员,例如实例变量,方法或构造函数; 大括号内的代码构成了类的主体。

A class’ constructor is simply a method whose purpose is to initialize an instance of that class. This means that whenever an instance of a class is created, the constructor ( where it is defined ) of the class is invoked to do something on that instance; it could maybe initialize the object’s properties with received parameters or default values when the former isn’t available.

类的构造函数只是一个方法,其目的是初始化该类的实例。 这意味着,每当创建一个类的实例时,都会调用该类的构造函数(在其中定义了该实例)以对该实例执行操作; 当前者不可用时,它可以使用接收到的参数或默认值初始化对象的属性。

There can only be a single constructor method associated with a class so be careful not to define multiple constructor methods as this would result in a SyntaxError. The super keyword can be used within an object’s constructor to call the constructor of its superclass.

与类关联的只能是一个构造函数方法,因此请注意不要定义多个构造函数方法,因为这会导致SyntaxError 。 可以在对象的构造函数中使用super关键字来调用其超类的构造函数。

class Animal {
  constructor(name, fierce){ // there can only be one constructor method
    this._name = name;
    this._fierce = fierce;
  }
}

The code within the body of a class is executed in strict mode.

类主体中的代码以严格模式执行。

定义方法 (Defining Methods)

The body of a class usually comprises instance variables to define the state of an instance of the class, and prototype methods to define the behaviour of an instance of that class. Before ES6, if we needed to define a method on a constructor function, we could do it like this:

类的主体通常包括用于定义类实例状态的实例变量和用于定义类实例行为的原型方法。 在ES6之前,如果需要在构造函数上定义方法,则可以这样进行:

function Animal (name, fierce) {
  Object.defineProperty(this, 'name', {
    get: function() { return name; }
  });

  Object.defineProperty(this, 'fierce', {
    get: function() { return fierce; }
  });
}

Animal.prototype.toString = function() {
  return 'A' + ' ' + ( this.fierce ? 'fierce' : 'tame' ) + ' ' + this.name;
}

Or

要么

function Animal (name, fierce) {
      Object.defineProperty(this, 'name', {
        get: function() { return name; }
      });

      Object.defineProperty(this, 'fierce', {
        get: function() { return fierce; }
      });

      this.toString = function() {
        return 'A' + ' ' + ( this.fierce ? 'fierce' : 'tame' ) + ' ' + this.name;
      }
    }

The two different methods we defined above are referred to as prototype methods and can be invoked by an instance of a class. In ES6, we can define two types of methods: prototype and static methods. Defining a prototype method in ES6 is quite similar to what we have above, except that the syntax is cleaner ( we don’t include the prototype property ) and more readable:

我们上面定义的两个不同的方法称为原型方法,可以由类的实例调用。 在ES6中,我们可以定义两种类型的方法:原型方法和静态方法。 在ES6中定义原型方法与上面的方法非常相似,除了语法更简洁(我们不包括prototype属性)并且可读性更高:

class Animal {

  constructor(name, fierce){
    this._name = name;
    this._fierce = fierce;
  }

  get name() {
    return this._name;
  }

  get fierce() {
    return ` This animal is ${ this._fierce ? 'fierce' : 'tame' }`;
  }

  toString() {
 return `This is a ${ this._fierce ? 'fierce' : 'tame' } ${this._name}`;
  }

}

Here we first define two getter methods using a shorter syntax, then we create a toString method that basically checks to see if an instance of the Animal class is a fierce or tame animal. These methods can be invoked by any instance of the Animal class but not by the class itself.

在这里,我们首先使用较短的语法定义两个getter 方法 ,然后创建一个toString方法,该方法基本上检查Animal类的实例是凶猛还是驯服的动物。 这些方法可以由Animal类的任何实例调用,但不能由类本身调用。

ES6 prototype methods can be inherited by children classes to simulate an object oriented behaviour in JavaScript but under the hood, the inheritance feature is simply a function of the existing prototype chain and we’d look into this very soon.

子类可以继承ES6原型方法,以在JavaScript中模拟面向对象的行为,但实际上,继承功能只是现有原型链的功能,我们将很快进行研究。

All ES6 methods cannot work as constructors and will throw a TypeError if invoked with the new keyword.

所有ES6方法都不能作为构造函数使用,如果使用new关键字调用,则将引发TypeError

静态方法 ( Static Methods )

Static methods resemble prototype methods in the fact that they define the behaviour of the invoking object but differ from their prototype counterparts as they cannot be invoked by an instance of a class. A static method can only be invoked by a class; an attempt to invoke a static method with an instance of a class would result to unexpected behaviour.

静态方法类似于原型方法,因为它们定义了调用对象的行为,但是与原型方法不同,因为它们不能被类的实例调用。 静态方法只能由类调用。 尝试使用类的实例调用静态方法将导致意外的行为。

A static method must be defined with the static keyword. In most cases, static methods are used as utility functions on classes.

必须使用static关键字定义静态方法。 在大多数情况下,静态方法用作类的实用程序函数。

Let’s define a static utility method on the Animal class that simply returns a list of animals:

让我们在Animal类上定义一个静态实用程序方法,该方法仅返回动物列表:

class Animal {

  constructor(name, fierce){
    this._name = name;
    this._fierce = fierce;
  }

  static animalExamples() {
    return `Some examples of animals are Lion, Elephant, Sheep, Rhinocerus etc`
  }

}

Now, we can call the animalExamples() method on the class itself:

现在,我们可以在类本身上调用animalExamples()方法:

console.log(Animal.animalExamples()); // "Some examples of animals are Lion, Elephant, Sheep, Rhinocerus etc"

ES6中的子类化 ( Subclassing in ES6 )

In object oriented programming, it’s good practice to create a base class that holds some generic methods and attributes, then create other more specific classes that inherit these generic methods from the base class and so on. In ES5 we relied on the prototype chain to simulate this behaviour and the syntax would sometimes become messy.

在面向对象的编程中,优良作法是创建一个包含一些通用方法和属性的基类,然后创建其他更具体的类,这些类从基类中继承这些通用方法,依此类推。 在ES5我们依靠原型链来模拟这种行为,并且语法有时会变得混乱。

ES6 introduced the somewhat familiar extends keyword that makes inheritance very easy. A subclass can easily inherit attributes from a base class like this:

ES6引入了一些熟悉的extends关键字,使继承非常容易。 子类可以轻松地从基类继承属性,如下所示:

class Animal {

  constructor(name, fierce){
    this._name = name;
    this._fierce = fierce;
  }

  get name() {
    return this._name;
  }

  get fierce() {
    return ` This animal is ${ this._fierce ? 'fierce' : 'tame' }`;
  }

  toString() {
     return `This is a ${ this._fierce ? 'fierce' : 'tame' } ${this._name}`;
  }

}


class Felidae extends Animal {

  constructor(name, fierce, family){
    super(name, fierce);
    this._family = family;
  }

  family() {
     return `A ${this._name} is an animal of the ${this._family} subfamily under the ${Felidae.name} family`;
  }
}

We have created a subclass here — Felidae ( colloquially referred to as “cats” ) — and it inherits the methods on the Animal class. We make use of the super keyword within the constructor method of the Felidae class to invoke the super class’ ( base class) constructor. Awesome, let’s try creating an instance of the Felidae class and invoking and own method and an inherited method:

我们在这里创建了一个子类Felidae(俗称“猫”),它继承了Animal类的方法。 我们在Felidae类的构造方法中使用super关键字来调用super类的(基类)构造函数。 太棒了,让我们尝试创建Felidae类的实例并调用并拥有自己的方法和一个继承的方法:

var Tiger = new Felidae('Tiger', true, 'Pantherinae');

    console.log(Tiger.toString()); // "This is a fierce Tiger"

    console.log(Tiger.family()); // "A Tiger is an animal of the Pantherinae subfamily under the Felidae family"

If a constructor is present within a sub-class, it needs to invoke super() before using "this".

如果子类中存在构造函数,则需要在使用“ this ”之前调用super()

It is also possible to use the extends keyword to extend a function-based “class”, but an attempt to extend a class solely created from object literals will result in an error.

也可以使用extends关键字来扩展基于函数的“类”,但是尝试扩展仅由对象文字创建的类将导致错误。

At the beginning of this article, we saw that most of the new keywords in ES6 are merely syntactical sugar over the existing prototype-based inheritance. Let’s now take a look under the sheets and see how the prototype chain works.

在本文开始时,我们看到ES6中的大多数新关键字仅仅是现有基于原型的继承上的语法糖。 现在,让我们看一下这些工作表,看看原型链是如何工作的。

原型继承 ( Prototypal Inheritance )

While it’s nice to define classes and perform inheritance with the new ES6 keywords, it’s even nicer to understand how things work at the canonical level. Lets take a look at JavaScript objects: All JavaScript objects have a private property that points to a second object ( except in a few rare cases where it points to null ) associated with them, this second object is called the prototype.

定义类并使用新的ES6关键字执行继承是很好的做法,但了解事物在规范级别的工作方式则更好。 让我们看一下JavaScript对象:所有JavaScript对象都有一个私有属性,该属性指向与其关联的第二个对象(在极少数情况下,它指向null除外),该第二个对象称为prototype

The first object inherits properties from the prototype object and the prototype may in-turn inherit some properties from its own prototype and it goes on like that until the last prototype on the chain has its prototype property equal to null.

第一个对象从prototype对象继承属性,并且原型可以依次从其自己的原型继承一​​些属性,并继续进行,直到链上的最后一个原型的原型属性等于null为止。

All JavaScript objects created by assigning an identifier the value of object literals share the same prototype object. This means that their private prototype property points to the same object in the prototype chain and hence, inherits its properties. This object can be referred to in JavaScript code as Object.prototype.

通过为标识符分配对象文字的值而创建的所有JavaScript对象都共享同一原型对象。 这意味着它们的私有原型属性指向原型链中的同一对象,并因此继承其属性。 该对象可以在JavaScript代码中称为Object.prototype

Objects created by invoking a class’ constructor or constructor function initialize their prototype from the prototype property of the constructor function. In other words, when a new object is created by invoking new Object(), that object's prototype becomes Object.prototype just like any object created from object literals. Similarly, a new Date() object will inherit from Date.prototype() and a new Number() from Number.prototype().

通过调用类的构造函数或构造函数创建的对象从构造函数的prototype属性初始化其原型。 换句话说,当通过调用new Object()创建new Object() ,该对象的原型将变为Object.prototype ,就像从对象文字创建的任何对象一样。 同样,一个new Date()对象将从Date.prototype()继承,一个new Number()Number.prototype()继承。

Nearly all objects in JavaScript are instances of Object which sits on the top of a prototype chain.

JavaScript中几乎所有对象都是位于原型链顶部的Object实例。

We have seen that it’s normal for JavaScript objects to inherit properties from another object ( prototype ) but the Object.prototype exhibits a rare behaviour where it does not have any prototype and does not inherit any properties ( it sits on the top of a prototype chain ) from another object.

我们已经看到JavaScript对象从另一个对象(原型)继承属性是正常的,但是Object.prototype表现出一种罕见的行为,即它没有任何原型并且不继承任何属性(它位于原型链的顶部) )从另一个对象。

Nearly all of JavaScript’s built-in constructors inherit from Object.prototype, so we can say that Number.prototype inherits properties from Object.prototype. The effect of this relationship : creating an instance of Number in JavaScript ( using new Number() ) will inherit properties from both Number.prototype and Object.prototype and that is the prototype chain.

几乎所有JavaScript的内置构造继承Object.prototype ,所以我们可以说, Number.prototype从继承属性Object.prototype 。 这种关系的效果:在JavaScript中创建Number的实例(使用new Number() )将继承Number.prototypeObject.prototype属性,这就是原型链。

JavaScript objects can be thought of as containers since they hold the properties defined on them and these properties are referred to as “ own properties” but they are not limited to just their own properties. The prototype chain plays a big role when a property is being sought on an object:

可以将JavaScript对象视为容器,因为它们拥有在其上定义的属性,并且这些属性称为“自己的属性”,但它们不仅限于它们自己的属性。 当在对象上寻找属性时,原型链扮演着重要的角色:

  • The property is first sought on the object as an own property

    首先在对象上将属性视为自己的属性
  • If the property isn’t found on the object, it’s prototype is checked next

    如果在对象上找不到该属性,则接下来检查其原型
  • If the property doesn’t exist on the prototype, the prototype of the prototype is queried

    如果原型中不存在该属性,则查询原型的原型
  • This querying of prototype after prototype continues until the property is found or until the end of the prototype chain is reached and an error is returned.

    原型之后的原型查询将继续进行,直到找到该属性或直到原型链的末尾并返回错误为止。

Let’s write some code to clearly simulate the behaviour of prototypal inheritance in JavaScript.

让我们编写一些代码来清楚地模拟JavaScript中原型继承的行为。

We will be using the ES5 method — Object.create() — for this example so let’s define it:

在此示例中,我们将使用ES5方法— Object.create() -让我们对其进行定义:

Object.create() is a method that creates a new object, using its first argument as the prototype of that object.

Object.create()是一种使用其第一个参数作为该对象的原型创建新对象的方法。

let Animals = {}; // Animal inherits object methods from Object.prototype.

    Animals.eat = true; // Animal has an own property - eat ( all Animals eat ).

    let Cat = Object.create(Animals); // Cat inherits properties from Animal and Object.prototype.

    Cat.sound = true; // Cat has it's own property - sound ( the animals under the cat family make sounds).

    let Lion = Object.create(Cat); // Lion ( a prestigious cat ) inherits properties from Cat, Animal, and Object.prototype.

    Lion.roar = true; // Lion has its own property - roar ( Lions can raw )

    console.log(Lion.roar); // true - This is an "own property".

    console.log(Lion.sound); // true - Lion inherits sound from the Cat object.

    console.log(Lion.eat); // true - Lion inherits eat from the Animal object.

    console.log(Lion.toString()); // "[object Object]" - Lion inherits toString method from Object.prototype.

Here’s a verbal interpretation of what we did above:

以下是我们上面所做的口头解释:

  • We created an Animal object and it inherits properties from Object.prototype

    我们创建了一个Animal对象,它继承了Object.prototype属性
  • We initialized Animal``'``s own property — eat — to true ( all Animals eat )

    我们初始化了Animal``'``s的财产– eat –变为true(所有Animals吃掉)
  • We created a new object — Cat — and initialized it’s prototype to Animal ( Therefore Cat inherits properties from Animal and Object.prototype )

    我们创建了一个新对象Cat,并将其原型初始化为Animal (因此Cat继承了AnimalObject.prototype属性)
  • We initialized Cat's own property — sound — to true ( the animals under the cat family make sounds )

    我们将Cat's自己的属性-声音-初始化为真实(cat家族下的动物发出声音)
  • We created a new object — Lion — and initialized it’s prototype to Cat ( Therefore Lion inherits properties from Cat, Animal and Object.prototype )

    我们创建了一个新对象Lion —并将其原型初始化为Cat (因此Lion继承了CatAnimalObject.prototype
  • We initialized Lion's own property — roar — to true ( Lions can raw )

    我们将Lion's自有属性(吼声)初始化为true(Lions可以原始)
  • Lastly, we logged own and inherited properties on the Lion object and they all returned the right values by first seeking for the properties on the Lion object then moving on to the prototypes ( and prototypes of prototypes ) where it wasn’t available on the former.

    最后,我们在Lion对象上记录了自己的属性和继承的属性,并且它们都返回了正确的值,方法是先在Lion对象上寻找属性,然后继续使用原型(和原型的原型),而前者则不可用。

This is a basic but accurate simulation of the prototypal inheritance in JavaScript using the prototype chain.

这是使用原型链对JavaScript中的原型继承进行基本但准确的模拟。

加起来 ( Summing Up )

In this article we have gone through the basics of ES6 classes and prototypal inheritance,. Hopefully you have learnt a thing or two from reading the article. If you have any questions, leave them below in the comments section.

在本文中,我们介绍了ES6类和原型继承的基础。 希望您从阅读本文中学到了一两件事。 如果您有任何疑问,请在下面的评论部分中保留。

翻译自: https://scotch.io/tutorials/demystifying-es6-classes-and-prototypal-inheritance

es6 类,继承,原型链

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值