我们除了可以使用 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); // function
User(); // 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) {
// 调用 setter
this.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); // John
user = 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; // Anonymous
alert(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