Open HuangHongRui opened 7 years ago
Square
继承自Rectangle
.. 这样做,必须用一个创建自Rectangle.prototype
的新对象重写Square.prototype
并调用 Rectangle.call()
方法
[这些步骤即使是经验丰富的大牛也常在此出错]
类的出现..使得更轻松地实现继承功能..
使用熟悉的 extends
关键字可以指定类继承的函数..
原型会自动调整
通过调用 super()
方法即可访问基类的构造函数..
class Rectangle {
constructor( length, width ) {
this.length = length
this.width = width
}
getArea() {
return this.length * this.width
}
}
class Square extends Rectangle {
constructor (length) {
super(length, length)
}
}
let square = new Square(3)
console.log(square.getArea())
console.log(square instanceof Square)
console.log(square instanceof Rectangle)
Square
类通过extends
关键字继承 Rectangle
类..
在 Square
构造函数中通过 super()
调用 Rectangle
构造函数并传入相应参数..
需要注意的是, 跟Es5不同..标识符Rectangle
只用于类声明(extends
之后).
继承自其他类的类 被称作 派生类
如果在派生类中指定了构造函数则必须调用 super
..[ 如不这样做,程序即报错]
如果选择不使用构造函数.则当创建新的类实例时会自动调用 super()
并传入所有参数.
class Square extends Rectangle {
// 无构造函数
}
// 等价于
class Square extends Rectangle {
constructor ( ...args ) {
super ( ...args );
}
}
第二个类是所有派生类的等效默认构造函数, 所有参数按照顺序被传递给基类的构造函数.. 这里展示的功能不太正确,因为 Square 的构造函数只需要一个参数, 所以最好手动定义构造函数..
当使用 super
时切记的几个关键
super()
..如果尝试在非派生类(不是用extends声明的类) 或函数中使用则会导致抛错.this
之前一定要调用super()
, 它负责初始化this
, 如果在调用super()
之前尝试访问this
会导致抛错super()
,唯一的方法是让类的构造函数返回一个对象..类方法屏蔽
派生类中的方法总会覆盖基类中的同名方法.. 例子:
class Square extends Rectangle {
constructor ( length ) {
super ( length, length );
}
//覆盖并遮蔽 Rectangle.prototype.getArea()方法..
getArea() {
return this.length * this.length
}
}
现在..因为Square定义了getArea()方法..
所以不能在Square的实例中调用Rectangle.prototype.getArea()
方法..
如果想调用基类中的此方法..可调用 super.getArea()
方法.
class Square extends Rectangle {
constructor (length) {
super(length, length);
}
//覆盖遮蔽后再调用 Rectangle.prototype.getArea()
getArea() {
return super.getArea()
}
}
这种方法使用Super.. this
值会被自动正确设置, 然后就可以进行简单的方法调用了.
静态成员在派生类中也可以用。
class Rectangle {
constructor(length, width) {
this.length = length
this.width = width
}
getArea() {
return this.length * this.width
}
static create (length, width) {
return new Rectangle(length, width)
}
}
class Square extends Rectangle {
constructor(length) {
super( length, length)
}
}
var rect = Square.create(3, 4)
console.log(rect.getArea()) // 12
console.log(rect instanceof Rectangle) // true
console.log(rect instanceof Square) // false
静态方法被添加到Rectangle类中
派生类表达式的类
Es6 最强大的一面或许是从表达式导出类的功能了 只要表达式可以被解析为一个函数并且具有[[constructor]]属性和原型,那就可用extend进行派生。
function Rectangle(length, width) {
this.length = length
this.width = width
}
Rectangle.prototype.getArea = function() {
return this.length * this.width
}
class Square extends Rectangle {
constructor(length){
super (length, length)
}
}
var x = new Square(3)
console.log(x.getArea())
console.log(x instanceof Rectangle)
Rectangle
是一个Es5风格构造函数,Square
是一个类
由于Rectangle
具有[[constructor]]属性和原型, 因此Square
类可以直接继承它。
extends
强大的功能使得类可以继承自任意类型的表达式。[创造更多可能性]
动态地确定类的继承目标。
function Rectangle(length, width) {
this.length = length
this.width = width
}
Rectangle.prototype.getArea = function() {
return this.length * this.width
}
function getBase() {
return Rectangle
}
class Square extends getBase() {
constructor(length) {
super (length, length)
}
}
var r = new Square(3)
console.log(r)
console.log(r.getArea())
console.log(r instanceof Rectangle)
console.log(r instanceof Square)
getBase()
函数是类声明的一部分。 直接调用后返回 Rectangle
此例子跟前面Code例子等价。
因为可动态确定使用哪个基类, 所以可创建不同的继承方法。
let SerializableMixin = {
serialize() {
return JSON.stringify(this)
}
}
let AreaMixin = {
getArea() {
return this.length * this.width
}
}
function mixin(...mixins) {
var base = function() {} // 创建一函数
Object.assign(base.prototype, ...mixins) // 将每个传入的对象属性赋值给指定的base函数
return base
}
// Square类再基于这个返回的base函数,用extends进行扩展
class Square extends mixin(AreaMixin, SerializableMixin) {
constructor(length) {
super() // 因使用了extends,所以在构造函数中需要调用super()
this.length = length
this.width = length
}
}
var r = new Square(3)
console.log(r.getArea())
console.log(r.serialize())
使用了mixin函数代替传统的继承方法, 可接受任意数量的 mixin 对象作为参数。
Square的实例拥有来自2个对象的方法。(都是通过原型继承实现的)
mixin()
函数会用所有mixin
对象的自有属性动态填充新函数的原型。(如多个mixin对象具有同样属性,那保留最后一个被添加的属性)
在 extends
后可以使用任意表达式, 但不是所有表达式最终都能生成合法的类。
如果使用 null
或生成器函数 会导致错误,类在这些情况下没有[[constructor]]属性,尝试为其创建新的实例会因程序无法调用[[constructor]]而抛错。
【内建对象的继承】
自 Js 数组诞生以来,开发者一直都希望通过继承的方式创建属于直接的特殊数组。 在Es5或前版本中几乎不可能,用传统的继承方式无法实现这样的功能: 如下例子
var colors = []
colors[0] = 'red'
console.log(colors.length) // 1
colors.length = 0
console.log(colors[0]) // undefined
function MyArray() {
Array.apply(this, arguments)
}
MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
})
var colors = new MyArray()
colors[0] = 'blue'
console.log(colors.length) // 0
colors.length = 0
console.log(colors[0]) // 'blue'
MyArray 实例的length和数值型属性的行为与内建数组中的不一致,这是因为通过传统Js继承形式实现的数组继承没有从Array.apply()或原型赋值中继承相关功能
Es6 类语法的一个目标就是支持内建对象继承, 因而Es6中的类继承模型与Es5和之前版本略有不同(两方面)。
this
的值(如MyArray),然后调用基类型的构造函数(如Array.apply()方法),这也意味,this
的值开始指向的是MyArray的实例,但随后会被来自Array的其他属性所修饰。this
的值, 再派生类的构造函数(MyArray)修改这个值。所以一开始可通过this访问基类的所有內建功能,然后再正确地接收所有与之相关的功能。例子: 一个基于类生成特殊数组的实践:
class MyArray extends Array {}
var colors = new MyArray()
colors[0] = 'Yellow'
console.log(colors.length) // 1
colors.length = 0
console.log(colors[0]) //undefined
MyArray直接继承自Array。 操作数值型属性会更新length属性,操作length属性也会更新数值型属性。 于是可以正确地继承Array对象来创建直接的派生数组类型,也可继承其他的内建对象。
【Symbol.species 属性】
内建对象继承的一个实用之处: 原本在內建对象中返回实例自身的方法 将自动返回派生类的实例 所以如有一个继承自Array的派生类MyArray,那么像slice()这样的方法也会返回一个MyArray的实例:
class MyArray extends Array {}
let items = new MyArray(1,2,3,4),
subitems = items.slice(1,3);
console.log( items instanceof MyArray ) // true
console.log( subitems instanceof MyArray ) // true
继承自Array的slice()
方法应该返回Array的实例
但是这段Code中,slice()返回的是MyArray的实例
在浏览器引擎背后是通过Symbol.speclies
属性实现了这一行为。
Symbol.species
是诸多内部Symbol中的一个
它被用于定义返回函数的静态访问器属性。
被返回的函数是一个构造函数,每当要在实例的方法中(不是构造函数中)创建类的实例时必须使用这个构造函数。
Array / ArrayBuffer / Map / Promise / RegExp / Set / Typed arrays 这些內建类型均已定义该属性
该属性的返回值为this
,意味着该属性总会返回构造函数。
如在自定义的类中实现此功能,Code如下:
class MyClass {
static get [Symbol.species]() {
return this
}
construtor(value) {
this.value = value
}
clone() {
return new this.constructor[Symbol.species](this.value)
}
}
此例, Symbol.species 被用来给MyClass赋值静态访问器属性
这里只有一个getter
方法却没有setter
方法,因为这里不可改变类的种类。
调用`this.constructor[Symbol.species]会返回MyClass
clone() 方法通过这个定义可以返回新的实例,从而允许派生类覆盖这个值
Next Code:
class MyClass { static get [Symbol.species]() { return this } construtor(val) { this.value = val } clone() { return new this.constructor[Symbol.species](this.value) } } class MyDerivedClass1 extends MyClass {} class MyDerivedClass2 extends MyClass { static get [Symbol.species]() { return MyClass; } } let instance1 = new MyDerivedClass1('Raine'), clone1 = instance1.clone(), instance2 = new MyDerivedClass2('Rui'), clone2 = instance2.clone();
console.log(clone1 instanceof MyClass) // true console.log(clone1 instanceof MyDerivedClass1) // true console.log(clone2 instanceof MyClass) // ture console.log(clone2 instanceof MyDerivedClass2) // false
MyDerivedClass1 继承MyClass时未改变Symbol.species属性,
由于this.constructor[Symbol.species] 的返回值是 MyDerivedClass1,
因此调用clone()返回的是MyDerivedClass1的实例。
MyDerivedClass2 继承Myclass前重写了Symbol.species,让其返回 MyClass
在调用clone()时,返回是一个MyClass的实例。
**通过Symbol.species可以定义当派生类的方法返回实例时,应该返回的值的类型。**
数组通过 Symbol.species
来指定那些返回数组的方法应当从哪个类中获取。
在一个派生自数组的类中, 可以决定继承的方法返回何种类型的对象
Code:
class MyArray extends Array { static get [Symbol.species]() { return Array; } } let items = new MyArray(1,2,3,4), subitems = items.slice(1,3);
console.log(items instanceof Array) // true console.log(items instanceof MyArray) // true console.log(subitems instanceof Array) // true console.log(subitems instanceof MyArray) // false
`MyArry` 继承自 Array的 `Symbol.species` 属性,
所有返回数组的继承方法现在将使用Array的实例。
**要想在类方法中调用 `this.constructor`,就该使用 `Symbol.species` 属性, 让派生类重写返回类型。**
如果正从一个已定义 `Symbol.species` 属性的类创建派生类,
要确保使用这个值而不是使用构造函数
Es6 之前, 实现继承与自定义类型需要多个步骤实现..