一、使用Object構造函數(shù)或對象字面量創(chuàng)建對象
缺點:使用同一個接口創(chuàng)建對象,會產(chǎn)生大量的重復性代碼。
解決方法:使用工廠模式創(chuàng)建對象。
1 //構造函數(shù)2 let obj1 = new Object();3 obj1.name = '我和鴿子有個約會';4 obj1.age = 22;5 //字面量6 let obj2 = {7 name: '我和鴿子有個約會',8 age: 22,9 };
二、使用工廠模式創(chuàng)建對象
核心思想:創(chuàng)建一種函數(shù),用函數(shù)來封裝以特定接口創(chuàng)建對象的細節(jié)。
優(yōu)點:
createPerson()可以根據(jù)接收來的三個參數(shù)來構建一個包含所有必要信息的person對象。
可以無數(shù)次地調(diào)用這個函數(shù),每次都能返回一個包含三個屬性和一個方法的對象。
這樣就解決了創(chuàng)建多個相似對象,產(chǎn)生大量重復代碼的問題。
缺點:
無法識別對象的類型,比如:我們想創(chuàng)建一個person類型(人類)的對象和cat類型(貓類)的對象,
但是我們無法將它們區(qū)分開來,因為它們本質(zhì)上都是通過Object構造函數(shù)創(chuàng)建的。
解決方法:構造函數(shù)模式
1 function createPerson(name, age, job) { 2 let obj = new Object(); 3 obj.name = name; 4 obj.age = 22; 5 obj.job = job; 6 obj.sayName = function () { 7 console.log(this.name); 8 }; 9 return obj;10 }11 12 let person1 = createPerson('鴿子1', 22, 'programmer');13 let person2 = createPerson('鴿子2', 20, 'student');14 let cat = createPerson('貓', 3, 'Catch mice');
三、構造函數(shù)模式
ECMAScript中的構造函數(shù)可以用來創(chuàng)建特定類型的對象,像Object、Array這樣的原生構造函數(shù),在運行時會自動出現(xiàn)在
執(zhí)行環(huán)境中,所以我們可以直接使用它們創(chuàng)建Object或者Array類型的對象。
當然,我們也可以自定義構造函數(shù),從而定義自定義對象類型的屬性和方法。
自定義構造函數(shù)與工廠模式中的函數(shù)的不同:
1.沒有顯示地創(chuàng)建對象;
2.直接將屬性和方法賦給了this對象;
3.沒有return語句
4.函數(shù)名首字母大寫,不是硬性要求只是一種規(guī)范,目的是為了將構造函數(shù)和普通函數(shù)區(qū)分開來,
因為構造函數(shù)也是函數(shù),只不過它可以用來創(chuàng)建對象而已。
使用構造函數(shù)創(chuàng)建對象這一行為,被稱為該構造函數(shù)的實例化(類的實例),其對象被稱為構造函數(shù)的實例或實例對象。
構造函數(shù)與其它函數(shù)的唯一區(qū)別就在于調(diào)用它們的方式不同。任何函數(shù),只要通過new操作符調(diào)用,那它就是構造函數(shù),
如果不通過new操作符來調(diào)用,那和普通函數(shù)沒什么區(qū)別。
注意:
使用構造函數(shù)創(chuàng)建對象需要使用 new 操作符。
這種方式調(diào)用構造函數(shù)實際上會經(jīng)歷以下4個步驟:
1.創(chuàng)建一個空對象;
2.將構造函數(shù)的作用域賦給新對象(因此this就指向了這個新對象);
3.執(zhí)行構造函數(shù)中的代碼(為這個新對象添加屬性);
4.返回新對象。
缺點:
所有方法都要在每個實例上重新創(chuàng)建一遍。
例如:person3和person4中的sayName()方法,雖然它們同屬于Person的實例,但其中的方法是獨立的,
也就是說,每實例化一個對象,都會在該對象中創(chuàng)建一個新的方法,這樣十分損耗性能。
解決方法:
1.將函數(shù)定義到構造函數(shù)外面,這樣每個實例對象中的方法都是同一個方法。
1 function Person(name, age, job) { 2 this.name = name; 3 this.age = age; 4 this.job = job; 5 this.sayName = sayName; 6 } 7 function sayName() { 8 console.log(this.name); 9 }10 console.log(person3.sayName === person4.sayName); //true 說明兩個實例對象中的方法是同一個
但這樣也有一個缺點:污染命名空間,比如一個構造函數(shù)有好多方法,就要定義好多變量,代碼多了就
有可能出現(xiàn)重名的情況。
2.使用原型模式
四、原型模式
每個函數(shù)都有一個prototype(原型)屬性,該屬性是一個指針,指向一個對象,而這個對象的用途是包含由構造
函數(shù)創(chuàng)建的所有實例對象共享的屬性和方法。
優(yōu)點:
可以讓所有實例對象共享它所包含的屬性和方法。換句話說,不必在構造函數(shù)中定義實例對象的信息,可以將
這些信息直接添加到原型對象中,這樣每個實例對象就能共用一個函數(shù)了,并且也不會污染命名空間。
1 function Person() { 2 } 3 4 Person.prototype.name = '鴿子1'; 5 Person.prototype.age = 22; 6 Person.prototype.job = 'programmer'; 7 Person.prototype.sayName = function () { 8 console.log(this.name); //鴿子1 9 };10 let person5 = new Person();11 let person6 = new Person();12 person5.sayName();13 person6.sayName();14 console.log(person5.sayName === person6.sayName); //true
4.1 原型對象
在創(chuàng)建函數(shù)的時候,系統(tǒng)會為該函數(shù)創(chuàng)建一個prototype指針,這個指針指向的對象被稱為原型對象。
每個原型對象中都有一個constructor(構造函數(shù))屬性,該屬性也是一個指針,指向其對應的構造函數(shù)。
每個實例對象中有兩個指針: constructor(指向其構造函數(shù)) __proto__(指向其構造函數(shù)的原型對象)
1 console.log(person5.__proto__ === Person.prototype); //true2 console.log(person5.constructor === Person); //true
兩個方法:
A.prototype.isProtypeof(B)
判斷實例對象B中是否存在一個指針,指向構造函數(shù)A的原型對象,是返回true,否返回false,
因為實例對象中有一個指針指向其構造函數(shù)的原型對象,所以我們可以間接的判斷B是不是A的實例對象。
Object.getPrototypeOf(obj) 返回實例對象的原型對象
1 console.log(Person.prototype.isPrototypeOf(person5));//true2 console.log(Object.getPrototypeOf(person5) === Person.prototype); //true3 console.log(Object.getPrototypeOf(person5).name); //鴿子1
代碼讀取實例對象屬性的過程:
每當代碼讀取某個對象的某個屬性時,都會先在實例對象中查找該屬性,如果查找到,則返回該屬性的值;如果沒找到,
則去往該對象的原型對象中查找,如果找到了,則返回該屬性的值。也就是說,在我們通過person5調(diào)用sayName方法
時,會先后執(zhí)行兩次搜索,第一次是在person5對象中,第二次是在person5的原型對象中。當我們通過person6調(diào)用
sayName方法時,會重現(xiàn)相同的搜索過程,這正是多個實例對象共享原型對象中保存的屬性和方法的基本原理。
注意:
雖然可以通過實例對象訪問其原型對象中的值,但是卻不能通過實例對象重寫原型中的值。如果我們在實例對象中添
加一個屬性,并且該屬性與實例對象的原型中的一個屬性同名,那么該屬性就會將原型中的同名屬性屏蔽,換句話說,
添加這個屬性只會阻止我們訪問原型中的那個屬性,而不會修改那個屬性,因為代碼讀取對象屬性的時候,首先會在
該對象中查找,查找到就不再搜索其原型中的屬性了。
1 console.log(person5.name);// 鴿子1 name來自原型對象2 person5.name = '兔子'; //將原型中的name屬性屏蔽3 console.log(person5.name);// 兔子 name來自實例對象
in 操作符
語法: 'name' in obj
判斷是否可以通過對象obj訪問到name屬性,可以則返回true,否則返回false,無論是該屬性存在于實例中還是
原型中。
hasOwnProperty()方法
語法:obj.hasOwnProperty('name')
判斷對象obj【自身中】是否含有name屬性,有則返回true,無則返回false
該方法用于檢測某屬性是存在于實例對象中還是原型對象中,只有給定屬性存在于實例對象中,才會返回true。
1 console.log('name' in person6);//true2 console.log(person5.hasOwnProperty('name'));//true3 console.log(person6.hasOwnProperty('name'));//false4 //創(chuàng)建一個函數(shù)判斷一個屬性到底是存在于對象中,還是存在于其原型中5 function hasPrototypeProperty(object, name) {6 return !object.hasOwnProperty(name) && name in object;//返回false則表示存在于對象中,返回true表示存在于原型中7 }8 9 hasPrototypeProperty(person5, 'name');//false
4.2.更簡單的原型語法
上面例子中,每給原型對象添加一個屬性或方法就要敲一遍Person.prototype。
為減少代碼量,也為了從視覺上更好地封裝原型的功能,更常見的做法是用一個包含所有屬性和方法的字面量對
象來重寫整個原型對象。
1 function Person() { 2 3 } 4 5 Person.prototype = { 6 name: '鴿子1', 7 age: 22, 8 job: 'programmer', 9 sayName: function () {10 console.log(this.name);11 },12 };13 console.log(Person.prototype.constructor === Person);//false
注意:
上面的代碼中,我們將Person.prototype指向一個新的對象,雖然結果相同,但有一個例外:
該原型對象的constructor屬性不再指向Person了,而是指向Object。
這是因為每創(chuàng)建一個函數(shù),都會生成一個prototype屬性指向它的原型對象,并且該原型對象有一個constructor
屬性指向這個函數(shù),然而,Person.prototype = {},本質(zhì)上是將prototype屬性指向一個新的對象,而這個新對
象的構造函數(shù)是Object。
如果constructor的值真的很重要,我們可以手動改回來
1 Person.prototype = { 2 constructor: Person,//手動更改原型對象中的constructor指向 3 name: '鴿子1', 4 age: 22, 5 job: 'programmer', 6 sayName: function () { 7 console.log(this.name); 8 }, 9 };10 console.log(Person.prototype.constructor === Person);//true
4.3 原型的動態(tài)性
因為在原型中查找值的過程是一次搜索,因此我們對原型對象的任何修改都能夠立即從實例上反映出來,即使是先創(chuàng)
建了實例后修改原型也是如此。
就像下邊的例子:
雖然friend實例是在新方法之前創(chuàng)建的,但是它仍能訪問到這個新方法,其原因可以歸結為實例與原型之間的
松散連接關系。當我們調(diào)用friend.sayHello()方法時,首先會在friend對象中搜索sayHello屬性,在沒
找到的情況下,會繼續(xù)搜索原型。
1 let friend = new Person();2 Person.prototype.sayHello = function () {3 console.log('hello');4 };5 friend.sayHello();//hello
注意:
盡管可以隨時為原型添加屬性和方法,并且修改能夠立即在對象實例中反映出來,但是如果重寫整個原型對象,那么
情況就不一樣了。
因為調(diào)用構造函數(shù)時會為實例添加一個指向最初原型的指針__proto__,而把原型修改為另一個對象就等于切斷了
構造函數(shù)與原型之間的聯(lián)系。
1 function Person() { 2 } 3 4 let person7 = new Person(); 5 Person.prototype = { 6 constructor: Person, 7 gugugu: '鴿子', 8 }; 9 console.log(person7.gugugu);//undefined10 console.log(person7.__proto__ === Person.prototype);//false
原型模式的缺點:
1.我們通過上面的例子可以發(fā)現(xiàn),它省略了為構造函數(shù)傳遞初始化參數(shù)這一環(huán)節(jié),結果所有實例對象在默認情況下都
將取得相同的屬性值。
2.原型模式最大的問題是由原型對象共享的特性所導致的。
原型中所有屬性是被多個實例所共享的,這種共享對于函數(shù)非常適合,對于那些包含基本數(shù)據(jù)類型值的屬性倒也說的
過去,畢竟,通過在實例上添加一個同名屬性,就可以隱藏原型中對應的屬性。然而,對于包含引用類型值的屬性來說,
這個問題就比較突出了。
1 function Animal() { 2 3 } 4 5 Animal.prototype = { 6 constructor: Animal, 7 name: '鴿子', 8 friends: ['貓咪', '大黃', '二哈'], 9 };10 let animal1 = new Animal();11 let animal2 = new Animal();12 animal1.friends.push('小白');13 console.log(animal1.friends);//['貓咪', '大黃', '二哈','小白']14 console.log(animal2.friends);//['貓咪', '大黃', '二哈','小白']15 console.log(animal1.friends === animal2.friends);//true
從上面的代碼中,我們就能看出問題:
當我們修改了animal1.friends引用的數(shù)組,向該數(shù)組中添加了一個字符串'小白'。由于friends數(shù)組存在于
Animal.prototype而非animal1中,所以剛才的修改也會通過animal2.friends(因為animal1.friends
和animal2.friends指向同一個數(shù)組)反映出來。
假如我們的初衷就是像這樣所有實例共享一個數(shù)組,那沒什么問題,但是實例一般都是要有屬于自己的全部屬性的,
所以很少有人會單獨使用原型模式。
解決方法:組合使用構造函數(shù)模式和原型模式
五、組合使用構造函數(shù)模式和原型模式
創(chuàng)建自定義類型的最常見的方式,就是組合使用構造函數(shù)模式和原型模式,構造函數(shù)模式用于定義每個實例獨立的屬
性,而原型模式用于定義方法和共享的屬性。
優(yōu)點:
1.每個實例都會有自己的一份實例屬性副本,但同時又能共享者對方法的引用,最大限度地節(jié)省了內(nèi)存;
2.支持向構造函數(shù)傳遞參數(shù)
1 function Animal(name, age, job) { 2 this.name = name; 3 this.age = age; 4 this.job = job; 5 this.friends = ['貓咪', '大黃', '二哈']; 6 } 7 8 Animal.prototype.sayName = function () { 9 console.log(this.name);10 };11 let animal3 = new Animal('鴿子', 3, 'gugugu');12 let animal4 = new Animal('大白鵝', 2, 'gugugu');13 animal3.sayName();14 animal4.sayName();15 animal3.friends.push('小白');16 console.log(animal3.friends);//['貓咪', '大黃', '二哈','小白']17 console.log(animal4.friends);//['貓咪', '大黃', '二哈']18 console.log(animal3.friends === animal4.friends);//false
作者:
我和鴿子有個約會本文版權歸作者和博客園共有,歡迎轉載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。