简介
js中,生成实例对象的传统方法是通过构造函数。
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);
基本上,ES6 的class
可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class
写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用 ES6 的class
改写,就是下面这样。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
typeof Point // "function"
Point === Point.prototype.constructor // true
类的数据类型就是函数,类本身就指向构造函数。
使用的时候,也是直接对类使用new
命令,跟构造函数的用法完全一致。
class Bar {
doStuff() {
console.log('stuff');
}
}
const b = new Bar();
b.doStuff() // "stuff"
构造函数的prototype
属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype
属性上面。
class Point {
constructor() {
// ...
}
toString() {
// ...
}
toValue() {
// ...
}
}
// 等同于
Point.prototype = {
constructor() {},
toString() {},
toValue() {},
};
因此,在类的实例上面调用方法,其实就是调用原型上的方法。
class B {}
const b = new B();
b.constructor === B.prototype.constructor // true
b
是B
类的实例,它的constructor()
方法就是B
类原型的constructor()
方法。
prototype
对象的constructor()
属性,直接指向“类”的本身,这与 ES5 的行为是一致的。
由于类的方法都定义在prototype
对象上面,所以类的新方法可以添加在prototype
对象上面。Object.assign()
方法可以很方便地一次向类添加多个方法。
class Point {
constructor(){
// ...
}
}
Object.assign(Point.prototype, {
toString(){},
toValue(){}
});
类的内部所有定义的方法,都是不可枚举的(non-enumerable)。
class Point {
constructor(x, y) {
// ...
}
toString() {
// ...
}
}
Object.keys(Point.prototype) //可以获取自己(不含原生链)的可枚举属性
// []
Object.getOwnPropertyNames(Point.prototype)//可以获取所有属性 包括不可枚举
// ["constructor","toString"]
constructor 方法
constructor()
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。如果没有显式定义,一个空的constructor()
方法会被默认添加。
constructor()
方法默认返回实例对象(即this
),完全可以指定返回另外一个对象。
class Foo {
constructor() {
return Object.create(null);
}
}
new Foo() instanceof Foo
// false
上面代码中,constructor()
函数返回一个全新的对象,结果导致实例对象不是Foo
类的实例。
类必须使用new
调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用new
也可以执行。
class Foo {
constructor() {
return Object.create(null);
}
}
Foo()
// TypeError: Class constructor Foo cannot be invoked without 'new'
类的实例
与 ES5 一样,实例的属性除非显式定义在其本身(即定义在this
对象上),否则都是定义在原型上(即定义在class
上)。
//定义类
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString') // true
上面代码中,x
和y
都是实例对象point
自身的属性(因为定义在this
对象上),所以hasOwnProperty()
方法返回true
,而toString()
是原型对象的属性(因为定义在Point
类上),所以hasOwnProperty()
方法返回false
。
var p1 = new Point(2,3);
var p2 = new Point(3,2);
p1.__proto__.printName = function () { return 'Oops' };
p1.printName() // "Oops"
p2.printName() // "Oops"
var p3 = new Point(4,2);
p3.printName() // "Oops"
p1
的原型上添加了一个printName()
方法,由于p1
的原型就是p2
的原型,因此p2
也可以调用这个方法。而且,此后新建的实例p3
也可以调用这个方法。这意味着,使用实例的__proto__
属性改写原型
取值函数(getter)和存值函数(setter)
与 ES5 一样,在“类”的内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
}
}
let inst = new MyClass();
inst.prop = 123;
// setter: 123
inst.prop
// 'getter'
注意点
(1)严格模式
类和模块的内部,默认就是严格模式,所以不需要使用use strict
指定运行模式。
(2)不存在提升
类不存在变量提升(hoist),这一点与 ES5 完全不同。
new Foo(); // ReferenceError
class Foo {}
(3)name 属性
由于本质上,ES6 的类只是 ES5 的构造函数的一层包装,所以函数的许多特性都被Class
继承,包括name
属性。
class Point {}
Point.name // "Point"
name
属性总是返回紧跟在class
关键字后面的类名。
(4)Generator 方法
如果某个方法之前加上星号(*
),就表示该方法是一个 Generator 函数。
class Foo {
constructor(...args) {
this.args = args;
}
* [Symbol.iterator]() {
for (let arg of this.args) {
yield arg;
}
}
}
for (let x of new Foo('hello', 'world')) {
console.log(x);
}
// hello
// wo
Symbol.iterator
方法返回一个Foo
类的默认遍历器,for...of
循环会自动调用这个遍历器。
(5)this 的指向
类的方法内部如果含有this
,它默认指向类的实例。
静态方法
如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用
如果静态方法包含this
关键字,这个this
指的是类,而不是实例。
class Foo {
static classMethod() {
return 'hello';
}
static bar() {
this.baz();
}
static baz() {
console.log('hello');
}
baz() {
console.log('world');
}
}
Foo.classMethod() // 'hello'
Foo.bar() // hello
var foo = new Foo();
foo.classMethod()
// TypeError: foo.classMethod is not a function
父类的静态方法,可以被子类继承
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"