class与构造函数
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
//类的写法
class Point{
getName(){
return 'lili';
}
}
//构造函数的写法
function Point2() {}
Point2.prototype.getName = function () {
return 'lili';
}
其实class也是函数,类里的方法也是定义在prototype上。类的方法都是不可枚举的,通过Object.keys不能获得,但是构造函数原型上的方法可以获得。
//类
typeof Point; //'function'
Point === Point.prototype.constructor //true
Object.getOwnPropertyNames(Point.prototype)
//[ 'constructor', 'getName' ]
Object.keys(Point.prototype)
//[]
//构造函数
Object.getOwnPropertyNames(Point2.prototype);//包括自身的,可枚举和不可枚举的
//[ 'constructor', 'getName' ]
Object.keys(Point2.prototype);
[ 'getName' ]
constructor方法
类中可以显示定义constructor方法,如果没有定义,则隐式创建一个空的constructor方法。
class point {}
//等同于
class Point{
constructor(){
}
}
constructor方法,默认返回值是类的实例对象(this)。可以自定义返回值 ,如果这样做改变this值了,最好不要这样做。
class Point{
constructor(x, y){
this.x = x;
this.y = y;
return {}; //不推荐用法
}
}
let point = new Point(1, 2); //{}
类实例
注意:生成实例,类必须使用new关键字定义,否则报错。构造函数不是必须使用new
class Point{}
let point = Point.call({}); //报错
function Point2(x) {
this.x = x;
return this;
}
let point2 = Point2.call({}, 1);//ok {x: 1}
类的所有实例共享一个原型对象
class Point {
y = 10; //定义属性方式
constructor(x){
this.x = x; //定义属性方式
}
setZ(z){
this.z = z; //定义属性
}
getX(){
return this.x;
}
}
let point = new Point(1);
let point2 = new Point(2);
point.hasOwnProperty('x'); //true
point.hasOwnProperty('getX'); //false
point.__proto__ === point2.__proto__ === Point.prototype //true
Object.getOwnPropertyNames(Point.prototype)
//[ 'constructor', 'getX' ]
this是实例对象,定义在this上的属于实例的自身的属性,如x,y,z。getX方法是定义在原型对象上的,所有的实例共享的。
静态属性和静态方法
静态属性现在还没有
静态方法
- 属于类,不被实例继承,直接通过类来调用。可以被子类继承
- 内部的this,执行Point本身,而不是实例
- 可以和非静态方法重名
class Point {
static getX() {
this.getY();
}
static getY(){
console.log('y');
}
getY(){
console.log('yy');
}
}
let point = new Point();
Point.getX();
//'y'
私有属性和私有方法
私有属性和私有方法还没有实现。只能约定下划线打头方法为私有的。或利用Symbol值的唯一性生成
const bar = Symbol('bar');
class Point {
[bar](){
console.log('x')
}
}
let point = new Point()
console.log(Reflect.ownKeys(Point.prototype));
//[ 'constructor', Symbol(bar) ]
一般情况下外界取不到bar的值,所以成了私有方法。但是也不是绝对不行,Reflect.ownKeys()依然可以拿到它们。
new.target
new是从构造函数生成实例对象的命令。ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令或Reflect.construct()调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。
class Point {
constructor() {
console.log(new.target === Point); //true
}
}
let point = new Point();
只能在构造函数constructor中使用new.target。其他地方使用会报错
function Point2(name) {
if (new.target === Point2) {
this.name = name;
} else {
throw new Error('必须使用 new 命令生成实例');
}
}
let point3 = new Point2('sha');
let point2 = Point2.call({}, 'li'); //抛出异常
利用new.target,限定构造函数创建实例,只能使用new操作符。
利用这个特点,可以写出不能独立使用、必须继承后才能使用的类
class Shape {
constructor() {
if (new.target === Shape) {
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape {
constructor(length, width) {
super();
// ...
}
}
var x = new Shape(); // 报错
var y = new Rectangle(3, 4); // 正确
继承
继承就是子类继承父类的属性和方法。注意以下几点:
- 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错
- 子类必须在constructor方法中调用super方法,否则新建实例时会报错。
- 父类的静态方法,也会被子类继承
class Point {
static hello() { //静态方法会被子类继承
console.log('hello world');
}
constructor(x, y) {
this.x = x;
this.y = y;
}
getX(){
return this.x;
}
}
class ColorPoint extends Point{
constructor(x, y, color){
this.color = color; //错误
super(x, y);
this.color = color; //正确
}
getX(){
console.log(this); //ColorPoint { x: 10, y: 1, color: 'red' }
return this.color + ' ' + super.getX();
}
}
let cp = new ColorPoint(10, 1, 'red');
console.log(cp.getX()); //red 10
ColorPoint.hello(); //hello world
Object.getPrototypeOf(ColorPoint) === Point //true
cp instanceof Point //true
cp instanceof ColorPoint //true
ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6 的继承机制完全不同,实质是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。如果不调用super方法,子类就得不到this对象。
super关键字
super这个关键字,既可以当作函数使用,也可以当作对象使用。
-
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。并且super函数只能用在子类的构造函数之中,用在其他地方就会报错。
-
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
在子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例。由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的。
在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
class A {
constructor() {
console.log(new.target.name);
this.x = 10;
}
getX(){
return this.x;
}
}
class B extends A {
constructor() {
super();
this.x = 20;
}
}
let a = new A() // A
let b = new B() // B
b.getX(); //20
B中super()在这里相当于A.prototype.constructor.call(this)。new.target指向当前正在执行的函数
class Parent {
static myMethod(msg) {
console.log('static', msg);
}
myMethod(msg) {
console.log('instance', msg);
}
}
class Child extends Parent {
static myMethod(msg) {
super.myMethod(msg);
}
myMethod(msg) {
super.myMethod(msg);
}
}
Child.myMethod(1); // static 1 静态方法
var child = new Child();
child.myMethod(2); // instance 2 普通方法
类的 prototype 属性和__proto__属性
-
子类的__proto__属性,表示构造函数的继承,总是指向父类。
-
子类prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A {
}
class B extends A {
}
B.__proto__ === A // true 属性继承
B.prototype.__proto__ === A.prototype // true 方法继承
注:本文是读阮一峰老师《ECMAScript 6 入门》的学习笔记