类的基本语法
ECMAScript 6 中的 Class(类)概念,实际上可以把它看作 ECMAScript 5 对象原型写法的语法糖。
基本写法
利用 ES5 实现对象原型:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' + this.x + ',' + this.y + ')';
};
var p = new Point(1, 2);
利用 ES6 实现对象原型:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ',' + this.y + ')';
}
}
表现形式
类有两种表现形式:声明式和表达式。
类的声明式
与 ES5 相同的是,类也是通过 new
关键字创建对象实例。
与 ES5 不同的是,在 ES6 中,我们将原型的实现写在了类中,但本质上还是一样的,都是需要新建一个类名,然后实现构造函数,再实现原型方法。
class Foo(){
// 构造函数
constructor(name = 'Tom'){
this.name = name
}
// 定义一个方法并且赋值给构造函数的原型
sayName(){
console.log(this.name)
}
}
const foo = new Foo()
foo.sayName()
// 'Tom'
类声明和函数声明的区别和特点:
- 函数声明可以被提升,类声明不能提升(与
let
声明类似) - 类声明中的代码自动强行运行在 <strong style="color: red">严格模式</strong> 下
- 只能通过
new
关键字来声明类,声明类会调用类的构造函数 - 每个类都有一个
[[construct]]
的方法,该方法就是构造函数 - 类的所有方法都定义在类的
prototype
属性上 - 类中的方法无需添加
function
关键字,只需直接添加到类中 - 方法间不需要添加逗号(
,
),加了会报错 - 类中的所有方法都是 <strong style="color: red">不可枚举的</strong>(Non-Enumerable),而自定义类型中,可以通过
Object.definedProperty()
手工指定不可枚举属性 - 在类的实例上调用方法,实质上就是调用原型上的方法
- 不能在类中修改类名
name
属性总是返回紧跟class
的关键字后的类名this
的指向默认指向 <strong style="color: red">类的实例</strong>
类的表达式
// 表达式
// 这个类的名称为 Baz2 而不是 Baz1
// Baz1 只有在类内部代码可用 指代当前类
const Baz2 = class Baz1 {
constructor() {}
getClassName() {
return Baz1.name;
}
};
类的组成
构造函数
构造函数(constructor
方法)是类的默认方法,通过 new
关键字生成对象实例时,自动调用该方法。若没有显式定义,一个空的构造函数会被默认添加。
⚠️ 注意:类必须使用
new
关键字调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new
关键字也能执行。
实例对象
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在 this
对象上),否则都是定义在 原型对象 上(即定义在 class
上)。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
const point = new Point(2, 3);
// 实例化类后,挂载在 this 上的属性会在挂载到实例对象上
point.toString();
// (2, 3)
point.hasOwnProperty('x');
// true
point.hasOwnProperty('y');
// true
// 而挂载在类上的方法则挂载在原型链上
point.hasOwnProperty('toString');
// false
point.__proto__.hasOwnProperty('toString');
// true
<br />
- 变量
x
和y
都是实例对象point
自身的属性(因为定义在this
变量上) - 而
toString
是原型对象的属性(因为定义在Point
类上)
与 ES5 一样,类的所有实例共享一个原型对象。这也意味着,可以通过实例的 __proto__
属性为类添加方法。
__proto__
并不是语言本身的特性,这是各大厂商具体实现时添加的私有属性,虽然目前很多现代浏览器的 JavaScript 引擎中都提供了这个私有属性,但依旧不建议在生产中使用该属性,避免对环境产生依赖。生产环境中,我们可以使用
Object.getPrototypeOf
方法来获取实例对象的原型,然后再来为原型添加方法/属性。
访问器属性
与 ES5 一样,在类的内部也可以使用 get
和 set
关键字,对某个属性设置 存值函数 和 取值函数,拦截该属性的存取行为。
尽管应该在类的构造函数中创建自己的属性,但是类也支持直接在原型上定义访问器属性。
class Student () {
constructor () {
// ...
}
get run () {
return 'get'
}
set run (value) {
console.log(`set: ${value}`)
}
}
let inst = new Student()
Student.run = 'abc'
// set: abc
Student.run
// get
属性表达式
类的属性名,可以采用表达式。
const methodName = 'getArea'
class Square(){
constructor(length){
// ...
}
[methodName](){
// ...
}
}
生成器方法
如果某个方法之前加上星号(*
),就表示该方法是一个生成器方法(Generator 函数)。
class Foo {
constructor(...args) {
this.args = args;
}
*[Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x);
}
// hello
// world
运行环境的指向
类的方法内部如果含有 this
,它默认指向 类的实例。
但是,如果将类方法内部的方法提取出来单独使用,this
会指向该方法 运行时所在的环境,因为找不到相对应的方法而导致报错。
因此,需要 在构造函数中绑定 this
,这样就不会找不到相对应的方法。
class Student {
constructor() {
this.sayName = this.sayName.bind(this);
}
}
另一种解决方法是使用 箭头函数。
class Car {
constructor() {
this.sayName = (name = 'BOT') => {
this.sayName(`My name is ${name}`);
};
}
}
还有一种解决方法是使用 Proxy
,获取方法的时候,自动绑定 this
。
参考资料: