类的基本语法
class MyClass {
// class 方法
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
new
会自动调用 constructor()
方法,因此我们可以在 constructor()
中初始化对象。
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// 用法:
let user = new User("John");
user.sayHi();
当 new User("John")
被调用:
一个新对象被创建;
constructor
使用给定的参数运行,并将其赋值给this.name
。
Class 是什么
在 JS 中,类是一种函数。
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// 佐证:User 是一个函数
alert(typeof User); // function
class User {...}
构造实际上做了如下的事儿:
创建一个名为
User
的函数,该函数成为类声明的结果。该函数的代码来自于constructor
方法(如果我们不编写这种方法,那么它就被假定为空);存储类中的方法,例如
User.prototype
中的sayHi
。
当 new User
对象被创建后,当我们调用其方法时,它会从原型中获取对应的方法。
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// class 是一个函数
alert(typeof User); // function
// ...或者,更确切地说,是 constructor 方法
alert(User === User.prototype.constructor); // true
// 方法在 User.prototype 中,例如:
alert(User.prototype.sayHi); // sayHi 方法的代码
// 在原型中实际上有两个方法
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
Class 不仅仅是语法糖
// 用纯函数重写 class User
// 1. 创建构造器函数
function User(name) {
this.name = name;
}
// 函数的原型(prototype)默认具有 "constructor" 属性,
// 所以,我们不需要创建它
// 2. 将方法添加到原型
User.prototype.sayHi = function() {
alert(this.name);
};
// 用法:
let user = new User("John");
user.sayHi();
这个定义的结果与使用类得到的结果基本相同。因此,这确实是将 class
视为一种定义构造器及其原型方法的语法糖的理由。
尽管,它们之间存在着重大差异:
- 首先,通过
class
创建的函数具有特殊的内部属性标记[[IsClassConstructor]]: true
。因此,它与手动创建并不完全相同。
编程语言会在许多地方检查该属性。例如,与普通函数不同,必须使用 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
方法,我们通常不希望 class 方法出现。
- 类总是使用
use strict
。 在类构造中的所有代码都将自动进入严格模式。
类表达式
就像函数一样,类可以在另外一个表达式中被定义,被传递,被返回,被赋值等。
这是一个类表达式的例子:
let User = class {
sayHi() {
alert("Hello");
}
};
类似于命名函数表达式(Named Function Expressions),类表达式可能也应该有一个名字。
如果类表达式有名字,那么该名字仅在类内部可见:
// “命名类表达式(Named Class Expression)”
// (规范中没有这样的术语,但是它和命名函数表达式类似)
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass 这个名字仅在类内部可见
}
};
new User().sayHi(); // 正常运行,显示 MyClass 中定义的内容
alert(MyClass); // error,MyClass 在外部不可见
Class 字段
如果一个对象方法被传递到某处,或者在另一个上下文中被调用,则 this
将不再是对其对象的引用。
例如,此代码将显示 undefined
:
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
这个问题被称为“丢失 this
”。
目前有两种方法可以修正它:
传递一个包装函数,例如
setTimeout(() => button.click(), 1000)
;将方法绑定到对象,例如在 constructor 中。
类字段提供了另一种非常优雅的语法:
class Button {
constructor(value) {
this.value = value;
}
click = () => {alert(this.value);}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
类字段 click = () => {...}
是基于每一个对象被创建的,在这里对于每一个 Button
对象都有一个独立的方法,在内部都有一个指向此对象的 this
。我们可以把 button.click
传递到任何地方,而且 this
的值总是正确的。
在浏览器环境中,它对于进行事件监听尤为有用。
总结
基本的类语法:
class MyClass {
prop = value; // 属性
constructor(...) { // 构造器
// ...
}
method(...) {} // method
get something(...) {} // getter 方法
set something(...) {} // setter 方法
[Symbol.iterator]() {} // 有计算名称(computed name)的方法(此处为 symbol)
// ...
}
技术上来说,MyClass
是一个函数(我们提供作为 constructor
的那个),而 methods、getters 和 setters 都被写入了 MyClass.prototype
。