[譯]ECMAScript 6中的集合類型,第二部分:Map


原文:http://www.nczonline.net/blog/2012/10/09/ecmascript-6-collections-part-2-maps/


Map[1]Set一樣,也是在其他語言中經常會用到的東西.其基本思想就是把一個值映射給一個唯一的鍵,這樣在任何時刻,都能根據這個鍵獲取到對應的值.在過去,JavaScript開發者們一直都把常規的對象當成Map來使用(譯者注:之所以說常規對象,是因為Map類型也是對象).實際上,JSON就是基於"對象是由鍵-值對組成的"這個前提發明的.可是,對象當作Set來使用的限制同樣存在於對象當作Map使用的情況下,那就是:鍵只能是字符串.

譯者注:也許你這里會疑惑:不對啊,數組的索引不是數字類型嗎?但其實:

> let arr = ["a","b","c"]               //定義一個數組
> arr[0]                                //之所以能用數字作為索引,是因為0會被自動轉換為"0"
"a"
> arr["0"]                              //這才是真實的索引
"a"
>arr[{toString:function(){return 0}}]   //對象當作索引也會被轉換為字符串
"a"
>Object.keys(arr)                       //獲取數組的索引,可以看到其實是字符串
["0", "1", "2"]
> typeof Object.keys(arr)[0] //用typeof再確認一下 "string"

在ECMAScript 6未完全實現之前,你可能看到過下面這樣的代碼:

//定義一個對象,當作map來使用
var map = {};

//如果不存在這個鍵,則為這個鍵賦值
if (!map[key]) {
    map[key] = value;
}

上面的代碼使用了一個常規的對象作為Map來使用,檢查一個給定的鍵是否存在.這里最大的限制就是鍵始終會被轉換成字符串.如果你確定自己的鍵不可能是非字符串,倒也沒什么太大的影響.如果你想存儲一些與某個特定的DOM元素相關的數據的話,你也許會這么做:

// 元素對象會被轉換成字符串
var data = {},
element
= document.getElementById("my-div");

data[element]
= metadata;

不幸的是,元素對象會被轉換成"[Object HTMLDivElement]"或者其他類似的字符串(不同瀏覽器上的值可能會不同).問題就有了:所有的<div>元素都會被轉換為相同的一個字符串,也就意味着雖然你使用了不同的對象作為鍵,但讀取或寫入的都是同一個鍵對應的值.因此,Map類型對JavaScript來說將會非常有用.

ECMAScript 6中的Map類型是個有序的鍵-值對列表,最關鍵的是鍵和值都可以是任意類型.5和"5"是兩個完全不同的鍵,鍵的使用規則和set中值的規則是一樣的:NaN被認為和另一個NaN是相同的, -0和+0是不同的值,也就是說內部不是用===來進行判斷的.你可以使用set()和get()方法從一個map中存儲和獲取數據,如下:

//定義Map
var map = new Map();

//存儲數據
map.set("name", "Nicholas");
map.set(document.getElementById("my-div"), { flagged: false });

//獲取數據
var name = map.get("name"),
    meta = map.get(document.getElementById("my-div"));

在這個例子中,一共存儲了兩個鍵值對.鍵"name"存儲了一個字符串,鍵document.getElementById("my-div")存儲了一個DOM元素的元數據.如果某個鍵不存在於一個map中,則在調用get()方法時會返回undefined.

Map有幾個和Set相同名稱的方法,比如has()方法用來判斷某個鍵是否存在與一個map中,delete()方法用來刪除一個map中的某個鍵-值對.還可以使用size()方法獲取到一個map中的鍵-值對個數:

var map = new Map();
map.set(
"name", "Nicholas");

console.log(map.has(
"name")); // true console.log(map.get("name")); // "Nicholas" console.log(map.size()); // 1 map.delete("name");
console.log(map.has(
"name")); // false console.log(map.get("name")); // undefined console.log(map.size()); // 0

為了能夠更方便的將大量數據添加到map中,你可以向Map構造函數中傳入一個包含數組的數組.由於在引擎內部,每個鍵-值對都是以一個包含兩個元素的數組存儲的,第一個元素作為鍵,第二個作為值.整個map就是一個包含了這些數組的數組,map可以用這樣的形式初始化也就不奇怪了:

var map = new Map([ ["name", "Nicholas"], ["title", "Author"]]);

console.log(map.has(
"name")); // true console.log(map.get("name")); // "Nicholas" console.log(map.has("title")); // true console.log(map.get("title")); // "Author" console.log(map.size()); // 2

在你想操作map中存儲的數據時,你有三種生成器方法可以使用,keys():遍歷map中的所有鍵;values():遍歷map中的所有值;items():遍歷map中的所有鍵-值對(items()是遍歷map時默認的生成器方法).通常使用for-of循環來配合上面的幾個方法:

for (let key of map.keys()) {
console.log(
"Key: %s", key);
}

for (let value of map.values()) {
console.log(
"Value: %s", value);
}

for (let item of map.items()) {
console.log(
"Key: %s, Value: %s", item[0], item[1]);
}

// 相當於map.items() for (let item of map) {
console.log(
"Key: %s, Value: %s", item[0], item[1]);

在單純遍歷鍵或者值時,你會在每個循環內部得到一個單個的值.但在遍歷一個鍵-值對時,你會得到一個數組,該數組的第一個元素是鍵,第二個元素是對應的值.

另外一種可以遍歷map的鍵-值對的方式是使用forEach()方法.該方法和數組的forEach()方法類似.你傳入一個回調函數,在回調函數執行時,會自動傳入三個參數:值,鍵,以及map本身.例如:

map.forEach(function(value, key, map)) {
console.log(
"Key: %s, Value: %s", key, value);
});

另外一個和數組的forEach()方法相同的一點是,你可以傳入第二個可選的參數來指定回調函數中使用的this值:

var reporter = {
report:
function(key, value) {
console.log(
"Key: %s, Value: %s", key, value);
}
};

map.forEach(
function(value, key, map) {
this.report(key, value);
}, reporter);

這里,回調函數中的this值指向了reporter.也就可以使用this.report()了.

和以前遍歷常規對象的方式比較一下:

for (let key in object) {

// 確保這個屬性不是繼承自原型! if (object.hasOwnProperty(key)) {
console.log(
"Key: %s, Value: %s", key, object[key]);
}

}

在使用常規對象充當map的角色時,始終要考慮的就是,`for-in`循環可能會遍歷到原型上的屬性.你必須使用`hasOwnProperty()`方法來確保這個屬性的確是對象自己的.當然,如果這個對象可能有自定義的方法,你也得過濾掉:

for (let key in object) {

// 確保該屬性不是繼承自原型且屬性值不是一個函數! if (object.hasOwnProperty(key) && typeof object[key] !== "function") {
console.log(
"Key: %s, Value: %s", key, object[key]);
}

}

Map的遍歷功能可以讓你更專注與要遍歷的數據本身,而不用擔心其他數據的干擾.這也是用Map存儲鍵-值對比用常規數組更好的一點.

瀏覽器支持

目前Firefox和Chrome都已經實現了Map類型,只是在Chrome中,你必須先通過下面的操作激活ECMAScript 6的新特性:打開頁面chrome://flags,勾選“啟用實驗性 JavaScript”.需要注意的是,這兩個瀏覽器的Map實現都不夠完全.都沒有實現Set的for-of迭代,Chrome的實現還缺少size()方法(屬於ECMAScript 6規范草案的一部分[2])和Map構造函數不能用一個數組的數組來進行初始化.

譯者注:Firefox17(以及更新的18,19)已經實現了用for-of進行Set和Map的遍歷操作,但還沒實現forEach()方法

譯者注:為了兼容那些舊的瀏覽器,可以使用這個實現了ES6中多種集合類型(Set,Map,WeakMap,HashMap)的shim:http://benvie.github.com/harmony-collections/

總結

ECMAScript 6中的Map類型給我們帶來一個很重要的,將來定會被頻繁使用的特性.開發者們長期以來一直想要一個更可靠的方式來存儲鍵-值對,以替代目前依賴於常規對象的情形.Map給我們提供了所有常規對象無法實現的功能, 包括更簡單的遍歷鍵和值以及無需擔心對象原型的干擾.

由於目前ECMAScript 6規范草案還沒有完成.因此,Map還被認為是實驗性的API,在最后規范定稿之前,可能還會發生一些變化.目前所有關於ECMAScript 6的文章都應該被視為是對未來的展望.雖然這些實驗性的API已經在一些瀏覽器中實現了,但最好不要使用在實際的生產環境中.

參考

  1. Simple Maps and Sets (ES6 Wiki)
  2. ECMAScript 6 Draft Specification (ECMA)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM