建立對象有不少種方式,首先是最簡單基本的兩種方式:chrome
①建立一個object實例安全
var o = new Object(); o.name = "a"; o.age = "12"; o.setName = function(){ alert(this.name); }
②對象字面量app
var o = { name: "a", age: "12", setName: function(){ alert(this.name); } };
這兩種方法建立單個對象是沒什麼問題,但很明顯的,若須要建立大量對象,就會產生不少重複的代碼。因此如下就講一下能解決這個問題的7種模式。函數
工廠模式:用一個函數來封裝建立對象的細節,返回建立的對象。this
function createPerson(name, age){ var o = new Object(); o.name = name; o.age = age; o.setName = function(){ alert(this.name); }; return o; } var p1 = createPerson('a', 12); var p2 = createPerson('b', 23);
雖然工廠模式解決了建立多個類似對象的問題,可是卻沒法識別對象類型。spa
function Person(name, age){ this.name = name; this.age = age; this.setName = function(){ alert(this.name); }; } var p1 = new Person('a', 12); var p2 = new Person('b', 23); alert(p1.constructor == Person);//true alert(p1 instanceof Object);//true alert(p1 instanceof Person);//true
它和工廠模式代碼上的主要區別就在於它顯式的建立了一個此構造函數的實例,而且還能夠注意到構造函數以一個大寫字母開頭。prototype
new建立實例的過程:指針
①建立一個新對象code
②將構造函數的做用域賦給新建立的對象對象
③爲新建立的對象添加屬性
④返回新對象
這種模式下,實例就有了特定的類型,好比上面的代碼中的p1,它的類型再也不只是Object,還有Person。
以這種方式定義的構造函數定義在Global對象中。
但這種模式並非沒有缺點:在構造函數中建立的方法會在每一個實例上從新建立一遍。在js中,方法即函數,也是一個對象,以上的建立方法就至關於:
function Person(name,age){ this.name = name; this.age = age; this.setName = new Function(" alert(this.name)"); } var p1 = new Person('a', 12); var p2 = new Person('b', 23); alert(p1.setName == p2.setName);//false
每建立一個實例,方法都會被建立一次,因此兩個實例的方法並非同一個方法,但其實兩個方法要完成的是相同的任務,那可不能夠把方法定義到構造函數外面呢?
function Person(name, age){ this.name = name; this.age = age; this.setName = setName; } function setName(){ alert(this.name); } var p1 = new Person('a', 12); var p2 = new Person('b', 23);
固然能夠,可是定義在全局做用域中的函數就使咱們建立的自定義引用類型再也不具備封裝性了,若是對象須要不少方法,那麼就須要定義不少個全局函數。
每一個被咱們建立的函數都有一個原型屬性,它是一個指針,指向了一個原型對象,而這個原型對象包含了能夠由特定類型的全部實例共享的屬性和方法。很明顯地,原型模式的好處:讓全部對象實例能夠共享它所包含的屬性和方法。
function Person(){ } Person.prototype.name = 'a'; Person.prototype.age = '12'; Person.prototype.setName = function(){ alert(this.name); }; var p1 = new Person(); var p2 = new Person(); alert(p1.setName == p2.setName);//true alert(Object.getPrototypeOf(p1));//Object object alert(Object.getPrototypeOf(p1) == Person.prototype);//true
原型對象和構造函數還有實例的關係:
原型對象中有一個constructor屬性指向構造函數,而構造函數中有一個prototype屬性指向原型對象,實例中又有一個內部指針[[prototype]]指向原型對象。
由於constructor屬性在原型對象中,因此這個屬性是共享的,能夠被對象實例訪問。而[[prototype]]是一個內部指針,全部實現都沒法訪問到(可是在ff、safari、chrome中每一個對象都支持一個屬性__proto__,其餘實現中,這個屬性對腳本則是徹底不可見的),只能經過isPrototypeOf()方法來肯定對象之間是否存在這種關係或者用Object.getPrototypeOf(instance)返回[[prototype]]的值。
能夠經過對象實例訪問保存在原型中的值,可是卻不能經過對象實例重寫原型的值,只能屏蔽原型中的那個屬性。
搜索過程以下:
每次代碼讀取一個對象的屬性時,會進行一次搜索。先搜索實例對象裏的屬性,若沒有,再去實例對應的原型對象中搜索。
若在實例中設置了和原型對象中同名的屬性,那麼即便再將實例中設置的同名屬性設置爲null,原型對象中的那個屬性也將再也不能被獲取到,它會阻斷咱們訪問原型中的哪一個屬性,但不會修改,除非使用delete操做符刪除實例屬性。
原型對象也能夠用對象字面量的方式:
function Person(){} Person.prototype = { name: 'a', age: 12, setName: function(){ alert(this.name); } };
可是用這種方式至關於將原型對象重寫了一遍,此時constructor就再也不指向Person了,而是指向了Object,因此應該在對象字面量中設置下constructor屬性(從新設置的constructor屬性的[[enumerable]]會被設置爲true)。
還須要注意的一點是,本來即使實例建立在原型以前也沒有關係,但若用對象字面量就會出錯:
function Person(){} var p1=new Person(); Person.prototype = { name: 'a', age: 12, setName: function(){ alert(this.name); } }; p1.setName();//error
由於實例對象的內部指針是指向原型對象的,而若在建立實例以後重寫原型對象,那麼原型對象就不是同一個了,這將會切斷現有原型與任何以前已經存在的對象實例之間的聯繫。
原型對象的問題:
function Person(){} Person.prototype = { name: 'a', age: 12, friends : ['z', 'x'], setName: function(){ alert(this.name); }; }; var p1 = new Person(); var p2 = new Person(); p1.friends.push('q'); alert(p1.friends);//['z','x','q'] alert(p2.friends);//['z','x','q']
當對實例一的引用類型屬性push值時,也會讓實例二的屬性也獲得push的那個值。
結合原型模式和構造函數模式,讓構造函數模式用於定義實例屬性,原型模式用於定義方法和共享的屬性。
function Person(name, age){ this.name = name; this.age = age; this.friends = ['z', 'x']; } Person.prototype.setName = function(){ alert(this.name); }; var p1 = new Person(); var p2 = new Person(); p1.friends.push('q'); alert(p1.friends);//['z','x','q'] alert(p2.friends);//['z','x']
這種模式最大限度地節省了內存,還支持向構造函數中傳遞參數,集兩種模式之長。
動態原型模式擁有上一個模式的優勢而且更像類。
function Person(name, age){ this.name = name; this.age = age;
// 只有在第一次調用構造函數時會添加此函數 if(typeof this.setName != 'function'){ Person.prototype.setName = function(){ alert(this.name); }; } }
若是用了這種模式,那麼就不能再使用對象字面量重寫原型,由於會切斷現有實例與新原型之間的聯繫。
寄生構造函數模式:建立一個構造函數,構造函數中封裝了建立對象的代碼,返回這個對象。
function Person(name, age){ var o = new Object(); o.name = name; o.age = age; o.setName = function(){ alert(this.name); }; return o; } var p1=new Person('a', 12);
這個模式通常用於建立一個具備額外方法的引用類型:
function SpecialArray(){ var values = new Array(); values.push.apply(values, arguments); values.toPipedString = function(){ return this.join('|'); }; return values; } var color = new SpecialArray('red', 'green');
alert(color instanceof SpecialArray); // false alert(color.toPipedString());//red|green
這個模式返回的對象與構造函數或者構造函數的原型屬性之間沒有關係,建議在可以使用其餘模式的狀況下,儘可能不使用這種模式。
穩妥構造函數模式:適合在安全的環境中,防止數據被其餘應用程序改動,它不能使用this和new。這種模式建立的對象和構造函數之間也沒有什麼關係。
function Person(name, age){ var o = new Object(); var name = name; o.sayName = function(){ alert(name); }; return o; } var p1 = Person('a',12); p1.sayName();//a
這個模式和寄生構造函數模式同樣,返回的對象與構造函數或者構造函數的原型屬性之間也沒有關係。