默認的繼承方法:經過原型來實現繼承關係鏈javascript
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; }; }
繼承的代碼:java
TwoDShape.prototype = new Shape(); Triangle.prototype = new TwoDShape();
對對象的prototype屬性進行徹底替換時(不一樣於向prototype指向的對象添加屬性,有可能會對對象的constructor屬性產生必定的反作用),數組
因此對這些對象的constructor屬性進行相應的重置:ide
TwoDShape.prototype.constructor = TwoDShape; Triangle.prototype.constructor = Triangle;
測試一下實現的內容:函數
var my = new Triangle(5, 10); my.getArea(); --25
my.toString(); --"Triangle"
在JavaScript引擎在my.toString()被調用時發生的事情。測試
1.會遍歷my對象中的全部屬性,沒找到一個叫toString()的方法。this
2.再去查看my.__proto__所指向的對象,該對象應該是在繼承關係構建中由new ToDShape()所建立的實體prototype
3.JS在遍歷ToDShape實體的過程當中依然不會找到toString方法,而後又繼續檢查該實體的__proto__屬性,該__proto__屬性所指向的實體由new Shape()所建立指針
4.在new Shape()所建立的實體中找到了toString()方法對象
5.該方法就會在my對象中被調用,而且其this指向了my
my.constructor === Triangle; --true
經過instanceof,能夠驗證my對象同時是上述三個構造器的實例:
my instanceof Shape; --true my instanceof TwoDShape; --true my instanceof Triangle; --true
my instanceof Array; --false
以my參數調用這些構造器原型的isPropertypeOf()方法時,結果也是如此:
Shape.prototype.isPrototypeOf(my); --true TwoDShape.prototype.isPrototypeOf(my); --true Triangle.prototype.isPrototypeOf(my); --true String.prototype.isPrototypeOf(my); --false
用其餘兩個構造器來建立對象,用new TwoDShape()所建立的對象也能夠得到繼承自Shape()的toString()的方法。
var td = new TwoDShape(); td.constructor === TwoDShape; --true
td.toString(); "2D Shape"
var s = new Shape(); s.constructor === Shape; --true
2.將共享屬性遷移到原型中去:
用某一個構造器建立對象時,其屬性就會被添加到this中去,被添加的屬性實際上不會隨着實體改變,這種作法沒有什麼效率。
function Shape() { this.name = 'Shape'; }
用new Shape()建立的每一個實體都會擁有一個全新的name屬性,並在內存中擁有本身的獨立存儲空間,能夠將name屬性添加到原型上去,全部實體就能夠共享這個屬性
function Shape() { } Shape.prototype.name = 'Shape';
將全部的方法和符合條件的屬性添加到原型對象中,Shape()和TwoDShape()而言,全部東西都是能夠共享的
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"
也能夠經過hasOwnPrototype()屬性來明確對象的自身屬性和原型屬性
my.hasOwnProperty('side'); --true
my.hasOwnProperty('name'); false
TwoDShape.prototype.isPrototypeOf(my); --true
my instanceof Shape; --true
3.只繼承與原型:
1.不要單獨爲繼承關係建立新對象
2.儘可能減小運行時的方法搜索
function Shape() { } Shape.prototype.name = 'shape'; Shape.prototype.toString = function () { return this.name; }; function TwoDShape() { } TwoDShape.prototype = Shape.prototype; TwoDShape.prototype.constructor = TwoDShape; TwoDShape.prototype.name = '2D Shape'; function Triangle(side, height) { this.side = side; this.height = height; } Triangle.prototype = TwoDShape.prototype; 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"
以上代碼採用了引用傳遞而不是值傳遞。
簡單的拷貝原型在效率上來講當然好一些,但有他的反作用,子對象和父對象指向同一個對象,一旦子對象對其原型就行修改,父對象也會隨即被改變,如:
Triangle.prototype.name = 'Triangle'; var s = new Shape(); s.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 my = new Triangle(5, 10); my.getArea(); --25
my.toString(); --"Triangle"
經過這種方法,咱們就能夠保持住原型鏈:
my.__proto__ === Triangle.prototype; --true my.__proto__.constructor == Triangle; --true my.__proto__.__proto__ === TwoDShape.prototype; --true my.__proto__.__proto__.__proto__.constructor === Shape; --true
而且父對象的屬性不會被子對象所覆蓋:
var s = new Shape(); s.name; --"Shape"
"I am a " + new TwoDShape(); --"I am a 2D shape"
將全部要共享的屬性與方法添加到原型中,而後只圍繞原型構建繼承關係。
3.uber--子對象訪問父對象的方式(指向父級原型對象)
function Shape() { } Shape.prototype.name = 'shape'; Shape.prototype.toString = function () { //var const1 = this.constructor; return this.constructor.uber ? this.constructor.uber.toString() + ', ' + this.name : this.name; }; 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 thi.side * this.height / 2; }; var my = new Triangle(5, 10); my.toString(); --"shape, 2D shape, Triangle"
增長如下內容:
1.將uber屬性設置成指向其父級原型的引用
2.對toString()方法進行了更新
檢查對象中是否存在this.constructor.uber屬性,若是存在,就先調用該屬性的toString方法,因爲this.constructor自己是一個函數,而this.constructor.uber則是指向當前對象父級原型的引用。
當調用Triangle實體的toString()方法時,其原型鏈上所用的toString()都會被調用。
4.將繼承部分封裝成函數:
function extend(Child, Perent) { var F = function () { }; F.prototype = Perent.prototype; Child.prototype = new F(); Child.prototype.constructor = Child; Child.uber = Perent.prototype; } function Shape() { }; Shape.prototype.name = 'Shape'; Shape.prototype.toString = function () { return this.constructor.uber ? this.constructor.uber.toString() + ', ' + this.name : this.name; }; function TwoDShape() { }; extend(TwoDShape, Shape); TwoDShape.prototype.name = '2D shape'; function Triangle(side, height) { this.side = side; this.height = height; } extend(Triangle, TwoDShape); Triangle.prototype.name = 'Triangle'; Triangle.prototype.getArea = function () { return this.side * this.height / 2; } new Triangle().toString(); --"Shape, 2D shape, Triangle"
6.屬性拷貝
將父對象的屬性拷貝給子對象,
function extend2(Child, Parent) { var p = Parent.prototype; var c = Child.prototype; for (var i in p) { c[i] = p[i]; } c.uber = p; }
這種方法僅適用於只包含基本數據類型的對象,全部的對象類型(包括函數與數組)都是不可複製的,他們只支持引用傳遞。
Shape的原型中包含了一個基本類型屬性name,和一個非基本類型屬性---toString()方法
var Shape = function () { }; var TwoDShape = function () { }; Shape.prototype.name = 'shape'; Shape.prototype.toString = function () { return this.uber ? this.uber.toString() + ', ' + this.name : this.name; };
經過extend()方法實現繼承,name屬性既不會是TwoDShape()實例的屬性,也不會成爲其原型對象的屬性,可是子對象依然能夠經過繼承方式來訪問該屬性
extend(TwoDShape, Shape); var td = new TwoDShape(); td.name;
--"shape"
TwoDShape.prototype.name; --"shape"
td.__proto__.name; --"shape"
td.hasOwnProperty('name'); --false
td.__proto__.hasOwnProperty('name'); --false
繼承經過extend2()方法來實現,TwoDShape()的原型中就會拷貝得到屬於本身的name屬性,一樣也會拷貝toString()方法,但這只是一個函數引用,函數自己並無被再次建立
extend2(TwoDShape, Shape); var td = new TwoDShape(); td.__proto__.hasOwnProperty('name');
--true
td.__proto__.hasOwnProperty('toString'); --true
td.__proto__.toString === Shape.prototype.toString; --true
extend2()方法的效率要低於extend()方法,主要是前者對部分原型屬性進行了重建
td.toString(); --"shape, shape"
TwoDShape並無從新定義name屬性,因此打印了兩個Shape,能夠在任什麼時候候從新定義name屬性,
TwoDShape.prototype.name = "2D shape"; td.toString(); --"shape, 2D shape"
6.當心處理引用拷貝
對象類型(包括函數與數組)一般都是以引用形式來進行拷貝的,會致使一些預期不一樣的結果:
function Papa() { } function Wee() { } Papa.prototype.name = 'Bear'; Papa.prototype.owns = ["porridge", "chair", "bed"];
讓Wee繼承Papa(經過extend()或extend2()來實現):
extend2(Wee, Papa);
即Wee的原型繼承了Papa的原型屬性,並將其變成了自身屬性
Wee.prototype.hasOwnProperty('name'); ---true Wee.prototype.hasOwnProperty('owns'); ---true
name屬於基本類型屬性,建立的是一份全新的拷貝,owns屬性是一個數組對象,它執行的引用拷貝。
Wee.prototype.owns; -- ["porridge", "chair", "bed"] Wee.prototype.owns === Papa.prototype.owns; --true
改變Wee中的name屬性,不會對Papa產生影響:
Wee.prototype.name += ', Little Bear'; --"Bear, Little Bear"
Papa.prototype.name; --"Bear"
若是改變的是Wee的owns屬性,Papa就會受到影響,這兩個屬性在內存中引用的是同一個數組:
--pop() 方法用於刪除並返回數組的最後一個元素。
Wee.prototype.owns.pop(); --"bed"
Papa.prototype.owns; --["porridge", "chair"]
用另外一個對象對Wee的owns屬性進行徹底重寫(不是修改現有屬性),這種狀況下,Papa的owns屬性將會繼續引用原有對象,而Wee的owns屬性指向了新對象。
Wee.prototype.owns = ["empty bowl", "broken chair"]; Papa.prototype.owns.push('bed'); Papa.prototype.owns; -- ["porridge", "chair", "bed"]
7.對象之間的繼承
在對象之間進行直接屬性拷貝
用var o={}語句建立一個沒有任何私有屬性的「空」對象做爲畫板,逐步爲其添加屬性,將現有對象的屬性所有拷貝過來
function extendCopy(p) { var c = {}; for (var i in p ) { c[i] = p[i]; }
var twoDee = extendCopy(shape); twoDee.name = '2D shape'; twoDee.toString = function () { return this.uber.toString() + ', ' + this.name; };
c.uber = p; return c; }
建立一個基本對象:
var shape = { name: 'Shape', toString: function () { return this.name; } };
根據就對象來建立一個新對象,調用extendCopy()函數,返回一個新對象,繼續對這個新對象進行擴展,添加額外的功能
var twoDee = extendCopy(shape); twoDee.name = '2D shape'; twoDee.toString = function () { return this.uber.toString() + ', ' + this.name; };
讓triangle對象繼承一個2D圖形對象。
var triangle = extendCopy(twoDee); triangle.name = 'Triangle'; triangle.getArea = function () { return this.side * this.height / 2; };
使用triangle:
triangle.side = 5; triangle.height = 10; triangle.getArea();
--25
triangle.toString();
---"Shape, 2D shape, Triangle"
8.深拷貝
深拷貝的實現方式與淺拷貝基本相同,須要經過遍歷對象的屬性來進行拷貝操做,在遇到一個對象引用性的屬性時,須要再次對其調用深拷貝函數。
當對象被拷貝時,實際上拷貝的只是該對象在內存中的位置指針----淺拷貝(若是咱們修改了拷貝對象,就等於修改了原對象)
function deepCopy(p, c) { c = c || {}; for (var i in p) { if (p.hasOwnProperty(i)) { if (typeof p[i] === 'object') { c[i] = Array.isArray(p[i]) ? [] : {}; 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 };
在深拷貝中,對拷貝對象的numbers屬性進行更改不會對原對象產生影響
var mydeep = deepCopy(parent); var myshallow = extendCopy(parent); mydeep.numbers.push(4, 5, 6);
mydeep.numbers; ---- [1, 2, 3, 4, 5, 6]
parent.numbers; ---- [1, 2, 3]
myshallow.numbers.push(10); --4
myshallow.numbers; -- [1, 2, 3, 10]
parent.numbers; -- [1, 2, 3, 10]
mydeep.numbers; --- [1, 2, 3, 4, 5, 6]
push:方法將一個或多個元素添加到數組的末尾,並返回新數組的長度
使用deepCopy()函數注意的地方:
1.在拷貝每一個屬性以前,使用hasOwnProperty()來確認不會誤拷貝不須要的繼承屬性
2.區分Array對象和普通Object對象至關繁瑣,ES5實現了Array.isArray()函數。
if (Array.isArray != "function") { Array.isArray = function (candidate) { return Object.prototype.toString.call(candidate) === '[Object Array]'; }; }
9.Object()
基於這種在對象之間直接構建構建繼承關係的理念,便可以用Object()函數來接收父對象,並返回一個以對象爲原型的新對象