摘要:檢查設定位操作符還有一些其他有用的位屏蔽應用。請注意,位掩碼中的位將有效地關閉十進制數中的相應位,因為。
原文標題:Interesting use cases for JavaScript bitwise operators原文地址:https://blog.logrocket.com/in...
本文首發于公眾號:符合預期的CoyPan
JavaScript提供了幾種運算符,可以對一些簡單的值進行基本操作,比如算術操作、賦值操作、邏輯操作、按位操作等。
我們經常可以看到混合了賦值操作,算術操作和邏輯操作的JavaScript代碼。但是,按位操作的代碼就不是那么常見了。
JavaScript的按位操作符~ — 按位非
& — 按位與
| — 按位或
^ — 按位異或
<< — 左移
>> — 有符號右移
>>> — 無符號右移
在本文中,我們將過一遍所有的按位操作符并且試著理解他們是怎么工作的。同時,我們會編寫簡單的JavaScript的代碼,來看一看一些有趣的按位操作符運用。這需要我們了解一下javascript位操作符如何將其操作數表示為有符號的32位整數。讓我們開始吧。
按位非(~)~運算符是一元運算符;因此,它只需要一個操作數。~運算符對其操作數的每一位執行NOT操作。非運算的結果稱為補碼。整數的補碼是通過將整數的每一位倒轉而形成的。
對于給定的整數(例如170),可以使用~運算符計算補碼,如下所示:
// 170 => 00000000000000000000000010101010 // -------------------------------------- // ~ 00000000000000000000000010101010 // -------------------------------------- // = 11111111111111111111111101010101 // -------------------------------------- // = -171 (decimal) console.log(~170); // -171
javascript按位運算符將其操作數轉換為二進制補碼格式的32位有符號整數。因此,當對整數使用~運算符時,得到的值是整數的補碼。整數A的補碼的結果為 - (A+1) 。
~170 => -(170 + 1) => -171
下面是一些需要注意的關于32位有符號整數的要點,這些整數由javascript位運算符使用:
最有意義(最左邊)的位稱為符號位。正整數的符號位總是0,負整數的符號位總是1。
除符號位之外的其余31位用于表示整數。因此,可以表示的最大32位整數是(2^32-1),它是2147483647,而最小整數是(2^31),它是-2147483648。
對于不在32位有符號整數范圍內的整數,最有效位將被丟棄,直到整數在該范圍內。
以下是一些重要數字的32位序列表示:
0 => 00000000000000000000000000000000 -1 => 11111111111111111111111111111111 2147483647 => 01111111111111111111111111111111 -2147483648 => 10000000000000000000000000000000
從上面的描述可以很容易得出:
~0 => -1 ~-1 => 0 ~2147483647 => -2147483648 ~-2147483648 => 2147483647找到索引
大多數JavaScript內置對象(如數組和字符串)都有一些有用的方法,可用于檢查數組中是否存在項或字符串中是否存在子字符串。以下是一些方法:
Array.indexOf()
Array.lastIndexOf()
Array.findIndex()
String.indexOf()
String.lastIndexOf()
String.search()
這些方法都返回某一項或子字符串的從零開始的索引(如果找到);否則,它們返回-1。例如:
const numbers = [1, 3, 5, 7, 9]; console.log(numbers.indexOf(5)); // 2 console.log(numbers.indexOf(8)); // -1
如果我們對么某一項或者子字符串的索引位置不感興趣,我們可以選擇使用布爾值。當未找到的項或者子字符串時,返回-1,我們可以認為是false,返回其他的值都是true。
function foundIndex (index) { return Boolean(~index); }
在上面的代碼片段中,~運算符在-1上使用時的值為0。使用boolean()將值強制轉換為boolean,返回false。對于其他每個索引值,返回true。因此,以前的代碼段可以修改如下:
const numbers = [1, 3, 5, 7, 9]; console.log(foundIndex(numbers.indexOf(5))); // true console.log(foundIndex(numbers.indexOf(8))); // false按位與(&)
& 操作符對其操作數的每一對對應位執行一個和運算。& 操作符僅當兩個位都為1時返回1;否則返回0。因此,與運算的結果等于將每一對對應的位相乘。
下面是與操作的可能值:
(0 & 0) === 0 // 0 x 0 = 0 (0 & 1) === 0 // 0 x 1 = 0 (1 & 0) === 0 // 1 x 0 = 0 (1 & 1) === 1 // 1 x 1 = 1"關閉"某些位
&操作符通常用于位屏蔽應用,以確保為給定的位序列關閉某些位。這是基于這樣一個事實,即對于任何位A:
(A & 0 = 0) — 和0進行與運算,位總是會變成0。
(A & 1 = A) — 和1進行與運算,位總是保持不變。
舉個例子,假設我們有一個8位的整數,我們希望確保前面的4位被關閉(置為0)。我們可以用&操作符來實現:
首先,創建一個位掩碼,其效果是關閉8位整數的前4位。該位掩碼將為0B111110000。請注意,位掩碼的前4位設置為0,而其他每一位設置為1。
接下來,使用8位整數和創建的位掩碼進行 &操作。
const mask = 0b11110000; // 222 => 11011110 // (222 & mask) // ------------ // 11011110 // & 11110000 // ------------ // = 11010000 // ------------ // = 208 (decimal) console.log(222 & mask); // 208檢查設定位
&操作符還有一些其他有用的位屏蔽應用。一個這樣的應用是確定給定的位序列是否設置了一個或多個位。例如,假設我們要檢查是否為給定的十進制數設置了第五位。以下是我們如何使用&運算符來執行此操作:
首先,創建一個位掩碼,用于檢查目標位(在本例中為第五位)是否設置為1。位掩碼上的每個位都設置為0,但目標位置的位除外,目標位置的位設置為1。二進制數文字可用于輕松實現這一點:
const mask = 0b10000;
接下來,使用十進制數和位掩碼作為操作數執行&操作,并將結果與位掩碼進行比較。如果所有目標位都設置為十進制數,&操作的結果將等于位掩碼。請注意,位掩碼中的0位將有效地關閉十進制數中的相應位,因為a&0=0。
// 34 => 100010 // (34 & mask) => (100010 & 010000) = 000000 console.log((34 & mask) === mask); // false // 50 => 110010 // (50 & mask) => (110010 & 010000) = 010000 console.log((50 & mask) === mask); // true奇數或偶數
使用&運算符檢查十進制數的設定位可以擴展到檢查給定的十進制數是偶數還是奇數。為了實現這一點,使用1作為位掩碼(以確定是否設置了第一位或最右邊的位)。
對于整數,可以使用最低有效位(第一位或最右邊的位)來確定數字是偶數還是奇數。如果啟用最低有效位(設置為1),則數字為奇數;否則,數字為偶數。
function isOdd (int) { return (int & 1) === 1; } function isEven (int) { return (int & 1) === 0; } console.log(isOdd(34)); // false console.log(isOdd(-63)); // true console.log(isEven(-12)); // true console.log(isEven(199)); // false有用的標識
在繼續下一個運算符之前,這里有一些&操作符的有用標識(對于任何帶符號的32位整數A):
(A & 0) === 0 (A & ~A) === 0 (A & A) === A (A & -1) === A按位或(|)
運算符對其操作數的每對對應位執行“或”運算。運算符僅當兩個位都為0時返回0;否則返回1。
對于一對位,這里是或操作的可能值:
(0 | 0) === 0 (0 | 1) === 1 (1 | 0) === 1 (1 | 1) === 1"打開"位
在位屏蔽應用中,可以使用運算符來確保位序列中的某些位被打開(設置為1)。這是基于這樣一個事實:對于任何給定的位A:
(A | 0 = A) — 和0進行或運算,位總是會保持不變。
(A | 1 = 1) — 和1進行或運算,位總是為1。
例如,假設我們有一個8位整數,我們希望確保所有偶數位(第二、第四、第六、第八)都打開(設置為1)。| 運算符可用于實現以下目的:
首先,創建一個位掩碼,其效果是打開8位整數的每個偶數位。該位掩碼將是0B101010。請注意,位掩碼的偶數位設置為1,而其他位設置為0。
接下來,使用8位整數和創建的位掩碼執行或操作:
const mask = 0b10101010; // 208 => 11010000 // (208 | mask) // ------------ // 11010000 // | 10101010 // ------------ // = 11111010 // ------------ // = 250 (decimal) console.log(208 | mask); // 250有用的標識
在繼續下一個運算符之前,這里有一些 | 操作符的有用標識(對于任何帶符號的32位整數A):
(A | 0) === A (A | ~A) === -1 (A | A) === A (A | -1) === -1按位異或(^)
^運算符對其操作數的每對對應位執行異或(異或)運算。如果兩個位相同(0或1),則^運算符返回0;否則,它返回1。
對于一對位,下面是可能的值:
(0 ^ 0) === 0 (0 ^ 1) === 1 (1 ^ 0) === 1 (1 ^ 1) === 0切換位
在位屏蔽應用程序中,^ 運算符通常用于切換或翻轉位序列中的某些位。這是基于這樣一個事實:對于任何給定的位A:
和0進行異或運算,位總是會保持不變。
(A ^ 0 = A)
當與相應的1位配對時,該位總是被切換。
(A ^ 1 = 1) — if A is 0
(A ^ 1 = 0) — if A is 1
例如,假設我們有一個8位整數,我們希望確保除了最低有效位(第一位)和最高有效位(第八位)之外,每個位都被切換。可以使用^運算符實現以下目的:
首先,創建一個位掩碼,其效果是切換8位整數的每個位,除了最低有效位和最高有效位。該位掩碼將為0b0111110。請注意,要切換的位設置為1,而其他位設置為0。
接下來,使用8位整數和創建的位掩碼執行^操作:
const mask = 0b01111110; // 208 => 11010000 // (208 ^ mask) // ------------ // 11010000 // ^ 01111110 // ------------ // = 10101110 // ------------ // = 174 (decimal) console.log(208 ^ mask); // 174有用的標識
在繼續下一個運算符之前,以下是^操作的一些有用標識(對于任何有符號的32位整數A):
(A ^ 0) === A (A ^ ~A) === -1 (A ^ A) === 0 (A ^ -1) === ~A
從上面列出的標識中可以明顯看出,-1上的xor操作等同于a上的按位非操作。因此,上面的foundIndex()函數也可以這樣編寫:
function foundIndex (index) { return Boolean(index ^ -1); }左移(<<)
左移位(<<)運算符接受兩個操作數。第一個操作數是整數,而第二個操作數是要向左移動的第一個操作數的位數。零(0)位從右邊移入,而從左邊移入的多余位被丟棄。
例如,考慮整數170。假設我們要向左移動三位。我們可以使用<<運算符,如下所示:
// 170 => 00000000000000000000000010101010 // 170 << 3 // -------------------------------------------- // (000)00000000000000000000010101010(***) // -------------------------------------------- // = (***)00000000000000000000010101010(000) // -------------------------------------------- // = 00000000000000000000010101010000 // -------------------------------------------- // = 1360 (decimal) console.log(170 << 3); // 1360
左移位位運算符(<<)可以使用以下javascript表達式定義:
(A << B) => A * (2 ** B) => A * Math.pow(2, B)
因此,回顧前面的示例:
(170 << 3) => 170 * (2 ** 3) => 170 * 8 => 1360顏色轉換:RGB到十六進制
左移位(<)運算符的一個非常有用的應用程序是將顏色從RGB表示轉換為十六進制表示。
RGB顏色的每個組件的顏色值在0-255之間。簡單地說,每個顏色值可以用8位完美地表示。
0 => 0b00000000 (2進制) => 0x00 (16進制) 255 => 0b11111111 (2進制) => 0xff (16進制)
因此,顏色本身可以完美地用24位來表示(紅色、綠色和藍色分量各8位)。從右邊開始的前8位表示藍色分量,接下來的8位表示綠色分量,之后的8位表示紅色分量。
(binary) => 11111111 00100011 00010100 (red) => 11111111 => ff => 255 (green) => 00100011 => 23 => 35 (blue) => 00010100 => 14 => 20 (hex) => ff2314
既然我們已經了解了如何將顏色表示為24位序列,那么讓我們來看看如何從顏色的各個組件的值組成顏色的24位。假設我們有一個用RGB(255、35、20)表示的顏色。以下是我們如何組合這些位:
(red) => 255 => 00000000 00000000 00000000 11111111 (green) => 35 => 00000000 00000000 00000000 00100011 (blue) => 20 => 00000000 00000000 00000000 00010100 // Rearrange the component bits and pad with zeroes as necessary // Use the left shift operator (red << 16) => 00000000 11111111 00000000 00000000 (green << 8) => 00000000 00000000 00100011 00000000 (blue) => 00000000 00000000 00000000 00010100 // Combine the component bits together using the OR (|) operator // ( red << 16 | green << 8 | blue ) 00000000 11111111 00000000 00000000 | 00000000 00000000 00100011 00000000 | 00000000 00000000 00000000 00010100 // ----------------------------------------- 00000000 11111111 00100011 00010100 // -----------------------------------------
既然過程非常清楚,下面是一個簡單的函數,它將顏色的RGB值作為輸入數組,并基于上述過程返回顏色的相應十六進制表示:
function rgbToHex ([red = 0, green = 0, blue = 0] = []) { return `#${(red << 16 | green << 8 | blue).toString(16)}`; }有符號右移(>>)
有符號右移(>>)運算符的符號接受兩個操作數。第一個操作數是整數,而第二個操作數是要右移的第一個操作數的位數。
已移到右邊的多余位將被丟棄,而符號位(最左邊的位)的副本將從左邊移入。所以,整數的符號位會一直保留。所以這種運算叫做有符號右移。
例如,考慮整數170和-170。假設我們想把三位移到右邊。我們可以使用>>運算符,如下所示:
// 170 => 00000000000000000000000010101010 // -170 => 11111111111111111111111101010110 // 170 >> 3 // -------------------------------------------- // (***)00000000000000000000000010101(010) // -------------------------------------------- // = (000)00000000000000000000000010101(***) // -------------------------------------------- // = 00000000000000000000000000010101 // -------------------------------------------- // = 21 (decimal) // -170 >> 3 // -------------------------------------------- // (***)11111111111111111111111101010(110) // -------------------------------------------- // = (111)11111111111111111111111101010(***) // -------------------------------------------- // = 11111111111111111111111111101010 // -------------------------------------------- // = -22 (decimal) console.log(170 >> 3); // 21 console.log(-170 >> 3); // -22
通過以下javascript表達式可以描述有符號右移:
(A >> B) => Math.floor(A / (2 ** B)) => Math.floor(A / Math.pow(2, B))
因此,之前的那個例子可以如下表示:
(170 >> 3) => Math.floor(170 / (2 ** 3)) => Math.floor(170 / 8) => 21 (-170 >> 3) => Math.floor(-170 / (2 ** 3)) => Math.floor(-170 / 8) => -22顏色提取
有符號右移(>>)運算符的一個非常好的應用是從顏色中提取RGB顏色值。當顏色以RGB表示時,很容易區分紅色、綠色和藍色顏色分量值。但是,對于以十六進制表示的顏色,這將花費更多的精力。
在上一節中,我們看到了從顏色的各個組成部分(紅色、綠色和藍色)的位組成顏色的過程。如果我們反向執行這個過程,我們將能夠提取顏色的各個組成部分的值。讓我們試一試。
假設我們有一個用十六進制表示法ff2314表示的顏色。下面是顏色的有符號32位表示:
(color) => ff2314 (hexadecimal) => 11111111 00100011 00010100 (binary) // 32-bit representation of color 00000000 11111111 00100011 00010100
為了獲得單個部分,我們將根據需要將顏色位按8的倍數右移,直到從右邊得到目標組件位作為前8位。由于顏色的32位中的符號標志位是0,因此我們可以安全地使用符號傳播右移位(>>)運算符。
color => 00000000 11111111 00100011 00010100 // Right shift the color bits by multiples of 8 // Until the target component bits are the first 8 bits from the right red => color >> 16 => 00000000 11111111 00100011 00010100 >> 16 => 00000000 00000000 00000000 11111111 green => color >> 8 => 00000000 11111111 00100011 00010100 >> 8 => 00000000 00000000 11111111 00100011 blue => color >> 0 => color => 00000000 11111111 00100011 00010100
現在我們將目標顏色位作為右前8位,我們需要一種方法來屏蔽除前8位之外的所有其他位。這使我們回到和(&)運算符。請記住,&運算符可用于確保關閉某些位。
讓我們從創建所需的位掩碼開始。就像這樣:
mask => 00000000 00000000 00000000 11111111 => 0b11111111 (binary) => 0xff (hexadecimal)
準備好位掩碼后,我們可以對上一次右移操作的每個結果執行與(&)操作,使用位掩碼提取目標顏色。
red => color >> 16 & 0xff => 00000000 00000000 00000000 11111111 => & 00000000 00000000 00000000 11111111 => = 00000000 00000000 00000000 11111111 => 255 (decimal) green => color >> 8 & 0xff => 00000000 00000000 11111111 00100011 => & 00000000 00000000 00000000 11111111 => = 00000000 00000000 00000000 00100011 => 35 (decimal) blue => color & 0xff => 00000000 11111111 00100011 00010100 => & 00000000 00000000 00000000 11111111 => = 00000000 00000000 00000000 00010100 => 20 (decimal)
基于上述過程,這里有一個簡單的函數,它以十六進制顏色字符串(帶有六個十六進制數字)作為輸入,并返回相應的RGB顏色分量值數組。
function hexToRgb (hex) { hex = hex.replace(/^#?([0-9a-f]{6})$/i, "$1"); hex = Number(`0x${hex}`); return [ hex >> 16 & 0xff, // red hex >> 8 & 0xff, // green hex & 0xff // blue ]; }無符號右移(>>>)
無符號右移位(>>>)運算符的行為非常類似于符號傳播右移位(>>)運算符。然而,關鍵區別在于從左邊移入的位。
顧名思義,0位總是從左邊移入。因此,>>運算符始終返回無符號32位整數,因為結果整數的符號位始終為0。對于正整數,>>和>>>都將始終返回相同的結果。
例如,考慮整數170和-170。假設我們要將3位移到右邊,我們可以使用>>>操作符,如下所示:
// 170 => 00000000000000000000000010101010 // -170 => 11111111111111111111111101010110 // 170 >>> 3 // -------------------------------------------- // (***)00000000000000000000000010101(010) // -------------------------------------------- // = (000)00000000000000000000000010101(***) // -------------------------------------------- // = 00000000000000000000000000010101 // -------------------------------------------- // = 21 (decimal) // -170 >>> 3 // -------------------------------------------- // (***)11111111111111111111111101010(110) // -------------------------------------------- // = (000)11111111111111111111111101010(***) // -------------------------------------------- // = 00011111111111111111111111101010 // -------------------------------------------- // = 536870890 (decimal) console.log(170 >>> 3); // 21 console.log(-170 >>> 3); // 536870890配置標志
在總結本教程之前,讓我們考慮另一個非常常見的位操作符和位屏蔽應用:配置標志。
假設我們有一個函數,它接受幾個布爾選項,這些選項可以用來控制函數的運行方式或返回的值的類型。創建此函數的一種可能方法是將所有選項作為參數傳遞給該函數,可能使用一些默認值,例如:
function doSomething (optA = true, optB = true, optC = false, optD = true, ...) { // something happens here... }
當然,這不太方便。在以下兩種情況下,這種方法開始變得相當有問題:
假設我們有10個以上的布爾選項。我們不能用這么多參數定義函數。
假設我們只想為第五個和第九個選項指定一個不同的值,并讓其他選項保留默認值。我們需要調用函數,將默認值作為所有其他選項的參數傳遞,同時為第五個和第九個選項傳遞所需的值。
用前面的方法解決問題的一種方法是為配置選項使用一個對象,如下所示:
const defaultOptions = { optA: true, optB: true, optC: false, optD: true, ... }; function doSomething (options = defaultOptions) { // something happens here... }
這種方法非常優雅,您很可能已經看到它被使用了,甚至自己在某個地方使用過。然而,使用這種方法時,options參數將始終是一個對象,對于配置選項來說,這可以被認為是多余的。
如果所有選項都采用布爾值,則可以使用整數而不是對象來表示選項。在這種情況下,整數的某些位將映射到指定的選項。如果某個位被打開(設置為1),則指定選項的值為“真”;否則為“假”。
我們可以用一個簡單的例子來演示這種方法。假設我們有一個函數,它規范化包含數字的數組列表中的項,并返回規范化的數組。返回的數組可以由三個選項控制,即:
fraction:將數組中的每個項除以數組中的最大項
unique:從數組中刪除重復項
sorted:將數組中的項從最低到最高排序
我們可以使用一個3位整數來表示這些選項,每個位都映射到一個選項。以下代碼段顯示選項標志:
const LIST_FRACTION = 0x1; // (001) const LIST_UNIQUE = 0x2; // (010) const LIST_SORTED = 0x4; // (100)
要激活一個或多個選項,可以根據需要使用運算符組合相應的標志。例如,我們可以創建一個標志來激活所有選項,如下所示:
const LIST_ALL = LIST_FRACTION | LIST_UNIQUE | LIST_SORTED; // (111)
同樣,假設我們只希望默認情況下激活fraction和sorted選項。我們可以再次使用運算符,如下所示:
const LIST_DEFAULT = LIST_FRACTION | LIST_SORTED; // (101)
雖然只使用三個選項看起來并不糟糕,但當有這么多選項時,它往往會變得非常混亂,并且默認情況下需要激活其中的許多選項。在這種情況下,更好的方法是使用^運算符停用不需要的選項:
const LIST_DEFAULT = LIST_ALL ^ LIST_UNIQUE; // (101)
這里,我們有一個列表“所有”標志,可以激活所有選項。然后,我們使用^運算符停用唯一選項,并根據需要保留其他選項。
現在我們已經準備好了選項標志,可以繼續定義normalizelist()函數:
function normalizeList (list, flag = LIST_DEFAULT) { if (flag & LIST_FRACTION) { const max = Math.max(...list); list = list.map(value => Number((value / max).toFixed(2))); } if (flag & LIST_UNIQUE) { list = [...new Set(list)]; } if (flag & LIST_SORTED) { list = list.sort((a, b) => a - b); } return list; }
為了檢查某個選項是否被激活,我們使用&運算符來檢查該選項的相應位是否被打開(設置為1)。&操作是通過傳遞給函數的flag參數和選項的對應標志來執行的,如下面的代碼段所示:
// Checking if the unique option is activated // (flag & LIST_UNIQUE) === LIST_UNIQUE (activated) // (flag & LIST_UNIQUE) === 0 (deactivated) flag & LIST_UNIQUE總結
嘿,我真的很高興你能讀完這篇文章,盡管讀了很長時間,但我強烈希望你在讀的時候學到一兩件事。謝謝你的時間。
正如我們在本文中所看到的,雖然使用得很謹慎,但javascript的位操作符有一些非常有趣的用例。我強烈希望您在閱讀本文的過程中獲得的見解從現在起用在你的日常開發中。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/110056.html
摘要:否則,將返回空數組的長度。該提案目前處于第階段,作為一項實驗性功能。轉換為布爾值除了常規的布爾值和之外,還將所有其他值視為或。這也可以用于將布爾值轉換為數字,如下所示在某些上下文中,將被解釋為連接操作符,而不是加法操作符。 譯者:前端小智 原文:medium.com/@bretcamero… 當我開始學習JavaScript時,我把我在別人的代碼、code challenge網站以及我使用...
摘要:原碼補碼和反碼原碼一個數在計算機中是以二進制的形式存在的,其中第一位存放符號正數為負數為。中的位運算在中按位操作符會將其操作數轉成補碼形式的有符號位整數。原文鏈接由扯到中的位運算 這個話題的由來是2016年3月份的時候 NPM 社區發生了‘left-pad’事件,不久后社區就有人發布了用來補救的,也是現在大家能用到的 left-pad 庫。 最開始這個庫的代碼是這樣的。 module....
摘要:千里之行始于足下題目交換兩個變量不允許使用臨時變量思路首先大家一定需要深入了解在語言中按位異或操作符的作用,不懂的建議大家去百度。 千里之行始于足下 題目: 交...
摘要:對的描述如下將會給數組里的每一個元素執行一遍回調函數,直到回調函數返回。的運行原理和類似,但回調函數是返回而不是。回調函數只會對已經指定值的數組項調用。 showImg(http://fw008950-flywheel.netdna-ssl.com/wp-content/uploads/2013/11/JavaScript_Array_Functions_Header.jpg); 在...
閱讀 2247·2021-11-23 09:51
閱讀 1042·2021-11-18 10:02
閱讀 3434·2021-10-13 09:49
閱讀 1262·2021-09-22 14:57
閱讀 10388·2021-08-18 10:20
閱讀 1181·2019-08-30 15:55
閱讀 2225·2019-08-29 16:06
閱讀 3232·2019-08-29 11:14