目录
本节要总结 ES6 中的类、类的继承的有关内容。
# 类
ES5 中没有类的概念,只能通过构造函数来初始化对象实例。ES6 中可以通过class
关键字来定义类。
// ES5实现
function Person(name) {
this.name = name;
}
Person.prototype.hello = function() {
console.log('Hi', this.name);
};
var person1 = new Person('Peter');
person1.hello(); // Hi Peter
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
说明:
先创建一个构造函数
Person
,然后定义一个方法 hello,并赋值给这个构造函数的原型。这样构造函数的所有实例都将共享这个方法。再通过new
操作符得到一个实例 person1,该实例可以调用 hello 方法。并且通过 instanceof 可以看出:该实例person1
是构造函数 Person 的实例、也是对象 Object 的实例。
// ES6实现
// 通过class关键字声明一个类,上述代码的ES6等价版本:
class Person {
constructor(name) {
this.name = name;
}
hello() {
console.log('Hi', this.name);
}
}
person1 = new Person('Peter');
person1.hello(); // Hi Peter
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
说明:
在定义 Person 类中,先定义 constructor 方法,这个方法等价于定义 Person 构造函数。然后在类中直接定义 hello 方法,这等价于往构造函数的原型上添加方法,即 hello 方法是添加到 Person.prototype 属性上。
class Person {
// 等价于定义Person构造函数
constructor(name) {
this.name = name;
}
// 等价于Person.prototype.hello
hello() {
console.log('Hi', this.name);
}
}
person1 = new Person('Peter');
person1.hello(); // Hi Peter
几点要注意的地方:
- (1)ES6 中的类是语法糖,本质还是函数,一个具有构造函数方法行为的函数; 例如上述例子中:
console.log(typeof Person); // function
- (2)类中通过
constructor
方法来定义构造函数,在用 new 关键字初始化实例时自动执行。并且,一个类必须显示定义constructor
方法,如果没有,则会默认添加一个空的constructor
方法。
class Person {
// 没有定义constructor方法
}
// 等价于
class Person {
constructor() {}
}
- (3)类声明不能被提升,就像 let 声明不能被提升;
// Uncaught ReferenceError: Person is not defined
let person1 = new Person('Peter');
class Person {
constructor(name) {
this.name = name;
}
hello() {
console.log('Hi', this.name);
}
}
- (4)类中定义方法时,不需要
function
关键字,写法是:方法名(){xxx}
这种形式;并且各方法之间不要逗号分隔; - (5)类的名称只在类中为常量,因此在定义类的方法中是不能修改类的名称的,不过可以在声明类之后修改; 内部修改类名会报错:
class Person {
constructor() {
Person = 'OterPerson';
}
}
let person2 = new Person(); // Uncaught TypeError: Assignment to constant variable.
- (6)类中的所有方法都是添加到类的原型上,即类的
prototype
属性上,并且都是不可枚举的,可以通过Object.keys()
查看; 验证:
class Person {
constructor(name) {
this.name = name;
}
hello() {
console.log('Hi', this.name);
}
}
console.log(Person.prototype);
console.log(Object.keys(Person.prototype)); // []
console.log(Object.getOwnPropertyNames(Person.prototype)); // ["constructor", "hello"]
补充:
Object.keys(obj)
返回给定对象obj的所有可枚举属性的字符串数组
Object.getOwnPropertyNames(obj)
返回给定对象obj的所有属性的字符串数组
- (7)调用类的构造函数,要通过 new 调用,不用 new 则会报错 验证:
class Person {
constructor(name) {
this.name = name;
}
hello() {
console.log('Hi', this.name);
}
}
let person1 = Person('Peter'); // Uncaught TypeError: Class constructor Person cannot be invoked without 'new'
- (8)类的 name 属性,就是 class 关键字后面的标识符。
class Person {
constructor(name) {
this.name = name;
}
hello() {
console.log('Hi', this.name);
}
}
console.log(Person.name); // Person
- (9)new.target
在类的构造函数中使用
new.target
,一般情况下,new.target
等于类的构造函数 验证:
class Person {
constructor(name) {
this.name = name;
console.log(new.target === Person);
}
hello() {
console.log('Hi', this.name);
}
}
let person1 = new Person('Peter'); // true
# 继承
引用:
ES5 的继承,实质是先创造子类的实例对象
this
,然后再将父类的方法添加到this
上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用 super 方法),然后再用子类的构造函数修改this
。
// ES5实现继承
function Parent(value) {
this.value = value;
}
Parent.prototype.printValue = function() {
return this.value;
};
function Child(value) {
Parent.call(this, value);
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true,
},
});
var child1 = new Child('Peter');
console.log(child1);
console.log(child1.printValue()); // Peter
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Parent); // true
说明:
要 Child 继承 Parent,就用一个从
Parent.prototype
创建的新对象重写 Child 的原型child.prototype
,并且调用Parent.call()
方法,child1:
ES6 实现继承
使用extends
关键字实现继承,通过调用super()
可以访问基类的构造函数
上述代码的 ES6 等价版本:
class Parent {
constructor(value) {
this.value = value;
}
printValue() {
return this.value;
}
}
class Child extends Parent {
constructor(value) {
// 等价于 Parent.call(this,value)
super(value);
this.value = value;
}
}
let child1 = new Child('Peter');
console.log(child1.printValue()); // Peter
console.log(child1 instanceof Child); // true
console.log(child1 instanceof Child); // true
几点要注意的地方:
- (1)使用
extends
关键字即可实现继承,更符合面向对象的编程语言的写法 - (2)关于
super()
: –只可以在子类的构造函数中使用 super,其他地方使用会报错; 验证,在非子类中使用super()
,会报错:
// Uncaught SyntaxError: 'super' keyword unexpected here
class Parent {
constructor(value) {
super(value);
this.value = value;
}
printValue() {
return this.value;
}
}
–在子类中指定了构造函数则必须调用super()
,否则会报错。因为 super 负责初始化this
,如果在调用 super()前使用this
,则会报错;
验证:
// Uncaught ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
class Parent {
constructor(value) {
this.value = value;
}
printValue() {
return this.value;
}
}
class Child extends Parent {
constructor(value) {
this.value = value;
}
}
let child1 = new Child('Peter');
–如果子类中没定义构造函数,则创建子类实例时,会自动调用super()
,并且传入所有参数。
class Child extends Parent {
// 没有构造函数
}
// 等价于
class Child extends Parent {
constructor(...args) {
super(...args);
}
}
- (3)子类也会继承父类的静态方法(static)
- (4)关于
new.target
如果是子类继承了父类,则父类中的new.target
是子类的构造函数: 验证:
// 定义父类
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle); // false
console.log(new.target === Square); // true
this.length = length;
this.width = width;
}
}
// 定义子类
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
let square1 = new Square(5);
# 小结
本文主要通过对比 ES5 的实现方法,分别总结了 ES6 中类和类的继承的有关基本知识。如有问题,欢迎指正。