了解 JavaScript 中的类

介绍

JavaScript 是一个基于原型的语言,JavaScript 中的每个对象都有一个隐藏的内部属性,称为 `[Prototype]],可用于扩展对象属性和方法。

直到最近,业余的开发人员使用 constructor functions来模仿JavaScript中的面向对象的设计模式。 2015年语言规格ECMAScript,通常被称为ES6,引入了JavaScript语言的类别。JavaScript中的类别实际上没有提供额外的功能,并且通常被描述为提供语法糖的原型和继承,因为它们提供了更清洁和更优雅的语法。

班级是功能

JavaScript 类是一种函数类型. 类被声明为class关键字. 我们将使用函数表达式语法来初始化函数和类表达式语法来初始化一类。

1[environment second]
2// Initializing a function with a function expression
3const x = function() {}
1[environment fourth]
2// Initializing a class with a class expression
3const y = class {}

我们可以使用 Object.getPrototypeOf() 方法访问对象的 `[Prototype]]。

1[environment second]
2Object.getPrototypeOf(x);
1[environment second]
2[secondary_label Output]
3ƒ () { [native code] }

我们也可以在我们刚刚创建的 上使用该方法。

1[environment fourth]
2Object.getPrototypeOf(y);
1[environment fourth]
2[secondary_label Output]
3ƒ () { [native code] }

函数表示的代码都返回函数[Prototype]

1[environment second]
2const x = function() {}
3
4// Initialize a constructor from a function
5const constructorFromFunction = new x();
6
7console.log(constructorFromFunction);
1[environment second]
2[secondary_label Output]
3x {}
4constructor: ƒ ()

这也适用于班级。

1[environment fourth]
2const y = class {}
3
4// Initialize a constructor from a class
5const constructorFromClass = new y();
6
7console.log(constructorFromClass);
1[secondary_label Output]
2[environment fourth]
3y {}
4constructor: class

这些原型构造器示例是空的,但我们可以看到,在语法下,两种方法都实现了相同的最终结果。

定义一类

原型和继承教程中,我们创建了一个基于文本的角色扮演游戏中基于角色创建的示例。

一个 constructor 函数被初始化为一系列参数,这些参数将被分配为this的属性,指的是函数本身。

1[label constructor.js]
2[environment second]
3// Initializing a constructor function
4function Hero(name, level) {
5    this.name = name;
6    this.level = level;
7}

当我们把它翻译成下面显示的 class语法时,我们看到它结构非常相似。

1[label class.js]
2[environment fourth]
3// Initializing a class definition
4class Hero {
5    constructor(name, level) {
6    	this.name = name;
7    	this.level = level;
8    }
9}

我们知道一个构造函数是指一个对象蓝图,由初始化器的第一个字母的资本化(这是可选的),并通过熟悉语法。

初始化语法的唯一区别在于使用代替函数的关键字,并在constructor()方法中分配属性。

定义方法

构建函数的常见做法是将方法直接分配给原型,而不是在初始化中,如下面的greet()方法所示。

 1[label constructor.js]
 2[environment second]
 3function Hero(name, level) {
 4    this.name = name;
 5    this.level = level;
 6}
 7
 8// Adding a method to the constructor
 9Hero.prototype.greet = function() {
10    return `${this.name} says hello.`;
11}

使用 ES6 中引入的 方法定义缩写,定义方法是一个更简洁的过程。

 1[label class.js]
 2[environment fourth]
 3class Hero {
 4    constructor(name, level) {
 5    	this.name = name;
 6    	this.level = level;
 7    }
 8
 9    // Adding a method to the constructor
10    greet() {
11    	return `${this.name} says hello.`;
12    }
13}

我们将使用关键字创建一个新的英雄实例,并分配一些值。

1[environment fourth]
2const hero1 = new Hero('Varg', 1);

如果我们使用「console.log(hero1)」打印更多关于新对象的信息,我们可以看到有关类初始化所发生的事情的更多细节。

1[secondary_label Output]
2[environment fourth]
3Hero {name: "Varg", level: 1}
4__proto__:
5   constructor: class Hero
6   greet: ƒ greet()

在输出中,我们可以看到‘constructor()’和‘greet()’函数被应用于‘proto__’或‘[Prototype]]的‘hero1’,而不是直接作为一个方法在‘hero1’对象上。

扩展一类

构建函数和类的一个优点是,它们可以扩展到基于母体的新对象蓝图,从而防止对类似但需要一些额外或更具体的功能的对象重复代码。

在下面的示例中,我们将创建一个更具体的字符类称为Mage,并使用call()Hero的属性分配给它,以及添加一个额外的属性。

1[label constructor.js]
2[environment second]
3// Creating a new constructor from the parent
4function Mage(name, level, spell) {
5    // Chain constructor with call
6    Hero.call(this, name, level);
7
8    this.spell = spell;
9}

在此时刻,我们可以使用与英雄相同的属性创建一个新的Mage实例,以及我们添加的新实例。

1[environment second]
2const hero2 = new Mage('Lejon', 2, 'Magic Missile');

hero2发送到主机,我们可以看到我们已经创建了一个基于构建器的新Mage

1[secondary_label Output]
2[environment second]
3Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
4__proto__:
5     constructor: ƒ Mage(name, level, spell)

在 ES6 类中,使用 super 关键字代替呼叫来访问主函数,我们将使用扩展来指引主类。

 1[label class.js]
 2[environment fourth]
 3// Creating a new class from the parent
 4class Mage extends Hero {
 5    constructor(name, level, spell) {
 6    	// Chain constructor with super
 7    	super(name, level);
 8
 9    	// Add a new property
10    	this.spell = spell;
11    }
12}

现在我们可以以同样的方式创建一个新的Mage实例。

1[environment fourth]
2const hero2 = new Mage('Lejon', 2, 'Magic Missile');

我们将打印hero2到控制台并查看输出。

1[environment fourth]
2[secondary_label Output]
3Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
4__proto__: Hero
5     constructor: class Mage

输出几乎是完全相同的,除了在类构造中[原型]与母体相关联,在这种情况下英雄

下面是对初始化、添加方法和继承构造函数和类的整个过程的对比。

 1[environment second]
 2[label constructor.js]
 3function Hero(name, level) {
 4    this.name = name;
 5    this.level = level;
 6}
 7
 8// Adding a method to the constructor
 9Hero.prototype.greet = function() {
10    return `${this.name} says hello.`;
11}
12
13// Creating a new constructor from the parent
14function Mage(name, level, spell) {
15    // Chain constructor with call
16    Hero.call(this, name, level);
17
18    this.spell = spell;
19}
 1[label class.js]
 2[environment fourth]
 3// Initializing a class
 4class Hero {
 5    constructor(name, level) {
 6    	this.name = name;
 7    	this.level = level;
 8    }
 9
10    // Adding a method to the constructor
11    greet() {
12    	return `${this.name} says hello.`;
13    }
14}
15
16// Creating a new class from the parent
17class Mage extends Hero {
18    constructor(name, level, spell) {
19    	// Chain constructor with super
20    	super(name, level);
21
22    	// Add a new property
23    	this.spell = spell;
24    }
25}

虽然语法非常不同,但两种方法之间的底层结果几乎相同. 类给了我们创建对象蓝图的更简洁的方式,而构造函数更准确地描述了帽子下发生的事情。

结论

在本教程中,我们了解了 JavaScript 构建函数和 ES6 类之间的相似之处和差异。

了解原型遗传对于成为一个有效的JavaScript开发人员至关重要,熟悉类是非常有用的,因为像React(https://reactjs.org/)这样的流行的JavaScript库经常使用语法。

Published At
Categories with 技术
comments powered by Disqus