摘要:創建對象兩個基本方法創建對象最基本的兩個方法是構造函數和對象字面量。當調用構造函數創建一個新的實例對象后,該實例內部會有一個指針指向構造函數的原型對象。碼農構造函數在不返回值的情況下,默認會返回新對象實例。
前言:本文主要總結一下javascript創建對象的方法、原型、原型鏈和繼承,但是先從創建對象的幾種方法開始,延伸到原型模式創建對象以及其它模式。繼承本來想一塊寫了,發現太多內容了,放到下里總結。
1.創建對象 (1)兩個基本方法創建對象最基本的兩個方法是:Object構造函數和對象字面量。
//Object構造函數方式 var person = new Object(); person.name = "Jack"; person.age = 12; person.sayName = function(){ alert(this.name); }; //字面量方式 var person = { name: "Jack", age: 14, job: "碼農", sayName: function(){ alert(this.name); } };(2)工廠模式
上述兩個基本方法的缺點是:使用同一個接口創建很多對象,會產生大量的復制代碼。針對這個缺點,看下面
原理是用函數來封裝以特定接口創建對象的細節
function createPerson(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var person1 = createPerson("Jack",15,"碼農"); var person2 = createPerson("rose",12,"程序媛");
函數createPerson能接收參數構建一個包含所有屬性的對象,并且可以用很少的代碼不斷的創建多個對象,但是由于它被函數所封裝,暴露的接口不能有效的識別對象的類型(即你不知道是Object還是自定義的什么對象)。
(3)構造函數模式function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; } var person1 = new Person("Jack",15,"碼農"); //滿滿的java復古風 var person2 = new Person("Rose",15,"程序媛");
與工廠模式相比,構造函數模式用Person()函數代替了createPerson()函數,并且沒有顯示的創建對象,直接把屬性和方法賦值給了this對象。
要創建Person的實例,必須使用new關鍵字。
person1和person2都是Person的實例,這兩個對象都有一個constructor(構造函數)屬性,該屬性指向Person。 person1.constructor == Person; //true
person1即是Person的實例又是Object的實例,后面繼承原型鏈會總結。
(3).1構造函數的使用//當做構造函數使用 var person1 = new Person("Jack",15,"碼農"); person1.sayName(); //"Jack" //當做普通函數使用 Person("Jack",16,"碼農"); //注意:此處添加到了window window.sayName(); //"Jack" //在另一個對象的作用域中調用 var o = new Object(); Person.call(o,"Jack",12,"碼農"); o.sayName(); //"Jack"
第一種當做構造函數使用就不多說了
當在全局作用域中調用Person("Jack",16,"碼農");時,this對象總是指向Global對象(瀏覽器中是window對象)。因此在執行完這句代碼后,可以通過window對象來調用sayName()方法,并且返回“Jack”。
最后也可以使用call()或者apply()在某個特殊對象的作用域中調用Person()函數
(3).2存在的問題在(3)構造函數模式的代碼中,對象的方法sayName的功能都一樣,就是alert當前對象的name。當實例化Person之后,每個實例(person1和person2)都有一個名為sayName的方法,但是兩個方法不是同一個Function實例。不要忘了,js中函數是對象,所以每個實例都包含一個不同的Function實例,然而創建兩個功能完全一樣的Function實例是完全沒有必要的。因此可以把函數定義轉移到構造函數外。
如下代碼:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } //實例化對象 var person1 = new Person("Jack",15,"碼農"); //滿滿的java復古風 var person2 = new Person("Rose",15,"程序媛");
但是這樣依然存在問題:
為了讓Person的實例化對象共享在全局作用域中定義的同一個sayName()函數,我們把函數sayName()定義在全局作用域中,并通過指針sayName指向構造函數,所以在全局作用域中的sayName()只能被特定對象調用,全局作用域名不符實,且污染全局變量。
并且如果對象需要很多種方法,那么就要定義很多全局函數,對于對象就沒有封裝性,并且污染全局。
2.原型 (1)原型模式創建對象js不同于強類型語言的java,java創建對象的過程是由類(抽象)到類的實例的過程,是一個從抽象到具體的過程。
javascript則不同,其用原型創建對象是一個具體到具體的過程,即以一個實際的實例為藍本(原型),去創建另一個實例對象。
所以用原型模式創建對象有兩種方式:
1.Object.create()方法
Object.create:它接收兩個參數,第一個是用作新對象原型的對象(即原型),一個是為新對象定義額外屬性的對象(可選,不常用)。
var Person = { name:"Jack", job:"碼農" }; //傳遞一個參數 var anotherPerson = Object.create(Person); anotherPerson.name //"Jack" //傳遞兩個參數 var yetPerson = Object.create(Person,{name:{value:"Rose"}}); yetPerson.name; //Rose
2.構造函數方法創建對象
任何一個函數都有一個prototype屬性(是一個指針),指向通過構造函數創建的實例對象的原型對象,原型對象可以讓所有對象實例共享它所包含的屬性和方法。
因此不必在構造函數中定義對象實例的信息,而是將這些屬性和方法直接添加到原型對象中,從而被實例對象多繼承(繼承后面總結)
//第一步:用構造函數創建一個空對象 function Person(){ } //第二步:給原型對象設置屬性和方法 Person.prototype.name = "Jack"; Person.prototype.age = 20; Person.prototype.job = "碼農"; Person.prototype.sayName = function(){ alert(this.name); }; //第三步:實例化對象后,便可繼承原型對象的方法和屬性 var person1 = new Person(); person1.sayName(); //Jack var person2 = new Person(); person2.sayName(); //Jack alert(person1.sayName == person2.sayName); //true
person1和person2說訪問的是同一組屬性和同一個sayName()函數。
(2)理解原型對象只要創建一個函數,就會為該函數創建一個prototype屬性,這個屬性指向函數的原型對象。
所有原型對象都會自動獲得一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。
當調用構造函數創建一個新的實例對象后,該實例內部會有一個指針([prototype]/_proto_),指向構造函數的原型對象。如下圖:
上圖中 :
Person.prototype指向了原型對象,而Person.prototype.construstor又指回了Person。
注意觀察原型對象,除了包含constructor屬性之外,還包括后來添加的其它屬性,這就是為什么每個實例化后的對象,雖然都不包含屬性和方法,但是都包含一個內部屬性指向了Person.prototype,能獲得原型中的屬性和方法。
(3)判斷一個實例對象的原型這個方法叫:Object.getPrototypeOf(),如下例子:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Jack"
這個方法可以很方便的取得一個對象的原型
還可以利用這個方法取得原型對象中的name屬性的值。
(3)搜索屬性的過程當我們在創建實例化的對象之后,調用這個實例化的對象的屬性時,會先后執行兩次搜索。
第一次搜索實例person1有name屬性嗎?沒有進行第二次搜索
第二次搜索person1的原型有name屬性嗎?有就返回。
因此進行一次思考,如果對實例進行屬性重寫和方法覆蓋之后,訪問實例對象的屬性和方法會顯示哪個?實例對象的還是對象原型的?
function Person(){ } Person.prototype.name = "Jack"; Person.prototype.age = 20; Person.prototype.job = "碼農"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Rose"; alert(person1.name); //Rose alert(person2.name); //Jack
當為對象實例添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。
但是這個屬性只會阻止我們訪問原型中的那個屬性,而不會修改那個屬性
3.使用delete操作符可以刪除實例屬性,從而重新訪問原型中的屬性。
function Person(){ } Person.prototype.name = "Jack"; Person.prototype.age = 20; Person.prototype.job = "碼農"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); var person2 = new Person(); person1.name = "Rose"; alert(person1.name); //Rose --來自實例 alert(person2.name); //Jack --來自原型 delete person1.name; alert(person1.name); //Jack --來自原型(4)判斷訪問的到底是對象還是原型屬性
hasOwnProperty()可以檢測一個屬性是存在于實例中,還是原型中,只有在給定屬性存在于對象實例中,才會返回true。
person1.hasOwnProperty("name"); //假設name存在于原型,返回false
in操作符會在通過對象能夠訪問給定屬性時返回true,無論該屬性是存在于實例中還是原型中
"name" in person1 //true
所以通過這兩個可以封裝一個hasPrototypeProperty()函數確定屬性是不是原型中的屬性。
function hasPrototypeProperty(object,name){ return !object.hasOwnProperty(name) && (name in object); }(5)更簡單的原型語法
前面每次添加一個屬性和方法都要寫一次Person.prototype,為了簡便可以直接這樣
function Person(){ } Person.prototype = { name:"Jack", age:20, job:"碼農", sayName:function(){ alert("this.name"); } };
上述代碼直接將Person.prototype設置為等于一個以對象字面量形式創建的新對象
上述這么做時:constructor屬性就不再指向Person了。
本質上完全重寫了默認的prototype對象,因此constructor屬性也就變成了新對象的constructor屬性(指向Object構造函數)。
因此如果constructor值很重要,可以在Person.prototype中設置回適當的值:
如上例中可以添加:constructor:Person,
我們對原型對象所做的任何修改都會立即從實例上反映出來-即使先創建實例對象后修改原型也如此
var friend = new Person(); Person.prototype.sayHi = function(){ alert("Hi"); }; friend.sayHi(); //"Hi"
盡管可以隨時為原型添加屬性和方法,并且修改能立即在實例對象中體現出來,但是如果重寫整個原型對象,就不一樣了。看下面例子:
function Person(){ } var friend = new Person(); Person.prototype = { constructor:Person, name:"Jack", age:20, sayName:function(){ alert(this.name); } }; friend.sayName(); //error
上述代碼先創建了一個Person實例,然后又重寫了其原型對象,在調用friend.sayName()時發生錯誤。
因為friend指向的原型中不包含以該名字命名的屬性。關系如下圖:
省略了為構造函數初始化參數這一環節,結果是所有實例都取得相同的屬性,但問題不大,可以為實例對象重寫屬性來解決。
2.但是,對于包含引用類型值的屬性來說,問題就比較突出了,因為引用類型中,屬性名只是一個指針,在實例中重寫該屬性并沒有作用。指針始終指向原來的。
如下例子:
function Person(){} Person.prototype = { constructor:Person, name:"Jack", job:"碼農", friends:["路人甲","路人乙","路人丙"], }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("路人丁"); alert(person1.friends); //["路人甲","路人乙","路人丙","路人丁"] alert(person2.friends); //["路人甲","路人乙","路人丙","路人丁"] alert(person1.friends === person2.friends); //true
上面這個,假如每個實例對象的引用值屬性不一樣,則無法修改。
3.組合使用構造函數和原型模式構造函數模式用于定義實例屬性
原型模式用于定義方法和共享的屬性
如下代碼:
function Person(name,age,job){ this.name = name; this.job = job; this.age = age; this.friends = ["路人甲","路人乙"]; } Person.prototype = { constructor:Person, sayName: function(){ alert(this.name); } } var person1 = new Person("Jack", 20, "碼農"); var person2 = new Person("Rose", 20, "程序媛"); person1.friends.push("路人丁"); alert(person1.friends); //["路人甲","路人乙","路人丁"] alert(person2.friends); //["路人甲","路人乙"] alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true4.寄生構造函數模式
該模式基本思想是創建一個函數,該函數作用僅僅是封裝創建對象的代碼,然后返回新創建的對象。
function Person(name,age,job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name); }; return o; } var friend = new Person("Jack", 16, "碼農"); friend.sayName(); //Jack
構造函數在不返回值的情況下,默認會返回新對象實例。
通過在構造函數末尾添加一個return語句,可以重寫調用構造函數時返回的值。
這個方法的用處是:可以創建一個額外方法的特殊的數組(因為原生對象Array的構造函數不能直接修改)
function SpecialArray(){ //創建數組 var values = new Array(); //添加值 values.push.apply(values,arguments); //添加方法 values.toPipedString = function(){ return this.join("|"); }; //返回數組 return values; } var colors = new SpecialArray("black","red","blue"); alert(colors.toPipedString());
本來想接著寫繼承的,發現實在太多了,分成兩篇吧。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/79979.html
摘要:當調用的構造函數時,在函數內部又會調用的構造函數,又在新對象上創建了實例屬性和,于是這兩個屬性就屏蔽了原型中的同名屬性。 前言:這次對上篇收個尾,主要總結一下javascript的繼承。 1.原型鏈 js中原型鏈是實現繼承的主要方法。基本思想是:利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。我們來簡單回顧一下以前的內容: 每個構造函數都有一個原型對象 每個原型對象都包含一個指...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:執行構造函數的一步說明對象可以通過函數來創建。是最頂級的構造函數,對象里面,就有好幾個其他屬性。構造函數與普通函數并沒有區別,只是調用方式不同。 主要問題:1、構造函數和普通函數有區別么?什么區別?2、prototype和__proto__有什么不同?3、instanceof的作用機制,為什么有限制?4、ES6的相關方法,Class繼承原理? 三、對象與原型 (一)、數據類型 Js...
閱讀 2643·2021-11-11 16:55
閱讀 680·2021-09-04 16:40
閱讀 3078·2019-08-30 15:54
閱讀 2615·2019-08-30 15:54
閱讀 2403·2019-08-30 15:46
閱讀 404·2019-08-30 15:43
閱讀 3227·2019-08-30 11:11
閱讀 2983·2019-08-28 18:17