new.target 属性
•new 是从构造函数生成实例对象的命令,ES6 为 new 命令引入一个 new.target 属性,该属性一版用在构造函数中,返回 new 命令作用域的那个构造函数。如果构造函数不是通过 new 或者 Reflect.construct
调用, 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, width) { super(length, width); } } var obj = new Square(3); // 输出 false
•利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。
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); // 正确
1.class 的继承
简介
•class 可通过 extends 关键字来实现继承,这比ES5的通过修改原型链实现继承方便很多
class Point { } class ColorPoint extends Point { } eg: 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 方法和 toString 方法之中,都出现了 super 关键字,在这里表示父类的构造函
数,用来新建父类的 this 对象。
•子类必须在 constructor 方法中调用 super 方法,否则新建实例时就会报错,这是因为子类自己的 this
对象, 必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后对其进行加工,加上子类自己的实例属性和方法。如果不调用 super 方法,子类就得不到 this 对象。
class Point { /* ... */ } class ColorPoint extends Point { constructor() { } } let cp = new ColorPoint(); // ReferenceError
// 这段代码中,因为在子类集成父类的时候没有调用super() 方法,在新建实例时出现报错。
•ES5 继承,实质上是先创造子类的实例对象的 this 对象,然后再将父类的方法添加到 this 上面,
Parent.apply(this)。
•ES6 继承机制完全不同,实质是将父类的实例对象的属性和方法,加到 this 上面(所以必需先调用 super 方法),
然后用子类的构造函数修改 this。
•如果子类没有定义 constructor 方法,这个方法都会被默认添加,也就是说,不管有没有显示定义,任何一个
子类都有 constructor 方法。
class ColorPoint extends Point { } // 等同于 class ColorPoint extends Point { constructor(...args) { super(...args); } }
•另一个需要注意的点:在子类的构造函数中,只有调用 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; // 正确 } }
•父类的静态方法,也会被子类继承
class A { static hello() { console.log('hello world'); } } class B extends A { } B.hello() // hello world
Object.getPrototypeOf()
•Object.getPrototypeOf() 方法可以从子类上获取父类。
Object.getPrototypeOf(ColorPoint) ===Point
// true
•使用这个方法,可以判断一个类是否继承了另一个类
super 关键字
•super 关键字既可以当函数使用,也可以当对对象使用,在两种情况下,用法完全不同。
1.用法一:super 关键字作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数
必需执行一次 super 函数。
class A {} class B extends A { constructor() { super(); } }
// 子类B 的构造函数之中的 super(),代表调用父类的构造函数,这是必需的,否则JavaScript引擎会报错。
•注意:这里在子类当中调用 super() 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即
super 内部的 this 指的是 B 的实例,因此 super 在这里相当于
A.prototype.constructor.call(this)
•super 作为函数时,super() 只能用在子类的构造函数中,用在其他地方就会报错。
class A {} class B extends A { m() { super(); // 报错 } }
// 上面代码中,super()用在B类的m方法之中,就会造成语法错误。
1.用法二: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 A { constructor() { this.p = 2; } } class B extends A { get m() { return super.p; } } let b = new B(); b.m // undefined
// 由于 A 中的p 是定义在A 类的构造函数上的,所以无法通过super获取到p
•上面代码中,super 当对象使用时,无法访问到父类构造函数中属性和方法,因为是定义在 A 类的构造
函数上,如果定义在 p 定义在 A.prototype 上,可以通过 super.p 访问到。
•ES6 规定,在子类普通方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例。
class A { constructor() { this.x = 1; } print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; } m() { super.print(); } } let b = new B(); b.m() // 2 /**
上面代码中,`super.print()` 虽然调用的是 `A.prototype.print()`,但是 `A.prototype.print()` 内部
的 `this` 指向子类的 B 的实例,导致输出的是 2,而不是 1,也就是说,实际上执行的是
`super.print.call(this)`。 */ class A { constructor() { this.x = 1; } } class B extends A { constructor() { super(); this.x = 2; super.x = 3; console.log(super.x); // undefined console.log(this.x); // 3 } } let b = new B(); /**
上面代码中,super.x = 3,这等同于对 this.x = 3,而当读取 super.x时,读的是 A.prototype.x,
返回的时 undefined
*/
•如果 super 作为对象,用在静态方法中,这时 super 将指向父类,而不是父类的原型对象。
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
•上面代码中,super 在静态方法中指向父类,在普通方法中指向父类的原型对象。
class A { constructor() { this.x = 1; } static print() { console.log(this.x); } } class B extends A { constructor() { super(); this.x = 2; } static m() { super.print(); } } B.x = 3; B.m() // 3 /**
上面代码中,静态方法B.m里面,super.print指向父类的静态方法,这个方法里面的this指向的是B,而不是
B的实例。
*/
•注意,在使用 super 时,必须显示的执行是作为函数还是作为对象,否则都会报错。
class A {} class B extends A { constructor() { super(); console.log(super); // 报错 } }
•由于对象总是继承其他对象的,所以可以在任意一个对象中,使用 super 关键字。
var obj = { toString() { return "MyObject: " + super.toString(); } }; obj.toString(); // MyObject: [object Object]