介绍
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库经常使用类
语法。