《JavaScript語言入門教程》記錄整理:運算符、語法和標準庫

本系列基於阮一峰老師的《JavaScrip語言入門教程》或《JavaScript教程》記錄整理,教程採用知識共享 署名-相同方式共享 3.0協議。這幾乎是學習js最好的教程之一(去掉之一都不過分)

最好的教程而阮一峰老師又採用開源方式共享出來,之所以重新記錄一遍,一是強迫自己重新認真讀一遍學一遍;二是對其中知識點有個自己的記錄,加深自己的理解;三是感謝這麼好的教程,希望更多人閱讀了解

運算符

算數運算符

  1. js提供了10種運算符
  • 加法運算符:x + y
  • 減法運算符:x - y
  • 乘法運算符:x * y
  • 除法運算符:x / y
  • 指數運算符:x ** y
  • 餘數運算符:x % y
  • 自增運算符:++x 或者 x++
  • 自減運算符:--x 或者 x--
  • 數值運算符: +x
  • 負數值運算符:-x
  1. js中非數值可以相加,比如布爾值與數值相加,字元串相加用於連接兩個字元串
true + true // 2
1 + true // 2

1 + 'a' // "1a"
false + 'a' // "falsea"

加法運算符是在運行時決定,到底是執行相加,還是執行連接。運運算元的不同,導致了不同的語法行為,這種現象稱為「重載」(overload)

'3' + 4 + 5 // "345"
3 + 4 + '5' // "75"

加法運算符存在重載。減法、除法和乘法等運算符不會重載:所有運運算元一律轉為數值,再進行相應的數學運算

  1. 對象的相加:運運算元是對象時,會先轉成原始類型的值,然後再相加。

對象默認轉成原始類型的值是[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. 餘數運算符(%)返回前一個運運算元被後一個運運算元除所得的餘數。結果的正負號由第一個運運算元決定
-1 % 2 // -1
1 % -2 // 1

可以使用絕對值,獲得負數的正確餘數值

// 正確的寫法
function isOdd(n) {
  return Math.abs(n % 2) === 1;
}
isOdd(-5) // true
isOdd(-4) // false
  1. 自增和自減運算符是一元運算符,只有一個運運算元。

運算之後,變數的值發生變化,這種效應叫做運算的副作用(side effect)。自增和自減運算符是僅有的兩個具有副作用的運算符,其他運算符都不會改變變數的值。

自增/自減放在變數後面,會先返回變數操作前的值,再進行自增/自減操作
自增/自減放在變數之前,會先進行自增/自減操作,再返回變數操作後的值

  1. 數值運算符(+)的作用可以將任何值轉為數值(與Number函數作用相同)

負數值運算符(-),將一個值轉為數值的負值

不會改變原始變數的值,而是返回新值

  1. 指數運算符(**)完成指數運算

指數運算符是右結合,而不是左結合。即多個指數運算符連用時,先進行最右邊的計算。

// 相當於 2 ** (3 ** 2)
2 ** 3 ** 2  // 512
  1. 賦值運算符(Assignment Operators)用於給變數賦值。還有複合的賦值運算符,如x += yx -= y

比較運算符

  1. 比較運算符比較兩個值的大小,並返回一個布爾值。js提供了8個比較運算符
  • > 大於運算符
  • < 小於運算符
  • <= 小於或等於運算符
  • >= 大於或等於運算符
  • == 相等運算符
  • === 嚴格相等運算符
  • != 不相等運算符
  • !== 嚴格不相等運算符
  1. 相等比較和非相等比較。

對於非相等的比較,演算法是先看兩個運運算元是否都是字元串,如果是的,就按照字典順序比較(實際上是比較 Unicode 碼點);否則,將兩個運運算元都轉成數值,再比較數值的大小。

  1. 相等運算符(==)比較兩個值是否相等,嚴格相等運算符(===)比較兩個值是否為「同一個值」。

如果兩個值不是同一類型,嚴格相等運算符===直接返回false,而相等運算符==會將它們轉換成同一個類型,再進行比較

  1. 嚴格相等運算符:類型不同返回false;同一類型的原始類型值,會比較兩者的值是否相等;複合類型的值(對象、數組、函數)比較的是是否指向同一個地址;undefined和null與自身嚴格相等

兩個對象的比較,嚴格相等運算符比較的是地址,而大於或小於運算符比較的是值

var obj1 = {};
var obj2 = {};

obj1 > obj2 //  比較的是值 false
obj1 < obj2 //  比較的是值 false
obj1 === obj2 // 比較的是地址 false

相等運算符比較是隱含了類型轉換,建議最好只使用嚴格相等運算符(===)。

布爾運算符

  1. 布爾運算符用於將表達式轉為布爾值。一共有4個
  • 取反運算符:!
  • 且運算符:&&
  • 或運算符:||
  • 三元運算符:?:
  1. 取反運算符將布爾值變為相反值。兩次取反就是將一個值轉為布爾值的簡便寫法
  2. 且運算符&&常用於多個表達式的求值

且運算符&&運算規則是:如果第一個運運算元的布爾值為true,則返回第二個運運算元的值(注意是值,不是布爾值);如果第一個運運算元的布爾值為false,則直接返回第一個運運算元的值,且不再對第二個運運算元求值。

&&且運算可以用來取代if語句

if (i) {
  doSomething();
}

// 等價於

i && doSomething();
  1. 或運算符(||)也用於多個表達式的求值。

或運算符||的運算規則是:如果第一個運運算元的布爾值為true,則返回第一個運運算元的值,且不再對第二個運運算元求值;如果第一個運運算元的布爾值為false,則返回第二個運運算元的值。

或運算符常用於為一個變數設置默認值。

function saveText(text) {
  text = text || '';
  // ...
}

// 或者寫成
saveText(this.text || '')
  1. 且運算符和或運算符,這種通過第一個表達式(運運算元)的值,控制是否運行第二個表達式(運運算元)的機制,就稱為「短路」(short-cut)
  2. 三元條件運算符(?:)是js中唯一一個需要三個運運算元的運算符

二進位位運算符

  1. 二進位位運算符用於直接對二進位位進行計算,一共有7個:
  • 二進位或運算符(or):符號為|,表示若兩個二進位位都為0,則結果為0,否則為1。
  • 二進位與運算符(and):符號為&,表示若兩個二進位位都為1,則結果為1,否則為0。
  • 二進位否運算符(not):符號為~,表示對一個二進位位取反。
  • 異或運算符(xor):符號為^,表示若兩個二進位位不相同,則結果為1,否則為0。
  • 左移運算符(left shift):符號為<<
  • 右移運算符(right shift):符號為>>
  • 頭部補零的右移運算符(zero filled right shift):符號為>>>
  1. 位運算符只對整數起作用,如果一個運運算元不是整數,會自動轉為整數後再執行。雖然在JavaScript內部,數值都是以64位浮點數的形式儲存,但是做位運算的時候,是以32位帶符號的整數進行運算的,並且返回值也是一個32位帶符號的整數

利用這個特性,可以寫出一個函數,將任意數值轉為32位整數。

function toInt32(x) {
  return x | 0;
}
  1. 位運算符可以用作設置對象屬性的開關。(開關作用有些抽象,但很精巧)

假定某個對象有四個開關,每個開關都是一個變數。那麼,可以設置一個四位的二進位數,它的每個位對應一個開關。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
  // ...
}
  • 假設需要打開ABD三個開關,可以先構造一個掩碼變數,然後通過二進位或運算掩碼變數,可以確保打開這三個開關
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和逗號運算符

  1. void運算符,執行一個表達式,然後不返回任何值,或者返回undefined
void 0 // undefined
void(0) // undefined   推薦寫法

void運算符的優先順序很高,使用括弧避免錯誤

var x = 3;
void (x = 5) //undefined
x // 5
  1. 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>
  1. 逗號運算符用於對兩個表達式求值,並返回後一個表達式的值。
'a', 'b' // "b"

var x = 0;
var y = (x++, 10);
x // 1
y // 10

用途是:在返回一個值之前,進行一些輔助操作。

var value = (console.log('Hi!'), true);
// Hi!

value // true

運算順序

  1. 運算符優先順序別(Operator Precedence)高的先執行
  2. 圓括弧()用來提高運算的優先順序(它的優先順序最高),即圓括弧中的表達式會第一個運算

圓括弧不是運算符,而是一種語法結構。它一共有兩種用法:一種是把表達式放在圓括弧之中,提升運算的優先順序;另一種是跟在函數的後面,作用是調用函數。

函數放在圓括弧中,會返回函數本身。圓括弧緊跟在函數的後面,表示調用函數。

圓括弧之中,只能放置表達式

  1. “左結合”(left-to-right associativity)運算符會先從左向右運算
    “右結合”(right-to-left associativity)運算符會先從右向左運算

js中賦值運算符(=)、三元條件運算符(?:)、指數運算符(**)是”右結合”的

語法

數據類型的轉換

  1. JavaScript 是一種動態類型語言,變數的類型無法在編譯階段確定,必須在運行時才能知道。而同時js的變數類型又可以隨意改變,因此又屬於弱類型語言
  2. JS中的運算符對數據類型有要求。因此常常發生類型自動轉換
  3. 強制類型轉換主要指使用Number()String()Boolean()手動將任意類型的值,分別轉換成數字、字元串或者布爾值。
  4. 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方法返回的是對象,就報錯。

自定義valueOftoString

Number({
  valueOf: function () {
    return 2;
  }
})
// 2

Number({
  toString: function () {
    return 3;
  }
})
// 3

Number({
  valueOf: function () {
    return 2;
  },
  toString: function () {
    return 3;
  }
})
// 2
  1. 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"
  1. Boolean()轉換為布爾值,規則簡單,除了下面6個值結果為false,其餘全部為true

undefinednull0(包含-0+0)、NaN''(空字元串)和false

所有對象(包括空對象)的轉換結果都是true,包括false對應的布爾對象new Boolean(false)也是true

  1. js中數據類型自動轉換髮生的情況:一、不同類型的數據相互運算時會自動轉換。二、對非布爾值類型的數據求布爾值時。三、對非數值類型的值使用一元運算符(即+-)。轉換時的規則是:預期什麼類型的值,就調用該類型的轉換函數。如果該位置既可以是字元串,又可以是數值,則默認轉為數值
  2. JavaScript在預期為布爾值的地方(比如if語句的條件部分),會將非布爾值的參數自動轉換為布爾值。系統內部會自動調用Boolean函數。

如下兩個方法將一個表達式轉為布爾值

// 寫法一
expression ? true : false

// 寫法二
!! expression
  1. 除了加法運算符(+)有可能把運運算元轉為字元串,其他運算符都會把運運算元自動轉成數值。

null數值0undefined數值NaN

錯誤處理機制

  1. JavaScript原生提供Error構造函數,所有拋出的錯誤都是這個構造函數的實例。當發生錯誤時,js引擎拋出Error實例對象以後,整個程式就中斷在發生錯誤的地方,不再往下執行。
var err = new Error('出錯了');
err.message // "出錯了"
  1. 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
  1. Error實例是最一般的錯誤類型,js還提供Error的6個派生對象
  • SyntaxError對象:解析程式碼時發生的語法錯誤
  • ReferenceError對象:引用一個不存在的變數時發生的錯誤。
  • RangeError對象:一個值超出有效範圍時發生的錯誤。
  • TypeError對象:變數或參數不是預期類型時發生的錯誤。
  • URIError對象:URI相關函數的參數不正確時拋出的錯誤。主要encodeURI()decodeURI()encodeURIComponent()decodeURIComponent()escape()unescape()
  • EvalError對象:已不再使用
  1. 自定義錯誤
function UserError(message) {
  this.message = message || '默認資訊';
  this.name = 'UserError';
}

UserError.prototype = new Error();
UserError.prototype.constructor = UserError;
  1. throw語句:手動中斷程式執行,拋出一個錯誤。
if (true) {
  throw new Error('x 必須為正數');
}
// Uncaught Error: x 必須為正數
//    at <anonymous>:2:9

throw可以拋出任何類型的值

  1. 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);
  }
  // ...
}
  1. 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();
}

編程風格

  1. “編程風格”(programming style)指的是編寫程式碼的樣式規則。
    你選擇的,不是你喜歡的風格,而是一種能夠清晰表達你的意圖的風格
  2. 編程風格主要考慮的幾點:縮進(indent)、區塊(block)、圓括弧(parentheses)、行尾的分號、變數聲明、嚴格相等、語句的合併書寫等
  3. 使用{}程式碼塊時,js中要使用左大括弧{緊挨著語句在同一行中,不要換行寫。這是因為JavaScript會自動添加句末的分號,從而產生一些難以察覺的錯誤。
block {
  // ...
}

如下return語句其實會變成兩句,從而導致出問題

return
{
  key: value
};

// 相當於
return;
{
  key: value
};

// 正確寫法
return {
  key : value
};
  1. 行尾的分號:分號表示一條語句的結束。js允許省略。

有三種情況,語法規定不需要在結尾添加分號。如果添加,js引擎將分號解釋為空語句

    1. forwhile 循環
for ( ; ; ) {
} // 沒有分號

while (true) {
} // 沒有分號

但是do...while要有分號

    1. 分支語句:ifswitchtry
if (true) {
} // 沒有分號

switch () {
} // 沒有分號

try {
} catch {
} // 沒有分號
  • 函數的聲明語句
function f() {
} // 沒有分號

函數表達式仍要使用分號

var f = function f() {
};

除了這三種情況,所有語句都應該使用分號。

在沒有分號時JavaScript會自動添加,這種語法特性叫”分號的自動添加”(Automatic Semicolon Insertion,簡稱ASI)

但是,如果下一行的開始可以與本行的結尾連在一起解釋,JavaScript就不會自動添加分號。

而是否自動添加分號無法預測,很有可能導致額外的錯誤。

一行的起首”自增”(++)或”自減”(–),則前面會自動添加分號

不應該省略結尾的分號,還有一個原因。有些JavaScript程式碼壓縮器(uglifier)不會自動添加分號,因此遇到沒有分號的結尾,就會讓程式碼保持原狀,而不是壓縮成一行,使得壓縮無法得到最優的結果。

另外,不寫結尾的分號,可能會導致腳本合併出錯。所以,有的程式碼庫在第一行語句開始前,會加上一個分號。可以避免與其他腳本合併時,前面的腳本最後一行語句沒有分號,導致運行出錯的問題。

;var a = 1;
// ...
  1. 避免全局變數的使用,如果必須使用,考慮大寫字母表示
  2. 變數聲明,由於存在變數提升,許多語句會導致產生全局變數(比如for循環中)。

所有函數都應該在使用之前定義。函數內部的變數聲明,都應該放在函數的頭部。

  1. 建議只使用嚴格相等運算符(===)
  2. switch...case結構可以用對象結構代替

switch...case結構類似於goto語句,容易造成程式流程的混亂,使得程式碼結構混亂不堪,不符合面向對象編程的原則。

console對象和控制台

  1. console對象是JavaScript的原生對象,可以輸出各種資訊到控制台
  2. console的常見用途:調試程式,顯示網頁程式碼運行時的錯誤資訊;提供了一個命令行介面,用來與網頁程式碼互動。
  3. 開發者工具的幾個面板。
  • Elements:查看網頁的 HTML 源碼和 CSS 程式碼。
  • Resources:查看網頁載入的各種資源文件(比如程式碼文件、字體文件 CSS 文件等),以及在硬碟上創建的各種內容(比如本地快取、Cookie、Local Storage等)。
  • Network:查看網頁的 HTTP 通訊情況。
  • Sources:查看網頁載入的腳本源碼,可進行斷點debug。
  • Timeline:查看各種網頁行為隨時間變化的情況。
  • Performance:查看網頁的性能情況,比如 CPU 和記憶體消耗。
  • Console:即控制台,用來運行js命令,和頁面中js程式碼console方法的輸出。
  1. console 對象的靜態方法
  • console.log()console.info()console.debug()
  • console.warn(),console.error()
  • console.table()
  • console.count()
  1. debugger語句主要用於除錯,作用是設置斷點。

標準庫

下面基本都是js原生對象的介紹,裡面許多屬性和方法僅了解一下即可,有需要時再查詢使用

Object對象

  1. JavaScript原生提供Object對象
  2. JavaScript的所有其他對象都繼承自Object對象,都是Object的實例。
  3. 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
  1. Object本身是一個函數,可以當作工具方法使用,將任意值轉為對象。保證某個值一定是對象。
  2. Object方法無參數或為undefinednull,返回一個空對象
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
  1. instanceof運算符驗證一個對象是否為指定的構造函數的實例
  2. Object構造函數用來生成新對象
var obj = new Object();

// 等價於
var obj = {};
  1. Object構造函數與工具方法類似。如果參數是一個對象,則直接返回該對象;如果是一個原始類型的值,則返回該值對應的包裝對象
  2. 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
  1. Object實例對象的方法:
  • Object.prototype.valueOf():返回當前對象對應的值,默認情況下返回對象本身。
  • Object.prototype.toString():返回當前對象對應的字元串形式,默認返回類型字元串。
  • Object.prototype.toLocaleString():返回當前對象對應的本地字元串形式。
  • Object.prototype.hasOwnProperty():判斷某個屬性是否為當前對象自身的屬性,還是繼承自原型對象的屬性。
  • Object.prototype.isPrototypeOf():判斷當前對象是否為另一個對象的原型。
  • Object.prototype.propertyIsEnumerable():判斷某個屬性是否可枚舉。
  1. 數組、字元串、函數、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 (中國標準時間)"
  1. 判斷數據類型

關於如何正確的判斷數據類型,由於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
  1. toLocaleString()用來實現自定義的本地字元串。如Array.prototype.toLocaleString()
    Number.prototype.toLocaleString()Date.prototype.toLocaleString()等對象自定義這個方法

屬性描述對象

  1. JS提供了叫做”屬性描述對象”(attributes object)的內部數據結構,用來描述對象的屬性,控制它的行為,比如該屬性是否可寫、可遍歷等。
  2. 每個屬性都有自己對應的屬性描述對象,保存該屬性的一些元資訊。
  3. 如下為屬性描述對象的例子:
{
  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屬性,否則會報錯。

  1. Object.getOwnPropertyDescriptor()獲取屬性描述對象
var obj = { p: 'a' };

Object.getOwnPropertyDescriptor(obj, 'p')
// Object { value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }
  1. Object.defineProperty()通過屬性描述對象,定義或修改屬性,並返回修改後的對象。
Object.defineProperty(object, propertyName, attributesObject)

參數:

  • object:屬性所在的對象
  • propertyName:字元串,屬性名
  • attributesObject:屬性描述對象

Object.defineProperties()可以一次定義多個屬性

  1. JSON.stringify方法會排除enumerable為false的屬性,有時可以利用這一點。如果對象的 JSON格式輸出要排除某些屬性,可以把這些屬性的enumerable設為false。
  2. 存取器(accessor,set-setter,get-getter)是另外定義屬性的方式,定義存取器後,將會執行對應的函數。

除了defineProperty方法中通過屬性描述對象定義存取器,還提供如下的寫法(且這種寫法configurableenumerable都為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: 新的值必須大於當前值
  1. 對象的拷貝:

由於對象是引用類型,數據存放在堆中,棧中值存放對象的地址。默認值類型的賦值是複製給另一個變數;但引用類型的賦值是直接將引用地址複製給另一個變數,賦值引用就是常說的淺拷貝(淺拷貝的對象共用一個記憶體地址)。而深拷貝指的是將引用類型的數據也完全複製一份給新的變數。

對象深拷貝的基本原理就是:通過遍歷對象的屬性,然後將屬性和遞歸至不是對象的屬性值重新賦值為另一個對象,如果屬性值是對象,則遞歸執行當前函數。

  • 方法一。 如下,缺點不能深拷貝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的sliceconcat等方法不改變原數組,但是返回的也是淺拷貝了的新數組
另:$.extend方法的第一個參數給bool值表示是否深拷貝:jQuery.extend( [deep ], target, object1 [, objectN ] )

  1. 控制對象狀態
  • Object.preventExtensions方法:使一個對象無法再添加新的屬性
  • Object.isExtensible方法檢查一個對象是否使用了Object.preventExtensions方法。檢查是否可以為一個對象添加屬性。
  • Object.seal方法使得一個對象既無法添加新屬性,也無法刪除舊屬性。Object.isSealed()
  • Object.freeze方法使一個對象變成常量。無法添加新屬性、無法刪除舊屬性、也無法改變屬性的值。Object.isFrozen()

上面三個方法鎖定對象的可寫性有一個漏洞:可以通過改變原型對象,來為對象增加屬性。解決方案是原型也凍結住。另外一個局限是,如果屬性值是對象,這些方法只能凍結屬性指向的對象,而不能凍結對象本身的內容。

Array 對象

  1. Array是JavaScript的原生對象,也是一個構造函數,用來生成新數組。
var arr = new Array(2);  // 等同於 var arr = Array(2);
arr.length // 2
arr // [ empty x 2 ]
  1. Array()構造函數有很大的缺陷,不同的參數生成的結果會不一樣。因此建議使用數組字面量的方式
// 不建議的方式
var arr = new Array(1, 2);

// 推薦
var arr = [1, 2];
  1. Array.isArray()靜態方法,判斷是否是數組
var arr = [1, 2, 3];

typeof arr // "object"
Array.isArray(arr) // true
  1. 數組對象的實例方法:
  • valueOf()返回數組本身
  • toString()返回數組的字元串形式
  • push()在數組的末端添加一個或多個元素,返回添加後的數組長度——(在數組末尾壓入元素)。該方法改變原數組。
  • pop()刪除數組的最後一個元素,並返回該元素——(彈出最後一個元素)。該方法改變原數組。

pushpop結合使用,可構成”後進先出”的棧結構(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變數

  • forEachmap相似,對數組的所有成員依次執行參數函數,但不返回值。

如果數組遍歷是為了得到返回值,可以使用map方法,否則使用forEach方法。

forEach方法無法中斷執行。如果想要中斷,可使用for循環、或someevery方法。

  • 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開始);原數組。前兩個必須

reducereduceRight的第二個參數可指定執行時的初始值

[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

空數組執行reducereduceRight時會報錯,可指定第二個參數初始值解決

藉助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
  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]"

包裝對象

  1. js的三種原始類型的值——數值、字元串、布爾值——在一定條件下會自動轉為對象,這就是原始類型的”包裝對象”(wrapper)
  2. “包裝對象”指的是與數值、字元串、布爾值分別相對應的NumberStringBoolean三個原生對象。這三個原生對象可以把原始類型的值變成(包裝成)對象。
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
  1. 包裝對象的設計目的:首先,使得”對象”這種類型可以覆蓋JavaScript所有的值,整門語言有一個通用的數據模型。其次,使得原始類型的值也有辦法調用自己的方法
  2. NumberStringBoolean作為普通函數調用時用以類型轉換,將任意類型的值轉為數值、字元串和布爾值等原始類型的值;作為構造函數使用(帶有new)時,將原始類型的值轉為對象
  3. 包裝對象繼承了Object對象的valueOf()——返回包裝對象實例對應的原始類型的值、toString()——返回對應的字元串形式方法
  4. 原始類型與實例對象的自動轉換:有時,原始類型的值會自動當作包裝對象調用,即調用包裝對象的屬性和方法。JavaScript 引擎會自動將原始類型的值轉為包裝對象實例,並在使用後立刻銷毀實例

比如字元串調用length屬性:

'abc'.length // 3

abc是一個字元串,本身不是對象,不能調用length屬性。JavaScript引擎自動將其轉為包裝對象,在這個對象上調用length屬性。調用結束後,這個臨時對象就會被銷毀。這就叫原始類型與實例對象的自動轉換

自動轉換生成的包裝對象是只讀的,無法修改。所以,字元串無法添加新屬性。同時調用結束後,包裝實例會自動銷毀,所以每次調用其實都是一個新的包裝對象。

var s = 'Hello World';
s.x = 123;
s.x // undefined

如果要為字元串添加屬性,只有在它的原型對象String.prototype上定義

  1. 可以在包裝對象的原型對象prototype上添加自定義方法或屬性

Boolean對象

  1. 通過valueOf()獲取包裝對象對應的原始類型值
new Boolean(false).valueOf()

Number對象

  1. 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
  1. 實例方法
  • 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對象

  1. 靜態方法String.fromCharCode()返回Unicode碼點組成的字元串

Unicode碼點不能大於0xFFFF,碼點大於0xFFFF的字元佔用四個位元組,而JavaScript默認支援的是兩個位元組的字元。比如0x20BB7需要拆成兩個字元來寫

String.fromCharCode(0xD842, 0xDFB7)   // "𠮷"
  1. 實例屬性String.prototype.length返回字元串長度
  2. 實例方法
  • 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對象

  1. Math提供各種數學功能。該對象不是構造函數,不能生成實例,必須在Math對象上調用屬性和方法。
  2. 靜態屬性,提供數學常數,只讀。
  • 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 的平方根。
  1. 靜態方法:
  • Math.abs():絕對值
  • Math.ceil():向上取整
  • Math.floor():向下取整
  • Math.max():最大值
  • Math.min():最小值
  • Math.pow():冪運算
  • Math.sqrt():平方根
  • Math.log():自然對數
  • Math.exp():e的指數
  • Math.round():四捨五入
  • Math.random():隨機數

Math.ceilMath.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()>=0Math.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"
  1. 三角函數
  • 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對象

  1. Date對象是js原生的時間庫。以國際標準時間(UTC)1970年1月1日00:00:00作為時間的零點,可表示的時間範圍是前後各1億天(單位為毫秒)
  2. 作為普通函數Date()使用時返回當前時間的字元串,且此時傳遞參數無效
  3. 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 (中國標準時間)"
  1. 日期運算:類型自動轉換時,Date實例如果轉為數值,則等於對應的毫秒數;如果轉為字元串,則等於對應的日期字元串。所以,兩個日期實例對象進行減法運算時,返回的是它們間隔的毫秒數;進行加法運算時,返回的是兩個字元串連接而成的新字元串。
  2. 靜態方法:
  • 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
  1. 實例方法
  • 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對象

  1. 正則表達式(regular expression)是一種表達文本模式(即字元串結構)的方法,用來按照「給定模式」匹配文本。
  2. 使用字面量新建正則表達式。斜杠表示開始結束。編譯程式碼時新建正則表達式
var regex = /xyz/;

使用RegExp構造函數新建正則。運行時新建正則表達式

var regex = new RegExp('xyz');

修飾符

var regex = new RegExp('xyz', 'i');
// 等價於
var regex = /xyz/i;
  1. 實例屬性

修飾符相關的屬性

  • 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:正則表達式的字元串形式(不包括反斜杠),屬性只讀。
  1. 實例方法
  • 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。

這一點對於獲取匹配非常有用,通常在字元串的匹配方法中獲取

  1. g修飾符允許正則的實例方法多次匹配。即對同一個正則執行多次test或exec等方法,每次會記住匹配到的位置,下次繼續匹配
  2. 字元串的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第二個參數函數還可接受多個參數,第二個參數表示第一個組匹配

  1. 正則表達式的匹配規則
  • 如果在正則表達式之中,某個字元只表示它字面的含義(比如/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表示匹配catdog

選擇符會包括它前後的多個字元,比如/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],其中的XA-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]表示除了xyz之外都可以匹配。

如果方括弧內沒有其他字元,即只有[^],就表示匹配一切字元,包括換行符。相比之下,點號元字元(.)不包括換行符。

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']

如上,使用gmatch方法只捕獲了匹配整個表達式的部分。

使用正則表達式的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對象

  1. JSON(JavaScript對象表示法JavaScript Object Notation的縮寫)是一種用於數據交換的文本格式,2001年Douglas Crockford提出,目的是取代繁瑣笨重的XML格式。
  2. Json的顯著優點:書寫簡單;符合js原生語法,js引擎可直接處理
  3. 每個JSON對象就是一個值,可能是數組、對象、或原始類型
  4. Json的類型和格式:
  • 複合類型的值只能是數組或對象,不能是函數、正則表達式對象、日期對象。
  • 原始類型的值只有四種:字元串、數值(必須以十進位表示)、布爾值和null(不能使用NaN, Infinity, -Infinityundefined)。
  • 字元串必須使用雙引號表示,不能使用單引號。
  • 對象的鍵名必須放在雙引號裡面。
  • 數組或對象最後一個成員的後面,不能加逗號。
  • null、空數組[]和空對象{}都是合法的 JSON 值

如下為合法的json

["one", "two", "three"]

{ "one": 1, "two": 2, "three": 3 }

{"names": ["張三", "李四"] }

[ { "name": "張三"}, {"name": "李四"} ]
  1. JSON對象是js的原生對象,用來處理JSON格式數據
  2. 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/"}
  1. 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}