网上有很多关于继承的东西. 渡一讲得个人觉得是最由浅入深的. 渡一的可以直接去腾讯课堂搜索. 我们这里在借鉴一两个博客,来抄写整理一下.
JS实现继承的几种方式 - 幻天芒 - 博客园
web前端开发必懂之一:JS继承和继承基础总结
首先不要被各种名字所吓倒. 工厂模式 原型链继承 构造继承 实例继承 拷贝继承 组合继承 寄生组合继承 等等.
如果清楚 实例,构造函数,原型之间的关系,那么一切都好理解.
我写的系统性肯定没那么强, 我每次都是按照我当时的水平, 按照自己理解的线索写的. 想要系统性的体系讲解,上面那两个博客也可以, 百度一下应该都有.
首先,我有一个误会,误解. 什么是继承? 我本以为的继承是, 实例继承原型. 也该是这个. 但我们看一下所谓的工厂模式,构造继承.
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
构造继承
function Father (name,age) {
this.name = name;
this.age = age;
}
function Son (name, age, height) {
Father.call(this,name , age);
this.height = height;
}
var son = new Son('mike',18,180);
你发现了嘛? 实际上son 是无法访问 Father的原型链的.也就是没有继承Father.prototype.
但这种也叫继承.
然后还有 工厂模式
function Son (name,age) {
var obj = new Object();
obj.name = name;
obj.age = age;
return obj
}
var son = Son('mike',18);
son 也是没有继承 Son.prototype的
因为我刚开始对继承的定义有误差,所以刚开始,我不太理解什么叫多继承?
要怎么样才能让一个对象继承多个没有继承关系的原型? 很难啊.不可能啊.
结果发现继承还指这种情况.
所谓的多继承就是.
我可以Father.call() Mother.call() Uncle.call()
我觉得这个当然也很重要.
但很明显,我觉得继承原型这个事可能更重要一点. 那我们到底是怎么继承的?
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
Son.prototype.height = 180;
function Son (name,age) {
this.name = name;
this.age = age;
}
var son = Son('mike',18);
console.log(son);
最基本的继承是,一个构造函数,new 出来的实例对象, 能继承函数的原型.
其实当时我刚碰到这个的时候,实在是很难理解.(你肯定耻笑我)
难以理解在哪里呢?
我们知道,现在有三个要素, 一个实例,一个函数,一个原型.
我们可以说, 实例的原型, 也可说 函数的原型.
我们可以说,实例的构造函数,也可说 原型的构造函数.
这尼玛,到底是谁的?
还好,我们应该只能说 这个函数的实例, 而不会说这个原型的实例.
我为什么纠结这个?
因为我想不通啊, 构造函数可以new出一个对象,
可现在是new出了两个对象啊, 一个实例,一个原型.
后来我明白了,刚开始应该是'函数的原型'.
为什么这么说呢?
因为一个函数即使他没有new ,只要定义了,他的prototype就会存在.
同理,即使没有new,这个原型对象的构造函数就是这个函数.
感觉说的不是人话..
Son.prototype.height = 180;
function Son (name,age) {
this.name = name;
this.age = age;
}
console.log(Son.prototype.height);// 可以访问.
console.log(Son.prototype.constructor);// 就是 Son
也就是说,一个实例上确实是没有constructor 这个属性的.
他访问的是原型上的constructor;
然后我们就能很好的理解这个现象了.
function Father () {}
function Son () {}
Son.prototype.constructor == Son // true 这个时候理解没问题
Son.prototype = new Father();// 此时Son.prototype这个对象被赋值了,原来的属性都没了.
Son.prototype.constructor == Father// true new Fahter()
这个实例上没有,去找Father.prototype 上的constructor 当然返回 Father了.
其实说的很多,里面的东西很少.
反正我们理解了,即使没有new ,Function 被定义的时候, 就已经有Function.prototype了,
那么new的时候到底干了什么?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
模拟写一次new.
var myNew = function (fn) {
var self = Object.create(fn.prototype);// 这个就是用来让self 继承 fn.prototype的.
return function () {
fn.apply(self,arguments);
return self;
}
}
function Son () {
this.name = 'mike';
return 123
}
var son = myNew(Son)();
console.log(son);
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
一旦new出一个实例之后, 会给实例添加一个属性,__proto__,
这个属性指向的就是构造函数的原型,一旦某个属性实例上没有,
就会去__proto__上去找.
刚new的时候, 函数的原型和实例的原型是一样的.
function Son () {
this.name = 'mike';
return 123
}
var son = new Son();
console.log(Son.prototype === son.__proto__);// 返回true
可是..
Son.prototype = {age:18};
console.log(Son.prototype === son.__proto__);// 返回false
console.log(son.age);//undefined;
其实这个好理解, Son.prototype 和 son.__proto__ 刚开始都指向同一个指针,
只要其中一个用 = 重新赋值,就变得毫无相干.
要谨记的是, son 每次访问的不是 Son.prototype 而是 son.__proto__
更神奇的是这个
console.log(son.constructor);// 返回 Son ,因为原来的原型里constructor 存的就是Son
console.log(son instanceof Son);// 返回 false! 这你敢信?
在写另一篇里我就发现 这个instanceof 是不靠谱的,
只有这个构造函数当前指向的原型在实例的原型链上,才会返回true;
说到这里,我猜你已经被我的文笔搞得想吐了, 我权当你已经完全明白我刚才讲的东西是什么. 我们回到继承问题.
我们假定,我们的需求就是要继承原型.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
共享模型
function Father () {}
function Son () {}
Son.prototype = Father.prototype;
var son = new Son();
封装成函数
function inherit (Target, Origin) {
Target.prototype = Origin.prototype;
}
缺点是,在Son.prototype上添加东西,会同时改动Father.prototype.
这个时候,我们回头看一看 工厂模式.
就会发现,这种继承和那种继承压根就是两回事吧?
难道就我自己这么觉得嘛?
当然此时我们在function Son里也可以用工厂模式.
我们想加哪个工厂加哪个工厂,不限数量.
但原型继承只能指定一个.
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
圣杯模式
很明显,这个才是值钱的. 以下三种其实都一样.
版本1.0
function inherit (Target, Origin) {
function F () {};
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uper = Origin.prototype;
}
版本2.0
var inherit = (function () {
function F () {};
return function (Target, Origin) {
F.prototype = Origin.prototype;
Target.prototype = new F();
Target.prototype.constructor = Target;
Target.prototype.uper = Origin.prototype;
}
})();
版本3.0
function inherit (Target, Origin) {
var obj =Object.create(Origin.prototype);// 让obj继承Origin.prototype,说过了是吧.
Target.prototype = obj;
Target.prototype.constructor = Target;
Target.prototype.uper = Origin.prototype;
}
你可能会好奇
为什么不绕过F这样
Target.prototype = new Origin();
因为new Origin() 出来的对象不是空对象, 或者我们只想继承他原型的,不想继承乱七八糟的.
其实写到这里,应该结束了. 但我想再写一写, 回头把这部分另起一篇也可以.
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
1. new是什么? 看到new 就是用来继承原型的. 如果不继承原型, 不用new也可以达到.
2.我们重新看一下一个函数,当我们看到一个函数的时候,
应该看到什么?
(function () {
区域1
var obj = {name: 'mike'};
fun.prototype = 区域2
function fun () {
区域3
var count = 1;
区域4
this.age = age;
return {区域5}
}
})();
上面这个结构, 只是我自己想要让自己习惯的一种看函数的方式.
区域1 是 fun的 私有变量区域.
区域2 是原型区域
区域3 是正常函数区域,可以定义变量,也可以执行代码,如果区域5返回函数,那么也成为私有变量区域.
区域4 工厂区? 也就是实例化的地方
区域5 实际上是硬加的. 但return 可以返回任何形式的东西.
所以一个函数返回一个对象的方式有两种,
一种是return {}
一种是 new () new 的方式 增加了一个原型.