復(fù)雜對(duì)象可以保存任何允許的 JavaScript 值。在以下代碼中,我創(chuàng)建一個(gè)名為 myObject 的 Object() 對(duì)象,然后添加表示 JavaScript 中可用的大多數(shù)值的屬性。
復(fù)雜對(duì)象
示例:sample29.html
<script> var myObject = {}; // Contain properties inside of myObject representing most of the native JavaScript values. myObject.myfunction = function () { }; myObject.myArray = []; myObject.myString = 'string'; myObject.mynumber = 33; myObject.myDate = new Date(); myObject.myRegExp = /a/; myObject.myNULL = null; myObject.myundefined = undefined; myObject.myObject = {}; myObject.myMath_PI = Math.PI; myObject.myError = new Error('Darn!'); console.log(myObject.myFunction, myObject.myArray, myObject.myString, myObject.myNumber, myObject.myDate, myObject.myRegExp, myObject.myNull, myObject.myNull, myObject.myUndefined, myObject.myObject, myObject.myMath_PI, myObject.myError); /* Works the same with any of the complex objects, for example a function. */ var myFunction = function () { }; myFunction.myFunction = function () { }; myFunction.myArray = []; myFunction.myString = 'string'; myFunction.myNumber = 33; myFunction.myDate = new Date(); myFunction.myRegExp = /a/; myFunction.myNull = null; myFunction.myUndefined = undefined; myFunction.myObject = {}; myFunction.myMath_PI = Math.PI; myFunction.myError = new Error('Darn!'); console.log(myFunction.myFunction, myFunction.myArray, myFunction.myString, myFunction.myNumber, myFunction.myDate, myFunction.myRegExp, myFunction.myNull, myFunction.myNull, myFunction.myUndefined, myFunction.myObject, myFunction.myMath_PI, myFunction.myError); </script>
這里要學(xué)習(xí)的簡(jiǎn)單概念是,復(fù)雜對(duì)象可以包含任何可以在 JavaScript 中名義上表達(dá)的內(nèi)容。當(dāng)您看到此操作完成時(shí),您不應(yīng)該感到驚訝,因?yàn)樗斜緳C(jī)對(duì)象都可以發(fā)生變化。這甚至適用于對(duì)象形式的 String()、Number() 和 Boolean() 值,即使用 new 運(yùn)算符創(chuàng)建它們時(shí)。
以編程方式有益的方式封裝復(fù)雜對(duì)象
Object()、Array() 和 Function() 對(duì)象可以包含其他復(fù)雜對(duì)象。在下面的示例中,我通過(guò)使用 Object() 對(duì)象設(shè)置對(duì)象樹(shù)來(lái)演示這一點(diǎn)。
示例:sample30.html
<script> // Encapsulation using objects creates object chains. var object1 = { object1_1: { object1_1_1: {foo: 'bar'}, object1_1_2: {}, }, object1_2: { object1_2_1: {}, object1_2_2: {}, } }; console.log(object1.object1_1.object1_1_1.foo); // Logs 'bar'. </script>
可以使用 Array() 對(duì)象(又名多維數(shù)組)或 Function() 對(duì)象完成同樣的操作。
示例:sample31.html
<script> // Encapsulation using arrays creates a multidimensional array chain. var myArray = [[[]]]; // An empty array, inside an empty array, inside an empty array. /* Here is an example of encapsulation using functions: An empty function inside an empty function inside an empty function. */ var myFunction = function () { // Empty function. var myFunction = function () { // Empty function. var myFunction = function () { // Empty function. }; }; }; // We can get crazy and mix and match too. var foo = [{ foo: [{ bar: { say: function () { return 'hi'; } }}]}]; console.log(foo[0].foo[0].bar.say()); // Logs 'hi'. </script>
這里要掌握的主要概念是,一些復(fù)雜對(duì)象被設(shè)計(jì)為以編程上有益的方式封裝其他對(duì)象。
使用點(diǎn)表示法或括號(hào)表示法獲取、設(shè)置和更新對(duì)象的屬性
我們可以使用點(diǎn)符號(hào)或方括號(hào)符號(hào)來(lái)獲取、設(shè)置或更新對(duì)象的屬性。
在下面的示例中,我演示了點(diǎn)表示法,這是通過(guò)使用對(duì)象名稱(chēng)后跟句點(diǎn),然后后跟要獲取、設(shè)置或更新的屬性來(lái)完成的(例如,objectName.Property)。
示例:sample32.html
<script> // Create a cody Object() object. var cody = new Object(); // Setting properties. cody.living = true; cody.age = 33; cody.gender = 'male'; cody.getGender = function () { return cody.gender; }; // Getting properties. console.log( cody.living, cody.age, cody.gender, cody.getGender() ); // Logs 'true 33 male male'. // Updating properties, exactly like setting. cody.living = false; cody.age = 99; cody.gender = 'female'; cody.getGender = function () { return 'Gender = ' + cody.gender; }; console.log(cody); </script>
點(diǎn)表示法是獲取、設(shè)置或更新對(duì)象屬性的最常見(jiàn)表示法。
除非需要,否則括號(hào)表示法并不常用。在下面的示例中,我將上一個(gè)示例中使用的點(diǎn)符號(hào)替換為括號(hào)符號(hào)。對(duì)象名稱(chēng)后跟一個(gè)左括號(hào)、屬性名稱(chēng)(用引號(hào)引起來(lái)),然后是一個(gè)右括號(hào):
示例:sample33.html
<script> // Creating a cody Object() object. var cody = new Object(); // Setting properties. cody['living'] = true; cody['age'] = 33; cody['gender'] = 'male'; cody['getGender'] = function () { return cody.gender; }; // Getting properties. console.log( cody['living'], cody['age'], cody['gender'], cody['getGender']() // Just slap the function invocation on the end! ); // Logs 'true 33 male male'. // Updating properties, very similar to setting. cody['living'] = false; cody['age'] = 99; cody['gender'] = 'female'; cody['getGender'] = function () { return 'Gender = ' + cody.gender; }; console.log(cody); </script>
當(dāng)您需要訪問(wèn)屬性鍵并且您必須使用包含表示屬性名稱(chēng)的字符串值的變量時(shí),括號(hào)表示法非常有用。在下一個(gè)示例中,我通過(guò)使用方括號(hào)表示法訪問(wèn)屬性 foobar 來(lái)演示括號(hào)表示法相對(duì)于點(diǎn)表示法的優(yōu)勢(shì)。我使用兩個(gè)變量來(lái)執(zhí)行此操作,這兩個(gè)變量在連接時(shí)會(huì)生成 foobarObject 中包含的屬性鍵的字符串版本。
示例:sample34.html
<script> var foobarObject = { foobar: 'Foobar is code for no code' }; var string1 = 'foo'; var string2 = 'bar'; console.log(foobarObject[string1 + string2]); // Let's see dot notation do this! </script>
此外,括號(hào)表示法可以方便地獲取無(wú)效 JavaScript 標(biāo)識(shí)符的屬性名稱(chēng)。在下面的代碼中,我使用一個(gè)數(shù)字和一個(gè)保留關(guān)鍵字作為屬性名稱(chēng)(作為字符串有效),只有括號(hào)表示法才能訪問(wèn)該屬性名稱(chēng)。
示例:sample35.html
<script> var myObject = { '123': 'zero', 'class': 'foo' }; // Let's see dot notation do this! Keep in mind 'class' is a keyword in JavaScript. console.log(myObject['123'], myObject['class']); //Logs 'zero foo'. // It can't do what bracket notation can do, in fact it causes an error. // console.log(myObject.0, myObject.class); </script>
因?yàn)閷?duì)象可以包含其他對(duì)象,所以 cody.object.object.object.object 或 cody[‘object’][‘object’][‘object’][‘object’] 可以在以下位置查看次。這稱(chēng)為對(duì)象鏈。對(duì)象的封裝可以無(wú)限期地進(jìn)行下去。
對(duì)象在 JavaScript 中是可變的,這意味著可以隨時(shí)對(duì)大多數(shù)對(duì)象執(zhí)行獲取、設(shè)置或更新它們。通過(guò)使用括號(hào)表示法(例如,cody[‘age’]),您可以模仿其他語(yǔ)言中的關(guān)聯(lián)數(shù)組。
如果對(duì)象內(nèi)的屬性是方法,您所要做的就是使用 () 運(yùn)算符(例如 cody.getGender())來(lái)調(diào)用屬性方法。
刪除對(duì)象屬性
delete 運(yùn)算符可用于完全刪除對(duì)象的屬性。在下面的代碼片段中,我們從 foo 對(duì)象中刪除 bar 屬性。
示例:sample36.html
<script> var foo = { bar: 'bar' }; delete foo.bar; console.log('bar' in foo); // Logs false, because bar was deleted from foo. </script>
delete 不會(huì)刪除在原型鏈上找到的屬性。
刪除是實(shí)際從對(duì)象中刪除屬性的唯一方法。將屬性設(shè)置為 undefined 或 null 僅更改該屬性的值。它不會(huì)從對(duì)象中刪除屬性。
如何解析對(duì)對(duì)象屬性的引用
如果您嘗試訪問(wèn)對(duì)象中未包含的屬性,JavaScript 將嘗試使用原型鏈查找該屬性或方法。在下面的示例中,我創(chuàng)建一個(gè)數(shù)組并嘗試訪問(wèn)尚未定義的名為 foo 的屬性。您可能會(huì)認(rèn)為,由于 myArray.foo 不是 myArray 對(duì)象的屬性,JavaScript 將立即返回 undefined。但是 JavaScript 會(huì)在另外兩個(gè)地方(Array.prototype 和 Object.prototype)查找 foo 的值,然后返回 undefined。
示例:sample37.html
<script> var myArray = []; console.log(myArray.foo); // Logs undefined. /* JS will look at Array.prototype for Array.prototype.foo, but it is not there. Then it will look for it at Object.prototype, but it is not there either, so undefined is returned! */ </script>
財(cái)產(chǎn)。如果它有該屬性,它將返回該屬性的值,并且不會(huì)發(fā)生繼承,因?yàn)樵玩湜](méi)有被杠桿化。如果實(shí)例沒(méi)有該屬性,JavaScript 將在對(duì)象的構(gòu)造函數(shù) prototype 對(duì)象中查找它。
所有對(duì)象實(shí)例都有一個(gè)屬性,該屬性是創(chuàng)建該實(shí)例的構(gòu)造函數(shù)的秘密鏈接(又名 __proto__)。可以利用這個(gè)秘密鏈接來(lái)獲取構(gòu)造函數(shù),特別是實(shí)例構(gòu)造函數(shù)的原型屬性。
這是 JavaScript 中對(duì)象最令人困惑的方面之一。但讓我們來(lái)推理一下。請(qǐng)記住,函數(shù)也是具有屬性的對(duì)象。允許對(duì)象從其他對(duì)象繼承屬性是有意義的。就像說(shuō):“嘿,對(duì)象 B,我希望你分享對(duì)象 A 擁有的所有屬性。”默認(rèn)情況下,JavaScript 通過(guò) prototype 對(duì)象將這一切連接到本機(jī)對(duì)象。當(dāng)您創(chuàng)建自己的構(gòu)造函數(shù)時(shí),您也可以利用原型鏈。
JavaScript 到底是如何實(shí)現(xiàn)這一點(diǎn)的?在您了解它的本質(zhì)之前,您會(huì)感到困惑:只是一組規(guī)則。讓我們創(chuàng)建一個(gè)數(shù)組來(lái)更仔細(xì)地檢查 prototype 屬性。
示例:sample38.html
<script> // myArray is an Array object. var myArray = ['foo', 'bar']; console.log(myArray.join()); // join() is actually defined at Array.prototype.join </script>
我們的 Array() 實(shí)例是一個(gè)具有屬性和方法的對(duì)象。當(dāng)我們?cè)L問(wèn)其中一種數(shù)組方法時(shí),例如 join(),我們問(wèn)自己:從 Array() 構(gòu)造函數(shù)創(chuàng)建的 myArray 實(shí)例是否有自己的 join() 方法?我們來(lái)檢查一下。
示例:sample39.html
<script> var myArray = ['foo', 'bar']; console.log(myArray.hasOwnProperty('join')); // Logs false. </script>
不,沒(méi)有。然而 myArray 可以訪問(wèn) join() 方法,就好像它是它自己的屬性一樣。這里發(fā)生了什么?好吧,您剛剛觀察了原型鏈的運(yùn)行情況。我們?cè)L問(wèn)了一個(gè)屬性,盡管該屬性不包含在 myArray 對(duì)象中,但 JavaScript 可以在其他地方找到該屬性。其他地方是非常具體的。當(dāng) Array() 構(gòu)造函數(shù)由 JavaScript 創(chuàng)建時(shí),join() 方法被添加(除其他外)作為 Array() 的 prototype 屬性的屬性。
重申一下,如果您嘗試訪問(wèn)不包含該屬性的對(duì)象上的屬性,JavaScript 將在 prototype 鏈中搜索該值。首先,它將查看創(chuàng)建對(duì)象的構(gòu)造函數(shù)(例如,Array),并檢查其原型(例如,Array.prototype)以查看是否可以在那里找到該屬性。如果第一個(gè)原型對(duì)象沒(méi)有該屬性,則 JavaScript 會(huì)繼續(xù)在初始構(gòu)造函數(shù)后面的構(gòu)造函數(shù)中沿鏈向上搜索。它可以一直做到這一點(diǎn),直到鏈的末端。
鏈條的終點(diǎn)在哪里?讓我們?cè)俅螜z查該示例,在 myArray 上調(diào)用 toLocaleString() 方法。
示例:sample40.html
<script> // myArray and Array.prototype contain no toLocaleString() method. var myArray = ['foo', 'bar']; // toLocaleString() is actually defined at Object.prototype.toLocaleString console.log(myArray.toLocaleString()); // Logs 'foo,bar'. </script>
toLocaleString() 方法未在 myArray 對(duì)象中定義。因此,原型鏈接規(guī)則被調(diào)用,JavaScript 在 Array 構(gòu)造函數(shù)原型屬性中查找屬性(例如,Array.prototype)。它也不存在,因此再次調(diào)用鏈?zhǔn)揭?guī)則,我們?cè)?Object() 原型屬性 (Object.prototype) 中查找該屬性。是的,它在那里找到。如果沒(méi)有在那里找到它,JavaScript 將產(chǎn)生一個(gè)錯(cuò)誤,指出該屬性是 undefined。
由于所有原型屬性都是對(duì)象,因此鏈中的最終鏈接是 Object.prototype。沒(méi)有其他可以檢查的構(gòu)造函數(shù)原型屬性。
前面有一整章將原型鏈分解為更小的部分,所以如果你完全不明白這一點(diǎn),請(qǐng)閱讀該章,然后再回到這個(gè)解釋來(lái)鞏固你的理解。從這篇簡(jiǎn)短的文章中,我希望您明白,當(dāng)找不到屬性時(shí)(并被視為 undefined),JavaScript 將查看幾個(gè)原型對(duì)象來(lái)確定屬性是 undefined。查找總是會(huì)發(fā)生,這個(gè)查找過(guò)程就是 JavaScript 處理繼承以及簡(jiǎn)單屬性查找的方式。
使用 hasOwnProperty 驗(yàn)證對(duì)象屬性不是來(lái)自原型鏈
雖然 in 運(yùn)算符可以檢查對(duì)象的屬性,包括來(lái)自原型鏈的屬性,但 hasOwnProperty 方法可以檢查對(duì)象的屬性是否來(lái)自原型鏈。
在下面的示例中,我們想知道 myObject 是否包含屬性 foo,并且它沒(méi)有從原型鏈繼承該屬性。為此,我們?cè)儐?wèn) myObject 是否有自己的名為 foo 的屬性。
示例:sample41.html
<script> var myObject = {foo: 'value'}; console.log(myObject.hasOwnProperty('foo')) // Logs true. // Versus a property from the prototype chain. console.log(myObject.hasOwnProperty('toString')); // Logs false. </script>
當(dāng)您需要確定屬性是對(duì)象的本地屬性還是從原型鏈繼承時(shí),應(yīng)該利用 hasOwnProperty 方法。
使用 in 運(yùn)算符檢查對(duì)象是否包含給定屬性
in 運(yùn)算符用于驗(yàn)證(true 或 false)對(duì)象是否包含給定屬性。在此示例中,我們檢查 foo 是否是 myObject 中的屬性。
示例:sample42.html
<script> var myObject = { foo: 'value' }; console.log('foo' in myObject); // Logs true. </script>
您應(yīng)該知道 in 運(yùn)算符不僅檢查引用的對(duì)象中包含的屬性,還檢查對(duì)象通過(guò) prototype 鏈繼承的任何屬性。因此,應(yīng)用相同的屬性查找規(guī)則,如果當(dāng)前對(duì)象中沒(méi)有該屬性,則將在 prototype 鏈上搜索該屬性。
這意味著上一個(gè)示例中的 myObject 實(shí)際上通過(guò) prototype 鏈 (Object.prototype.toString) 包含一個(gè) toString 屬性方法,即使我們沒(méi)有指定一個(gè)(例如 myObject.toString) = ‘foo’)。
示例:sample43.html
<script> var myObject = { foo: 'value' }; console.log('toString' in myObject); // Logs true. </script>
在最后一個(gè)代碼示例中,toString 屬性實(shí)際上并不位于 myObject 對(duì)象內(nèi)部。但是,它是從 Object.prototype 繼承的,因此 in 運(yùn)算符得出的結(jié)論是 myObject 實(shí)際上具有繼承的 toString() 屬性方法。
使用 for in 循環(huán)枚舉(循環(huán))對(duì)象的屬性
通過(guò)使用 for in,我們可以循環(huán)訪問(wèn)對(duì)象中的每個(gè)屬性。在以下示例中,我們使用 for in 循環(huán)從 cody 對(duì)象中檢索屬性名稱(chēng)。
示例:sample44.html
<script> var cody = { age: 23, gender: 'male' }; for (var key in cody) { // key is a variable used to represent each property name. // Avoid properties inherited from the prototype chain. if (cody.hasOwnProperty(key)) { console.log(key); } } </script>
for in 循環(huán)有一個(gè)缺點(diǎn)。它不僅會(huì)訪問(wèn)正在循環(huán)的特定對(duì)象的屬性。它還將在循環(huán)中包含對(duì)象繼承(通過(guò)原型鏈)的任何屬性。因此,如果這不是期望的結(jié)果,而且大多數(shù)情況下都不是,我們必須在循環(huán)內(nèi)使用簡(jiǎn)單的 if 語(yǔ)句來(lái)確保我們只訪問(wèn)我們正在循環(huán)的特定對(duì)象中包含的屬性。這可以通過(guò)使用所有對(duì)象繼承的 hasOwnProperty() 方法來(lái)完成。
在循環(huán)中訪問(wèn)屬性的順序并不總是在循環(huán)中定義它們的順序。此外,您定義屬性的順序不一定是訪問(wèn)它們的順序。
只有可枚舉的屬性(即在循環(huán)對(duì)象屬性時(shí)可用)才顯示在 for in 循環(huán)中。例如,構(gòu)造函數(shù)屬性將不會(huì)顯示。可以使用 propertyIsEnumerable() 方法檢查哪些屬性是可枚舉的。
主機(jī)對(duì)象和本機(jī)對(duì)象
您應(yīng)該知道,執(zhí)行 JavaScript 的環(huán)境(例如 Web 瀏覽器)通常包含所謂的主機(jī)對(duì)象。宿主對(duì)象不是 ecmascript 實(shí)現(xiàn)的一部分,但在執(zhí)行期間可作為對(duì)象使用。當(dāng)然,宿主對(duì)象的可用性和行為完全取決于宿主環(huán)境提供的內(nèi)容。
例如,在網(wǎng)絡(luò)瀏覽器環(huán)境中,window/head 對(duì)象及其所有包含對(duì)象(不包括 JavaScript 提供的對(duì)象)都被視為宿主對(duì)象。
在下面的示例中,我檢查 window 對(duì)象的屬性。
示例:sample45.html
<script> for (x in window) { console.log(x); // Logs all of the properties of the window/head object. } </script>
您可能已經(jīng)注意到,本機(jī) JavaScript 對(duì)象未在主機(jī)對(duì)象中列出。瀏覽器區(qū)分主機(jī)對(duì)象和本機(jī)對(duì)象是相當(dāng)常見(jiàn)的。
就 Web 瀏覽器而言,所有托管對(duì)象中最著名的是用于處理 HTML 文檔的界面,也稱(chēng)為 dom。以下示例是列出瀏覽器環(huán)境提供的 window.document 對(duì)象內(nèi)包含的所有對(duì)象的方法。
示例:sample46.html
<script> for (x in window.document) { console.log(); } </script>
我希望您在這里了解的是 JavaScript 規(guī)范本身并不關(guān)心宿主對(duì)象,反之亦然。 JavaScript 提供的內(nèi)容(例如,JavaScript 1.5、ECMA-262、第 3 版與 Mozilla 的 JavaScript 1.6、1.7、1.8、1.8.1、1.8.5)和主機(jī)環(huán)境提供的內(nèi)容之間存在一條分界線,并且這兩者不應(yīng)該存在感到困惑。
運(yùn)行 JavaScript 代碼的主機(jī)環(huán)境(例如 Web 瀏覽器)通常提供頭對(duì)象(例如 Web 瀏覽器中的 window 對(duì)象),其中語(yǔ)言的本機(jī)部分與主機(jī)對(duì)象(例如 一起存儲(chǔ)) window.location(Web 瀏覽器中的 window.location)和用戶定義的對(duì)象(例如,您編寫(xiě)的在 Web 瀏覽器中運(yùn)行的代碼)。
有時(shí),網(wǎng)絡(luò)瀏覽器制造商作為 JavaScript 解釋器的宿主,會(huì)在獲得批準(zhǔn)之前推出 JavaScript 版本或添加未來(lái)的 JavaScript 規(guī)范(例如,Mozilla 的 firefox JavaScript 1.6、1.7、1.8、1.8.1) ,1.8.5)。
使用 Underscore.js 增強(qiáng)和擴(kuò)展對(duì)象
當(dāng)需要認(rèn)真操作和管理對(duì)象時(shí),JavaScript 1.5 有所欠缺。如果您在 Web 瀏覽器中運(yùn)行 JavaScript,那么當(dāng)您需要比 JavaScript 1.5 提供的更多功能時(shí),我想在這里大膽建議使用 Underscore.js。 Underscore.js 在處理對(duì)象時(shí)提供以下功能。
這些函數(shù)適用于所有對(duì)象和數(shù)組:
- each()
- map()
- reduce()
- reduceRight()
- 檢測(cè)()
- 選擇()
- reject()
- all()
- any()
- include()
- 調(diào)用()
- pluck()
- max()
- min()
- sortBy()
- sortIndex()
- toArray()
- size()
這些函數(shù)適用于所有對(duì)象:
- keys()
- values()
- 函數(shù)()
- extend()
- 克隆()
- tap()
- isEqual()
- isEmpty()
- isElement()
- isArray()
- isArguments
- isFunction()
- isString()
- isNumber
- isBoolean
- isDate
- isRegExp
- isNaN
- isNull
- isUn??defined
結(jié)論
我喜歡這個(gè)庫(kù),因?yàn)樗昧藶g覽器支持的 JavaScript 的新本機(jī)添加功能,而且還為不支持的瀏覽器提供了相同的功能,所有這些都無(wú)需更改 JavaScript 的本機(jī)實(shí)現(xiàn),除非必須這樣做。 p>
開(kāi)始使用 Underscore.js 之前,請(qǐng)確保您的代碼中可能已使用的 JavaScript 庫(kù)或框架尚未提供您所需的功能。