生成实例对象的传统方法是通过构造函数
// 先定义一个函数,强行叫它构造函数
function Point(x, y) {
this.x = x;
this.y = y;
}
// 构造函数的方法都定义在构造函数的原型上
Point.prototype.toString = function () {
return '(' + this.x + ', ' + this.y + ')';
};
// 使用new的方式得到一个实例对象
var p = new Point(1, 2);
定义类
ES6
引入了class
(类),让javascript
的面向对象编程变得更加容易清晰和容易理解。类只是基于原型的面向对象模式的语法糖。
类实际上是个“特殊的函数”,就像你能够定义的函数表达式和函数声明一样,类语法有两个组成部分:类声明和类表达式。
- 类声明
类声明是定义类的一种方式,使用关键字class
,后面跟上类名,然后就是一对大括号。把类需要定义的方法放在大括号中。
//定义类
class Point {
constructor(x, y) { // 定义构造方法
// this关键字代表实例对象
this.x = x;
this.y = y;
}
// 定义在类中的方法不需要添加 function
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
//定义类,可以省略 constructor
class P {
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
- 类表达式
类表达式是定义类的另一种形式,类似于函数表达式,把一个函数作为值赋给变量。可以把定义的类赋值给一个变量,这时候变量就为类名。class
关键字之后的类名可有可无,如果存在,则只能在类内部使用。
- 定义类 class后面有类名:
const People = class StdInfo {
constructor(){
console.log(StdInfo); //可以打印出值,是一个函数
}
}
new People();
new StdInfo(); //报错,StdInfo is not defined;
- 定义类 class后面没有类名:
const People = class {
constructor(){
}
}
new People();
- 立即执行的类:
let person = new class {
constructor(name) {
this.name = name;
}
sayName() {
console.log(this.name);
}
}('张三');
person.sayName(); // "张三"
不存在变量提升
定义类不存在变量提升,只能先定义类后使用,跟函数声明有区别的(函数声明会被提升)。
//-----函数声明-------
//定义前可以先使用,因为函数声明提升的缘故,调用合法。
func();
function func(){}
//-----定义类-------
new Foo(); // ReferenceError
class Foo {}
类与构造函数
-
ES6
的类,完全可以看作构造函数的另一种写法。类的数据类型就是函数,类本身就指向构造函数。
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
- 构造函数的
prototype
属性,在ES6
的“类”上面继续存在;类的所有方法都定义在类的prototype
属性上面,Object.assign
方法可以一次向类添加多个方法。
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
constructor
方法
-
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
- 一个类中只能有一个
constructor
函数,定义多个会报错。 -
constructor
默认返回该对象实例(即this
),也可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
- 在一个构造方法中可以使用
super
关键字来调用一个父类的构造方法。
class A {}
class B extends A {
constructor() {
super();
}
}
类的实例对象
使用new
命令生成类的实例对象。类必须使用new
调用,否则会报错。
class Point {
// ...
}
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);
类的所有实例共享一个原型对象。
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__ === p2.__proto__ //true
Class
的静态方法
- 类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上
static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
class Foo {
static classMethod() {
return 'hello';
}
}
Foo.classMethod() // 'hello'
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
- 如果静态方法包含
this
关键字,这个this
指的是类,而不是实例。
class Foo {
static bar () {
this.baz();
}
// 静态方法可以与非静态方法重名
static baz () {
console.log('hello');
}
baz () {
console.log('world');
}
}
Foo.bar() // hello
- 父类的静态方法,可以被子类继承。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
}
Bar.classMethod() // 'hello'
- 静态方法也是可以从
super
对象上调用的。
class Foo {
static classMethod() {
return 'hello';
}
}
class Bar extends Foo {
static classMethod() {
return super.classMethod() + ', too';
}
}
Bar.classMethod() // "hello, too"
new.target
属性
-
new
是从构造函数生成实例对象的命令。在构造函数之中,new.target
属性返回new
命令作用于的那个构造函数。如果构造函数不是通过new
命令调用的,new.target
会返回undefined
,因此这个属性可以用来确定构造函数是怎么调用的。
function Person(name) {
if (new.target !== undefined) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
// 另一种写法
function Person(name) {
if (new.target === Person) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三'); // 报错
-
Class
内部调用new.target
,返回当前Class
。
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
this.length = length;
this.width = width;
}
}
var obj = new Rectangle(3, 4); // 输出 true
- 子类继承父类时,
new.target
会返回子类。
class Rectangle {
constructor(length, width) {
console.log(new.target === Rectangle);
// ...
}
}
class Square extends Rectangle {
constructor(length) {
super(length, length);
}
}
var obj = new Square(3); // 输出 false
class的继承
-
Class
可以通过extends
关键字实现继承,这比ES5
的通过修改原型链实现继承,要清晰和方便很多。使用继承的方式,子类就拥有了父类的方法。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return this.color + ' ' + super.toString(); // 调用父类的toString()
}
}
- 如果子类中有
constructor
构造函数,则必须使用调用super
。如果不调用super
方法,子类就得不到this
对象。
class Point { /* ... */ }
class ColorPoint extends Point {
constructor() {
}
}
let cp = new ColorPoint(); // ReferenceError
- 在子类的构造函数中,只有调用
super
之后,才可以使用this
关键字,否则会报错。
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; // 正确
}
}
super
关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
-
super
作为函数调用时,代表父类的构造函数。ES6
要求,子类的构造函数必须执行一次super
函数。
class A {
constructor() {
console.log(new.target.name);
}
}
class B extends A {
constructor() {
super();
}
}
new A() // A
new B() // B
作为函数时,super()
只能用在子类的构造函数之中,用在其他地方就会报错。
-
super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();
上面代码中,子类B当中的super.p()
,就是将super
当作一个对象使用。这时,super
在普通方法之中,指向A.prototype
,所以super.p()
就相当于A.prototype.p()
。
由于super
指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super
调用的。
参考文章
- class的基本语法-阮一峰
- class的继承-阮一峰
- 类-MDN