繼承模式
# 原型鏈
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"
```
# 小結
實現繼承的方法(模式)
* 基于構造器工作的模式;
* 基于對象工作的模式;
分類條件:
* 是否使用原型;
* 是否執行屬性拷貝;
* 兩者都有(原型屬性拷貝);