淺嘗ECMAScript6
簡介
ECMAScript6 是最新的ECMAScript標准,於2015年6月正式推出(所以也稱為ECMAScript 2015),相比於2009年推出的es5, es6定義了更加豐富的語言特性,基於該標准的Javascript語言也迎來了語法上的重大變革。本文列舉了部分es6新特性,希望之前沒接觸es6的小伙伴讀完本文能對下一代js編程有一個初步的認識。
箭頭函數
箭頭函數用 "=>"簡化函數定義,類似於C#, Java8中的Lambda表達式,支持語句塊和表達式函數體,和普通函數的唯一區別在於函數體引用的 this 和包裹代碼的this一致。
// 表達式 var odds = evens.map(v => v + 1); var nums = evens.map((v, i) => v + i); var pairs = evens.map(v => ({even: v, odd: v + 1})); // 代碼塊 nums.forEach(v => { if (v % 5 === 0) fives.push(v); }); // this , 這里 this引用 leon var bob = { _name: "leon", _friends: [], printFriends() { this._friends.forEach(f => console.log(this._name + " knows " + f)); } }
類
ES6的類簡單而言只是基於原型繼承的語法糖, 使用方便的聲明式定義,使得類型定義和其他面向對象語言一致。類支持原型繼承,父類調用,實體和靜態方法以及構造函數。
'use strict' class Animal { constructor(name) { this.name = name; } speak(msg) { console.log(`${this.name} says ${msg}`); } static fight(a, b) { console.log(`${a.name} and ${b.name} are fighting!`); } } class Dog extends Animal { constructor(name) { super('DOG' + name); } } let dogA = new Dog('foo'); let dogB = new Dog('bar'); dogA.speak('hi'); dogB.speak(`what's up`) Animal.fight(dogA, dogB); /** *DOGfoo says hi *DOGbar says what's up *DOGfoo and DOGbar are fighting! */
增強的對象字面量
對象字面量現在支持在對象構建時設置原型對象,定義方法,調用父類構造函數,以及用表達式計算屬性. 這些特性使得對象字面量定義和類型定義十分相似, 給基於對象的設計帶來了便利。
var obj = { // 原型對象 __proto__: theProtoObj, // 簡化定義成員, ‘handler: handler’ handler, // 方法 toString() { // 父類調用 return "d " + super.toString(); }, // 動態屬性名 [ 'prop_' + (() => 42)() ]: 42 };
模板字符串
模板字符串提供了一種構建字符串的語法糖,類似於Perl, Python和其他語言中的字符串插值特性.
// 多行字符串 `In JavaScript this is not legal.` // 字符串插值 var name = "Leon", time = "today"; `Hello ${name}, how are you ${time}?`
解構賦值
解構賦值允許你使用類似數組或對象字面量的語法將數組和對象的屬性賦給各種變量。這種賦值語法極度簡潔,同時還比傳統的屬性訪問方法更為清晰。
// 數組 a=1,b=2,c=3 var [a, b, c] = [1,2,3]; // 數組,沒有匹配的返回undefined , a=1,b=2,c=undefined var [a, b, c] = [1,2]; // 對象, m='leon', n=18 var {name:m,age:n}={name:'leon',age:18} // 對象屬性名與變量名一致時,可以簡寫為 var {foo,bar}={foo:'1',bar:2} // 嵌套 name='leon' var {x:{y:{z:name}}}={x:{y:{z:'leon'}}}
默認參數和不定參數
函數調用者不需要傳遞所有可能存在的參數,沒有被傳遞的參數由默認參數進行填充。 在所有函數參數中,只有最后一個才可以被標記為不定參數。函數被調用時,不定參數前的所有參數都正常填充,任何“額外的”參數都被放進一個數組中並賦值給不定參數。如果沒有額外的參數,不定參數就是一個空數組,它永遠不會是undefined。
// 默認參數 function f(x, y=12) { // 如果沒有傳入y 或傳入的值為undefined ,則y=12 return x + y; } f(3) == 15 // 不定參數 function f(x, ...y) { // y 是一個數組 return x * y.length; } f(3, "hello", true) == 6 function f(x, y, z) { return x + y + z; } // 數組中的每個元素作為參數傳入 f(...[1,2,3]) == 6
let + const 塊級作用域變量
let 和 const 聲明的變量具有塊級作用域, 傳統使用var申明的變量在整個函數內都可訪問。const聲明的變量只可以在聲明時賦值,不可隨意修改,否則會導致SyntaxError(語法錯誤)。
function f() { { let x; { const x = "me"; // 因為是const, 所以不可修改,報錯 x = "foo"; } // 報錯, 用let重定義變量會拋出一個語法錯誤 let x = "inner"; } }
迭代器 和 for..of
迭代器類似於 .NET CLR 的 IEnumerable 或 Java 的 Iterable, 所有擁有[Symbol.iterator]的對象被稱為可迭代的, 而 for ..of 用於遍歷實現了迭代器方法的對象,比如Array, Map, Set, Array-like Object
for (var value of [1,2,3]) { // 依次打印 1,2,3 console.log(value); }
生成器
生成器對象由生成器函數(function* 定義的函數)返回,它同時准守iterator 和 Iterable 協議。著名的koa nodejs框架就是基於此特性構建。
'use strict' function* gen(i){ yield i+1; yield i+2; } function* gen1(i){ yield i; yield* gen(i); // delegate to gen, after gen finished , delegate back and continue yield i+3; return 'finished'; } let g=gen1(10); // g is a generator object console.log(g.next().value); // 10 console.log(g.next().value); // 11 console.log(g.next().value); // 12 console.log(g.next().value); // 13 console.log(g.next().value); // finished console.log(g.next().value); // undefined
模塊
模塊已經得到語言級別的支持,ES6的模塊設計參照了AMD CommonJS 規范。
// lib/math.js // export 定義要導出的對象 export function sum(x, y) { return x + y; } export var pi = 3.141593; // 沒加export, 所以foo不被導出 function foo(){} // app.js // 導入全部在lib/math.js中標記為導出的對象 import * as math from "lib/math"; alert("2π = " + math.sum(math.pi, math.pi)); // otherApp.js // 顯示標記需要導入的成員 import {sum, pi} from "lib/math"; alert("2π = " + sum(pi, pi)); // lib/mathplusplus.js // 導入lib/math中的全部對象並重新導出 export * from "lib/math"; export var e = 2.71828182846; // 默認導出對象 export default function(x) { return Math.log(x); } // app.js // ln為上面的默認導出對象, pi來自於lib/math,e來自於mathplusplus import ln, {pi, e} from "lib/mathplusplus"; alert("2π = " + ln(e)*pi*2);
Map + Set + WeakMap + WeakSet
// Sets var s = new Set(); s.add("hello").add("goodbye").add("hello"); s.size === 2; s.has("hello") === true; // Maps var m = new Map(); m.set("hello", 42); m.set(s, 34); m.get(s) == 34; // Weak Maps var wm = new WeakMap(); wm.set(s, { extra: 42 }); wm.size === undefined // Weak Sets var ws = new WeakSet(); ws.add({ data: 42 });
Symbol
Symbol是es6新添加的一個基元數據類型, 其他的基元類型有string,number,boolean,null,undefined. Symbol是唯一的,一般用作對象的key以存取相關狀態信息。
'use strict' //A symbol is a unique and immutable data type and may be used as an identifier for object properties. //Symbol([description]) let s1 = Symbol(); let s2 = Symbol('hi'); let s3 = Symbol('how are you'); let obj = {[s1]: 18}; // need [] to wrap obj[s2] = "hello"; console.log(obj[s1]); //18 console.log(obj[s2]); //hello obj[s3] = `I'm fine,thank you`; for (let s of Object.getOwnPropertySymbols(obj)) { /** Symbol() Symbol(hi) Symbol(how are you) * */ console.log(s); } var ar = Object.getOwnPropertySymbols(obj); console.log(ar.length); // 3
子類化內置對象
ES6中的內置對象,比如 Array, Date 等可以被子類繼承
// 這里定義一個Array的子類MyArray class MyArray extends Array { constructor(...args) { super(...args); } }
Math + Number + String + Array + Object 新增的方法和屬性
Number.EPSILON Number.isInteger(Infinity) // false Number.isNaN("NaN") // false Math.acosh(3) // 1.762747174039086 Math.hypot(3, 4) // 5 Math.imul(Math.pow(2, 32) - 1, Math.pow(2, 32) - 2) // 2 "abcde".includes("cd") // true "abc".repeat(3) // "abcabcabc" Array.from(document.querySelectorAll('*')) // 返回一個真實的數組 [1, 2, 3].find(x => x == 3) // 3 [1, 2, 3].findIndex(x => x == 2) // 1 ["a", "b", "c"].keys() // iterator 0, 1, 2 ["a", "b", "c"].values() // iterator "a", "b", "c" Object.assign(Point, { origin: new Point(0,0) })
二進制和八進制字面量
// 二進制 0b111110111 === 503 // true // 八進制 0o767 === 503 // true
Promises
Promise 是異步編程的一種規范,解決了js異步編程中callback hell問題, 在es6中原生支持.
'use strict' let p=new Promise(function(resolve,reject){ setTimeout(function(){ resolve(1); },100); }); p.then(function(v){ console.log(`the previous function returned ${v}`); }); Promise.resolve(1).then(function(v){ console.log(v); //1 }).catch(function(r){ console.log(r); }); Promise.reject('failed-leon').then(function(v){ console.log(v); }).catch(function(r){ console.log(r); // failed-leon }); Promise.reject('failed').then(function(v){ console.log(v); },function(r){ console.log(r+'0'); // failed0 }).catch(function(r){ console.log(r+'1'); // not executed }); //Returns a promise that either resolves when all of the promises in the iterable argument have resolved or rejects as soon as one of the promises in the iterable argument rejects. If the returned promise resolves, it is resolved with an array of the values from the resolved promises in the iterable. If the returned promise rejects, it is rejected with the reason from the promise in the iterable that rejected. This method can be useful for aggregating results of multiple promises together. Promise.all([Promise.resolve(1),Promise.resolve(2),Promise.resolve(3)]).then(function(vs){ for(let v of vs){ console.log(v); // 1 2 3 } }); //Returns a promise that resolves or rejects as soon as one of the promises in the iterable resolves or rejects, with the value or reason from that promise. Promise.race([Promise.resolve('a'),Promise.resolve('b'),Promise.resolve('c')]).then(function(v){ console.log(v); // a });
結語
除了以上特性,es6還增強了對unicode編碼的支持,以便更好的支持應用國際化, 還有代理等等特性,這里就不一一列舉了,目前javascript開發范圍越來廣泛,web, 移動應用,智能家居...2015年中旬es6規范正式推出,盡管當前各廠商瀏覽器js引擎對es6還沒有完全支持,但是新版的nodejs已經默認支持了大部分es6特性,而且我們還可以利用babel將es6編譯成es5供瀏覽器端使用。另外對於.net/java程序員而言,新增的es6特性無需費多大力氣就可以熟悉,所以還沒有開始接觸es6的小伙伴現在可以投入es6的懷抱,用js去改變世界了!