分类
文章

理解javascript中的类

介绍

JavaScript是一种基于原型的语言,JavaScript中的每个对象都有一个称为[[Prototype]]的隐藏内部属性,可用于扩展对象的属性和方法。您可以在我们的JavaScript理解原型和继承教程中阅读有关原型的更多信息。

直到最近,勤奋的开发人员都使用构造函数来模仿JavaScript中的面向对象设计模式。语言规范ECMAScript 2015(通常称为ES6)向JavaScript语言引入了类。JavaScript中的类实际上并没有提供其他功能,通常被描述为在原型和继承上提供”语法糖”,因为它们提供了更简洁,更优雅的语法。由于其他编程语言都使用类,因此JavaScript中的类语法使开发人员可以在各种语言之间移动更直接。

类就是函数

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

// Initializing a function with a function expression
const x = function() {}
// Initializing a class with a class expression
const y = class {}

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

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

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

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

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

定义类

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

constructor function使用许多参数初始化,这些参数将被指定为this属性,并引用函数本身。标识符的首字母将按惯例大写。

constructor.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 ,并在constructor()方法内分配属性。

定义方法

构造函数的通常做法是直接将方法分配给prototype而不是将其分配给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语法。

发表评论

电子邮件地址不会被公开。 必填项已用*标注