JavaScript建立對象的方式有不少,經過Object構造函數或對象字面量的方式也能夠建立單個對象,顯然這兩種方式會產生大量的重複代碼,並不適合量產。接下來介紹七種很是經典的建立對象的方式,他們也各有優缺點。(內容主要來自於《JavaScript高級程序設計》,還參考了一下別人寫的文章)安全
function createPerson(name, job) { var o = new Object(); o.name = name; o.job = job; o.sayName = function() { console.log(this.name); } return o } var person1 = createPerson('Mike', 'student') var person2 = createPerson('X', 'engineer')
能夠無數次調用這個工廠函數,每次都會返回一個包含兩個屬性和一個方法的對象。
工廠模式雖然解決了建立多個類似對象的問題,可是沒有解決對象識別問題,即不能知道一個對象的類型。函數
function Person(name, job) { this.name = name; this.job = job; this.sayName = function() { console.log(this.name); } } var person1 = new Person('Mike', 'student') var person2 = new Person('X', 'engineer')
沒有顯示的建立對象,使用new來調用這個構造函數,使用new後會自動執行以下操做:
①建立一個新對象;
②將構造函數的做用域賦給新對象(所以this就指向了這個新對象);
③執行構造函數中的代碼(爲這個新對象添加屬性);
④返回新對象。
缺點:每一個方法都要在每一個實例上從新建立一遍。
建立兩個完成一樣任務的的Function實例的確沒有必要。何況有this對象在,根本不用在執行代碼前就把函數綁定到特定的對象上,能夠經過這樣的形式定義:this
function Person( name, age, job ){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert( this.name ); }
如此一來,就能夠將sayName()函數的定義轉移到構造函數外部。而在構造函數內部,咱們將sayName屬性設置成全局的sayName函數。這樣的話,因爲sayName包含的是一個指向函數的指針,所以person1和person2對象就能夠共享在全局做用域中定義的同一個sayName()函數。prototype
這樣作解決了兩個函數作同一件事的問題,可是新的問題又來了:在全局做用域中定義的函數實際上只能被某個對象調用,這讓全局做用域有點名存實亡。而更重要的是:若是對象須要定義不少方法,那麼就須要定義不少個全局函數,這樣一來,咱們自定義的這個引用類型就毫無封裝性可言了。設計
這些問題能夠經過使用原型模式來解決。指針
function Person() { } Person.prototype.name = 'Mike' Person.prototype.job = 'student' Person.prototype.sayName = function() { console.log(this.name) } var person1 = new Person()
將信息直接添加到原型對象上。使用原型的好處是可讓全部的實例對象共享它所包含的屬性和方法,沒必要在構造函數中定義對象實例信息,而是能夠將這些信息直接添加到原型對象中。
①理解原型
不管何時,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個prototype屬性。
在默認狀況下,全部prototype屬性都會自動得到一個constructor(構造函數)屬性,這個屬性包含一個指向prototype屬性所在函數的指針。
每當代碼讀取某個對象的某個屬性時,都會執行一搜索,目標是具備給定名字的屬性。搜索首先從對象實例自己開始。若是在實例中找到了具備給定名字的屬性,則返回該屬性的值;若是沒有找到,則繼續搜索指針指向的原型對象,在原型對象中查找具備給定名字的屬性。若是在原型對象中找到了這個屬性,則返回該屬性的值。
雖然能夠經過對象實例訪問保存在原型中的值,但卻不能經過對象實例重寫原型中的值。
若是咱們在實例中添加了一個屬性,而該屬性與實例中的一個屬性同名,那麼就會在實例中建立該屬性,該屬性將會屏蔽原型中的那個屬性。
即便是將屬性設置爲null,也只是在實例中的屬性值爲null。
不過,使用delete操做符能夠徹底刪除實例屬性,從而可以從新訪問原型中的屬性。
使用hasOwnProperty() 方法能夠檢測一個屬性是存在於實例中,仍是存在與原型中。這個方法只在給定屬性存在於對象實例中時,纔會返回true。code
②原型與in操做符
in操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性是存在於實例中仍是原型中。對象
③更簡單的原型語法ip
function Person(){ } Person.prototype = { name : "Mike", age : 29, job : "engineer", syaName : function(){ alert( this.name ); } };
//在上面的代碼中,將Person.prototype設置爲等於一個以對象字面量形式建立的新對象。最終結果相同,但有一個例外:constructor屬性再也不指向Person。作用域
組合使用構造函數模式和原型模式是使用最爲普遍、認同度最高的一種建立自定義類型的方法。它能夠解決上面那些模式的缺點,使用此模式可讓每一個實例都會有本身的一份實例屬性副本,但同時又共享着對方法的引用,這樣的話,即便實例屬性修改引用類型的值,也不會影響其餘實例的屬性值了。還支持向構造函數傳遞參數,可謂是集兩種模式的優勢。
function Person(name) { this.name = name; this.friends = ['Jack', 'Merry']; } Person.prototype.sayName = function() { console.log(this.name); } var person1 = new Person(); var person2 = new Person(); person1.friends.push('Van'); console.log(person1.friends) //["Jack", "Merry", "Van"] console.log(person2.friends) // ["Jack", "Merry"] console.log(person1.friends === person2.friends) //false
動態原型模式將全部信息都封裝在了構造函數中,初始化的時候。能夠經過檢測某個應該存在的方法是否有效,來決定是否須要初始化原型。
function Person(name, job) { // 屬性 this.name = name; this.job = job; // 方法 if(typeof this.sayName !== 'function') { Person.prototype.sayName = function() { console.log(this.name) } } } var person1 = new Person('Mike', 'Student') person1.sayName()
只有在sayName方法不存在的時候,纔會將它添加到原型中。這段代碼只會初次調用構造函數的時候纔會執行。此後原型已經完成初始化,不須要在作什麼修改了,這裏對原型所作的修改,可以當即在全部實例中獲得反映。
其次,if語句檢查的能夠是初始化以後應該存在的任何屬性或方法,因此沒必要用一大堆的if語句檢查每個屬性和方法,只要檢查一個就行。
這種模式的基本思想就是建立一個函數,該函數的做用僅僅是封裝建立對象的代碼,而後再返回新建的對象
function Person(name, job) { var o = new Object(); o.name = name; o.job = job; o.sayName = function() { console.log(this.name) } return o } var person1 = new Person('Mike', 'student') person1.sayName()
這個模式,除了使用new操做符並把使用的包裝函數叫作構造函數以外,和工廠模式幾乎同樣。
構造函數若是不返回對象,默認也會返回一個新的對象,經過在構造函數的末尾添加一個return語句,能夠重寫調用構造函數時返回的值。
首先明白穩妥對象指的是沒有公共屬性,並且其方法也不引用this。穩妥對象最適合在一些安全環境中(這些環境會禁止使用this和new),或防止數據被其餘應用程序改動時使用。
穩妥構造函數模式和寄生模式相似,有兩點不一樣:1.是建立對象的實例方法不引用this;2.不使用new操做符調用構造函數
function Person(name, job) { var o = new Object(); o.name = name; o.job = job; o.sayName = function() { console.log(name) //注意這裏沒有了"this"; } return o } var person1 = Person('Mike', 'student') person1.sayName();
和寄生構造函數模式同樣,這樣建立出來的對象與構造函數之間沒有什麼關係,instanceof操做符對他們沒有意義