我们除了可以使用 new function 的方式,使用构造函数创建实例对象外,还可以使用新引入的“class”语法创建实例对象,这个新引入的语法特性对面向对象编程更加友好和有用。
“class”语法
基本语法如下:
class MyClass {// 类的方法constructor() { ... }method1() { ... }method2() { ... }method3() { ... }...}
这里声明了一个名为 MyClass 的类,然后我们就可以用 new MyClass() 方式创建对象了。新对象中就包含了上述列举的几个方法。
举个例子:
class User {constructor(name) {this.name = name;}sayHi() {alert(this.name);}}
new MyClass("John") 的调用结果如下:
- 会创建一个新对象。
constructor携带给定参数"John"执行,并将值赋给了this.name。
然后,我们就可以调用诸如 user.sayHi() 的对象方法了。
⚠️ 类方法之间无需使用逗号 ** 开发者在这里常犯的错误就是在类方法之间用逗号(
,)隔开。这不是不对的,会引发语法错误。注意,不要把类语法跟对象字面量语法搞混淆了。在类中,方法之间是无需使用逗号。
什么是类?
其实 class 并不想某些人认为的那样,是 JavaScript 中引入的新的语言层面的实体。
下面我们将来揭开类的神秘面纱,帮助你理解这背后的运行机制。
JavaScript 中,类就是个函数。
不信,来看一下:
class User {constructor(name) { this.name = name; }sayHi() { alert(ths.name); }}// 证据来了alert(typeof User); // function。User 是个函数
class User {...} 做了下面这些事情:
- 创建一个名为
User的函数,也就是类声明后的返回结果。函数体代码使用的是constructor方法里的(如果我们没写的话,那就是空的)。 - 将诸如
sayHi的类方法存储在User.prototype上。
new User 创建完对象后,在对象上调用的方法,就是从原型上取的。因此,对象就可以访问类中的方法了。
我们用下面这张示例图,展示了 class User 做了哪些事情:

从下面的代码,能看出类背后的运行机制:
class User {constructor(name) { this.name = name; }sayHi() { alert(this.name); }}// 类是个函数alert(typeof User); // function// ...或更准确地说是 constructor 方法alert(User === User.prototype.constructor); // true// 方法在 User.prototype 上。例如:alert(User.prototype.sayHi); // alert(this.name);// 原型上有两个方法alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
不只是语法糖
有时会听人说 class 呀就是个“语法糖”(是指在不引入新东西的情况下,让代码变得更加易读而设计的语法)。这是因为,我们实际上可以在不使用 class 关键字的情况下写出相同功能的代码:
// 使用纯函数重写 class User// 1. 创建构造函数function User(name) {this.name = name;}// 所有函数的原型对象上都有一个默认的 constructor 属性// 因此无需我们手动创建// 2. 像原型添加方法User.prototype.sayHi = function() {alert(this.name);};// 使用:let user = new User("John");user.sayHi();
以上代码得到的结果,可以近似等于 class User {...} 的书写形式。因此,这就是为什么可以把 class 当成是定义构造函数和构造函数原型上方法的一个语法糖。
当然,也是有区别的:
- 首先,通过
class创建的函数会使用一个特别的内部属性[[FunctionKind]]:"classConstructor"标记。因此与手动创建的方式还是不完全一样的。
与普通函数不同的是,类构造函数必须要用 new 调用,否则就报错:
class User {constructor() {}}alert(typeof User); // functionUser(); // Error: Class constructor User cannot be invoked without 'new'
而且,在大多数 JavaScript 引擎中,类构造器的字符串表示通常是以“class…”开头的。
class User {constructor() {}}alert(User); // class User { ... }
- 类方法都是不可枚举的。类定义会将
"prototype"中方法的enumerable标记都设置为false。
这样一来,用 for..in 去遍历对象实例的时候,不会出现这些类方法。
- 类代码内部默认都是启用了严格模式(
"use strict")。所有类内部代码都是在严格模式下执行的。
除此之外,class 语法还有其他特性。咱们接着来讨论。
类表达式
与函数类似,类可以在一个表达式中定义、传递、返回、赋值等等。
下面举了一个类表达式的列子:
let User = class {sayHi() {alert("Hello");}};
与命名函数表达式类似,类表达式也可以有名字。
不过类表达式的名字,只能在类内部引入使用:
// “命名类表达式”// (规范中无此概念,但它类似于命名函数表达式)let User = class MyClass {sayHi() {alert(MyClass); // MyClass 这个名称只能在类内部引入使用}};new User().sayHi(); // class MyClass {...}。这里能成功展示 MyClass 类的定义代码alert(MyClass); // ReferenceError: MyClass is not defined。MyClass 这个
我们甚至可以“按需”动态地创建类:
function makeClass(phrase) {// 声明并返回一个类return class {sayHi() {alert(phrase);};};}// 创建一个类let User = makeClass("Hello");new User().sayHi(); // Hello
Getters/setters
类似于字面量对象,类中也能包含 getters/setters,还有计算属性这些。
下列的 user.name 就是靠 get/set 实现的:
class User {constructor(name) {// 调用 setterthis.name = name;}get name() {return this._name;}set name(value) {if (value.length < 4) {alert("Name is too short.");return;}this._name = value;}}let user = new User("John");alert(user.name); // Johnuser = new User(""); // Name is too short.
类声明在 User.prototype 上创建了 getters、setters。类似下面这样:
Object.defineProperties(User.prototype, {name: {get() {return this._name},set(name) {// ...}}});
同时,我们还可以在类中使用方括号 […] 声明计算属性:
class User {['say' + 'Hi']() {alert("Hello");}}new User().sayHi();
类属性
⚠️ 可能需要 polyfill
类属性是最近新添加到语言中的特性。
上例中 User 只有方法。现在来为它添加属性:
class User {name = "Anonymous";sayHi() {alert(`Hello, ${this.name}!`);}}const user = new User()user.sayHi(); // Hello, Anonymous!user.name; // Anonymousalert(User.prototype.sayHi); // 存在于 User.prototype 上面alert(User.prototype.name); // undefined。没在 User.prototype 上面
总结
类的基本语法如下:
class MyClass {prop = value; // 属性constructor(...) { // 构造器// ...}method(...) {} // 方法get something(...) {} // getter 方法set something(...) {} // setter 方法[Symbol.iterator]() {} // 使用计算属性声明的方法(Symbol 类型)// ...}
MyClass 从技术上讲是个函数(函数体就是在 constructor 中定义的代码)。而方法、getters、setters 则是定义在 MyClass.prototype 上的。
(完)
📄 文档信息
🕘 更新时间:2020/01/20
🔗 原文链接:https://javascript.info/class
