原型及繼承
原型
每個函數都有一個prototype屬性,指向一個原型對象,這個對象專門保存特定類型的所有實例【共有的屬性和方法】。
所有原型對象都會自動獲得constructor屬性,指向構造函數。
在調用構造函數創建新實例對象時,會自動設置新實例的內部屬性[[Prototype]]指向構造函數的原型對象。
所有對象都有[[Prototype]]屬性,字面量對象的原型為Object.prototype。
所有子對象共有的成員屬性,都要保存在構造函數的原型對象中。即一次定義,重復使用。
每當代碼讀取對象的某個屬性時,會執行一次搜索,目標是給定名字的屬性。搜索首先從對象實例本身開始。如果在實例中找到了給定的屬性,則立即停止繼續搜索並返回該屬性的值;如果沒有找到,則繼續搜索指針指向的原型對象。如果在原型對象中查找到該屬性,則返回該屬性的值。
實例中與原型同名的屬性會屏蔽原型中的那個屬性,所以不能在實例中修改原型中的屬性,必須在原型上修改。
組合使用構造函數和原型:構造函數用於定義實例屬性,原型用於定義方法(引用類型)和共享的屬性。這樣每個實例都會獲得一份自己獨立的實例屬性副本,彼此間互不影響。
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.friends = [“Shelby”, “Court”]; } Person.prototype = { constructor: Person, sayName:function () { alert(this.name); } }; var person1 = new Person(“Nicholas”, 29, “Software Engineer”); var person2 = new Person(“Greg”, 27, “Doctor”); person1.friends.push(“Van”); alert(person1.friends); //”Shelby,Court,Van” alert(person2.friends); //”Shelby,Court” alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
原型相關API:
- 獲取原型對象:
從構造函數獲得原型對象: 構造函數.prototype
從子對象獲得父級原型對象:
子對象.__proto__(有兼容性問題)
Object.getPrototypeOf({...}) === Object.prototype
- 判斷原型對象是否在實例的原型鏈上:
obj.isPrototypeOf(實例) //Array.prototype.isPrototypeOf([])判斷數組
- 自有屬性和共有屬性:
obj.hasOwnProperty() //檢測一個屬性是否存在於實例中。
- in關鍵字:property in obj
在obj的原型鏈上查找指定屬性名property
- 刪除對象的屬性:delete obj.property //不能刪除共有屬性
原型鏈
本質是重寫原型對象,讓一個類型的原型對象等於另一個類型的實例。
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(); console.log(instance.getSuperValue()); //true
繼承的實現
1. 單個繼承
Object.create(Proto [,propertiesObject]); // 使用指定的原型對象和屬性創建一個新對象。
等價於:
function object(o){ function F(){}; F.prototype=o; return new F(); }
Object.setPrototypeOf(Plane.prototype,Flyer.prototype);
//直接設置一個對象的內部[[Prototype]]屬性到另一個對象或null,實現繼承。子類型的原型對象依然存在,子類型原型中重寫父類型fly方法,不會影響父類型的原型對象。有性能問題,慎用
2.原型鏈方式
因為有可能重寫或添加子類型的方法,為了保證之後創建的所有子類型都繼承同一個超類型,一定要在創建新實例之前修改原型對象。
子類型構造函數.prototype=超類型的實例;
註意:
通過原型鏈實現的繼承,不能使用對象字面量創建原型方法,因為這樣會重寫原型鏈。
不能向超類型的構造函數中傳遞參數。
如果原型被重寫,最好同時修改原型的constructor屬性。
如果超類型的屬性中有引用類型值,所有實例共享一個屬性,不好。
3. 組合繼承
(1)在子類型構造函數內部用call/apply調用父類型的構造函數。
function SubType(sColor, sName) { SupType.apply(this, arguments); this.name = sName; }
註意:arguments指SubType接收到的所有參數,SupType從0位開始按順序讀取。所以SubType構造函數的參數中前面是SupType的參數,之後才是SubType用到的參數(SubType能夠用標識符識別參數的值,而SupType只能通過位序使用)。
(2)將父類型的實例賦值給子類型的原型,繼承父類共享的屬性和方法。
SubType.prototype=new SuperType(); SubType.prototype.constructor=SubType; //原型重寫時最好同時修改
註意:組合繼承會兩次調用父類構造函數,在子類實例和原型中會有重復的屬性。
4.寄生式組合繼承
通過借用構造函數來繼承屬性,通過原型鏈的混合形式來繼承方法。不必為了指定子類型的原型而調用超類型的構造函數,只需父類型的原型副本。
//使用指定的原型對象創建一個新的空對象作為中間體,類似於Object.create() 方法
//使用指定的原型對象創建一個新的空對象作為中間體,類似於Object.create() 方法 function cloneProto(o){ function F(){} F.prototype = o; return new F(); } //子類型的原型為上述空對象 function inheritPrototype(subType, superType){ var prototype = cloneProto(superType.prototype); prototype.constructor = subType; subType.prototype = prototype; } 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; } //不能直接繼承父類型的原型對象(公用一個原型,子類型修改會影響父類型) //也不能直接繼承父類型的實例(父類型的實例屬性也會被子類型實例繼承,需要再次覆蓋) //通過一個中間對象只繼承父類型的原型方法和屬性,子類型修改時也不影響父類型 inheritPrototype(SubType, SuperType); SubType.prototype.sayAge = function(){ alert(this.age); };
原型及繼承