ECMAScript 6 – 新對象和現有對象更新
本文是對發布于 DNC 雜志一月版 上的 ES6 系列文章的一個繼續。在第一部分中,我們看見了在 ES6 中即將來臨的對 JavaScript 語言的一些改進。本文將著眼于新的對象類型,以及對該語言中已經存在的對象的 API 的更新。
就如在 第一篇文章 中已經提及的,ES6 致力于匹配 JavaScript 來使之更適合于編寫大規模的應用程序。為了實現該目標,該語言的設計者們已經對其添加了不少的新特性,這些特性的靈感源于那些“有類型(typed)”的 JavaScript 的替代版以及一些其它的庫,包括一些服務端的庫。以下是關于那些新的和更新過的對象的一瞥:
-
新的數據結構,用于更簡單的儲存唯一性的值(Set),或,有唯一性的鍵的鍵-值對(Map)
</li> -
已經存在的對象,比如 Math 和 Number 會獲得新的功能來執行更多的操作,和用更優的方式來執行已存在的操作。
</li> -
String 類型將會獲得一些使得 “解析(parsing)” 操作更方便的特性。
</li> -
Object 類型將會獲得一些函數來 “分配(assign)” 一個對象以及在兩個對象之間進行比較。
</li> -
在 Array 類型上的新函數集現在可以更方便的來查找到一個元素,一個元素的索引,以及在數組內部對元素進行拷貝操作。
</li> -
新的 Proxy 對象來對以存在的對象或者函數擴展(extend)功能集。
</li> </ul>為 ES6 API 提供平臺支持
這些 API 目前并不完全支持所有的平臺。Chrome 和 Opera 最新的版本不支持一些在 String 上的新功能并且根本不支持 Proxy 對象。類似于 traceur 和 6to5 這樣的編譯器沒有提供polyfill相關的API。我是用Firefox nightly去測試所有的這些例子的。一些腳本為了arrow功能和short hand功能使用ES6的新語法,你最好使用traceur編譯腳本。
現在你得到了一個關于ES6 API更新的結論,讓我們開始探索他們吧。
Set 和 WeakSet
Set
Set 是一個包含互不相同的值的集合,這些值可以是任何 JavaScript 類型(即Number,Boolean,String,Object等)。它會忽略任何對其插入重復值的嘗試。Set 是可迭代的;意思是我們可以在 Set 上用 for…of 循環來遍歷。
我們可以通過調用 Set 類型的構造器來創建一個新的 Set 實例,就像這樣:
var mySet = new Set(listOfItems);
..其中
listOfItems是可選參數,它包含一個將要被插入到 Set 中的可迭代的元素列表。如果沒有傳遞這個參數,將會創建一個空的 Set。下面是一個 Set 對象的例子:
var setOfObjects = new Set([17, 19, 38, 82, 17]);
我們可以在 Set 對象上用 for…of 循環來遍歷它,因為 Set 是一個可迭代的對象。下面的代碼會打印 Set 內部存儲的值:
for (let item of setOfObjects) { console.log(item); }注意檢查這個循環的輸出;重復添加到這個 Set 的值是不會顯示出來的。在我們這個例子中,“17”只被存儲了一次。Set 內部通過使用 SameValueZero(x,y) 來忽略重復的值。
接下來讓我們看看 Set 在 API 中提供了哪些方法。
添加元素
元素可以通過 set.add() 方法添加到 Set 中。
setOfObjects.add(4); setOfObjects.add(45); setOfObjects.add(18); setOfObjects.add(45);
第二次嘗試將 45 插入到 Set 中的代碼無法執行成功,因為 Set 中已經包含了那個值。
驗證一個對象的存在性
Set 上的 has() 方法能檢查 Set 中是否包含傳入的對象。對象是按引用而非值比較的。下面的例子說明了這點:
var obj = {value: 100}; setOfObjects.add(obj); console.log(setOfObjects.has(obj)); // true console.log(setOfObjects.has({prop: 100})); // false刪除對象
存儲在Set中的對象,可以通過它們的引用,使用delete() 方法刪除,或者使用clear() 方法清除。 下面是一些例子:
setOfObjects.delete(obj);setOfObjects.clear();Set的大小
Set的size屬性包含了當前的對象數目。
console.log(setOfObjects.size);遍歷Set
之前提到過,Set 可以通過常規的 for…of 循環來遍歷。除此之外,對于Set,還有一些其它的迭代或者循環方式。如下所示: (* 表明方法返回迭代器)
*entries(): 返回一個包含key-value組合對象的迭代器。由于鍵值(key值)和數值(value值)在Set中是一樣的,每個入口都是一個不斷重復關聯數值的數組。
for(let item of setOfObjects.entries()){console.log(item);}*values(): 返回遍歷Set中數值(value值)的迭代器。
for(let item of setOfObjects.values()){console.log(item);}*keys():返回遍歷Set中鍵值(Key值)的迭代器. 由于鍵值(key值)和數值(value值)在Set中是一樣的,所以keys方法和values方法返回一樣的結果
for(let item of setOfObjects.keys()){console.log(item.);}forEach(callback): 這是Set中遍歷入口的另一種方法。Set中的每個入口都會調用回調函數
setOfObjects.forEach(item => console.log(item));WeakSet
WeakSet 是 Set 對應的弱引用版本。WeakSet 不會阻止插入其中的值被垃圾收集。它的工作方式和 Set 類似,但也有以下例外:
-
只能包含對象。屬于 Number、String、Boolean、null 和 undefined 的值都不能被添加進 WeakSet
-
無法迭代或遍歷 WeakSet 中包含的值,也就是說,WeakSet 不支持像 values()、entries() 或 forEach() 那樣的方法
-
WeakSet 支持以下一組操作:add、has 和 delete。這些方法工作起來和用 Set 時一樣
之所以取名叫 WeakSet,是因為它們不會阻止存儲在它們內部的值被垃圾回收。
由于 WeakSet 天生存在上述限制,只有少數情況下會用到它。
Map和WeakMap
Map
Map是鍵值對(key-value pair)對象;鍵(key)和值(value)可以是任意的JavaScript對象或值。鍵(key)在給定的Map中必須是唯一的。像Set一樣,Map是可迭代的。
新的Map對象可以通過如下的 Map 構造函數來創建:
varmyDictionary =newMap(...arguments);Map 構造函數參數是可選的。 如果傳遞參數,這些參數將會用來創建Map;否則,Map 對象將不會包含任何內容。
下面的代碼段顯示了如何使用對象集合來創建Map :
varmyDictionary =newMap([["key1","value1"], ["key2","value2"]]);由于字典是可迭代的,我們可以使用 for…of 循環遍歷每個子項。
for(let dictionaryEntry of myDictionary){console.log(dictionaryEntry);}Map 提供了一系列方法與之交互。讓我們來看一看。
增加項目
新的項目可以通過set()方法加入到Map之中。 這個方法會檢查傳遞到Map的鍵是否已經存在,如果鍵不存在,則將其添加到Map之中;否則就放棄。
下面的代碼段增加了更多的項目到之前創建的Map之中:
myDictionary.set("key3","value4"); myDictionary.set("key2","value5"); varobj = {id:"1"}; myDictionary.set(obj, 1000); myDictionary.set(obj, 1900);以key2 作為鍵,同時以obj 作為鍵,嘗試插入myDictionary的時候,由于它們在myDictionary中已經存在,所以會被丟棄。
鍵通過引用校驗,而不是值。 所以,下面的代碼段會增加一個項目到myDictionary之中。
myDictionary.set({id:"1"}, 826);通過 key 獲取 value
如果 key 是已知的,那么可以使用 get() 方法從一個 Map 中提取 value。如果無法在 Map 中找到 key,那么方法會返回 undefined。
檢查一個 key 是否存在
我們可以用 has() 方法檢查一個 key 是否已經添加到 Map 中。和 Set 的情況類似,has() 方法按引用檢查 key 的匹配項。
console.log(myDictionary.has("key2")); // true console.log(myDictionary.has(obj)); // true console.log(myDictionary.has({id: "1"})); // false通過 key 得到 value
如果 key 是已知的,那么可以使用 get 方法從一個 Map 中提取 value。如果無法在 Map 中找到 key,則方法將返回 undefined。
console.log(myDictionary.get("key2")); // value2 console.log(myDictionary.get("key2ii")); // undefined移除對象
Map 中的對象可以通過 delete 方法一個個的移除,也可以通過 clear 方法一下子全部移除。delete 方法接收 key 值,如果找到這個 key 值所對應的條目并成功刪除,返回 'true',否則返回 'false'。
下面是調用delete 和 clear 方法的一些例子:
console.log(myDictionary.delete({prop: 2000})); //falseconsole.log(myDictionary.delete(obj)); //trueconsole.log(myDictionary.delete("key1")); //truemyDictionary.clear();
Map 的大小
Map 對象的 size 屬性保存了 Map 中條目的個數。
console.log(myDictionary.size);
遍歷 Map
在前面曾提到過,Maps 可以通過常規的 for...of 語句進行遍歷。另外,還有一些方法可以遍歷或循環 Maps 中 key 或 value 的值。下面是這些方法的示例(*表示返回值為迭代器)
*entries(): 返回一個包含 key-value pair 對象的迭代器。迭代器的每個條目是一個長度為 2 的數組,其中第一個值為 key,第二個值為 value。
for(let item of myDictionary.entries()){console.log(item);}*values(): 返回一個包含 Map 中所有 value 的迭代器
for(let item of myDictionary.values()){console.log(item);}*keys():返回一個包含 Map 中所有 key 的迭代器
for(let item of myDictionary.keys()){console.log(item);}forEach(callback): 另外一種循環 Map 中 key 值的方法。對于每一個 key,都會調用一次 Callback 函數。
myDictionary.forEach(item => console.log(item));WeakMap
WeakMap 的工作方式和 Map 類似,但也有一些例外。這些例外和用 WeakSet 時的一樣。WeakMap 不會限制被用于 key 的對象遭到垃圾收集。以下是 WeakMap 的特性列表:
-
key 只能是對象;key 不能是值類型。value 可以是任何類型
-
不支持對其元素進行遍歷。因此,for…of 循環不能被用于遍歷 WeakMap 的元素。entries、values 和 keys 方法是不被支持的
被支持的操作是:set、get、has 和 delete。這些操作的行為和它們在 Map 上的行為是一致的。
Numbers
一些用于處理數字的全局函數如 parseInt, parseFloat 被移到了 Number 對象中,而且在語言層面對不同的數字表示系統做了更好的支持。讓我們看一看這些變化。
數字系統
ES6 定義了顯示表示8進制和2進制數字系統的方法。現在,你可以很方便的使用這些表示方法并與10進制數字進行轉換。
8進制數字的表示方法是在前面加上前綴 “0o”. 通過 Number 對象,可以將帶有這種格式的字符串轉化為對應的數字類型。例如:
varoctal = 0o16;console.log(octal);//output: 14varoctalFromString = Number("0o20");console.log(octalFromString); //output: 16類似的,2進制數字的表示方法為在前面加上“0b”. 你同樣可以將這種格式的字符串轉化為對應的數字類型。
varbinary = 0b1100;console.log(binary); //output: 12varbinaryFromString = Number("0b11010");console.log(binaryFromString); //output: 26
parseInt 和 parseFloat
現在 parseInt 和 parseFloat 函數可通過 Number 對象調用,這樣會更加明確。它們的工作方式和之前一樣。
console.log(Number.parseInt("182"));console.log(Number.parseFloat("817.12"));
isNaN
現在我們可以通過 Number 對象的 isNaN 函數來檢測表達式是否是合法的數字值(number)。全局 isNaN 函數和 Number.isNaN 的不同之處在于,該方法會在檢測是否是數字值前先將值轉換為數字值。下面是一些示例:
console.log(Number.isNaN("10"));//false as “10” is converted to the number 10 which is not NaNconsole.log(Number.isNaN(10));//false
“NaN” 表示 “該值是IEEE-754標準中的非數字值”
另外一種判斷是否是數字值類型的方法是使用typeof()。
isFinite
該函數用來檢測值是否是有限的數字值(number)。該函數會在檢測前試圖將值轉換成數字值。下面是該函數的一些示例:
console.log(Number.isFinite("10")); //false console.log(Number.isFinite("x19")); //falseisInteger
該函數用來檢測值是否是合法的整數。該函數不會在檢測前將值轉換成數字值。下面是該函數的一些示例:
console.log(Number.isInteger("10")); //false console.log(Number.isInteger(19)); //true常量
Number API 現在包含了兩個常量:
-
EPSILON (分數可能的最小數字值)。其值是 2.220446049250313e-16
-
MAX_INTEGER (數值可能的最大值)。其值是 1.7976931348623157e+308
Math
Math 對象上新添加了一些方法,包括對數函數、雙曲函數以及其他一些實用函數。下面羅列了添加到 Math 的方法:
對數函數
-
log10:計算傳入值的以10為底數的對數
-
log2:計算傳入值的以2為底數的對數
-
log1p:將傳入值自增1,然后計算其自然對數
-
expm1:實現前一個函數的逆運算。以傳入值為指數對自然對數的底數求冪,所得結果再減去1
雙曲函數
-
sinh, cosh, tanh:分別是雙曲正弦、雙曲余弦和雙曲正切函數
-
asinh, acosh, atanh:分別是反雙曲正弦、反雙曲余弦和反雙曲正切函數
雜項函數
-
hypot:接受兩個數值作為直角三角形的兩條直角邊長度,返回斜邊的長度
-
trunc:截斷傳入值的小數部分
-
sign:返回傳入值的正負號。如果傳入 NaN 則返回 NaN,傳 -0 返回 -0,傳 +0 返回 +0,任何負數返回 -1,任何正數返回 +1
-
cbrt:返回傳入值的立方根
String
字符串模板化
在每一個JavaScript程序中的很多情況下, 我們會使用大量的字符串, 我們需要使用字符串來拼接出變量的值. 以前, 我們通常使用連接(+)操作符完成拼接. 有時, 這種方式會讓人抓狂. ES6提供了字符串的模板化特性來解決這個問題.
如果我們使用模板, 就不需要手動將字符串分割開來與各種值拼接在一起. 我們可以一口氣寫出完整的字符串. 通過這個特性, 我們不需要使用單引號或者雙引號; 我們要用的是反引號(`). 下面的示例顯示了使用模板的語法. 它將一個變量值和一個字符串組裝成一個REST的API地址:
varemployeeId ='E1001'; vargetDepartmentApiPath = `/api/department/${employeeId}`; console.log(getDepartmentApiPath);模板里支持使用任意數值. 下面的例子顯示了使用兩個變量組成一個API地址:
varprojectId ='P2001'; varemployeeProjectDetailsApiPath = `/api/project/${projectId}/${employeeId}`; console.log(employeeProjectDetailsApiPath);我們可以在模板里進行一些簡單的算術運算. 下面的代碼片段中顯示了使用方式:
varx=20, y=10; console.log(`${x} + ${y} = ${x+y}`); console.log(`${x} - ${y} = ${x-y}`); console.log(`${x} * ${y} = ${x*y}`); console.log(`${x} / ${y} = ${x/y}`);實用函數
在 ES6 中,String 添加了一個 repeat 實用函數。這個函數將字符串重復指定的次數并將其返回。任何字符串都能調用它。
var thisIsCool = "Cool! "; var repeatedString = thisIsCool.repeat(4); console.log(repeatedString);
子串匹配函數
ES6 在 String 的原型上添加了startsWith、endsWith 和 includes 函數,它們被用來檢查某個子串是否分別在給定字符串的開頭、末尾或任何位置出現過。所有這些函數都返回布爾值。includes 函數還被用來檢查在給定的 index 處是否出現了子串。下面的例子演示了這些函數的用法:
startsWith():
console.log(repeatedString.startsWith("Cool! ")); console.log(repeatedString.startsWith("cool! "));endsWith():
console.log(repeatedString.endsWith("Cool! ")); console.log(repeatedString.endsWith("Cool!"));includes():
console.log(repeatedString.includes("Cool! ")); console.log(repeatedString.includes("Cool! ", 6)); console.log(repeatedString.includes("Cool! ", 10));Unicode 函數
ES6 中提供了一些函數可以將 Unicode 編碼轉化為相應的字符,將字符轉化為相應的 Unicode 編碼,或使用不同的合字方式標準化 Unicode 字符串。
codePointAt(): 返回字符串中某個指定位置的字符的 Unicode 編碼。
console.log(repeatedString.codePointAt(0));fromCodePoint(): 是 string 對象的靜態方法。傳入 Unicode 編碼,返回對應的字符。
console.log(String.fromCodePoint(200));normalize(): 返回經過 Unicode 標準化的字符串。傳入標準化格式作為參數。如果該格式是一個錯誤的格式,則使用 NFC 格式。請查看MDN 中的文檔 ,了解這個函數的詳細信息。
"c\u067e".normalize("NFKC"); //"c?e"數組
數組在任意編程語言中都是最常用的數據結構。ES6為數組類型的對象增加了一些新的實用函數,同時,也為數組增加了一些靜態方法,用來查找元素,拷貝元素,遍歷元素,以及將非數組類型轉換為數組類型。
遍歷數組
像Map一樣, Array提供了entries() 和keys() 方法,用來遍歷所有的元素。
*entries(): 從entries 函數返回的每個項目,都是一個包含鍵和其對應值的數組。 is an array of two elements containing a key and its corresponding value. 對數組來說,鍵就和索引一樣。
varcitiesList = ["Delhi","Mumbai","Kolkata","Chennai","Hyderabad","Bangalore"]; for(let entry of citiesList.entries()){ console.log(entry); }*keys(): 鍵和索引一樣;所以這個函數返回數組中每個項目的索引。
for(let key of citiesList.keys()){ console.log(key); }查找
數組 有兩個方法,即 find 和 findIndex,它們通過謂詞來匹配,返回滿足條件的項目。對于謂詞,我們可以傳遞箭頭函數。
find(): 接受謂詞參數,并返回數組中第一個滿足條件的項目。
console.log(citiesList.find( city => city.startsWith("M") ));findIndex():接受謂詞參數,并返回數組中第一個滿足條件的項目的索引。
console.log(citiesList.findIndex( city => city.startsWith("M") ));填充和復制
填充整個 Array,或者用一個元素填充 Array 的一部分,又或將部分 Array 元素填充至其余部分,這些都將變得簡單。
fill():下面是 fill 函數的調用語法:
arrayObject.fill(objectToFill, startIndex, endIndex);只有第一個參數是必需的。當只傳一個參數就調用它時,它將傳入的值填充至整個數組。
citiesList.fill("Pune"); citiesList.fill("Hyderabad", 2); citiesList.fill("Bangalore", 3, 5);copyWithin():將數組中的一個或多個元素復制到數組的其他位置。
citiesList.copyWithin(0, 3); // elements at 0 to 2 into elements from 3 onwards citiesList.copyWithin(0, 3, 5); // elements at 0 to 2 into elements from 3 to 5 citiesList.copyWithin(0, -3); // negative index starts from end of the array
轉換成數組
ES6 為 Array 添加了兩個靜態方法,用于將數據集合和數據流轉換成 Array。
of():這個函數傳入一個對象列表,返回一個包含這些對象的 Array。
var citiesInUS = Array.of("New York", "Chicago", "Los Angeles", "Seattle");from():用于將形如 Array 的數據(即函數的參數)轉換成數組。
function convertToArray() { return Array.from(arguments); } var numbers = convertToArray(19, 72, 18, 71, 37, 91);對象
Object 在 ES6 中獲得了兩個新的靜態函數——用來比較兩個對象,以及用來將多個對象上的可枚舉屬性賦值到一個對象上。
is():接受兩個對象,返回一個用來表示對象是否相等的布爾值
var obj = {employeeId: 100}; var obj2 = obj; console.log(Object.is(obj, {employeeId: 100})); // false console.log(Object.is(obj, obj2)); // trueassign():下面是這個函數的調用語法:
Object.assign(target, source1, source2, …)將所有來源對象上的可枚舉屬性賦值到目標對象上。
var obj3 = {departmentName: "Accounts"}; var obj4 = {}; Object.assign(obj4, obj, obj3); // contents of obj4: {employeeId: 100, departmentName: "Accounts"}代理(Proxy)
正如其名,Proxy 對象被用來在對象和方法周圍創建代理。Proxy 對象對于完成某些任務很有幫助,例如在調用一個函數前進行校驗,當訪問一個屬性的值時對其進行格式化。在我看來,在 JavaScript 中,代理定義了一種新的裝飾(decorate)對象的途徑。讓我們來實戰演練一下。
假設有這樣一個對象:
var employee = { employeeId: 'E10101', name: 'Hari', city: 'Hyderabad', age: 28, salary: 10000, calculateBonus() { return this.salary * 0.1; } };代理 Getters
當這個員工(employee)的工資(salary)被訪問時,我們來格式化它的值。為此我們需要在對象屬性的 getter 上定義一個代理來格式化數據。為了完成這個任務,讓我們來定義一個 Proxy 對象:
var employeeProxy = new Proxy(employee, { get(target, property) { if (property === "salary") { return `$ ${target[property]}`; } return target[property]; } }); console.log(employeeProxy.salary);正如你所見,代理對象的 get 方法帶有兩個參數:
· target:被重新定義 getter 的那個對象
· property:被訪問的那個屬性的名稱
再看一遍這個代碼段,你會發現我在編寫時用到了兩個 ES6 的特性:定義方法的簡寫形式以及模版字符串。
代理 Setters
對于一個員工(employee)的 EmployeeId 而言只能被賦值一次,接下來任何對其賦值的嘗試都將被阻止。為了做到這點,我們可以在對象的 setter 上創建代理,比如以下代碼片段:
var employeeProxy = new Proxy(employee, { set(target, property, value) { if (property === "employeeId") { console.error("employeeId cannot be modified"); } else { target[property] = value; } } }); employeeProxy.employeeId = "E0102"; // Logs an error in the console代理函數調用
假定當員工(employee)的工資(salary)在 $16,000 以上時,要為其計算獎金(bonus)。但是上述對象的
calculateBonus方法沒有檢查這個條件。讓我們通過定義一個代理來檢查這個條件。employee.calculateBonus = new Proxy(employee.calculateBonus, { apply(target, context, args) { if (context.salary < 16000) { return 0; } return target.apply(context, args); } }); console.log(employee.calculateBonus()); // Output: 0 employee.salary = 16000; console.log(employee.calculateBonus()); // Output: 1600總結
正如我們所見,ES6 為已有的對象帶來了一些新的 API,同時帶來了一些新的對象類型和數據結構,從而簡化了很多工作。正如所提到的,截止到發稿時,其中的部分 API 還未在所有平臺上得到支持。希望它們在不久的將來能得到支持。在以后的文章里,我們將探索有關 promise 和 ES6 模塊化的內容。
下載本文的完整源代碼(GitHub)
本文由用戶 dwd4 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享! -