概述
生成方法
對(duì)象(object)是 JavaScript 語言的核心概念藕赞,也是最重要的數(shù)據(jù)類型。
什么是對(duì)象帘靡?簡(jiǎn)單說玄货,對(duì)象就是一組“鍵值對(duì)”(key-value)的集合,是一種無序的復(fù)合數(shù)據(jù)集合悼泌。
var obj = {
foo: 'Hello',
bar: 'World'
};
上面代碼中松捉,大括號(hào)就定義了一個(gè)對(duì)象,它被賦值給變量obj
馆里,所以變量obj
就指向一個(gè)對(duì)象隘世。該對(duì)象內(nèi)部包含兩個(gè)鍵值對(duì)(又稱為兩個(gè)“成員”),第一個(gè)鍵值對(duì)是foo: 'Hello'
鸠踪,其中foo
是“鍵名”(成員的名稱)丙者,字符串Hello
是“鍵值”(成員的值)。鍵名與鍵值之間用冒號(hào)分隔营密。第二個(gè)鍵值對(duì)是bar: 'World'
械媒,bar
是鍵名,World
是鍵值。兩個(gè)鍵值對(duì)之間用逗號(hào)分隔纷捞。
鍵名
對(duì)象的所有鍵名都是字符串(ES6 又引入了 Symbol 值也可以作為鍵值)痢虹,所以加不加引號(hào)都可以。上面的代碼也可以寫成下面這樣主儡。
var obj = {
'foo': 'Hello',
'bar': 'World'
};
如果鍵名是數(shù)值奖唯,會(huì)被自動(dòng)轉(zhuǎn)為字符串。
var obj = {
1: 'a',
3.2: 'b',
1e2: true,
1e-2: true,
.234: true,
0xFF: true
};
obj
// Object {
// 1: "a",
// 3.2: "b",
// 100: true,
// 0.01: true,
// 0.234: true,
// 255: true
// }
obj['100'] // true
上面代碼中糜值,對(duì)象obj
的所有鍵名雖然看上去像數(shù)值丰捷,實(shí)際上都被自動(dòng)轉(zhuǎn)成了字符串。
如果鍵名不符合標(biāo)識(shí)名的條件(比如第一個(gè)字符為數(shù)字寂汇,或者含有空格或運(yùn)算符)病往,且也不是數(shù)字,則必須加上引號(hào)健无,否則會(huì)報(bào)錯(cuò)荣恐。
// 報(bào)錯(cuò)
var obj = {
1p: 'Hello World'
};
// 不報(bào)錯(cuò)
var obj = {
'1p': 'Hello World',
'h w': 'Hello World',
'p+q': 'Hello World'
};
上面對(duì)象的三個(gè)鍵名,都不符合標(biāo)識(shí)名的條件累贤,所以必須加上引號(hào)叠穆。
對(duì)象的每一個(gè)鍵名又稱為“屬性”(property),它的“鍵值”可以是任何數(shù)據(jù)類型臼膏。如果一個(gè)屬性的值為函數(shù)硼被,通常把這個(gè)屬性稱為“方法”,它可以像函數(shù)那樣調(diào)用渗磅。
var obj = {
p: function (x) {
return 2 * x;
}
};
obj.p(1) // 2
上面代碼中嚷硫,對(duì)象obj
的屬性p
,就指向一個(gè)函數(shù)始鱼。
如果屬性的值還是一個(gè)對(duì)象仔掸,就形成了鏈?zhǔn)揭谩?/p>
var o1 = {};
var o2 = { bar: 'hello' };
o1.foo = o2;
o1.foo.bar // "hello"
上面代碼中,對(duì)象o1
的屬性foo
指向?qū)ο?code>o2医清,就可以鏈?zhǔn)揭?code>o2的屬性起暮。
對(duì)象的屬性之間用逗號(hào)分隔,最后一個(gè)屬性后面可以加逗號(hào)(trailing comma)会烙,也可以不加负懦。
var obj = {
p: 123,
m: function () { ... },
}
上面的代碼中,m
屬性后面的那個(gè)逗號(hào)柏腻,有沒有都可以纸厉。
屬性可以動(dòng)態(tài)創(chuàng)建,不必在對(duì)象聲明時(shí)就指定五嫂。
var obj = {};
obj.foo = 123;
obj.foo // 123
上面代碼中颗品,直接對(duì)obj
對(duì)象的foo
屬性賦值,結(jié)果就在運(yùn)行時(shí)創(chuàng)建了foo
屬性。
對(duì)象的引用
如果不同的變量名指向同一個(gè)對(duì)象抛猫,那么它們都是這個(gè)對(duì)象的引用蟆盹,也就是說指向同一個(gè)內(nèi)存地址。修改其中一個(gè)變量闺金,會(huì)影響到其他所有變量逾滥。
var o1 = {};"hello world"
var o2 = o1;
o1.a = 1;
o2.a // 1
o2.b = 2;
o1.b // 2
上面代碼中,o1
和o2
指向同一個(gè)對(duì)象败匹,因此為其中任何一個(gè)變量添加屬性寨昙,另一個(gè)變量都可以讀寫該屬性。
此時(shí)掀亩,如果取消某一個(gè)變量對(duì)于原對(duì)象的引用舔哪,不會(huì)影響到另一個(gè)變量。
var o1 = {};
var o2 = o1;
o1 = 1;
o2 // {}
上面代碼中槽棍,o1
和o2
指向同一個(gè)對(duì)象捉蚤,然后o1
的值變?yōu)?,這時(shí)不會(huì)對(duì)o2
產(chǎn)生影響炼七,o2
還是指向原來的那個(gè)對(duì)象缆巧。
但是,這種引用只局限于對(duì)象豌拙,如果兩個(gè)變量指向同一個(gè)原始類型的值陕悬。那么,變量這時(shí)都是值的拷貝按傅。
var x = 1;
var y = x;
x = 2;
y // 1
上面的代碼中捉超,當(dāng)x
的值發(fā)生變化后,y
的值并不變唯绍,這就表示y
和x
并不是指向同一個(gè)內(nèi)存地址拼岳。
表達(dá)式還是語句?
對(duì)象采用大括號(hào)表示况芒,這導(dǎo)致了一個(gè)問題:如果行首是一個(gè)大括號(hào)裂问,它到底是表達(dá)式還是語句?
{ foo: 123 }
JavaScript 引擎讀到上面這行代碼牛柒,會(huì)發(fā)現(xiàn)可能有兩種含義。第一種可能是痊乾,這是一個(gè)表達(dá)式皮壁,表示一個(gè)包含foo
屬性的對(duì)象;第二種可能是哪审,這是一個(gè)語句蛾魄,表示一個(gè)代碼區(qū)塊,里面有一個(gè)標(biāo)簽foo
,指向表達(dá)式123
滴须。
為了避免這種歧義舌狗,JavaScript 規(guī)定,如果行首是大括號(hào)扔水,一律解釋為語句(即代碼塊)痛侍。如果要解釋為表達(dá)式(即對(duì)象),必須在大括號(hào)前加上圓括號(hào)魔市。
({ foo: 123})
這種差異在eval
語句(作用是對(duì)字符串求值)中反映得最明顯主届。
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
上面代碼中,如果沒有圓括號(hào)待德,eval
將其理解為一個(gè)代碼塊君丁;加上圓括號(hào)以后,就理解成一個(gè)對(duì)象将宪。
屬性的操作
讀取屬性
讀取對(duì)象的屬性绘闷,有兩種方法,一種是使用點(diǎn)運(yùn)算符较坛,還有一種是使用方括號(hào)運(yùn)算符印蔗。
var obj = {
p: 'Hello World'
};
obj.p // "Hello World"
obj['p'] // "Hello World"
上面代碼分別采用點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符,讀取屬性p
燎潮。
請(qǐng)注意喻鳄,如果使用方括號(hào)運(yùn)算符,鍵名必須放在引號(hào)里面确封,否則會(huì)被當(dāng)作變量處理除呵。
var foo = 'bar';
var obj = {
foo: 1,
bar: 2
};
obj.foo // 1
obj[foo] // 2
上面代碼中,引用對(duì)象obj
的foo
屬性時(shí)爪喘,如果使用點(diǎn)運(yùn)算符颜曾,foo
就是字符串;如果使用方括號(hào)運(yùn)算符秉剑,但是不使用引號(hào)泛豪,那么foo
就是一個(gè)變量,指向字符串bar
侦鹏。
方括號(hào)運(yùn)算符內(nèi)部還可以使用表達(dá)式诡曙。
obj['hello' + ' world']
obj[3 + 3]
數(shù)字鍵可以不加引號(hào),因?yàn)闀?huì)自動(dòng)轉(zhuǎn)成字符串略水。
var obj = {
0.7: 'Hello World'
};
obj['0.7'] // "Hello World"
obj[0.7] // "Hello World"
上面代碼中价卤,對(duì)象obj
的數(shù)字鍵0.7
,加不加引號(hào)都可以渊涝,因?yàn)闀?huì)被自動(dòng)轉(zhuǎn)為字符串慎璧。
注意床嫌,數(shù)值鍵名不能使用點(diǎn)運(yùn)算符(因?yàn)闀?huì)被當(dāng)成小數(shù)點(diǎn)),只能使用方括號(hào)運(yùn)算符胸私。
var obj = {
123: 'hello world'
};
obj.123 // 報(bào)錯(cuò)
obj[123] // "hello world"
上面代碼的第一個(gè)表達(dá)式厌处,對(duì)數(shù)值鍵名123
使用點(diǎn)運(yùn)算符,結(jié)果報(bào)錯(cuò)岁疼。第二個(gè)表達(dá)式使用方括號(hào)運(yùn)算符阔涉,結(jié)果就是正確的。
屬性的賦值
點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符五续,不僅可以用來讀取值洒敏,還可以用來賦值。
var obj = {};
obj.foo = 'Hello';
obj['bar'] = 'World';
上面代碼中疙驾,分別使用點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符凶伙,對(duì)屬性賦值。
JavaScript 允許屬性的“后綁定”它碎,也就是說函荣,你可以在任意時(shí)刻新增屬性,沒必要在定義對(duì)象的時(shí)候扳肛,就定義好屬性傻挂。
var obj = { p: 1 };
// 等價(jià)于
var obj = {};
obj.p = 1;
查看所有屬性
查看一個(gè)對(duì)象本身的所有屬性,可以使用Object.keys
方法挖息。
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
delete 命令
delete
命令用于刪除對(duì)象的屬性金拒,刪除成功后返回true
。
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
上面代碼中套腹,delete
命令刪除對(duì)象obj
的p
屬性绪抛。刪除后,再讀取p
屬性就會(huì)返回undefined
电禀,而且Object.keys
方法的返回值也不再包括該屬性幢码。
注意,刪除一個(gè)不存在的屬性尖飞,delete
不報(bào)錯(cuò)症副,而且返回true
。
var obj = {};
delete obj.p // true
上面代碼中政基,對(duì)象obj
并沒有p
屬性贞铣,但是delete
命令照樣返回true
。因此沮明,不能根據(jù)delete
命令的結(jié)果咕娄,認(rèn)定某個(gè)屬性是存在的。
只有一種情況珊擂,delete
命令會(huì)返回false
圣勒,那就是該屬性存在,且不得刪除摧扇。
var obj = Object.defineProperty({}, 'p', {
value: 123,
configurable: false
});
obj.p // 123
delete obj.p // false
上面代碼之中圣贸,對(duì)象obj
的p
屬性是不能刪除的,所以delete
命令返回false
(關(guān)于Object.defineProperty
方法的介紹扛稽,請(qǐng)看《標(biāo)準(zhǔn)庫(kù)》的 Object 對(duì)象一章)吁峻。
另外,需要注意的是在张,delete
命令只能刪除對(duì)象本身的屬性用含,無法刪除繼承的屬性(關(guān)于繼承參見《面向?qū)ο缶幊獭氛鹿?jié))。
var obj = {};
delete obj.toString // true
obj.toString // function toString() { [native code] }
上面代碼中帮匾,toString
是對(duì)象obj
繼承的屬性啄骇,雖然delete
命令返回true
,但該屬性并沒有被刪除瘟斜,依然存在缸夹。這個(gè)例子還說明,即使delete
返回true
螺句,該屬性依然可能讀取到值虽惭。
in 運(yùn)算符
in
運(yùn)算符用于檢查對(duì)象是否包含某個(gè)屬性(注意,檢查的是鍵名蛇尚,不是鍵值)芽唇,如果包含就返回true
,否則返回false
取劫。
var obj = { p: 1 };
'p' in obj // true
in
運(yùn)算符的一個(gè)問題是匆笤,它不能識(shí)別哪些屬性是對(duì)象自身的,哪些屬性是繼承的勇凭。
var obj = {};
'toString' in o // true
上面代碼中疚膊,toString
方法不是對(duì)象obj
自身的屬性,而是繼承的屬性虾标。但是寓盗,in
運(yùn)算符不能識(shí)別,對(duì)繼承的屬性也返回true
璧函。
for…in 循環(huán)
for...in
循環(huán)用來遍歷一個(gè)對(duì)象的全部屬性傀蚌。
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log(obj[i]);
}
// 1
// 2
// 3
下面是一個(gè)使用for...in
循環(huán),提取對(duì)象屬性名的例子蘸吓。
var obj = {
x: 1,
y: 2
};
var props = [];
var i = 0;
for (var p in obj) {
props[i++] = p
}
props // ['x', 'y']
for...in
循環(huán)有兩個(gè)使用注意點(diǎn)善炫。
- 它遍歷的是對(duì)象所有可遍歷(enumerable)的屬性,會(huì)跳過不可遍歷的屬性库继。
- 它不僅遍歷對(duì)象自身的屬性箩艺,還遍歷繼承的屬性窜醉。
舉例來說,對(duì)象都繼承了toString
屬性艺谆,但是for...in
循環(huán)不會(huì)遍歷到這個(gè)屬性榨惰。
var obj = {};
// toString 屬性是存在的
obj.toString // toString() { [native code] }
for (var p in obj) {
console.log(p);
} // 沒有任何輸出
上面代碼中,對(duì)象obj
繼承了toString
屬性静汤,該屬性不會(huì)被for...in
循環(huán)遍歷到琅催,因?yàn)樗J(rèn)是“不可遍歷”的。關(guān)于對(duì)象屬性的可遍歷性虫给,參見《標(biāo)準(zhǔn)庫(kù)》章節(jié)中 Object 一章的介紹藤抡。
如果繼承的屬性是可遍歷的,那么就會(huì)被for...in
循環(huán)遍歷到抹估。但是缠黍,一般情況下,都是只想遍歷對(duì)象自身的屬性棋蚌,所以使用for...in
的時(shí)候嫁佳,應(yīng)該結(jié)合使用hasOwnProperty
方法,在循環(huán)內(nèi)部判斷一下谷暮,某個(gè)屬性是否為對(duì)象自身的屬性蒿往。
var person = { name: '老張' };
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
with 語句
with
語句的格式如下:
with (對(duì)象) {
語句;
}
它的作用是操作同一個(gè)對(duì)象的多個(gè)屬性時(shí)址儒,提供一些書寫的方便锭碳。
// 例一
var obj = {
p1: 1,
p2: 2,
};
with (obj) {
p1 = 4;
p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
// 例二
with (document.links[0]){
console.log(href);
console.log(title);
console.log(style);
}
// 等同于
console.log(document.links[0].href);
console.log(document.links[0].title);
console.log(document.links[0].style);
注意,如果with
區(qū)塊內(nèi)部有變量的賦值操作校套,必須是當(dāng)前對(duì)象已經(jīng)存在的屬性颊埃,否則會(huì)創(chuàng)造一個(gè)當(dāng)前作用域的全局變量蔬充。
var obj = {};
with (obj) {
p1 = 4;
p2 = 5;
}
obj.p1 // undefined
p1 // 4
上面代碼中,對(duì)象obj
并沒有p1
屬性班利,對(duì)p1
賦值等于創(chuàng)造了一個(gè)全局變量p1
饥漫。正確的寫法應(yīng)該是,先定義對(duì)象obj
的屬性p1
罗标,然后在with
區(qū)塊內(nèi)操作它庸队。
這是因?yàn)?code>with區(qū)塊沒有改變作用域,它的內(nèi)部依然是當(dāng)前作用域闯割。這造成了with
語句的一個(gè)很大的弊病彻消,就是綁定對(duì)象不明確。
with (obj) {
console.log(x);
}
單純從上面的代碼塊宙拉,根本無法判斷x
到底是全局變量宾尚,還是對(duì)象obj
的一個(gè)屬性。這非常不利于代碼的除錯(cuò)和模塊化谢澈,編譯器也無法對(duì)這段代碼進(jìn)行優(yōu)化煌贴,只能留到運(yùn)行時(shí)判斷御板,這就拖慢了運(yùn)行速度。因此崔步,建議不要使用with
語句稳吮,可以考慮用一個(gè)臨時(shí)變量代替with
。
with(obj1.obj2.obj3) {
console.log(p1 + p2);
}
// 可以寫成
var temp = obj1.obj2.obj3;
console.log(temp.p1 + temp.p2);
參考鏈接
- Dr. Axel Rauschmayer井濒,Object properties in JavaScript
- Lakshan Perera, Revisiting JavaScript Objects
- Angus Croll, The Secret Life of JavaScript Primitivesi
- Dr. Axel Rauschmayer, JavaScript’s with statement and why it’s deprecated