摘要:創(chuàng)建對象在之前說過通過構(gòu)造函數(shù)或者對象字面量的方式可以創(chuàng)建對象。寄生構(gòu)造函數(shù)模式寄生構(gòu)造函數(shù)模式的基本思想是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后在返回新創(chuàng)建的對象。
在之前說過通過Object構(gòu)造函數(shù)或者對象字面量的方式可以創(chuàng)建對象。但是這些方式有一個明顯的問題,使用同一個接口創(chuàng)建很多對象會產(chǎn)生大量的重復(fù)代碼。例如:
//如果你要創(chuàng)建三個對象,通過Object構(gòu)造函數(shù)你就要new三個Object構(gòu)造函數(shù) var obj1 = new Object() var obj2 = new Object() var obj3 = new Object()
工廠模式抽象的創(chuàng)建了具體對象的過程,通過用函數(shù)封裝以特定接口創(chuàng)建對象。
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("Nicholas", 29, "software enginner") var person2 = createPerson("greg", 27, "doctor")
如上,通過調(diào)用createPerson函數(shù),然后傳入特定的參數(shù)可以創(chuàng)建特定的對象,雖然工廠模式解決了創(chuàng)建多個相似對象的問題,但是沒有解決對象識別的問題(即怎樣知道一個對象的類型)。
ECMAScript中的構(gòu)造函數(shù)可以用來創(chuàng)建特定類型的對象,像Object和Array這樣的原生構(gòu)造函數(shù)在運行時會自動出現(xiàn)在執(zhí)行環(huán)境中。這樣我們也可以創(chuàng)建自定義的構(gòu)造函數(shù),從而定義自定義對象類型的屬性和方法。
function Person(name, age, job){ this.name = name this.age = age this.job = job this.sayName = function() { alert(this.name) } } var person1 = new Person("Nicholas", 29, "software enginner") var person2 = new Person("Grey", 27, "Doctor")
如上例子,Person構(gòu)造函數(shù)取代了之前的createPerson工廠函數(shù)。與之前的工廠函數(shù)創(chuàng)建對象的方法有以下幾點區(qū)別:
1. 沒有顯示的創(chuàng)建對象 2. 直接將屬性和方法賦給了this對象 3. 沒有return語句
還有一點可以看到,我們是通過new操作符創(chuàng)建對象,那么使用new操作符發(fā)生了什么呢?
1. 創(chuàng)建一個新的對象 2. 將構(gòu)造函數(shù)的作用域賦給新對象(因此this指向了這個新對象) 3. 執(zhí)行構(gòu)造函數(shù)中的代碼 4. 返回一個新對象
在前面例子的最后,person1和person2分別保存著Person的一個不同實例,這兩個對象都有一個constructor(構(gòu)造函數(shù))屬性,該屬性指向Person。
console.log(person1.constructor) //? Person(name, age, job){ // this.name = name // this.age = age // this.job = job // this.sayName = function() { // alert(this.name) // } //} person1.constructor == Person //true person2.constructor == Person //true
對象的constructor屬性最初是用來標(biāo)識對象類型的。但是,提到檢測對象類型,還是instranceof操作符更可靠點。我們在例子中創(chuàng)建的所有對象既是Object的實例,同時也是Person的實例。
console.log(person1 instanceof Object) //true console.log(person1 instanceof Person) // true console.log(person2 instanceof Object) //true console.log(person2 instanceof Person) // true
創(chuàng)建自定義的構(gòu)造函數(shù)意味著可以將它的實例標(biāo)識為一種特定的類型,這正是構(gòu)造函數(shù)模式勝過工廠模式的地方。
構(gòu)造函數(shù)也是函數(shù),與普通函數(shù)之間的區(qū)別就是構(gòu)造函數(shù)和普通函數(shù)調(diào)用的方式不一樣。
function Person(name, age, job){ this.name = name this.age = age this.job = job this.sayName = function() { alert(this.name) } } //當(dāng)作構(gòu)造函數(shù)使用 var person = new Person("Nicholas", 29, "software engineer") //作為普通函數(shù)使用 Person("gray", 29, "doctor") window.sayName() //gray //再另一個對象的作用域中調(diào)用 var o = new Object() Person.call(o, "kir", 25, "nurse") o.sayName() //"nurse"
構(gòu)造函數(shù)創(chuàng)建對象的缺點:構(gòu)造函數(shù)方式創(chuàng)建對象相對于工廠函數(shù)的優(yōu)勢就是可以將它的實例作為一種特定類型。但是通過構(gòu)造函數(shù)方式創(chuàng)建的對象,每個方法都要在每個實例上重新創(chuàng)建一次。在之前的例子上person1和person2都有一個名為sayName的方法,但是那兩個方法不是同一個Function實例。改寫下之前的構(gòu)造函數(shù)方式就會很清楚:
function Person(name, age, job) { this.name = name this.age = age this.job = job this.sayName = new Function("alert(this.name)") }
從改寫的例子上可以看到,每個Person實例都包含一個不同的Function實例。然而創(chuàng)建兩個完成相同功能的Function實例是沒有必要的。況且有this對象的存在,根本不用在執(zhí)行代碼前就把函數(shù)綁定到特定對象上面,因此可以像下面這樣把函數(shù)轉(zhuǎn)移到構(gòu)造函數(shù)外部:
function Person(name, age, job) { this.name = name this.age = age this.job = job this.sayName = sayName } function sayName() { alert(this.name) }
如上,通過Person構(gòu)造函數(shù)創(chuàng)建的對象都共享全局作用域中的sayName函數(shù)。但是這樣的寫法總感覺有點不合適,如果對象有很多方法,那么就要在全局作用域中創(chuàng)建很多函數(shù)。接下來介紹一個原型模式可以解決這個問題。
我們創(chuàng)建的每個函數(shù)都有一個prototype屬性,這個屬性時一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法。
通過字面意思了解,prototype就是通過調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個對象實例的原型對象,使用原型對象的好處是可以讓所有對象實例共享它所包含的屬性和方法。
function Person() {} Person.prototype.name = "Nicholas" Person.prototype.age = 29 Person.prototype.job = "software engineer" Person.prototype.sayName = function() { alert(this.name) } var person1 = new Person() person1.sayName() //"Nicholas" var person2 = new Person() person2.sayName() //"Nicholas" console.log(person1.sayName == person2.sayName) //true
如上可以看到,創(chuàng)建的對象的這些屬性和方法是由所有實例共享的,換句話說,person1和person2訪問的都是同一組屬性和同一個sayName函數(shù)。
首先我們理解下什么是原型對象?
當(dāng)我們創(chuàng)建任意一個新函數(shù)的時候,就會根據(jù)特定的規(guī)則為該函數(shù)創(chuàng)建一個prototype屬性,這個屬性指向函數(shù)的原型對象。默認(rèn)情況下,原型對象會有一個constructor屬性,該屬性指向prototype屬性所在函數(shù)的指針。
function Person() {} //Person.prototype.constructor 指向的就是Person構(gòu)造函數(shù), 通過這個構(gòu)造函數(shù)我們可以繼續(xù)為原型對象添加屬性和方法
創(chuàng)建了自定義的構(gòu)造函數(shù)之后,其原型對象默認(rèn)有constructor屬性,至于其他方法都是從Object繼承而來。當(dāng)調(diào)用構(gòu)造函數(shù)創(chuàng)建一個新實例后,該實例內(nèi)部將包含一個指針(內(nèi)部屬性)指向構(gòu)造函數(shù)的原型對象。通常我們是用(__proto__)來表示的。這個連接是存在于對象實例和構(gòu)造函數(shù)原型對象之間的,而不是對象實例和構(gòu)造函數(shù)之間。
isPrototypeOf( )
我們可以通過isPrototypeOf( )方法來判斷對象實例的__proto__是否指向?qū)?yīng)的原型對象。
Person.prototype.isPrototypeOf(person1) //true Person.prototype.isPrototypeOf(person2) //true
Object.getPrototypeOf( )
該方法會返回傳入的實例對象的原型對象的值。
Object.getPrototypeOf(person1) == Person.prototype //true Object.getPrototypeOf(person1).name = "Nicholas"
每當(dāng)代碼讀取某個對象屬性的時候就會執(zhí)行一次搜索。搜索是首先從對象實例本身開始,如果在實例中找到了具有給定名字的屬性,則返回該屬性的值,如果沒有找到則繼續(xù)搜索指針指向的原型對象,在原型對象中找具有給定名字的屬性。
function Person() { this.name = "bob" } Person.prototype.name = "jim" var p = new Person() console.log(p.name) // "bob"
可以看到上面輸出的"bob"因為在每個實例對象上面有具有一個name為bob的屬性。
雖然我們可以通過對象實例訪問到原型對象中的值,但是我們并不能通過實例對象修改原型對象中的值。
hasOwnProperty()
通過hasOwnProperty()方法可以檢測一個屬性是存在于實例中,還是存在于原型中。該方法是從Object繼承來的。當(dāng)屬性存在于對象實例中時返回true。
function Person() {} Person.prototype.name = "Nicholas" Person.prototype.age = 29 Person.prototype.job = "soft ware" var p1 = new Person() var p2 = new Person() console.log(p1.hasOwnProperty("name")) //false p1.name = "bob" console.log(p1.hasOwnProperty("name")) //true
通過使用hasOwnProperty()方法,什么時候訪問的是實例屬性,什么時候訪問的是原型屬性就可以一清二楚了。
原型對象與in操作符
有兩種方式使用in操作符,多帶帶使用和在for-in循環(huán)中使用。in操作符會在通過對象能夠訪問給定屬性時返回true。(不管在對象實例上還是在原型對象上的屬性)
function Person() {} Person.prototype.name = "Nicholas" Person.prototype.age = 29 Person.job = "software engineer" var p1 = new Person() console.log("name" in p1) //true
要取得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法,這個方法接受一個對象作為參數(shù),返回一個包含所有可枚舉屬性的字符串?dāng)?shù)組。
function P() { this.a = "a" } P.prototype.b = "b" var o = new P() console.log(Object.keys(o)) //["a"]
Object.getOwnPropertyNames()
如果你想獲得所有的實例屬性,不管是否可枚舉的,可以通過Object.getOwnPropertyNames().
原型的動態(tài)性
由于在原型中查找值的過程是一次搜索,因此我們隊原型對象所做的任何修改都能夠立即從實例上反映出來,即使是先創(chuàng)建實例對象后修改原型也是一樣。因為實例與原型對象之間的連接只不過是一個指針,而非一個副本。
注意,我們可以隨時為原型對象添加屬性和方法,但是如果重寫了整個原型對象,那么情況就不一樣了。
function Person() {} var friend = new Person() Person.prototype = { constructor: Person, name: "nike", sayName: function() { alert(this.name) } } friend.sayName() //error
重寫了原型對象切斷了現(xiàn)有原型和任何之前已經(jīng)存在的對象實例之間的關(guān)系,它們引用的任然是之前最初的原型。
通過使用這兩種結(jié)合的方式創(chuàng)建對象是很常見的方式,構(gòu)造函數(shù)模式用于定義實例屬性,而原型模式用于定義方法和共享屬性。結(jié)果每個實例都會有自己的一份實例屬性的副本,但同時又共享著對方法的引用,最大限度的節(jié)約了內(nèi)存。
function Person(name, age, job) { this.name = name this.age = age this.job = job this.friends = ["shelby", "count"] } Person.prototype = { constructor: Person, sayName: function() { alert(this.name) } }
動態(tài)原型模式把所有的信息都封裝在了構(gòu)造函數(shù)中,而通過在構(gòu)造函數(shù)中初始化原型,又保持了同時使用構(gòu)造函數(shù)和原型的優(yōu)點??梢酝ㄟ^檢查某個應(yīng)該存在的方法是否有效,來決定是否需要初始化原型。
function Person(name, age, job) { this.name = name this.age = age this.job = job if(typeof this.sayName != "function") { Person.prototype.sayName = function() { alert(this.name) } } }
寄生構(gòu)造函數(shù)模式的基本思想是創(chuàng)建一個函數(shù),該函數(shù)的作用僅僅是封裝創(chuàng)建對象的代碼,然后在返回新創(chuàng)建的對象。但從表面上看,這個函數(shù)又很像是典型的構(gòu)造函數(shù)。
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("Nicholas", 29, "software engineer") friend.sayName() //"Nicholas"
如上可以看到,Person函數(shù)創(chuàng)建了一個新對象,并初始化該對象的屬性和方法,然后返回該對象。其實這個方式和工廠模式是幾乎一樣的,構(gòu)造函數(shù)在不返回值的情況下,默認(rèn)會返回新對象實例。這里通過return語句,重寫了構(gòu)造函數(shù)的返回值。
//工廠模式, 可以看到寄生構(gòu)造函數(shù)模式和工廠模式不同的地方就是使用new操作符 function Person(name, age, job) { var o = new Object() o.name = name o.age = age o.job = job o.sayName = function() { alert(this.name) } } var person1 = Person("Nicholas", 29, "software enginner")
那么這個方法的使用場景是什么呢?(其實我也沒弄明白)這個模式可以在特殊的情況下來為對象創(chuàng)建構(gòu)造函數(shù)。假設(shè)我們想創(chuàng)建一個具有額外方法的特殊數(shù)組。由于不能直接修改Array構(gòu)造函數(shù),因此可以使用這個模式。
function SpecialArray() { var values = new Array() values.push.apply(values, arguments) values.toPipedString = function() { return this.join("|") } return values } var c = new SpecialArray("red", "blue", "green") console.log(c.toPipedString()) // "red|blue|green"
關(guān)于寄生構(gòu)造函數(shù)模式,返回的對象與構(gòu)造函數(shù)或者和構(gòu)造函數(shù)的原型屬性沒有關(guān)系,也就是說構(gòu)造函數(shù)返回的對象與在構(gòu)造函數(shù)外部創(chuàng)建的對象沒什么不同。所以通過instanceof操作符來確定對象類型是沒有意義的。
穩(wěn)妥對象指的是沒有公共屬性,而且方法也不引用this對象。穩(wěn)妥對象醉適合在一些安全的環(huán)境中(禁止使用this和new),或者在防止數(shù)據(jù)被其他應(yīng)用程序改動時使用。
function Person(name, age, jbo) { var o = new Object() o.sayName = function() { alert(name) } return o }
注意,這里看著也很像工廠函數(shù),但是和工廠函數(shù)有區(qū)別的就是沒有使用this。這里只有通過sayName方法來訪問name。
var friend = Person("Nicholas", 29, "software Enigneer") friend.sayName()
這樣,變量friend中保存了一個穩(wěn)妥對象,除了調(diào)用sayName方法外,沒有其他的方式可以訪問其數(shù)據(jù)成員。即使可以通過其他辦法給這個對象添加方法或數(shù)據(jù)成員,但也沒有辦法進(jìn)行訪問,穩(wěn)妥構(gòu)造函函數(shù)模式提供的這種安全性,令他非常適合在某些安全執(zhí)行環(huán)境。
總結(jié):關(guān)于創(chuàng)建對象的幾種方式基本上都在這里了,個人覺得常用的還是構(gòu)造函數(shù)模式和原型模式的結(jié)合版本。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/89093.html
摘要:構(gòu)造函數(shù)通常首字母大寫,用于區(qū)分普通函數(shù)。這種關(guān)系常被稱為原型鏈,它解釋了為何一個對象會擁有定義在其他對象中的屬性和方法。中所有的對象,都有一個屬性,指向?qū)嵗龑ο蟮臉?gòu)造函數(shù)原型由于是個非標(biāo)準(zhǔn)屬性,因此只有和兩個瀏覽器支持,標(biāo)準(zhǔn)方法是。 從這篇文章開始,復(fù)習(xí) MDN 中級教程 的內(nèi)容了,在初級教程中,我和大家分享了一些比較簡單基礎(chǔ)的知識點,并放在我的 【Cute-JavaScript】系...
摘要:本文是重溫基礎(chǔ)系列文章的第十四篇。元,是指程序本身。有理解不到位,還請指點,具體詳細(xì)的介紹,可以查看維基百科元編程。攔截,返回一個布爾值。 本文是 重溫基礎(chǔ) 系列文章的第十四篇。 這是第一個基礎(chǔ)系列的最后一篇,后面會開始復(fù)習(xí)一些中級的知識了,歡迎持續(xù)關(guān)注呀! 接下來會統(tǒng)一整理到我的【Cute-JavaScript】的JavaScript基礎(chǔ)系列中。 今日感受:獨樂樂不如眾樂樂...
摘要:系列目錄復(fù)習(xí)資料資料整理個人整理重溫基礎(chǔ)篇重溫基礎(chǔ)對象介紹重溫基礎(chǔ)對象介紹重溫基礎(chǔ)介紹重溫基礎(chǔ)相等性判斷本章節(jié)復(fù)習(xí)的是中的關(guān)于閉包,這個小哥哥呀,看看。這里隨著閉包函數(shù)的結(jié)束,執(zhí)行環(huán)境銷毀,變量回收。 本文是 重溫基礎(chǔ) 系列文章的第十九篇。今日感受:將混亂的事情找出之間的聯(lián)系,也是種能力。 系列目錄: 【復(fù)習(xí)資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎(chǔ)】...
摘要:內(nèi)存泄露內(nèi)存泄露概念在計算機(jī)科學(xué)中,內(nèi)存泄漏指由于疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存。判斷內(nèi)存泄漏,以字段為準(zhǔn)。 本文是 重溫基礎(chǔ) 系列文章的第二十二篇。 今日感受:優(yōu)化學(xué)習(xí)方法。 系列目錄: 【復(fù)習(xí)資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎(chǔ)】1-14篇 【重溫基礎(chǔ)】15.JS對象介紹 【重溫基礎(chǔ)】16.JSON對象介紹 【重溫基礎(chǔ)】1...
摘要:本文是重溫基礎(chǔ)系列文章的第七篇。系列目錄復(fù)習(xí)資料資料整理個人整理重溫基礎(chǔ)語法和數(shù)據(jù)類型重溫基礎(chǔ)流程控制和錯誤處理重溫基礎(chǔ)循環(huán)和迭代重溫基礎(chǔ)函數(shù)重溫基礎(chǔ)表達(dá)式和運算符重溫基礎(chǔ)數(shù)字本章節(jié)復(fù)習(xí)的是中的時間對象,一些處理的方法。 本文是 重溫基礎(chǔ) 系列文章的第七篇。今日感受:做好自律。 系列目錄: 【復(fù)習(xí)資料】ES6/ES7/ES8/ES9資料整理(個人整理) 【重溫基礎(chǔ)】1.語法和數(shù)據(jù)類型...
摘要:本文是重溫基礎(chǔ)系列文章的第十二篇。注意對象的名稱,對大小寫敏感?;A(chǔ)用法第一個參數(shù)是目標(biāo)對象,后面參數(shù)都是源對象。用途遍歷對象屬性。用途將對象轉(zhuǎn)為真正的結(jié)構(gòu)。使用場景取出參數(shù)對象所有可遍歷屬性,拷貝到當(dāng)前對象中。類似方法合并兩個對象。 本文是 重溫基礎(chǔ) 系列文章的第十二篇。 今日感受:需要總結(jié)下2018。 這幾天,重重的感冒發(fā)燒,在家休息好幾天,傷···。 系列目錄: 【復(fù)習(xí)資料...
閱讀 2054·2021-09-22 16:05
閱讀 9410·2021-09-22 15:03
閱讀 2910·2019-08-30 15:53
閱讀 1727·2019-08-29 11:15
閱讀 938·2019-08-26 13:52
閱讀 2379·2019-08-26 11:32
閱讀 1833·2019-08-26 10:38
閱讀 2598·2019-08-23 17:19