Class的基本语法
简介
类的由来
在JS中,生成实例对象的传统方法是通过构造函数的方式,如下:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return "(" + this.x + ", " + this.y + ")";
};
var p = new Point(2, 3);
在ES6中,引入了Class(类)的概念,和Java的类的语法相似,上面的例子用ES6的class可以改写为:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return "(" + this.x + ", " + this.y + ")";
}
}
var p = new Point(2, 3);
在class中,有一个constructor()方法,这就是构造方法,this代表实例对象。
在定义方法时,前面不需要加上function,直接写方法名就行。另外,方法和方法之间不要加逗号。
alert(typeof Point); //function
alert(Point == Point.prototype.constructor); //true
上面代码说明,类的数据类型就是函数,类本身就指向构造函数。
构造函数的prototype属性,在类中依然存在。在类中的所有方法,实际上都定义在类的prototype属性上。例如,上面的Point类中的constructor()和toString()方法实际上都定义在Point.prototype上。
因此,调用实例上的方法就是调用原型上的方法。
alert(p.constructor === Point.prototype.constructor); //true
由于类的方法都定义在prototype对象上,因此类的新方法可以直接添加在prototype对象上。Object.assign()方法可以很方便地一次向类中添加多个方法。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
Object.assign(Point.prototype, {
toString() {
return "(" + this.x + ", " + this.y + ")";
},
toValue() {
...
}
});
在类中的所有方法都是不可枚举的,这一点与ES5不同。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return "(" + this.x + ", " + this.y + ")";
}
}
Object.keys(Point.prototype); //得到对象上可枚举的所有属性
//[]
Object.getOwnPropertyNames(Point.prototype); //得到对象上的所有属性
//["constructor","toString"]
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return "(" + this.x + ", " + this.y + ")";
};
Object.keys(Point.prototype);
//["toString"]
Object.getOwnPropertyNames(Point.prototype);
//["constructor","toString"]
在ES5中,Point的原型对象上的toString()方法是可枚举的。
constructor方法
constructor()方法是类的默认方法,通过new生成对象实例时,自动调用该方法。一个类必须有constructor()方法,如果没有,JS引擎会自动添加一个空的constructor()方法。
class Point {}
//等同于
class Point {
constructor() {}
}
constructor()方法默认返回实例对象(即this)。
下面的代码中constructor()方法返回一个新的对象,导致实例对象不是Point类的实例。
class Point {
constructor() {
return Object.create(null);
}
}
alert(new Point() instanceof Point); //false
类的实例
与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return "(" + this.x + ", " + this.y + ")";
}
}
var p = new Point(2, 3);
alert(p.hasOwnProperty("x")); //true
alert(p.hasOwnProperty("y")); //true
alert(p.hasOwnProperty("toString")); //false
//p.__proto__表示实例对象p的原型对象
alert(p.__proto__.hasOwnProperty("toString")); //true
与 ES5 一样,类的所有实例共享一个原型对象。
var p1 = new Point(2, 3);
var p2 = new Point(2, 3);
alert(Object.getPrototypeOf(p1) === Object.getPrototypeOf(p2)); //true
取值函数(getter)和存值函数(setter)
与ES5一样,在类的内部可以对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass {
constructor() {}
get prop() {
return "getter";
}
set prop(value) {
alert("setter: " + value);
}
}
var mc = new MyClass();
alert(mc.prop); //"getter"
mc.prop = 123; //"setter: 123"
set和get方法都是设置在属性的描述符(Descriptor)对象上,这与ES5相同。
var descriptor = Object.getOwnPropertyDescriptor(MyClass.prototype, "prop");
alert("get" in descriptor); //true
alert("set" in descriptor); //true
属性表达式
类的属性名,可以采用表达式。
var methodName = "toString";
class Point {
[methodName]() {}
}
Class表达式
与函数一样,类也可以使用表达式的形式定义
const myClass = class Me {
getClassName() {
return Me.name;
}
};
let mc = new myClass();
alert(mc.getClassName()); //Me
Me.name; //UncaughtReferenceError: Me is not defined
如果类的内部没有用到的话,还可以省略Me。
const myClass = class { ... };
采用class表达式,还可以写出立即执行的class。
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
alert(this.name);
}
}("Tom");
person.sayName(); //"Tom"
注意点
严格模式
在类和模块的内部,默认就是严格模式。
不存在提升
类必须先定义,再使用。
name属性
name属性总是返回class后面的类名。
class Point {}
alert()Point.name); //Point
Generator方法
如果某个方法前加上星号(*),就表示该方法是一个Generator函数。
this的指向
???
静态方法
所有在类中定义的方法,实际上都定义在原型对象上,都会被实例继承。在方法前加上static关键字,表示该方法不会被实例继承,只能通过类来调用,这就称为“静态方法”。
class MyClass {
static sayHello() {
alert("hello");
}
}
MyClass.sayHello(); //"hello"
let mc = new MyClass();
mc.sayHello(); //UncaughtTypeError: mc.sayHello is not a function
注意:如果静态方法中包含this,这个this指向的是类,而不是实例。
静态方法和非静态方法还可以重名。
class MyClass {
static sayHello() {
this.sayHi();
}
static sayHi() {
alert("Hello");
}
sayHell() {
alert("World");
}
}
MyClass.sayHello(); //"Hello"
父类的静态方法可以被子类继承。
class Foo {
static sayHello() {
alert("Hello");
}
}
class Bar extends Foo {
}
Bar.sayHello(); //"Hello"
静态方法也可以从super对象上调用。
???
静态属性
静态属性指的是Class本身的属性,而不是定义在实例对象上的属性。
class Foo {}
Foo.prop = 1;
alert(Foo.prop); // 1
new.target属性
该属性一般用在构造函数中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined。
function Person(name) {
if(new.target === Person) {
this.name = name;
} else {
throw new Error("必须使用new命令生成实例");
}
}
var person = new Person("Tom"); //正确
var notAPerson = Person.call(person, "Tom"); //报错
Class内部调用new.target,返回当前Class。
class Person {
constructor(name) {
alert(new.target === Person);
this.name = name;
}
}
var person = new Person("Tom"); //true