理解JavaScript中的类

来自菜鸟教程
(重定向自理解JavaScript中的类
跳转至:导航、​搜索

介绍

JavaScript 是一种基于原型的语言,JavaScript 中的每个对象都有一个隐藏的内部属性,称为 Prototype,可用于扩展对象属性和方法。 您可以在我们的 Understanding Prototypes and Inheritance in 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 {}

我们可以使用 Object.getPrototypeOf() 方法 访问对象的 Prototype。 让我们用它来测试我们创建的空 函数

Object.getPrototypeOf(x);
Outputƒ () { [native code] }

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

Object.getPrototypeOf(y);
Outputƒ () { [native code] }

functionclass 声明的代码都返回一个函数 Prototype。 使用原型,任何函数都可以使用 new 关键字成为构造函数实例。

const x = function() {}

// Initialize a constructor from a function
const constructorFromFunction = new x();

console.log(constructorFromFunction);
Outputx {}
constructor: ƒ ()

这也适用于类。

const y = class {}

// Initialize a constructor from a class
const constructorFromClass = new y();

console.log(constructorFromClass);
Outputy {}
constructor: class

这些原型构造函数示例在其他方面是空的,但我们可以看到在语法之下,两种方法如何实现相同的最终结果。

定义一个类

原型和继承教程中,我们创建了一个基于文本角色扮演游戏中角色创建的示例。 让我们继续这里的示例,将语法从函数更新为类。

构造函数 使用许多参数进行初始化,这些参数将被分配为 this 的属性,指的是函数本身。 按照惯例,标识符的第一个字母将大写。

构造函数.js

// Initializing a constructor function
function Hero(name, level) {
    this.name = name;
    this.level = level;
}

当我们将其转换为 class 语法时,如下所示,我们看到它的结构非常相似。

类.js

// Initializing a class definition
class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }
}

我们知道构造函数是一个对象蓝图,通过初始化器的首字母大写(这是可选的)和熟悉语法。 class 关键字以更直接的方式传达了我们函数的目标。

初始化语法的唯一区别是使用 class 关键字而不是 function,并在 constructor() 方法中分配属性。

定义方法

构造函数的常见做法是直接将方法分配给 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中引入的方法定义简写,定义一个方法是一个更加简洁的过程。

类.js

class Hero {
    constructor(name, level) {
        this.name = name;
        this.level = level;
    }

    // Adding a method to the constructor
    greet() {
        return `${this.name} says hello.`;
    }
}

让我们来看看这些属性和方法的作用。 我们将使用 new 关键字创建 Hero 的新实例,并分配一些值。

const hero1 = new Hero('Varg', 1);

如果我们用 console.log(hero1) 打印出更多关于我们的新对象的信息,我们可以看到更多关于类初始化发生了什么的细节。

OutputHero {name: "Varg", level: 1}
__proto__:
  ▶ constructor: class Hero
  ▶ greet: ƒ greet()

我们可以在输出中看到 constructor()greet() 函数应用于 __proto__hero1Prototype,而不是直接作为 hero1 对象上的方法。 虽然这在创建构造函数时很明显,但在创建类时并不明显。 类允许更简单和简洁的语法,但在过程中牺牲了一些清晰度。

扩展类

构造函数和类的一个优势特性是它们可以扩展为基于父对象的新对象蓝图。 这可以防止对相似但需要一些额外或更具体功能的对象重复代码。

可以使用 call() 方法从父级创建新的构造函数。 在下面的示例中,我们将创建一个更具体的字符类,称为 Mage,并使用 call() 为其分配 Hero 的属性,并添加一个附加属性。

构造函数.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;
}

此时,我们可以使用与 Hero 相同的属性以及我们添加的新实例来创建 Mage 的新实例。

const hero2 = new Mage('Lejon', 2, 'Magic Missile');

hero2 发送到控制台,我们可以看到我们已经基于构造函数创建了一个新的 Mage

OutputMage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__:
    ▶ constructor: ƒ Mage(name, level, spell)

对于 ES6 类,使用 super 关键字代替 call 来访问父函数。 我们将使用 extends 来引用父类。

类.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 打印到控制台并查看输出。

OutputMage {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;
}

类.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 开发人员至关重要。 熟悉类非常有帮助,因为 React 等流行的 JavaScript 库经常使用 class 语法。