JavaScript 是一种基于原型的语言,JavaScript 中的每个对象都有一个隐藏的内部属性,称为[[Prototype]]
可用于扩展对象属性和方法。您可以在我们的文章中阅读有关原型的更多信息了解 JavaScript 中的原型和继承教程。
直到最近,勤奋的开发人员还使用构造函数模仿 JavaScript 中的面向对象设计模式。语言规范 ECMAScript 2015(通常称为 ES6)向 JavaScript 语言引入了类。 JavaScript 中的类实际上并不提供额外的功能,并且通常被描述为在原型和继承之上提供“语法糖”,因为它们提供了更清晰、更优雅的语法。由于其他编程语言使用类,因此 JavaScript 中的类语法使开发人员可以更直接地在语言之间切换。
JavaScript 类是一种函数。类的声明是class
关键词。我们将使用函数表达式语法来初始化函数,使用类表达式语法来初始化类。
// Initializing a function with a function expression
const x = function() {}
// Initializing a class with a class expression
const y = class {}
我们可以访问[[Prototype]]
的对象使用Object.getPrototypeOf() method。让我们用它来测试空function我们创造了。
Object.getPrototypeOf(x);
Output
ƒ () { [native code] }
我们也可以使用该方法class我们刚刚创建。
Object.getPrototypeOf(y);
Output
ƒ () { [native code] }
声明的代码function
and class
两者都返回一个函数[[Prototype]]
。通过原型,任何函数都可以使用以下方式成为构造函数实例new
关键词。
const x = function() {}
// Initialize a constructor from a function
const constructorFromFunction = new x();
console.log(constructorFromFunction);
Output
x {}
constructor: ƒ ()
这也适用于类。
const y = class {}
// Initialize a constructor from a class
const constructorFromClass = new y();
console.log(constructorFromClass);
Output
y {}
constructor: class
这些原型构造函数示例是空的,但我们可以看到在语法之下,两种方法如何实现相同的最终结果。
In the 原型和继承教程,我们创建了一个基于文本角色扮演游戏中的角色创建的示例。让我们继续这里的示例,将语法从函数更新为类。
A 构造函数使用许多参数进行初始化,这些参数将被分配为属性this
,指的是函数本身。按照惯例,标识符的第一个字母应大写。
构造函数.js
// Initializing a constructor function
function Hero(name, level) {
this.name = name;
this.level = level;
}
当我们把它翻译成class语法如下所示,我们看到它的结构非常相似。
class.js
// Initializing a class definition
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
}
我们知道构造函数应该是一个对象蓝图,通过初始化器的第一个字母的大写(这是可选的)并熟悉语法。这class
关键字以更直接的方式传达我们功能的目标。
初始化语法的唯一区别是使用class
关键字而不是function
,并分配 a 内的属性constructor()
method.
构造函数的常见做法是将方法直接分配给prototype
而不是在初始化中,如greet()
方法如下。
构造函数.js
function Hero(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
Hero.prototype.greet = function() {
return `${this.name} says hello.`;
}
对于类,这种语法得到了简化,并且可以将方法直接添加到类中。使用方法定义简写ES6 中引入,定义方法是一个更加简洁的过程。
class.js
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
greet() {
return `${this.name} says hello.`;
}
}
让我们看看这些属性和方法的实际应用。我们将创建一个新实例Hero
使用new
关键字,并分配一些值。
const hero1 = new Hero('Varg', 1);
如果我们打印出有关新对象的更多信息console.log(hero1)
,我们可以看到有关类初始化所发生情况的更多详细信息。
Output
Hero {name: "Varg", level: 1}
__proto__:
▶ constructor: class Hero
▶ greet: ƒ greet()
我们可以在输出中看到constructor()
and greet()
函数被应用到__proto__
, or [[Prototype]]
of hero1
,而不是直接作为方法hero1
目的。虽然在创建构造函数时这一点很明显,但在创建类时却并不明显。类允许更简单和简洁的语法,但在过程中牺牲了一些清晰度。
构造函数和类的一个有利特征是它们可以扩展到基于父对象的新对象蓝图。这可以防止类似但需要一些附加或更具体功能的对象重复代码。
可以使用父级创建新的构造函数call()方法。在下面的示例中,我们将创建一个更具体的字符类,称为Mage
,并分配属性Hero
对其使用call()
,以及添加额外的属性。
构造函数.js
// Creating a new constructor from the parent
function Mage(name, level, spell) {
// Chain constructor with call
Hero.call(this, name, level);
this.spell = spell;
}
此时,我们可以创建一个新的实例Mage
使用相同的属性Hero
以及我们添加的一个新的。
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
Sending hero2
到控制台,我们可以看到我们已经创建了一个新的Mage
基于构造函数。
Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__:
▶ constructor: ƒ Mage(name, level, spell)
对于 ES6 类,super使用关键字代替call
访问父函数。我们将使用extends
来引用父类。
class.js
// Creating a new class from the parent
class Mage extends Hero {
constructor(name, level, spell) {
// Chain constructor with super
super(name, level);
// Add a new property
this.spell = spell;
}
}
现在我们可以创建一个新的Mage
以同样的方式实例。
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
我们将打印hero2
到控制台并查看输出。
Output
Mage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__: Hero
▶ constructor: class Mage
输出几乎完全相同,除了在类构造中[[Prototype]]
链接到父级,在本例中Hero
.
下面是构造函数和类的初始化、添加方法和继承的整个过程的并排比较。
构造函数.js
function Hero(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
Hero.prototype.greet = function() {
return `${this.name} says hello.`;
}
// Creating a new constructor from the parent
function Mage(name, level, spell) {
// Chain constructor with call
Hero.call(this, name, level);
this.spell = spell;
}
class.js
// Initializing a class
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
greet() {
return `${this.name} says hello.`;
}
}
// Creating a new class from the parent
class Mage extends Hero {
constructor(name, level, spell) {
// Chain constructor with super
super(name, level);
// Add a new property
this.spell = spell;
}
}
尽管语法完全不同,但这两种方法之间的基本结果几乎相同。类为我们提供了一种更简洁的创建对象蓝图的方法,而构造函数更准确地描述了幕后发生的事情。
在本教程中,我们了解了 JavaScript 构造函数和 ES6 类之间的异同。类和构造函数都模仿 JavaScript 的面向对象继承模型,JavaScript 是一种基于原型的继承语言。
了解原型继承对于成为一名高效的 JavaScript 开发人员至关重要。熟悉类非常有帮助,因为流行的 JavaScript 库如React经常使用class
syntax.