教程
https://wangdoc.com/javascript/types/general.html
1 概述
1.1 數(shù)據(jù)類型分類
- 數(shù)值,number, 整數(shù)和小數(shù)
- 字符串,string, 文本 Hello World
- 布爾值,bool
- undefined, 表示未定義或不存在,比如僅是聲明的變量它的值就是 undefined
- null,表示空值,此處的值為空
- 對象,object,各種值組成的集合
number , string & bool 合成為原始類型(primitive type),因為它們是最基本的數(shù)據(jù)類型,不能再細分了。
對象是合成類型(complex type),因為一個對象往往由多個原始類型的值合成,是一個存放各種值的容器。
undefined 和 nulll,一般是將它們看為兩個特殊值。
對象是最復雜的數(shù)據(jù)類型,又可以分為三個子類型:
- 狹義的對象(object)
- 數(shù)組 (array)
- 函數(shù) (function)
狹義的對象和數(shù)組是兩種不同的數(shù)據(jù)組合方式,教程中的對象都是指的是狹義的對象。
函數(shù)其實是處理數(shù)據(jù)的方法,JavaScript 把它當做一種數(shù)據(jù)類型,可以賦值給變量,這給編程帶來靈活性,也為 JS 的函數(shù)式編程 奠定基礎。Python 也可以把函數(shù)賦值給變量。Go 也是支持把函數(shù)賦值給變量的。
1.2 typeof 運算符
JavaScript 有三種方法,確定一個值到底是什么類型
- typeof 運算符
- instanceof 運算符
- Object.prototype.toString 方法
typeof 可以返回一個值的數(shù)據(jù)類型。(這個 typeof 居然不是駝峰命名,也是一大怪)
數(shù)值、字符串、布爾值分別返回 "number" / "string" / "boolean"
typeof 123 // "number"
typeof "abc" // "string"
typeof false // "boolean"
函數(shù)返回 "function"
function f() {}
typeof f // "function"
undefined 返回 "undefined"
typeof undefined // "undefined"
typeof 的使用場景
可以用來檢測一個沒有聲明的變量
// 上下文中沒有對 v 的任何定義
// 如果直接調(diào)用 v,會:Uncaught ReferenceError: v is not defined
typeof v // "undefined"
實際編程中,這個特點通常放在判斷語句中。
// 錯誤的寫法
if (v){
....
}
// ReferenceError: v is not defined
// 正確的寫法
if (typeof v === "undefined"){
....
}
對象返回 "object"
typeof window / document // "object"
typeof {} // "object"
typeof [] // "object"
數(shù)組的類型也是 “object”,這表示在 JS 內(nèi)部,數(shù)組本質(zhì)上是一種特殊的對象。
null 返回 "object"
typeof null
// "object"
null 的類型是 "object" 這是歷史原因造成的。JS 語言第一版只設計了五種數(shù)據(jù)類型(對象、整數(shù)、浮點數(shù)、字符串和布爾值),當時只是把 null 當成 object 的一種特殊值。后來 null 獨立出來,作為一種單獨的數(shù)據(jù)類型,但是為了兼容以前的代碼,typeof null 返回 "object" 就沒有辦法改了。
2 null, undefined 和 布爾值
2.1 undefined 和 null 的區(qū)別
教程 http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html
- 相似性
將一個變量賦值為 undefined 或 null,幾乎沒有區(qū)別:
let a = undefined;
let b = null;
使用相等運算符,會直接報告它們相等
undefined == null // true
它們的布爾值都是 false
Boolean(null) // false
Boolean(undefined) // false
- 差別
null 表示一個空對象,在轉為數(shù)值時可以轉化為 0; undefined 是一個“此處無定義”的原始值,轉化為 NaN
Number(null) // 0
5 + null // 5
Number(undefined) // NaN
- undefined 表示未定義,下面是 undefined 的典型場景
- 變量聲明了,但是沒有賦值
var i;
i // undefined
- 調(diào)用函數(shù)時,應該提供的參數(shù)沒有提供,該參數(shù)等于 undefined; 這一點算得上是拙劣的設計了,如果是其他的語言會直接拋出異常。
function f(x){
return x;
}
f() // undefined
- 對象沒有賦值的屬性
var o = new Object();
o.p // undefined
- 函數(shù)沒有返回值,默認返回 undefined
function f() {}
f() // undefined
2.1 布爾值
Python 和 JS 都會對一些值進行隱式的布爾值轉換。Go 和 Java 這種強類型語言則不會
- undefined
- null
- false
- 0
- NaN
- "" 或 ‘’(空字符串)
這幾種值的布爾值都是 undefined。在應用中,可以寫出這種形式:
if (''){
}
注意,空 [] 和 {} 對應的布爾值是 true, 這也是 JS 的特性:
Boolean([]) // true
Boolean({}) // true
3 數(shù)值
https://wangdoc.com/javascript/types/number.html
3.1 概述
3.1.1 整數(shù)和浮點數(shù)
JavaScript 內(nèi)部,所有數(shù)字都是以 64 位浮點數(shù)形式存儲,即使整數(shù)也是如此。所以,在 1 與 1.0 是相同的,是同一個數(shù)。
1 === 1.0 // true
這就是說,JavaScript 語言的底層根本沒有整數(shù),所有數(shù)字都是小數(shù)(64 位浮點數(shù))。容易造成混淆的是,某些運算是只有整數(shù)才能完成,此時 JavaScript 會自動把 64 位浮點數(shù)轉為 32 位整數(shù),然后再進行運算。
由于浮點數(shù)不是精確的值,所以涉及到小數(shù)的比較和運算要特別小心。
0.1 + 0.2
// 0.300000000000000004
0.3 / 0.1
// 2.999999999999999996
0.3 - 0.2
// 0.099999999999999998
3.1.2 數(shù)值精度
精度最多只能到 53 個二進制,這意味著,絕對值小于等于 2 的 53 次方的整數(shù)。
Math.pow(2, 53)
// 9007199254740992
3.1.3 數(shù)值范圍
如果一個數(shù)大于等于 2 的 1024 次方,那么就會發(fā)生 “正向溢出”,即返回 JavaScript 無法表示這么大的數(shù),這時就會返回 Infinity.
Math.pow(2, 1024) // Infinity
如果一個數(shù)小于等于 2 的 -1075 次方,那么就會發(fā)生 "負向溢出",JS 無法表示這么小的數(shù),會直接返回0
Math.pow(2, -1075) // 0
JavaScript 提供 Number 對象的 MAX_VALUE 和 MIN_VALUE 屬性,返回可以表示的具體的最大值和最小值
Number.MAX_VALUE // 1.7....
Number.MIN_VALUE // 5e-324
3.2 數(shù)值的表示法
JavaScript 的數(shù)值有多種表示方法,可以用字面形式直接表示,比如 35 (十進制)和 0xFF (十六進制)
123e3 // 123000
123e-3 // 0.123
-3.1E+12
.1e-23
科學計數(shù)法允許字母 e 和 E 的后面,跟著一個整數(shù),表示這個數(shù)值的指數(shù)部分。
以下兩種情況,JavaScript 會自動將數(shù)值轉為科學計數(shù)法表示,其他情況都采用字面形式表示。
1)小數(shù)點前的數(shù)字多于 21 位。
1111111111111111111111111111111111111111111111111
// 1.1111111111111112e+48
- 小數(shù)點后面的零多于5個。
// 小數(shù)點后緊跟 5 個以上的零,
// 就自動轉化為科學計數(shù)法
0.0000003 // 3e-7
3.3 數(shù)值的進制
使用字面量(literal)直接表示一個數(shù)值時,JavaScript 對整數(shù)提供四種進制的表示方法:十進制、十六進制、八進制、二進制。
- 十進制:沒有前導 0 的數(shù)值。
- 八進制:有前綴
0o
或00
的數(shù)值,或者有前導0、且只用到 0-7 的八個阿拉伯數(shù)值。 - 十六進制:有前綴
0x
或0X
的數(shù)值 - 二進制:有前綴
0b
或0B
的數(shù)值。
3.4 特殊數(shù)值
JavaScript 提供了幾個特殊的數(shù)值。
3.4.1 正零和負零
JavaScript 的 64 位浮點數(shù)之中,有一個二進制位是符號位。這意味著,任何一個數(shù)都有一個對應的負值,就連 0
也不例外。
JavaScript 中存在兩個0:一個是+0,一個是-0,區(qū)別就是64位浮點數(shù)表示法的符號位不同。它們是等價的。
-0 === +0 //true
0 === -0 //true
0 === +0 //true
幾乎在所有場合,正零和負零都會被當作正常的 0
+0 // 0
-0 // 0
(-0).toString() // '0'
(+0).toString() // '0'
唯一有區(qū)別的場合是,+0 或 -0 當作分母,返回的值是不相等的。
(1 / +0) === (1 / -0) // false
上面的代碼之所以出現(xiàn)這樣的結果,是因為除以正零得到 +Infinity
,除以負零得到-Infinity
,這兩者是不相等的。
3.4.2 NaN
(1) 含義
NaN 是 JS 的特殊值,表示 “非數(shù)字”(Not a Number), 主要出現(xiàn)在將字符串解析成數(shù)字出錯的場合。
5 - 'x' // NaN
上面的代碼運行時,會自動將字符串 x
轉化為數(shù)值,但是由于 x
不是數(shù)值,所以最終得到的結果是 NaN
,表示它是非數(shù)字.
NaN 不是特殊的數(shù)據(jù)類型,而是一個特殊數(shù)值,它的數(shù)據(jù)類型依然屬于 Number
,使用 typeof
運算符可以看清楚。
typeof NaN // 'number'
(2) 運算規(guī)則
NaN 不等于任何值,包括它本身。
NaN === NaN // false
數(shù)組的 indexOf 方法內(nèi)部使用的是嚴格相等運算符,所以該方法對 NaN不成立。
[NaN].indexOf(NaN) // false
NaN 在的布爾值是 false
Boolean(NaN) // false
NaN 與任何數(shù)(包括它自己)的運算,得到的結果都是 NaN.
NaN + 32 // NaN
NaN + NaN // NaN
3.4.2 Infinity
(1) 含義
Infinity
表示“無窮”,用來表示兩種場景。一種是一個正的數(shù)值太大,或一個負的數(shù)值太小,無法表示;另一種是非 0 數(shù)值除以 0,得到 Infinity
。
// 場景一
Math.pow(2, 1024) // Infinity
場景二
1/ 0 // Infinity
Infinity 有正負之分,Infinity
表示正的無窮,-Infinity
表示負的無窮。
Infinity === -Infinity // false
1 / -0 // -Infinity
由于數(shù)值正向溢出和負向溢出和被0除,JavaScript 都不報錯,而是返回 Infinity, 所以單純的數(shù)學運算機會沒有可能拋出錯誤。
Infinity
大于一切數(shù)值(除了 NaN
), -Infinity
小于一切數(shù)值(除了 NaN
)
Infinity > 111111111111111111111111 //true
-Infinity < -11111111111111 // true
Infinity
與 NaN
比較,總是返回 false
Infinity < NaN // false
Infinity > NaN // false
(2) 運算規(guī)則
Infinity 的運算規(guī)則,符合無窮的數(shù)學計算規(guī)則。
5 * Infinity // Infinity
5 - Infinity // -Infinity
Infinity / 5 // Infinity
5 / Infinity // 0
0 乘以 Infinity,返回 NaN; 0 除以 Infinity, 返回 0; Infinity 除以0,返回 Infinity。
0 * Infinity // NaN
0 / Infinity // 0
Infinity / 0 // Infinity
Infinity 加上或乘以 Infinity, 返回的還是 Infinity。
Infinity + Infinity // Infinity
Infinity * Infinity // Infinity
Infinity 減去或除以 Infinity, 得到 NaN
Infinity - Infinity // NaN
Infinity / Infinity // NaN
Infinity 與 null 計算時, null 會轉成0,等同于與 0 的計算。
null * Infinity // NaN
null / Infinity // 0
Infinity / null // Infinity
Infinity 與 undefined 計算,返回的都是 NaN
undefined + Infinity // NaN
undefined - Infinity // NaN
undefined * Infinity // NaN
undefined / Infinity // NaN
Infinity / undefined // NaN
3.5 與數(shù)值相關的全局方法
全局方法,也就是像 Python 那樣的內(nèi)置函數(shù)
3.5.1 parseInt()
(1) 基本用法
parseInt
方法用于將字符串轉為整數(shù)
parseInt('123') // 123
如果字符串頭部或尾部有空格,空格會被自動去除
parseInt(" 123 ") // 123
如果 parseInt
的參數(shù)不是字符串,則會先轉為字符串再轉化。
parseInt(1.23) // 1
// 等同于
parseInt('1.23') // 1
字符串轉化為整數(shù)的時候,是一個個字符依次轉化下去的,如果遇到不能轉化為數(shù)字的字符,就不再進行下去,返回已經(jīng)轉好的部分?!@個特性真是中二!
parseInt("12e3") // 12
如果字符串的第一個字符就不能轉化為數(shù)字的話,則會返回NaN。(第一位為數(shù)字的正負號除外)
parseInt('abc') // NaN
parseInt('.3') // NaN
parseInt('') // NaN
parseInt('+') NaN
如果字符串以 0x
或 0X
開頭,parseInt
會將其按照十六進制數(shù)解析。
parseInt('0x10') // 16
如果字符串以 0
開頭,將其按照 10 進制解析。
parseInt('011') // 11
對于那些會自動轉為科學計數(shù)法的數(shù)字, parseInt
會將科學計數(shù)法的表示方法視為字符串,因此導致一些奇怪的結果。
parseInt(111111111111111111111111111111) // 1
// 等同于
parseInt("1.11111111112e+54") // 1
parseInt(0.000008) // 8
parseInt('8e-7') //8
(2) 進制轉換
parseInt
方法還可以接受第二個參數(shù)(2到36之間),表示被解析的值的進制,返回該值對應的十進制數(shù)。默認情況下,parseInt
的第二個參數(shù)為10,表示默認是十進制轉化為十進制。
parseInt('1000') // 1000
// 等同于
parseInt('1000', 10) // 1000
下面是轉化指定進制的數(shù)的例子
parseInt('1000', 2) // 2
parseInt('1000', 6) // 216
parseInt('1000', 8) // 512
如果第二個參數(shù)不是數(shù)值,會被自動轉為一個整數(shù)。這個整數(shù)只有在2到36之間,才能得到有意義的結果,超出這個范圍,則返回 NaN 。如果第二個參數(shù)是0、undefined 和 null,則直接忽略
parseInt('10', 37) // NaN
parseInt('1000', 2.2) // 2
3.5.2 parseFloat()
parseFloat() 也是一個內(nèi)置方法,作用是跟 parseInt
一樣的。
3.5.3 isNaN()
isNaN 方法可以用來一個值是否為 NaN
isNaN(NaN) // true
isNaN(123) // false
但是,isNaN
只對數(shù)值有效,如果傳入其他值,會被先轉成數(shù)值。比如,傳入字符串的時候,字符串會被先轉成 NaN
,所以最后返回 true
,這一點要特別引起注意。也就是說,isNaN
為 true
的值,有可能不是 NaN
,而是一個字符串。
isNaN('Hello') // true
isNaN(Number('Hello')) // true
出于同樣的原因,對于對象和數(shù)組,isNaN 也返回 true
isNaN({}) // true
// 等同于
isNaN(Number({})) // true
// ...
isNaN(['xzy']) // true
// 等同于
isNaN(Number(['xzy'])) //true
但是,對于空數(shù)組和只有一個數(shù)值成員的數(shù)組,isNaN 返回 false。
isNaN([]) // false
isNaN([123]) // false
isNaN(['123']) // false
上面代碼之所以返回 false
,原因是這些數(shù)組能被 Number
函數(shù)轉成為數(shù)值,請參見《數(shù)據(jù)類型轉換》一章。
因此,使用isNaN
之前,最好判斷一下數(shù)據(jù)類型。
function myIsNaN(value){
return typeof value === 'number' && isNaN(value);
}
myIsNaN(NaN) // true
判斷 NaN
更可靠的方法是,利用 NaN
為唯一不等于自身的值的這個特點,進行判斷:
function myIsNaN(value){
return value !== value;
}
3.5.4 isFinite()
isFinite
方法返回一個布爾值,表示某個值是否正常的數(shù)值。
isFinite(Infinity) // false
isFinite(-Infinity) // false
isFinite(NaN) // false
isFinite(undefined) // false
isFinite(null) // true
isFinite(-1) // true
除了 Infinity
/ -Infinity
/ NaN
和 undefined
這個值會返回 false
,isFinite
對于其他的數(shù)值都會返回 true
4 字符串
4.1 概述
4.1.1 定義
字符串就是零個或多個排在一起的字符,放在單引號或雙引號之中。
'abc'
"abc"
在 Java 、Go 等靜態(tài)語言中,有字符這個概念,用單引號引起來,字符串是字符數(shù)組的語法糖用雙引號引起來。
可以用反斜杠來轉義。
"Did she say \"Hello\" ?"
由于 HTML 語言的屬性值使用雙引號,所以很多項目約定 JavaScript 語言的字符串只使用單引號。
let longStr = `long \ long`
可以使用 \
多行定義字符串,在 es6 中可以使用 `` 來定義多行字符串。
也可以使用 + 連接多個單行字符串。
還有一種利用多行注釋的變通方法。
(function(){/*
line 1
line 2
line 3
*/}).toString().split('\n').slice(1, -1).join('\n')
4.1.2 轉義
反斜杠 () 在字符串內(nèi)有特殊含義,用來表示一些特殊字符,所以又稱為轉義符。
需要用反斜杠轉義的特殊字符,主要有下面這些:
-
\0
: null -
\b
: 后退鍵 -
\f
: 換頁符 -
\n
: 換行符 -
\r
: 回車鍵 -
\t
: 制表符 - \ : 反斜杠
- ' : 單引號
- " : 雙引號
反斜杠還有三種特殊用法。
-
\HHH
反斜杠后面緊跟三個八進制數(shù)(000
到377
),代表一個字符。HHH
對應該字符的 Unicode 碼點,比如\251
表示版權符號。顯然,這種方法只能輸出 256 種字符。 -
\xHH
\x
后面緊跟兩個十六進制數(shù)(00
到FF
),代表一個字符。HH
對應該字符的 Unicode 碼點,比如\xA9
表示版權符號。 -
\uXXXX
\u
后面緊跟四個十六進制數(shù)(0000
到FFFF
),代表一個字符。XXXX
對應該字符的 Unicode 碼點,比如\u00A9
表示版權符號。
如果在非特殊字符前面使用反斜杠,則反斜杠會被省略。
'\a' // "a"
如果字符串的正常之中,需要包含反斜杠,用來對自身轉義。
"Prev \\ Next"
// "Prev \ Next"
4.1.3 字符串與數(shù)組
字符串可以視為字符數(shù)組,可以進行索引。(阮大說的真費勁)
如果方括號中不是數(shù)字,或者索引越界,則會返回 undefined, 而不是強類型語言一樣報錯。
let s = 'Hello';
s[0] // "h"
s["0"] // "h"
s[null] // undefined
s[1000] // undefined
字符串只是和數(shù)組的相似性僅此而已。實際上,無法改變字符串之中的單個字符。
對字符串進行刪改的操作會失效,但是不會出錯。
let s = "hello";
delete s[0];
s[1] = 'a';
s // hello
4.1.4 length 屬性
length 屬性返回字符串的長度,該屬性也是無法改變的
let s = "hello";
s.length // 5
s.length = 10
s.length // 5
4.2 字符集
JavaScript 使用 Unicode 字符集。JavaScript 引擎內(nèi)部,所有字符都用 Unicode 表示。
JavaScript 不僅以 Unicode 儲存字符,還允許直接在程序中使用 Unicode 碼點表示字符,即將字符寫成 \uxxxx
的形式,其中xxxx
代表該字符的 Unicode 碼點。比如,\u0049
代表版權符號。
let s = '\u00A9';
s // "?"
解析代碼的時候,JavaScript 會自動識別一個字符是字面形式表示,還是 Unicode 形式。輸出給用戶的時候,所以字符都會轉成字面形式。
let f\u006F\u006F = 'abc';
foo // "abc"
上面代碼中,第一行的變量名是 foo
是 Unicode 形式表示,第二行是字面形式表示。JavaScript 會自動識別。
每個字符在 JavaScript 內(nèi)部以 16 位(2個字節(jié))的 UTF-16 格式存儲。也就是說,JavaScript 的單位字符長度固定為 16 位長度,2個字節(jié)。
UTF-16 有兩種長度:對于碼點在 U+0000
到U+FFFF
之間的字符,長度為16位(2個字節(jié));對于碼點在 U+10000
到 U+10FFFF
之間的字符,長度為 32 位(即 4 個字節(jié))。
JavaScript 對 UTF-16 的支持是不完整的,由于歷史原因,只支持兩字節(jié)的字符,不支持四字節(jié)的字符。所以對于四字節(jié)字符,瀏覽器會正確識別這是一個字符,但是 JS 會認為這個兩個字符。
總之,JS 返回的字符串長度可能是不正確的。
4.3 Base64 轉碼
有時候,文本里面包含一些不可打印的符號,比如 ASCII 碼 0 到 31 的符號都無法打印出來,這時可以使用 Base64 編碼,將它們轉成可以打印的字符。另外一個場景是,有時需要以文本格式傳遞二進制數(shù)據(jù),可以使用 Base64 編碼。
Base64 就是一種編碼方式,可以將任意值轉化成 0~9 \ A-Z \ a-z + 和 / 這64個字符組合的可打印字符。使用它的主要目的,不是為了加密,而是為了不出現(xiàn)特殊字符,簡化程序的處理。
JavaScript 原生提供了兩個 Base64 相關的方法。
-
btoa()
: 任意值轉為 Base64 編碼。 -
atob()
: Base64 編碼轉為原來的值。
let str = "Hello";
btoa(str) // SGVsbG8=
atob("SGVsbG8=") // Hello
注意,這兩個方法不適合非 ASCII 碼的字符,會報錯。
btoa('你好') // 報錯,The string to be encoded contains characters outsie of the Latin1 range (字符串包含了非 Latin1(拉丁) 字符)
要將非 ASCII 碼字符轉 Base64 編碼,必須中間插入一個轉碼環(huán)節(jié),再使用這兩個方法。
function b64Encode(str){
return btoa(encodeURIComponent(str));
}
function b64Decode(str){
return decodeURIComponent(atob(str));
}
4.4 ASCII 與 非ASCII 轉化
// 轉化為 ASCII
encodeURIComponent(str)
// 將 ASCII 轉化為 非 ASCII
decodeURIComponent(str)
5 對象
https://wangdoc.com/javascript/types/object.html
5.1 概述
5.1.1 生成方法
對象( Object ) 是 JavaScript 語言的核心,也是最重要的數(shù)據(jù)類型。
簡單來說,對象就是一組鍵值對的“集合”
5.1.2 鍵名
鍵名是數(shù)值時,會自動轉化為字符串。但是打印出來的時候看不出來。
let d = {1:"a"}
如果鍵名不符合變量名的命名規(guī)則,如第1位是數(shù)值或者以空格開頭,那么需要用引號引起來。
d["1px"] = "c"
對象的每一個鍵名都又稱為“屬性”(property),鍵值可以是任何數(shù)據(jù)類型。如果一個屬性的值是函數(shù)時,這個屬性一般被稱作方法,它可以像函數(shù)一樣被調(diào)用。
let b = {
f : (x)=>{return x}
}
b.f(3) // 3
如果屬性的值是一個對象,就形成了鏈式引用。
let a = {b:{c:3}}
a.b.c //3
5.1.3 對象的引用
如果不同的變量名指向同一個對象,那么它們都是這個對象的引用,也就是指向同一個內(nèi)存地址。如果修改其中一個變量,會影響到其他所有的變量。
let a = {w:1}
let b = a;
b.w = 2
a.w // 2
這個問題很經(jīng)典,python 里面的深拷貝和淺拷貝也是這個樣子,Java 和 Go 也有類似的問題。這個問題的關鍵是,要分清這個值是什么類型,如果值是原始類型/基礎類型等,那么分別賦予不同的變量名就會發(fā)生值拷貝,修改彼此不發(fā)生影響。如果是值引用類型,這個類型通常是個容器,里面的成員是個指向真實值地址的引用。這時修改這個容器,就會影響所有的變量名。
5.1.4 表達式還是語句
{foo:123}
如果行首出現(xiàn)這行代碼,會有兩種含義:
這是一個對象,
這是一個語句,表示是一個代碼塊,里面一個標簽 foo ,指向代碼123。
所以為了避免歧義,行首是大括號時,最好在前面加上括號。
5.2 屬性的操作
5.2.1 屬性的讀取
有兩種方式,用點和方括號(python就是這樣)。
但是需要注意,當使用方括號時,鍵必須被引號引起來,否則就會被當作變量處理。
let a = "ok"
let d = {
a : 1,
ok: 2
}
d[a] // 2
d["a"] // 1
鍵名是數(shù)字可以不加引號,因為可以自動轉化為字符串。并且數(shù)字鍵名不能使用點運算符,因為會被當作小數(shù)點。
let a = {3:"three"}
a.3 // SyntaxError: Unexpected number
a[3] // "three"
5.2.2 屬性的賦值
跟 python 一樣,略。
5.2.3 屬性的查看
查看所有屬性:Object.keys()
let w = {1:2,2:3,3:4}
Object.keys(w) // ["1","2","3"]
python 跟這個不一樣,
w = {1:2,2:3,3:4}
w.keys()
造成這樣不一樣的原因是,在 python 中講究一切皆對象。代碼中的 w
是 python dict 類的一個實例。而 python 定義的dict有一個 keys() 的方法。
而JS中并不是這樣做的。而是把 w 當作參數(shù)傳入進去。
5.2.4 屬性的刪除:delete 命令
刪除屬性使用 delete,但是 delete 在刪除屬性時無論該屬性是否存在都會返回 true。
let obj = {}
delete obj.p // true
除非是刪除私有屬性才會返回 false,并且這樣的私有屬性不會通過 delete 刪除。
let obj = Object.defineProperty({},'p',{value:2})
obj.p // 2
delete obj.p // false
另外需要注意,繼承的屬性是不能刪除的
let obj = {}
delete obj.toString // true
阮大這個思考很深入啊,之前沒有想過刪除一下從內(nèi)置的繼承屬性
del w.keys
// AttributeError: 'dict' object attribute 'keys' is read-only
// keys 只是可讀屬性。屬性分兩種,可讀和可修改
5.2.5 屬性是否存在:in 運算符
基本上可以按照 Python 中的 in 來理解,可以用在對象和數(shù)組上。Python 中的 in 作用的對象要求必須是 iterable 。
in 運算符有一個問題,它不能識別哪些屬性是對象自身的,哪些屬性是繼承的。
let arr = []
"toString" in arr // true
這時,可以使用對象的 hasOwnProperty
方法判斷一下,是否為對象自身的屬性。
let obj = {};
if('toString' in obj){
console.log(obj.hasOwnProperty('toString'));
}
5.2.6 屬性的遍歷:for ... in 循環(huán)
for ... in 循環(huán)用來遍歷一個對象的所有屬性。
for(let i in {a:1,b:2}){
console.log(i)
}
數(shù)組本質(zhì)上也是一種特殊的屬性,所以用 for ... in 遍歷返回的是索引位置。
for ... in 循環(huán)使用時有兩個注意點:
- 它遍歷的是對象所有可遍歷(enumerable)的屬性,會跳過不可遍歷的屬性
- 它不僅遍歷對象自身的屬性,還遍歷繼承的屬性。
如果只想遍歷對象自身的屬性,需要結合hasOwnProperty
, 在循環(huán)內(nèi)部判斷一下,某個屬性是否為對象自身的屬性
let person = {name:333}
for(let i in person){
if (person.hasOwnProperty(i)){
console.log(i);
}
}
使用 for ... of 遍歷對象會拋異常:
typeError : intermediate value is not iterable
復制值是不可迭代的
這個錯誤也凸顯 for ... of 和 for ... in 真正的區(qū)別:
for ... in 是遍歷對象;
for ... of 是遍歷可迭代對象,這個才是模仿 python 的 for ... in...
5.3 with 語句
with
語句的格式如下:
with (對象){
語句;
}
它的作用是操作同一個對象的多個屬性時,提供一些書寫方便。
let obj = {
p1 : 1,
p2 : 2,
}
with (obj){
p1 = 4;
p2 = 5;
}
// 等同于
obj.p1 = 4;
obj.p2 = 5;
注意,如果 with 區(qū)塊內(nèi)部有變量的賦值操作,必須是當前已經(jīng)存在的屬性,否則就會創(chuàng)造一個當前作用域的全局變量。
let obj = {a:1}
with (obj){
a = 8;
b = 99;
}
obj.a // 8
obj.b // undefined
b // 99
with 區(qū)塊沒有改變作用域,它的內(nèi)部依然是當前作用域。這造成了 with 語句的一個很大弊病,就是綁定對象不明確。
編譯器無法在編譯時提前優(yōu)化,因為 它無法判斷with 語句中的變量是全局變量還是 obj 的一個屬性,只能等到運行時判斷,這就拖慢了運行速度。
python 中也有 with,但是 with 是一個上下文管理工具,和 js 中 with 作用完全不一樣。
6 函數(shù)
教程:https://wangdoc.com/javascript/types/function.html
6.1 概述
6.1.1 函數(shù)的聲明
JavaScript 有三種聲明函數(shù)的方法。
(1) 使用 function
(2) 函數(shù)表達式
let a = ()=>{}
一般這種形式,函數(shù)表達式都是一個匿名函數(shù),但是也可以是一個帶有函數(shù)名的函數(shù)。這時,該函數(shù)名只在函數(shù)體內(nèi)部有效,在函數(shù)體外部無效。
let print = function x(){
console.log(typeof x);
}
x // ReferenceError: x is not defined
print() // function
這種寫法有兩種好處:
一是可以在函數(shù)體內(nèi)部調(diào)用自身,
二是方便排錯,除錯工具顯示函數(shù)調(diào)用棧時,將顯示函數(shù)名,而不再這里是一個匿名函數(shù)。
let f = function f() {};
需要注意的是,函數(shù)的表達式需要在語句的結尾加上分號,表示語句結束。函數(shù)的聲明在結尾的大括號后面不用加分號。
第三種聲明函數(shù)的方式是 Function 構造函數(shù)
let add = new Function(
'x',
'y',
'return x + y'
)
// 這種寫法等同于
function add(x,y){
return x + y
}
可以傳遞任意數(shù)量的參數(shù)給 Function
構造函數(shù),只有最后一個參數(shù)會被當做函數(shù)體,如果只有一個參數(shù),該參數(shù)就是函數(shù)體。
6.1.2 函數(shù)的重復聲明
如果一個函數(shù)被多次重復聲明,那么后面的聲明就會覆蓋前面的聲明。
阮大真是腦回路清奇,實驗了一下,在 Python 也是如此,后面的聲明可以覆蓋前面的聲明。
但是,由于 JS 函數(shù)名的提升(參見下文), 前一次聲明在任何時候都是無效的,這一點特別注意。Python 中因為不會像JS 這樣先解釋在運行,而是邊解釋邊運行,所以還是有效的。
function a(){
console.log("111");
}
a()
function a(){
console.log("222");
}
a()
// 222
// 222
6.1.3 圓括號運算符、return 語句和遞歸
圓括號運算符就是調(diào)用函數(shù)。
遞歸就是遞歸計算唄
6.1.4 第一等公民
函數(shù)是 JS 中的一等公民,也就是說函數(shù)是一種值,它與其他值(數(shù)值、布爾值、字符串等等)地位相同。凡是可以使用值的地方,就能使用函數(shù)。
function add(x, y){
return x + y;
}
// 將函數(shù)賦值給一個變量
let op = add;
// 將函數(shù)作為參數(shù)和返回值
function a(op){
return op;
}
// python 也支持這一系列操作的
// a(add) 就是函數(shù) add 本身了
// (1,1) 表示要調(diào)用 add 這個函數(shù)
a(add)(1,1)
6.1.5 函數(shù)名的提升
JavaScript 引擎將函數(shù)名視同變量名,所以采用 function
命令聲明函數(shù)時,整個函數(shù)會像變量聲明一樣,被提升到代碼的頭部。所以,下面代碼是不會報錯的。
f();
function f() {}
但是如果使用賦值語句定義函數(shù),JS 就會報錯。
f();
var f = ()=>{};
// TypeError: f is not defined
上面的代碼等于
var f ;
f();
f = function () {};
因此,同時采用 function
命令和賦值語句聲明同一個函數(shù),最后總是采用賦值語句的定義。
6.2 函數(shù)的屬性和方法
6.2.1 name 屬性
函數(shù)的name
屬性返回函數(shù)的名字
function f1(){}
f1.name // f1
如果通過變量賦值定義的函數(shù),name
屬性返回變量名。
let f2 = function(){}
f2.name // "f2"
如果變量的值是一個具名函數(shù),那么name
屬性返回的是具名函數(shù)的函數(shù)名。
let f2 = function myFunction(){}
f2.name // "myFunction"
name
屬性的應用場景
name
屬性的一個用處,就是獲取參數(shù)函數(shù)的名字。
let myFunc = function(){};
function test(f){
console.log(f.name);
}
test(myFunc)
//"myFunc"
6.2.2 length 屬性
函數(shù)的 length
屬性返回函數(shù)簽名中需要返回的函數(shù)個數(shù)
function f(a, b){}
f.length // 2
6.2.3 toString()
返回一個字符串,內(nèi)容為函數(shù)的定義,即便是注釋部分也會被返回
6.3 函數(shù)作用域
6.3.1 定義
作用域(scope)指的是變量存在的范圍。在 ES5 中,JS 只有兩宗作用域:全局作用域,變量在整個程序中一直存在,所有地方都可以讀取;函數(shù)作用域,變量只在函數(shù)內(nèi)部存在。ES6 新增了塊級作用域。
函數(shù)外部聲明的變量就是全局作用域,全局作用域可以在函數(shù)內(nèi)部讀取。
var a = 1;
function w(){
console.log(a);
}
// 1
函數(shù)內(nèi)部定義的變量就是局部變量,不能在函數(shù)外部讀取。
function w(){
var a = 1;
}
console.log(a);
// ReferenceError: v is not defined
如果全局變量和局部變量重名,那么在局部作用域內(nèi)局部變量會覆蓋全局變量。這個很好理解。
需要注意的是,var 只有在函數(shù)內(nèi)部定義的才是局部變量,在其他塊級作用域內(nèi)定義的都是全局變量。
if(true){
var a = 3;
}
console.log(a);
// a
這點也是 let 與 var 的區(qū)別,let 定義的變量即便在其他的塊級作用域內(nèi),依舊是局部變量。
if(true){
let a = 3;
}
console.log(a);
// RefereceError: a is not defined
6.3.2 函數(shù)內(nèi)部的變量提升
在函數(shù)作用域內(nèi)部,也會出現(xiàn)變量提升。在函數(shù)作用域內(nèi)部,var 聲明的變量不管在什么位置,會提升到函數(shù)作用域的頭部。
function foo(){
console.log(w);
var w = 100;
}
foo() // undefined
6.3.3 函數(shù)本身的作用域
有點莫名其妙,看完之后覺得都是應該的。
6.4 參數(shù)
6.4.1 概述
函數(shù)的參數(shù)就是函數(shù)簽名中括號中的那些變量。
6.4.2 參數(shù)的省略
在 JS 中函數(shù)的參數(shù)可以省略,這點 JS 獨有的。比如在 Python 中如果省略參數(shù)會直接拋出異常,在 JAVA 中不同的參數(shù)個數(shù)還可以區(qū)分不同的方法。
function f(a, b){
return a + b;
}
f(2) // NaN
f() // NaN
當前不能省略前面的參數(shù),這點是毫無疑問的
6.4.3 傳遞方式
函數(shù)參數(shù)如果是原始類型的值(數(shù)值、字符串、布爾值),則是值傳遞。即傳入的參數(shù)值是原始值的拷貝,在函數(shù)內(nèi)修改參數(shù)的值,不會對原始值產(chǎn)生影響。
函數(shù)參數(shù)如果是容器類型(數(shù)組、對象、其他函數(shù)等),那么傳值的方式是值引用,傳進入的是一個地址。如果在函數(shù)內(nèi)修改參數(shù)的值,原始值也會隨之發(fā)生改變。
// Python
def a(x):
x[0] = 33
w = [1, 2]
a(w)
w //[33,2]
但是注意,如果函數(shù)內(nèi)部修改的,不是參數(shù)對象的屬性,而是替換整個參數(shù),這時不會影響到原始值。
def a(x):
x = [1, 2, 3]
w = ["a", "b"]
a(w)
w
// ["a", "b"]
這是因為,形式參數(shù) x 的值實際是參數(shù) w 的地址,重新對 x 賦值導致 x 指向另一個地址,保存在原地址上的值當然不受影響。
為什么對于原始值是值拷貝,容器類型則是值引用?當然是容器類型東西太多了,為了提高效率才這么做的。
6.4.4 同名參數(shù)
這個也算是 JS 特性了。
如果有同名的參數(shù),則取最后出現(xiàn)的那個值。
function f(a, a){
console.log(a);
}
f(1,2)
// 2
取值的時候,以后面的 a
為準,即使后面的 a
沒有值或者被省略,也是以其為準。
f(1) // undefined
調(diào)用函數(shù) f
的時候,沒有提供第二個參數(shù),a
的取值就變成了 undefined
。這時,如果要獲得第一個 a
的值,可以使用 arguments
的對象。
function f(a,a){
console.log(arguments[0]);
}
f(1) // 1
6.4.5 arguments 對象
(1) 定義
由于 JS 允許函數(shù)有不定數(shù)目的參數(shù),所以需要一種機制,可以在函數(shù)體內(nèi)部讀取所有參數(shù)。這就是 arguments 對象的由來。
這個特點可以聯(lián)想到 shell,shell 的參數(shù)也可以使用 1 /2 / $3 ... 來表示。arguments[0] 表示第一個參數(shù)、arguments[1] 表示第二個參數(shù)...
arguments 對象只有在函數(shù)體內(nèi)部才可以使用。
正常情況下,arguments 對象可以在運行時修改
let f = function(a, b){
arguments[0] = 3;
arguments[1] = 2;
return a + b ;
}
f(1, 1) // 5
但是在嚴格模式下,arguments
對象是一個只讀對象,修改它是無效的,但是不會報錯
function f(a,b){
"use strict"
arguments[0] = 1;
arguments[1] = 2;
return a + b;
}
f(1,1) // 2
可以通過 arguments
對象的 length
屬性,可以判斷函數(shù)調(diào)用時到底帶幾個參數(shù)。
function f(){
return arguments.length;
}
f(1,2,3) //3
f() // 0
(2) 與數(shù)組的關系
阮大對 JS arguments 的描述,讓我想起了 Python 的不定參數(shù) *args
。
arguments 很像數(shù)組,但是它是一個對象。數(shù)組專有的方法(比如 slice
和 forEach
),不能在 arguments
上直接使用。
如果想讓 arguments
對象使用數(shù)組方法,真正的解決方法是將 arguments
轉化為真正的數(shù)組。轉換方法有兩種:
// 方法一
var args = Array.prototype.slice.call(arguments);
// 方法二
var args = [];
for(var i = 0; i < arguments.length; i++){
args.push(arguments[i]);
}
(3) callee 屬性
callee屬性返回它所對應的原函數(shù)。
這個屬性在嚴格模式下是禁止使用的。
6.5 函數(shù)的其他知識點
6.5.1 閉包
理解閉包(closure),首先要理解作用域。JS 有兩種作用域:全局作用域和函數(shù)作用域。
函數(shù)內(nèi)部可以直接讀取全局變量。
函數(shù)外部不可以讀取函數(shù)內(nèi)部聲明的局部變量。
如果想在函數(shù)外部讀取函數(shù)內(nèi)部定義的局部變量,就需要使用閉包。
function f1(){
var n = 999;
function f2(){
console.log(n);
}
f2(); // 999
}
f2 在 f1 的內(nèi)部,f1 內(nèi)部所有的變量對于 f2 來說都是可見的。反過來,當然不成立。
既然 f2
可以讀取 f1
的局部變量,那么只要把f2
當作返回值,就可以在f1 函數(shù)的外部讀取 f1 內(nèi)部的變量了。
function f1(){
var n = 999;
function f2(){
return n;
}
return f2;
}
f1()() // 999
函數(shù) f2
就是閉包,即能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。由于在 JS 語言中,只有函數(shù)內(nèi)部的子函數(shù)能夠讀取內(nèi)部變量,因此可以把閉包簡單理解為“定義在函數(shù)內(nèi)部的函數(shù)”。
閉包有兩個作用:
- 可以讀取函數(shù)內(nèi)部的變量;
- 讓這些變量始終保持在內(nèi)存中,即閉包可以使得它的誕生環(huán)境一直存在。
function createIncrementor(start){
return function (){
return start++;
}
}
// i++ 第一次是加0,不知道是為什么
var inc = createIncrementor(5);
inc(); // 5
inc(); // 6
閉包 inc
使得函數(shù) createIncrementor
的內(nèi)部環(huán)境一直存在。原因在于,inc
始終在內(nèi)存中,而 inc
的存在依賴于 createIncrementor
,因此也始終在內(nèi)存中,不會在調(diào)用結束后,被垃圾回收。
閉包的另一個用處,是封裝對象的私有屬性和私有方法。
function Person(name){
var _age;
function setAge(n){
_age = n;
}
function getAge(){
return _age;
}
return {
name: name,
setAge: setAge,
getAge: getAge,
}
}
var p = Person("LiMing");
p.setAge(5);
p.getAge(); // 5
注意,外層函數(shù)每次運行,都會生成一個新的閉包,這個閉包又會保留外層函數(shù)的內(nèi)部變量,所以內(nèi)存消耗很大。換成面向?qū)ο蟮恼Z言來說,每次調(diào)用 Person對象 都會生成一個新的實例,這些實例有自己的屬性,生成的實例多了當然會占內(nèi)存。
6.5.2 立即調(diào)用的函數(shù)表達式(IIFE)
在 JS 中,圓括號()
是一種運算符,跟在函數(shù)名后面表示函數(shù)立即調(diào)用。
但是需要注意,定義函數(shù)之后,不能這樣立即調(diào)用函數(shù)。
function() { /* code */}();
// SyntaxError: Unexpected token (
產(chǎn)生這個錯誤的原因,function
這個關鍵字即可以當作語句,又可以當作表達式。
// 語句
function f() {}
// 表達式
var f = function f() {}
為了避免解析上的歧義,JS 引擎規(guī)定,如果 function
關鍵字出現(xiàn)在行首,一律解析成語句。因此,JS 引擎看到行首是 function
關鍵字之后,認為這一段是函數(shù)的定義,不應該以圓括號結尾,就報錯了。
解決辦法是不要讓function
出現(xiàn)在行首,讓引擎將其理解為一個表達式。最簡單的處理辦法是,將函數(shù)體放到一個圓括號里。
(function(){}());
// 或者
(function(){})();
這種就叫做“立即調(diào)用的函數(shù)表達式”(Immediately-Invoked Function Expression),簡稱 IIFE。
注意,上面兩種寫法最后的分號都是必須的。如果省略分號,遇到連著兩個 IIFE,可能會報錯。
(function(){}())
(function(){})()
// TypeError: (intermediate value)(...) is not a function
推而廣之,任何讓解釋器以表達式來處理函數(shù)定義的方法,都能產(chǎn)生同樣的效果,比如下面三種寫法:
var i = function(){}();
true && function(){}();
0, function(){}();
甚至像下面這樣寫,也是可以的:
!function(){}();
~function(){}();
+function(){}();
對匿名函數(shù)使用這種 “立即執(zhí)行的函數(shù)表達式”,有兩個目的:一是不必為函數(shù)命名,避免了污染全局變量;二是IIFE內(nèi)部形成了一個單獨的作用域,可以封裝一些外部無法讀取的私有變量。
// 寫法一
var tmp = newData;
processData(tmp);
storeData(tmp);
// 寫法二
(function(){
var tmp = newData;
processData(tmp);
storeData(tmp);
})();
6.6 eval 命令
6.6.1 基本用法
eval 接受一個字符串當作參數(shù),并將這個字符串當作語句執(zhí)行。
eval('var a = 1');
a // 1
如果參數(shù)字符串無法當作語句執(zhí)行,就會報錯。
eval 沒有自己的作用域,都在當前作用域內(nèi)執(zhí)行,因此可能會修改當前作用域的變量的值,造成安全問題。
var a = 1;
eval('a = 3');
a
// 3
為了避免這種風險,JS 規(guī)定,如果使用嚴格模式, eval 內(nèi)部聲明的變量,不會影響到外部作用域。
(function(){
'use strict';
eval('var foo = 123;');
console.log(foo)
}())
// ReferenceError: foo is not defined
不過即使在嚴格模式下, eval
依然可以讀寫當前作用域的變量。
(function f(){
'use strict';
var foo =1;
eval('foo = 2');
console.log(foo); // 2
})()
總之,eval
的本質(zhì)是在當前作用域之中,注入代碼。eval 不利于引擎優(yōu)化。
6.6.2 eval 的別名調(diào)用
引擎在靜態(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)化,JS 標準規(guī)定,凡是使用別名執(zhí)行eval
,eval
內(nèi)部一律是全局作用域。
var a =1;
function f(){
var a = 2;
var e = eval;
e('console.log(a)');
}
f() // 1
引擎只能識別 eval()
,eval 的別名調(diào)用都屬于別名調(diào)用。
eval.call(null, '...')
window.eval('...')
(1,eval)('...')
(eval,eval)('...')
7 數(shù)組
教程地址:https://wangdoc.com/javascript/types/array.html
7.1 定義
跟 python 一樣
7.2 數(shù)組的本質(zhì)
數(shù)組的本質(zhì)是特殊的對象。typeof
運算符會返回數(shù)組的類型是 object
。
typeof [1,2,3] // "object"
數(shù)組的鍵名是按次序排列的一組整數(shù)。
var arr = ['a', 'b', 'c'];
Object.keys(arr)
// ["0", "1", "2"]
由于數(shù)組成員的鍵名總是固定的,因此數(shù)組不用為每個元素指定鍵名。JS 語言規(guī)定,對象的鍵名一律為字符串,所以,數(shù)組的鍵名是字符串。之所以可以用數(shù)值讀取,是因為非字符串的鍵名會被轉化為字符串。
var arr = ['a', 'b', 'c'];
arr[0] // 'a'
arr['0'] // 'a'
所以,arr 添加新元素除了 push 之后,還可以這樣子
let arr = [];
a[3] = 888
arr
// [undefined, undefined, undefined, 888]
這個操作,Python 沒有。
7.3 length 屬性
arr.length
// 返回數(shù)組的長度
length 屬性可以讀寫。如果人為把 length 減少到小于數(shù)組的長度,那么數(shù)組內(nèi)元素的個數(shù)也會減少。
let arr = [1,2,3,4];
arr.length = 2;
arr // [1,2]
清空數(shù)組,可以把數(shù)組的長度置為0.
arr.length = 0
如果把length 的長度增大,那么數(shù)組內(nèi)多出來的是空位. 空位和 元素是 undefined 不是一個概念。
Python 數(shù)組的長度當然不能這么改變。因為 len 是函數(shù)調(diào)用。
7.4 in 運算符
in 是用來檢查鍵名是否存在于對象,這個也適用于數(shù)組。
要注意,檢查的是數(shù)組的鍵名,也就是索引值。
let arr = ["a", "b", "c"]
1 in arr // true
"a" in arr // false
7.5 for...in 循環(huán)和數(shù)組的遍歷
for ... in 切莫當成 Python 的 for...in 。
JS 的 for ... in 索引的是鍵名。
let arr = ["a", "b", "c"];
for(let i in arr){
console.log(i);
}
// 0 1 2
此外,數(shù)組不僅會遍歷數(shù)組所有的數(shù)字鍵,還會遍歷非數(shù)字鍵。
var a = [1, 2, 3];
a.foo = true
for(let key in a){
console.log(key);
}
// 0 1 2 foo
遍歷數(shù)組一般可以使用 for
循環(huán)或者 while
循環(huán)。
也可以使用 forEach
7.6 數(shù)組的空位
當數(shù)組的某個位置是空元素,即兩個逗號之間沒有任何值,我們稱數(shù)組之間存在空位(hole)。
let a = [1,,3]
a.length // 3
空位不影響 length 屬性。
空位是可以讀取的,返回 undefined
var a = [,,,];
a[0] // undefined
使用 delete 刪除一個數(shù)組成員,會形成空位,并且不會影響 length 屬性。
var a = [1,2,3];
delete a[1];
a[1] // undefined
a.length // 3
delete 刪除了數(shù)組的第二個元素,這個位置就是空位,對 length
屬性沒有影響。
數(shù)組的某個位置是空位,與某個位置是 undefined
,是不一樣的。如果是空位,使用數(shù)組的 forEach
,for .. in , Object.keys 方法進行遍歷,空位都會被跳過。
var a = [,,,];
空位就是數(shù)組沒有這個元素,所以不會遍歷到,而 undefined
則表示數(shù)組有這個元素,值是 undefined
,所以遍歷不會跳過。
7.7 類似數(shù)組的對象
如果一個對象的所有鍵名都是正整數(shù)或0,并且有 length 屬性,那么這個對象就很像數(shù)組,語法上稱為 “類似數(shù)組的對象”。
var obj = {
0: 'a',
1: 'b',
2: 'c',
length: 3
}
obj[0] // 'a'
obj.length // 3
類似數(shù)組的對象,當然不能使用 push 方法了,當然不能使用 length 刪減元素了。
典型的 “類似數(shù)組的對象”是函數(shù)的arguments
對象,以及大多數(shù) DOM 元素集,還有字符串。
// arguments 對象
function args(){
return arguments
}
let arrayLike = args('a', 'b');
arrayLike[0] // 'a'
arrayLike.length // 2
arrayLike instanceof Array // false
// DOM 元素集
let ele = document.getElementsByTagName('h');
ele.length // 0
ele[0] // undefined
// 字符串
let str = "abdc"
str.length // 4
str instanceof Array // false
數(shù)組的 slice
方法可以將“類似數(shù)組的對象”變成真正的數(shù)組。
let arr = Array.prototype.slice.call(arrayLike)'
還有一個辦法,通過call
把數(shù)組的方法放到對象上面。
let arrLike = {
0: 1,
1:22,
2:33,
length:3,
}
function print(value, index){
console.log(index + ":" + value);
}
Array.prototype.forEach.call(arrayLike, print);
arrayLike 是個類數(shù)組的對象,本來不可以使用數(shù)組的 forEach() 方法的,但是通過 call(),可以把 forEach()
嫁接到 arrayLike 上面調(diào)用。
Python 也是有類似行為的,一個類如果加上某些特定的雙下劃線方法就可以使用一些 Python 方法。
字符串也是類似數(shù)組的對象,所以也可以使用 Array.prototype.forEach.call
遍歷。
Array.prototype.forEach.call('abc', function (chr){
console.log(chr);
});
// a b c
這種方法比直接調(diào)用數(shù)組原生的 forEach
要慢,所以最好還是將類似數(shù)組的對象轉為真正的數(shù)組,然后再調(diào)用數(shù)組的 forEach
方法。
let arr = Array.prototype.slice.call('abc');
arr.forEach((chr)=>{
console.log(chr);
});