一尘不染

ES2015 (ES6) `class` 语法有什么好处?

javascript

我对 ES6 类有很多疑问。

使用class语法有什么好处?我读到 public/private/static 将成为 ES7 的一部分,这是一个原因吗?

此外,是class另一种 OOP 还是 JavaScript 的原型继承?我可以使用修改它.prototype吗?或者它只是同一个对象,但有两种不同的声明方式。

有速度优势吗?如果您有像大型应用程序这样的大型应用程序,也许更容易维护/理解?


阅读 120

收藏
2022-05-23

共1个答案

一尘不染

class语法虽然不完全是语法糖(但是,你知道,是一种好的糖)。它显着简化了构造函数的编写以及它们作为原型分配给它们创建的对象的对象,尤其是在设置继承层次结构时,这在 ES5 语法中很容易出错。但与旧方法不同,class语法还支持super.example()超级调用(众所周知,旧方法很难做到)以及属性声明私有字段私有方法(包括静态方法)。

(有时人们说class如果你想子类化Error或者Array[在 ES5 中不能被正确子类化],你必须使用语法。那不是真的,你可以使用不同的 ES2015 特性,Reflect.construct[规范MDN ],如果你不想使用class语法。¹)

此外,是class另一种 OOP 还是 JavaScript 的原型继承?

它与我们一直拥有的原型继承相同,只是如果您喜欢使用构造函数(new Foo等),它具有更简洁、更方便且不易出错的语法,以及一些附加功能。

我可以使用修改它.prototype吗?

prototype是的,一旦创建了类,您仍然可以在类的构造函数上修改对象。例如,这是完全合法的:

class Foo {
    constructor(name) {
        this.name = name;
    }

    test1() {
        console.log("test1: name = " + this.name);
    }
}
Foo.prototype.test2 = function() {
    console.log("test2: name = " + this.name);
};

有速度优势吗?

通过为此提供特定的习语,我认为引擎可能能够更好地优化工作。但他们已经非常擅长优化,我不认为会有显着差异。关于语法的一件事class是,如果您使用属性声明,您可以最大限度地减少对象在构造时所经历的形状更改次数,这可以使解释和稍后编译代码更快一些。但同样,它不会很大。

classES2015 (ES6)语法有什么好处?

简而言之:如果您首先不使用构造函数,首选Object.create或类似的,class对您没有用处。

如果您确实使用构造函数,则有一些好处class

  • 语法更简单,更不容易出错。
  • 使用新语法设置继承层次结构比使用旧语法更容易(而且更不容易出错)。
  • class保护您免受未能new与构造函数一起使用的常见错误(通过让构造函数抛出异常)。
  • 使用新语法调用父原型的方法版本比旧语法(super.method()而不是ParentConstructor.prototype.method.call(this)or Object.getPrototypeOf(Object.getPrototypeOf(this)).method.call(this))要简单得多。
  • 属性声明可以使正在创建的实例的形状更清晰,将其与构造函数逻辑分开。
  • class您可以通过语法而不是 ES5 语法使用私有字段和方法(实例和静态) 。

这是层次结构的语法比较(没有私有成员):

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        // ...
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        // ...use `result` for something...
        return result;
    }

    managerMethod() {
        // ...
    }
}

例子:

// ***ES2015+**
class Person {
    constructor(first, last) {
        this.first = first;
        this.last = last;
    }

    personMethod() {
        return `Result from personMethod: this.first = ${this.first}, this.last = ${this.last}`;
    }
}

class Employee extends Person {
    constructor(first, last, position) {
        super(first, last);
        this.position = position;
    }

    personMethod() {
        const result = super.personMethod();
        return result + `, this.position = ${this.position}`;
    }

    employeeMethod() {
        // ...
    }
}

class Manager extends Employee {
    constructor(first, last, position, department) {
        super(first, last, position);
        this.department = department;
    }

    personMethod() {
        const result = super.personMethod();
        return result + `, this.department = ${this.department}`;
    }

    managerMethod() {
        // ...
    }
}

const m = new Manager("Joe", "Bloggs", "Special Projects Manager", "Covert Ops");
console.log(m.personMethod());

对比

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    // ...
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    // ...use `result` for something...
    return result;
};
Manager.prototype.managerMethod = function() {
    // ...
};

现场示例:

// **ES5**
var Person = function(first, last) {
    if (!(this instanceof Person)) {
        throw new Error("Person is a constructor function, use new with it");
    }
    this.first = first;
    this.last = last;
};

Person.prototype.personMethod = function() {
    return "Result from personMethod: this.first = " + this.first + ", this.last = " + this.last;
};

var Employee = function(first, last, position) {
    if (!(this instanceof Employee)) {
        throw new Error("Employee is a constructor function, use new with it");
    }
    Person.call(this, first, last);
    this.position = position;
};
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;
Employee.prototype.personMethod = function() {
    var result = Person.prototype.personMethod.call(this);
    return result + ", this.position = " + this.position;
};
Employee.prototype.employeeMethod = function() {
    // ...
};

var Manager = function(first, last, position, department) {
    if (!(this instanceof Manager)) {
        throw new Error("Manager is a constructor function, use new with it");
    }
    Employee.call(this, first, last, position);
    this.department = department;
};
Manager.prototype = Object.create(Employee.prototype);
Manager.prototype.constructor = Manager;
Manager.prototype.personMethod = function() {
    var result = Employee.prototype.personMethod.call(this);
    return result + ", this.department = " + this.department;
};
Manager.prototype.managerMethod = function() {
    // ...
};        

var m = new Manager("Joe", "Bloggs", "Special Projects Manager", "Covert Ops");
console.log(m.personMethod());

正如你所看到的,那里有很多重复和冗长的东西,很容易出错并且重新输入很无聊(我曾经使用脚本来处理它,早在出现之前class)。

我应该注意,在 ES2015 代码中,Person函数是函数的原型Employee,但在 ES5 代码中并非如此。在 ES5 中,没有办法做到这一点。所有函数都Function.prototype用作它们的原型。不过,某些环境支持__proto__可能允许更改的伪属性。在这些环境中,您可以这样做:

Employee.__proto__ = Person; // Was non-standard in ES5

如果出于某种原因您想使用function语法而不是class在 ES2015+ 环境中执行此操作,则可以改用标准Object.setPrototypeOf

Object.setPrototypeOf(Employee, Person); // Standard ES2015+

但我看不出在 ES2015+ 环境中使用旧语法的任何强烈动机(除了尝试了解管道的工作原理)。

(ES2015 还定义了一个__proto__访问器属性,它是一个包装器Object.setPrototypeOfObject.getPrototypeOf因此这些非标准环境中的代码成为标准,但它只为遗留代码定义,并且是“规范的可选”,这意味着环境不需要提供它。)


¹如果您不想使用语法,以下是您将如何使用Reflect.construct子类(例如):Error``class

Hide code snippet

// Creating an Error subclass:
function MyError(...args) {
  return Reflect.construct(Error, args, this.constructor);
}
MyError.prototype = Object.create(Error.prototype);
MyError.prototype.constructor = MyError;
MyError.prototype.myMethod = function() {
  console.log(this.message);
};

// Example use:
function outer() {
  function inner() {
    const e = new MyError("foo");
    console.log("Callng e.myMethod():");
    e.myMethod();
    console.log(`e instanceof MyError? ${e instanceof MyError}`);
    console.log(`e instanceof Error? ${e instanceof Error}`);
    throw e;
  }
  inner();
}
outer();
.as-console-wrapper {
  max-height: 100% !important;
}
2022-05-23