es5--數(shù)據(jù)類型筆記

數(shù)值

  • 判斷NaN更可靠的方法是,利用NaN為唯一不等于自身的值的這個(gè)特點(diǎn),進(jìn)行判斷宵蛀。
function myIsNaN(value) {
  return value !== value;
}
  • isFinite方法返回一個(gè)布爾值,表示某個(gè)值是否為正常的數(shù)值县貌。除了Infinity术陶、-InfinityNaNundefined這幾個(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ù)(000377)圆丹,代表一個(gè)字符滩愁。HHH對(duì)應(yīng)該字符的 Unicode 碼點(diǎn),比如\251表示版權(quán)符號(hào)辫封。顯然硝枉,這種方法只能輸出256種字符廉丽。
  • \xHH
    \x后面緊跟兩個(gè)十六進(jìn)制數(shù)(00FF),代表一個(gè)字符妻味。HH對(duì)應(yīng)該字符的 Unicode 碼點(diǎn)正压,比如\xA9表示版權(quán)符號(hào)。這種方法也只能輸出256種字符责球。
  • \uXXXX
    \u后面緊跟四個(gè)十六進(jìn)制數(shù)(0000FFFF)焦履,代表一個(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

上面代碼中站辉,o1o2指向同一個(gè)對(duì)象呢撞,因此為其中任何一個(gè)變量添加屬性,另一個(gè)變量都可以讀寫該屬性饰剥。
此時(shí)殊霞,如果取消某一個(gè)變量對(duì)于原對(duì)象的引用,不會(huì)影響到另一個(gè)變量汰蓉。

var o1 = {};
var o2 = o1;

o1 = 1;
o2 // {}

上面代碼中绷蹲,o1o2指向同一個(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的值并不變做裙,這就表示yx并不是指向同一個(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ì)象objfoo屬性時(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ù)barbar的作用域綁定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ù)組專有的方法(比如sliceforEach)昧识,不能在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,通過閉包getAgesetAge,變成了返回對(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

上面代碼中政钟,變量meval的別名路克。靜態(tài)代碼分析階段樟结,引擎分辨不出m('var x = 1')執(zhí)行的是eval命令。

為了保證eval的別名不影響代碼優(yōu)化精算,JavaScript 的標(biāo)準(zhǔn)規(guī)定瓢宦,凡是使用別名執(zhí)行evaleval內(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末萌壳,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子日月,更是在濱河造成了極大的恐慌袱瓮,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爱咬,死亡現(xiàn)場(chǎng)離奇詭異尺借,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)精拟,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門燎斩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蜂绎,你說我怎么就攤上這事瘫里。” “怎么了荡碾?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長局装。 經(jīng)常有香客問我坛吁,道長,這世上最難降的妖魔是什么铐尚? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任拨脉,我火速辦了婚禮,結(jié)果婚禮上宣增,老公的妹妹穿的比我還像新娘玫膀。我一直安慰自己,他們只是感情好爹脾,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布帖旨。 她就那樣靜靜地躺著,像睡著了一般灵妨。 火紅的嫁衣襯著肌膚如雪解阅。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天泌霍,我揣著相機(jī)與錄音货抄,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蟹地,可吹牛的內(nèi)容都是我干的积暖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼怪与,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼夺刑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琼梆,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤性誉,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后茎杂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體错览,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年煌往,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了倾哺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡刽脖,死狀恐怖羞海,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情曲管,我是刑警寧澤却邓,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站院水,受9級(jí)特大地震影響腊徙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜檬某,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一撬腾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恢恼,春花似錦民傻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至漏隐,卻和暖如春彭雾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锁保。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工薯酝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留半沽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓吴菠,卻偏偏與公主長得像者填,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子做葵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容