ECMAScript學習筆記(七)——繼承
ECMAScript依靠原型鏈來實現繼承;
原型鏈
原型鏈,是利用原型讓一個引用型別繼承另一個引用型別的屬性和方法。
每個建構函式都有一個原型物件,原型物件包含一個指向建構函式的指標,而例項都包含一個指向原型物件的內部指標。
如果,一個原型又是另一個原型的例項,那麼上述關係依舊成立,如此層層遞進,就構成了例項和原型的鏈條,就是所謂的原型鏈
eg:
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } function SubType() { this.subproperty = false; } SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function() { return this.subproperty; }; var instance = new SubType();
以上程式碼定義了兩個型別:SuperType和SubType。SubType繼承了SuperType。 這個繼承是通過:建立一個SuperType的例項,然後將這個例項賦值給SubType.prototype實現的。
實現的本質是:重寫原型物件,代之以一個新型別的例項。
所有的引用型別都預設幾成了Object。這個繼承也是通過原型鏈實現的。
原型和例項的關係:可以通過instanceof操作符和isPrototypeOf()方法來確認。
給原型新增方法的程式碼,一定要放在替換原型的語句之後。
function SuperType() { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; } function SubType() { this.subproperty = false; } SubType.prototype = new SubperType(); SubType.prototype.getSubValue = function() { return this.subproperty; } SubType.prototype.getSuperValue = function() { return false; } var instance = new SubType(); alert(instance.getSuperType()); // false
以上程式碼,添加了getSubValue()方法到SubType中,而重寫了SubType中的getSubType()方法,將SuperType中的getSuperValue()覆蓋了。
原型鏈的問題
1.在通過原型實現繼承時,原型會變成另一個型別的例項。於是,原先的例項屬性就會變成現在的原型屬性了。
eg:
function SuperType() { this.color = {"red", "blue", "green"}; } function SubType() { } SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors); // red, blue, green, black var instance2 = new SubType(); alert(instance2.colors); // red, blue, green, black
2.建立子型別的例項時,不能向超型別的建構函式中傳遞引數。
借用建構函式
為了解決原型中包含引用型別值,所帶來的問題。人們開始使用一種叫借用建構函式
的技術(偽造物件,或者經典繼承)。
這種方法:在子型別構造器的內部呼叫超型別的建構函式。
eg:
function SuperType() { this.colors = ["red", "blue", "green"]; } function SubType() { SuperType.call(this); } var instance1 = new SubType(); instance1.colors.push("black"); // red, blue, green, black var instance2 = new SubTyp(); instance2.colors // red, blue, green
上述程式碼,在建構函式中,呼叫了超型別的建構函式。等於說是在這個建構函式中,執行了一邊超型別的建構函式的程式碼。
另外,這個方式還可以像超型別的建構函式傳遞引數:
function SuperType(name) { this.name = name; } function SubType() { SuperType.call(this, "Nicholas"); this.age = 29; } var instance = new SubType(); alert(instance.name); // "Nicholas" alert(instance.age); // 29
這種方式存在的問題:這種方式的繼承,方法都在建構函式中定義,所以就沒辦法做函式複用了。
組合繼承
組合繼承,是指將原型鏈和借用建構函式組合。
eg:
function SuperType(name) { this.name = name; this.colors = {"red", "blue", "green"}; } SuperType.prototype.sayName = function() { alert(this.name); }; function SubType(name, age) { SuperType.call(this, name); this.age = age; } SubType.prototype = new SuperType(); SubType.prototype.sayAge = function() { alert(this.age); }; var instance1 = new SubType("Nicholas", 29); instance1.colors.push("black"); // red, blue, green, black instance1.sayName(); // Nicholas instance1.sayAge(); // 29 var instance2 = new SubType("Gred", 27); instance2.colors; // red, blue, green instance2.sayName(); // Gred instance2.sayAge(); // 27
這是JavaScript中最常用的繼承模式了。
原型式繼承
基於原有的物件建立新物件,就不必建立自定義型別了。
function object(o) { function F(){} F.prototype = o; return new F(); }
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = object(person); anotherPerson.name = "Greg"; anotherPerson.friends.push("Rob"); var yetAnothorPerson = object(person); yetAnothorPerson.name = "Linda"; yetAnothorPerson.friends.push("Barbie"); alert(person.friends); // "Shelby", "Court", "Van", "Rob", "Barbie"
這種原型式繼承,要求必須有一個物件可以作為另一個物件的基礎。
ECMAScript 5新增的Object.create()方法,規範了原型式繼承:
var person = { name: "Nicholas", friends: ["Shelby", "Court", "Van"] }; var anotherPerson = Objcet.create(person); var yetAnothorPerson = Objcet.create(person, { name: { value: "Greg" } });
這種方式,適用於只想讓一個物件和另一個物件保持相思的情況。
寄生式繼承
function createAnther(original) { var clone = object(original); clone.sayHi = function() { alert("Hi"); }; return clone; }