《JavaScript語言入門教程》記錄整理:運算符、語法和標準庫
- 2020 年 8 月 4 日
- 筆記
- javascript, JavaScript入門教程, js原生對象, js操作符, js語法
本系列基於阮一峰老師的《JavaScrip語言入門教程》或《JavaScript教程》記錄整理,教程採用知識共享 署名-相同方式共享 3.0協議。這幾乎是學習js最好的教程之一(去掉之一都不過分)
最好的教程而阮一峰老師又採用開源方式共享出來,之所以重新記錄一遍,一是強迫自己重新認真讀一遍學一遍;二是對其中知識點有個自己的記錄,加深自己的理解;三是感謝這麼好的教程,希望更多人閱讀了解
運算符
算數運算符
- js提供了10種運算符
- 加法運算符:
x + y
- 減法運算符:
x - y
- 乘法運算符:
x * y
- 除法運算符:
x / y
- 指數運算符:
x ** y
- 餘數運算符:
x % y
- 自增運算符:
++x
或者x++
- 自減運算符:
--x
或者x--
- 數值運算符:
+x
- 負數值運算符:
-x
- js中非數值可以相加,比如布爾值與數值相加,字元串相加用於連接兩個字元串
true + true // 2
1 + true // 2
1 + 'a' // "1a"
false + 'a' // "falsea"
加法運算符是在運行時決定,到底是執行相加,還是執行連接。運運算元的不同,導致了不同的語法行為,這種現象稱為「重載」(overload)。
'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"
加法運算符存在重載。減法、除法和乘法等運算符不會重載:所有運運算元一律轉為數值,再進行相應的數學運算。
- 對象的相加:運運算元是對象時,會先轉成原始類型的值,然後再相加。
對象默認轉成原始類型的值是[object Object]
var obj = { p: 1 };
obj+5 // "[object Object]5"
對象轉成原始類型的值,規則:
- 自動調用對象的
valueOf
方法。對象的valueOf
方法默認返回對象自身 - 再調用
toString
方法轉為字元串。對象的toString
方法默認返回[object Object]
自定義valueOf
方法或toString
方法(同時改寫兩個方法時要小心),改變對象相加的結果
obj.valueOf() // {p: 1}
obj.valueOf().toString() // "[object Object]"
obj.valueOf=function () {
return 1;
}
obj+5 // 6
唯一的特例是,當運運算元是Date
對象時,會優先執行toString
方法
var obj = new Date();
obj.valueOf = function () { return 1 };
obj.toString = function () { return 'hello' };
obj + 5 // "hello5"
- 餘數運算符(
%
)返回前一個運運算元被後一個運運算元除所得的餘數。結果的正負號由第一個運運算元決定
-1 % 2 // -1
1 % -2 // 1
可以使用絕對值,獲得負數的正確餘數值
// 正確的寫法
function isOdd(n) {
return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false
- 自增和自減運算符是一元運算符,只有一個運運算元。
運算之後,變數的值發生變化,這種效應叫做運算的副作用(
side effect
)。自增和自減運算符是僅有的兩個具有副作用的運算符,其他運算符都不會改變變數的值。
自增/自減放在變數後面,會先返回變數操作前的值,再進行自增/自減操作
自增/自減放在變數之前,會先進行自增/自減操作,再返回變數操作後的值
- 數值運算符(
+
)的作用可以將任何值轉為數值(與Number函數作用相同)
負數值運算符(-
),將一個值轉為數值的負值
不會改變原始變數的值,而是返回新值
- 指數運算符(
**
)完成指數運算
指數運算符是右結合,而不是左結合。即多個指數運算符連用時,先進行最右邊的計算。
// 相當於 2 ** (3 ** 2)
2 ** 3 ** 2 // 512
- 賦值運算符(
Assignment Operators
)用於給變數賦值。還有複合的賦值運算符,如x += y
、x -= y
比較運算符
- 比較運算符比較兩個值的大小,並返回一個布爾值。js提供了8個比較運算符
>
大於運算符<
小於運算符<=
小於或等於運算符>=
大於或等於運算符==
相等運算符===
嚴格相等運算符!=
不相等運算符!==
嚴格不相等運算符
- 相等比較和非相等比較。
對於非相等的比較,演算法是先看兩個運運算元是否都是字元串,如果是的,就按照字典順序比較(實際上是比較 Unicode 碼點);否則,將兩個運運算元都轉成數值,再比較數值的大小。
- 相等運算符(
==
)比較兩個值是否相等,嚴格相等運算符(===
)比較兩個值是否為「同一個值」。
如果兩個值不是同一類型,嚴格相等運算符
===
直接返回false,而相等運算符==
會將它們轉換成同一個類型,再進行比較
- 嚴格相等運算符:類型不同返回false;同一類型的原始類型值,會比較兩者的值是否相等;複合類型的值(對象、數組、函數)比較的是是否指向同一個地址;undefined和null與自身嚴格相等
兩個對象的比較,嚴格相等運算符比較的是地址,而大於或小於運算符比較的是值
var obj1 = {};
var obj2 = {};
obj1 > obj2 // 比較的是值 false
obj1 < obj2 // 比較的是值 false
obj1 === obj2 // 比較的是地址 false
相等運算符比較是隱含了類型轉換,建議最好只使用嚴格相等運算符(===
)。
布爾運算符
- 布爾運算符用於將表達式轉為布爾值。一共有4個
- 取反運算符:
!
- 且運算符:
&&
- 或運算符:
||
- 三元運算符:
?:
- 取反運算符將布爾值變為相反值。兩次取反就是將一個值轉為布爾值的簡便寫法
- 且運算符
&&
常用於多個表達式的求值
且運算符
&&
運算規則是:如果第一個運運算元的布爾值為true,則返回第二個運運算元的值(注意是值,不是布爾值);如果第一個運運算元的布爾值為false,則直接返回第一個運運算元的值,且不再對第二個運運算元求值。
&&
且運算可以用來取代if
語句
if (i) {
doSomething();
}
// 等價於
i && doSomething();
- 或運算符(
||
)也用於多個表達式的求值。
或運算符
||
的運算規則是:如果第一個運運算元的布爾值為true
,則返回第一個運運算元的值,且不再對第二個運運算元求值;如果第一個運運算元的布爾值為false
,則返回第二個運運算元的值。
或運算符常用於為一個變數設置默認值。
function saveText(text) {
text = text || '';
// ...
}
// 或者寫成
saveText(this.text || '')
- 且運算符和或運算符,這種通過第一個表達式(運運算元)的值,控制是否運行第二個表達式(運運算元)的機制,就稱為「短路」(
short-cut
) - 三元條件運算符(
?:
)是js中唯一一個需要三個運運算元的運算符
二進位位運算符
- 二進位位運算符用於直接對二進位位進行計算,一共有7個:
- 二進位或運算符(or):符號為
|
,表示若兩個二進位位都為0,則結果為0,否則為1。 - 二進位與運算符(and):符號為
&
,表示若兩個二進位位都為1,則結果為1,否則為0。 - 二進位否運算符(not):符號為
~
,表示對一個二進位位取反。 - 異或運算符(xor):符號為
^
,表示若兩個二進位位不相同,則結果為1,否則為0。 - 左移運算符(left shift):符號為
<<
, - 右移運算符(right shift):符號為
>>
, - 頭部補零的右移運算符(zero filled right shift):符號為
>>>
,
- 位運算符只對整數起作用,如果一個運運算元不是整數,會自動轉為整數後再執行。雖然在JavaScript內部,數值都是以64位浮點數的形式儲存,但是做位運算的時候,是以32位帶符號的整數進行運算的,並且返回值也是一個32位帶符號的整數
利用這個特性,可以寫出一個函數,將任意數值轉為32位整數。
function toInt32(x) {
return x | 0;
}
- 位運算符可以用作設置對象屬性的開關。(開關作用有些抽象,但很精巧)
假定某個對象有四個開關,每個開關都是一個變數。那麼,可以設置一個四位的二進位數,它的每個位對應一個開關。A、B、C、D四個開關,每個開關佔有一個二進位位
var FLAG_A = 1; // 0001
var FLAG_B = 2; // 0010
var FLAG_C = 4; // 0100
var FLAG_D = 8; // 1000
- 用二進位與運算,檢查當前設置是否打開了指定開關
var flags = 5; // 二進位的0101
// 檢驗是否打開了開關C
if (flags & FLAG_C) { // 0101 & 0100 => 0100 => true
// ...
}
- 假設需要打開
A
、B
、D
三個開關,可以先構造一個掩碼變數,然後通過二進位或運算掩碼變數,可以確保打開這三個開關
var mask = FLAG_A | FLAG_B | FLAG_D;
// 0001 | 0010 | 1000 => 1011
flags = flags | mask; // 代表三個開關的二進位位都打開的變數
- 二進位與運算可以將當前設置中凡是與開關設置不一樣的項,全部關閉
flags = flags & mask;
- 異或運算可以切換(
toggle
)當前設置,即第一次執行可以得到當前設置的相反值,再執行一次又得到原來的值。
flags = flags ^ mask;
- 二進位否運算可以翻轉當前設置
flags = ~flags;
void和逗號運算符
- void運算符,執行一個表達式,然後不返回任何值,或者返回
undefined
void 0 // undefined
void(0) // undefined 推薦寫法
void運算符的優先順序很高,使用括弧避免錯誤
var x = 3;
void (x = 5) //undefined
x // 5
- void運算符的主要用途是瀏覽器的書籤工具(Bookmarklet),以及在超鏈接中插入程式碼防止網頁跳轉。
如下程式碼,點擊鏈接後先執行onclick
,然後返回false
,所以瀏覽器不會跳轉
<script>
function f() {
console.log('Hello World');
}
</script>
<a href="//example.com" onclick="f(); return false;">點擊</a>
void
運算符可以取代上面的寫法:
<a href="javascript: void(f())">文字</a>
或者,實現點擊鏈接提交表單,但不產生頁面跳轉
<a href="javascript: void(document.form.submit())">
提交
</a>
- 逗號運算符用於對兩個表達式求值,並返回後一個表達式的值。
'a', 'b' // "b"
var x = 0;
var y = (x++, 10);
x // 1
y // 10
用途是:在返回一個值之前,進行一些輔助操作。
var value = (console.log('Hi!'), true);
// Hi!
value // true
運算順序
- 運算符優先順序別(
Operator Precedence
)高的先執行 - 圓括弧
()
用來提高運算的優先順序(它的優先順序最高),即圓括弧中的表達式會第一個運算
圓括弧不是運算符,而是一種語法結構。它一共有兩種用法:一種是把表達式放在圓括弧之中,提升運算的優先順序;另一種是跟在函數的後面,作用是調用函數。
函數放在圓括弧中,會返回函數本身。圓括弧緊跟在函數的後面,表示調用函數。
圓括弧之中,只能放置表達式
- “左結合”(
left-to-right associativity
)運算符會先從左向右運算
“右結合”(right-to-left associativity
)運算符會先從右向左運算
js中賦值運算符(=
)、三元條件運算符(?:
)、指數運算符(**
)是”右結合”的
語法
數據類型的轉換
- JavaScript 是一種動態類型語言,變數的類型無法在編譯階段確定,必須在運行時才能知道。而同時js的變數類型又可以隨意改變,因此又屬於弱類型語言
- JS中的運算符對數據類型有要求。因此常常發生類型自動轉換
- 強制類型轉換主要指使用
Number()
、String()
和Boolean()
手動將任意類型的值,分別轉換成數字、字元串或者布爾值。 Number()
轉換為數值。比parseInt函數嚴格
- 轉換原始類型的值
// 數值:轉換後還是原來的值
Number(324) // 324
// 字元串:如果可以被解析為數值,則轉換為相應的數值
Number('324') // 324
// 字元串:如果不可以被解析為數值,返回 NaN
Number('324abc') // NaN
// 空字元串轉為0
Number('') // 0
// 布爾值:true 轉成 1,false 轉成 0
Number(true) // 1
Number(false) // 0
// undefined:轉成 NaN
Number(undefined) // NaN
// null:轉成0
Number(null) // 0
// 忽略前後空格
Number('\t\v\r12.34\n') // 12.34
- 轉換對象時,規則如下:
第一步,調用對象自身的valueOf
方法。如果返回原始類型的值,則直接對該值使用Number函數,不再進行後續步驟。
第二步,如果valueOf方法返回的還是對象,則改為調用對象自身的toString方法。如果toString方法返回原始類型的值,則對該值使用Number函數,不再進行後續步驟。
第三步,如果toString方法返回的是對象,就報錯。
自定義valueOf
或toString
Number({
valueOf: function () {
return 2;
}
})
// 2
Number({
toString: function () {
return 3;
}
})
// 3
Number({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// 2
String()
轉換字元串的規則如下:
-
原始類型的值
數值:相應的字元串。
字元串:原來的值。
布爾值:true-“true”,false-“false”。
undefined:”undefined”。
null:”null”。 -
對象
String
參數如果是對象,返回一個類型字元串;如果是數組,返回該數組的字元串形式。
String({a: 1}) // "[object Object]"
String([1, 2, 3]) // "1,2,3"
轉換規則如下:
第一步,先調用對象自身的toString方法。如果返回原始類型的值,則對該值使用String函數,不再進行以下步驟。
第二步,如果toString方法返回的是對象,再調用原對象的valueOf方法。如果valueOf方法返回原始類型的值,則對該值使用String函數,不再進行以下步驟。
第三步,如果valueOf方法返回的是對象,就報錯。
String({
toString: function () {
return 3;
}
})
// "3"
String({
valueOf: function () {
return 2;
}
})
// "[object Object]"
String({
valueOf: function () {
return 2;
},
toString: function () {
return 3;
}
})
// "3"
Boolean()
轉換為布爾值,規則簡單,除了下面6個值結果為false,其餘全部為true
undefined
、null
、0
(包含-0
和+0
)、NaN
、''
(空字元串)和false
所有對象(包括空對象)的轉換結果都是true
,包括false
對應的布爾對象new Boolean(false)
也是true
- js中數據類型自動轉換髮生的情況:一、不同類型的數據相互運算時會自動轉換。二、對非布爾值類型的數據求布爾值時。三、對非數值類型的值使用一元運算符(即
+
和-
)。轉換時的規則是:預期什麼類型的值,就調用該類型的轉換函數。如果該位置既可以是字元串,又可以是數值,則默認轉為數值 - JavaScript在預期為布爾值的地方(比如if語句的條件部分),會將非布爾值的參數自動轉換為布爾值。系統內部會自動調用
Boolean
函數。
如下兩個方法將一個表達式轉為布爾值
// 寫法一
expression ? true : false
// 寫法二
!! expression
- 除了加法運算符(
+
)有可能把運運算元轉為字元串,其他運算符都會把運運算元自動轉成數值。
null
數值0
,undefined
數值NaN
錯誤處理機制
- JavaScript原生提供
Error
構造函數,所有拋出的錯誤都是這個構造函數的實例。當發生錯誤時,js引擎拋出Error
實例對象以後,整個程式就中斷在發生錯誤的地方,不再往下執行。
var err = new Error('出錯了');
err.message // "出錯了"
Error
實例的屬性:
message
:錯誤提示資訊name
:錯誤名稱(非標準屬性)stack
:錯誤的堆棧(非標準屬性)
function throwit() {
throw new Error('');
}
function catchit() {
try {
throwit();
} catch(e) {
console.log(e.stack); // print stack trace
}
}
catchit()
// Error
// at throwit (<anonymous>:2:9)
// at catchit (<anonymous>:7:5)
// at <anonymous>:1:1
Error
實例是最一般的錯誤類型,js還提供Error
的6個派生對象
SyntaxError
對象:解析程式碼時發生的語法錯誤ReferenceError
對象:引用一個不存在的變數時發生的錯誤。RangeError
對象:一個值超出有效範圍時發生的錯誤。TypeError
對象:變數或參數不是預期類型時發生的錯誤。URIError
對象:URI
相關函數的參數不正確時拋出的錯誤。主要encodeURI()
、decodeURI()
、encodeURIComponent()
、decodeURIComponent()
、escape()
和unescape()
。EvalError
對象:已不再使用
- 自定義錯誤
function UserError(message) {
this.message = message || '默認資訊';
this.name = 'UserError';
}
UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
throw
語句:手動中斷程式執行,拋出一個錯誤。
if (true) {
throw new Error('x 必須為正數');
}
// Uncaught Error: x 必須為正數
// at <anonymous>:2:9
throw
可以拋出任何類型的值
try...catch
結構用於對錯誤進行處理,選擇是否往下執行。catch
程式碼塊捕獲錯誤後,程式不會中斷。
try {
throw new Error('出錯了!');
} catch (e) {
console.log(e.name + ": " + e.message);
console.log(e.stack);
}
// Error: 出錯了!
// at <anonymous>:3:9
// ...
catch
程式碼塊中加入判斷語句,捕獲不同類型的錯誤
try {
foo.bar();
} catch (e) {
if (e instanceof SyntaxError) {
console.log(e.name + ": " + e.message);
} else if (e instanceof RangeError) {
console.log(e.name + ": " + e.message);
}
// ...
}
try...catch...finally
結構中的finally
程式碼塊,不管是否出現錯誤,都會在最後執行。
function cleansUp() {
try {
throw new Error('出錯了……');
console.log('此行不會執行');
} finally {
console.log('完成清理工作');
}
}
finally
程式碼塊前面即使有return
返回語句,依舊會執行完再返回。
function idle(x) {
try {
console.log(x);
return 'result';
} finally {
console.log('FINALLY');
}
}
idle('hello')
// hello
// FINALLY
如下說明:return
語句的執行在finally
程式碼之前,只是等到finally
執行完最終才返回
var count = 0;
function countUp() {
try {
return count;
} finally {
count++;
}
}
countUp()
// 0
count
// 1
finally
程式碼塊的典型場景
openFile();
try {
writeFile(Data);
} catch(e) {
handleError(e);
} finally {
closeFile();
}
編程風格
- “編程風格”(
programming style
)指的是編寫程式碼的樣式規則。
你選擇的,不是你喜歡的風格,而是一種能夠清晰表達你的意圖的風格 - 編程風格主要考慮的幾點:縮進(
indent
)、區塊(block
)、圓括弧(parentheses
)、行尾的分號、變數聲明、嚴格相等、語句的合併書寫等 - 使用
{}
程式碼塊時,js中要使用左大括弧{
緊挨著語句在同一行中,不要換行寫。這是因為JavaScript會自動添加句末的分號,從而產生一些難以察覺的錯誤。
block {
// ...
}
如下return
語句其實會變成兩句,從而導致出問題
return
{
key: value
};
// 相當於
return;
{
key: value
};
// 正確寫法
return {
key : value
};
- 行尾的分號:分號表示一條語句的結束。js允許省略。
有三種情況,語法規定不需要在結尾添加分號。如果添加,js引擎將分號解釋為空語句
-
for
和while
循環
for ( ; ; ) {
} // 沒有分號
while (true) {
} // 沒有分號
但是do...while
要有分號
-
- 分支語句:
if
,switch
,try
- 分支語句:
if (true) {
} // 沒有分號
switch () {
} // 沒有分號
try {
} catch {
} // 沒有分號
- 函數的聲明語句
function f() {
} // 沒有分號
函數表達式仍要使用分號
var f = function f() {
};
除了這三種情況,所有語句都應該使用分號。
在沒有分號時JavaScript會自動添加,這種語法特性叫”分號的自動添加”(Automatic Semicolon Insertion
,簡稱ASI
)
但是,如果下一行的開始可以與本行的結尾連在一起解釋,JavaScript就不會自動添加分號。
而是否自動添加分號無法預測,很有可能導致額外的錯誤。
一行的起首”自增”(++)或”自減”(–),則前面會自動添加分號
不應該省略結尾的分號,還有一個原因。有些JavaScript程式碼壓縮器(
uglifier
)不會自動添加分號,因此遇到沒有分號的結尾,就會讓程式碼保持原狀,而不是壓縮成一行,使得壓縮無法得到最優的結果。另外,不寫結尾的分號,可能會導致腳本合併出錯。所以,有的程式碼庫在第一行語句開始前,會加上一個分號。可以避免與其他腳本合併時,前面的腳本最後一行語句沒有分號,導致運行出錯的問題。
;var a = 1; // ...
- 避免全局變數的使用,如果必須使用,考慮大寫字母表示
- 變數聲明,由於存在變數提升,許多語句會導致產生全局變數(比如
for
循環中)。
所有函數都應該在使用之前定義。函數內部的變數聲明,都應該放在函數的頭部。
- 建議只使用嚴格相等運算符(
===
) switch...case
結構可以用對象結構代替
switch...case
結構類似於goto
語句,容易造成程式流程的混亂,使得程式碼結構混亂不堪,不符合面向對象編程的原則。
console對象和控制台
console
對象是JavaScript的原生對象,可以輸出各種資訊到控制台console
的常見用途:調試程式,顯示網頁程式碼運行時的錯誤資訊;提供了一個命令行介面,用來與網頁程式碼互動。- 開發者工具的幾個面板。
Elements
:查看網頁的 HTML 源碼和 CSS 程式碼。Resources
:查看網頁載入的各種資源文件(比如程式碼文件、字體文件 CSS 文件等),以及在硬碟上創建的各種內容(比如本地快取、Cookie、Local Storage等)。Network
:查看網頁的 HTTP 通訊情況。Sources
:查看網頁載入的腳本源碼,可進行斷點debug。Timeline
:查看各種網頁行為隨時間變化的情況。Performance
:查看網頁的性能情況,比如 CPU 和記憶體消耗。Console
:即控制台,用來運行js命令,和頁面中js程式碼console方法的輸出。
console.log()
,console.info()
,console.debug()
console.warn(),console.error()
console.table()
console.count()
debugger
語句主要用於除錯,作用是設置斷點。
標準庫
下面基本都是js原生對象的介紹,裡面許多屬性和方法僅了解一下即可,有需要時再查詢使用
Object對象
- JavaScript原生提供
Object
對象 - JavaScript的所有其他對象都繼承自
Object
對象,都是Object
的實例。 Object
對象的原生方法分成兩類:Object
本身的方法(“靜態方法”)與Object
的實例方法。
Object
對象本身的方法:直接定義在Object
對象上的方法Object
的實例方法:定義在Object
原型對象Object.prototype
上的方法。它可以被Object
實例直接使用。
// 本身的方法
Object.selfPrint = function (o) { console.log(o) };
// 實例方法
Object.prototype.print = function () {
console.log(this);
};
var obj = new Object();
obj.print() // Object
Object
本身是一個函數,可以當作工具方法使用,將任意值轉為對象。保證某個值一定是對象。Object
方法無參數或為undefined
、null
,返回一個空對象
var obj = Object();
// 等同於
var obj = Object(undefined);
var obj = Object(null);
obj instanceof Object // true
參數是原始類型,將原始類型的值轉換為對應的包裝對象的實例
參數是一個對象,則返回該對象(不進行轉換)
var arr = [];
var obj = Object(arr); // 返回原數組
obj === arr // true
var value = {};
var obj = Object(value) // 返回原對象
obj === value // true
var fn = function () {};
var obj = Object(fn); // 返回原函數
obj === fn // true
- 判斷變數是否為對象
function isObject(value) {
return value === Object(value);
}
isObject([]) // true
isObject(true) // false
instanceof
運算符驗證一個對象是否為指定的構造函數的實例Object
構造函數用來生成新對象
var obj = new Object();
// 等價於
var obj = {};
Object
構造函數與工具方法類似。如果參數是一個對象,則直接返回該對象;如果是一個原始類型的值,則返回該值對應的包裝對象- Object 的靜態方法
Object.keys()
,Object.getOwnPropertyNames()
遍歷對象的屬性。兩者都返回對象自身的(而不是繼承的)所有屬性名組成的數組。Object.keys
方法只返回可枚舉的屬性;Object.getOwnPropertyNames
還返回不可枚舉的屬性名。
通常使用Object.keys
遍歷對象屬性
計算對象屬性的個數
var obj = {
p1: 123,
p2: 456
};
Object.keys(obj).length // 2
Object.getOwnPropertyNames(obj).length // 2
Object
實例對象的方法:
Object.prototype.valueOf()
:返回當前對象對應的值,默認情況下返回對象本身。Object.prototype.toString()
:返回當前對象對應的字元串形式,默認返回類型字元串。Object.prototype.toLocaleString()
:返回當前對象對應的本地字元串形式。Object.prototype.hasOwnProperty()
:判斷某個屬性是否為當前對象自身的屬性,還是繼承自原型對象的屬性。Object.prototype.isPrototypeOf()
:判斷當前對象是否為另一個對象的原型。Object.prototype.propertyIsEnumerable()
:判斷某個屬性是否可枚舉。
- 數組、字元串、函數、Date對象都自定義了
toString
方法,覆蓋了Object.prototype.toString
方法。
[1, 2, 3].toString() // "1,2,3"
'123'.toString() // "123"
(function () {
return 123;
}).toString()
// "function () {
// return 123;
// }"
(new Date()).toString()
// "Fri Jul 31 2020 21:24:16 GMT+0800 (中國標準時間)"
- 判斷數據類型
關於如何正確的判斷數據類型,由於typeof
僅能準確返回數值、字元串、布爾值、undefined的類型,其他返回object。所以無法藉助它準確判斷類型;而instanceof
對於繼承的對象,除了判斷當前對象實例時返回true
,判斷繼承的上級對象實例時也會返回true
,並且只能判斷是否是某個對象的實例,無法判斷基本類型。
因此最準確的辦法是利用 Object.prototype.toString
方法返回對象的類型字元串 這一特點,判斷一個值的類型
如下,空對象的toString
方法,返回字元串object Object
,第二個Object
表示當前值的構造函數。
var obj = {};
obj.toString() // "[object Object]"
Object.prototype.toString.call(value) // 對value這個值調用Object.prototype.toString方法
Object.prototype.toString
可以確認一個值是什麼類型。如下,實現比typeof
運算符更準確的類型判斷函數
var type = function (o){
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
type({}); // "object"
type([]); // "array"
type(5); // "number"
type(null); // "null"
type(); // "undefined"
type(/abcd/); // "regex"
type(new Date()); // "date"
實現判斷某種類型的方法:
var type = function (o){
var s = Object.prototype.toString.call(o);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
['Null',
'Undefined',
'Object',
'Array',
'String',
'Number',
'Boolean',
'Function',
'RegExp'
].forEach(function (t) {
type['is' + t] = function (o) {
return type(o) === t.toLowerCase();
};
});
type.isObject({}) // true
type.isNumber(NaN) // true
type.isRegExp(/abc/) // true
toLocaleString()
用來實現自定義的本地字元串。如Array.prototype.toLocaleString()
、
Number.prototype.toLocaleString()
、Date.prototype.toLocaleString()
等對象自定義這個方法
屬性描述對象
- JS提供了叫做”屬性描述對象”(
attributes object
)的內部數據結構,用來描述對象的屬性,控制它的行為,比如該屬性是否可寫、可遍歷等。 - 每個屬性都有自己對應的屬性描述對象,保存該屬性的一些元資訊。
- 如下為屬性描述對象的例子:
{
value: 123, // 屬性的屬性值 默認undefined
writable: false, // 屬性值(value)是否可改變(可寫) 默認true
enumerable: true, // 屬性是否可遍歷,默認true
configurable: false,// 屬性的可配置性,默認true 控制屬性描述對象的可寫性
get: undefined, // get該屬性的取值函數(getter),默認undefined
set: undefined // set該屬性的存值函數(setter),默認undefined。
}
定義了取值函數
get
(或存值函數set
),就不能將writable
屬性設為true,或者同時定義value屬性,否則會報錯。
Object.getOwnPropertyDescriptor()
獲取屬性描述對象
var obj = { p: 'a' };
Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
// writable: true,
// enumerable: true,
// configurable: true
// }
Object.defineProperty()
通過屬性描述對象,定義或修改屬性,並返回修改後的對象。
Object.defineProperty(object, propertyName, attributesObject)
參數:
- object:屬性所在的對象
- propertyName:字元串,屬性名
- attributesObject:屬性描述對象
Object.defineProperties()
可以一次定義多個屬性
JSON.stringify
方法會排除enumerable
為false的屬性,有時可以利用這一點。如果對象的JSON
格式輸出要排除某些屬性,可以把這些屬性的enumerable
設為false。- 存取器(
accessor
,set-setter,get-getter)是另外定義屬性的方式,定義存取器後,將會執行對應的函數。
除了defineProperty
方法中通過屬性描述對象定義存取器,還提供如下的寫法(且這種寫法configurable
和enumerable
都為true,是可遍歷的屬性。更常用)
var obj = {
get p() {
return 'getter';
},
set p(value) {
console.log('setter: ' + value);
}
};
存取器常用於:屬性的值依賴對象內部數據的場合。
var obj ={
$n : 5,
get next() { return this.$n++ },
set next(n) {
if (n >= this.$n) this.$n = n;
else throw new Error('新的值必須大於等於當前值');
}
};
obj.next // 5
obj.next = 10;
obj.next // 10
obj.next = 5;
// Uncaught Error: 新的值必須大於當前值
- 對象的拷貝:
由於對象是引用類型,數據存放在堆中,棧中值存放對象的地址。默認值類型的賦值是複製給另一個變數;但引用類型的賦值是直接將引用地址複製給另一個變數,賦值引用就是常說的淺拷貝(淺拷貝的對象共用一個記憶體地址)。而深拷貝指的是將引用類型的數據也完全複製一份給新的變數。
對象深拷貝的基本原理就是:通過遍歷對象的屬性,然後將屬性和遞歸至不是對象的屬性值重新賦值為另一個對象,如果屬性值是對象,則遞歸執行當前函數。
- 方法一。 如下,缺點不能深拷貝
function
,對象存取器屬性拷貝出來的是一個值
var DeepCopy = function dc(obj) {
if (obj===null) {
return obj;
}
else if (typeof obj === 'object') {
if (obj instanceof Array) {
var newArr = [], i, len = obj.length;
for (i = 0; i < len; i++) {
newArr[i] = dc(obj[i]);
}
return newArr;
} else {
var newObj = {};
for (var name in obj) {
newObj[name] = dc(obj[name]);
}
return newObj;
}
}
// 'number' 'string' 'boolean' undefined null
return obj;
}
var objFunction=function(){
//
}
var obj0={
p1:1,
get p2(){
return this.p1;
},
p3:objFunction
}
var obj1=DeepCopy(obj0);
// {p1: 1, p2: 1, p3: ƒ}
// p1: 1
// p2: 1
// p3: ƒ ()
obj1.p3===obj0.p3 // true
- 方法二。使用
defineProperty
設定屬性描述器,完成拷貝屬性,可實現拷貝對象存取器屬性。但是此時複製的存取器屬性函數屬於淺拷貝
var DeepCopy = function dc(obj) {
if (obj===null) {
return obj;
}
else if(typeof obj === 'object'){
if (obj instanceof Array) {
var newArr = [], i, len = obj.length;
for (i = 0; i < len; i++) {
newArr[i] = dc(obj[i]);
}
return newArr;
} else {
var newObj = {};
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
Object.defineProperty(
newObj,
name,
Object.getOwnPropertyDescriptor(obj, name)
);
}
}
return newObj;
}
}
return obj;
}
- 方法三。如下,利用
new Function
構造函數實現函數function的深拷貝。這也是處理js深拷貝最全的方法了,
var DeepCopy = function dc(obj) {
if (obj===null) {
return obj;
}
else if (typeof obj === 'object') {
if (obj instanceof Array) {
var newArr = [], i, len = obj.length;
for (i = 0; i < len; i++) {
newArr[i] = dc(obj[i]);
}
return newArr;
} else {
var newObj = {};
for (var name in obj) {
if (obj.hasOwnProperty(name)) {
//newObj[name] = dc(obj[name]);
if(typeof obj[name] === 'function'){
newObj[name] = dc(obj[name]);
}
else{
Object.defineProperty(
newObj,
name,
Object.getOwnPropertyDescriptor(obj, name)
);
}
}
}
return newObj;
}
}
else if(typeof obj === 'function'){
// var funStr="var f="+obj.toString()+";"
// return new Function(funStr+"return f;");
return new Function("return "+obj+";");
}
return obj;
}
obj1=DeepCopy(obj0);
// {p1: 1, p3: ƒ}p1: 1p2: (...)p3: ƒ anonymous( )get p2: ƒ p2()__proto__: Object
obj1.p3===obj0.p3 // false
obj1.p2===obj0.p2 // true
-
方法四。對於存取器屬性函數的深拷貝,可以通過
getOwnPropertyDescriptor
獲取的屬性描述器對象,判斷其get和set屬性,完成其函數的深拷貝 -
方法五。還有一個簡便的方法,使用
JSON.stringfy()
或JSON.parse()
序列化為json字元串然後解析為js對象,實現一個對象的深拷貝。但是它存在一個致命的問題,就是自定義的函數無法拷貝(JSON.stringfy()
方法無法將函數值轉為json字元串。json無法表示函數類型)
var objFunction=function(){
//
}
var obj0={
p1:1,
get p2(){
return this.p1;
},
p3:objFunction,
p4:{
p5:5
}
}
var newObj = JSON.parse(JSON.stringify(obj0));
newObj
// {p1: 1, p2: 1, p4: {…}}
// p1: 1
// p2: 1
// p4:
// p5: 5
以上對象拷貝的都是可遍歷屬性,且可能改變不可寫的屬性為可寫。最最重要的是,新對象和舊對象的原型對象obj.prototype
各自獨立
ES6中實現對象複製的方式:比如Object.assign
(淺拷貝)、展開操作符…
(淺拷貝)
另:Array的slice
和concat
等方法不改變原數組,但是返回的也是淺拷貝了的新數組
另:$.extend
方法的第一個參數給bool值表示是否深拷貝:jQuery.extend( [deep ], target, object1 [, objectN ] )
- 控制對象狀態
Object.preventExtensions
方法:使一個對象無法再添加新的屬性Object.isExtensible
方法檢查一個對象是否使用了Object.preventExtensions
方法。檢查是否可以為一個對象添加屬性。Object.seal
方法使得一個對象既無法添加新屬性,也無法刪除舊屬性。Object.isSealed()
Object.freeze
方法使一個對象變成常量。無法添加新屬性、無法刪除舊屬性、也無法改變屬性的值。Object.isFrozen()
上面三個方法鎖定對象的可寫性有一個漏洞:可以通過改變原型對象,來為對象增加屬性。解決方案是原型也凍結住。另外一個局限是,如果屬性值是對象,這些方法只能凍結屬性指向的對象,而不能凍結對象本身的內容。
Array 對象
Array
是JavaScript的原生對象,也是一個構造函數,用來生成新數組。
var arr = new Array(2); // 等同於 var arr = Array(2);
arr.length // 2
arr // [ empty x 2 ]
Array()
構造函數有很大的缺陷,不同的參數生成的結果會不一樣。因此建議使用數組字面量的方式
// 不建議的方式
var arr = new Array(1, 2);
// 推薦
var arr = [1, 2];
Array.isArray()
靜態方法,判斷是否是數組
var arr = [1, 2, 3];
typeof arr // "object"
Array.isArray(arr) // true
- 數組對象的實例方法:
valueOf()
返回數組本身toString()
返回數組的字元串形式push()
在數組的末端添加一個或多個元素,返回添加後的數組長度——(在數組末尾壓入元素)。該方法改變原數組。pop()
刪除數組的最後一個元素,並返回該元素——(彈出最後一個元素)。該方法改變原數組。
push
和pop
結合使用,可構成”後進先出”的棧結構(stack
)。
var arr = [];
arr.push(1, 2);
arr.push(3);
arr.pop();
arr // [1, 2]
shift()
刪除數組的第一個元素,並返回該元素——(彈出第一個元素)。該方法改變原數組。
shift()
方法可以遍歷並清空一個數組。
var list = [1, 2, 3, 4];
while (list.length) {
console.log(list.shift());
}
list // []
push()
和shift()
結合使用,就構成了”先進先出”的隊列結構(queue
)。
unshift()
在數組的第一個位置添加元素,並返回添加後的數組長度——(數組頭部壓入一個元素)。該方法會改變原數組。
var arr = [ 'c', 'd' ];
arr.unshift('a', 'b') // 4
arr // [ 'a', 'b', 'c', 'd' ]
join()
以指定參數作為分隔符,將所有數組成員連接為一個字元串返回。默認用逗號分隔。
var a = [1, 2, 3, 4,undefined, null];
a.join(' ') // '1 2 3 4 '
a.join(' | ') // "1 | 2 | 3 | 4 | | "
a.join() // "1,2,3,4,,"
undefined或null或空位被轉為空字元串
通過call
方法,join也可以用於字元串或類似數組的對象
concat()
用於多個數組的合併。將新數組的成員,添加到原數組成員的後部,並返回一個新數組,原數組不變。
['hello'].concat(['world'])
// ["hello", "world"]
['hello'].concat(['world'], ['!'])
// ["hello", "world", "!"]
[].concat({a: 1}, {b: 2})
// [{ a: 1 }, { b: 2 }]
concat連接的數組中有對象時,返回的淺拷貝
reverse()
翻轉數組,用於顛倒排列數組元素,返回改變後的數組。該方法將改變原數組。slice()
用於提取數組的一部分,返回一個新數組。原數組不變。
左閉右開,返回結果不包含end位置的元素。
arr.slice(start, end);
省略第二個參數,會一直返回數組最後的成員;或省略全部參數,返回元素組;第一個參數大於等於數組長度,或者第二個參數小於第一個參數,則返回空數組。
slice()
一個重要應用,是將類似數組的對象轉為真正的數組。
splice()
用於刪除原數組的一部分成員,並可以在刪除的位置添加新的數組成員,返回值是被刪除的元素。該方法會改變原數組。
參數為起始位置、刪除的元素個數,添加到刪除位置的新元素
arr.splice(start, count, addElement1, addElement2, ...);
第二個參數設為0,可實現插入元素
var a = [1, 1, 1];
a.splice(1, 0, 2) // []
a // [1, 2, 1, 1]
只提供第一個參數,將”剪切”到數組末尾
sort()
對數組成員進行排序,默認按照字典順序排序。原數組將被改變。
['d', 'c', 'b', 'a'].sort()
// ['a', 'b', 'c', 'd']
[4, 3, 2, 1].sort()
// [1, 2, 3, 4]
[11, 101].sort()
// [101, 11]
[10111, 1101, 111].sort()
// [10111, 1101, 111]
通過傳入一個函數,可以讓sort方法按照自定義方式排序
[10111, 1101, 111].sort(function (a, b) {
return a - b;
})
// [111, 1101, 10111]
[
{ name: "張三", age: 30 },
{ name: "李四", age: 24 },
{ name: "王五", age: 28 }
].sort(function (o1, o2) {
return o1.age - o2.age;
})
// [
// { name: "李四", age: 24 },
// { name: "王五", age: 28 },
// { name: "張三", age: 30 }
// ]
sort
參數函數接受兩個參數,表示進行比較的兩個數組成員。如果函數的返回值大於0,表示第一個成員排在第二個成員後面;如果函數的返回值小於等於0,則第一個元素排在第二個元素前面。
自定義的排序函數應該返回數值
map()
將數組的所有成員依次傳入參數函數,然後把每一次的執行結果組成一個新數組返回。元素組不變
var numbers = [1, 2, 3];
numbers.map(function (n) {
return n + 1;
});
// [2, 3, 4]
numbers // [1, 2, 3]
map
參數函數的三個參數:當前成員、當前位置和數組本身。
[1, 2, 3].map(function(elem, index, arr) {
return elem * index;
});
// [0, 2, 6]
map
的第二個參數,用來綁定回調函數內部的this
變數
forEach
與map
相似,對數組的所有成員依次執行參數函數,但不返回值。
如果數組遍歷是為了得到返回值,可以使用map方法,否則使用forEach方法。
forEach方法無法中斷執行。如果想要中斷,可使用for循環、或some
、every
方法。
some()
,every()
方法類似”斷言”(assert
),返回布爾值,表示數組成員是否符合某種條件
some方法是只要一個成員的返回值是true,則整個some方法的返回值就是true,否則返回false。
every方法是所有成員的返回值都是true,整個every方法才返回true,否則返回false。
藉助這一點,可以循環執行數組每個元素時,some方法的參數函數中判斷某個條件然後返回true,every方法的參數函數中判斷某個條件然後返回false,即可起到類似for循環中break中斷的作用;
var arr = [1, 2, 3, 4, 5];
arr.some(function (elem, index, arr) {
console.log(elem); //執行操作
return elem >= 3;
});
// 1
// 2
// 3
// true
arr.every(function (elem, index, arr) {
if(elem<=3){
console.log(elem);
return true;
}
else{
return false;
}
});
// 1
// 2
// 3
// false
對於空數組,some方法返回false,every方法返回true,回調函數都不會執行
filter()
過濾數組成員,滿足條件的成員組成一個新數組返回——即filter的參數函數返回true的成員保留下來組成新數組。不會改變原數組。
參數函數的三個參數:當前成員,當前位置和整個數組。
[1, 2, 3, 4, 5].filter(function (elem) {
return (elem > 3);
})
[1, 2, 3, 4, 5].filter(function (elem, index, arr) {
return index % 2 === 0;
});
reduce()
、reduceRight()
依次處理數組的每個成員,最終累計為一個值。處理的是上一次累計值和當前元素執行結果的累計值。區別是,reduce
是從左到右處理(從第一個成員到最後一個成員),reduceRight
則是從右到左(從最後一個成員到第一個成員)。
[1, 2, 3, 4, 5].reduce(function (a, b) {
console.log("上一次的累計值:"+a, "當前值:"+b);
return a + b;
})
// 上一次的累計值:1 當前值:2
// 上一次的累計值:3 當前值:3
// 上一次的累計值:6 當前值:4
// 上一次的累計值:10 當前值:5
// 15
第一次執行時,累計值a是數組的第一個元素,之後就是累計值和元素值
其參數函數可接受四個變數:累積變數,默認為數組的第一個成員;當前變數,默認為數組的第二個成員;當前位置(從0開始);原數組。前兩個必須
reduce
和reduceRight
的第二個參數可指定執行時的初始值
[1, 2, 3, 4, 5].reduce(function (a, b) {
console.log("上一次的累計值:"+a, "當前值:"+b);
return a + b;
},10)
// 上一次的累計值:10 當前值:1
// 上一次的累計值:11 當前值:2
// 上一次的累計值:13 當前值:3
// 上一次的累計值:16 當前值:4
// 上一次的累計值:20 當前值:5
// 25
空數組執行reduce
或reduceRight
時會報錯,可指定第二個參數初始值解決
藉助reduce
(或reduceRight
)可以實現一些遍歷操作,比如找出字元長度最大的數組元素
function findLongest(entries) {
return entries.reduce(function (longest, entry) {
return entry.length > longest.length ? entry : longest;
}, '');
}
findLongest(['aaa', 'bb', 'c']) // "aaa"
indexOf()
返回給定元素在數組中第一次出現的位置,沒有則返回-1
。第二個參數表示搜索開始的位置lastIndexOf()
返回給定元素在數組中最後一次出現的位置,沒有則返回-1
。
- 鏈式調用,如果數組方法返回的還是數組,就可以接著調用數組方法,實現鏈式調用
var users = [
{name: 'tom', email: '[email protected]'},
{name: 'peter', email: '[email protected]'}
];
users.map(function (user) {
return user.email;
})
.filter(function (email) {
return /^t/.test(email);
})
.forEach(function (email) {
console.log(email);
});
// "[email protected]"
包裝對象
- js的三種原始類型的值——數值、字元串、布爾值——在一定條件下會自動轉為對象,這就是原始類型的”包裝對象”(
wrapper
) - “包裝對象”指的是與數值、字元串、布爾值分別相對應的
Number
、String
、Boolean
三個原生對象。這三個原生對象可以把原始類型的值變成(包裝成)對象。
var v1 = new Number(123);
var v2 = new String('abc');
var v3 = new Boolean(true);
typeof v1 // "object"
typeof v2 // "object"
typeof v3 // "object"
v1 === 123 // false
v2 === 'abc' // false
v3 === true // false
- 包裝對象的設計目的:首先,使得”對象”這種類型可以覆蓋JavaScript所有的值,整門語言有一個通用的數據模型。其次,使得原始類型的值也有辦法調用自己的方法。
Number
、String
和Boolean
作為普通函數調用時用以類型轉換,將任意類型的值轉為數值、字元串和布爾值等原始類型的值;作為構造函數使用(帶有new
)時,將原始類型的值轉為對象- 包裝對象繼承了
Object
對象的valueOf()
——返回包裝對象實例對應的原始類型的值、toString()
——返回對應的字元串形式方法 - 原始類型與實例對象的自動轉換:有時,原始類型的值會自動當作包裝對象調用,即調用包裝對象的屬性和方法。JavaScript 引擎會自動將原始類型的值轉為包裝對象實例,並在使用後立刻銷毀實例。
比如字元串調用length
屬性:
'abc'.length // 3
abc
是一個字元串,本身不是對象,不能調用length
屬性。JavaScript引擎自動將其轉為包裝對象,在這個對象上調用length
屬性。調用結束後,這個臨時對象就會被銷毀。這就叫原始類型與實例對象的自動轉換。
自動轉換生成的包裝對象是只讀的,無法修改。所以,字元串無法添加新屬性。同時調用結束後,包裝實例會自動銷毀,所以每次調用其實都是一個新的包裝對象。
var s = 'Hello World';
s.x = 123;
s.x // undefined
如果要為字元串添加屬性,只有在它的原型對象String.prototype
上定義
- 可以在包裝對象的原型對象
prototype
上添加自定義方法或屬性
Boolean對象
- 通過
valueOf()
獲取包裝對象對應的原始類型值
new Boolean(false).valueOf()
Number對象
- Number對象的靜態屬性:
Number.POSITIVE_INFINITY
:正的無限,指向Infinity
。Number.NEGATIVE_INFINITY
:負的無限,指向-Infinity
。Number.NaN
:表示非數值,指向NaN
Number.MIN_VALUE
:表示最小正數(即最接近0的正數,在64位浮點數體系中為5e-324),相應的,最接近0的負數為-Number.MIN_VALUE
。Number.MAX_VALUE
:表示最大正數Number.MAX_SAFE_INTEGER
:表示能夠精確表示的最大整數,即9007199254740991
。Number.MIN_SAFE_INTEGER
:表示能夠精確表示的最小整數,即-9007199254740991
。
- 實例方法
Number.prototype.toString()
,用於將一個數值轉為字元串形式。該方法可以接受一個參數,表示輸出的進位
(10).toString() // "10"
(10).toString(2) // "1010"
(10).toString(8) // "12"
(10).toString(16) // "a"
調用時,數值必須用括弧括起來,否則js引擎會把.
解讀為小數點,從而混淆。任何不至於誤讀的寫法都可以
10.toString(2)
// SyntaxError: Unexpected token ILLEGAL
10.5.toString() // "10.5"
10.5.toString(2) // "1010.1"
10.5.toString(8) // "12.4"
10.5.toString(16) // "a.8"
可使用方括弧調用
10['toString'](2) // "1010"
如果想將其他進位的數轉為十進位,使用parseInt
Number.prototype.toFixed()
將一個數轉為指定位數的小數,然後返回個這小數對應的字元串。
(10).toFixed(2) // "10.00"
10.005.toFixed(2) // "10.01"
由於浮點數的原因,js中小數5的四捨五入是不確定的,使用的時候必須小心。
-
Number.prototype.toExponential()
將一個數轉為科學計數法形式 -
Number.prototype.toLocaleString()
接受地區碼作為參數,返回當前數字在該地區的當地書寫形式。
(123).toLocaleString('zh-Hans-CN-u-nu-hanidec')
// "一二三"
toLocaleString()
第二個參數是配置對象,可以訂製返回的字元串。比如style屬性指定輸出樣式,默認值decimal
(十進位形式),還可取值percent
(百分比)、currency
(貨幣格式)
(123).toLocaleString('zh-Hans-CN', { style: 'percent' })
// "12,300%"
(123).toLocaleString('zh-Hans-CN', { style: 'currency', currency: 'CNY' })
// "¥123.00"
(123).toLocaleString('de-DE', { style: 'currency', currency: 'EUR' })
// "123,00 €"
(123).toLocaleString('en-US', { style: 'currency', currency: 'USD' })
// "$123.00"
Number.prototype
對象上可以自定義方法
Number.prototype.add = function (x) {
return this + x;
};
Number.prototype.subtract = function (x) {
return this - x;
};
(8).add(2).subtract(4) // 6
String對象
- 靜態方法
String.fromCharCode()
返回Unicode碼點組成的字元串
Unicode碼點不能大於0xFFFF
,碼點大於0xFFFF
的字元佔用四個位元組,而JavaScript默認支援的是兩個位元組的字元。比如0x20BB7
需要拆成兩個字元來寫
String.fromCharCode(0xD842, 0xDFB7) // "𠮷"
- 實例屬性
String.prototype.length
返回字元串長度 - 實例方法
String.prototype.charAt()
返回指定位置的字元。和字元串的數組下標等同String.prototype.concat()
連接兩個字元串slice()
用於從原字元串取出子字元串並返回,不改變原字元串。slice(start,end)
左閉右開。省略第二個參數會一直取到字元串結束substring()
從原字元串取出子字元串並返回,不改變原字元串,和slice
很相像。但substring()
第一個參數大於第二個參數時取得結果是更換兩者位置的結果,且負數會被轉為0,由於這些反直覺的行為,因此推薦使用slice
substr()
從原字元串取出子字元串並返回,不改變原字元串。第一個參數是字元串的開始位置,第二個參數是長度。省略第二個參數一直取到結尾;第二個參數為負值會被轉為0
'JavaScript'.substr(4, 6) // "Script"
'JavaScript'.substr(4) // "Script"
'JavaScript'.substr(-6) // "Script"
'JavaScript'.substr(4, -1) // ""
trim()
去除字元串兩端的空格,返回新字元串。不改變原字元串。indexOf()
返回一個字元串在另一個字元串中第一次出現的位置。不匹配返回-1。第二個參數表示查找的起始位置。lastIndexOf()
從尾部開始匹配toLowerCase()
將一個字元串全部轉為小寫,toUpperCase()
全部轉為大寫。均不改變原字元串match()
查找原字元串是否匹配某個子字元串,返回匹配的第一個字元串組成的數組。沒有匹配返回null。返回的數組包含index屬性(匹配字元串的開始位置)和input屬性(原始字元串)
var matches = 'cat, bat, sat, fat'.match('at');
matches.index // 1
matches.input // "cat, bat, sat, fat"
match()
參數可以是正則表達式
search()
返回匹配字元串的第一個位置。無匹配返回-1。參數可以是正則表達式replace()
替換匹配的子字元串,一般情況下只替換第一個匹配(除非使用帶有g
修飾符的正則表達式)
'aaa'.replace('a', 'b') // "baa"
split()
根據參數分割字元串,返回分割後子字元串組成的數組。分割參數為空字元串,則分割每個字元;如果省略參數,返回原字元串組成的數組
'a|b|c'.split('|') // ["a", "b", "c"]
'a|b|c'.split('') // ["a", "|", "b", "|", "c"]
'a|b|c'.split() // ["a|b|c"]
'a||c'.split('|') // ['a', '', 'c']
'|b|c'.split('|') // ["", "b", "c"]
'a|b|'.split('|') // ["a", "b", ""]
split第二個參數表示返回數組的(最大)長度
localeCompare()
比較兩個字元串
Math對象
Math
提供各種數學功能。該對象不是構造函數,不能生成實例,必須在Math對象上調用屬性和方法。- 靜態屬性,提供數學常數,只讀。
Math.E
:常數e。Math.LN2
:2 的自然對數。Math.LN10
:10 的自然對數。Math.LOG2E
:以 2 為底的e的對數。Math.LOG10E
:以 10 為底的e的對數。Math.PI
:常數π。Math.SQRT1_2
:0.5 的平方根。Math.SQRT2
:2 的平方根。
- 靜態方法:
Math.abs()
:絕對值Math.ceil()
:向上取整Math.floor()
:向下取整Math.max()
:最大值Math.min()
:最小值Math.pow()
:冪運算Math.sqrt()
:平方根Math.log()
:自然對數Math.exp()
:e的指數Math.round()
:四捨五入Math.random()
:隨機數
Math.ceil
和Math.floor
兩個方法結合實現返回一個數值的整數部分:
function ToInteger(x) {
x = Number(x);
return x < 0 ? Math.ceil(x) : Math.floor(x);
}
ToInteger(3.2) // 3
ToInteger(-3.2) // -3
Math.round
四捨五入處理負數時有所不同
Math.round(1.5) //2
Math.round(1.6) //2
Math.round(-1.5) //-1
Math.round(-1.6) //-2
求圓的面積
var radius = 20;
var area = Math.PI * Math.pow(radius, 2);
Math.sqrt
求參數值的平方根,參數不能為負數
Math.random()
返回0到1之間的一個偽隨機數,Math.random()>=0
且Math.random()<1
。
任意範圍的隨機數生成函數
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
任意範圍的隨機整數生成函數
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
生成隨機字元的例子
function random_str(length) {
var ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
ALPHABET += 'abcdefghijklmnopqrstuvwxyz';
ALPHABET += '0123456789-_';
var str = '';
for (var i = 0; i < length; ++i) {
var rand = Math.floor(Math.random() * ALPHABET.length);
str += ALPHABET.substr(rand, 1);
}
return str;
}
random_str(6) // "NdQKOr"
- 三角函數
Math.sin()
:返回參數的正弦(參數為弧度值)Math.cos()
:返回參數的餘弦(參數為弧度值)Math.tan()
:返回參數的正切(參數為弧度值)Math.asin()
:返回參數的反正弦(返回值為弧度值)Math.acos()
:返回參數的反餘弦(返回值為弧度值)Math.atan()
:返回參數的反正切(返回值為弧度值)
弧度和角度的轉換可以參考如下:arc——弧度,angle——角度
Math.PI=180度
arc=(Math.PI/180)angle //角度轉弧度
angle=(180/Math.PI)arc //弧度轉角度
Date對象
- Date對象是js原生的時間庫。以國際標準時間(UTC)1970年1月1日00:00:00作為時間的零點,可表示的時間範圍是前後各1億天(單位為毫秒)
- 作為普通函數
Date()
使用時返回當前時間的字元串,且此時傳遞參數無效 - Date作為構造函數,使用
new
返回Date對象的實例。
var today = new Date();
構造函數使用的Date可接受多種參數
// 參數為時間零點開始計算的毫秒數
new Date(1578218728000)
// Sun Jan 05 2020 18:05:28 GMT+0800 (中國標準時間)
// 參數為日期字元串
new Date('January 30, 2020');
// Thu Jan 30 2020 00:00:00 GMT+0800 (中國標準時間)
// 參數為多個整數,
// 代表年、月、日、小時、分鐘、秒、毫秒
new Date(2020, 6, 1, 0, 0, 0, 0)
// Wed Jul 01 2020 00:00:00 GMT+0800 (中國標準時間)
Date
構造函數的參數
- 參數是負整數,表示1970年元旦之前的時間。
- 只要是能被
Date.parse()
方法解析的字元串,都可以當作參數。 - 參數為年、月、日等多個整數時,年和月不能省略。只有一個參數Date會將其解釋為毫秒數。
- 各個參數的取值範圍:
年:使用四位數年份,比如2000。如果寫成兩位數或個位數,則加上1900,即10代表1910年。如果是負數,表示公元前。
月:0表示一月,依次類推,11表示12月。
日:1到31。
小時:0到23。
分鐘:0到59。
秒:0到59
毫秒:0到999。
參數使用負數,表示扣去的時間
獲取Date實例的值時返回的是字元串。即Date對象求值時調用的是
toString()
方法,而其他對象求值時調用的是valueOf()
方法var today = new Date(); today // Sun Aug 02 2020 09:30:29 GMT+0800 (中國標準時間) // 等同於 today.toString() // "Sun Aug 02 2020 09:30:29 GMT+0800 (中國標準時間)"
- 日期運算:類型自動轉換時,Date實例如果轉為數值,則等於對應的毫秒數;如果轉為字元串,則等於對應的日期字元串。所以,兩個日期實例對象進行減法運算時,返回的是它們間隔的毫秒數;進行加法運算時,返回的是兩個字元串連接而成的新字元串。
- 靜態方法:
Date.now()
返回當前時間距離時間零點(1970年1月1日 00:00:00 UTC)的毫秒數Date.parse()
解析日期字元串,返回距離時間零點(1970年1月1日 00:00:00)的毫秒數。解析失敗返回NaN
Date.UTC()
接受年、月、日等參數,返回距離時間零點的毫秒數。被當作UTC時間處理,而new Date()
是當地時間
// 格式
Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
// 用法
Date.UTC(2011, 0, 1, 2, 3, 4, 567) // 1293847384567
- 實例方法
-
valueOf()
返回毫秒數 -
toString()
返回完整的日期字元串 -
toUTCString()
返回UTC時間(比北京晚8小時) -
toISOString()
返回ISO8601寫法 -
toJSON()
與toISOString()
結果一樣 -
toDateString()
返回日期字元串(不含時間) -
toTimeString()
返回時間字元串(不含日期) -
toLocaleString()
本地日期時間 -
toLocaleDateString()
本地日期 -
toLocaleTimeString()
本地時間 -
getTime()
:距離1970年1月1日00:00:00的毫秒數,等同於valueOf方法。 -
getDate()
:返回實例對象對應每個月的幾號(從1開始)。 -
getDay()
:返回星期幾,星期日為0,星期一為1,以此類推。 -
getFullYear()
:返回四位的年份。 -
getMonth()
:返回月份(0表示1月,11表示12月)。 -
getHours()
:返回小時(0-23)。 -
getMilliseconds()
:返回毫秒(0-999)。 -
getMinutes()
:返回分鐘(0-59)。 -
getSeconds()
:返回秒(0-59)。 -
getTimezoneOffset()
:返回當前時間與 UTC 的時區差異,以分鐘表示,返回結果考慮到了夏令時因素。 -
setDate(date)
:設置實例對象對應的每個月的幾號(1-31),返回改變後毫秒時間戳。 -
setFullYear(year [, month, date])
:設置四位年份。 -
setHours(hour [, min, sec, ms])
:設置小時(0-23)。 -
setMilliseconds()
:設置毫秒(0-999)。 -
setMinutes(min [, sec, ms])
:設置分鐘(0-59)。 -
setMonth(month [, date])
:設置月份(0-11)。 -
setSeconds(sec [, ms])
:設置秒(0-59)。 -
setTime(milliseconds)
:設置毫秒時間戳。
RegExp對象
- 正則表達式(
regular expression
)是一種表達文本模式(即字元串結構)的方法,用來按照「給定模式」匹配文本。 - 使用字面量新建正則表達式。斜杠表示開始結束。編譯程式碼時新建正則表達式
var regex = /xyz/;
使用RegExp
構造函數新建正則。運行時新建正則表達式
var regex = new RegExp('xyz');
修飾符
var regex = new RegExp('xyz', 'i');
// 等價於
var regex = /xyz/i;
- 實例屬性
修飾符相關的屬性
RegExp.prototype.ignoreCase
:布爾值,是否設置了i修飾符。RegExp.prototype.global
:布爾值,是否設置了g修飾符。RegExp.prototype.multiline
:布爾值,是否設置了m修飾符。RegExp.prototype.flags
:字元串,包含了已經設置的所有修飾符,按字母排序。
var r = /abc/igm;
r.ignoreCase // true
r.global // true
r.multiline // true
r.flags // 'gim'
其他屬性
RegExp.prototype.lastIndex
:下一次開始搜索的位置。屬性可讀寫RegExp.prototype.source
:正則表達式的字元串形式(不包括反斜杠),屬性只讀。
- 實例方法
test()
返回布爾值,表示當前模式是否能匹配參數字元串。
正則帶g
修飾符時,test方法不要連續對不同的字元串匹配調用多次
exec()
執行一個正則匹配並返回匹配結果。匹配成功,返回一個匹配成功的字元串組成的數組;否則返回null
var s = '_x_x';
var r1 = /x/;
r1.exec(s)
// ["x", index: 1, input: "_x_x", groups: undefined]
// 0: "x"
// groups: undefined
// index: 1
// input: "_x_x"
// length: 1
// __proto__: Array(0)
如果正則表達式包含圓括弧(即含有「組匹配」),則返回的數組第一個成員是整個匹配成功的結果,後面的成員就是圓括弧對應的匹配成功的組。也就是說,第二個成員對應第一個括弧,第三個成員對應第二個括弧,以此類推。整個數組的length屬性等於組匹配的數量再加1。
這一點對於獲取匹配非常有用,通常在字元串的匹配方法中獲取
g
修飾符允許正則的實例方法多次匹配。即對同一個正則執行多次test或exec等方法,每次會記住匹配到的位置,下次繼續匹配- 字元串的
match()
、search()
、replace()
、split()
等方法均可使用正則表達式作為參數
match()
的正則使用g時,會返回所有匹配成功的結果
var s = 'abba';
var r = /a/g;
s.match(r) // ["a", "a"]
正則的lastIndex屬性對match無效
-
search()
返回第一個匹配的位置 -
str.replace(search, replacement)
。使用g
修飾符替換所有匹配成功的值
消除字元串首尾兩端的空格
str.replace(/^\s+|\s+$/g, '')
replace
的第二個參數中可以使用美元符號$
,用來指代所替換的內容。
$&
:匹配的子字元串。- $`:匹配結果前面的文本。
$'
:匹配結果後面的文本。$n
:匹配成功的第n組內容,n是從1開始的自然數。$$
:指代美元符號$。
'輸出hello world是編程的第一步'.replace(/(\w+)\s(\w+)(是)/, " ($'$3$2 $1$`——$$——匹配項是:$&) ")
// "輸出 (編程的第一步是world hello輸出——$——匹配項是:hello world是) 編程的第一步"
replace
的第二個參數還可以是一個函數,將每一個匹配內容替換為函數返回值。
'3 and 5'.replace(/[0-9]+/g, function (match) {
return 2 * match;
})
// "6 and 10"
var a = 'The quick brown fox jumped over the lazy dog.';
var pattern = /quick|brown|lazy/ig;
a.replace(pattern, function replacer(match) {
return match.toUpperCase();
});
// The QUICK BROWN fox jumped over the LAZY dog.
replace
第二個參數函數還可接受多個參數,第二個參數表示第一個組匹配
- 正則表達式的匹配規則
- 如果在正則表達式之中,某個字元只表示它字面的含義(比如/a/表示匹配a),則稱為”字面量字元”(
literal characters
)。 - 除字面量字元外,還有一部分字元有特殊含義,不代表字面的意思。稱為”元字元”(
metacharacters
)
(1)點字元(.
):匹配除回車(\r
)、換行(\n
) 、行分隔符(\u2028
)和段分隔符(\u2029
)以外的所有字元。
(2)位置字元:提示字元所處的位置。^
表示開始位置;$
表示結束位置
// test必須出現在開始位置
/^test/.test('test123') // true
// test必須出現在結束位置
/test$/.test('new test') // true
// 從開始位置到結束位置只有test
/^test$/.test('test') // true
/^test$/.test('testtest') // false
/^test$/.test('test test') // false
(3)選擇符(|
):豎線符號|
表示「或關係」(OR),即cat|dog
表示匹配cat
或dog
。
選擇符會包括它前後的多個字元,比如
/ab|cd/
指的是匹配ab
或者cd
,而不是匹配b
或者c
。使用圓括弧可以改變匹配的範圍。
實際匹配是可以成功的,只是含義上有所不同/a( |\t)b/.test('a\tb') // true
- 如果想要匹配特殊含義的”元字元”,則需要會用轉義字元
\
/1+1/.test('1+1') // false
/1\+1/.test('1+1') // true
需要轉義的字元有^
、.
、[
、$
、(
、)
、|
、*
、+
、?
、{
和\
使用RegExp生成正則對象時,轉義需要使用兩個斜杠,因為字元串內部會先轉義一次。
- 對不能列印的特殊字元正則表達式使用如下方式表示:
(1) \cX
表示Ctrl-[X]
,其中的X
是A-Z
之中任一個英文字母,用來匹配控制字元。
(2) [\b]
匹配退格鍵(U+0008
),不要與\b
混淆。
(3) \n
匹配換行鍵。
(4) \r
匹配回車鍵。
(5) \t
匹配製表符 tab(U+0009)。
(6) \v
匹配垂直製表符(U+000B)。
(7) \f
匹配換頁符(U+000C)。
(8) \0
匹配null字元(U+0000)。
(9) \xhh
匹配一個以兩位十六進位數(\x00
–\xFF
)表示的字元。
(10) \uhhhh
匹配一個以四位十六進位數(\u0000
–\uFFFF
)表示的 Unicode 字元。
- 字元類(
class
)表示有一系列字元可供選擇,匹配[]
中字元的任意一個
匹配abc
中的任意一個即為true
/[abc]/.test('hello world') // false
/[abc]/.test('apple') // true
字元類中的特殊字元——脫字元^
:如方括弧內第一個字元是[^]
,則表示否定,指除了字元類中的其他字元。
比如[^xyz]
表示除了x
、y
、z
之外都可以匹配。
如果方括弧內沒有其他字元,即只有[^]
,就表示匹配一切字元,包括換行符。相比之下,點號元字元(.
)不包括換行符。
var s = 'Please yes\nmake my day!';
s.match(/yes.*day/) // null
s.match(/yes[^]*day/) // [ 'yes\nmake my day']
字元類中的特殊字元——連字元(-
),在[]
中連字元-
表示字元的連續範圍,提供連續多字元的簡寫。比如[abc]
可寫成[a-c]
,[0123456789]
可寫成[0-9]
。連字號(dash)只有在[]
中才表示連續範圍的簡寫
/[a-b-]/.test('-') // true
/[a-b]/.test('-') // false
[A-z]不能表示A-z之間的52個字母,因為ASCII中大寫和小寫字母之間還有其他字元
/[A-z]/.test('\\') // true
- 預定義模式指的是某些常見模式的簡寫方式。
(1) \d
匹配0-9之間的任一數字,相當於[0-9]
。
(2) \D
匹配所有0-9以外的字元,相當於[^0-9]
。
(3) \w
匹配任意的字母、數字和下劃線,相當於[A-Za-z0-9_]
。
(4) \W
除所有字母、數字和下劃線以外的字元,相當於[^A-Za-z0-9_]
。
(5) \s
匹配空字元(包括換行符、製表符、空格符等),相等於[ \t\r\n\v\f]
。
(6) \S
匹配非空字元,相當於[^ \t\r\n\v\f]
。
(7) \b
匹配詞的邊界。
(8) \B
匹配非詞邊界,即在詞的內部。
使用[\s\S]
匹配所有字元
var html = "<b>Hello</b>\n<i>world!</i>";
// .不匹配換行符
/.*/.exec(html)[0] // "<b>Hello</b>"
// 匹配所有字元
/[\S\s]*/.exec(html)[0] // "<b>Hello</b>\n<i>world!</i>"
- 重複類
大括弧{}
表示模式的匹配次數,{n}
表示恰好重複n
次,{n,}
表示至少重複n
次,{n,m}
表示重複不少於n
次,不多於m
次。
/lo{2}k/.test('look') // true
/lo{2,5}k/.test('looook') // true
- 量詞符用來設定某個模式出現的次數
?
問號表示某個模式出現0次或1次,等同於{0, 1}
。
*
星號表示某個模式出現0次或多次,等同於{0,}
。
+
加號表示某個模式出現1次或多次,等同於{1,}
。
// t 出現0次或1次
/t?est/.test('test') // true
/t?est/.test('est') // true
// t 出現1次或多次
/t+est/.test('test') // true
/t+est/.test('ttest') // true
/t+est/.test('est') // false
// t 出現0次或多次
/t*est/.test('test') // true
/t*est/.test('ttest') // true
/t*est/.test('tttest') // true
/t*est/.test('est') // true
- 貪婪模式:如上的量詞符,默認情況下都是最大可能匹配,即匹配到下一個字元不滿足匹配規則為止。這被稱為貪婪模式。
如下,/a+/
匹配1個或多個a
,貪婪模式下會一直匹配到不符合這個規則為止;/a+?/
中通過添加?
改為非貪婪模式,會在第一次滿足條件時就不再繼續匹配
var s = 'aaa';
s.match(/a+/) // ["aaa"]
s.match(/a+?/) // ["a"]
非貪婪模式的符號+?
、*?
、??
- 修飾符(
modifier
)表示模式的附加規則,放在正則模式的最尾部。
(1) g
修飾符表示全局匹配(global)。正則對象將匹配全部符合條件的結果
(2) i
修飾符表示忽略大小寫(ignoreCase)
(3) m
修飾符表示多行模式(multiline)。默認情況下(不加m修飾符時),^
和$
匹配字元串的開始處和結尾處,加上m
修飾符以後,^
和$
還會匹配行首和行尾,即^
和$
會識別換行符(\n)。
加上m
後,$
可以匹配行尾,相當於把\n
看作另一行,之前的為上一行(行尾)
/world$/.test('hello world\n') // false
/world$/m.test('hello world\n') // true
加上m
後,換行符\n
被認為是一行的開始,所以後面的字元表示另一行的行首
/^b/m.test('a\nb') // true
- 組匹配:括弧表示分組匹配,括弧中的模式可以用來匹配分組的內容。
()
表示組匹配
/fred+/.test('fredd') // true
/(fred)+/.test('fredfred') // true
使用組匹配時,不宜同時使用
g
修飾符,否則match
方法不會捕獲分組的內容。var m = 'abcabc'.match(/(.)b(.)/g); m // ['abc', 'abc']
如上,使用
g
,match
方法只捕獲了匹配整個表達式的部分。使用正則表達式的
exec
方法,配合循環,才能讀到每一輪匹配的組捕獲var str = 'abcabc'; var reg = /(.)b(.)/g; while (true) { var result = reg.exec(str); if (!result) break; console.log(result); } // ["abc", "a", "c"] // ["abc", "a", "c"]
正則表達式內部,可以用\n
引用括弧匹配的內容,n
是從1開始的自然數
/(.)b(.)\1b\2/.test("abcabc") // true
括弧可以嵌套,如下\1
指向外層括弧,\2
指向內層括弧
/y((..)\2)\1/.test('yabababab') // true
如下匹配標籤
var tagName = /<(([^>]+))>[^<]*<\/\1>/;
tagName.exec("<b>bold</b>")[1] // 'b'
實現匹配帶有屬性的標籤
var html = '<h1 class="hello">Hello<i>world</i></h1>';
var tag = /<(\w+)([^>]*)>(.*?)<\/\1>/g;
var match = tag.exec(html);
match
// (4) ["<b class="hello">Hello</b>", "b", " class="hello"", "Hello", index: 0, input: "<b class="hello">Hello</b><i>world</i>", groups: undefined]
// 0: "<b class="hello">Hello</b>"
// 1: "b"
// 2: " class="hello""
// 3: "Hello"
// groups: undefined
// index: 0
// input: "<b class="hello">Hello</b><i>world</i>"
// length: 4
// match[0]中再匹配是否滿足標籤
match = tag.exec(html);
> (4) ["<i>world</i>", "i", "", "world", index: 26, input: "<》b class="hello">Hello</b><i>world</i>", groups: undefined]
> 0: "<i>world</i>"
> 1: "i"
> 2: ""
> 3: "world"
> groups: undefined
> index: 26
> input: "<b class="hello">Hello</b><i>world</i>"
> length: 4
- 非捕獲組:
(?:x)
稱為非捕獲組(Non-capturing group
),表示不返回該組匹配的內容,即匹配的結果中不計入這個括弧。
非捕獲組的作用請考慮這樣一個場景,假定需要匹配foo
或者foofoo
,正則表達式就應該寫成/(foo){1, 2}/
,但是這樣會佔用一個組匹配。這時,就可以使用非捕獲組,將正則表達式改為/(?:foo){1, 2}/
,它的作用與前一個正則是一樣的,但是不會單獨輸出括弧內部的內容。
var m = 'abc'.match(/(?:.)b(.)/);
m // ["abc", "c"]
正則拆解網址
// 正常匹配
var url = /(http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
url.exec('//google.com/');
// ["//google.com/", "http", "google.com", "/"]
// 非捕獲組匹配
var url = /(?:http|ftp):\/\/([^/\r\n]+)(\/[^\r\n]*)?/;
url.exec('//google.com/');
// ["//google.com/", "google.com", "/"]
- 先行斷言:
x(?=y)
稱為先行斷言(Positive look-ahead
),x
後面跟著y
才匹配,y
不會被計入返回結果。比如,要匹配後面跟著百分號的數字,可以寫成/\d+(?=%)/
。
“先行斷言”中,括弧的部分不會返回。
var m = 'abc'.match(/b(?=c)/);
m // ["b"]
- 先行否定斷言:
x(?!y)
稱為先行否定斷言(Negative look-ahead
),x
後面不跟著y
才匹配,y
不會被計入返回結果。比如,要匹配後面跟的不是百分號的數字,就要寫成/\d+(?!%)/。
/\d+(?!\.)/.exec('3.14') // ["14"]
JSON對象
- JSON(JavaScript對象表示法
JavaScript Object Notation
的縮寫)是一種用於數據交換的文本格式,2001年Douglas Crockford提出,目的是取代繁瑣笨重的XML格式。 - Json的顯著優點:書寫簡單;符合js原生語法,js引擎可直接處理
- 每個JSON對象就是一個值,可能是數組、對象、或原始類型
- Json的類型和格式:
- 複合類型的值只能是數組或對象,不能是函數、正則表達式對象、日期對象。
- 原始類型的值只有四種:字元串、數值(必須以十進位表示)、布爾值和null(不能使用
NaN
,Infinity
,-Infinity
和undefined
)。 - 字元串必須使用雙引號表示,不能使用單引號。
- 對象的鍵名必須放在雙引號裡面。
- 數組或對象最後一個成員的後面,不能加逗號。
null
、空數組[]
和空對象{}
都是合法的 JSON 值
如下為合法的json
["one", "two", "three"]
{ "one": 1, "two": 2, "three": 3 }
{"names": ["張三", "李四"] }
[ { "name": "張三"}, {"name": "李四"} ]
- JSON對象是js的原生對象,用來處理JSON格式數據
- JSON靜態方法:
JSON.stringify()
將一個值轉為JSON字元串,且可以被JSON.parse()
還原。
使用和介紹
如果對象的屬性是undefined
、函數或XML
對象,該屬性會被JSON.stringify
過濾
var obj = {
a: undefined,
b: function () {}
};
JSON.stringify(obj) // "{}"
如果數組的成員是undefined
、函數或XML對象,則被轉成null
var arr = [undefined, function () {}];
JSON.stringify(arr) // "[null,null]"
正則對象會被轉為空對象
JSON.stringify(/foo/) // "{}"
對象的不可遍歷屬性會被JSON.stringify
忽略
var obj = {};
Object.defineProperties(obj, {
'foo': {
value: 1,
enumerable: true
},
'bar': {
value: 2,
enumerable: false
}
});
JSON.stringify(obj); // "{"foo":1}"
JSON.stringify
第二個參數
JSON.stringify
方法第二個參數如果是數組,用來指定需要轉成字元串的屬性。只對對象的屬性有效
var obj = {
'prop1': 'value1',
'prop2': 'value2',
'prop3': 'value3'
};
var selectedProperties = ['prop1', 'prop2'];
JSON.stringify(obj, selectedProperties) // "{"prop1":"value1","prop2":"value2"}"
第二個參數如果為函數,可用來更改JSON.stringify
返回值。
function f(key, value) {
if (typeof value === "number") {
value = 2 * value;
}
return value;
}
JSON.stringify({ a: 1, b: 2 }, f) // '{"a": 2,"b": 4}'
如果處理函數返回undefined
或沒有返回值,則該屬性會被忽略
function f(key, value) {
if (typeof(value) === "string") {
return ; // return undefined;
}
return value;
}
JSON.stringify({ a: "abc", b: 123 }, f)
JSON.stringify第三個參數
JSON.stringify
可以接受第三個參數,用於增加返回的 JSON 字元串的可讀性,格式化返回的json字元串。數字表示每個屬性前面添加的空格(最多不超過10個);字元串(不超過10個字元),則該字元串會添加在每行前面。
JSON.stringify({ p1: 1, p2: 2 }, null, 4);
/*
"{
"p1": 1,
"p2": 2
}"
*/
JSON.stringify({ p1:1, p2:2 }, null, '==||==');
/*
"{
==||=="p1": 1,
==||=="p2": 2
}"
*/
參數對象的toJSON方法
如果參數對象有自定義的toJSON
方法,JSON.stringify
會使用這個方法的返回值作為參數,而忽略原對象。
Date
對象就有一個自己的toJSON
方法
var date = new Date('2020-01-01');
date.toJSON() // "2020-01-01T00:00:00.000Z"
JSON.stringify(date) // ""2020-01-01T00:00:00.000Z""
toJSON()
的一個應用是,將正則對象自動轉為字元串。
var obj = {
reg: /foo/
};
// 不設置 toJSON 方法時
JSON.stringify(obj) // "{"reg":{}}"
// 設置 toJSON 方法
RegExp.prototype.toJSON = RegExp.prototype.toString;
JSON.stringify(obj) // "{"reg":"/foo/"}"
JSON.parse(JSON.stringify(obj)) // {reg: "/foo/"}
- JSON靜態方法:
JSON.parse()
將JSON字元串轉換成對應的js值。字元串必須是有效的JSON格式
try {
JSON.parse("'String'");
} catch(e) {
console.log('parsing error');
}
JSON.parse()
第二個參數可接受一個函數,與JSON.stringify
方法類似。
function f(key, value) {
if (key === 'a') {
return value + 10;
}
return value;
}
JSON.parse('{"a": 1, "b": 2}', f)
// {a: 11, b: 2}