摘要:為了使程序員能夠一次一個地處理集合中的元素,引入了迭代器接口。迭代器使用該方法獲取對象屬性名稱的數組,然后將其分配給常量。迭代器的缺點是它們不適合表示異步數據源。每次循環時,都會調用迭代器的方法,它返回一個。
前言
原文地址:https://css-tricks.com/new-es2018-features-every-javascript-developer-should-know/
原文作者:Faraz Kelhini
譯者:Timbok
翻譯工具:Google Translate
本文首發于我的個人網站: Timbok.top正文
ECMAScript標準的第九版,官方稱為ECMAScript 2018(或簡稱ES2018),于2018年6月發布。從ES2016開始,ECMAScript規范的新版本每年發布而不是每幾年發布一次,并且添加的功能少于主要版本以前。該標準的最新版本通過添加四個新RegExp功能,rest/spread屬性,asynchronous iteration,和Promise.prototype.finally。此外,ES2018從標記模板中刪除了轉義序列的語法限制。
這些新變化將在后面的小節中解釋。
rest/spread屬性ES2015最有趣的功能之一是點差運算符。該運算符使復制和合并數組變得更加簡單。您可以使用運算符...,而不是調用concat()or slice()方法:
const arr1 = [10, 20, 30]; // make a copy of arr1 const copy = [...arr1]; console.log(copy); // → [10, 20, 30] const arr2 = [40, 50]; // merge arr2 with arr1 const merge = [...arr1, ...arr2]; console.log(merge); // → [10, 20, 30, 40, 50]
在必須作為函數的多帶帶參數傳入數組的情況下,擴展運算符也派上用場。例如:
const arr = [10, 20, 30] // equivalent to // console.log(Math.max(10, 20, 30)); console.log(Math.max(...arr)); // → 30
ES2018通過向對象文字添加擴展屬性來進一步擴展此語法。使用spread屬性,您可以將對象的自身可枚舉屬性復制到新對象上。請考慮以下示例:
const obj1 = { a: 10, b: 20 }; const obj2 = { ...obj1, c: 30 }; console.log(obj2); // → {a: 10, b: 20, c: 30}
在此代碼中,...運算符用于檢索屬性obj1并將其分配給obj2。在ES2018之前,嘗試這樣做會引發錯誤。如果有多個具有相同名稱的屬性,則將使用最后一個屬性:
const obj1 = { a: 10, b: 20 }; const obj2 = { ...obj1, a: 30 }; console.log(obj2); // → {a: 30, b: 20}
Spread屬性還提供了一種合并兩個或多個對象的新方法,可以將其用作方法的替代Object.assign()方法:
const obj1 = {a: 10}; const obj2 = {b: 20}; const obj3 = {c: 30}; // ES2018 console.log({...obj1, ...obj2, ...obj3}); // → {a: 10, b: 20, c: 30} // ES2015 console.log(Object.assign({}, obj1, obj2, obj3)); // → {a: 10, b: 20, c: 30}
但請注意,spread屬性并不總是產生相同的結果Object.assign()。請考慮以下代碼:
Object.defineProperty(Object.prototype, "a", { set(value) { console.log("set called!"); } }); const obj = {a: 10}; console.log({...obj}); // → {a: 10} console.log(Object.assign({}, obj)); // → set called! // → {}
在此代碼中,該Object.assign()方法執行繼承的setter屬性。相反,傳播屬性完全忽略了setter。
重要的是要記住,spread屬性只復制可枚舉的屬性。在以下示例中,type屬性不會顯示在復制的對象中,因為其enumerable屬性設置為false:
const car = { color: "blue" }; Object.defineProperty(car, "type", { value: "coupe", enumerable: false }); console.log({...car}); // → {color: "blue"}
即使它們是可枚舉的,也會忽略繼承的屬性:
const car = { color: "blue" }; const car2 = Object.create(car, { type: { value: "coupe", enumerable: true, } }); console.log(car2.color); // → blue console.log(car2.hasOwnProperty("color")); // → false console.log(car2.type); // → coupe console.log(car2.hasOwnProperty("type")); // → true console.log({...car2}); // → {type: "coupe"}
在此代碼中,car2繼承color屬性car。因為spread屬性只復制對象的自己的屬性,color所以不包含在返回值中。
請記住,spread屬性只能生成對象的淺表副本。如果屬性包含對象,則僅復制對象的引用:
const obj = {x: {y: 10}}; const copy1 = {...obj}; const copy2 = {...obj}; console.log(copy1.x === copy2.x); // → true
這里copy1和copy2的x是指在內存中的同一對象,所以全等運算返回true。
ES2015中添加的另一個有用功能是rest參數,它使JavaScript程序員可以使用它...來表示值作為數組。例如:
const arr = [10, 20, 30]; const [x, ...rest] = arr; console.log(x); // → 10 console.log(rest); // → [20, 30]
這里,arr的第一個值被分配給對應的x,而剩余的元素被分配給rest變量。這種稱為陣列解構的模式變得如此受歡迎,以至于Ecma技術委員會決定為對象帶來類似的功能:
const obj = { a: 10, b: 20, c: 30 }; const {a, ...rest} = obj; console.log(a); // → 10 console.log(rest); // → {b: 20, c: 30}
此代碼使用解構賦值中的其余屬性將剩余的自身可枚舉屬性復制到新對象中。請注意,rest屬性必須始終出現在對象的末尾,否則會引發錯誤:
const obj = { a: 10, b: 20, c: 30 }; const {...rest, a} = obj; // → SyntaxError: Rest element must be last element
還要記住,在對象中使用多個rest會導致錯誤,除非它們是嵌套的:
const obj = { a: 10, b: { x: 20, y: 30, z: 40 } }; const {b: {x, ...rest1}, ...rest2} = obj; // no error const {...rest, ...rest2} = obj; // → SyntaxError: Rest element must be last elementSupport for Rest/Spread
Chrome | Firefox | Safari | Edge |
---|---|---|---|
60 | 55 | 11.1 | No |
Chrome Android | Firefox Android | iOS Safari | Edge Mobile | Samsung Internet | Android Webview |
---|---|---|---|---|---|
60 | 55 | 11.3 | No | 8.2 | 60 |
8.0.0(運行時需要加-harmony)
8.3.0(完全支持)
Asynchronous Iteration(異步迭代)迭代數據集是編程的重要部分。此前ES2015,提供的JavaScript語句如for,for...in和while,和方法map(),filter()以及forEach()都用于此目的。為了使程序員能夠一次一個地處理集合中的元素,ES2015引入了迭代器接口。
如果對象具有Symbol.iterator屬性,則該對象是可迭代的。在ES2015中,字符串和集合對象(如Set,Map和Array)帶有Symbol.iterator屬性,因此可以迭代。以下代碼給出了如何一次訪問可迭代元素的示例:
const arr = [10, 20, 30]; const iterator = arr[Symbol.iterator](); console.log(iterator.next()); // → {value: 10, done: false} console.log(iterator.next()); // → {value: 20, done: false} console.log(iterator.next()); // → {value: 30, done: false} console.log(iterator.next()); // → {value: undefined, done: true}
Symbol.iterator是一個眾所周知的符號,指定一個返回迭代器的函數。與迭代器交互的主要方法是next()方法。此方法返回具有兩個屬性的對象:value和done。value屬性為集合中下一個元素的值。done屬性的值為true或false表示集合是否迭代完成。
默認情況下,普通對象不可迭代,但如果在其上定義Symbol.iterator屬性,則它可以變為可迭代,如下例所示:
const collection = { a: 10, b: 20, c: 30, [Symbol.iterator]() { const values = Object.keys(this); let i = 0; return { next: () => { return { value: this[values[i++]], done: i > values.length } } }; } }; const iterator = collection[Symbol.iterator](); console.log(iterator.next()); // → {value: 10, done: false} console.log(iterator.next()); // → {value: 20, done: false} console.log(iterator.next()); // → {value: 30, done: false} console.log(iterator.next()); // → {value: undefined, done: true}
此對象是可迭代的,因為它定義了一個Symbol.iterator屬性。迭代器使用該Object.keys()方法獲取對象屬性名稱的數組,然后將其分配給values常量。它還定義了一個計數器變量i,并給它一個初始值0.當執行迭代器時,它返回一個包含next()方法的對象。每次調用next()方法時,它都返回一對{value, done},value保持集合中的下一個元素并done保持一個布爾值,指示迭代器是否已達到集合的需要。
雖然這段代碼完美無缺,但卻不必要。使用生成器函數可以大大簡化過程:
const collection = { a: 10, b: 20, c: 30, [Symbol.iterator]: function * () { for (let key in this) { yield this[key]; } } }; const iterator = collection[Symbol.iterator](); console.log(iterator.next()); // → {value: 10, done: false} console.log(iterator.next()); // → {value: 20, done: false} console.log(iterator.next()); // → {value: 30, done: false} console.log(iterator.next()); // → {value: undefined, done: true}
在這個生成器中,for...in循環用于枚舉集合并產生每個屬性的值。結果與前一個示例完全相同,但它大大縮短了。
迭代器的缺點是它們不適合表示異步數據源。ES2018的補救解決方案是異步迭代器和異步迭代。異步迭代器與傳統迭代器的不同之處在于,它不是以形式返回普通對象{value, done},而是返回履行的承諾{value, done}。異步迭代定義了一個返回異步迭代器的Symbol.asyncIterator方法(而不是Symbol.iterator)。
一個例子讓這個更清楚:
const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]() { const values = Object.keys(this); let i = 0; return { next: () => { return Promise.resolve({ value: this[values[i++]], done: i > values.length }); } }; } }; const iterator = collection[Symbol.asyncIterator](); console.log(iterator.next().then(result => { console.log(result); // → {value: 10, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 20, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 30, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: undefined, done: true} }));
請注意,不可使用promises的迭代器來實現相同的結果。雖然普通的同步迭代器可以異步確定值,但它仍然需要同步確定done的狀態。
同樣,您可以使用生成器函數簡化過程,如下所示:
const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]: async function * () { for (let key in this) { yield this[key]; } } }; const iterator = collection[Symbol.asyncIterator](); console.log(iterator.next().then(result => { console.log(result); // → {value: 10, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 20, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: 30, done: false} })); console.log(iterator.next().then(result => { console.log(result); // → {value: undefined, done: true} }));
通常,生成器函數返回帶有next()方法的生成器對象。當調用next()時,它返回一個{value,done},其value屬性保存了yield值。異步生成器執行相同的操作,除了它返回一個履行{value,done}的promise。
迭代可迭代對象的一種簡單方法是使用for...of語句,但是for...of不能與async iterables一起使用,因為value和done不是同步確定的。因此,ES2018提供了for...await...of。我們來看一個例子:
const collection = { a: 10, b: 20, c: 30, [Symbol.asyncIterator]: async function * () { for (let key in this) { yield this[key]; } } }; (async function () { for await (const x of collection) { console.log(x); } })(); // logs: // → 10 // → 20 // → 30
在此代碼中,for...await...of語句隱式調用Symbol.asyncIterator集合對象上的方法以獲取異步迭代器。每次循環時,都會調用迭代器的next()方法,它返回一個promise。一旦解析了promise,就會將結果對象的value屬性讀取到x變量中。循環繼續,直到返回的對象的done屬性值為true。
請記住,該for...await...of語句僅在異步生成器和異步函數中有效。違反此規則會導致一個SyntaxError報錯。
next()方法可能會返回拒絕的promise。要優雅地處理被拒絕的promise,您可以將for...await...of語句包裝在語句中try...catch,如下所示:
const collection = { [Symbol.asyncIterator]() { return { next: () => { return Promise.reject(new Error("Something went wrong.")) } }; } }; (async function() { try { for await (const value of collection) {} } catch (error) { console.log("Caught: " + error.message); } })(); // logs: // → Caught: Something went wrong.Support for Asynchronous Iterators
Chrome | Firefox | Safari | Edge |
---|---|---|---|
63 | 57 | 12 | No |
Chrome Android | Firefox Android | iOS Safari | Edge Mobile | Samsung Internet | Android Webview |
---|---|---|---|---|---|
63 | 57 | 12 | No | 8.2 | 63 |
8.0.0(運行時需要加-harmony)
8.3.0(完全支持)
Promise.prototype.finallyES2018的另一個令人興奮的補充是finally()方法。一些JavaScript庫之前已經實現了類似的方法,這在許多情況下證明是有用的。這鼓勵了Ecma技術委員會正式添加finally()到規范中。使用這個方法,程序員將能不管promise的結果如何,都能執行一個代碼塊。我們來看一個簡單的例子:
fetch("https://www.google.com") .then((response) => { console.log(response.status); }) .catch((error) => { console.log(error); }) .finally(() => { document.querySelector("#spinner").style.display = "none"; });
finally()無論操作是否成功,當您需要在操作完成后進行一些清理時,該方法會派上用場。在此代碼中,該finally()方法只是在獲取和處理數據后隱藏加載微調器。代碼不是在then()和catch()方法中復制最終邏輯,而是在promise被fulfilled或rejected后注冊要執行的函數。
你可以使用promise.then(func,func)而不是promise.finally(func)來實現相同的結果,但你必須在fulfillment處理程序和rejection處理程序中重復相同的代碼,或者為它聲明一個變量:
fetch("https://www.google.com") .then((response) => { console.log(response.status); }) .catch((error) => { console.log(error); }) .then(final, final); function final() { document.querySelector("#spinner").style.display = "none"; }
和then()和catch()一樣,finally()方法總是返回一個promise,因此可以鏈接更多的方法。通常,您希望使用finally()作為最后一個鏈,但在某些情況下,例如在發出HTTP請求時,最好鏈接另一個catch()以處理finally()中可能發生的錯誤。
Support for Promise.prototype.finallyChrome | Firefox | Safari | Edge |
---|---|---|---|
63 | 58 | 11.1 | 18 |
Chrome Android | Firefox Android | iOS Safari | Edge Mobile | Samsung Internet | Android Webview |
---|---|---|---|---|---|
63 | 58 | 11.1 | No | 8.2 | 63 |
10.0.0(完全支持)
新的RegExp功能ES2018為該RegExp對象增加了四個新功能,進一步提高了JavaScript的字符串處理能力。這些功能如下:
S(DOTALL)標志
Named Capture Groups(命名捕獲組)
Lookbehind Assertions(后向斷言)
Unicode Property Escapes(Unicode屬性轉義)
S(DOTALL)標志點(.)是正則表達式模式中的特殊字符,它匹配除換行符之外的任何字符,例如換行符( )或回車符( )。匹配所有字符(包括換行符)的解決方法是使用具有兩個相反短字的字符類,例如[dD]。此字符類告訴正則表達式引擎找到一個數字(d)或非數字(D)的字符。因此,它匹配任何字符:
console.log(/one[dD]two/.test("one two")); // → true
ES2018引入了一種模式,其中點可用于實現相同的結果??梢允褂胹標志在每個正則表達式的基礎上激活此模式:
console.log(/one.two/.test("one two")); // → false console.log(/one.two/s.test("one two")); // → true
使用標志來選擇新行為的好處是向后兼容性。因此,使用點字符的現有正則表達式模式不受影響。
Named Capture Groups(命名捕獲組)在一些正則表達式模式中,使用數字來引用捕獲組可能會令人困惑。例如,使用/(d{4})-(d{2})-(d{2})/與日期匹配的正則表達式。由于美式英語中的日期符號與英式英語不同,因此很難知道哪個組指的是哪一天,哪個組指的是月份:
const re = /(d{4})-(d{2})-(d{2})/; const match= re.exec("2019-01-10"); console.log(match[0]); // → 2019-01-10 console.log(match[1]); // → 2019 console.log(match[2]); // → 01 console.log(match[3]); // → 10
ES2018引入了使用(?
const re = /(?d{4})-(? d{2})-(? d{2})/; const match = re.exec("2019-01-10"); console.log(match.groups); // → {year: "2019", month: "01", day: "10"} console.log(match.groups.year); // → 2019 console.log(match.groups.month); // → 01 console.log(match.groups.day); // → 10
您可以使用k
const re = /(?w+)s+k /; const match = re.exec("Get that that cat off the table!"); console.log(match.index); // → 4 console.log(match[0]); // → that that
要將命名的捕獲組插入到方法的替換字符串中replace(),您需要使用$
const str = "red & blue"; console.log(str.replace(/(red) & (blue)/, "$2 & $1")); // → blue & red console.log(str.replace(/(?Lookbehind Assertions(后向斷言)red) & (? blue)/, "$ & $ ")); // → blue & red
ES2018為JavaScript帶來了后向性斷言,這些斷言已經在其他正則表達式實現中可用多年。以前,JavaScript只支持超前斷言。后向斷言用表示(?<=...),并使您能夠匹配基于模式之前的子字符串的模式。例如,如果要在不捕獲貨幣符號的情況下以美元,英鎊或歐元匹配產品的價格,則可以使用/(?<=$|£|€)d+(.d*)?/:
const re = /(?<=$|£|€)d+(.d*)?/; console.log(re.exec("199")); // → null console.log(re.exec("$199")); // → ["199", undefined, index: 1, input: "$199", groups: undefined] console.log(re.exec("€50")); // → ["50", undefined, index: 1, input: "€50", groups: undefined]
還有一個lookbehind的否定版本,用(?,只有當模式前面沒有lookbehind中的模式時,負lookbehind才允許您匹配模式。例如,模式/(?匹配沒有“un”前綴的可用詞
這段翻譯的不好,放上原文
There is also a negative version of lookbehind, which is denoted by (?. A negative lookbehind allows you to match a pattern only if it is not preceded by the pattern within the lookbehind. For example, the pattern /(? matches the word available if it does not have a "un" prefix:
Unicode Property Escapes(Unicode屬性轉義)ES2018提供了一種稱為Unicode屬性轉義的新類型轉義序列,它在正則表達式中提供對完整Unicode的支持。假設您要在字符串中匹配Unicode字符?。雖然?被認為是一個數字,但是你不能將它與d速記字符類匹配,因為它只支持ASCII [0-9]字符。另一方面,Unicode屬性轉義可用于匹配Unicode中的任何十進制數:
const str = "?"; console.log(/d/u.test(str)); // → false console.log(/p{Number}/u.test(str)); // → true
同樣,如果要匹配任何Unicode字母字符,你可以使用p{Alphabetic}:
const str = "?"; console.log(/p{Alphabetic}/u.test(str)); // → true // the w shorthand cannot match ? console.log(/w/u.test(str)); // → false
還有一個否定版本p{...},表示為P{...}:
console.log(/P{Number}/u.test("?")); // → false console.log(/P{Number}/u.test("?")); // → true console.log(/P{Alphabetic}/u.test("?")); // → true console.log(/P{Alphabetic}/u.test("?")); // → false
除了字母和數字之外,還有幾個屬性可以在Unicode屬性轉義中使用。您可以在當前規范提案中找到支持的Unicode屬性列表。
Support for New RegExp| Chrome | Firefox | Safari | Edge
S(DOTALL)標志 | 62 | No | 11.1 | No |
命名捕獲組 | 64 | No | 11.1 | No |
后向斷言 | 62 | No | No | No |
Unicode屬性轉義 | 64 | No | 11.1 | No |
| Chrome Android | Firefox Android | iOS Safari | Edge Mobile | Samsung Internet | Android Webview
S(DOTALL)標志 | 62 | No | 11.3 | No | 8.2 | 62 |
命名捕獲組 | 64 | No | 11.3 | No | No | 64 |
后向斷言 | 62 | No | No | No | 8.2 | 62 |
Unicode屬性轉義 | 64 | No | 11.3 | No | No | 64 |
8.3.0 (運行時需要加-harmony)
8.10.0 (support for s (dotAll) flag and lookbehind assertions)
10.0.0 (完全支持)
模板字符串當模板字符串緊跟在表達式之后時,它被稱為標記模板字符串。當您想要使用函數解析模板文字時,標記的模板會派上用場。請考慮以下示例:
function fn(string, substitute) { if(substitute === "ES6") { substitute = "ES2015" } return substitute + string[1]; } const version = "ES6"; const result = fn`${version} was a major update`; console.log(result); // → ES2015 was a major update
在此代碼中,調用標記表達式(它是常規函數)并傳遞模板文字。該函數只是修改字符串的動態部分并返回它。
在ES2018之前,標記的模板字符串具有與轉義序列相關的語法限制。反斜杠后跟某些字符序列被視為特殊字符:x解釋為十六進制轉義符,u解釋為unicode轉義符,后跟一個數字解釋為八進制轉義符。其結果是,字符串,例如"C:xxxuuu"或者"ubuntu"被認為是由解釋無效轉義序列,并會拋出SyntaxError。
ES2018從標記模板中刪除了這些限制,而不是拋出錯誤,表示無效的轉義序列如下undefined
function fn(string, substitute) { console.log(substitute); // → escape sequences: console.log(string[1]); // → undefined } const str = "escape sequences:"; const result = fn`${str} ubuntu C:xxxuuu`;
請記住,在常規模板文字中使用非法轉義序列仍會導致錯誤:
const result = `ubuntu`; // → SyntaxError: Invalid Unicode escape sequenceSupport for Template Literal Revision
Chrome | Firefox | Safari | Edge |
---|---|---|---|
62 | 56 | 11 | No |
Chrome Android | Firefox Android | iOS Safari | Edge Mobile | Samsung Internet | Android Webview |
---|---|---|---|---|---|
62 | 56 | 11 | No | 8.2 | 62 |
8.3.0 (運行時需要加-harmony)
8.10.0(完全支持)
總結我們已經仔細研究了ES2018中引入的幾個關鍵特性,包括異步迭代,rest/spread屬性Promise.prototype.finally()以及RegExp對象的添加。雖然其中一些瀏覽器供應商尚未完全實現其中一些功能,但由于像Babel這樣的JavaScript轉換器,它們今天仍然可以使用。
ECMAScript正在迅速發展,并且每隔一段時間就會引入新功能,因此請查看已完成提案的列表,了解新功能的全部內容。
第一次翻譯文章,能力有限,水平一般,翻譯不妥之處,還望指正。感謝。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/101058.html
摘要:它不僅從前端移動到后端,我們也開始看到它用于機器學習和增強現實,簡稱。由于其高使用率,年的現狀調查將其稱為采用的安全技術。機器學習框架在年的開發者峰會上,宣布了他們的機器學習框架的實現,稱為。更高級別的用于在之上構建機器學習模型。 2019,開發者應該學習的16個JavaScript框架 showImg(https://segmentfault.com/img/remote/14600...
摘要:類將源代碼解釋并構建成抽象語法樹,使用類來創建它們,并使用類來分配內存。類抽象語法樹的訪問者類,主要用來遍歷抽象語法樹。在該函數中,先使用類來生成抽象語法樹再使用類來生成本地代碼。 通過上一篇文章,我們知道了JavaScript引擎是執行JavaScript代碼的程序或解釋器,了解了JavaScript引擎的基本工作原理。我們經常聽說的JavaScript引擎就是V8引擎,這篇文章我們...
摘要:在整篇文章中,我將解釋如何使用以及為什么去使用。如果沒有,這兩個錯誤將被忽視,導致最終應用程序出現一些錯誤。我們可以在從其他文件導入的類中使用自動完成功能。維護的難度是和開發人員避免將大型項目遷移到的主要原因之一。 showImg(https://segmentfault.com/img/remote/1460000017062509?w=800&h=248);在我的職業生涯開始時,我...
摘要:在整個年,看到發布版增加了許多功能,包括新的生命周期方法新的上下文指針事件延遲函數和。它在等待渲染異步響應時數據,是延遲函數背后用來管理組件的代碼分割的。發布自第版開始將近年后,于年發布。 前端發展發展迅速,非常的快。 本文將回顧2018年一些重要的前端新聞,事件和 JavaScript 趨勢。 想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等著你! showImg(ht...
摘要:我們的目標是找出最有職業投資回報率的主題和技術。比特幣在幾年內增長了若干個量級。比特幣倍拐點在這個圖表中,每個箭頭始于倍點,指向價格修正后的最低點。 showImg(https://segmentfault.com/img/remote/1460000017919159); 圖:Jon Glittenberg Happy New Year 2019 (CC BY 2.0) 又到了一年的...
閱讀 1370·2021-09-30 09:55
閱讀 1895·2021-08-27 13:10
閱讀 2243·2019-08-29 17:22
閱讀 1297·2019-08-29 16:30
閱讀 3463·2019-08-26 18:37
閱讀 2347·2019-08-26 11:47
閱讀 1162·2019-08-23 14:44
閱讀 1739·2019-08-23 13:46