继承模式
# 原型链
new创建的对象 通过__proto__指向 其原型对象;
原型对象也具有对象固有的普遍特征,又指向原型对象的原型对象,以此类推,形成一条链条。
```
function Shape(){
this.name = 'shape';
this.toString = function () {
return this.name;
}
}
function TwoDShape(){
this.name = '2D shape';
}
function Triangle(side, height){
this.name = 'Triangle';
this.side = side;
this.height = height;
this.getArea = function(){
return this.side * this.height/2;
}
}
Shape.prototype
// {constructor: ƒ} constructor: ƒ Shape() __proto__: Object
Shape.constructor
// ƒ Function() { [native code] }
TwoDShape.prototype
// {constructor: ƒ} constructor:ƒ TwoDShape() __proto__:Object
TwoDShape.constructor
// ƒ Function() { [native code] }
Triangle.prototype
// {constructor: ƒ} constructor:ƒ Triangle(side, height) __proto__:Object
Triangle.constructor
// ƒ Function() { [native code] }
// 另建一个新实体,用它覆盖对象的原型
TwoDShape.prototype = new Shape();
Triangle.prototype = new TwoDShape();
Shape.prototype
// {constructor: ƒ}
Shape.constructor
// ƒ Function() { [native code] }
TwoDShape.prototype
// Shape {name: "shape", toString: ƒ}
TwoDShape.constructor
// ƒ Function() { [native code] }
Triangle.prototype
// Shape {name: "2D shape"}
Triangle.constructor
// ƒ Function() { [native code] }
new TwoDShape().constructor
new Triangle().constructor
new Shape().constructor
// ƒ Shape(){
// this.name = 'shape';
// this.toString = function () {
// return this.name;
// }
// }
// 将对象的prototype重写,有可能影响对象的contructor属性,记得重置!
TwoDShape.prototype.constructor = TwoDShape;
Triangle.prototype.constructor = Triangle;
var my = new Triangle(5, 10);
my.getArea();
my.toString();
my.constructor;
my instanceof Triangle;
my instanceof TwoDShape;
my instanceof Shape;
my instanceof Array;
Triangle.prototype.isPrototypeOf(my)
TwoDShape.prototype.isPrototypeOf(my)
Shape.prototype.isPrototypeOf(my)
String.prototype.isPrototypeOf(my)
```
## 将共享属性迁移到原型中去
```
function Shape(){
this.name = 'shape';
}
```
用构造器创建对象时,每个实体都会有一个全新的属性,拥有独立的存储空间;
```
function Shape(){}
Shape.prototype.name = 'shape';
```
对于不可变属性(共享属性)及共享方法来说,这样处理更有效率。
```
function Shape(){}
Shape.prototype.name='shape';
// "shape"
Shape.prototype.constructor= function(){return this.name;}
// ƒ (){return this.name;}
function TwoDShape(){}
TwoDShape.prototype.name = '2D shape'; // 先扩展
// "2D shape"
TwoDShape.prototype = new Shape(); // 后继承
// Shape.constructor {}
TwoDShape.prototype.constructor = TwoDShape;
// ƒ TwoDShape(){}
TwoDShape.prototype.name;
// "shape" // 被覆盖
```
💗通常**先**完成相关的**继承**关系构建,**再**对原型对象进行**扩展**!
```
function Shape(){};
Shape.prototype.name = 'shape';
Shape.prototype.toString = function(){return this.name;};
function TwoDShape(){};
// 先继承
TwoDShape.prototype = new Shape();
TwoDShape.prototype.constructor = TwoDShape;
// 后扩展
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height){
this.side = side;
this.height = height;
}
Triangle.prototype = new TwoDShape();
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = 'triangle';
Triangle.prototype.getArea = function(){
return this.side * this.height / 2;
}
var my = new Triangle(5, 10);
my.getArea(); // 25
my.toString(); // triangle
my.hasOwnProperty('name'); // false
my.hasOwnProperty('height'); // true
TwoDShape.prototype.isPrototypeOf(my); // true
my instanceof Shape; // true
```
# 只继承于原型
处于效率方面的考虑,应**尽可能将可重用的属性和方法添加到原型中**
原型中的代码都是可重用的
改善效率的做法:
* 不要单独为继承关系创建新对象
* 尽量减少运行时方法搜索
```
...
TwoDShape.prototype = Shape.prototype;
...
Triangle.prototype = TwoDShape.prototype;
...
my.toString(); // 此时查找次数相较之前的方式有变化吗?
```
toString方法只会查找两次,自身一次,Shape.prototype一次就可以了。
采用了引用传递,都指向Shape.prototype
❌副作用:子对象对原型的修改,会影响父对象及所有继承关系。如:
`Triangle.prototype.name = 'triangle';` 相当于:`Shape.prototype.name = 'triangle';`
## 临时构造器——new F()
空函数f(),中介作用,打破所有属性都指向一个相同的对象。
```
function Shape(){};
Shape.prototype.name = 'shape';
Shape.prototype.toString = function(){return this.name;};
function TwoDShape(){};
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height){
this.side = side;
this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.prototype.name = 'triangle';
Triangle.prototype.getArea = function(){
return this.side * this.height / 2;
}
var mu = new Triangle(5, 10);
mu.getArea(); // 25
mu.toString(); // "triangle"
var s = new Shape();
s.toString(); // "shape"
```
**围绕原型构建继承关系!**
# uber——子对象访问父对象的方式
```
function Shape(){};
Shape.prototype.name = 'shape';
Shape.prototype.toString = function(){
var result = [];
// 先检查是否存在uber属性
if(this.constructor.uber){
// 调用toString()
result[result.length] = this.constructor.uber.toString();
}
result[result.length] = this.name;
// 放进数组并返回
return result.join(', ');
};
function TwoDShape(){};
var F = function(){};
F.prototype = Shape.prototype;
TwoDShape.prototype = new F();
TwoDShape.prototype.constructor = TwoDShape;
TwoDShape.uber = Shape.prototype;
TwoDShape.prototype.name = '2D shape';
function Triangle(side, height){
this.side = side;
this.height = height;
}
var F = function(){};
F.prototype = TwoDShape.prototype;
Triangle.prototype = new F();
Triangle.prototype.constructor = Triangle;
Triangle.uber = TwoDShape.prototype; // 指向父级原型
Triangle.prototype.name = 'triangle';
Triangle.prototype.getArea = function(){
return this.side * this.height / 2;
}
var my = new Triangle(5, 10);
my.toString();
// "shape, 2D shape, triangle"
```
# 将继承部分封装成函数
```
function extend(Child, Parent){
var F = function(){};
F.prototype = Parent.prototype;
Child.prototype = new F();
Child.prototype.constructor = Child;
Child.uber = Parent.prototype;
}
```
# 属性拷贝
```
function extend2(Child, Parent){
var p = Parent.prototype;
var c = Child.prototype;
for(var i in p){
c[i] = p[i];
}
c.uber = p;
}
extend2()方法的效率要低于extend()方法,主要是前者对部分原型属性进行了重建。不过,属性查找操作会更多的停留在对象本身,从而减少了原型链上的查找。
```
# 对象之间的继承
在对象之间进行直接属性拷贝
浅copy:拷贝对象在内存中位置的指针
```
// 直接将现有对象的属性全部拷贝,浅copy
function extendCopy(p){
var c = {};
for(var i in p){
c[i] = p[i];
}
c.uber = p;
return c;
}
// 基本对象,模板
var shape = {
name: 'shape',
toString: function(){
return this.name;
}
}
// 创建新对象
var twoDee = extendCopy(shape);
// 扩展
twoDee.name = '2D shape';
twoDee.toString = function(){return this.uber.toString() + ', ' + this.name;};
var triangle = extendCopy(twoDee);
triangle.name = 'triangle';
triangle.getArea = function(){
return this.side * this.height /2;
}
// 用init()函数解决手动设置的繁琐过程
triangle.side = 5;triangle.height = 10; triangle.getArea();
// 25
triangle.toString();
// "shape, 2D shape, triangle"
```
# 深拷贝
实现方式同浅拷贝,遍历对象的属性,在遇到对象引用属性,再次调用深拷贝函数。
```
function deepCopy(p, c){
var c = c || {};
for(var i in p){
if(typeof p[i] === 'object'){
c[i] = (p[i].constructor === Array)? [] : {};
deepCopy(p[i], c[i]);
} else {
c[i] = p[i];
}
}
return c;
}
var parent = {
numbers: [1,2,3],
letters: ['a','b','c'],
obj: {
prop: 1
},
bool: true
}
var mydeep = deepCopy(parent);
mydeep.numbers.push(4,5,6);
mydeep.numbers; // [1,2,3,4,5,6]
parent.numbers; // [1,2,3]
```
# object()
```
function object(o){
function F(){};
F.prototype = o;
return new F();
}
function object(o){
var n;
function F(){};
F.prototype = o;
n = new F();
n.uber = o;
return n;
}
var triangle = object(twoDee);
triangle.name = 'triangle';
triangle.getArea = function(){return this.side * this.height / 2;};
```
原型继承:将父对象设置成子对象的原型。
# 原型继承与属性拷贝混合应用
继承,主要目标是将一些现有的功能归为己有。
* 使用原型继承的方式克隆现存对象
* 对其他对象使用属性拷贝的方式
```
function objectPlus(o, stuff){
var n;
function F(){};
F.prototype = o;
n = new F();
n.uber = o;
for(var i in stuff){
n[i] = stuff[i];
}
return n;
}
```
对象o用于继承,对象stuff用于拷贝方法与属性。
```
var shape = {
name: 'shape',
toString: function(){
return this.name;
}
}
var twoDee = objectPlus(shape, {name: '2D shape', toString: function(){return this.uber.toString() + ', ' + this.name}});
var triangle = objectPlus(twoDee, {name: 'triangle',side: 0, height: 0, getArea: function(){return this.side * this.height / 2; }});
var my = objectPlus(triangle, {side: 4, height: 4});
my.getArea()
// 8
my.toString();
// "shape, 2D shape, triangle, triangle"
```
# 多重继承
```
// 双重循环,外层遍历传递的对象,内层复制属性
function multi(){
var n = {}, stuff, j = 0, len = arguments.length;
for(j = 0; j < len; j++){
stuff = arguments[j];
for(var i in stuff){
n[i] = stuff[i];
}
}
return n;
}
var shape = {
name: 'shape',
toString: function(){return this.name}
}
var twoDee = {
name: '2D shape',
dimensions: 2
}
// 继承多个对象
var triangle = multi(shape, twoDee, {name: 'triangle',side: 5, height: 10, getArea: function(){return this.side * this.height / 2 }});
triangle.getArea(); // 25
triangle.dimensions; // 2
triangle.toString(); // "triangle"
```
## 混合插入 mixins
不通过子对象的继承与扩展来完成继承
# 寄生式继承
在创建对象的函数中直接吸收其他对象的功能,然后对其进行扩展并返回。
```
function object(o){
var n;
function F(){};
F.prototype = o;
n = new F();
n.uber = o;
return n;
}
var twoD = {
name: '2D shape',
dimensions: 2
}
function triangle(s, h){
var that = object(twoD);
that.name = 'triangle';
that.getArea = function(){return this.side * this.height / 2};
that.side = s;
that.height = h;
return that;
}
var t = triangle(5, 10);
t.dimensions; // 2
var t2 = new triangle(5,5);
t2.getArea(); // 12.5
```
* 将twoD对象克隆进that对象;
* 扩展that对象;
* 返回that对象;
# 构造器借用
子对象构造器通过call()或apply()方法来调用父对象的构造器。
```
function Shape(id){
this.id = id;
}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function(){return this.name};
function Triangle(){
Shape.apply(this, arguments);
}
Triangle.prototype.name = 'triangle';
var t = new Triangle(101);
t.name; // "triangle"
t.id; // 101
t.toString(); // "[object Object]"
```
## 借用构造器与原型复制
```
function extend2(Child, Parent){
var p = Parent.prototype;
var c = Child.prototype;
for(var i in p){
c[i] = p[i];
}
c.uber = p;
}
function Shape(id){
this.id = id;
}
Shape.prototype.name = 'shape';
Shape.prototype.toString = function(){return this.name};
function Triangle(){
Shape.apply(this, arguments);
}
extend2(Triangle, Shape);
Triangle.prototype.name = 'triangle';
var t = new Triangle(101);
t.toString(); // "Triangle"
t.id; // 101
typeof t.__proto__.id; // "undefined"
t.uber.name; // "shape"
```
# 小结
实现继承的方法(模式)
* 基于构造器工作的模式;
* 基于对象工作的模式;
分类条件:
* 是否使用原型;
* 是否执行属性拷贝;
* 两者都有(原型属性拷贝);