數(shù)值
- 判斷NaN更可靠的方法是,利用NaN為唯一不等于自身的值的這個(gè)特點(diǎn),進(jìn)行判斷宵蛀。
function myIsNaN(value) {
return value !== value;
}
-
isFinite
方法返回一個(gè)布爾值,表示某個(gè)值是否為正常的數(shù)值县貌。除了Infinity
术陶、-Infinity
、NaN
和undefined
這幾個(gè)值會(huì)返回false
煤痕,isFinite
對(duì)于其他的數(shù)值都會(huì)返回true
梧宫。
isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true
字符串
- 如果要在單引號(hào)字符串的內(nèi)部,使用單引號(hào)摆碉,就必須在內(nèi)部的單引號(hào)前面加上反斜杠塘匣,用來轉(zhuǎn)義。雙引號(hào)字符串內(nèi)部使用雙引號(hào)巷帝,也是如此忌卤。
'Did she say \'Hello\'?'
// "Did she say 'Hello'?"
"Did she say \"Hello\"?"
// "Did she say "Hello"?"
- 字符串默認(rèn)只能寫在一行內(nèi),分成多行將會(huì)報(bào)錯(cuò)楞泼。如果長字符串必須分成多行驰徊,可以在每一行的尾部使用反斜杠。
下面代碼表示堕阔,加了反斜杠以后棍厂,原來寫在一行的字符串,可以分成多行書寫超陆。但是牺弹,輸出的時(shí)候還是單行,效果與寫在同一行完全一樣时呀。注意张漂,反斜杠的后面必須是換行符,而不能有其他字符(比如空格)退唠,否則會(huì)報(bào)錯(cuò)鹃锈。
var longString = 'Long \
long \
long \
string';
longString
// "Long long long string"
-
轉(zhuǎn)義
反斜杠(\)在字符串內(nèi)有特殊含義荤胁,用來表示一些特殊字符瞧预,所以又稱為轉(zhuǎn)義符。
需要用反斜杠轉(zhuǎn)義的特殊字符仅政,主要有下面這些垢油。
-
\0
:null(\u0000
) -
\b
:后退鍵(\u0008
) -
\f
:換頁符(\u000C
) -
\n
:換行符(\u000A
) -
\r
:回車鍵(\u000D
) -
\t
:制表符(\u0009
) -
\v
:垂直制表符(\u000B
) -
\'
:單引號(hào)(\u0027
) -
\"
:雙引號(hào)(\u0022
) -
\\
:反斜杠(\u005C
)
反斜杠還有三種特殊用法。
-
\HHH
反斜杠后面緊跟三個(gè)八進(jìn)制數(shù)(000
到377
)圆丹,代表一個(gè)字符滩愁。HHH
對(duì)應(yīng)該字符的 Unicode 碼點(diǎn),比如\251
表示版權(quán)符號(hào)辫封。顯然硝枉,這種方法只能輸出256種字符廉丽。 -
\xHH
\x
后面緊跟兩個(gè)十六進(jìn)制數(shù)(00
到FF
),代表一個(gè)字符妻味。HH
對(duì)應(yīng)該字符的 Unicode 碼點(diǎn)正压,比如\xA9
表示版權(quán)符號(hào)。這種方法也只能輸出256種字符责球。 -
\uXXXX
\u
后面緊跟四個(gè)十六進(jìn)制數(shù)(0000
到FFFF
)焦履,代表一個(gè)字符。HHH
對(duì)應(yīng)該字符的 Unicode 碼點(diǎn)雏逾,比如\u00A9
表示版權(quán)符號(hào)嘉裤。
'\251' // "?"
'\xA9' // "?"
'\u00A9' // "?"
'\172' === 'z' // true
'\x7A' === 'z' // true
'\u007A' === 'z' // true
如果在非特殊字符前面使用反斜杠,則反斜杠會(huì)被省略栖博。如果字符串的正常內(nèi)容之中屑宠,需要包含反斜杠,則反斜杠前面需要再加一個(gè)反斜杠仇让,用來對(duì)自身轉(zhuǎn)義侨把。
'\a' (a是一個(gè)正常字符,前面加反斜杠沒有特殊含義妹孙,反斜杠會(huì)被自動(dòng)省略)
// "a"
"Prev \\ Next"
// "Prev \ Next"
-
Base64轉(zhuǎn)碼
有時(shí)秋柄,文本里面包含一些不可打印的符號(hào),比如 ASCII 碼0到31的符號(hào)都無法打印出來蠢正,這時(shí)可以使用 Base64 編碼骇笔,將它們轉(zhuǎn)成可以打印的字符。另一個(gè)場(chǎng)景是嚣崭,有時(shí)需要以文本格式傳遞二進(jìn)制數(shù)據(jù)笨触,那么也可以使用 Base64 編碼。
所謂 Base64 就是一種編碼方法雹舀,可以將任意值轉(zhuǎn)成 0~9芦劣、A~Z、a-z说榆、+和/這64個(gè)字符組成的可打印字符虚吟。使用它的主要目的,不是為了加密签财,而是為了不出現(xiàn)特殊字符串慰,簡化程序的處理。
JavaScript 原生提供兩個(gè) Base64 相關(guān)的方法唱蒸。
btoa()
:任意值轉(zhuǎn)為 Base64 編碼
atob()
:Base64 編碼轉(zhuǎn)為原來的值
var string = 'Hello World!';
btoa(string) // "SGVsbG8gV29ybGQh"
atob('SGVsbG8gV29ybGQh') // "Hello World!"
注意邦鲫,這兩個(gè)方法不適合非 ASCII 碼的字符,會(huì)報(bào)錯(cuò)神汹。
btoa('你好') //報(bào)錯(cuò)
要將非 ASCII 碼字符轉(zhuǎn)為 Base64 編碼庆捺,必須中間插入一個(gè)轉(zhuǎn)碼環(huán)節(jié)古今,再使用這兩個(gè)方法。
function b64Encode(str) {
return btoa(encodeURIComponent(str));
}
function b64Decode(str) {
return decodeURIComponent(atob(str));
}
b64Encode('你好') // "JUU0JUJEJUEwJUU1JUE1JUJE"
b64Decode('JUU0JUJEJUEwJUU1JUE1JUJE') // "你好"
encodeURIComponent()
函數(shù)可把字符串作為 URI 組件進(jìn)行編碼滔以。
該方法不會(huì)對(duì) ASCII 字母和數(shù)字進(jìn)行編碼沧卢,也不會(huì)對(duì)這些 ASCII 標(biāo)點(diǎn)符號(hào)進(jìn)行編碼:- _ . ! ~ * ' ( )
。
其他字符(比如 :;/?:@&=+$,#
這些用于分隔 URI 組件的標(biāo)點(diǎn)符號(hào))醉者,都是由一個(gè)或多個(gè)十六進(jìn)制的轉(zhuǎn)義序列替換的但狭。
decodeURIComponent()
函數(shù)可對(duì)encodeURIComponent()
函數(shù)編碼的 URI 進(jìn)行解碼。
對(duì)象
對(duì)象(object)是 JavaScript 語言的核心概念撬即,也是最重要的數(shù)據(jù)類型立磁。
什么是對(duì)象?簡單說剥槐,對(duì)象就是一組“鍵值對(duì)”(key-value)的集合唱歧,是一種無序的復(fù)合數(shù)據(jù)集合。
如果鍵名不符合標(biāo)識(shí)名的條件(比如第一個(gè)字符為數(shù)字粒竖,或者含有空格或運(yùn)算符)颅崩,且也不是數(shù)字,則必須加上引號(hào)蕊苗,否則會(huì)報(bào)錯(cuò)沿后。
對(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)肺素,也可以不加恨锚。
屬性可以動(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 = {};
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)?code>1顾孽,這時(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)存地址离斩。
對(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
。
為了避免這種歧義睁本,V8 引擎規(guī)定尿庐,如果行首是大括號(hào),一律解釋為對(duì)象呢堰。不過抄瑟,為了避免歧義,最好在大括號(hào)前加上圓括號(hào)枉疼。這種差異在eval
語句(作用是對(duì)字符串求值)中反映得最明顯皮假。
eval('{foo: 123}') // 123
eval('({foo: 123})') // {foo: 123}
上面代碼中,如果沒有圓括號(hào)骂维,eval
將其理解為一個(gè)代碼塊惹资;加上圓括號(hào)以后,就理解成一個(gè)對(duì)象席舍。
-
對(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á)式肛根。數(shù)字鍵可以不加引號(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)算符掺喻。
obj['hello' + ' world']
obj[3 + 3]
var obj = {
0.7: 'Hello World'
};
obj['0.7'] // "Hello World"
obj[0.7] // "Hello World"
var obj = {
123: 'hello world'
};
obj.123 // 報(bào)錯(cuò)
obj[123] // "hello world"
點(diǎn)運(yùn)算符和方括號(hào)運(yùn)算符芭届,不僅可以用來讀取值储矩,還可以用來賦值。
JavaScript 允許屬性的“后綁定”喉脖,也就是說椰苟,你可以在任意時(shí)刻新增屬性抑月,沒必要在定義對(duì)象的時(shí)候树叽,就定義好屬性。
var obj = { p: 1 };
// 等價(jià)于
var obj = {};
obj.p = 1;
obj['p'] = 1;
-
查看一個(gè)對(duì)象本身的所有屬性谦絮,可以使用
Object.keys
方法题诵。
var obj = {
key1: 1,
key2: 2
};
Object.keys(obj);
// ['key1', 'key2']
-
delete
命令用于刪除對(duì)象的屬性,刪除成功后返回true
层皱。
var obj = { p: 1 };
Object.keys(obj) // ["p"]
delete obj.p // true
obj.p // undefined
Object.keys(obj) // []
注意性锭,刪除一個(gè)不存在的屬性,delete
不報(bào)錯(cuò)叫胖,而且返回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
另外,需要注意的是垦藏,delete命令只能刪除對(duì)象本身的屬性梆暖,無法刪除繼承的屬性
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
豁延。它的左邊是一個(gè)字符串,表示屬性名腊状,右邊是一個(gè)對(duì)象诱咏。
in
運(yùn)算符的一個(gè)問題是,它不能識(shí)別哪些屬性是對(duì)象自身的缴挖,哪些屬性是繼承的袋狞。就像下面代碼中,對(duì)象obj
本身并沒有toString
屬性映屋,但是in運(yùn)算符會(huì)返回true
苟鸯,因?yàn)檫@個(gè)屬性是繼承的。
這時(shí)棚点,可以使用對(duì)象的hasOwnProperty
方法判斷一下早处,是否為對(duì)象自身的屬性,返回值為booleam
瘫析。
所有繼承了 [Object
] 的對(duì)象都會(huì)繼承到 hasOwnProperty
方法砌梆。這個(gè)方法可以用來檢測(cè)一個(gè)對(duì)象是否含有特定的自身屬性;和 [in
] 運(yùn)算符不同贬循,該方法會(huì)忽略掉那些從原型鏈上繼承到的屬性咸包。
var obj = { p: 1 };
'p' in obj // true
'toString' in obj // true
var obj = {};
if ('toString' in obj) {
console.log(obj.hasOwnProperty('toString')) // false
}
-
屬性的遍歷:for...in 循環(huán)
for...in
循環(huán)用來遍歷一個(gè)對(duì)象的全部屬性。使用是注意點(diǎn):
- 它遍歷的是對(duì)象所有可遍歷(enumerable)的屬性甘有,會(huì)跳過不可遍歷的屬性诉儒。
- 它不僅遍歷對(duì)象自身的屬性,還遍歷繼承的屬性亏掀。
舉例來說忱反,對(duì)象都繼承了toString
屬性,但是for...in
循環(huán)不會(huì)遍歷到這個(gè)屬性滤愕。
var obj = {a: 1, b: 2, c: 3};
for (var i in obj) {
console.log('鍵名:', i);
console.log('鍵值:', obj[i]);
}
// 鍵名: a
// 鍵值: 1
// 鍵名: b
// 鍵值: 2
// 鍵名: c
// 鍵值: 3
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)是“不可遍歷”的注竿。
如果繼承的屬性是可遍歷的,那么就會(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語句
它的作用是操作同一個(gè)對(duì)象的多個(gè)屬性時(shí)漩怎,提供一些書寫的方便。with
語句的格式如下:
with (對(duì)象) {
語句;
}
// 例一
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);
函數(shù)
JavaScript 有三種聲明函數(shù)的方法妈候。
-
function命令
function
命令聲明的代碼區(qū)塊,就是一個(gè)函數(shù)挂滓。function
命令后面是函數(shù)名苦银,函數(shù)名后面是一對(duì)圓括號(hào),里面是傳入函數(shù)的參數(shù)赶站。函數(shù)體放在大括號(hào)里面幔虏。
function print(s) {
console.log(s);
}
-
函數(shù)表達(dá)式,采用變量賦值的寫法贝椿。
var print = function(s) {
console.log(s);
};
這種寫法將一個(gè)匿名函數(shù)賦值給變量想括。這時(shí),這個(gè)匿名函數(shù)又稱函數(shù)表達(dá)式(Function Expression)团秽,因?yàn)橘x值語句的等號(hào)右側(cè)只能放表達(dá)式主胧。
采用函數(shù)表達(dá)式聲明函數(shù)時(shí)叭首,function命令后面不帶有函數(shù)名。如果加上函數(shù)名踪栋,該函數(shù)名只在函數(shù)體內(nèi)部有效焙格,在函數(shù)體外部無效。
var print = function x(){
console.log(typeof x);
};
x
// ReferenceError: x is not defined
print()
// function
上面代碼在函數(shù)表達(dá)式中夷都,加入了函數(shù)名x眷唉。這個(gè)x只在函數(shù)體內(nèi)部可用,指代函數(shù)表達(dá)式本身囤官,其他地方都不可用冬阳。這種寫法的用處有兩個(gè),一是可以在函數(shù)體內(nèi)部調(diào)用自身党饮,二是方便除錯(cuò)(除錯(cuò)工具顯示函數(shù)調(diào)用棧時(shí)肝陪,將顯示函數(shù)名,而不再顯示這里是一個(gè)匿名函數(shù))刑顺。因此氯窍,這種形式聲明函數(shù)也非常常見。
需要注意的是蹲堂,函數(shù)的表達(dá)式需要在語句的結(jié)尾加上分號(hào)狼讨,表示語句結(jié)束。而函數(shù)的聲明在結(jié)尾的大括號(hào)后面不用加分號(hào)柒竞≌總的來說,這兩種聲明函數(shù)的方式朽基,差別很細(xì)微布隔,可以近似認(rèn)為是等價(jià)的。
-
Function 構(gòu)造函數(shù)
var add = new Function(
'x',
'y',
'return x + y'
);
// 等同于
function add(x, y) {
return x + y;
}
上面代碼中踩晶,Function
構(gòu)造函數(shù)接受三個(gè)參數(shù)执泰,除了最后一個(gè)參數(shù)是add
函數(shù)的“函數(shù)體”,其他參數(shù)都是add
函數(shù)的參數(shù)渡蜻。
你可以傳遞任意數(shù)量的參數(shù)給Function
構(gòu)造函數(shù)术吝,只有最后一個(gè)參數(shù)會(huì)被當(dāng)做函數(shù)體,如果只有一個(gè)參數(shù)茸苇,該參數(shù)就是函數(shù)體排苍。
Function
構(gòu)造函數(shù)可以不使用new
命令,返回結(jié)果完全一樣学密√匝茫總的來說,這種聲明函數(shù)的方式非常不直觀腻暮,幾乎無人使用彤守。
function fib(num) {
if (num === 0) return 0;
if (num === 1) return 1;
return fib(num - 2) + fib(num - 1);
}
fib(6) // 8
函數(shù)可以調(diào)用自身毯侦,這就是遞歸(recursion)。上面就是通過遞歸具垫,計(jì)算斐波那契數(shù)列的代碼侈离。
javascript里面執(zhí)行過程(純屬個(gè)人這樣子認(rèn)為方便自己理解)
第1次進(jìn)入函數(shù)fib (6-2)+(6-1)
第2次進(jìn)入函數(shù)fib (4-2)+(4-1)+(5-2)+(5-1)
第3次進(jìn)入函數(shù)fib (2-2)+(2-1)+(3-2)+(3-1)+(3-2)+(3-1)+(4-2)+(4-1)
第4次進(jìn)入函數(shù)fib (0)0+(1)1+(1)1+(2-2)+(2-1)+(1)+(2-2)+(2-1)+(2-2)+(2-1)+(3-2)+(3-1)
第5次進(jìn)入函數(shù)fib (0)+(1)+(1)+(0)+(1)+(1)+(0)+(1)+(0)+(1)+(1)+(2-2)+(2-1)
第6次進(jìn)入函數(shù)fib (0)+(1)+(1)+(0)+(1)+(1)+(0)+(1)+(0)+(1)+(1)+(0)+(1)
最后結(jié)果就為最后一次進(jìn)入函數(shù)值的和為8
在js里函數(shù)看作一種值,與其他值一樣的用法筝蚕。采用function命令聲明函數(shù)時(shí)卦碾,這個(gè)函數(shù)和變量一樣會(huì)提升,也就是js預(yù)解析或者js引擎運(yùn)作起宽。但是如果采用賦值語句定義函數(shù)洲胖,就不會(huì)整個(gè)函數(shù)提升會(huì)報(bào)錯(cuò)。由于function聲明會(huì)被提升坯沪,賦值聲明不會(huì)绿映,所以當(dāng)function命令和賦值語句同時(shí)聲明一個(gè)函數(shù),最后總是采用賦值語句的定義屏箍。
var f = function () {
console.log('1');
}
function f() {
console.log('2');
}
f() // 1
-
函數(shù)的屬性和方法
fn.name
屬性 獲取函數(shù)名(注意賦值語句定義時(shí)的匿名函數(shù)和具名函數(shù)區(qū)別)
fn.length
屬性 返回函數(shù)預(yù)期傳入的參數(shù)個(gè)數(shù)绘梦,即函數(shù)定義之中的參數(shù)個(gè)數(shù)
fn.toString()
方法 返回一個(gè)字符串,內(nèi)容是函數(shù)的源碼赴魁,函數(shù)內(nèi)部的注釋也可以返回。
-
函數(shù)作用域
作用域(scope)指的是變量存在的范圍钝诚。在 ES5 的規(guī)范中颖御,Javascript 只有兩種作用域:一種是全局作用域,變量在整個(gè)程序中一直存在凝颇,所有地方都可以讀扰斯啊;另一種是函數(shù)作用域芍锚,變量只在函數(shù)內(nèi)部存在叶沛。(ES6 又新增了塊級(jí)作用域)
函數(shù)內(nèi)部定義的變量拴测,會(huì)在該作用域內(nèi)覆蓋同名全局變量。
var v = 1;
function f(){
var v = 2;
console.log(v);
}
f() // 2
v // 1
函數(shù)內(nèi)部的變量提升和全局變量提升一樣禽最,函數(shù)內(nèi)部也會(huì)發(fā)生預(yù)解析(js引擎運(yùn)作)
函數(shù)本身也是一個(gè)值,也有自己的作用域袱饭。它的作用域與變量一樣川无,就是其聲明時(shí)所在的作用域,與其運(yùn)行時(shí)所在的作用域無關(guān)
var a = 1;
var x = function () {
console.log(a);
};
function f() {
var a = 2;
x();
}
f() // 1
上面代碼中虑乖,函數(shù)x
是在函數(shù)f的外部聲明的懦趋,所以它的作用域綁定外層,內(nèi)部變量a
不會(huì)到函數(shù)f
體內(nèi)取值疹味,所以輸出1
仅叫,而不是2
帜篇。
總之,函數(shù)執(zhí)行時(shí)所在的作用域诫咱,是定義時(shí)的作用域坠狡,而不是調(diào)用時(shí)所在的作用域。
很容易犯錯(cuò)的一點(diǎn)是遂跟,如果函數(shù)A調(diào)用函數(shù)B逃沿,卻沒考慮到函數(shù)B不會(huì)引用函數(shù)A的內(nèi)部變量。同樣的幻锁,函數(shù)體內(nèi)部聲明的函數(shù)凯亮,作用域綁定函數(shù)體內(nèi)部。
例一:
var x = function () {
console.log(a);
};
function y(f) {
var a = 2;
f();
}
y(x) // ReferenceError: a is not defined
例二:
function foo() {
var x = 1;
function bar() {
console.log(x);
}
return bar;
}
var x = 2;
var f = foo();
f() // 1
上面代碼例一中將函數(shù)x
作為參數(shù)哄尔,傳入函數(shù)y
假消。但是,函數(shù)x是在函數(shù)y
體外聲明的岭接,作用域綁定外層富拗,因此找不到函數(shù)y
的內(nèi)部變量a
,導(dǎo)致報(bào)錯(cuò)鸣戴。例二代碼中啃沪,函數(shù)foo
內(nèi)部聲明了一個(gè)函數(shù)bar
,bar
的作用域綁定foo
窄锅。當(dāng)我們?cè)?code>foo外部取出bar
執(zhí)行時(shí)创千,變量x
指向的是foo
內(nèi)部的x
,而不是foo
外部的x
入偷。正是這種機(jī)制追驴,構(gòu)成了下文要講解的“閉包”現(xiàn)象。
-
參數(shù)
函數(shù)參數(shù)不是必需的疏之,Javascript 允許省略參數(shù)殿雪。但是,沒有辦法只省略靠前的參數(shù)锋爪,而保留靠后的參數(shù)丙曙。如果一定要省略靠前的參數(shù),只有顯式傳入undefined
几缭。
函數(shù)參數(shù)如果是原始類型的值(數(shù)值河泳、字符串、布爾值)傳遞方式是傳值傳遞年栓,就是原始值的拷貝傳遞拆挥。如果是復(fù)合類型的值(數(shù)組、對(duì)象、其他函數(shù))傳遞方式就是傳址傳遞纸兔,在函數(shù)內(nèi)部修改參數(shù)會(huì)影響到原始值惰瓜。(注意:如果函數(shù)內(nèi)部修改的不是參數(shù)對(duì)象的某個(gè)屬性,而是替換整個(gè)參數(shù)汉矿,這是不會(huì)影響到原始值)
//傳值傳遞
var p = 2;
function f(p) {
p = 3;
}
f(p);
p // 2
//傳址傳遞
var obj = { p: 1 };
function f(o) {
o.p = 2;
}
f(obj);
obj.p // 2
//注意事項(xiàng)
var obj = [1, 2, 3];
function f(o) {
o = [2, 3, 4];
}
f(obj);
obj // [1, 2, 3]
上面注意事項(xiàng)代碼中崎坊,在函數(shù)f
內(nèi)部,參數(shù)對(duì)象obj
被整個(gè)替換成另一個(gè)值洲拇。這時(shí)不會(huì)影響到原始值奈揍。這是因?yàn)椋问絽?shù)(o
)的值實(shí)際是參數(shù)obj
的地址赋续,重新對(duì)o
賦值導(dǎo)致o
指向另一個(gè)地址男翰,保存在原地址上的值當(dāng)然不受影響。
如果有同名的參數(shù)纽乱,則取最后出現(xiàn)的那個(gè)值蛾绎。調(diào)用是沒有提供最后的參數(shù),就會(huì)返回undefined
鸦列,這時(shí)租冠,如果要獲取第一個(gè)參數(shù)的值,可以使用arguments
對(duì)象
function f(a, a) {
console.log(a);
}
f(1, 2) // 2
function f(a, a) {
console.log(a);
}
f(1) // undefined
function f(a, a) {
console.log(arguments[0]);
}
f(1) // 1
-
arguments對(duì)象
arguments
對(duì)象包含了函數(shù)運(yùn)行時(shí)的所有參數(shù)薯嗤,arguments[0]
就是第一個(gè)參數(shù)顽爹,arguments[1]
就是第二個(gè)參數(shù),以此類推应民。這個(gè)對(duì)象只有在函數(shù)體內(nèi)部话原,才可以使用。
var f = function (one) {
console.log(arguments[0]);
console.log(arguments[1]);
console.log(arguments[2]);
}
f(1, 2, 3)
// 1
// 2
// 3
非嚴(yán)格模式下诲锹,arguments
對(duì)象可以在運(yùn)行時(shí)修改。嚴(yán)格模式下涉馅,arguments
對(duì)象是一個(gè)只讀對(duì)象归园,修改它是無效的,但不會(huì)報(bào)錯(cuò)稚矿。
//非嚴(yán)格模式
var f = function(a,b){
arguments[0]=2;
arguments[1]=3;
return a+b;
}
f(1,1) //5
//嚴(yán)格模式
var f=function(a,b){
'use strict'; //開啟嚴(yán)格模式
arguments[0]=2;
arguments[1]=3;
return a+b;
}
f(1,1) //2
通過arguments
對(duì)象的length
屬性庸诱,可以判斷函數(shù)調(diào)用時(shí)到底帶幾個(gè)參數(shù)。
function f() {
return arguments.length;
}
f(1, 2, 3) // 3
f(1) // 1
f() // 0
需要注意的是晤揣,雖然arguments
很像數(shù)組桥爽,但它是一個(gè)對(duì)象。數(shù)組專有的方法(比如slice
和forEach
)昧识,不能在arguments
對(duì)象上直接使用钠四。
如果要讓arguments
對(duì)象使用數(shù)組方法,真正的解決方法是將arguments
轉(zhuǎn)為真正的數(shù)組。下面是兩種常用的轉(zhuǎn)換方法:slice
方法和逐一填入新數(shù)組缀去。
var args = Array.prototype.slice.call(arguments); //因?yàn)锳rray是一個(gè)類侣灶,不能直接引用,需要獲取原型后才能使用缕碎。
// 或者
var args = [];
for (var i = 0; i < arguments.length; i++) {
args.push(arguments[i]);
}
arguments
對(duì)象帶有一個(gè)callee
屬性褥影,返回它所對(duì)應(yīng)的原函數(shù)∮酱疲可以通過arguments.callee
凡怎,達(dá)到調(diào)用函數(shù)自身的目的。這個(gè)屬性在嚴(yán)格模式里面是禁用的赊抖,因此不建議使用统倒。
var f = function () {
console.log(arguments.callee === f);
}
f() // true
-
閉包
閉包(closure)是 Javascript 語言的一個(gè)難點(diǎn),也是它的特色熏迹,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)檐薯。
理解閉包,首先必須理解變量作用域注暗。前面提到坛缕,JavaScript 有兩種作用域:全局作用域和函數(shù)作用域。函數(shù)內(nèi)部可以直接讀取全局變量捆昏,但是赚楚,函數(shù)外部無法讀取函數(shù)內(nèi)部聲明的變量。
var n = 999;
function f1() {
console.log(n);
}
f1() // 999
function f1(){
var n = 999;
}
console.log(n); // uncaught ReferenceError: n is not defined
上面代碼中骗卜,函數(shù)f2
就在函數(shù)f1
內(nèi)部宠页,這時(shí)f1
內(nèi)部的所有局部變量,對(duì)f2
都是可見的寇仓。但是反過來就不行举户,f2
內(nèi)部的局部變量,對(duì)f1
就是不可見的遍烦。這就是 JavaScript 語言特有的"鏈?zhǔn)阶饔糜?結(jié)構(gòu)(chain scope)俭嘁,子對(duì)象會(huì)一級(jí)一級(jí)地向上尋找所有父對(duì)象的變量。所以服猪,父對(duì)象的所有變量供填,對(duì)子對(duì)象都是可見的,反之則不成立罢猪。
既然f2
可以讀取f1
的局部變量近她,那么只要把f2
作為返回值,我們不就可以在f1
外部讀取它的內(nèi)部變量了嗎膳帕!
function f1() {
var n = 999;
function f2() {
console.log(n);
}
return f2;
}
var result = f1();
result(); // 999
上面代碼中粘捎,函數(shù)f1
的返回值就是函數(shù)f2
,由于f2
可以讀取f1
的內(nèi)部變量,所以就可以在外部獲得f1
的內(nèi)部變量了晌端。
閉包就是函數(shù)f2
捅暴,即能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。由于在 JavaScript 語言中咧纠,只有函數(shù)內(nèi)部的子函數(shù)才能讀取內(nèi)部變量蓬痒,因此可以把閉包簡單理解成“定義在一個(gè)函數(shù)內(nèi)部的函數(shù)”。閉包最大的特點(diǎn)漆羔,就是它可以“記住”誕生的環(huán)境梧奢,比如f2
記住了它誕生的環(huán)境f1
,所以從f2
可以得到f1
的內(nèi)部變量演痒。在本質(zhì)上亲轨,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來的一座橋梁。
閉包的最大用處有兩個(gè)鸟顺,一個(gè)是可以讀取函數(shù)內(nèi)部的變量惦蚊,另一個(gè)就是讓這些變量始終保持在內(nèi)存中,即閉包可以使得它誕生環(huán)境一直存在讯嫂。請(qǐng)看下面的例子蹦锋,閉包使得內(nèi)部變量記住上一次調(diào)用時(shí)的運(yùn)算結(jié)果。
function createIncrementor(start) {
return function () {
return start++;
};
}
var inc = createIncrementor(5);
inc() // 5
inc() // 6
inc() // 7
上面代碼中欧芽,start
是函數(shù)createIncrementor
的內(nèi)部變量莉掂。通過閉包,start
的狀態(tài)被保留了千扔,每一次調(diào)用都是在上一次調(diào)用的基礎(chǔ)上進(jìn)行計(jì)算憎妙。從中可以看到,閉包inc
使得函數(shù)createIncrementor
的內(nèi)部環(huán)境曲楚,一直存在厘唾。所以,閉包可以看作是函數(shù)內(nèi)部作用域的一個(gè)接口龙誊。
為什么會(huì)這樣呢阅嘶?原因就在于inc
始終在內(nèi)存中,而inc的存在依賴于createIncrementor
载迄,因此也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后抡蛙,被垃圾回收機(jī)制回收护昧。
閉包的另一個(gè)用處,是封裝對(duì)象的私有屬性和私有方法粗截。
function Person(name) {
var _age;
function setAge(n) {
_age = n;
}
function getAge() {
return _age;
}
return {
name: name,
getAge: getAge,
setAge: setAge
};
}
var p1 = Person('張三');
p1.setAge(25);
p1.getAge() // 25
上面代碼中惋耙,函數(shù)Person
的內(nèi)部變量_age
,通過閉包getAge
和setAge
,變成了返回對(duì)象p1
的私有變量绽榛。
注意湿酸,外層函數(shù)每次運(yùn)行,都會(huì)生成一個(gè)新的閉包灭美,而這個(gè)閉包又會(huì)保留外層函數(shù)的內(nèi)部變量推溃,所以內(nèi)存消耗很大。因此不能濫用閉包届腐,否則會(huì)造成網(wǎng)頁的性能問題铁坎。
-
立即調(diào)用的函數(shù)表達(dá)式(IIFE)
在 Javascript 中,圓括號(hào)()
是一種運(yùn)算符犁苏,跟在函數(shù)名之后硬萍,表示調(diào)用該函數(shù)。比如围详,print()
就表示調(diào)用print
函數(shù)朴乖。有時(shí),我們需要在定義函數(shù)之后助赞,立即調(diào)用該函數(shù)买羞。這時(shí),你不能在函數(shù)的定義之后加上圓括號(hào)嫉拐,這會(huì)產(chǎn)生語法錯(cuò)誤哩都。產(chǎn)生這個(gè)錯(cuò)誤的原因是,function
這個(gè)關(guān)鍵字即可以當(dāng)作語句婉徘,也可以當(dāng)作表達(dá)式漠嵌。
為了避免解析上的歧義,JavaScript 引擎規(guī)定盖呼,如果 function 關(guān)鍵字出現(xiàn)在行首儒鹿,一律解釋成語句。因此几晤,JavaScript 引擎看到行首是 function 關(guān)鍵字之后约炎,認(rèn)為這一段都是函數(shù)的定義,不應(yīng)該以圓括號(hào)結(jié)尾蟹瘾,所以就報(bào)錯(cuò)了圾浅。解決方法就是不要讓 function 出現(xiàn)在行首,讓引擎將其理解成一個(gè)表達(dá)式憾朴。最簡單的處理狸捕,就是將其放在一個(gè)圓括號(hào)里面。
// 語句
function f() {}
// 表達(dá)式
var f = function f() {}
(function(){ /* code */ }());
// 或者
(function(){ /* code */ })();
上面兩種寫法都是以圓括號(hào)開頭众雷,引擎就會(huì)認(rèn)為后面跟的是一個(gè)表示式灸拍,而不是函數(shù)定義語句做祝,所以就避免了錯(cuò)誤。這就叫做“立即調(diào)用的函數(shù)表達(dá)式”(Immediately-Invoked Function Expression)鸡岗,簡稱 IIFE混槐。
注意,上面兩種寫法最后的分號(hào)都是必須的轩性。如果省略分號(hào)声登,遇到連著兩個(gè) IIFE,可能就會(huì)報(bào)錯(cuò)炮姨。兩行之間沒有分號(hào)捌刮,JavaScript 會(huì)將它們連在一起解釋,將第二行解釋為第一行的參數(shù)舒岸。
推而廣之绅作,任何讓解釋器以表達(dá)式來處理函數(shù)定義的方法,都能產(chǎn)生同樣的效果蛾派,比如下面的寫法俄认。
var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();
!function () { /* code */ }();
~function () { /* code */ }();
-function () { /* code */ }();
+function () { /* code */ }();
通常情況下,只對(duì)匿名函數(shù)使用這種“立即執(zhí)行的函數(shù)表達(dá)式”洪乍。它的目的有兩個(gè):一是不必為函數(shù)命名眯杏,避免了污染全局變量;二是 IIFE 內(nèi)部形成了一個(gè)單獨(dú)的作用域壳澳,可以封裝一些外部無法讀取的私有變量岂贩。
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function () {
var tmp = newData;
processData(tmp);
storeData(tmp);
}());
上面代碼中,寫法二比寫法一更好巷波,因?yàn)橥耆苊饬宋廴救肿兞俊?/p>
-
eval命令
eval
命令接受一個(gè)字符串作為參數(shù)萎津,并將這個(gè)字符串當(dāng)作語句執(zhí)行。
eval("var a=1;")
console.log(a) //1
上面代碼將字符串當(dāng)作語句運(yùn)行抹镊,生成了變量a
锉屈。如果參數(shù)字符串無法當(dāng)作語句運(yùn)行,那么就會(huì)報(bào)錯(cuò)垮耳。放在eval
中的字符串颈渊,應(yīng)該有獨(dú)自存在的意義,不能用來與eval
以外的命令配合使用终佛。舉例來說俊嗽,下面的代碼將會(huì)報(bào)錯(cuò)。
eval("3x"); // Uncaught SyntaxError: Invalid or unexpected token
eval("return;"); // Uncaught SyntaxError: Invalid or unexpected token
上面代碼會(huì)報(bào)錯(cuò)铃彰,因?yàn)樽址?code>3x無法當(dāng)作語句運(yùn)行乌询、return
不能單獨(dú)使用,必須在函數(shù)中使用豌研。
如果eval
的參數(shù)不是字符串妹田,那么會(huì)原樣返回。eval
沒有自己的作用域鹃共,都在當(dāng)前作用域內(nèi)執(zhí)行鬼佣,因此可能會(huì)修改當(dāng)前作用域的變量的值,造成安全問題霜浴。
eval(123) //123
var a = 1;
eval(" a = 2 ");
console.log(a) //2
上面代碼中晶衷,eval
命令修改了外部變量a的值。由于這個(gè)原因阴孟,eval
有安全風(fēng)險(xiǎn)晌纫。為了防止這種風(fēng)險(xiǎn),JavaScript 規(guī)定永丝,如果使用嚴(yán)格模式锹漱,eval
內(nèi)部聲明的變量,不會(huì)影響到外部作用域慕嚷。
(function f() {
'use strict';
eval('var foo = 123');
console.log(foo); // ReferenceError: foo is not defined
})()
上面代碼中浮还,函數(shù)f內(nèi)部是嚴(yán)格模式茫藏,這時(shí)eval
內(nèi)部聲明的foo
變量,就不會(huì)影響到外部。不過率碾,即使在嚴(yán)格模式下,eval
依然可以讀寫當(dāng)前作用域的變量刽肠。
(function f() {
'use strict';
var foo = 1;
eval('foo = 2');
console.log(foo); // 2
})()
上面代碼中纯命,嚴(yán)格模式下,eval
內(nèi)部還是改寫了外部變量损俭,可見安全風(fēng)險(xiǎn)依然存在蛙奖。
總之,eval
的本質(zhì)是在當(dāng)前作用域之中撩炊,注入代碼外永。由于安全風(fēng)險(xiǎn)和不利于 JavaScript 引擎優(yōu)化執(zhí)行速度,所以一般不推薦使用拧咳。通常情況下伯顶,eval
最常見的場(chǎng)合是解析 JSON數(shù)據(jù)的字符串,不過正確的做法應(yīng)該是使用原生的 JSON.parse
方法骆膝。
前面說過eval
不利于引擎優(yōu)化執(zhí)行速度祭衩。更麻煩的是,還有下面這種情況阅签,引擎在靜態(tài)代碼分析的階段掐暮,根本無法分辨執(zhí)行的是eval
。
var m = eval;
m('var x = 1');
x // 1
上面代碼中政钟,變量m
是eval
的別名路克。靜態(tài)代碼分析階段樟结,引擎分辨不出m('var x = 1')
執(zhí)行的是eval
命令。
為了保證eval
的別名不影響代碼優(yōu)化精算,JavaScript 的標(biāo)準(zhǔn)規(guī)定瓢宦,凡是使用別名執(zhí)行eval
,eval
內(nèi)部一律是全局作用域灰羽。
var a = 1;
function f() {
var a = 2;
var e = eval;
e('console.log(a)');
}
f() // 1
上面代碼中驮履,eval
是別名調(diào)用,所以即使它是在函數(shù)中廉嚼,它的作用域還是全局作用域玫镐,因此輸出的a
為全局變量。這樣的話怠噪,引擎就能確認(rèn)e()
不會(huì)對(duì)當(dāng)前的函數(shù)作用域產(chǎn)生影響恐似,優(yōu)化的時(shí)候就可以把這一行排除掉。
eval
的別名調(diào)用的形式五花八門舰绘,只要不是直接調(diào)用蹂喻,都屬于別名調(diào)用,因?yàn)橐嬷荒芊直?code>eval()這一種形式是直接調(diào)用捂寿。下面這些形式都是eval
的別名調(diào)用口四,作用域都是全局作用域。
eval.call(null, '...')
window.eval('...')
(1, eval)('...')
(eval, eval)('...')
數(shù)組
數(shù)組(array)是按次序排列的一組值秦陋。每個(gè)值的位置都有編號(hào)(從0開始)蔓彩,整個(gè)數(shù)組用方括號(hào)表示。除了在定義時(shí)賦值驳概,數(shù)組也可以先定義后賦值赤嚼。任何類型的數(shù)據(jù),都可以放入數(shù)組顺又。如果數(shù)組的元素還是數(shù)組更卒,就形成了多維數(shù)組。
var arr = [
{a: 1},
[1, 2, 3],
function() {return true;}
];
arr[0] // Object {a: 1} 對(duì)象
arr[1] // [1, 2, 3] 數(shù)組
arr[2] // function (){return true;} 函數(shù)
本質(zhì)上稚照,數(shù)組屬于一種特殊的對(duì)象蹂空。typeof
運(yùn)算符會(huì)返回?cái)?shù)組的類型是object
。數(shù)組的特殊性體現(xiàn)在果录,它的鍵名是按次序排列的一組整數(shù)(0上枕,1,2...)弱恒。
console.log(typeof [1,2,3]); //object
var arr = ['a','b','c'];
Object.keys(arr) //['0','1','2']
上面代碼中辨萍,Object.keys
方法返回?cái)?shù)組的所有鍵名》档可以看到數(shù)組的鍵名就是整數(shù)0锈玉、1爪飘、2。
由于數(shù)組成員的鍵名是固定的(默認(rèn)總是0嘲玫、1悦施、2...),因此數(shù)組不用為每個(gè)元素指定鍵名去团,而對(duì)象的每個(gè)成員都必須指定鍵名。JavaScript 語言規(guī)定穷蛹,對(duì)象的鍵名一律為字符串土陪,所以,數(shù)組的鍵名其實(shí)也是字符串肴熏。之所以可以用數(shù)值讀取鬼雀,是因?yàn)榉亲址逆I名會(huì)被轉(zhuǎn)為字符串。
對(duì)象有兩種讀取成員的方法:點(diǎn)結(jié)構(gòu)(object.key
)和方括號(hào)結(jié)構(gòu)(object[key]
)蛙吏。但是源哩,對(duì)于數(shù)值的鍵名,不能使用點(diǎn)結(jié)構(gòu)鸦做。因?yàn)閱为?dú)的數(shù)值不能作為標(biāo)識(shí)符(identifier)励烦,所以,數(shù)組成員只能用方括號(hào)arr[0]
表示(方括號(hào)是運(yùn)算符泼诱,可以接受數(shù)值)坛掠。
var arr = [1,2,3];
console.log(arr.0) //syntaxError
數(shù)組的屬性length
,返回?cái)?shù)組成員的數(shù)量治筒。只要是數(shù)組就一定有length
屬性屉栓,該屬性是一個(gè)動(dòng)態(tài)的值,等于鍵名中的最大整數(shù)+1耸袜。
var arr = ['a', 'b'];
arr.length // 2
arr[2] = 'c';
arr.length // 3
arr[9] = 'd';
arr.length // 10
arr[1000] = 'e';
arr.length // 1001
length
屬性是可寫的友多。如果人為設(shè)置一個(gè)小于當(dāng)前成員個(gè)數(shù)的值,該數(shù)組的成員會(huì)自動(dòng)減少到length
設(shè)置的值堤框。
var arr = [ 'a', 'b', 'c' ];
arr.length // 3
arr.length = 2;
arr // ["a", "b"]
上面代碼表示域滥,當(dāng)數(shù)組的length
屬性設(shè)為2(即最大的整數(shù)鍵只能是1)那么整數(shù)鍵2(值為c
)就已經(jīng)不在數(shù)組中了,被自動(dòng)刪除了胰锌。
清空數(shù)組的一個(gè)有效方法骗绕,就是將length
屬性設(shè)為0。如果人為設(shè)置length
大于當(dāng)前元素個(gè)數(shù)资昧,則數(shù)組的成員數(shù)量會(huì)增加到這個(gè)值酬土,新增的位置都是空位。如果人為設(shè)置length
為不合法的值格带,JavaScript 會(huì)報(bào)錯(cuò)撤缴。
var arr = [ 'a', 'b', 'c' ];
arr.length = 0;
console.log(arr) // []
var arr = [ 'a' ];
arr.length = 2;
arr[1] //undefined
// 設(shè)置負(fù)值
[].length = -1
// RangeError: Invalid array length
// 數(shù)組元素個(gè)數(shù)大于等于2的32次方
[].length = Math.pow(2, 32)
// RangeError: Invalid array length
// 設(shè)置字符串
[].length = 'abc'
// RangeError: Invalid array length
值得注意的是刹枉,由于數(shù)組本質(zhì)上是一種對(duì)象,所以可以為數(shù)組添加屬性屈呕,但是這不影響length
屬性的值微宝。
var a = [];
a['p'] = 'abc';
a.length // 0
a[2.1] = 'abc';
a.length // 0
上面代碼將數(shù)組的鍵分別設(shè)為字符串和小數(shù),結(jié)果都不影響length
屬性虎眨。因?yàn)椋?code>length屬性的值就是等于最大的數(shù)字鍵加1蟋软,而這個(gè)數(shù)組沒有整數(shù)鍵,所以length
屬性保持為0嗽桩。
如果數(shù)組的鍵名是添加超出范圍的數(shù)值岳守,該鍵名會(huì)自動(dòng)轉(zhuǎn)為字符串。
var arr = [];
arr[-1] = 'a';
arr[Math.pow(2, 32)] = 'b';
arr.length // 0
arr[-1] // "a"
arr[4294967296] // "b"
上面代碼中碌冶,我們?yōu)閿?shù)組arr
添加了兩個(gè)不合法的數(shù)字鍵湿痢,結(jié)果length
屬性沒有發(fā)生變化。這些數(shù)字鍵都變成了字符串鍵名扑庞。最后兩行之所以會(huì)取到值譬重,是因?yàn)槿℃I值時(shí),數(shù)字鍵名會(huì)默認(rèn)轉(zhuǎn)為字符串罐氨。
-
in運(yùn)算符
檢查某個(gè)鍵名是否存在的運(yùn)算符in
臀规,適用于對(duì)象,也適用于數(shù)組岂昭。注意以现,如果數(shù)組的某個(gè)位置是空位,in
運(yùn)算符返回false
约啊。
//例一
var arr = [ 'a', 'b', 'c' ];
2 in arr // true
'2' in arr // true
4 in arr // false
//例二
var arr = [];
arr[100] = 'a';
100 in arr // true
1 in arr // false
上面代碼例一表明邑遏,數(shù)組存在鍵名為2的鍵。由于鍵名都是字符串恰矩,所以數(shù)值2會(huì)自動(dòng)轉(zhuǎn)成字符串记盒。上面代碼例二中,數(shù)組arr
只有一個(gè)成員arr[100]
外傅,其他位置的鍵名都會(huì)返回false
纪吮。
-
for...in 循環(huán)和數(shù)組的遍歷
for...in
循環(huán)不僅可以遍歷對(duì)象,也可以遍歷數(shù)組萎胰,畢竟數(shù)組只是一種特殊對(duì)象碾盟。但是,for...in
不僅會(huì)遍歷數(shù)組所有的數(shù)字鍵技竟,還會(huì)遍歷非數(shù)字鍵冰肴。所以,不推薦使用for...in
遍歷數(shù)組。
var a = [1, 2, 3];
for (var i in a) {
console.log(a[i]);
}
// 1
// 2
// 3
var a = [1, 2, 3];
a.foo = true;
for (var key in a) {
console.log(key);
}
// 0
// 1
// 2
// foo
數(shù)組的遍歷可以考慮使用for
循環(huán)或while
循環(huán)熙尉。下面代碼是三種遍歷數(shù)組的寫法联逻。最后一種寫法是逆向遍歷,即從最后一個(gè)元素向第一個(gè)元素遍歷检痰。
var a = [1, 2, 3];
// for循環(huán)
for(var i = 0; i < a.length; i++) {
console.log(a[i]);
}
// while循環(huán)
var i = 0;
while (i < a.length) {
console.log(a[i]);
i++;
}
var l = a.length;
while (l--) {
console.log(a[l]);
}
數(shù)組的forEach方法包归,也可以用來遍歷數(shù)組
var colors = ['red', 'green', 'blue'];
colors.forEach(function (color) {
console.log(color);
});
// red
// green
// blue
-
數(shù)組空位
當(dāng)數(shù)組的某個(gè)位置是空元素,即兩個(gè)逗號(hào)之間沒有任何值铅歼,我們稱該數(shù)組存在空位(hole)公壤。數(shù)組的空位不影響length
屬性。需要注意的是椎椰,如果最后一個(gè)元素后面有逗號(hào)境钟,并不會(huì)產(chǎn)生空位。也就是說俭识,有沒有這個(gè)逗號(hào),結(jié)果都是一樣的洞渔。數(shù)組的空位是可以讀取的套媚,返回undefined
。使用delete
命令刪除一個(gè)數(shù)組成員磁椒,會(huì)形成空位堤瘤,并且不會(huì)影響length
屬性。
var a = [1, , 1];
a.length // 3
var a = [1, 2, 3,];
a.length // 3
a // [1, 2, 3]
var a = [, , ,];
a[1] // undefined
var a = [1, 2, 3];
delete a[1];
a[1] // undefined
a.length // 3
上面代碼用delete
命令刪除了數(shù)組的第二個(gè)元素浆熔,這個(gè)位置就形成了空位本辐,但是對(duì)length
屬性沒有影響。也就是說医增,length
屬性不過濾空位慎皱。所以,使用length
屬性進(jìn)行數(shù)組遍歷叶骨,一定要非常小心茫多。
數(shù)組的某個(gè)位置是空位,與某個(gè)位置是undefined
忽刽,是不一樣的天揖。如果是空位,使用數(shù)組的forEach
方法跪帝、for...in
結(jié)構(gòu)今膊、以及Object.keys
方法進(jìn)行遍歷,空位都會(huì)被跳過伞剑。不過使用for
循環(huán)和while
循環(huán)遍歷空位會(huì)返回undefined
不會(huì)跳過斑唬。如果某個(gè)位置是undefined
,遍歷的時(shí)候就不會(huì)被跳過。
var a = [, , ,];
a.forEach(function (x, i) {
console.log(i + '. ' + x);
})
// 不產(chǎn)生任何輸出
for (var i in a) {
console.log(i);
}
// 不產(chǎn)生任何輸出
Object.keys(a)
// []
var a = [undefined, undefined, undefined];
a.forEach(function (x, i) {
console.log(i + '. ' + x);
});
// 0. undefined
// 1. undefined
// 2. undefined
for (var i in a) {
console.log(i);
}
// 0
// 1
// 2
Object.keys(a)
// ['0', '1', '2']
-
類似數(shù)組的對(duì)象
如果一個(gè)對(duì)象的所有鍵名都是正整數(shù)或零赖钞,并且有·length·屬性腰素,那么這個(gè)對(duì)象就很像數(shù)組,語法上稱為“類似數(shù)組的對(duì)象”(array-like object)雪营。
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
};
obj[0] // 'a'
obj[1] // 'b'
obj.length // 3
obj.push('d') // TypeError: obj.push is not a function
上面代碼中弓千,對(duì)象obj
就是一個(gè)類似數(shù)組的對(duì)象。但是献起,“類似數(shù)組的對(duì)象”并不是數(shù)組洋访,因?yàn)樗鼈儾痪邆鋽?shù)組特有的方法。對(duì)象obj
沒有數(shù)組的push
方法谴餐,使用該方法就會(huì)報(bào)錯(cuò)姻政。
“類似數(shù)組的對(duì)象”的根本特征,就是具有length
屬性岂嗓。只要有length
屬性汁展,就可以認(rèn)為這個(gè)對(duì)象類似于數(shù)組。但是有一個(gè)問題厌殉,這種length
屬性不是動(dòng)態(tài)值食绿,不會(huì)隨著成員的變化而變化。
var obj = {
length: 0
};
obj[3] = 'd';
obj.length // 0
上面代碼為對(duì)象obj
添加了一個(gè)數(shù)字鍵公罕,但是length
屬性沒變器紧。這就說明了obj
不是數(shù)組。
典型的“類似數(shù)組的對(duì)象”是函數(shù)的arguments
對(duì)象楼眷,以及大多數(shù) DOM
元素集铲汪,還有字符串。
// arguments對(duì)象
function args() { return arguments }
var arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false
// DOM元素集
var elts = document.getElementsByTagName('h3');
elts.length // 3
elts instanceof Array // false
// 字符串
'abc'[1] // 'b'
'abc'.length // 3
'abc' instanceof Array // false
上面代碼包含三個(gè)例子罐柳,它們都不是數(shù)組(instanceof
運(yùn)算符返回false
)掌腰,但是看上去都非常像數(shù)組。數(shù)組的slice
方法可以將“類似數(shù)組的對(duì)象”變成真正的數(shù)組硝清。
var arr = Array.prototype.slice.call(arrayLike);
除了轉(zhuǎn)為真正的數(shù)組辅斟,“類似數(shù)組的對(duì)象”還有一個(gè)辦法可以使用數(shù)組的方法,就是通過call()把數(shù)組的方法放到對(duì)象上面芦拿。
function print(value, index) {
console.log(index + ' : ' + value);
}
Array.prototype.forEach.call(arrayLike, print);
上面代碼中士飒,arrayLike
代表一個(gè)類似數(shù)組的對(duì)象,本來是不可以使用數(shù)組的forEach()
方法的蔗崎,但是通過call()
酵幕,可以把forEach()
嫁接到arrayLike
上面調(diào)用。
下面的例子就是通過這種方法缓苛,在arguments
對(duì)象上面調(diào)用forEach
方法芳撒。
// forEach 方法
function logArgs() {
Array.prototype.forEach.call(arguments, function (elem, i) {
console.log(i + '. ' + elem);
});
}
// 等同于 for 循環(huán)
function logArgs() {
for (var i = 0; i < arguments.length; i++) {
console.log(i + '. ' + arguments[i]);
}
}
字符串也是類似數(shù)組的對(duì)象邓深,所以也可以用Array.prototype.forEach.call
遍歷。
Array.prototype.forEach.call('abc', function (chr) {
console.log(chr);
});
// a
// b
// c
注意笔刹,這種方法比直接使用數(shù)組原生的forEach
要慢芥备,所以最好還是先將“類似數(shù)組的對(duì)象”轉(zhuǎn)為真正的數(shù)組,然后再直接調(diào)用數(shù)組的forEach
方法舌菜。
var arr = Array.prototype.slice.call('abc');
arr.forEach(function (chr) {
console.log(chr);
});
// a
// b
// c