类声明
Class由来
Class写法更接近传统的面向对象语言的语法,它的绝大部分功能,ES5都可以实现,只是为了在写法上更加清晰、更像面向对象语言的语法。其思想和 ES5 是一致的。
Class语法
// es5
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return '(' + this.x + ',' + this.y + ')';
}
const p = new Point(1,2);
等同于
//es6
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ',' + this.y + ')';
}
}
const p = new Point(1,2); // 生成类的实例的写法,与 ES5 完全一样,也是使用`new`命令
其中 constructor
方法是类的构造函数,是一个默认方法。通过 new
命令创建对象实例时,会自动调用该方法。一个类必须有 constructor
方法,如果没有显式定义,会自动添加一个默认的consructor方法。所以即使你没有添加构造函数,也是会有一个默认的构造函数的。一般 constructor
方法返回实例对象 this
,但是也可以指定 constructor
方法返回一个全新的对象,让返回的实例对象不是该类的实例。
constructor
方法中的this指向实例对象p,参数x,y的值是创建实例时,通过new Point(1,2)传递进来的。x,y相当于是给p对象的x,y属性设置初始值。创建实例时,可以不传x,y参数,默认为undefined。
// 不设置初始值
const p = new Point(); // p:{x: undefined, y: undefined}
// 设置初始值
const p = new Point(1,2); // p:{x: 1, y: 2}
在Class中定义的所有方法都定义在类的prototype
属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
与 ES5 一样,类的所有实例共享一个原型对象。也共享原型上的属性。
静态属性和静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
静态属性指的是 Class 本身的属性。同理,如果在一个属性前,加上static
关键字,就代表该属性是 Class 本身的属性。该属性也不会被实例继承。
class Foo {
static myStaticProp = 42;
static classMethod() {
return 'hello';
}
}
Foo.myStaticProp // 42
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.myStaticProp;
// undefined
foo.classMethod()
// TypeError: foo.classMethod is not a function
注意,如果静态方法包含this
关键字,这个this
指的是类,而不是实例。所以,静态方法中this
调用的只能是静态属性和静态方法。
class Foo {
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.bar() // hello
上面代码中,静态方法bar
调用了this.baz
,这里的this
指的是Foo
类,而不是Foo
的实例,等同于调用Foo.baz
。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。
ES5函数声明与ES6类声明的区别
1.枚举性
ES6类内部定义的方式,是不可枚举的;
Object.keys(Point.prototype)
// []
ES5 的写法,toString()
方法就是可枚举的。
Object.keys(Point.prototype)
// ["toString"]
2.变量提升
函数声明和类声明之间的一个重要区别在于,函数声明会提升,类声明不会。你首先需要声明你的类,然后再访问它,否则类似以下的代码将抛出ReferenceError
:
let p = new Rectangle(); // ReferenceError
class Rectangle {}
3.严格模式
使用类声明,在其内部采用的是严格模式,而函数声明不是。
在函数声明中,未声明变量count不会报错:
function Foo () {
count = 1
}
const foo = new Foo()
console.log(foo)
使用class声明的方式
class Bar {
constructor () {
count = 2 // Uncaught ReferenceError: count is not defined
}
}
const bar = new Bar()
console.log(bar)
4.new
创建实例
类声明必须通过new
命令创建实例,而函数声明可以不使用new
命令创建实例
function Foo(){
console.log('函数声明')
}
const foo = Foo();
class Bar{
constructor(){
console.log('类声明')
}
}
const bar = Bar();
// Uncaught TypeError: Class constructor Bar cannot be invoked without 'new'
5.重写类名
类声明class内部无法重写类名,而函数声明可以重写类名。
function Foo(){
Foo = 'abc'
}
new Foo()
// 重写类名报错
class Bar{
constructor(){
Bar = 'def' // Uncaught TypeError: Assignment to constant variable.
}
}
new Bar()
类继承
Class继承语法
Class 可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Point {
}
class ColorPoint extends Point {
}
Super关键字
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
另一个需要注意的地方是,在子类的构造函数中,只有调用super
之后,才可以使用this
关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有super
方法才能调用父类实例。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
父类的静态方法,也会被子类继承。
ES5与ES6继承的区别
ES5 的继承,实质是先创造子类的实例对象this
,然后再将父类的方法添加到this
上面(Parent.apply(this)
)。
ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
。
参考:https://es6.ruanyifeng.com/?search=%E7%BB%A7%E6%89%BF&x=0&y=0#docs/class