三 數(shù)據(jù)類型

教程

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 的典型場景
  1. 變量聲明了,但是沒有賦值
var i;
i // undefined
  1. 調(diào)用函數(shù)時,應該提供的參數(shù)沒有提供,該參數(shù)等于 undefined; 這一點算得上是拙劣的設計了,如果是其他的語言會直接拋出異常。
function f(x){
  return x;
}
f() // undefined
  1. 對象沒有賦值的屬性
var o = new Object();
o.p // undefined
  1. 函數(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
  1. 小數(shù)點后面的零多于5個。
// 小數(shù)點后緊跟 5 個以上的零,
// 就自動轉化為科學計數(shù)法
0.0000003 // 3e-7

3.3 數(shù)值的進制

使用字面量(literal)直接表示一個數(shù)值時,JavaScript 對整數(shù)提供四種進制的表示方法:十進制、十六進制、八進制、二進制。

  • 十進制:沒有前導 0 的數(shù)值。
  • 八進制:有前綴 0o00的數(shù)值,或者有前導0、且只用到 0-7 的八個阿拉伯數(shù)值。
  • 十六進制:有前綴 0x0X的數(shù)值
  • 二進制:有前綴 0b0B 的數(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

InfinityNaN 比較,總是返回 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

如果字符串以 0x0X 開頭,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,這一點要特別引起注意。也就是說,isNaNtrue 的值,有可能不是 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 / NaNundefined 這個值會返回 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: 制表符
  • \ : 反斜杠
  • ' : 單引號
  • " : 雙引號

反斜杠還有三種特殊用法。

  1. \HHH
    反斜杠后面緊跟三個八進制數(shù)(000377),代表一個字符。HHH 對應該字符的 Unicode 碼點,比如 \251 表示版權符號。顯然,這種方法只能輸出 256 種字符。
  2. \xHH
    \x 后面緊跟兩個十六進制數(shù)(00FF),代表一個字符。HH 對應該字符的 Unicode 碼點,比如 \xA9 表示版權符號。
  3. \uXXXX
    \u 后面緊跟四個十六進制數(shù)(0000FFFF),代表一個字符。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+0000U+FFFF之間的字符,長度為16位(2個字節(jié));對于碼點在 U+10000U+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ù)組專有的方法(比如 sliceforEach),不能在 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

上面的代碼中,變量 meval 的別名。靜態(tài)代碼分析階段,引擎分辨不出m('var x=1 ') 執(zhí)行的 eval 命令。
為了保證 eval 的別名不影響代碼優(yōu)化,JS 標準規(guī)定,凡是使用別名執(zhí)行evaleval 內(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);
});
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

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

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,185評論 0 13
  • 1、標識符 標識符是指變量、函數(shù)、屬性的名字,或函數(shù)的參數(shù)。 格式規(guī)則: 第一個字符必須是一個字母、下劃線(_)或...
    霜天曉閱讀 708評論 0 0
  • 第3章 基本概念 3.1 語法 3.2 關鍵字和保留字 3.3 變量 3.4 數(shù)據(jù)類型 5種簡單數(shù)據(jù)類型:Unde...
    RickCole閱讀 5,144評論 0 21
  • 本章內(nèi)容 語法 數(shù)據(jù)類型 流控制語句 理解函數(shù) 3.1 語法 3.1.1 區(qū)分大小寫 區(qū)分大小寫 3.1.2 標識...
    悶油瓶小張閱讀 730評論 0 0
  • 晚上看了《一站到底》的年度總決賽,正常是周一晚上播的。雖然看標題就知道最后是誰奪冠了,但看的過程依然緊張到肌肉繃緊...
    西楚黎明閱讀 398評論 0 0