JavaScript 中的繼承比較奇葩,無法實作介面繼承,只能靠原型繼承。
原型鏈
原型就是一個對象,透過建構函式創建出來的實例會有指針指向原型得到原型的屬性和方法。這樣,實例物件就帶有建構函式的屬性方法和原型的屬性方法,然後將需要繼承的建構函式的原型指向這個實例,即可擁有這個實例的所有屬性方法實作繼承。
看下面示範程式碼:
//声明超类,通过构造函数和原型添加有关属性和方法 function Super(){ this.property = true; } Super.prototype.getSuperValue = function() { return this.property; }; //声明子类的构造函数 function SubType() { this.subproperty = false; } //将子类的原型指向超类的实例,得到超类的一切 SubType.prototype = new Super(); SubType.prototype.constructor = SubType; SubType.prototype.getSubValue = function(){ return this.subproperty; }; //由子类创建对象,测试是否继承超类方法和属性 var instance = new SubType(); console.log(instance.getSuperValue());
所有函數的預設原型都是 Object 的實例,因此預設原型都會包含一個內部指針,指向 Object.prototype。
使用 instanceof 和 isPrototypeOf 可以確定原型和實例的關係:
instance instanceof Object; Object.prototype.isPrototypeOf(instance);
使用原型鏈的時候,需要謹慎的定義方法。子類別需要重寫超類型的某個方法或擴充,一定要放在替換原型的語句後面,這樣才能生效。此外,透過原型鏈實現繼承時,不能使用物件字面量來建立原型方法,這樣會重寫原型鏈:
...... SubType.prototype = new Super(); SubType.prototype = { .... };
這會更換指針指向新對象,從而重寫了原型鏈。
原型鏈的繼承方法是有缺陷的,主要有兩個問題:
1,來自包含引用類型值的原型,會被所有實例共用。
前面文章介紹過包含引用類型值的原型屬性會被所有實例共享,一個實例修改,其他實例會隨之改變,因此需要在建構子中定義屬性。而原型鏈繼承的時候,無論超類別中屬性是在建構子或原型中定義,全部都變成了實例物件被子類別繼承,從而對子類別的實例產生影響。
2,在建立子類型的實例時,不能傳遞參數給超類型的建構子。
原型鏈的繼承,直接將子類別原型指向超類別的實例,這時候可以傳遞參數給超類別。但是當子類別建立實例的時候,只能傳遞參數給子類別的建構子參數,而不能傳遞參數給超類別的建構子。
因此在實際應用中,很少單獨使用原型鏈。
相關的一些程式碼實作
鑑別一個原型屬性
function hasPrototypeProperty(object, name) { return name in object && !object.hasOwnProperty(name); }
在建構函式中使用原型物件
function Person(name) { this.name = name; } Person.prototype = { constructor: Person, sayName: function () { console.log(this.name); }, toString: function() { } }; var person1 = new Person('Nicholas'); var person2 = new Person('Greg); console.log(person1 instanceof Person); // true console.log(person1.constructor === Person); // true console.log(person1.constructor === Object); // false console.log(person2 instanceof Person); // true console.log(person2.constructor === Person); // true console.log(person2.constructor === Object); // false
物件繼承
var person1 = { name: 'Nicholas', sayName: function () { console.log(this.name); } }; var person2 = Object.create(person1, { name: { configurable: true, enumerable: true, value: 'Greg', writable: true } }); person1.sayName(); // Nicholas person2.sayName(); // Greg console.log(person1.hasOwnProperty('sayName')); // true console.log(person1.isPropertyOf(person2)); // true console.log(person2.hasOwnProperty('sayName')); // false
模組模式
var person = (function () { var age = 25; function getAge() { return age; } function growOlder() { age++; } return { name: 'Nicholas', getAge: getAge, growOlder: growOlder }; }());
作用域的建構子
function Person(name) { this.name = name; } Person.prototype.sayName = function() { console.log(this.name); }; var person1 = Person('Nicholas'); console.log(person1 instanceof Person); // false console.log(typeof person1); // undefined console.log(name); // Nicholas