W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
對(duì)象(object)是JavaScript的核心概念,也是最重要的數(shù)據(jù)類型。JavaScript的所有數(shù)據(jù)都可以被視為對(duì)象。
簡(jiǎn)單說,所謂對(duì)象,就是一種無(wú)序的數(shù)據(jù)集合,由若干個(gè)“鍵值對(duì)”(key-value)構(gòu)成。
var o = {
p: 'Hello World'
};
上面代碼中,大括號(hào)就定義了一個(gè)對(duì)象,它被賦值給變量o
。這個(gè)對(duì)象內(nèi)部包含一個(gè)鍵值對(duì)(又稱為“成員”),p
是“鍵名”(成員的名稱),字符串Hello World
是“鍵值”(成員的值)。鍵名與鍵值之間用冒號(hào)分隔。如果對(duì)象內(nèi)部包含多個(gè)鍵值對(duì),每個(gè)鍵值對(duì)之間用逗號(hào)分隔。
var o = {
p1: 'Hello',
p2: 'World'
};
對(duì)象的生成方法,通常有三種方法。除了像上面那樣直接使用大括號(hào)生成({}
),還可以用new
命令生成一個(gè)Object
對(duì)象的實(shí)例,或者使用Object.create
方法生成。
var o1 = {};
var o2 = new Object();
var o3 = Object.create(Object.prototype);
上面三行語(yǔ)句是等價(jià)的。一般來(lái)說,第一種采用大括號(hào)的寫法比較簡(jiǎn)潔,第二種采用構(gòu)造函數(shù)的寫法清晰地表示了意圖,第三種寫法一般用在需要對(duì)象繼承的場(chǎng)合。關(guān)于第二種寫法,詳見《標(biāo)準(zhǔn)庫(kù)》一章的《Object 對(duì)象》一節(jié),第三種寫法詳見《面向?qū)ο缶幊獭芬徽隆?/p>
對(duì)象的所有鍵名都是字符串,所以加不加引號(hào)都可以。上面的代碼也可以寫成下面這樣。
var o = {
'p': 'Hello World'
};
如果鍵名是數(shù)值,會(huì)被自動(dòng)轉(zhuǎn)為字符串。
var o ={
1: 'a',
3.2: 'b',
1e2: true,
1e-2: true,
.234: true,
0xFF: true
};
o
// Object {
// 1: "a",
// 3.2: "b",
// 100: true,
// 0.01: true,
// 0.234: true,
// 255: true
// }
但是,如果鍵名不符合標(biāo)識(shí)名的條件(比如第一個(gè)字符為數(shù)字,或者含有空格或運(yùn)算符),也不是數(shù)字,則必須加上引號(hào),否則會(huì)報(bào)錯(cuò)。
var o = {
'1p': "Hello World",
'h w': "Hello World",
'p+q': "Hello World"
};
上面對(duì)象的三個(gè)鍵名,都不符合標(biāo)識(shí)名的條件,所以必須加上引號(hào)。
注意,JavaScript的保留字可以不加引號(hào)當(dāng)作鍵名。
var obj = {
for: 1,
class: 2
};
對(duì)象的每一個(gè)“鍵名”又稱為“屬性”(property),它的“鍵值”可以是任何數(shù)據(jù)類型。如果一個(gè)屬性的值為函數(shù),通常把這個(gè)屬性稱為“方法”,它可以像函數(shù)那樣調(diào)用。
var o = {
p: function (x) {
return 2 * x;
}
};
o.p(1)
// 2
上面的對(duì)象就有一個(gè)方法p
,它就是一個(gè)函數(shù)。
對(duì)象的屬性之間用逗號(hào)分隔,最后一個(gè)屬性后面可以加逗號(hào)(trailing comma),也可以不加。
var o = {
p: 123,
m: function () { ... },
}
上面的代碼中m
屬性后面的那個(gè)逗號(hào),有或沒有都不算錯(cuò)。
屬性可以動(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
屬性。
如果不同的變量名指向同一個(gè)對(duì)象,那么它們都是這個(gè)對(duì)象的引用,也就是說指向同一個(gè)內(nèi)存地址。修改其中一個(gè)變量,會(huì)影響到其他所有變量。
var o1 = {};
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
還是指向原來(lái)的那個(gè)對(duì)象。
但是,這種引用只局限于對(duì)象,對(duì)于原始類型的數(shù)據(jù)則是傳值引用,也就是說,都是值的拷貝。
var x = 1;
var y = x;
x = 2;
y // 1
上面的代碼中,當(dāng)x
的值發(fā)生變化后,y
的值并不變,這就表示y
和x
并不是指向同一個(gè)內(nèi)存地址。
對(duì)象采用大括號(hào)表示,這導(dǎo)致了一個(gè)問題:如果行首是一個(gè)大括號(hào),它到底是表達(dá)式還是語(yǔ)句?
{ foo: 123 }
JavaScript引擎讀到上面這行代碼,會(huì)發(fā)現(xiàn)可能有兩種含義。第一種可能是,這是一個(gè)表達(dá)式,表示一個(gè)包含foo
屬性的對(duì)象;第二種可能是,這是一個(gè)語(yǔ)句,表示一個(gè)代碼區(qū)塊,里面有一個(gè)標(biāo)簽foo
,指向表達(dá)式123
。
為了避免這種歧義,JavaScript規(guī)定,如果行首是大括號(hào),一律解釋為語(yǔ)句(即代碼塊)。如果要解釋為表達(dá)式(即對(duì)象),必須在大括號(hào)前加上圓括號(hào)。
({ foo: 123})
這種差異在eval
語(yǔ)句中反映得最明顯。
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 o = {
p: 'Hello World'
};
o.p // "Hello World"
o['p'] // "Hello World"
上面代碼分別采用點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符,讀取屬性p
。
請(qǐng)注意,如果使用方括號(hào)運(yùn)算符,鍵名必須放在引號(hào)里面,否則會(huì)被當(dāng)作變量處理。但是,數(shù)字鍵可以不加引號(hào),因?yàn)闀?huì)被當(dāng)作字符串處理。
var o = {
0.7: 'Hello World'
};
o['0.7'] // "Hello World"
o[0.7] // "Hello World"
方括號(hào)運(yùn)算符內(nèi)部可以使用表達(dá)式。
o['hello' + ' world']
o[3 + 3]
數(shù)值鍵名不能使用點(diǎn)運(yùn)算符(因?yàn)闀?huì)被當(dāng)成小數(shù)點(diǎn)),只能使用方括號(hào)運(yùn)算符。
obj.0xFF
// SyntaxError: Unexpected token
obj[0xFF]
// true
上面代碼的第一個(gè)表達(dá)式,對(duì)數(shù)值鍵名0xFF
使用點(diǎn)運(yùn)算符,結(jié)果報(bào)錯(cuò)。第二個(gè)表達(dá)式使用方括號(hào)運(yùn)算符,結(jié)果就是正確的。
如果讀取一個(gè)不存在的鍵,會(huì)返回undefined
,而不是報(bào)錯(cuò)。可以利用這一點(diǎn),來(lái)檢查一個(gè)全局變量是否被聲明。
// 檢查a變量是否被聲明
if (a) {...} // 報(bào)錯(cuò)
if (window.a) {...} // 不報(bào)錯(cuò)
if (window['a']) {...} // 不報(bào)錯(cuò)
上面的后二種寫法之所以不報(bào)錯(cuò),是因?yàn)樵跒g覽器環(huán)境,所有全局變量都是window
對(duì)象的屬性。window.a
的含義就是讀取window
對(duì)象的a
屬性,如果該屬性不存在,就返回undefined
,并不會(huì)報(bào)錯(cuò)。
需要注意的是,后二種寫法有漏洞,如果a
屬性是一個(gè)空字符串(或其他對(duì)應(yīng)的布爾值為false
的情況),則無(wú)法起到檢查變量是否聲明的作用。正確的做法是可以采用下面的寫法。
if ('a' in window) {
// 變量 a 聲明過
} else {
// 變量 a 未聲明
}
點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符,不僅可以用來(lái)讀取值,還可以用來(lái)賦值。
o.p = 'abc';
o['p'] = 'abc';
上面代碼分別使用點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符,對(duì)屬性p賦值。
JavaScript允許屬性的“后綁定”,也就是說,你可以在任意時(shí)刻新增屬性,沒必要在定義對(duì)象的時(shí)候,就定義好屬性。
var o = { p: 1 };
// 等價(jià)于
var o = {};
o.p = 1;
查看一個(gè)對(duì)象本身的所有屬性,可以使用Object.keys
方法。
var o = {
key1: 1,
key2: 2
};
Object.keys(o);
// ['key1', 'key2']
delete
命令用于刪除對(duì)象的屬性,刪除成功后返回true
。
var o = {p: 1};
Object.keys(o) // ["p"]
delete o.p // true
o.p // undefined
Object.keys(o) // []
上面代碼中,delete
命令刪除o
對(duì)象的p
屬性。刪除后,再讀取p
屬性就會(huì)返回undefined
,而且Object.keys
方法的返回值中,o
對(duì)象也不再包括該屬性。
注意,刪除一個(gè)不存在的屬性,delete
不報(bào)錯(cuò),而且返回true
。
var o = {};
delete o.p // true
上面代碼中,o
對(duì)象并沒有p
屬性,但是delete
命令照樣返回true
。因此,不能根據(jù)delete
命令的結(jié)果,認(rèn)定某個(gè)屬性是存在的,只能保證讀取這個(gè)屬性肯定得到undefined
。
只有一種情況,delete
命令會(huì)返回false
,那就是該屬性存在,且不得刪除。
var o = Object.defineProperty({}, 'p', {
value: 123,
configurable: false
});
o.p // 123
delete o.p // false
上面代碼之中,o
對(duì)象的p
屬性是不能刪除的,所以delete
命令返回false
(關(guān)于Object.defineProperty
方法的介紹,請(qǐng)看《標(biāo)準(zhǔn)庫(kù)》一章的Object
對(duì)象章節(jié))。
另外,需要注意的是,delete
命令只能刪除對(duì)象本身的屬性,無(wú)法刪除繼承的屬性(關(guān)于繼承參見《面向?qū)ο缶幊獭芬还?jié))。
var o = {};
delete o.toString // true
o.toString // function toString() { [native code] }
上面代碼中,toString
是對(duì)象o
繼承的屬性,雖然delete
命令返回true
,但該屬性并沒有被刪除,依然存在。
最后,delete
命令不能刪除var
命令聲明的變量,只能用來(lái)刪除屬性。
var p = 1;
delete p // false
delete window.p // false
上面命令中,p
是var
命令聲明的變量,delete
命令無(wú)法刪除它,返回false
。因?yàn)?code class="highlighter-rouge">var聲明的全局變量都是頂層對(duì)象的屬性,而且默認(rèn)不得刪除。
in
運(yùn)算符用于檢查對(duì)象是否包含某個(gè)屬性(注意,檢查的是鍵名,不是鍵值),如果包含就返回true
,否則返回false
。
var o = { p: 1 };
'p' in o // true
在JavaScript語(yǔ)言中,所有全局變量都是頂層對(duì)象(瀏覽器的頂層對(duì)象就是window
對(duì)象)的屬性,因此可以用in
運(yùn)算符判斷,一個(gè)全局變量是否存在。
// 假設(shè)變量x未定義
// 寫法一:報(bào)錯(cuò)
if (x) { return 1; }
// 寫法二:不正確
if (window.x) { return 1; }
// 寫法三:正確
if ('x' in window) { return 1; }
上面三種寫法之中,如果x
不存在,第一種寫法會(huì)報(bào)錯(cuò);如果x
的值對(duì)應(yīng)布爾值false
(比如x
等于空字符串),第二種寫法無(wú)法得到正確結(jié)果;只有第三種寫法,才能正確判斷變量x
是否存在。
in
運(yùn)算符的一個(gè)問題是,它不能識(shí)別對(duì)象繼承的屬性。
var o = new Object();
o.hasOwnProperty('toString') // false
'toString' in o // true
上面代碼中,toString
方法不是對(duì)象o
自身的屬性,而是繼承的屬性,hasOwnProperty
方法可以說明這一點(diǎn)。但是,in
運(yùn)算符不能識(shí)別,對(duì)繼承的屬性也返回true
。
for...in
循環(huán)用來(lái)遍歷一個(gè)對(duì)象的全部屬性。
var o = {a: 1, b: 2, c: 3};
for (var i in o) {
console.log(o[i]);
}
// 1
// 2
// 3
下面是一個(gè)使用for...in
循環(huán),提取對(duì)象屬性的例子。
var obj = {
x: 1,
y: 2
};
var props = [];
var i = 0;
for (props[i++] in obj);
props // ['x', 'y']
for...in
循環(huán)有兩個(gè)使用注意點(diǎn)。
請(qǐng)看下面的例子。
// name 是 Person 本身的屬性
function Person(name) {
this.name = name;
}
// describe是Person.prototype的屬性
Person.prototype.describe = function () {
return 'Name: '+this.name;
};
var person = new Person('Jane');
// for...in循環(huán)會(huì)遍歷實(shí)例自身的屬性(name),
// 以及繼承的屬性(describe)
for (var key in person) {
console.log(key);
}
// name
// describe
上面代碼中,name
是對(duì)象本身的屬性,describe
是對(duì)象繼承的屬性,for...in
循環(huán)的遍歷會(huì)包括這兩者。
如果只想遍歷對(duì)象本身的屬性,可以使用hasOwnProperty
方法,在循環(huán)內(nèi)部判斷一下是不是自身的屬性。
for (var key in person) {
if (person.hasOwnProperty(key)) {
console.log(key);
}
}
// name
對(duì)象person
其實(shí)還有其他繼承的屬性,比如toString
。
person.toString()
// "[object Object]"
這個(gè)toString
屬性不會(huì)被for...in
循環(huán)遍歷到,因?yàn)樗J(rèn)設(shè)置為“不可遍歷”,詳見《標(biāo)準(zhǔn)庫(kù)》一章的Object
對(duì)象部分。
一般情況下,都是只想遍歷對(duì)象自身的屬性,所以不推薦使用for...in
循環(huán)。
with
語(yǔ)句的格式如下:
with (object) {
statements;
}
它的作用是操作同一個(gè)對(duì)象的多個(gè)屬性時(shí),提供一些書寫的方便。
// 例一
with (o) {
p1 = 1;
p2 = 2;
}
// 等同于
o.p1 = 1;
o.p2 = 2;
// 例二
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)前作用域的全局變量。這是因?yàn)?code class="highlighter-rouge">with區(qū)塊沒有改變作用域,它的內(nèi)部依然是當(dāng)前作用域。
var o = {};
with (o) {
x = "abc";
}
o.x // undefined
x // "abc"
上面代碼中,對(duì)象o
沒有屬性x
,所以with
區(qū)塊內(nèi)部對(duì)x
的操作,等于創(chuàng)造了一個(gè)全局變量x
。正確的寫法應(yīng)該是,先定義對(duì)象o
的屬性x
,然后在with
區(qū)塊內(nèi)操作它。
var o = {};
o.x = 1;
with (o) {
x = 2;
}
o.x // 2
這是with
語(yǔ)句的一個(gè)很大的弊病,就是綁定對(duì)象不明確。
with (o) {
console.log(x);
}
單純從上面的代碼塊,根本無(wú)法判斷x
到底是全局變量,還是o
對(duì)象的一個(gè)屬性。這非常不利于代碼的除錯(cuò)和模塊化,編譯器也無(wú)法對(duì)這段代碼進(jìn)行優(yōu)化,只能留到運(yùn)行時(shí)判斷,這就拖慢了運(yùn)行速度。因此,建議不要使用with
語(yǔ)句,可以考慮用一個(gè)臨時(shí)變量代替with
。
with(o1.o2.o3) {
console.log(p1 + p2);
}
// 可以寫成
var temp = o1.o2.o3;
console.log(temp.p1 + temp.p2);
with
語(yǔ)句少數(shù)有用場(chǎng)合之一,就是替換模板變量。
var str = 'Hello <%= name %>!';
上面代碼是一個(gè)模板字符串。假定有一個(gè)parser
函數(shù),可以將這個(gè)字符串解析成下面的樣子。
parser(str)
// '"Hello ", name, "!"'
那么,就可以利用with
語(yǔ)句,進(jìn)行模板變量替換。
var str = 'Hello <%= name %>!';
var o = {
name: 'Alice'
};
function tmpl(str, obj) {
str = 'var p = [];' +
'with (obj) {p.push(' + parser(str) + ')};' +
'return p;'
var r = (new Function('obj', str))(obj);
return r.join('');
}
tmpl(str, o)
// "Hello Alice!"
上面代碼的核心邏輯是下面的部分。
var o = {
name: 'Alice'
};
var p = [];
with (o) {
p.push('Hello ', name, '!');
};
p.join('') // "Hello Alice!"
上面代碼中,with
區(qū)塊內(nèi)部,模板變量name
可以被對(duì)象o
的屬性替換,而p
依然是全局變量。這就是很多模板引擎的實(shí)現(xiàn)原理。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: