了解JavaScript中的类

介绍

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

我们可以使用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属性,指向函数本身。 标识符的第一个字母将按照惯例大写。

constructor.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()方法所示。

constructor.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.`;
    }
}

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

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

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

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

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

扩展一个类

构造函数和类的一个有利特征是它们可以基于父类被扩展到新的对象蓝图中。 这样可以防止对类似的对象重复代码,但需要一些额外的或更多特定的功能。

可以使用call()方法从父级创建新的构造函数。 在下面的例子中,我们将创建一个更具体的角色类Mage ,并使用call()Hero的属性赋值给它,并添加一个额外的属性。

constructor.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来引用父类。

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

OutputMage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__: Hero
    ▶ constructor: class Mage

输出几乎完全相同,除了在类构造中, [[Prototype]]与父项相关联,在本例中为Hero

以下是整个初始化过程,添加方法以及构造函数和类的继承的并行比较。

constructor.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库(如React)经常使用class语法。

赞(52) 打赏
未经允许不得转载:优客志 » 系统运维
分享到:

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏