My Little World

对象构造和继承

对象特性

对象的属性在定义时,都带有一些特征值,js通过这些特征值定义他们的行为
这些特征值描述对象属性的各种特征,成为对象属性的特性
特性是内部值,放在两对方括号中访问
特性分为数据属性和访问器属性
数据属性:Configurable、Enumerable、Writable、Value
访问器属性:Configurable、Enumerable、Get、Set
定义某个属性的特性:Object.defineProperty(对象名,对象属性名,{特性1:值,特性2:值…})
定义多个属性特性:Object.defineProperties(对象名,{属性1:{特性1:值,特性2:值…},属性2:{特性1:值….}})
读取属性:Object.getOwnPropertyDescriptor(对象名,属性名) 返回一个对象

构造对象

工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function createobj(name,age){
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name)
};
return o;
}
var person1 = createobj('gray',25);
var person2 = createobj('black',26);
person1.sayName()//gray
person2.sayName()//black
console.log(typeof person1)//object
console.log(person1 instanceof createobj) //false

缺点:无法识别对象类型

寄生构造函数

定义样子同工厂模式,只不过创建实例时用new 操作符
将包装函数叫做 构造函数
实例与构造函数及其原型没有关系

1
2
3
4
5
6
7
8
9
10
11
12
13
function createobj(name,age){  //构造函数
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name)
};
return o;
}
var person1 = new createobj('gray',25);
person1.sayName()//gray
console.log(person1 instanceof createobj) //false
console.log(person1 instanceof Object) //true

应用:为无法修改构造函数的对象添加方法

1
2
3
4
5
6
7
8
9
10
11
//给数组增加特殊方法
function specialArray(){
var values = new Array();
values.push.apply(values,arguments);
values.toPiedString = function(str){ //特殊方法
return this.join(str);
};
return values;
}
var colors = new specialArray('red','blue');
console.log(colors.toPiedString('|'));//"red|blue"

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function createobj(name,age){
this.name = name;
this.age = age;
this.sayName = function(){
console.log(this.name)
};
}
var person1 = new createobj('gray',25);
person1.sayName(); //gray

console.log(person1 instanceof createobj)//true
console.log(person1 instanceof Object)//true

console.log(window.name)//''
var person2 = createobj('black',26);
console.log(person2) //undefined
console.log(window.name) //black
console.log(person1.sayName == window.sayName)//false

var person3 = new createobj('orange',25);
console.log(person1.sayName == person3.sayName)//false

console.log(person1.constructor == createobj)//true
console.log(person3.constructor == person1.constructor)//true

1、构造函数的实例均指向构造函数。例:person1、person3的constructor均指向createobj)
2、不使用new 关键字调用构造函数,构造函数的方法和属性会挂到window上面。例:person2
3、定义在构造函数上的方法,创建不同实例后,不同实例各自拥有自己的该方法,不同实例之间构造函数方法不相等,不共享

使用构造函数创建对象,如果相让对象方法在不同实例实现共享,则在定义方法时,采用引用函数的方法

1
2
3
4
5
6
7
8
9
10
11
function createobj(name,age){
this.name = name;
this.age = age;
this.sayName = sayName;
}
function sayName(){
console.log(this.name)
}
var person1 = new createobj('gray',25);
var person3 = new createobj('orange',25);
console.log(person1.sayName == person3.sayName)//true

缺点:若对象需要定义多个方法则需要创建多个全局函数供构造函数引用,失去封装性

原型模式

在对象的prototype中定义的属性和方法,可以在所有实例中共享
定义方法一

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
30
31
32
33
34
35
36
function createobj(name,age){}
createobj.prototype.name = 'black';
createobj.prototype.age = 26;
createobj.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new createobj();
var person2 = new createobj();
person1.sayName();//black
person2.sayName();//black
console.log(person1.sayName == person2.sayName)//true

console.log(person1.constructor == createobj)//true
console.log(person2.constructor == person1.constructor)//true


console.log(person1.age) //26 来自原型
person1.age = 27; //相当于创建了一个实例属性
console.log(person1.age) //27 来自实例属性
console.log(person2.age) //26 来自原型
delete person1.age; //删除实例属性
console.log(person1.age) //26 来自原型
delete person1.age; //不能删除原型属性
console.log(person1.age) //26
console.log(person2.age) //26

console.log(person1 == createobj.prototype)//false
console.log(person1.prototype == createobj.prototype)//false
console.log(createobj.prototype.isPrototypeOf(person1))//true
console.log(person1.prototype) //undefined
console.log(Object.getPrototypeOf(person1));//createobj.prototype

console.log(person1.hasOwnProperty('name'));//false
console.log('name' in person1);//true
console.log(Object.keys(createobj.prototype));//["name", "age", "sayName"]
console.log(Object.getOwnPropertyNames(createobj.prototype));//["constructor", "name", "age", "sayName"]

obj1
1、实例的内部指针[[Prototype]] 会指向构造函数的原型对象,它是不能直接访问的
2、构造函数的prototype也会指向构造函数的原型对象,但他是构造函数的一个属性,可以直接访问
3、Object.getPrototypeOf(实例名); 返回实例的原型对象 .即[[Prototype]]指向的对象
4、实例名.hasOwnProperty(属性名字符串) ; 只检查实例的实例属性中是否有该属性,没有的话,返回false,可用于检查该属性在实例属性中,还是在原型属性中
5、在获取属性值时,实例属性中有该属性就取该实例属性的值,没有则用原型属性值,原型属性也没有的话则返回undefined
6、实例属性可用delete删除,实例无法更改原型属性的值
7、属性名字符串 in 实例名; 检查实例和原型所有属性中是否有该属性,有的话就返回true;for-in循环可枚举的属性,包括实例属性和原型属性
8、Object.keys(对象名);返回对象可枚举属性的字符串数组
9、Object.getOwnPropertyNames(对象名);返回所有对象的实例属性,无论它是否枚举

定义方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createobj(){}
createobj.prototype={
name:'seven',
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
person1.sayName();//seven
console.log(person1.constructor == createobj);//false
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//true
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function object(){}

console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//true

1、将挂在构造函数原型上的属性以一个对象的形式,一起赋给构造函数原型,即构造函数原型被赋值为一个对象
2、构造函数原型被Object创建,实例、构造函数的原型对象的构造函数为Object对象
3、若在定义原型对象时,指定原型对象的constructor,则对象实例的constructor也会跟着指到相应的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createobj(){}
createobj.prototype={
constructor:createobj, //明确指出,同时特性的枚举属性会被设置为true,默认为false
name:'seven',
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
person1.sayName();//seven
console.log(person1.constructor == createobj);//true
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//false
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function createobj(){}

console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//true

4、 实例中的指针指向原型,不指向构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function createobj(){}

var person1 = new createobj();
console.log(person1 instanceof createobj);//true
createobj.prototype={
constructor:createobj,
name:'seven',
sayName:function(){
console.log(this.name)
}
};
//person1.sayName();//person1.sayName is not a function
console.log(person1.constructor == createobj);//true
console.log(person1.constructor == createobj.prototype);//false
console.log(person1.constructor == Object);//false
console.log(person1.constructor == createobj.prototype.constructor);//true
console.log(person1.constructor);//function createobj(){}

console.log(person1 instanceof Object);//true
console.log(person1 instanceof createobj);//false

obj2
对象生成实例后,再去重写构造函数的原型对象,会将构造函数的原型对象指向后来被赋的对象,
切断与旧的原型对象之间的关系,新的原型对象与实例没有任何关系,实例仍引用旧的原型对象

原型方法可用于修改添加原生对象的属性方法
缺点:在原型对象prototype中的属性方法全都共享,当更改一个实例中的属性值时,其他实例一起变

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
//定义一
function createobj(name,age){}
createobj.prototype.name = [1,2];
createobj.prototype.sayName = function(){
console.log(this.name)
}
var person1 = new createobj();
var person2 = new createobj();
person1.sayName();//[1, 2]
person2.sayName();//[1, 2]
person1.name.push(3);
person1.sayName();//[1, 2, 3]
person2.sayName();//[1, 2, 3]
//定义二
function createobj(){}
createobj.prototype={
constructor:createobj,
name:[1,2],
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj();
var person2 = new createobj();
console.log(person1.name)//[1, 2]
console.log(person2.name)//[1, 2]
person1.name.push(3);
console.log(person1.name)//[1, 2, 3]
console.log(person2.name)//[1, 2, 3]

构造函数+原型模式

构造函数定义实例属性,
原型模式定义方法和共享属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createobj(name,age){
this.name = name;
this.age = age;
this.friends = [1,2];
}
createobj.prototype={
constructor:createobj,
sayName:function(){
console.log(this.name)
}
};
var person1 = new createobj('halo',24);
var person2 = new createobj('jack',25);
person1.friends.push(3);
console.log(person1.friends);//[1, 2, 3]
console.log(person2.friends);//[1, 2]

是用来定义引用类型的一种默认模式

动态原型模式

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
30
31
32
33
34
35
36
37
38
39
40
function createobj(name,age){
this.name = name;
this.age = age;
if(typeof this.sayName !='function'){
createobj.prototype.sayName = function(){
console.log(this.name)
}
}
}
var person1 = new createobj('halo',24);
var person2 = new createobj('jack',26);
person1.sayName();//halo
person2.sayName();//jack

//修改原型
createobj.prototype.sayName = function(){
console.log('111')
}
person1.sayName();//111
person2.sayName();//111

//使用字面方式重写
createobj.prototype={
constructor:createobj,
sayName:function(){
console.log(222)
}
};
var person3 = new createobj('kitty',24);
person1.sayName();//111
person2.sayName();//111
person3.sayName();//222
console.log(person1.prototype);//undefined

//重写一个原型方法
person1.sayName=function(){
console.log('333');
}
person1.sayName();//333
person2.sayName();//111

1、判断sayName函数是否存在的语句,只会在初次调用构造函数的时候运行,即第一次创建实例的时候运行,
运行过后,构造函数原型中就会存在sayName函数,即完成初始化,之后就不会在运行
2、对原型模式定义的方法属性能够在所有实例中立即得到放映
3、如果对构造函数原型使用字面方式重写,将切断已有实例与构造函数原型的联系,
已有实例会指向就原型对象,新建实例会指向新原型对象
4、在一个实例中重写一个原型方法,不会影响原型对象方法,其他实例和新建实例仍会调用原型对象方法

稳妥构造函数

创建对象的实例方法,不使用this
不使用new调用构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createobj(name,age){  //构造函数
var o=new Object();
o.name = name;
o.age = age;
o.sayName = function(){
console.log(this.name);
console.log(this == o)
};
return o;
}
var person1 = createobj('gray',25);
person1.sayName()//gray true

console.log(person1 instanceof createobj) //false

只用通过sayName函数访问传入到构造函数中的原始成员

继承

实例原型链模式

继承函数的原型被赋值为祖先函数的实例

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
30
31
32
33
34
35
36
37
38
//祖先函数
function Superfn(){
this.pro = true;
this.color = [1,2];
this.getsuper = function(){
console.log('aaa');
}
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
//继承函数
function Subfn(){
this.subpro = false;
this.getsuper = function(){
console.log('000');
}
}
Subfn.prototype = new Superfn();
Subfn.prototype.getsuper = function(){
console.log('ccc')
}
var ins = new Subfn();
ins.getsuper();

console.log(ins instanceof Object); //true
console.log(ins instanceof Superfn); //true
console.log(ins instanceof Subfn); //true
console.log(Object.prototype.isPrototypeOf(ins)); //true
console.log(Superfn.prototype.isPrototypeOf(ins)); //true
console.log(Subfn.prototype.isPrototypeOf(ins)); //true

var ins1 = new Subfn();
console.log(ins.color)//[1,2]
console.log(ins1.color)//[1,2]
ins1.color.push(4);
console.log(ins.color)//[1,2,4]
console.log(ins1.color)//[1,2,4]

1.继承函数实例的属性/方法调用顺序:
自己定义的->继承函数实例->继承函数prototype->祖先函数实例属性->祖先函数prototype
2.因为所有引用类型默认继承object,所以调用toString等方法时,其实调用的的是object的prototype
3.重写原型中祖先函数的方法,一定要在继承函数原型被赋值祖先函数实例之后,相当于用实例方法覆盖原型方法
但注意不能用字面量方法重写继承函数原型,这样会导致继承函数原型重新指向新对象,切断与祖先函数联系
4.因为继承函数prototype指向祖先函数实例时,祖先函数所有属性相当于继承函数prototype属性,
构造函数原型的属性会被所有实例共享,所以创建继承函数实例时,
不能像祖先函数的构造函数中传递参数,因为会导致其他实例同时共享这些参数导致的后果

借用构造函数

使用apply()或者call()方法。在继承函数构造函数中调用祖先函数构造函数,这样,
在new 一个继承函数实例时,就会去执行祖先函数中所有对象初始化代码,每个实例都会具有自己的祖先函数属性的副本

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
function Superfn(time){
this.color = [1,2];
this.time = time;
}

Superfn.prototype.getsuper=function(){
console.log('bbb');
}

function Subfn(){
Superfn.call(this,'11:23:30');
this.age = 29;
}

var ins = new Subfn();
var ins1 = new Subfn();
console.log(ins.color) //[1, 2]
console.log(ins1.color)//[1, 2]
ins1.color.push(4);
console.log(ins.color)//[1, 2]
console.log(ins1.color)//[1, 2, 4]

console.log(ins.time);//"11:23:30"
console.log(ins.age);//29

ins.getsuper();//ins.getsuper is not a function

1、可以像祖先函数构造函数中传递参数;
2、定义继承函数自己的属性必须放在调用祖先函数之后,防止祖先函数重写继承函数属性
缺点: 所有的属性只能使用构造函数模式定义,而且,继承函数实例,不能调用祖先函数原型上的方法

组合继承

在继承函数构造函数中调用祖先函数,实现属性继承
将继承函数的原型赋值为祖先函数实例,实现方法复制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function Superfn(time){
this.color = [1,2];
this.time = time;
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
function Subfn(){
Superfn.call(this,'11:23:30');
this.age = 29;
}
Subfn.prototype= new Superfn();
var ins = new Subfn();
var ins1 = new Subfn();
console.log(ins.color)
console.log(ins1.color)
ins1.color.push(4);
console.log(ins.color)
console.log(ins1.color)

console.log(ins.time);//"11:23:30"
console.log(ins.age);//29

ins.getsuper();//bbb

缺点:调用了两次Superfn(),初始化时,Subfn.prototype中的Superfn的实例属性会被构造函数中创建的Superfn的实例属性覆盖,
重复创建,没必要

原型式继承

Object.creat()方法原理像如下函数
function object(o){
function F(){
F.prototype = o;
}
return new F();
}
原型式继承 即使用Object.creat(祖先对象)定义实例,实例会共享祖先对象属性方法,通过添加第二个参数,覆盖祖先对象属性,或新增属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Superfn={
color:['red','green'],
getsuper:function(){
console.log(222);
}
}

var test = Object.create(Superfn,{getsuper:{value:function(){console.log(111)}}});
var test1 = Object.create(Superfn,{color:{value:[1,2]},name:{value:'jack'}});
console.log(test.color);//['red','green']
console.log(test1.color,test1.name); //[1,2] jack
console.log(test.name);//undefined
test.getsuper(); //111
test1.getsuper();//222

寄生式继承

1
2
3
4
5
6
7
8
9
10
11
12
13
function createAnthoer(oriobj){
var clone = Object(oriobj);//创建oriobj对象副本
clone.sayHi=function(){//以某种方式增强这个对象,例如定义自己的方法
console.log('hi')
}
return clone;
}
var Superfn={
color:['red','green'],
}
var test = createAnthoer(Superfn);
test.sayHi()//hi
console.log(test.color);//["red", "green"]

最理想继承-寄生组合式继承

对比组合式继承
仅调用一次superfn函数,避免在Subfn.prototype中创建多余superfn实例属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function inhertPrototype(subfn,superfn){
var prototype = Object(superfn.prototype);//创建对象 ,仅继承祖先函数原型
prototype.constructor = subfn;//增强对象
subfn.prototype = prototype;//指定对象
}
function Superfn(name){
this.color = [1,2];
this.name = name;
}
Superfn.prototype.getsuper=function(){
console.log('bbb');
}
function Subfn(name,age){
Superfn.call(this,name); //仅继承祖先函数实例属性,不会继承祖先函数原型
this.age = age;
}
Subfn.prototype.getsuper=function(){
console.log('ccc');
}
inhertPrototype(subfn,superfn);

类的定义相当于es5 对象构造函数的另一种写法,一种新语法
es5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面
es6 类的继承,实质是先创造父类的实例对象this(必须先调用super方法),然后再用子类的构造函数修改this

类的定义

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//es5
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
let methodName = 'getArea';
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
this.toString = function(){
console.log(111);
}
}

toString() {
return '(' + this.x + ', ' + this.y + ')';
}

[methodName]() {
console.log(666);
}
}

var temp = new Point('helo','world');
console.log(temp)// constructor定义的内容
console.log(temp.toString()) //先找实例属性,在去原型找属性

console.log(typeof Point) // "function"
console.log(Point === Point.prototype.constructor) // true

console.log(Object.keys(Point.prototype));//[]
console.log(Object.getOwnPropertyNames(Point.prototype));//["constructor", "toString"]
Object.assign(Point.prototype, {
toString222(){console.log(222);},
toValue(){console.log(333);}
});
temp.toString222()
console.log(Object.keys(Point.prototype));//["toString222", "toValue"]
console.log(Object.getOwnPropertyNames(Point.prototype));//["constructor", "toString", "toString222", "toValue"]


temp[methodName]() //666

class Foo {
constructor() {
return Object.create(null);
}
}
console.log(Foo.name)//Foo
console.log(new Foo() instanceof Foo)//false
var tt = new Foo();
console.log(tt.__proto__ == Point) //TRUE

var p1 = new Point(2,3);
var p2 = new Point(3,2);
console.log(p1.__proto__ === p2.__proto__)//true
p1.__proto__.printName = function () { console.log('Oops') };
p1.printName() // "Oops"
p2.printName() // "Oops"
var p3 = new Point(4,2);
p3.printName() // "Oops

//这个类的名字是MyClass而不是Me,Me只在 Class 的内部代码可用,指代当前类。
const MyClass = class Me {
getClassName() {
console.log(Me.name);
}
};
let inst = new MyClass();
inst.getClassName() // Me
console.log(MyClass.name)//me

//立即执行的 Class
let person = new class {
constructor(name) {
this.name = name;
}

sayName() {
console.log(this.name);
}
}('张三');

person.sayName(); // "张三"

class MyClass {
constructor() {
// ...
}
get prop() {
return 'getter';
}
set prop(value) {
console.log('setter: '+value);
s}
let inst = new MyClass();
inst.prop = 123;// setter: 123
console.log(inst.prop)// 'getter'

1.constructor函数定义实例属性,this代表实例对象,生成对象实例时,自动调用该方法,
默认返回实例对象this,也可以指定返回另外一个对象,则生成的实例将是另外这个对象的实例
如果没有显式定义,一个空的constructor方法会被默认添加。
其他在constructor函数之外定义的函数全都挂在类的prototype对象上(没有定义在this上的全都定义在class上)
其实constructor函数也挂在prototype对象上
2.定义原型方法时,不需要加上function这个关键字,方法之间不需要逗号分隔
3.类的数据类型就是函数,类本身就指向构造函数
4.使用Object.assign()新增原型方法,定义类时定义的原型方法是不可枚举的,但Object.assign()新增的可枚举
5.可以使用变量值做原型方法名
6.定义实例仍使用new关键字,且必须使用new定义,否则会报错
7.类的所有实例共享一个原型对象,使用实例的proto属性改写原型,会改变“类”的原始定义,影响到所有实例
8.类也可以使用表达式的形式定义,采用 Class 表达式,可以写出立即执行的 Class
9.类必须先定义再使用,无论是生成实例还是进行继承
10.类的name属性总是返回紧跟在class关键字后面的类名。
11.对某个属性设置对应的存值函数和取值函数,因此赋值和读取行为都被自定义了。存值函数和取值函数是设置在属性的 Descriptor 对象上的。
12.静态方法,该方法不会被实例继承,而是直接通过类来调用
如果静态方法包含this关键字,这个this指的是类,而不是实例
父类的静态方法,可以被子类继承。
静态方法也是可以从super对象上调用的
13.
ES6 为new命令引入了一个new.target属性,该属性一般用在构造函数之中,返回new命令作用于的那个构造函数。如果构造函数不是通过new命令调用的,new.target会返回undefined,因此这个属性可以用来确定构造函数是怎么调用的。

类的继承

定义

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
30
31
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
console.log(new.target.name);
}
}

class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
}

let cp0 = new Point(25, 8);//"Point"
let cp = new ColorPoint(25, 8, 'green');//"ColorPoint" super()使用

console.log(cp instanceof ColorPoint)//true
console.log(cp instanceof Point)//true
console.log(Object.getPrototypeOf(ColorPoint) === Point)

console.log(ColorPoint.__proto__ === Point) // true
console.log(ColorPoint.prototype.__proto__ === Point.prototype) // true

console.log(cp.__proto__.__proto__ === cp0.__proto__ )// true
cp.__proto__.__proto__.printName = function () {
console.log('Ha');
};

cp0.printName() // "Ha"

1、使用extends关键字进行继承:class 子类名 extends 父类名{}
2、在子类构造函数constructor中使用super关键字继承父类this对象,必需在super语句之后使用this
3、Object.getPrototypeOf方法可以用来从子类上获取父类
4、子类的proto属性,表示构造函数的继承,总是指向父类。
子类prototype属性的proto属性,表示方法的继承,总是指向父类的prototype属性。
5、子类的原型的原型,是父类的原型,通过子类实例的proto.proto属性,可以修改父类实例的行为

关于super关键字

1、用作函数
作为函数时,super()只能用在子类的构造函数之中,用在其他地方就会报错,代表调用父类构造函数
super()在这里相当于parent.prototype.constructor.call(this),this指子类

2、用作对象

作为对象使用时,在普通方法中指向父类原型对象

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
class A {
constructor() {
this.p = 2;
}
q() {
console.log(this.p)
}
}

class B extends A {
constructor() {
super();
this.p =3
super.q();
super.p = 5;
console.log(super.p); // undefined
console.log(this.p); // 5
super.tt = 6;
console.log(this.tt); // 6
console.log(super.valueOf() instanceof B); // true
}
get m() {
return super.p;
}
}

let b = new B();//3
console.log(b.m) // undefined

在普通方法中指向父类原型对象,即使用super.xxxx(),相当于使用parent.prototype.xxxx(),
所以只能调用父类原型属性方法,不能使用实例属性方法

通过super调用父类的方法时,super会绑定子类的this,
即在子类使用super.xxxx()时,xxxx()里面的this指子类
通过super对某个属性赋值,赋值的属性会变成子类实例的属性

在静态方法中使用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); // static 1 super在静态方法之中指向父类

var child = new Child();
child.myMethod(2); // instance 2 在普通方法之中指向父类的原型对象。

extends特殊对象

第一种特殊情况,子类继承Object类。

1
2
3
4
5
class A extends Object {
}

A.__proto__ === Object // true
A.prototype.__proto__ === Object.prototype // true

这种情况下,A其实就是构造函数Object的复制,A的实例就是Object的实例。

第二种特殊情况,不存在任何继承。

1
2
3
4
5
class A {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === Object.prototype // true

这种情况下,A作为一个基类(即不存在任何继承),就是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回一个空对象(即Object实例),所以A.prototype.proto指向构造函数(Object)的prototype属性。

第三种特殊情况,子类继承null。

1
2
3
4
5
class A extends null {
}

A.__proto__ === Function.prototype // true
A.prototype.__proto__ === undefined // true

这种情况与第二种情况非常像。A也是一个普通函数,所以直接继承Function.prototype。但是,A调用后返回的对象不继承任何方法,所以它的proto指向Function.prototype,即实质上执行了下面的代码。

1
2
3
class C extends null {
constructor() { return Object.create(null); }
}

第四种情况,允许继承原生构造函数定义子类,但无法通过super方法向父类Object传参

1
2
3
4
5
6
7
class NewObj extends Object{
constructor(){
super(...arguments);
}
}
var o = new NewObj({attr: true});
console.log(o.attr === true) // false

Mixin 模式

使用如下mix函数将多个对象合成为一个类

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
function mix(...mixins) {
class Mix {}

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);
}
}
}

//使用:继承返回的合成类
class DistributedEdit extends mix(Loggable, Serializable) {
// ...
}

几种创建方式对比

let a = null ===> null
let b = Object.create(null) ===> {} 但没有任何属性
let c = {} ===> {} proto指向Object
let d = new Object() ===> {} proto指向Object
let e = new Object(null) ===> {} proto指向Object
let f = Object.create(c) ===> {} proto指向c, proto.proto指向Object
!(obj4)[/image/ob4.png]
c instanceof Object , d instanceof Object ,e instanceof Object ===> true

let dd = {a:1}
let cc = new Object(dd)
let ee = Object.create(dd)
console.log(cc,ee)
!(obj3)[/image/ob3.png]
dd.isPrototypeOf(cc) ===>false
dd.isPrototypeOf(ee) ===>true