总结 1、Class
可以通过extends
关键字实现继承,继承父类的所有属性和方法
2、父类子类没有部署任何代码
,这两个类完全一样
,等于复制了一个父类
3、子类必须在constructor
方法中调用super
方法,否则新建实例时会报错
子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法
,然后再对其进行加工,加上子类自己的实例属性和方法
如果不调用super
方法,子类就得不到this对象
ES5 的继承,实质是先创造子类
的实例对象this
,然后再将父类
的方法添加到this
上面(Parent.apply(this)
)
ES6 的继承机制完全不同,实质是先将父类
实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
如果子类没有定义constructor
方法,super
方法会被默认添加
在子类的构造函数中,只有调用super
之后,才可以使用this
关键字
4、父类的静态方法,也会被子类继承 5、Object.getPrototypeOf(),用来从子类上获取父类
6、super 关键字既可以当作函数
使用,也可以当作对象
使用
super作为函数调用,代表父类的构造函数
super()
ES6
要求,子类的构造函数必须执行一次super函数
super
内部的this
指的是子类的实例
super
虽然代表了父类A
的构造函数,但是返回的
是子类B
的实例
即super内部的this指的是B的实例
,因此super()
在这里相当于A.prototype.constructor.call(this)
在super()
执行时,它指向的是子类B
的构造函数
super()
只能用在子类的构造函数之中
super作为对象时 super.xxx
super作为对象在子类普通方法中,指向父类的原型对象
(可以获取公有属性)
super.p()就相当于A.prototype.p()
定义在父类实例上
的方法或属性无法通过super调用
(无法获取私有属性)
super可以取到定义在父类原型(父类.prototype.xxx
)对象上的属性
子类普通方法中通过super
调用父类的方法时,方法内部的this
指向当前的子类实例
通过super对某个属性赋值,是赋值的子类实例
的属性
super作为对象在子类静态方法之中,指向父类
在子类的静态方法
中通过super
调用父类的方法时,方法内部的this
指向当前的子类
,而不是子类的实例
使用super的时候,必须显式指定是作为函数、还是作为对象使用(就是super后边必须跟东西),否则会报错
可以在任意一个对象中,使用super关键字
7、ES5
实现之中,每一个对象都有__proto__
属性,指向对应的构造函数的prototype
属性 8、ES6:子类的__proto__
属性,表示构造函数的继承
,总是指向父类
9、ES6:子类prototype
属性的__proto__
属性,表示方法的继承
,总是指向父类的prototype属性
10、8,9这两条继承链,可以这样理解:
作为一个对象
,子类(B
)的原型(__proto__
属性)是父类(A);B.proto === A // true ,
作为一个构造函数
,子类(B
)的原型对象(prototype
属性)是父类的原型对象(prototype属性)的实例;B.prototype.proto === A.prototype // true 11、类的继承的实现模式
B 的实例继承 A 的实例,Object.setPrototypeOf(B.prototype, A.prototype)
B 继承 A 的静态属性,Object.setPrototypeOf(B, A) 12、Object.setPrototypeOf方法的实现,obj.__proto__ = proto
13、extends关键字后面可以跟多种类型的值
extends
关键字后面可以跟任意函数,函数都有prototype
属性(除了Function.prototype
函数)
子类可以继承Object类
class A ,是一个基类,不存在任何继承,就是一个普通函数,A.proto === Function.prototype ;A调用后返回一个空对象(即Object实例)所以A.prototype.proto (指向构造函数Object
的prototype
属性)=== Object.prototype 14、实例的 __proto__属性
实例的__proto__
指向实例构造函数的prototype
子类实例的__proto__
属性的__proto__
属性(子类的prototype
),指向父类实例的__proto__
属性(父类的prototype
)。都指向父类的prototype也就是说,子类的原型的原型,是父类的原型
15、原生构造函数
是指语言内置的构造函数
,通常用来生成数据结构
16、ES5,原生构造函数是无法继承的,因为
1、ES5
是先新建子类的实例对象this
,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数
2、原生构造函数
会忽略apply
方法传入的this
,也就是说,原生构造函数
的this无法绑定
,导致拿不到内部属性
17、ES6 允许继承原生构造函数定义子类,原因:
ES6
是先新建父类的实例对象this
,然后再用子类的构造函数修饰this
,使得父类的所有行为都可以继承
extends
关键字不仅可以用来继承类,还可以用来继承原生的构造函数 18、NewObj
继承了Object
,但是 无法通过super
方法向父类Object
传参,这是因为ES6
改变了Object
构造函数的行为,一旦发现Object
方法不是
通过new Object()
这种形式调用,ES6
规定Object
构造函数会忽略参数
19、Mixin 指的是:多个对象合成一个新的对象,新对象具有各个组成成员的接口
简介 1、Class
可以通过extends
关键字实现继承,继承父类的所有属性和方法
2、父类子类没有部署任何代码
,这两个类完全一样
,等于复制了一个父类
1 2 3 4 5 class Point {} class ColorPoint extends Point {}
3、子类必须在constructor
方法中调用super
方法,否则新建实例时会报错 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class Point { constructor (x, y) { this .x = x; this .y = y; } toString(){ } } class ColorPoint extends Point { constructor (x, y, color) { super (x, y); this .color = color; } toString() { return this .color + ' ' + super .toString(); } }
(1)因为:子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法
,然后再对其进行加工,加上子类自己的实例属性和方法 (2)如果不调用super
方法,子类就得不到this对象
(3)ES5 的继承,实质是先创造子类
的实例对象this
,然后再将父类
的方法添加到this
上面(Parent.apply(this)
) (4)ES6 的继承机制完全不同,实质是先将父类
实例对象的属性和方法,加到this
上面(所以必须先调用super
方法),然后再用子类的构造函数修改this
(5)如果子类没有定义constructor
方法,super
方法会被默认添加 1 2 3 4 5 6 7 8 9 class ColorPoint extends Point {} class ColorPoint extends Point { constructor (...args) { super (...args); } }
(6)在子类的构造函数中,只有调用super
之后,才可以使用this
关键字 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Point { constructor (x, y) { this .x = x; this .y = y; } } class ColorPoint extends Point { constructor (x, y, color) { this .color = color; super (x, y); this .color = color; } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Point { constructor (x, y) { this .x = x; this .y = y; } } class ColorPoint extends Point { constructor (x, y, color) { super (x, y); this .color = color; } } let cp = new ColorPoint(25 , 8 , 'green' );cp instanceof ColorPoint cp instanceof Point
4、父类的静态方法,也会被子类继承 1 2 3 4 5 6 7 8 9 10 class A { static hello() { console .log('hello world' ); } } class B extends A {} B.hello()
Object.getPrototypeOf(),用来从子类上获取父类
1 2 3 4 5 6 7 8 9 10 class Point {} class ColorPoint extends Point { constructor (x, y, color) { super (x, y); } } Object .getPrototypeOf(ColorPoint) === Point
super 关键字既可以当作函数
使用,也可以当作对象
使用 1、super作为函数调用,代表父类的构造函数
(1)ES6
要求,子类的构造函数必须执行一次super函数
因为:子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法
,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this对象
。
1 2 3 4 5 6 7 class A {}class B extends A { constructor () { super (); } }
(2)super内部的this指的是子类的实例 super
虽然代表了父类A
的构造函数,但是返回的
是子类B
的实例即super内部的this指的是B的实例
,因此super()
在这里相当于A.prototype.constructor.call(this)
1 2 3 4 5 6 7 8 9 10 11 12 class A { constructor () { console .log(new .target.name); } } class B extends A { constructor () { super (); } } new A() new B()
上面代码中,new.target
指向当前正在执行的函数。可以看到,在super()
执行时,它指向的是子类B
的构造函数,而不是父类A
的构造函数。也就是说,super()
内部的this
指向的是B
。
(3)super()只能用在子类的构造函数之中 1 2 3 4 5 6 7 class A {}class B extends A { m() { super (); } }
2、super作为对象时 2.1、在普通方法中,指向父类的原型对象
(可以获取公有属性) (1)super.p()就相当于A.prototype.p() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class A { p() { return 2 ; } } class B extends A { constructor () { super (); console .log(super .p()); } } let b = new B();
上面代码中,子类B
当中的super.p()
,就是将super当作一个对象
使用。这时,super
在普通方法
之中,指向A.prototype
,所以super.p()
就相当于A.prototype.p()
。
(2)定义在父类实例上
的方法或属性无法通过super调用
(无法获取私有属性) 由于super
指向父类的原型对象(父类.prototype)
,所以定义在父类实例上
的方法
或属性
,是无法通过super调用
的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 class A { constructor () { this .p = 2 ; } } class B extends A { get m () { return super .p; } } let b = new B();b.m
上面代码中,p
是父类A
实例的属性,super.p
就引用不到它。
(3)super可以取到定义在父类原型(父类.prototype)对象上的属性 1 2 3 4 5 6 7 8 9 10 11 class A {}A.prototype.x = 2 ; class B extends A { constructor () { super (); console .log(super .x) } } let b = new B();
(4)子类普通方法中通过super调用父类的方法时,方法内部的this指向当前的子类实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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()
上面代码中,super.print()
虽然调用的是A.prototype.print()
,但是A.prototype.print()
内部的this
指向子类B
的实例,导致输出的是2
,而不是1
。也就是说,实际上执行的是super.print.call(this)
。
(5)通过super对某个属性赋值,是赋值的子类实例
的属性
上面说过this
指向子类实例,所以如果通过super
对某个属性赋值,这时super就是this
,赋值的属性会变成子类实例的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class A { constructor () { this .x = 1 ; } } class B extends A { constructor () { super (); this .x = 2 ; super .x = 3 ; console .log(super .x); console .log(this .x); } } let b = new B();
上面代码中,super.x
赋值为3
,这时等同于
对this.x
赋值为3
。而当读取super.x
的时候,读的是A.prototype.x
,所以返回undefined
。
2.2、在静态方法之中,指向父类
在静态方法之中,这时super
将指向父类
,而不是父类的原型对象
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 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 ); var child = new Child();child.myMethod(2 );
(1)在子类的静态方法
中通过super
调用父类的方法时,方法内部的this
指向当前的子类
,而不是子类的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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()
上面代码中,静态方法B.m
里面,super.print
指向父类的静态方法。这个方法里面的this
指向的是B
,而不是B的实例
。
3、使用super的时候,必须显式指定是作为函数、还是作为对象使用(就是super后边必须跟东西),否则会报错 1 2 3 4 5 6 7 8 class A {}class B extends A { constructor () { super (); console .log(super ); } }
4、可以在任意一个对象中,使用super关键字 由于对象总是继承其他对象的
,所以可以在任意一个对象中,使用super关键字。
1 2 3 4 5 6 7 var obj = { toString() { return "MyObject: " + super .toString(); } }; obj.toString();
类的 prototype 属性和__proto__
属性 ES5
实现之中,每一个对象都有__proto__
属性,指向对应的构造函数的prototype
属性ES6 子类: (1)子类的__proto__
属性,表示构造函数的继承
,总是指向父类
(2)子类prototype
属性的__proto__
属性,表示方法的继承
,总是指向父类的prototype属性
这两条继承链,可以这样理解: (1)作为一个对象
,子类(B
)的原型(__proto__
属性)是父类(A) (2)作为一个构造函数
,子类(B
)的原型对象(prototype
属性)是父类的原型对象(prototype属性)的实例 1 2 3 4 5 6 7 class A {} class B extends A {} B.__proto__ === A B.prototype.__proto__ === A.prototype
类的继承的实现模式 (1)B 的实例继承 A 的实例Object.setPrototypeOf(B.prototype, A.prototype) (2)B 继承 A 的静态属性Object.setPrototypeOf(B, A) 1 2 3 4 5 6 7 8 9 10 11 12 13 class A {} class B {} Object .setPrototypeOf(B.prototype, A.prototype);Object .setPrototypeOf(B, A);const b = new B();
Object.setPrototypeOf方法的实现,obj.__proto__ = proto
1 2 3 4 Object .setPrototypeOf = function (obj, proto ) { obj.__proto__ = proto; return obj; }
1 2 3 4 5 6 7 Object .setPrototypeOf(B.prototype, A.prototype);B.prototype.__proto__ = A.prototype; Object .setPrototypeOf(B, A);B.__proto__ = A;
1 2 3 B.prototype = Object .create(A.prototype); B.prototype.__proto__ = A.prototype;
extends关键字后面可以跟多种类型的值
上面代码的A
,只要是一个有prototype
属性的函数,就能被B
继承。由于函数都有prototype
属性(除了Function.prototype函数
),因此A
可以是任意函数。
(1)子类继承Object类 1 2 3 4 5 class A extends Object {} A.__proto__ === Object A.prototype.__proto__ === Object .prototype
A
其实就是构造函数Object的复制
,A
的实例就是Object
的实例
(2),不存在任何继承 1 2 3 4 5 class A {} A.__proto__ === Function .prototype A.prototype.__proto__ === Object .prototype
A
作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Function.prototype
但是,A
调用后返回一个空对象(即Object
实例),所以A.prototype.__proto__
指向构造函数Object
的prototype
属性 实例的 __proto__
属性 实例的__proto__
指向实例构造函数的prototype
子类实例的__proto__
属性的__proto__
属性,指向父类实例的__proto__
属性。都指向父类的prototype也就是说,子类的原型的原型,是父类的原型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class A {} class B extends A { constructor (){ super (); } } var p1=new A();var p2=new B();console .log(p2.__proto__ === B.prototype); console .log(B.prototype.__proto__ === A.prototype); console .log(p2.__proto__.__proto__ === A.prototype); console .log(p1.__proto__ === A.prototype); console .log(p2.__proto__.__proto__ === p1.__proto__);
上面代码中,B
继承了A
,导致前者原型的原型是后者的原型(B.prototype.__proto__ === A.prototype
)
通过子类实例的__proto__.__proto__
属性,可以修改父类实例
的行为
1 2 3 4 5 p2.__proto__.__proto__.printName = function ( ) { console .log('Ha' ); }; p1.printName()
原生构造函数的继承 原生构造函数
是指语言内置的构造函数
,通常用来生成数据结构
Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
以前,原生构造函数是无法继承的 1、因为ES5
是先新建子类的实例对象this
,再将父类的属性添加到子类上,由于父类的内部属性无法获取,导致无法继承原生的构造函数 以自己定义一个Array的子类为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 ] = "red" ; colors.length colors.length = 0 ; colors[0 ]
继承Array
的MyArray
类的行为,与Array
完全不一致
。 原因是:子类
无法获得原生构造函数
的内部属性
2、原生构造函数
会忽略apply
方法传入的this
,也就是说,原生构造函数
的this无法绑定
,导致拿不到内部属性
ES6 允许继承原生构造函数定义子类 1、ES6
是先新建父类的实例对象this
,然后再用子类的构造函数修饰this,使得父类的所有行为都可以继承
这意味着,ES6 可以自定义原生数据结构(比如Array、String等)的子类,这是 ES5 无法做到的。
继承Array的例子:
1 2 3 4 5 6 7 8 9 10 11 12 class MyArray extends Array { constructor (...args) { super (...args); } } var arr = new MyArray();arr[0 ] = 12 ; arr.length arr.length = 0 ; arr[0 ]
2、extends
关键字不仅可以用来继承类,还可以用来继承原生的构造函数
因此可以在原生数据结构的基础上,定义自己的数据结构。
一个带版本功能的数组: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 class VersionedArray extends Array { constructor () { super (); this .history = [[]]; } commit() { this .history.push(this .slice()); } revert() { this .splice(0 , this .length, ...this.history[this .history.length - 1 ]); } } var x = new VersionedArray();x.push(1 ); x.push(2 ); x x.history x.commit(); x.history x.push(3 ); x x.history x.revert(); x
上面代码中,VersionedArray
会通过commit
方法,将自己的当前状态
生成一个版本快照,存入history
属性。revert
方法用来将数组重置为最新一次保存的版本。除此之外,VersionedArray
依然是一个普通数组,所有原生的数组方法都可以在它上面调用。
自定义Error子类,来定制报错时的行为 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class ExtendableError extends Error { constructor (message) { super (); this .message = message; this .stack = (new Error ()).stack; this .name = this .constructor.name; } } class MyError extends ExtendableError { constructor (m) { super (m); } } var myerror = new MyError('ll' );myerror.message myerror instanceof Error myerror.name myerror.stack
继承Object的子类,有一个行为差异 1 2 3 4 5 6 7 class NewObj extends Object { constructor (){ super (...arguments); } } var o = new NewObj({attr : true });o.attr === true
上面代码中,NewObj
继承了Object
,但是 无法通过super
方法向父类Object
传参。
这是因为ES6
改变了Object
构造函数的行为,一旦发现Object
方法不是
通过new Object()
这种形式调用,ES6
规定Object
构造函数会忽略参数
Mixin 模式的实现 Mixin 指的是:多个对象合成一个新的对象,新对象具有各个组成成员的接口 最简单实现如下 1 2 3 4 5 6 7 const a = { a: 'a' }; const b = { b: 'b' }; const c = {...a, ...b};
更完备的实现 将多个类的接口“混入”(mix in)另一个类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 function mix (...mixins ) { class Mix { constructor () { for (let mixin of mixins) { copyProperties(this , new mixin()); } } } for (let mixin of mixins) { copyProperties(Mix, mixin); copyProperties(Mix.prototype, mixin.prototype); } return Mix; } function copyProperties (target, source ) { for (let key of Reflect .ownKeys(source)) { if ( key !== 'constructor' && key !== 'prototype' && key !== 'name' ) { let desc = Object .getOwnPropertyDescriptor(source, key); Object .defineProperty(target, key, desc); } } }
使用的时候,只要继承这个类即可
1 2 3 class DistributedEdit extends mix (Loggable , Serializable ) { }