ES6 的class
可以看作是一个语法糖,这种写法只是让对象原型的写法更加清晰、更像面向对象编程的语法。
比如这是一个构造函数生成实例对象的例子,
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);
console.log(p.toString()) // (1, 2)
上面的代码用类来改写是这样的,
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var p = new Point(1, 2);
console.log(p.toString()) // (1, 2)
所以类完全可以看作构造函数的另一种写法,
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
类的数据类型就是函数,类本身就指向构造函数。
构造函数的
prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
以下代码表明类的内部所有定义的方法,都是不可枚举的(non-enumerable)
class Point {
constructor(x, y) {
// ...
}
toString() {
// ...
}
}
Object.keys(Point.prototype) // []
Object.getOwnPropertyNames(Point.prototype) // ["constructor","toString"]
而直接定义在原型上的都是可枚举的
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
Point.prototype.toString1 = function() {
// ...
};
console.log(Object.keys(Point.prototype)) // [ 'toString1' ]
console.log(Object.getOwnPropertyNames(Point.prototype)) // [ 'constructor', 'toString', 'toString1' ]
构造函数的继承需要手动处理原型链,并确保构造函数和原型的正确指向,以下示例可看出
function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(this.name + ' is eating.');
};
function Dog(name, breed) {
Animal.call(this, name); // 调用父类构造函数
this.breed = breed;
}
Dog.prototype = Object.create(Animal.prototype); // 设置子类的原型对象
Dog.prototype.constructor = Dog; // 重设构造函数指向自身
Dog.prototype.bark = function() {
console.log(this.name + ' is barking.');
};
const dog = new Dog('Buddy', 'Golden Retriever');
dog.eat(); // 输出: Buddy is eating.
dog.bark(); // 输出: Buddy is barking.
类的继承示例
class Animal {
constructor(name) {
this.name = name;
}
eat() {
console.log(`${this.name} is eating.`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
bark() {
console.log(`${this.name} is barking.`);
}
}
const dog = new Dog('Buddy', 'Golden Retriever');
dog.eat(); // Buddy is eating.
dog.bark(); // Buddy is barking.
需要注意的是,子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。
如果子类没有定义constructor
方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor
方法。
class Dog extends Animal {
}
// 等同于
class Dog extends Animal {
constructor(...args) {
super(...args);
}
}
另一个需要注意的地方是,在子类的构造函数中,只有调用super
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super
方法才能调用父类实例。
class Animal {
constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name, color) {
this.color = color; // ReferenceError
super(name);
this.color = color; // 正确
}
}
this
指向调用函数的对象this
指向实例对象class Foo {
static classMethod() {
return 'hello';
}}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.staticMethod = function() {
console.log("This is a static method.");
};
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
static staticMethod() {
console.log("This is a static method.");
}
}
class Person {
constructor(name, age) {
this._name = name;
this._age = age;
}
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
}
var person = new Person("Tom", 20);
console.log(person.name); // 输出 "Tom"
person.name = "Jerry";
console.log(person.name); // 输出 "Jerry"
在上述例子中,通过getter和setter方法定义了name访问器属性,可以通过person.name进行属性的访问和修改。
instanceof
运算符来判断对象的类型
instanceof
运算符通过检查对象的原型链来确定对象是否是某个构造函数的实例。它会逐级向上查找对象的__proto__
(或prototype
)属性,直到找到与指定构造函数的prototype
属性相等的对象或到达原型链的末尾。如果找到了相等的对象,则返回true
,否则返回false
。
function Person() {}
var person = new Person();
console.log(person instanceof Person); // 输出 true
typeof
运算符来判断类的类型class Person {}
var person = new Person();
console.log(typeof person); // 输出 "object"
console.log(typeof Person); // 输出 "function"
在上述例子中,typeof person
返回object
,typeof Person
返回function
,可以看出,在ES6中类的类型也是object
,类本身的类型是function
。