diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md index 6a4c20baf..dcf41d2c5 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/solution.md @@ -1,4 +1,4 @@ -Let's store read messages in `WeakSet`: +讓我們將已讀的訊息存在 `WeakSet` 中: ```js run let messages = [ @@ -9,35 +9,35 @@ let messages = [ let readMessages = new WeakSet(); -// two messages have been read +// 兩個訊息已經被讀取了 readMessages.add(messages[0]); readMessages.add(messages[1]); -// readMessages has 2 elements +// readMessages 有兩個元素 -// ...let's read the first message again! +// ...讓我們再次讀取第一個訊息! readMessages.add(messages[0]); -// readMessages still has 2 unique elements +// readMessages 依然有兩個唯一的元素 -// answer: was the message[0] read? +// 回答:message[0] 是否已被讀取? alert("Read message 0: " + readMessages.has(messages[0])); // true messages.shift(); -// now readMessages has 1 element (technically memory may be cleaned later) +// 現在 readMessages 只有一個元素(技術上來說,記憶體可能會在稍後才被清理) ``` -The `WeakSet` allows to store a set of messages and easily check for the existance of a message in it. +`WeakSet` 允許儲存訊息的集合,且能簡單地檢查一個訊息是否存在於集合內。 -It cleans up itself automatically. The tradeoff is that we can't iterate over it, can't get "all read messages" from it directly. But we can do it by iterating over all messages and filtering those that are in the set. +它會自動清理自己,但代價是我們不能夠迭代它,無法直接取得 "所有已讀訊息"。但我們可以透過迭代所有訊息,並過濾掉那些存在集合中的訊息來達到同樣目的。 -Another, different solution could be to add a property like `message.isRead=true` to a message after it's read. As messages objects are managed by another code, that's generally discouraged, but we can use a symbolic property to avoid conflicts. +另外一個不同的解法可以是當訊息被讀取後,增加一個屬性,像 `message.isRead=true` 到訊息中。當訊息物件被其他程式碼管理時,這樣的作法一般是不建議的,但我們可以用 Symbol 屬性來避免衝突。 -Like this: +像這樣: ```js -// the symbolic property is only known to our code +// Symbol 屬性只會在我們的程式碼中被認得 let isRead = Symbol("isRead"); messages[0][isRead] = true; ``` -Now third-party code probably won't see our extra property. +現在第三方程式碼可能不會看到我們的額外屬性。 -Although symbols allow to lower the probability of problems, using `WeakSet` is better from the architectural point of view. +雖然 Symbol 可以降低問題發生的機率,但從架構的觀點來看,使用 `WeakSet` 是比較好的。 diff --git a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md index fd31a891b..49efd1b04 100644 --- a/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md +++ b/1-js/05-data-types/08-weakmap-weakset/01-recipients-read/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Store "unread" flags +# 儲存 "未讀" 旗標 -There's an array of messages: +有一個訊息陣列: ```js let messages = [ @@ -14,10 +14,10 @@ let messages = [ ]; ``` -Your code can access it, but the messages are managed by someone else's code. New messages are added, old ones are removed regularly by that code, and you don't know the exact moments when it happens. +你的程式碼可以存取它,但這些訊息是被其他人的程式碼管理著。該程式碼定期地加入新訊息,移除舊訊息,而你不知道確切何時會發生這些變化。 -Now, which data structure could you use to store information about whether the message "has been read"? The structure must be well-suited to give the answer "was it read?" for the given message object. +現在,哪一種資料結構你可以用來儲存訊息 "是否已被讀取"?該結構必須非常合適地針對給定的訊息物件給出 "它被讀取了嗎" 的答案。 -P.S. When a message is removed from `messages`, it should disappear from your structure as well. +註:當一個訊息從 `messages` 中被移除時,它也應該要從你的結構中消失。 -P.P.S. We shouldn't modify message objects, add our properties to them. As they are managed by someone else's code, that may lead to bad consequences. +註 2:我們不應該更動訊息物件,或是增加我們自己的屬性到上面。因為它們被其他人的程式碼管理著,這樣做可能會導致壞的後果。 diff --git a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md index 2af0547c1..10807455a 100644 --- a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/solution.md @@ -1,5 +1,5 @@ -To store a date, we can use `WeakMap`: +要儲存日期,我們可以用 `WeakMap`: ```js let messages = [ @@ -11,5 +11,5 @@ let messages = [ let readMap = new WeakMap(); readMap.set(messages[0], new Date(2017, 1, 1)); -// Date object we'll study later +// 我們之後會讀到日期物件 ``` diff --git a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md index 8e341c184..2c71b4fe0 100644 --- a/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md +++ b/1-js/05-data-types/08-weakmap-weakset/02-recipients-when-read/task.md @@ -2,9 +2,9 @@ importance: 5 --- -# Store read dates +# 儲存已讀日期 -There's an array of messages as in the [previous task](info:task/recipients-read). The situation is similar. +與 [上一個課題](info:task/recipients-read) 類似的情境,存在一個訊息陣列。 ```js let messages = [ @@ -14,8 +14,8 @@ let messages = [ ]; ``` -The question now is: which data structure you'd suggest to store the information: "when the message was read?". +現在的問題是:你建議用哪個資料結構來儲存這樣的資訊:"訊息在何時被讀取?"。 -In the previous task we only needed to store the "yes/no" fact. Now we need to store the date, and it should only remain in memory until the message is garbage collected. +在前一個任務中,我們只需要儲存 "是/否" 的事實陳述,現在我們需要儲存日期,且它應該只能在訊息被垃圾回收前存在。 -P.S. Dates can be stored as objects of built-in `Date` class, that we'll cover later. +註:日期可以以內建 `Date` 類別的物件來儲存,我們晚點會介紹。 diff --git a/1-js/05-data-types/08-weakmap-weakset/article.md b/1-js/05-data-types/08-weakmap-weakset/article.md index 11ff9d5eb..87dc104ec 100644 --- a/1-js/05-data-types/08-weakmap-weakset/article.md +++ b/1-js/05-data-types/08-weakmap-weakset/article.md @@ -1,43 +1,44 @@ -# WeakMap and WeakSet +# WeakMap 和 WeakSet -As we know from the chapter , JavaScript engine stores a value in memory while it is reachable (and can potentially be used). +如同我們從章節 得知的,JavaScript 引擎會將可達的(且有可能會被使用到的)值儲存在記憶體中。 + +舉例來說: -For instance: ```js let john = { name: "John" }; -// the object can be accessed, john is the reference to it +// 該物件可以被存取,john 為其參考。 -// overwrite the reference +// 覆寫其參考 john = null; *!* -// the object will be removed from memory +// 該物件將會從記憶體中被移除 */!* ``` -Usually, properties of an object or elements of an array or another data structure are considered reachable and kept in memory while that data structure is in memory. +通常,物件的屬性,陣列的元素或是其它資料結構,在其容器資料結構還存在記憶體中時,會被視為可達的並被持續留在記憶體中。 -For instance, if we put an object into an array, then while the array is alive, the object will be alive as well, even if there are no other references to it. +例如,如果我們將一個物件放入一個陣列,當此陣列還活著時,該物件也將會活著,即使沒有其他參考指向它。 -Like this: +像這樣: ```js let john = { name: "John" }; let array = [ john ]; -john = null; // overwrite the reference +john = null; // 覆寫其參考 *!* -// john is stored inside the array, so it won't be garbage-collected -// we can get it as array[0] +// john 被儲存於陣列內,所以它不會被垃圾回收掉。 +// 我們可以透過 array[0] 來存取它。 */!* ``` -Similar to that, if we use an object as the key in a regular `Map`, then while the `Map` exists, that object exists as well. It occupies memory and may not be garbage collected. +類似於此,如果我們將一個物件當作一個普通 `Map` 的鍵值,當 `Map` 存在時,該物件也會存在。它會佔據記憶體,且可能不會被垃圾回收。 -For instance: +例如: ```js let john = { name: "John" }; @@ -45,36 +46,36 @@ let john = { name: "John" }; let map = new Map(); map.set(john, "..."); -john = null; // overwrite the reference +john = null; // 覆寫其參考 *!* -// john is stored inside the map, -// we can get it by using map.keys() +// john 存在於 map 內, +// 我們可以用 map.keys() 來取得它 */!* ``` -`WeakMap` is fundamentally different in this aspect. It doesn't prevent garbage-collection of key objects. +`WeakMap` 在此方面有著根本上的不同。它並不會防止鍵值物件被垃圾回收。 -Let's see what it means on examples. +讓我們從範例來看看這代表什麼意思。 ## WeakMap -The first difference from `Map` is that `WeakMap` keys must be objects, not primitive values: +與 `Map` 的第一個差異是,`WeakMap` 一定要是物件,不能是原生類型值: ```js run let weakMap = new WeakMap(); let obj = {}; -weakMap.set(obj, "ok"); // works fine (object key) +weakMap.set(obj, "ok"); // 正常運作 (物件當作鍵值) *!* -// can't use a string as the key -weakMap.set("test", "Whoops"); // Error, because "test" is not an object +// 不能用字串當作鍵值 +weakMap.set("test", "Whoops"); // 錯誤, 因為 "test" 並非一個物件 */!* ``` -Now, if we use an object as the key in it, and there are no other references to that object -- it will be removed from memory (and from the map) automatically. +現在,如果我們在裡面用物件當作鍵值,且沒有其他參考指向該物件 -- 它將會自動從記憶體中被移除(還有從 map 中移除) ```js let john = { name: "John" }; @@ -82,104 +83,104 @@ let john = { name: "John" }; let weakMap = new WeakMap(); weakMap.set(john, "..."); -john = null; // overwrite the reference +john = null; // 覆寫參考 -// john is removed from memory! +// john 從記憶體中被移除了! ``` -Compare it with the regular `Map` example above. Now if `john` only exists as the key of `WeakMap` -- it will be automatically deleted from the map (and memory). +與上面普通的 `Map` 範例比較。若現在 `john` 僅存在於 `WeakMap` 的鍵之中 -- 它將會自動地從 map(和記憶體)中刪除。 -`WeakMap` does not support iteration and methods `keys()`, `values()`, `entries()`, so there's no way to get all keys or values from it. +`WeakMap` 不支援迭代和方法 `keys()`、`values()`、`entries()`,所以沒有方法可以從中取得所有的鍵與值。 -`WeakMap` has only the following methods: +`WeakMap` 只有下面的方法: - `weakMap.get(key)` - `weakMap.set(key, value)` - `weakMap.delete(key)` - `weakMap.has(key)` -Why such a limitation? That's for technical reasons. If an object has lost all other references (like `john` in the code above), then it is to be garbage-collected automatically. But technically it's not exactly specified *when the cleanup happens*. +為什麼有這樣的限制?這是為了技術上的原因。如果一個物件喪失了其他所有的參考(如上述程式碼範例中的 `john`),那它會被自動垃圾回收掉。但技術上來說,並沒有明確指定 *何時要執行清理*。 -The JavaScript engine decides that. It may choose to perform the memory cleanup immediately or to wait and do the cleaning later when more deletions happen. So, technically the current element count of a `WeakMap` is not known. The engine may have cleaned it up or not, or did it partially. For that reason, methods that access all keys/values are not supported. +由 JavaScript 引擎決定。它可能選擇立即執行記憶體清掃,或是等待晚點更多的刪除發生後再執行清理。所以,技術上來說,`WeakMap` 目前的元素數量是未知的。引擎可能會清理也可能不會,或是只做一部分。出於此因,不支援能夠存取所有鍵/值的方法。 -Now where do we need such data structure? +好,那麼在哪種地方我們需要這樣的資料結構呢? -## Use case: additional data +## 使用案例: 附加的資料 -The main area of application for `WeakMap` is an *additional data storage*. +`WeakMap` 的主要應用領域是 *附加的資料儲存空間*。 -If we're working with an object that "belongs" to another code, maybe even a third-party library, and would like to store some data associated with it, that should only exist while the object is alive - then `WeakMap` is exactly what's needed. +如果我們正在操作一個 "屬於" 其他程式碼的物件,甚至可能是一個第三方套件,然後我們想要儲存一些資料,那些資料與此相關,但只有在物件活著時存在 - 那 `WeakMap` 就是我們需要的。 -We put the data to a `WeakMap`, using the object as the key, and when the object is garbage collected, that data will automatically disappear as well. +我們將資料放入一個 `WeakMap`,用物件當作鍵值,然後當物件被垃圾回收時,該資料也會自動消失。 ```js weakMap.set(john, "secret documents"); -// if john dies, secret documents will be destroyed automatically +// 如果 john 死了,secret documents 將會被自動消滅。 ``` -Let's look at an example. +讓我們來看一個範例。 -For instance, we have code that keeps a visit count for users. The information is stored in a map: a user object is the key and the visit count is the value. When a user leaves (its object gets garbage collected), we don't want to store their visit count anymore. +例如,我們有一份程式碼,用於保留使用者們訪問的次數。該資訊儲存於一個 map 中:某個 user 物件做為鍵,而訪問次數是值。當有個使用者離開(它的物件被資料回收),我們就也不想儲存它的訪問次數了。 -Here's an example of a counting function with `Map`: +這是一個使用 `Map` 的計次函式: ```js // 📁 visitsCount.js -let visitsCountMap = new Map(); // map: user => visits count +let visitsCountMap = new Map(); // map: user => 訪問次數 -// increase the visits count +// 增加訪問次數 function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -And here's another part of the code, maybe another file using it: +這裡是程式碼的另一部分,可能是另一份檔案在使用它: ```js // 📁 main.js let john = { name: "John" }; -countUser(john); // count his visits +countUser(john); // 它的訪問次數 countUser(john); -// later john leaves us +// 晚點 john 離開了我們 john = null; ``` -Now `john` object should be garbage collected, but remains in memory, as it's a key in `visitsCountMap`. +現在 `john` 物件應該要被垃圾回收,但卻還是作為 `visitsCountMap` 中的一個鍵存在於記憶體中。 -We need to clean `visitsCountMap` when we remove users, otherwise it will grow in memory indefinitely. Such cleaning can become a tedious task in complex architectures. +當我們移除使用者時,我們需要清理 `visitsCountMap`,否則記憶體會無窮擴大。在複雜的架構中,這樣的清潔可能會是一個繁瑣乏味的任務。 -We can avoid it by switching to `WeakMap` instead: +我們可以用 `WeakMap` 來取代並避免上述狀況: ```js // 📁 visitsCount.js -let visitsCountMap = new WeakMap(); // weakmap: user => visits count +let visitsCountMap = new WeakMap(); // weakmap: user => 訪問次數 -// increase the visits count +// 增加訪問次數 function countUser(user) { let count = visitsCountMap.get(user) || 0; visitsCountMap.set(user, count + 1); } ``` -Now we don't have to clean `visitsCountMap`. After `john` object becomes unreachable by all means except as a key of `WeakMap`, it gets removed from memory, along with the information by that key from `WeakMap`. +現在我們不用清理 `visitsCountMap` 了。當 `john` 物件變成除了作為 `WeakMap` 的鍵值以外,其餘皆不可達的情況時,它就會連同那些從 `WeakMap` 的鍵得來的資訊,一起從記憶體中被移除。 -## Use case: caching +## 使用案例: 快取(caching) -Another common example is caching: when a function result should be remembered ("cached"), so that future calls on the same object reuse it. +另一個常見的範例是快取:當一個函數的結果應該要被記憶住("快取"),這樣之後呼叫相同物件時可以重複使用。 -We can use `Map` to store results, like this: +我們可以用 `Map` 來存結果,像這樣: ```js run // 📁 cache.js let cache = new Map(); -// calculate and remember the result +// 計算並將結果記起來 function process(obj) { if (!cache.has(obj)) { - let result = /* calculations of the result for */ obj; + let result; // = obj 的計算結果 cache.set(obj, result); } @@ -188,26 +189,26 @@ function process(obj) { } *!* -// Now we use process() in another file: +// 現在我們可以在另一個檔案中使用 process(): */!* // 📁 main.js -let obj = {/* let's say we have an object */}; +let obj = {/* 假設我們有一個物件 */}; -let result1 = process(obj); // calculated +let result1 = process(obj); // 計算 -// ...later, from another place of the code... -let result2 = process(obj); // remembered result taken from cache +// ...之後,從程式碼的別處 +let result2 = process(obj); // 從快取中取出紀錄的結果 -// ...later, when the object is not needed any more: +// ...之後,當物件不再被需要: obj = null; -alert(cache.size); // 1 (Ouch! The object is still in cache, taking memory!) +alert(cache.size); // 1(哎呦!該物件還是在快取中,佔據記憶體!) ``` -For multiple calls of `process(obj)` with the same object, it only calculates the result the first time, and then just takes it from `cache`. The downside is that we need to clean `cache` when the object is not needed any more. +針對同個物件多次呼叫 `process(obj)`,只有第一次會進行計算,之後就只從 `cache` 中取出結果。這樣做的缺點是,當物件不再被需要時,我們需要清除 `cache`。 -If we replace `Map` with `WeakMap`, then this problem disappears: the cached result will be removed from memory automatically after the object gets garbage collected. +如果我們用 `WeakMap` 取代 `Map`,那這問題就不復存在了:快取結果會在物件被垃圾回收後,自動從記憶體中被移除。 ```js run // 📁 cache.js @@ -215,10 +216,10 @@ If we replace `Map` with `WeakMap`, then this problem disappears: the cached res let cache = new WeakMap(); */!* -// calculate and remember the result +// 計算且記住結果 function process(obj) { if (!cache.has(obj)) { - let result = /* calculate the result for */ obj; + let result; // = obj 的計算結果 cache.set(obj, result); } @@ -227,30 +228,30 @@ function process(obj) { } // 📁 main.js -let obj = {/* some object */}; +let obj = {/* 一些物件 */}; let result1 = process(obj); let result2 = process(obj); -// ...later, when the object is not needed any more: +// ...之後,當物件不再被需要: obj = null; -// Can't get cache.size, as it's a WeakMap, -// but it's 0 or soon be 0 -// When obj gets garbage collected, cached data will be removed as well +// 無法取得 cache.size,因為這是 WeakMp, +// 但它會是 0 或是很快就會是 0 +// 當物件被垃圾回收,快取資料也會被一併移除。 ``` ## WeakSet -`WeakSet` behaves similarly: +`WeakSet` 有類似的行為: -- It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives). -- An object exists in the set while it is reachable from somewhere else. -- Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations. +- 它類似於 `Set`,但我們只能將物件加入 `WeakSet`(原生值不行)。 +- 當它還可從其他地方被存取時,物件就還會存在於集合中。 +- 像是 `Set`,它支援 `add`、`has` 和 `delete`,但不支援 `size`、`keys()` 且沒有迭代。 -Being "weak", it also serves as an additional storage. But not for an arbitrary data, but rather for "yes/no" facts. A membership in `WeakSet` may mean something about the object. +身為 "weak",它也可作為附加的儲存空間。但不是給隨意的資料使用,而是針對 "是/否" 這類的事實陳述。`WeakSet` 中的成員關係可能代表物件的某些資訊。 -For instance, we can add users to `WeakSet` to keep track of those who visited our site: +舉例來說,我們可以將使用者加入 `WeakSet` 來追蹤誰曾拜訪過我們的網站: ```js run let visitedSet = new WeakSet(); @@ -259,31 +260,31 @@ let john = { name: "John" }; let pete = { name: "Pete" }; let mary = { name: "Mary" }; -visitedSet.add(john); // John visited us -visitedSet.add(pete); // Then Pete -visitedSet.add(john); // John again +visitedSet.add(john); // John 拜訪過我們 +visitedSet.add(pete); // 接著是 Pete +visitedSet.add(john); // John 再度拜訪 -// visitedSet has 2 users now +// visitedSet 現在有兩個使用者 -// check if John visited? +// 檢查 John 是否拜訪過? alert(visitedSet.has(john)); // true -// check if Mary visited? +// 檢查 Mary 是否拜訪過? alert(visitedSet.has(mary)); // false john = null; -// visitedSet will be cleaned automatically +// visitedSet 將會被自動清理。 ``` -The most notable limitation of `WeakMap` and `WeakSet` is the absence of iterations, and inability to get all current content. That may appear inconvenient, but does not prevent `WeakMap/WeakSet` from doing their main job -- be an "additional" storage of data for objects which are stored/managed at another place. +`WeakMap` 與 `WeakSet` 最值得注意的限制是缺乏迭代功能,以及無法一次取得目前所有的內容。這可能很不方便,但並不影響 `WeakMap/WeakSet` 執行他們的主要工作 -- 為在另一個地方被儲存/管理的物件提供一個 "附加" 的儲存空間來儲存其資料。 -## Summary +## 總結 -`WeakMap` is `Map`-like collection that allows only objects as keys and removes them together with associated value once they become inaccessible by other means. +`WeakMap` 是一個類似 `Map` 的集合,只允許用物件當作鍵,且當其關聯的值不再能夠被存取時,會跟著一同被移除。 -`WeakSet` is `Set`-like collection that stores only objects and removes them once they become inaccessible by other means. +`WeakSet` 是一個類似 `Set` 的集合,只能儲存物件,且當該物件不再能夠被存取時,會跟著一同被移除。 -Both of them do not support methods and properties that refer to all keys or their count. Only individual operations are allowed. +它們兩個都不支援能夠存取所有鍵或是計數值的方法或屬性。只允許個別的操作。 -`WeakMap` and `WeakSet` are used as "secondary" data structures in addition to the "main" object storage. Once the object is removed from the main storage, if it is only found as the key of `WeakMap` or in a `WeakSet`, it will be cleaned up automatically. +`WeakMap` 和 `WeakSet` 被作為附加於 "主要" 物件儲存空間的 "次要" 資料結構。一但物件從主要儲存空間中被移除,如果該物件只被當作 `WeakMap` 的鍵,或是只存在於 `WeakSet` 中,那它將會自動被清除。