前言:本文主要總結一下javascript建立對象的方法、原型、原型鏈和繼承,可是先從建立對象的幾種方法開始,延伸到原型模式建立對象以及其它模式。繼承原本想一塊寫了,發現太多內容了,放到下里總結。javascript
建立對象最基本的兩個方法是:Object構造函數
和對象字面量
。java
//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); } };
上述兩個基本方法的缺點是:使用同一個接口建立不少對象,會產生大量的複製代碼。針對這個缺點,看下面
原理是用函數來封裝以特定接口建立對象的細節數組
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仍是自定義的什麼對象)。瀏覽器
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對象。app
要建立Person的實例,必須使用new關鍵字。
函數
person1和person2都是Person的實例,這兩個對象都有一個constructor(構造函數)屬性,該屬性指向Person。 person1.constructor == Person; //true
this
person1便是Person的實例又是Object的實例,後面繼承原型鏈會總結。spa
//當作構造函數使用 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"
第一種當作構造函數使用就很少說了prototype
當在全局做用域中調用Person("Jack",16,"碼農");
時,this對象老是指向Global對象(瀏覽器中是window對象)。所以在執行完這句代碼後,能夠經過window對象來調用sayName()
方法,而且返回「Jack」。指針
最後也可使用call()
或者apply()
在某個特殊對象的做用域中調用Person()
函數
在(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()
只能被特定對象調用,全局做用域名不符實,且污染全局變量。
而且若是對象須要不少種方法,那麼就要定義不少全局函數,對於對象就沒有封裝性,而且污染全局。
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()函數。
只要建立一個函數,就會爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象。
全部原型對象都會自動得到一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。
當調用構造函數建立一個新的實例對象後,該實例內部會有一個指針([prototype]/_proto_),指向構造函數的原型對象。以下圖:
上圖中 :
Person.prototype指向了原型對象,而Person.prototype.construstor又指回了Person。
注意觀察原型對象,除了包含constructor屬性以外,還包括後來添加的其它屬性,這就是爲何每一個實例化後的對象,雖然都不包含屬性和方法,可是都包含一個內部屬性指向了Person.prototype,能得到原型中的屬性和方法。
這個方法叫:Object.getPrototypeOf()
,以下例子:
alert(Object.getPrototypeOf(person1) == Person.prototype);
//truealert(Object.getPrototypeOf(person1).name);
//"Jack"
這個方法能夠很方便的取得一個對象的原型
還能夠利用這個方法取得原型對象中的name屬性的值。
當咱們在建立實例化的對象以後,調用這個實例化的對象的屬性時,會前後執行兩次搜索。
第一次搜索實例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 --來自原型
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); }
前面每次添加一個屬性和方法都要寫一次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
上面這個,假如每一個實例對象的引用值屬性不同,則沒法修改。
構造函數模式用於定義實例屬性
原型模式用於定義方法和共享的屬性
以下代碼:
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); //true
該模式基本思想是建立一個函數,該函數做用僅僅是封裝建立對象的代碼,而後返回新建立的對象。
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());
原本想接着寫繼承的,發現實在太多了,分紅兩篇吧。