js 中的原型鏈與繼承
ECMAScript中將原型鏈作為實現繼承的主要方法,其基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。
1、原型鏈
先回憶一下構造函數和原型以及實例的關系:每個構造函數都有一個原型對象,原型對象中有一個指向構造函數的指針,而所有實例都有一個指向原型對象的內部指針。
那麽實現 原型鏈的具體操作是這樣的:讓構造函數的原型對象等於另一個類型的實例。此時,原型對象將包含一個指向另一個原型的指針,另一個原型中也包含這一個指向另一個構造函數的指針。
如果另一個原型又是再另一個類型的實例的話,依然存在上述關系,如此層層遞進,就構成一個鏈條,即為原型鏈。
通過原型鏈繼承實例:
functionSuperType(){ 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 = newSubType(); console.log(instance.getSuperValue()); //true console.log(instance.getSubValue()); //false
原型鏈關系圖
出現Object的原因是:所有引用類型默認都繼承了Object,而且也是通過原型鏈繼承的。
2、繼承
上面的例子就是一個僅僅使用原型繼承的例子,但僅僅使用原型繼承會共享所有屬性和方法,也不能在不影響所有對象實例的情況下向超類的構造函數傳遞參數。下面介紹其他繼承方式。
2.1 僅通過構造函數繼承
function SuperType(name){ this.name= name;this.colors = ["red","blue","green"]; } function SubType(){ SuperType.call(this,"tom"); this.age = 29; } var instance = new SubType(); console.log(instance.age); // 29 console.log(instance.name); // tom
這種方法需要用到 apply() 或 call() 函數。
能有自己的特性,也可以傳遞參數給超類。但超類的原型中的屬性和方法對子類是不可見的(訪問不到)。
2.2 構造函數和原型組合繼承
function SuperType(name){ this.name= name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name,age){ SuperType.call(this,name); //執行new SubType(name,age)時,第二遍執行 SuperType(),子類對象實例將包含超類構造函數的屬性 this.age = age; } // 對象實例的原型會包含超類構造函數的屬性,還有超類構造函數原型的屬性 SubType.prototype = new SuperType(); // 第一遍執行 SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function (){ console.log(this.age); } var instance1 = new SubType("tom",29); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "green", "black"] instance1.sayName(); // tom instance1.sayAge(); //29 var instance2 = new SubType("jack",32); console.log(instance2.colors); // ["red", "blue", "green"] instance2.sayName(); //jack instance2.sayAge(); //32
這種方法比較常用。
2.3 原型式繼承
function createObject(o){ function F(){}; F.prototype = o; return new F(); } var person = { name: "Tom", friends:["Shelby","Court","Van"] } var p1 = createObject(person); p1.name = "Greg"; p1.friends.push("Rob"); var p2 = createObject(person); p2.name = "Linda"; p2.friends.push("Barbie"); console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
這種方法的核心是:將已有的對象作為新對象的原型,相當於繼承。這種繼承比較簡單,方便。
可以將上面的新修改一下,在已有對象的基礎上給新對象添加新屬性。
function createObject(o){ function F(){}; F.prototype = o; var object = new F(); object.sayHi = function(){ console.log("hi!"); } return object; }
ECMAScript提供了一個方法 Object.create() 來實現這種原型模式繼承。接受兩個參數:作為新對象原型的對象和(可選的)一個為新對象定義額外屬性的對象,第二個參數和Object.defineProperties()方法的第二個參數相同。
var person = { name: "Tom", friends:["Shelby","Court","Van"] } var p1 = Object.create(person,{ name:{value : "Greg"} }); p1.name = "Greg"; p1.friends.push("Rob"); console.log(p1.name); //Greg var p2 = Object.create(person); p2.name = "Linda"; p2.friends.push("Barbie"); console.log(person.friends); // ["Shelby", "Court", "Van", "Rob", "Barbie"]
2.4 原型式的組合繼承
上述提到的構造函數和原型的組合繼承因為在創建新子類對象實例時,會執行兩次超類構造函數(一次是給子類構造函數原型賦值,一次是執行 new SubType() ),這導致子類對象實例和它的原型都有一套超類構造函數的屬性。
可以減少一次:只要給子類原型指定為超類原型對象,而不必為了指定子類的原型而調用超類的構造函數。
function SuperType(name){ this.name= name; this.colors = ["red","blue","green"]; } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age = age; } function inheritPrototype(subType,superType){ function F(){}; F.prototype = superType.prototype; subType.prototype = new F(); // 子類的原型繼承了超類的原型對象 subType.prototype.constructor = subType; } inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function (){ console.log(this.age); }var instance1 = new SubType("tom",29); instance1.colors.push("black"); console.log(instance1.colors); // ["red", "blue", "green", "black"] instance1.sayName(); // tom instance1.sayAge(); //29 var instance2 = new SubType("jack",32); console.log(instance2.colors); // ["red", "blue", "green"] instance2.sayName(); //jack instance2.sayAge(); //32
js 中的原型鏈與繼承