溫故而知新--JavaScript書摘(二)

前言

畢業到入職騰訊已經差很少一年的時光了,接觸了不少項目,也積累了不少實踐經驗,在處理問題的方式方法上有很大的提高。隨着時間的增長,越發發現基礎知識的重要性,不少開發過程當中遇到的問題都是由最基礎的知識點遺忘形成,基礎不牢,地動山搖。因此,就再次迴歸基礎知識,從新學習JavaScript相關內容,加深對JavaScript語言本質的理解。日知其所亡,身爲有追求的程序員,理應不斷學習,不斷拓展本身的知識邊界。本系列文章是在此階段產生的積累,以記錄下以往沒有關注的核心知識點,供後續查閱之用。

2017

03/13

ECMAScript 語言類型包 括 Undefined、Null、Boolean、String、Number 和 Object,符號(symbol,ES6 中新增)
「類型」:對語言引擎和開發人員來講,類型是值 的內部特徵,它定義了值的行爲,以使其區別於其餘值。
檢測null :
var a = null;
 (!a && typeof a === "object"); // true
JavaScript 中的變量是沒有類型的,只有值纔有。變量能夠隨時持有任何類型的值。  
在對變量執行 typeof 操做時,獲得的結果並非該變量的類型,而是該變量持有的值的類 型,由於 JavaScript 中的變量沒有類型。
已在做用域中聲明但尚未賦值的變量,是 undefined 的。相反,尚未在做用域中聲明 過的變量,是 undeclared 的。

03/14

對於 undeclared(或者 not defined)變量,typeof 照樣返回 "undefined",這是由於 typeof 有一個特殊的安全防範 機制。
如何在程序中檢查全局變量 DEBUG 纔不會出現 ReferenceError 錯誤。這時 typeof 的 安全防範機制就成了咱們的好幫手;還有一種不用經過 typeof 的安全防範機制的方法,就是檢查全部全局變量是不是全局對象 的屬性,瀏覽器中的全局對象是 window。
使用 delete 運算符能夠將單元從數組中刪除,可是請注意,單元刪除後,數 組的 length 屬性並不會發生變化。數組經過數字進行索引,但有趣的是它們也是對象,因此也能夠包含字符串鍵值和屬性 (但這些並不計算在數組長度內): 若是字符串鍵值可以被強制類型轉換爲十進制數字的話,它 就會被看成數字索引來處理。
字符串不可變是指字符串的成員函數不會改變其原始值,而是建立並返回一個新的字符 串。而數組的成員函數都是在其原始值上進行操做。
字符串和數組的確很類似,它們都是類數組,都有 length 屬性以及 indexOf(..)(從 ES5 開始數組支持此方法)和 concat(..) 方法。
特別大和特別小的數字默認用指數格式顯示,與 toExponential() 函數的輸出結果相同。 toPrecision(..) 方法用來指定有效數位的顯示位數。
不過對於 . 運算符須要給予特別注 意,由於它是一個有效的數字字符,會被優先識別爲數字常量的一部分,而後纔是對象屬 性訪問運算符。
// 無效語法: 
42.toFixed( 3 ); // SyntaxError 
// 下面的語法都有效:
(42).toFixed( 3 ); // "42.000" 
0.42.toFixed( 3 ); // "0.420" 
42..toFixed( 3 ); // "42.000"
a | 0 能夠將變量 a 中的數值轉換爲 32 位有符號整數,由於數位運算符 | 只適用於 32 位 整數(它只關心 32 位之內的值,其餘的數位將被忽略)。所以與 0 進行操做便可截取 a 中 的 32 位數位。
  • null 指空值(empty value)
  • undefined 指沒有值(missing value)
null 是一個特殊關鍵字,不是標識符,咱們不能將其看成變量來使用和賦值。然而 undefined 倒是一個標識符,能夠被看成變量來使用和賦值。

03/15

void 並不改變表達式的結果, 只是讓表達式不返回值。void 0、void 1 和 undefined 之間並無實質上的區別。  
NaN 是一個「警惕值」(sentinel value,有特殊用途的常規值),用於指出數字類型中的錯誤 狀況,即「執行數學運算沒有成功,這是失敗後返回的結果」。
NaN 是一個特殊值,它和自身不相等,是惟一一個非自反(自反,reflexive,即 x === x 不 成立)的值。而 NaN != NaN 爲 true,isNaN(..) 有一個嚴重的缺陷,它的檢查方式過於死板,就 是「檢查參數是否不是 NaN,也不是數字」。 
要區分 -0 和 0,不能僅僅依賴開發調試窗口的顯示結果,還須要作一些特殊處理: function isNegZero(n) { n = Number( n ); return (n === 0) && (1 / n === -Infinity); }
  • 簡單值(即標量基本類型值,scalar primitive)老是經過值複製的方式來賦值 / 傳遞,包括 null、undefined、字符串、數字、布爾和 ES6 中的 symbol。
  • 複合值(compound value)——對象(包括數組和封裝對象)和函數,則老是經過引用複製的方式來賦值 / 傳遞。  因爲引用指向的是值自己而非變量,因此一個引用沒法更改另外一個引用的指向。
function foo(x) {
    x.push(4);
    x; // [1,2,3,4] 
    x = [4, 5, 6];
    x.push(7);
    x; // [4,5,6,7]
}
var a = [1, 2, 3];
foo(a);
a; // 是[1,2,3,4],不是[4,5,6,7]
咱們不能經過引用 x 來更改引用 a 的指向,只能更改 a 和 x 共同指向的值。
slice(..) 不帶參數會返回當前數組的一個淺複本(shallow copy)。

03/16

經常使用的原生函數有:
  • String()
  • Number()
  • Boolean()
  • Array()
  • Object()
  • Function()
  • RegExp()
  • Date()
  • Error()
  • Symbol()——ES6 中新加入的!
全部 typeof 返回值爲 "object" 的對象(如數組)都包含一個內部屬性 [[Class]](咱們可 以把它看做一個內部的分類,而非傳統的面向對象意義上的類)。這個屬性沒法直接訪問, 通常經過 Object.prototype.toString(..) 來查看。 
雖然 Null() 和 Undefined() 這樣的原生構造函數並不存在,可是內部 [[Class]] 屬性值仍 然是 "Null" 和 "Undefined"。
通常狀況下,咱們不須要直接使用封裝對象。最好的辦法是讓 JavaScript 引擎本身決定什 麼時候應該使用封裝對象。
若是想要自行封裝基本類型值,可使用 Object(..) 函數(不帶 new 關鍵字)。 
若是想要獲得封裝對象中的基本類型值,可使用 valueOf() 函數。在須要用到封裝對象中的基本類型值的地方會發生隱式拆封。
強烈建議使用常量形式(如 /^a*b+/g)來定義正則表達式,這樣不只語法簡單,執行效率 也更高,由於 JavaScript 引擎在代碼執行前會對它們進行預編譯和緩存。與前面的構造函 數不一樣,RegExp(..) 有時仍是頗有用的,好比動態定義正則表達式時。
建立錯誤對象(error object)主要是爲了得到當前運行棧的上下文(大部分 JavaScript 引擎 經過只讀屬性 .stack 來訪問)。棧上下文信息包括函數調用棧信息和產生錯誤的代碼行號, 以便於調試(debug)。
符號並不是對象,而是一種簡單標量基本類型。

03/17

將值從一種類型轉換爲另外一種類型一般稱爲類型轉換(type casting),這是顯式的狀況隱 式的狀況稱爲強制類型轉換(coercion)。
JavaScript 中的強制類型轉換老是返回標量基本類型值,如字 符串、數字和布爾值,不會返回對象和函數。
類型轉換髮生在靜態類型語言的編譯階段,而強制類型轉換則發生在動態類型語言的運行時(runtime)。 在 JavaScript 中一般將它們統稱爲強制類型轉換。
基本類型值的字符串化規則爲:null 轉換爲 "null",undefined 轉換爲 "undefined",true 轉換爲 "true"。數字的字符串化則遵循通用規則,不過那些極小和極大的 數字使用指數形式。 
對普通對象來講,除非自行定義,不然 toString()(Object.prototype.toString())返回內部屬性 [[Class]] 的值,如 "[object Object]"。  
數組的默認 toString() 方法通過了從新定義,將全部單元字符串化之後再用 "," 鏈接起 來, toString() 能夠被顯式調用,或者在須要字符串化時自動調用。
全部安全的 JSON 值(JSON-safe)均可以使用 JSON.stringify(..) 字符串化。安全的 JSON 值是指可以呈現爲有效 JSON 格式的值。 爲了簡單起見,咱們來看看什麼是不安全的 JSON 值。undefined、function、symbol (ES6+)和包含循環引用(對象之間相互引用,造成一個無限循環)的對象都不符合 JSON 結構標準,支持 JSON 的語言沒法處理它們。  JSON.stringify(..) 在對象中遇到 undefined、function 和 symbol 時會自動將其忽略,在 數組中則會返回 null(以保證單元位置不變)。  對包含循環引用的對象執行 JSON.stringify(..) 會出錯。  
若是對象中定義了 toJSON() 方法,JSON 字符串化時會首先調用該方法,而後用它的返回 值來進行序列化。 若是要對含有非法 JSON 值的對象作字符串化,或者對象中的某些值沒法被序列化時,就 須要定義 toJSON() 方法來返回一個安全的 JSON 值。  
(1) 字符串、數字、布爾值和 null 的 JSON.stringify(..) 規則與 ToString 基本相同。
(2) 若是傳遞給 JSON.stringify(..) 的對象中定義了 toJSON() 方法,那麼該方法會在字符 串化前調用,以便將對象轉換爲安全的 JSON 值。
var a = {
    b: 42,
    c: "42",
    d: [1, 2, 3]
};
JSON.stringify(a, null, 3);
/* 
"{
   "b": 42,
   "c": "42",
   "d": [
      1,
      2,
      3
   ]
}"
*/

var a = {
    b: 42,
    c: "42",
    d: [1, 2, 3]
};
JSON.stringify(a, ["b", "c"]); // "{"b":42,"c":"42"}" 
JSON.stringify(a, function(k, v) {
    if (k !== "c") return v;
}); // "{"b":42,"d":[1,2,3]}"

03/18

ToNumber  
其中 true 轉換爲 1,false 轉換爲 0。undefined 轉換爲 NaN,null 轉換爲 0。  
對象(包括數組)會首先被轉換爲相應的基本類型值,若是返回的是非數字的基本類型 值,則再遵循以上規則將其強制轉換爲數字。 爲了將值轉換爲相應的基本類型值,抽象操做 ToPrimitive(參見 ES5 規範 9.1 節)會首先 (經過內部操做 DefaultValue,參見 ES5 規範 8.12.8 節)檢查該值是否有 valueOf() 方法。 若是有而且返回基本類型值,就使用該值進行強制類型轉換。若是沒有就使用 toString() 的返回值(若是存在)來進行強制類型轉換。 若是 valueOf() 和 toString() 均不返回基本類型值,會產生 TypeError 錯誤。
從 ES5 開始,使用 Object.create(null) 建立的對象 [[Prototype]] 屬性爲 null,而且沒 有 valueOf() 和 toString() 方法,所以沒法進行強制類型轉換。 

03/19

ToBoolean
1. 假值(falsy value)
(1) 能夠被強制類型轉換爲 false 的值 (2) 其餘(被強制類型轉換爲 true 的值)
 JavaScript 規範具體定義了一小撮能夠被強制類型轉換爲 false 的值。
如下這些是假值:
  • undefined
  • null
  • false
  • +0、-0 和 NaN
  • ""
從邏輯上說,假值列表之外的都應該是真值(truthy)。但 JavaScript 規範對此並無明確 定義,只是給出了一些示例,例如規定全部的對象都是真值,咱們能夠理解爲假值列表以 外的值都是真值。
 假值對象(falsy object)  
瀏覽器在某些特定狀況下,在常規 JavaScript 語法基礎上本身建立了一些外來(exotic) 值,這些就是「假值對象」。 假值對象看起來和普通對象並沒有二致(都有屬性,等等),但將它們強制類型轉換爲布爾 值時結果爲 false。
最多見的例子是 document.all,它是一個類數組對象,包含了頁面上的全部元素,由 DOM(而不是 JavaScript 引擎)提供給 JavaScript 程序使用。它之前曾是一個真正意義上 的對象,布爾強制類型轉換結果爲 true,不過如今它是一個假值對象。

03/20

1. 日期顯式轉換爲數字
一元運算符 + 的另外一個常見用途是將日期(Date)對象強制類型轉換爲數字,返回結果爲 Unix 時間戳,以微秒爲單位(從 1970 年 1 月 1 日 00:00:00 UTC 到當前時間)。
ES5 中新加入的靜態方法 Date.now()。
2. 奇特的 ~ 運算符
字位運算符只適用於 32 位整數,運算符會強制操做數使用 32 位 格式。ToInt32 首先執行 ToNumber 強制類型轉換,好比 "123" 會先被轉換爲 123,而後再執行 ToInt32。
Math.floor( -49.6 ); // -50 
~~-49.6; // -49
解析容許字符串中含有非數字字符,解析按從左到右的順序,若是遇到非數字字符就停 止。而轉換不容許出現非數字字符,不然會失敗並返回 NaN。  
var a = "42"; var b = "42px";
Number( a ); // 42 
parseInt( a ); // 42 
Number( b ); // NaN 
parseInt( b ); // 42
parseInt(..) 針對的是字符串值,非字符串參數會首先被強制類型轉換爲字符串。 若是沒有第二個參數來指定轉換的 基數(又稱爲 radix),parseInt(..) 會根據字符串的第一個字符來自行決定基數。  若是第一個字符是 x 或 X,則轉換爲十六進制數字。若是是 0,則轉換爲八進制數字。 從 ES5 開始 parseInt(..) 默認轉換爲十進制數,除非另外指定。若是你的代碼須要在 ES5 以前的環境運行,請記得將第二個參數設置爲 10。

03/24

parseInt( 1/0, 19 ); // 18  
一元運算符 ! 顯式地將值強制類型轉換爲布爾值。可是它同時還將 真值反轉爲假值(或者將假值反轉爲真值)。因此顯式強制類型轉換爲布爾值最經常使用的方 法是 !!,由於第二個 ! 會將結果反轉回原值。 建議使用 Boolean(a) 和 !!a 來進行顯式強制類型轉換。

03/25

簡單來講就是,若是 + 的其中一個操做數是字符串, 則執行字符串拼接;不然執行數字加法。  a + ""(隱式)和前面的 String(a)(顯式)之間有一個細微的差異須要注意。根據 ToPrimitive 抽象操做規則,a + "" 會對 a 調用 valueOf() 方法,而後經過 ToString 抽象 操做將返回值轉換爲字符串。而 String(a) 則是直接調用 ToString()。
「操做數選擇器」 :
|| 和 && 首先會對第一個操做數(a 和 c)執行條件判斷,若是其不是布爾值(如上例)就 先進行 ToBoolean 強制類型轉換,而後再執行條件判斷。 對於 || 來講,若是條件判斷結果爲 true 就返回第一個操做數(a 和 c)的值,若是爲 false 就返回第二個操做數(b)的值。 && 則相反,若是條件判斷結果爲 true 就返回第二個操做數(b)的值,若是爲 false 就返 回第一個操做數(a 和 c)的值。 || 和 && 返回它們其中一個操做數的值,而非條件判斷的結果(其中可能涉及強制類型轉 換)。c && b 中 c 爲 null,是一個假值,所以 && 表達式的結果是 null(即 c 的值),而非 條件判斷的結果 false。
但 ES6 中引入了符號類型,它的強制類型轉換有一個坑,在這裏有必要提一下。ES6 容許 從符號到字符串的顯式強制類型轉換,然而隱式強制類型轉換會產生錯誤。符號不可以被強制類型轉換爲數字(顯式和隱式都會產生錯誤),但能夠被強制類型轉換 爲布爾值(顯式和隱式結果都是 true)。

03/29

== 容許在相等比較中進行強制類型轉換,而 === 不容許。== 和 === 都會檢查操做數的類型。區別在於操做數類型不一樣時它們的處理方 式不一樣。

03/30

== 在比較兩個不一樣類型的值時會發生隱式強制類型轉換,會將其中之 一或二者都轉換爲相同的類型後再進行比較。  
(1) 若是 Type(x) 是數字,Type(y) 是字符串,則返回 x == ToNumber(y) 的結果。
(2) 若是 Type(x) 是字符串,Type(y) 是數字,則返回 ToNumber(x) == y 的結果。
 
(1) 若是 Type(x) 是布爾類型,則返回 ToNumber(x) == y 的結果;
(2) 若是 Type(y) 是布爾類型,則返回 x == ToNumber(y) 的結果。
 
null 和 undefined 之間的 == 也涉及隱式強制類型轉換。ES5 規範 11.9.3.2-3 規定:
(1) 若是 x 爲 null,y 爲 undefined,則結果爲 true。
(2) 若是 x 爲 undefined,y 爲 null,則結果爲 true。
var a = null;
var b;
a == b; // true 
a == null; // true 
b == null; // true 
a == false; // false 
b == false; // false 
a == ""; // false 
b == ""; // false 
a == 0; // false 
b == 0; // false
null 和 undefined 之間的強制類型轉換是安全可靠的,上例中除 null 和 undefined 之外的 其餘值均沒法獲得假陽(false positive)結果。我的認爲經過這種方式將 null 和 undefined 做爲等價值來處理比較好。  
(1) 若是 Type(x) 是字符串或數字,Type(y) 是對象,則返回 x == ToPrimitive(y) 的結果;
(2) 若是 Type(x) 是對象,Type(y) 是字符串或數字,則返回 ToPromitive(x) == y 的結果。
以前介紹過的 ToPromitive 抽象操做的全部特性(如 toString()、valueOf()) 在這裏都適用。若是咱們須要自定義 valueOf() 以便從複雜的數據結構返回 一個簡單值進行相等比較,這些特性會頗有幫助。

03/31

"0" == null; //
false "0" == undefined; // false
"0" == false; // true -- 暈!
"0" == NaN; // false
"0" == 0; // true
"0" == ""; // false
false == null; // false
false == undefined; // false
false == NaN; // false
false == 0; // true -- 暈!
false == ""; // true -- 暈!
false == []; // true -- 暈!
false == {}; // false
"" == null; // false
"" == undefined; // false
"" == NaN; // false
"" == 0; // true -- 暈!
"" == []; // true -- 暈!
"" == {}; // false
0 == null; // false
0 == undefined; // false
0 == NaN; // false
0 == []; // true -- 暈!
0 == {}; // false

04/05

""、"\n"(或者 " " 等其餘空格組合)等空字符串被 ToNumber 強制類型轉換 爲 0。
咱們要對 == 兩邊的值認真推敲,如下兩個原則可讓咱們有效地避免出錯。
  • 若是兩邊的值中有 true 或者 false,千萬不要使用 ==。
  • 若是兩邊的值中有 []、"" 或者 0,儘可能不要使用 ==。 這時最好用 === 來避免不經意的強制類型轉換。這兩個原則可讓咱們避開幾乎全部強制 類型轉換的坑。
a < b : 
比較雙方首先調用 ToPrimitive,若是結果出現非字符串,就根據 ToNumber 規則將雙方強 制類型轉換爲數字來進行比較。  若是比較雙方都是字符串,則按字母順序來進行比較。
var a = { b: 42 }; 
var b = { b: 43 }; 
a < b; // false
a == b; // false 
a > b; // false 
a <= b; // true 
a >= b; // true  
由於根據規範 a <= b 被處理爲 b < a,而後將結果反轉。由於 b < a 的結果是 false,所 以 a <= b 的結果是 true。  
 <= 應該是「小於或者等於",實際上 JavaScript 中 <= 是 「不大於」的意思(即 !(a > b),處理爲 !(b < a))。同理 a >= b 處理爲 b <= a。

04/06

然而 JavaScript 經過標籤跳轉可以實現 goto 的部分功能。continue 和 break 語句均可以帶 一個標籤,所以可以像 goto 那樣進行跳轉。
// 標籤爲foo的循環 foo: 
for (var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
        // 若是j和i相等,繼續外層循環 
        if (j == i) {
            // 跳轉到foo的下一個循環 
            continue foo;
        }
        // 跳過奇數結果
        if ((j * i) % 2 == 1) {
            // 繼續內層循環(沒有標籤的) 
            continue;
        }
        console.log(i, j);
    }
}
contine foo 並非指「跳轉到標籤 foo 所在位置繼續執行」,而是「執行 foo 循環的下一輪循環」。因此這裏的 foo 並不是 goto。
帶標籤的循環跳轉一個更大的用處在於,和 break __ 一塊兒使用能夠實現從內層循環跳轉到 外層循環。沒有它們的話實現起來有時會很是麻煩: 
// 標籤爲foo的循環 foo: 
for (var i = 0; i < 4; i++) {
    for (var j = 0; j < 4; j++) {
        if ((i * j) >= 3) {
            console.log("stopping!", i, j);
            break foo;
        }
        console.log(i, j);
    }
}
break foo 不是指「跳轉到標籤 foo 所在位置繼續執行」,而是「跳出標籤 foo 所在的循環 / 代碼塊,繼續執行後面的代碼」。所以它並不是傳統意義上的 goto。
標籤也能用於非循環代碼塊,但只有 break 才能夠。咱們能夠對帶標籤的代碼塊使用 break ___,可是不能對帶標籤的非循環代碼塊使用 continue ___,也不能對不帶標籤的代 碼塊使用 break。
JSON 的確是 JavaScript 語法的一個子集,可是 JSON 自己並非合法的 JavaScript 語法。 JSON-P(將 JSON 數據封裝爲函數調用, 好比 foo({"a":42}))經過將 JSON 數據傳遞給函數來實現對其的訪問。
事實上 JavaScript 沒有 else if,但 if 和 else 只包含單條語句的時候能夠省略代碼塊的 { }。下面的代碼你必定不會陌生:
if (a) doSomething(a);
else 也是如此,因此咱們常常用到的 else if 其實是這樣的:
if (a) {
    // .. 
} else {
    if (b) {
        // .. 
    } else {
        // .. 
    }
}
if (b) { .. } else { .. } 其實是跟在 else 後面的一個單獨的語句,因此帶不帶 { } 都 能夠。換句話說,else if 不符合前面介紹的編碼規範,else 中是一個單獨的 if 語句。 else if 極爲常見,能省掉一層代碼縮進,因此很受青睞。但這只是咱們本身發明的用法, 切勿想固然地認爲這些都屬於 JavaScript 語法的範疇。  

04/07

有時 JavaScript 會自動爲代碼行補上缺失的分號,即自動分號插入(Automatic Semicolon Insertion,ASI)。  請注意,ASI 只在換行符處起做用,而不會在代碼行的中間插入分號。 若是 JavaScript 解析器發現代碼行可能由於缺失分號而致使錯誤,那麼它就會自動補上分 號。而且,只有在代碼行末尾與換行符之間除了空格和註釋以外沒有別的內容時,它纔會 這樣作。 語法規定 do..while 循環後面必須帶 ;,而 while 和 for 循環後則不須要。大多數開發人員 都不記得這一點,此時 ASI 就會自動補上分號。 ASI 是一個語法糾錯機制。若將換行符看成有意義的字符來對待,就會遇到不少 問題。
向函數傳遞參數時,arguments 數組中的對應單元會和命名參數創建關聯(linkage)以得 到相同的值。相反,不傳遞參數就不會創建關聯。
var a = "42";
switch (true) {
    case a == 10:
        console.log("10 or '10'");
        break;
    case a == 42;
    console.log("42 or '42'");
    break;
    default:
        // 永遠執行不到這裏 
} // 42 or '42'

04/09

還有一個不太爲人所知的事實是:因爲瀏覽器演進的歷史遺留問題,在建立帶有 id 屬性 的 DOM 元素時也會建立同名的全局變量。例如: <div id="foo"></div> 以及: if (typeof foo == "undefined") { foo = 42; // 永遠也不會運行 } console.log( foo ); // HTML元素 你可能認爲只有 JavaScript 代碼才能建立全局變量,而且習慣使用 typeof 或 .. in window 來檢測全局變量。可是如上例所示,HTML 頁面中的內容也會產生全局變量,而且稍不注 意就很容易讓全局變量檢查錯誤百出。
JavaScript 規範對於函數中參數的個數,以及字符串常量的長度等並無限制;可是因爲 JavaScript 引擎實現各異,規範在某些地方有一些限制。
  • 字符串常量中容許的最大字符數(並不是只是針對字符串值);
  • 能夠做爲參數傳遞到函數中的數據大小(也稱爲棧大小,以字節爲單位);
  • 函數聲明中的參數個數;
  • 未經優化的調用棧(例如遞歸)的最大層數,即函數調用鏈的最大長度;
  • JavaScript 程序以阻塞方式在瀏覽器中運行的最長時間(秒);
  • 變量名的最大長度。
相關文章
相關標籤/搜索