摘要:將構造函數的作用域賦值給新的對象因此指向了這個新對象。以這種方式定義的構造函數是定義在對象在瀏覽器是對象中的。構造函數在不返回值的情況下,默認會返回新對象實例。在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。
創建對象
雖然Object構造函數或對象字面量都可以用來創建單個對象,但是這些方式有明顯的缺點:使用同一個接口創建很多對象,會產生大量的重復代碼。為解決這個問題,人們開始使用工廠模式的一種變體。
工廠模式工廠模式是軟件工程領域一種廣為人知的設計模式,這種模式抽象了創建具體對象的過程。考慮到ECMAScript中無法創建類,開發人員就發明了一種函數,用函數來封裝以特定接口創建對象的細節。
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName=function(){ console.log(this.name) } return o; } var person1 = createPerson("Nicholas",29,"SoftWare"); var person2 = createPerson("gres",27,"Doctor");
函數cretePerson()能夠根據接受的參數來構建一個包含所有必要信息的Person對象。可以無數次的調用這個函數,而每次它都會返回一個包含三個屬性一個方法的對象。工廠模式雖然解決了創建多個相似對象的問題,但是卻沒有解決對象識別問題。
構造函數模式function Person(name,age,job){ this.name = name; this.age = age; this.sayName = function(){ console.log(this.name); } } var person1 = new Person("Nicholas",29,"Software"); var person2 = new Person("gres",24,"Docotor");
在這個例子中,Person()函數取代了createPerson()函數。不同之處:
1.沒有顯式的創建對象。 2.直接將屬性和方法賦給了this對象。 3.沒有return語句。
要創建Person實例必須使用new操作符。以這種方式調用構造函數實際上會經歷以下4個步驟:
1.創建一個對象。 2.將構造函數的作用域賦值給新的對象(因此this指向了這個新對象)。 3.執行構造函數中的代碼。(為這個新對象添加屬性)。 4.返回新的對象。
上面的例子:person1和person2分別保存著Person的一個不同的實例。 這兩個對象都有一個constructor屬性,該屬性指向Person。
person1.constructor == Person//true person2.constructor == Person//true person1 instanceof Object;//true person1 instanceof Person;//true
創建自定義的構造函數意味著將來可以將它的實例標示為一種特定的類型;而這正是構造模式勝過工廠模式的地方。(以這種方式定義的構造函數是定義在Global對象(在瀏覽器是window對象)中的)。
構造函數與其它函數的唯一區別,就在于調用它們的方式不同。不過,構造函數畢竟也是函數,不存在定義構造函數的特殊語法。任何函數,只要通過new操作符來調用,那他就可以作為構造函數;任何函數,如果不通過new運算符調用,那它就跟普通函數沒有區別。
構造函數的問題:
使用構造函數的主要問題就是每個方法都要在每個實例上重新創建一遍。在上面的例子中,person1和person2都有一個名為sayName()的方法,但那兩個方法不是同一個Function的實例。在js中函數也是對象,因此每定義一個函數,也就是實例化了一個對象。
function Person(name, age){ this.name = name; this.age = age; this.sayName = new Function(console.log(this.name)) }
從這個角度上看構造函數,更容易明白每個Perosn實例都包含一個不同的Function實例的本質。說明白些,以這種方式創建函數,會導致不同的作用域鏈和標識符解析,但是創建Function新實例的機制仍然相同。因此不同實例上的同名函數是不相等的。
console.log(person1.sayName==person2.sayName);//false.
然而,創建兩個完成同樣任務的Function實例沒有必要;況且有this對象在,根本不用在執行代碼前就把函數綁定到特定對象上面。因此,可以向下面這樣,通過把函數定義轉移到構造函數外部來解決這個問題。
function Person(name,age){ this.name = name; this.age = age; this.sayName = sayName; } function sayName(){ console.log(this.name) } var person1 = new Person("ll",24); var person2 = new Person("kk",25);原型模式
我們創建的每一個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以有特定類型的所有實例共享的屬性和方法。
function Person(){} Person.prototype.name = "ll"; Person.prototype.age = 24; Person.prototype.sayName=function(){ console.log(this.name) } var person1 = new Person(); var person2 = new Person();
在此我們將sayName()方法和屬性直接添加到Person的prototype屬性中,構造函數變成了空函數。即使如此,也仍然可以通過調用構造函數來創建新的對象,而且新的對象還會具有相同的屬性和方法。但與構造函數模式不同的是,新對象的屬性和方法是由所有實例共享的。
理解原型對象:
無論什么時候,只要創建了一個新函數,就會根據一定的規則為該函數創建一個prototype屬性,這個屬性指向函數的原型對象。在默認情況下,所有原型對象都會自動獲得一個constructor屬性,這個屬性是一個指向prototype屬性所在函數的指針。Person.prototype.constructor指向Person。而通過這個構造函數,我們還可以繼續為原型對象創建其它屬性和方法。 創建了自定義構造函數之后,其原型對象默認只會得到constructor屬性;至于其它屬性和方法都是從Object對象繼承而來的。當調用構造函數的一個新實例后,該實例內部將包含一個指針,指向構造函數的原型對象。(__proto__);person1和person2都包含一個內部屬性,該屬性僅僅指向了Person.prototype,和構造函數沒有直接的關系。 每當代碼讀取某個對象的某個屬性時,都會執行一次搜索,目標是具有給定名字的屬性,搜索首先從對象實例本身開始,如果在實例中找到了具有給定名字的屬性,則返回該屬性的值;如果沒有找到,則繼續搜索指針指向的原型對象,如果在原型對象中找到了該屬性,則返回該屬性的值。
function Person(){} Person.prototype.name = "ll"; Person.prototype.age = 24; Person.prototype.sayName=function(){ console.log(this.name) } var person1 = new Person(); var person2 = new Person(); person1.name = "kk"; console.log(person1.name)//kk-來自實例 console.log(person2.name)//ll來自原型
當對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。
原型模式的缺點:
原型中所有屬性是被很多實例共享的,這種共享對于函數非常合適。對于那些包含基本值的屬性也說的過去,通過在實例上添加一個同名屬性可以隱藏原型中對應的屬性。然而對于包含引用類型值的屬性來說,問題就比較突出了:
function Person(){} Person.prototype={ constructor:Person, name:"kk", age:24, friends:["ll","jj"], sayName:function(){ console.log(this.name); } } var person1 = new Person(); var person2 = new Person(); person1.friends.push("aa"); console.log(person1.friends);//ll jj aa console.log(person2.friends);//ll jj aa console.log(person1.friends==person2.friends);//true
修改person1.friends引用的數組,person2同樣會修改。
組合使用構造函數和原型模式創建自定義類型的最常見方式,就是組合使用構造函數和原型模式。構造函數模式用于定義實例屬性,原型模式用于定義共享的屬性和方法。另外這種模式還支持向構造函數穿參數。
function Person(name,age){ this.name = name; this.age = age; this.friends=["kk","ll"]; } Person.prototype={ constructor:Person, sayName:function(){ console.log(this.name) } } var person1 = new Person("nnn",24); var person2 = new Person("mmm",29); person1.friends.push("aaa"); console.log(person1.friends);//kk ll aa console.log(person2.friends);//kk ll console.log(person1.friends==person2.friends);//false console.log(person1.sayName==person2.sayName);//true寄生構造函數模式
function Person(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName=function(){ console.log(this.name); } return o; } var friend = new Person("kk",24,"software");
在這個實例中,Person函數創建了一個新對象,并以相應的屬性和方法初始化該對象,然后又返回了這個對象。除了使用new操作符并把使用的包裝函數叫做構造函數之外,這與工廠模式沒有什么區別。構造函數在不返回值的情況下,默認會返回新對象實例。而通過在構造函數末尾添加一個return語句,可以重寫調用構造函數時返回的值。
這個模式可以在特殊的情況下用來為對象創建構造函數。假設我們想創建一個具有額外方法的特殊數組。由于不能直接修改Array的構造函數,因此可以使用這個模式:
function SpecialArray(){ console.log(this) var values = new Array(); values.push.apply(values,arguments); values.toPipedString=function(){ return this.join("|") } return values; } var colors = new SpecialArray("red","blue","pink"); console.log(colors.toPipedString())
關于寄生構造函數模式,返回的對象與構造函數或則構造函數的原型屬性之間沒有關系。
穩妥構造函數模式所謂穩妥對象,指的是沒有公共屬性,而其方法也不引用this的對象。穩妥構造函數模式與寄生構造函數模式類似,但是有兩點不同,一是創建對象的實例方法不引用this,二是不使用new操作符調用構造函數。
function Person(name,age,job){ //創建要返回的對象 var o = new Object(); //可以在這里定義私有變量和屬性 //添加方法 o.sayName=function(){ console.log(name); } //返回對象 return o; } var friend = Person("kk",24,"software"); friend.sayName();//kk繼承
每個構造函數都有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那么,假如我們讓原型對象等于另一個類型的實例,此時的原型對象將包含一個指向另一個原型的指針,相應的,另一個原型中也包含著一個指向另一個構造函數的指針。
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subProperty = true; } SubType.prototype = new SuperType(); console.log(SubType.prototype) SubType.prototype.getSubValue = function(){ return this.subProperty; } var instance = new SubType(); console.log(instance)
以上代碼定義了兩個類型:SuperType和SubType。每個類型分別有一個屬性和一個方法。它們的主要區別在于SubType繼承了SuperType,而繼承是通過創建SuperType的實例,并將該實例賦值給SubType.prototype實現的。實現的本質是重寫原型對象,代之以一個新類型的實例。
在上面的代碼中,我們沒有使用Subtype默認提供的原型,而是給它換了一個新的原型:這個新的原型就是SuperType的實例。于是新原型不僅具有SuperType的實例所擁有的全部屬性和方法,而且其內部還有一個指針,指向了SuperType的原型。
原型鏈的問題:
1.引用類型值的原型屬性會被所有實例共享。 2.在創建子類型的實例時,不能向超類型的構造函數中傳遞參數。借用構造函數
在解決原型中包含引用類型值所帶來問題的過程中,開發人員開始使用一種叫做借用構造函數的技術。這種技術的思想相當簡單,即在子類構造函數的內部調用超類型構造函數。
function SuperType(){ this.colors = ["red","blue","pink"] } function SubType(){ //繼承了SuperType SuperType.call(this); } var instance = new SubType(); instance.colors.push("black"); console.log(instance.colors);//red blue pink black var instance2 = new SubType(); console.log(instance2.colors);//red blue pink
同時,借用構造函數有一個很大的優勢,即可以在子類構造函數中向超類型構造函數傳遞參數。
function SuperType(name){ this.colors = ["red","blue","pink"]; this.name = name; } function SubType(){ //繼承了SuperType SuperType.call(this,"kk"); } var instance = new SubType(); console.log(instance.name);//kk
借用構造函數的問題:
方法都需要在構造函數中定義,無法復用。組合繼承
function SuperType(name){ this.name = name; this.colors=["red","blue","pink"] } SuperType.prototype.sayName=function(){ console.log(this.name); } function SubType(name,age){ //繼承屬性 SuperType.call(this,name); this.age = age; } //繼承方法 SubType.prototype = new SuperType(); SubType.prototype.constructor = SubType; SubType.prototype.sayAge=function(){ console.log(this.age); } var instance1 = new SubType("kk",24); instance1.colors.push("black"); console.log(instance1.colors);//red blue pink black instance1.sayName();//kk instance1.sayAge();//24 var instance2 = new SubType("ll",26); console.log(instance2.colors);//red blue pink instance2.sayAge();//26 instance2.sayName();//ll
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優點。
原型式繼承function object(o){ function F(){}; F.prototype = o; return new F(); }
在object()函數內部,先創建了一個臨時性的構造函數,然后將傳入的對象作為這個構造函數的原型,然后返回了這個臨時類型的一個實例。從本質上講object()對傳入中的對象進行了淺復制。
var Person={ name:"kk", friends:["ll","aa","cc"] } var instance1 = object(Person); instance1.name = "Greg"; instance1.friends.push("dd"); console.log(instance1) var instance2 = object(Person); instance2.name = "Linda"; instance2.friends.push("oo"); console.log(instance2)
obejct()會返回一個新對象,這個對象將Person作為原型,所以它的原型中包含一個基本類型值屬性和一個引用類型值屬性。這就意味著friends不僅是Person的,也同時被instance1和instance2共享。
寄生式繼承寄生式繼承思路和寄生式構造函數和工廠模式類似,即創建一個僅用于封裝繼承過程的函數,該函數在內部以某種方式來增強對象,最后返回對象。
function object(o){ function F(){}; F.prototype = o; return new F(); } function createAnother(original){ var clone = object(original); clone.sayName=function(){ console.log(this.name) }; return clone; } var Person={ name:"ll", friens:["kk","cc","aaa"] } var instance = createAnother(Person); console.log(instance) instance.sayName();寄生組合式繼承
組合繼承是js最常用的繼承模式;不過,他也有自己的不足。組合繼承最大的問題就是無論什么情況下,都會調用兩次超類型構造函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。
function SuperType(name){ this.name = name; this.colors=["red","blue","pink"]; } SuperType.prototype.sayName=function(){ console.log(this.name) } function SubType(name,age){ SuperType.call(this,name);//第二次調用SuperType() this.age = age; } SubType.prototype = new SuperType()//第一次調用SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge=function(){ console.log(this.age); }
所謂寄生式組合繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。其背后的基本思路:不必為了指定子類型的原型而調用超類型的構造函數,我們需要的無非是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然后將結果指定給子類型的原型。
function object(o){ function F(){}; F.prototype = o; return new F(); } function inheritPrototype(subType,superType){ var prototype = object(superType.prototype);//創建對象; prototype.constructor = subType;//增強對象 subType.prototype = prototype;//指定對象 } function SuperType(name){ this.name = name; this.colors = ["red","blue","pink"]; } SuperType.prototype.sayName=function(){ console.log(this.name); } function SubType(name,age){ SuperType.call(this,name); this.age = age; } var aaa = inheritPrototype(SubType,SuperType); SubType.prototype.sayAge = function(){ console.log(this.age); } var instance = new SubType("kk",24); console.log(instance)小結
在沒有類的情況下,可以采用下列模式創建對象。
1.工廠模式,使用簡單的函數創建對象,為對象添加屬性和方法,然后返回對象。這個模式后來被構造函數模式所替代。
2.構造函數模式,可以創建自定義引用類型,可以像創建內置對象實例一樣使用new操作符。不過,構造函數也有缺點,即他的每個成員都無法得到復用,包括函數。
3.原型模式,使用構造函數的prototype屬性來指定那些應該共享的屬性和方法。組合使用構造函數模式和原型模式,使用構造函數定義實例的屬性,而使用原型定義共享的屬性和方法。
JavaScript主要通過原型鏈實現繼承。原型鏈的構建是通過將一個類型的實例賦值給另一個構造函數的原型來實現的。這樣,子類型就能夠訪問超類型的所有屬性和方法,這一點基于類的繼承很相似。原型鏈的問題是對象實例共享所有屬性和方法,因此不宜多帶帶使用。解決這個問題的技術是借用構造函數,即在子類型構造函數內部調用超類型構造函數。這樣就可以做到每個實例都具有自己的屬性,同時還能保證只使用構造函數模式來定義類型。使用最多的繼承模式是組合繼承,這種模式使用原型鏈繼承共享的屬性和方法,而通過借用構造函數來繼承實例屬性。
1.原型式繼承,可以在不必預先定義構造函數的情況下實現繼承,其本質是執行對給定對象的淺復制。而復制得到的副本還可以得到進一步的改造。
2.寄生式繼承,與原型式繼承非常相似,也是基于某個對象或某些信息創建一個對象,然后增強對象,最后返回對象。為了解決組合繼承模式由于多次調用超類型構造函數而導致的低效率問題,可以將這個模式和組合繼承一起使用。
3.寄生組合式繼承,集寄生式繼承和組合繼承的優點與一身,是實現基于類型繼承的最有效方法。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/103531.html
摘要:組合構造原型模式將自身屬性于構造函數中定義,公用的方法綁定至原型對象上原型對象的解釋每一個函數創建時本身內部會有一個固有的原型對象,可以通過函數名去訪問,而其原型對象又有一個屬性指針指向該函數。 每次遇到JS面對對象這個概念,關于繼承及原型,腦海里大概有個知識框架,但是很不系統化,復習下,將其系統化,內容涉及到對象的創建,原型鏈,以及繼承。 創建對象 兩種常用方式,其余的比較少見工廠模...
摘要:除此之外,在超類型的原型中定義的方法,對子類型而言也是不可兼得,結果所有類型都只能用構造函數模式。創建對象增強對象指定對象繼承屬性這個例子的高效率體現在它只調用了一次構造函數。 1、原型鏈 原型鏈的基本思想是利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。構造函數、原型和實例的關系:每個構造函數都有一個原型對象;原型對象都包含著一個指向構造函數的指針;實例都包含一個指向原型對象的...
摘要:在沒有必要創建構造函數,只想讓一個對象與另一個對象保持類似的情況下,原型式繼承是完全可以勝任的。方便我們進行面向對象的編程。 理解對象 屬性類型 1.數據屬性 特性: Configurable : 表示能否通過 delete 刪除屬性,能否修改屬性特性,能否把屬性改為訪問器屬性 Enumerable : 表示能否通過 for in 循環返回 Writable : 表示能否修改屬性的值...
摘要:即另外,注意到構造函數里的屬性,都沒有經過進行初始化,而是直接使用進行綁定。并且在模式下,構造函數沒有使用進行調用,也會導致報錯。調用構造函數千萬不要忘記寫。 1. 基礎 JavaScript不區分類和實例的概念,而是通過原型來實現面向對象編程。Java是從高級的抽象上設計的類和實例,而JavaScript的設計理念,聽起來就好比Heros里的Peter,可以復制別人的能力。JavaS...
閱讀 2982·2023-04-25 19:45
閱讀 2699·2021-11-19 09:40
閱讀 704·2021-10-14 09:49
閱讀 2715·2021-09-30 09:47
閱讀 2245·2021-09-26 09:55
閱讀 1236·2021-09-22 16:01
閱讀 2822·2019-08-30 14:19
閱讀 715·2019-08-29 16:44