摘要:對基本包裝類型的實例調用會返回,而且所有基本包裝類型的對象都會被轉換為布爾值。構造函數也會像工廠方法一樣,根據傳入值的類型返回相應基本包裝類型的實例。要注意的是,使用調用基本包裝類型的構造函數,與直接調用同名的轉型函數是不一樣的。
為了便于操作基本類型值,JavaScript 還提供了3個特殊的引用類型:Boolean、Number 和 String。實際上,每當讀取一個基本類型值的時候,后臺就會創建一個對應的基本包裝類型的對象,從而讓我們能夠調用一些方法來操作這些數據。來看下面的例子。
var s1 = "some text"; var s2 = s1.substring(2);
這個例子中的變量 s1 包含一個字符串,字符串當然是基本類型值。而下一行調用了 s1 的 substring() 方法,并將返回的結果保存在了 s2 中。我們知道,基本類型值不是對象,因而從邏輯上講它們不應該有方法(盡管如我們所愿,它們確實有方法)。其實,為了讓我們實現這種直觀的操作,后臺已經自動完成了一系列的處理。當第二行代碼訪問 s1 時,訪問過程處于一種讀取模式,也就是要從內存中讀取這個字符串的值。而在讀取模式中訪問字符串時,后臺都會自動完成下列處理。
創建 String 類型的一個實例;
在實例上調用指定的方法;
銷毀這個實例。
可以將以上三個步驟想象成是執行了下列 JavaScript 代碼。
var s1 = new String("some text"); var s2 = s1.substring(2); s1 = null;
經過此番處理,基本的字符串值就變得跟對象一樣了。而且,上面這三個步驟也分別適用于 Boolean 和 Number 類型對應的布爾值和數字值。
引用類型與基本包裝類型的主要區別就是對象的生存期。使用 new 操作符創建的引用類型的實例,在執行流離開當前作用域之前都一直保存在內存中。而自動創建的基本包裝類型的對象,則只存在于一行代碼的執行瞬間,然后立即被銷毀。這意味著我們不能在運行時為基本類型值添加屬性和方法。來看下面的例子:
var s1 = "some text"; s1.color = "red"; console.log(s1.color); // undefined
當然,可以顯式地調用 Boolean、Number 和 String 來創建基本包裝類型的對象。不過,應該在絕對必要的情況下再這樣做,因為這種做法很容易讓人分不清自己是在處理「基本類型」還是「引用類型」的值。對基本包裝類型的實例調用 typeof 會返回 "object",而且所有基本包裝類型的對象都會被轉換為布爾值 true。
Object 構造函數也會像工廠方法一樣,根據傳入值的類型返回相應基本包裝類型的實例。例如:
var obj = new Object("some text"); console.log(obj instanceof String); // true
把字符串傳給 Object 構造函數,就會創建 String 的實例;而傳入數值參數會得到 Number 的實例,傳入布爾值參數就會得到 Boolean 的實例。
要注意的是,使用 new 調用基本包裝類型的構造函數,與直接調用同名的轉型函數是不一樣的。 例如:
var value = "25"; var number = Number(value); // 轉型函數 console.log(typeof number); // "number" var obj = new Number(value); // 構造函數 console.log(typeof obj); // "object"
盡管我們不建議顯式地創建基本包裝類型的對象,但它們操作基本類型值的能力還是相當重要的。而每個基本包裝類型都提供了操作相應值的便捷方法。
Boolean 類型Boolean 類型是與布爾值對應的引用類型。要創建 Boolean 對象,可以像下面這樣調用 Boolean 構造函數并傳入 true 或 false 值。
var booleanObject = new Boolean(true);
Boolean 類型的實例重寫了 valueOf() 方法,返回基本類型值 true 或 false;重寫了 toString() 方法,返回字符串 "true" 和 "false"。可是,Boolean 對象在 JavaScript 中的用處不大,因為它經常會造成人們的誤解。其中最常見的問題就是在布爾表達式中使用 Boolean 對象,例如:
var falseObject = new Boolean(false); var result = falseObject && true; console.log(result); // true var falseValue = false; result = falseValue && true; console.log(result); // false
在這個例子中,我們使用 false 值創建了一個 Boolean 對象。然后,將這個對象與基本類型值 true 構成了邏輯與表達式。在布爾運算中,false && true 等于 false。可是,示例中的這行代碼是對 falseObject 而不是對它的值 false 進行求值。布爾表達式中的所有對象都會被轉換為 true,因此 falseObject 對象在布爾表達式中代表的是 true。結果,true && true 當然就等于 true 了。
基本類型與引用類型的布爾值還有兩個區別。首先,typeof 操作符對基本類型返回 "boolean",而對引用類型返回 "object"。其次,由于 Boolean 對象是 Boolean 類型的實例,所以使用 instanceof 操作符測試 Boolean 對象會返回 true,而測試基本類型的布爾值則返回 false。例如:
console.log(typeof falseObject); // object console.log(typeof falseValue); // boolean console.log(falseObject instanceof Boolean); // true console.log(falseValue instanceof Boolean); // false
理解基本類型的布爾值與 Boolean 對象之間的區別非常重要,我們的建議是永遠不要使用 Boolean 對象。
Number 類型Number 是與數字值對應的引用類型。要創建 Number 對象,可以在調用 Number 構造函數時向其中傳遞相應的數值。下面是一個例子。
var numberObject = new Number(10);
與 Boolean 類型一樣,Number 類型也重寫了 valueOf()、toLocaleString() 和 toString() 方法。重寫后的 valueOf() 方法返回對象表示的基本類型的數值,另外兩個方法則返回字符串形式的數值。可以為 toString() 方法傳遞一個表示基數的參數,告訴它返回幾進制數值的字符串形式,如下面的例子所示。
var num = 10; console.log(num.toString()); // "10" console.log(num.toString(2)); // "1010" console.log(num.toString(8)); // "12" console.log(num.toString(10)); // "10" console.log(num.toString(16)); // "a"
除了繼承的方法之外,Number 類型還提供了一些用于將數值格式化為字符串的方法。其中,toFixed() 方法會按照指定的小數位返回數值的字符串表示,例如:
var num = 10; console.log(num.toFixed(2)); // "10.00"
這里給 toFixed() 方法傳入了數值 2,意思是顯示幾位小數。于是,這個方法返回了 "10.00",即以 0 填補了必要的小數位。如果數值本身包含的小數位比指定的還多,那么接近指定的最大小數位的值就會舍入,如下面的例子所示。
var num = 10.005; console.log(num.toFixed(2)); // "10.01"
能夠自動舍入的特性,使得 toFixed() 方法很適合處理貨幣值。
但需要注意的是,不同瀏覽器給這個方法設定的舍入規則可能會有所不同。
在給 toFixed() 傳入0的情況下,IE8 及之前版本不能正確舍入范圍在{(-0.94,-0.5],[0.5,0.94)}之間的值。對于這個范圍內的值,IE8 會返回0,而不是-1或1;其他瀏覽器都能返回正確的值。IE9 修復了這個問題。
toFixed() 方法可以表示帶有0到20個小數位的數值。但這只是標準實現的范圍,有些瀏覽器也可能支持更多位數。
另外可用于格式化數值的方法是 toExponential(),該方法返回以指數表示法(也稱 e 表示法)表示的數值的字符串形式。與 toFixed() 一樣,toExponential() 也接收一個參數,而且該參數同樣也是指定輸出結果中的小數位數。看下面的例子。
var num = 10; console.log(num.toExponential(1)); // "1.0e+1"
以上代碼輸出了 "1.0e+1";不過,這么小的數值一般不必使用 e 表示法。如果你想得到表示某個數值的最合適的格式,就應該使用 toPrecision() 方法。
對于一個數值來說,toPrecision() 方法可能會返回固定大小(fixed)格式,也可能返回指數(exponential)格式;具體規則是看哪種格式最合適。這個方法接收一個參數,即表示數值的所有數字的位數(不包括指數部分)。請看下面的例子。
var num = 99; console.log(num.toPrecision(1)); // "1e+2" console.log(num.toPrecision(2)); // "99" console.log(num.toPrecision(3)); // "99.0"
以上代碼首先完成的任務是以一位數來表示 99,結果是 "1e+2",即 100。因為一位數無法準確地表示 99,因此 toPrecision() 就將它向上舍入為 100,這樣就可以使用一位數來表示它了。而接下來的用兩位數表示 99,當然還是 "99"。最后,在想以三位數表示 99 時,toPrecision() 方法返回了 "99.0"。實際上,toPrecision() 會根據要處理的數值決定到底是調用 toFixed() 還是調用 toExponential()。而這三個方法都可以通過向上或向下舍入,做到以最準確的形式來表示帶有正確小數位的值。
toPrecision() 方法可以表現1到21位小數。但這只是標準實現的范圍,有些瀏覽器也可能支持更多位數。
與 Boolean 對象類似,Number 對象也以后臺方式為數值提供了重要的功能。但與此同時,我們仍然不建議直接實例化 Number 類型,而原因與顯式創建 Boolean 對象一樣。具體來講,就是在使用 typeof 和 instanceof 操作符測試基本類型數值與引用類型數值時,得到的結果完全不同,如下面的例子所示。
var numberObject = new Number(10); var numberValue = 10; console.log(typeof numberObject); // "object" console.log(typeof numberValue); // "number" console.log(numberObject instanceof Number); // true console.log(numberValue instanceof Number); // falseString 類型
String 類型是字符串的對象包裝類型,可以像下面這樣使用 String 構造函數來創建。
var stringObject = new String("hello world");
String 對象的方法也可以在所有基本的字符串值中訪問到。其中,繼承的 valueOf()、toLocaleString() 和 toString() 方法,都返回對象所表示的基本字符串值。
String 類型的每個實例都有一個 length 屬性,表示字符串中包含多個字符。來看下面的例子。
var stringValue = "hello world"; console.log(stringValue.length); // 11
應該注意的是,即使字符串中包含雙字節字符(不是占一個字節的 ASCII 字符),每個字符也仍然算一個字符。例如:
var stringValue = "大家好"; console.log(stringValue.length); // 3
String 類型提供了很多方法,用于輔助完成對 JavaScript 中字符串的解析和操作。
字符方法兩個用于訪問字符串中特定字符的方法是:charAt() 和 charCodeAt()。這兩個方法都接收一個參數,即基于0的字符位置。其中,charAt() 方法以單字符字符串的形式返回給定位置的那個字符(JavaScript 中沒有字符類型)。例如:
var stringValue = "hello world"; console.log(stringValue.charAt(1)); // "e"
如果你想得到的不是字符而是字符編碼,那么就要像下面這樣使用 charCodeAt() 了。例如:
var stringValue = "hello world"; console.log(stringValue.charCodeAt(1)); // 101,101是小寫字母"e"的字符編碼
ECMAScript 5 還定義了另一個訪問個別字符的方法。在支持瀏覽器中,可以使用方括號加數字索引來訪問字符串中的特定字符,如下面的例子所示。
var stringValue = "hello world"; console.log(stringValue[1]); // "e"字符串操作方法
下面介紹與操作字符串有關的幾個方法。第一個就是 concat(),用于將一或多個字符串拼接起來,返回拼接得到的新字符串。先來看一個例子。
var stringValue = "hello "; var result = stringValue.concat("world"); console.log(result); // "hello world" console.log(stringValue); // "hello"
實際上,concat() 方法可以接受任意多個參數,也就是說可以通過它拼接任意多個字符串。再看一個例子:
var stringValue = "hello "; var result = stringValue.concat("world", "!"); console.log(result); // "hello world!" console.log(stringValue); // "hello"
雖然 concat() 是專門用來拼接字符串的方法,但實踐中使用更多的還是加號操作符 + 。而且,使用加號操作符 + 在大多數情況下都比使用 concat()方法要簡便易行(特別是在拼接多個字符串的情況下)。
JavaScript 還提供了三個基于子字符串創建新字符串的方法:slice()、substr() 和 substring()。這三個方法都會返回被操作字符串的一個子字符串,而且也都接受一或兩個參數。第一個參數指定子字符串的開始位置,第二個參數(在指定的情況下)表示子字符串到哪里結束。具體來說,slice() 和 substring() 的第二個參數指定的是子字符串最后一個字符后面的位置。而 substr() 的第二個參數指定的則是返回的字符個數。如果沒有給這些方法傳遞第二個參數,則將字符串的長度作為結束位置。與 concat() 方法一樣,slice()、substr() 和 substring()也不會修改字符串本身的值,它們只是返回一個基本類型的字符串值,對原始字符串沒有任何影響。請看下面的例子。
var stringValue = "hello world"; console.log(stringValue.slice(3)); // "lo world" console.log(stringValue.substring(3)); // "lo world" console.log(stringValue.substr(3)); // "lo world" console.log(stringValue.slice(3, 7)); // "lo w" console.log(stringValue.substring(3,7)); // "lo w" console.log(stringValue.substr(3, 7)); // "lo worl"
在傳遞給這些方法的參數是負值的情況下,它們的行為就不盡相同了。其中,slice() 方法會將傳入的負值與字符串的長度相加,substr() 方法將負的第一個參數加上字符串的長度,而將負的第二個參數轉換為0。最后,substring() 方法會把所有負值參數都轉換為0。下面來看例子。
var stringValue = "hello world"; console.log(stringValue.slice(-3)); // "rld" console.log(stringValue.substring(-3)); // "hello world" console.log(stringValue.substr(-3)); // "rld" console.log(stringValue.slice(3, -4)); // "lo w" console.log(stringValue.substring(3, -4)); // "hel" console.log(stringValue.substr(3, -4)); //""(空字符串)字符串位置方法
有兩個可以從字符串中查找子字符串的方法:indexOf() 和 lastIndexOf()。這兩個方法都是從一個字符串中搜索給定的子字符串,然后返子字符串的位置(如果沒有找到該子字符串,則返回-1)。這兩個方法的區別在于:indexOf() 方法從字符串的開頭向后搜索子字符串,而 lastIndexOf() 方法是從字符串的末尾向前搜索子字符串。還是來看一個例子吧。
var stringValue = "hello world"; console.log(stringValue.indexOf("o")); // 4 console.log(stringValue.lastIndexOf("o")); // 7
這兩個方法都可以接收可選的第二個參數,表示從字符串中的哪個位置開始搜索。換句話說,indexOf()會從該參數指定的位置向后搜索,忽略該位置之前的所有字符;而lastIndexOf()則會從指定的位置向前搜索,忽略該位置之后的所有字符。看下面的例子。
var stringValue = "hello world"; console.log(stringValue.indexOf("o", 6)); // 7 console.log(stringValue.lastIndexOf("o", 6)); // 4
在使用第二個參數的情況下,可以通過循環調用 indexOf() 或 lastIndexOf() 來找到所有匹配的子字符串,如下面的例子所示:
var stringValue = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; var positions = new Array(); var pos = stringValue.indexOf("e"); while(pos > -1){ positions.push(pos); pos = stringValue.indexOf("e", pos + 1); } console.log(positions); // "3,24,32,35,52"trim() 方法
ECMAScript 5 為所有字符串定義了 trim() 方法。這個方法會創建一個字符串的副本,刪除前置及后綴的所有空格,然后返回結果。例如:
var stringValue = " hello world "; var trimmedStringValue = stringValue.trim(); console.log(stringValue); // " hello world " console.log(trimmedStringValue); // "hello world"字符串大小寫轉換方法
JavaScript 中涉及字符串大小寫轉換的方法有4個:toLowerCase()、toLocaleLowerCase()、toUpperCase() 和 toLocaleUpperCase()。其中,toLowerCase() 和 toUpperCase() 是兩個經典的方法,借鑒自 java.lang.String 中的同名方法。而 toLocaleLowerCase() 和 toLocaleUpperCase() 方法則是針對特定地區的實現。對有些地區來說,針對地區的方法與其通用方法得到的結果相同,但少數語言(如土耳其語)會為 Unicode 大小寫轉換應用特殊的規則,這時候就必須使用針對地區的方法來保證實現正確的轉換。以下是幾個例子。
var stringValue = "hello world"; console.log(stringValue.toLocaleUpperCase()); // "HELLO WORLD" console.log(stringValue.toUpperCase()); // "HELLO WORLD" console.log(stringValue.toLocaleLowerCase()); // "hello world" console.log(stringValue.toLowerCase()); // "hello world"
一般來說,在不知道自己的代碼將在哪種語言環境中運行的情況下,還是使用針對地區的方法更穩妥一些。
字符串的模式匹配方法String 類型定義了幾個用于在字符串中匹配模式的方法。第一個方法就是 match(),在字符串上調用這個方法,本質上與調用 RegExp 的 exec() 方法相同。match() 方法只接受一個參數,要么是一個正則表達式,要么是一個 RegExp 對象。來看下面的例子。
var text = "cat, bat, sat, fat"; var pattern = /.at/; // 與pattern.exec(text)相同 var matches = text.match(pattern); console.log(matches.index); // 0 console.log(matches[0]); // "cat" console.log(pattern.lastIndex); // 0
另一個用于查找模式的方法是 search()。這個方法的唯一參數與 match() 方法的參數相同:由字符串或 RegExp 對象指定的一個正則表達式。search() 方法返回字符串中第一個匹配項的索引;如果沒有找到匹配項,則返回-1。而且,search() 方法始終是從字符串開頭向后查找模式。看下面的例子。
var text = "cat, bat, sat, fat"; var pos = text.search(/at/); console.log(pos); // 1,即"at"第一次出現的位置
為了簡化替換子字符串的操作,JavaScript 提供了 replace() 方法。這個方法接受兩個參數:第一個參數可以是一個 RegExp 對象或者一個字符串(這個字符串不會被轉換成正則表達式),第二個參數可以是一個字符串或者一個函數。如果第一個參數是字符串,那么只會替換第一個子字符串。要想替換所有子字符串,唯一的辦法就是提供一個正則表達式,而且要指定全局 g 標志,如下所示。
var text = "cat, bat, sat, fat"; var result = text.replace("at", "ond"); console.log(result); // "cond, bat, sat, fat" result = text.replace(/at/g, "ond"); console.log(result); // "cond, bond, sond, fond"
最后一個與模式匹配有關的方法是 split(),這個方法可以基于指定的分隔符將一個字符串分割成多個子字符串,并將結果放在一個數組中。分隔符可以是字符串,也可以是一個 RegExp 對象(這個方法不會將字符串看成正則表達式)。split() 方法可以接受可選的第二個參數,用于指定數組的大小,以便確保返回的數組不會超過既定大小。請看下面的例子。
var colorText = "red,blue,green,yellow"; var colors1 = colorText.split(","); // ["red", "blue", "green", "yellow"] var colors2 = colorText.split(",", 2); // ["red", "blue"]localeCompare() 方法
這個方法比較兩個字符串,并返回下列值中的一個:
如果字符串在字母表中應該排在字符串參數之前,則返回一個負數(大多數情況下是-1,具體的值要視實現而定);
如果字符串等于字符串參數,則返回0;
如果字符串在字母表中應該排在字符串參數之后,則返回一個正數(大多數情況下是1,具體的值同樣要視實現而定)。
下面是幾個例子。
var stringValue = "yellow"; console.log(stringValue.localeCompare("brick")); // 1 console.log(stringValue.localeCompare("yellow")); // 0 console.log(stringValue.localeCompare("zoo")); // -1
這個例子比較了字符串 "yellow" 和另外幾個值:"brick"、"yellow" 和 "zoo"。因為 "brick" 在字母表中排在 "yellow" 之前,所以 localeCompare() 返回了1;而 "yellow" 等于 "yellow",所以 localeCompare() 返回了0;最后,"zoo" 在字母表中排在 "yellow" 后面,所以 localeCompare() 返回了-1。再強調一次,因為 localeCompare() 返回的數值取決于實現,所以最好是像下面例子所示的這樣使用這個方法。
function determineOrder(value) { var result = stringValue.localeCompare(value); if (result < 0){ console.log("The string "yellow" comes before the string "" + value + ""."); } else if (result > 0) { console.log("The string "yellow" comes after the string "" + value + ""."); } else { console.log("The string "yellow" is equal to the string "" + value + ""."); } } determineOrder("brick"); determineOrder("yellow"); determineOrder("zoo");
使用這種結構,就可以確保自己的代碼在任何實現中都可以正確地運行了。
localeCompare() 方法比較與眾不同的地方,就是實現所支持的地區(國家和語言)決定了這個方法的行為。比如,美國以英語作為 JavaScript 實現的標準語言,因此 localeCompare() 就是區分大小寫的,于是大寫字母在字母表中排在小寫字母前頭就成為了一項決定性的比較規則。不過,在其他地區恐怕就不是這種情況了。
fromCharCode() 方法另外,String 構造函數本身還有一個靜態方法:fromCharCode()。這個方法的任務是接收一或多個字符編碼,然后將它們轉換成一個字符串。從本質上來看,這個方法與實例方法 charCodeAt() 執行的是相反的操作。來看一個例子:
console.log(String.fromCharCode(104, 101, 108, 108, 111)); // "hello" var s = "hello"; for(let i=0;i在這里,我們給 fromCharCode() 傳遞的是字符串 "hello" 中每個字母的字符編碼。
關卡// 挑戰一 var falseObject = new Object(false); console.log(typeof falseObject); // ??? console.log(falseObject instanceof Object); // ??? console.log(falseObject instanceof Boolean); // ???// 挑戰二 var numberObject = new Object(100); console.log(typeof numberObject); // ??? console.log(numberObject instanceof Object); // ??? console.log(numberObject instanceof Number); // ???// 挑戰三 var stringObject = new Object("abcde"); console.log(typeof stringObject); // ??? console.log(stringObject instanceof Object); // ??? console.log(stringObject instanceof String); // ???// 挑戰四,翻轉一個字符串 // 提示:可以使用數組的 reverse() 方法 var reverse = function(str) { // 待實現方法體 } console.log(reverse("hello")); // "olleh"更多關注微信公眾號「劼哥舍」回復「答案」,獲取關卡詳解。
關注 https://github.com/stone0090/javascript-lessons,獲取最新動態。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/91093.html
摘要:對象數組初始化表達式,闖關記之上文檔對象模型是針對和文檔的一個。闖關記之數組數組是值的有序集合。數組是動態的,根闖關記之語法的語法大量借鑒了及其他類語言如和的語法。 《JavaScript 闖關記》之 DOM(下) Element 類型 除了 Document 類型之外,Element 類型就要算是 Web 編程中最常用的類型了。Element 類型用于表現 XML 或 HTML 元素...
摘要:本課程之所以叫做闖關記,是因為部分章節精心設計了挑戰關卡,通過提供更多的實戰機會,讓大家可以循序漸進地有目的地有挑戰地開展學習。課程結構及目錄以下目錄只是初步構想,課程結構及內容會根據實際情況隨時進行調整。 為何寫作此課程 stone 主要負責基于 Web 的企業內部管理系統的開發,雖然能夠熟練地使用 JavaScript,但隨著對 JavaScript 的理解越來越深,才發現自己尚...
摘要:瀏覽器只是實現的宿主環境之一,其他宿主環境包括和。年月,版發布,成為國際標準。事件定義了事件和事件處理的接口。對于已經正式納入標準的來說,盡管各瀏覽器都實現了某些眾所周知的共同特性,但其他特性還是會因瀏覽器而異。 JavaScript 是面向 Web 的編程語言,絕大多數現代網站都使用了 JavaScript,并且所有的現代 Web 瀏覽器(電腦,手機,平板)均包含了 JavaScri...
摘要:會自動調用轉換函數將這個表達式的結果轉換為一個布爾值。語句語句與語句的關系最為密切,而且也是在其他語言中普遍使用的一種流控制語句。 表達式在 JavaScript 中是短語,那么語句就是整句命令。表達式用來計算出一個值,語句用來執行以使某件事發生。從本質上看,語句定義了 JavaScript 中的主要語法,語句通常使用一或多個關鍵字來完成給定任務。語句可以很簡單,例如通知函數退出;也可...
摘要:使用元素嵌入代碼時,只需為指定屬性。需要注意的是,帶有屬性的元素不應該在其和元素之間再包含額外的代碼。在包含外部文件時,必須將屬性設置為指向相應文件的。所有元素都會按照他們在頁面中出現的先后順序依次被解析。關注,獲取最新動態。 當學習一門新的編程語言的時候,應該邊學邊做,反復演練以加深理解。因此,你需要一個 JavaScript 解釋器。幸運的是,每一個 Web 瀏覽器都包含一個 Ja...
閱讀 3952·2021-11-18 13:21
閱讀 4759·2021-09-27 14:01
閱讀 3110·2019-08-30 15:53
閱讀 2388·2019-08-30 15:43
閱讀 1730·2019-08-30 13:10
閱讀 1508·2019-08-29 18:39
閱讀 887·2019-08-29 15:05
閱讀 3341·2019-08-29 14:14