![](http://static.javashuo.com/static/loading.gif)
這篇文章介紹了5個 ES6 特性,使你的 JavaScript 代碼變的更好。不用說,咱們大多數前端開發工程師很是關注 JavaScript 的性能和特性,這就是爲何 ES6 對於咱們來講是如此使人興奮。css
ES6的變化是巨大的,是使人興奮的,也有使人困惑的地方。在本文中,我將介紹5個 ES6 的新特性,您能夠當即使用它們來加強你的 JavaScript代碼,以及哪些特性不建議使用。前端
Ecma是什麼?
JavaScript 多年來已經有了不少版本,但整體而言,進化很緩慢,直到最近。 我以爲在過去的兩年裏,發生了比過去二十年更多的變化。 ES6 是 EcmaScript 6 的簡稱。它也稱爲 EcmaScript 2015 或 ES 2015。這些都是「新的Javascript」的不一樣名稱。java
我不徹底肯定爲何最近 JS 改變如此之大,但彷佛是由於主要的 JavaScript 引擎廠商終於有興趣推進該語言的發展了。es6
此外,像 TypeScript 這樣的 轉移器 的出現,使得在將其用於瀏覽器以前,可使用新的語言特性。這兩個組合將大大推進 JavaScript 的發展。web
JS很重要,由於它是 web 的構造者,並愈來愈多的服務器端使用開始 Node ,愈來愈多的人使用 Cordova,React Native 和 Electron 來開發手機和桌面應用程序。typescript
簡而言之:JS無處不在。shell
因此重要的是咱們來推進它。不演化的語言開始走向死亡。改善語言意味着咱們能夠改進咱們的代碼。咱們的應用程序能夠更少地出現錯誤。其中一些特性能使代碼更快地運行。因此讓咱們從變量開始,來一塊兒來看看 ES6 的新特性。數據庫
變量的問題
在 JavaScript 中,當您要建立一個變量時,可使用 var
關鍵字。 var
很是強大,但它有一些問題。首先,變量老是不斷變化的。沒有辦法在 JavaScript 中定義一個真正的常量。您能夠經過建立具備不可變屬性的對象來近似僞造常量。您能夠覆蓋該屬性的setter,使其不能由外部代碼設置,但它須要不少額外的代碼,須要一個完整的對象來代替一個簡單的變量。json
JavaScript 代碼:
- var VARS = {
- foo
- set = function() { }
- get = function() { return 42 }
- }
- VARS.foo = 42; // now I have a constant
常規Javascript變量還有一個做用域問題。 看看這段代碼。
JavaScript 代碼:
- function badCode() {
- if(foo) {
- var x = 5;
- }
- if(bar) {
- console.log(x);
- }
- }
在函數體內經過 var
聲明的變量,在函數體內全局可用, var
聲明的變量沒有塊級做用域。若是你認爲上面的 console.log
語句不能正常執行,由於在 bar
定義在不一樣的塊中。那麼我告訴你,在相似於 java 或 C# 等語言中,你是對的,可是在 JavaScript 中,var
聲明的變量,在函數體內全局可用,而不是在語句塊中可用。
當咱們添加變量提高時,它會變得更糟。 考慮這個代碼:
JavaScript 代碼:
- function badCode() {
- console.log("foo is",foo);
- var foo = 'bar';
- }
我在 foo
定義以前使用它。 這段代碼意味着什麼? JavaScript 引擎會將變量聲明提高到函數塊的頂部。 但變量提高很容易引起一些難以診斷的細微錯誤。
看看這個 for
循環:
JavaScript 代碼:
- for(var i=0; i<5; i++) {
- console.log(i);
- }
- console.log(i);
該變量 i
僅在循環體內使用,但我仍然能夠在循環體外部引用它。 這可能會引起大量的 bug 。
好消息是咱們不用再必須使用 var
定義變量了,咱們可使用 const
和 let
。
介紹 const 和 let
新的關鍵字 const
徹底符合這個名字的意思。它能夠定義一個真正的常量。若是您嘗試從新設置常量值,您將收到運行時錯誤。更好的是,Lint語法檢查工具能夠在編譯時檢測出這種錯誤,因此您能夠在開發時較早的發現這種錯誤。
另外一種新的變量定義方式是使用 let
關鍵字。let
就像 var
,可是它是塊級做用域而不是函數做用域。下面代碼console.log(x)
會報錯:
JavaScript 代碼:
- function goodCode() {
- if(foo) {
- let x = 5;
- }
- if(bar) {
- console.log(x); //error
- }
- }
讓咱們回顧一下 for
循環的例子:
JavaScript 代碼:
- function goodCode() {
- for(let i=0; i<5; i++) {
- console.log(i);
- }
- console.log(i); //error
- }
如今,個人變量只限於for循環體內使用。沒有辦法能夠不經意地使用。另外,let
定義的變量不能提高,因此全部這些難以想象的移動變量都消失了。
新的關鍵字 const
和 let
能夠徹底替代 var
。 使用現代瀏覽器和最新版本的Node,沒有任何理由使用 var
了,甩掉歷史包袱。(愚人碼頭注:我的認爲仍是要看使用環境。)
超級字符串
ES6引入了一種新類型的字符串,稱爲模板字面量。我更喜歡稱之爲超級字符串。你使用一個超級字符串就像一個常規的字符串,可是你不光可使用單引號或雙引號,你還可使用反引號`
。
JavaScript 代碼:
- var q = 'foo';
- var qq = "foo";
- var bq = `foo`;
- var qq = "Sally sells \"sea shells\"";
- var bq = `Sally sells "sea shells"`;
到如今爲止還挺好,但沒有什麼非同尋常的地方。不過它確實有一個直接的優點。若是您須要在字符串中使用雙引號或單引號,則沒必要再轉義(愚人碼頭注:原來單引號雙引號配對不是也能夠嗎?不知道做者爲何這樣寫。嘎嘎)。然而,超級字符串還有其餘一些技巧。
多行字符串串聯
終於咱們能夠有真正的多行字符串了。若是你須要引用幾行的字符串?你沒必要再使用加號的方式連接字符串。直接放入換行符,它就能正常工做。
JavaScript 代碼:
- var q = 'foo';
- var qq = "foo";
- var bq = `foo`;
- var qq = "Sally sells \"sea shells\"";
- var bq = `Sally sells "sea shells"`;
表達式換碼
另外一個新特性是表達式換碼(愚人碼頭注:前端說的比較多的是替換符)。在超級字符串中,您能夠在${}
的括號內放入任何有效的 JavaScript 表達式。這比雙引號更加乾淨,最新的IDE通常都會語法高亮顯示這些表達式。
JavaScript 代碼:
- var name = "Alice";
- var greeting = `Good morning ${name}`;
- var amount = 5;
- var price = 3;
- var sales = `That costs ${amount*price} dollars`;
將表達式換碼與多行字符串支持相結合,爲咱們提供了很棒的HTML模板。
JavaScript 代碼:
- var template = `
- <div>
- <h3>Good Morning ${name}</h3>
- <p>
- That item will cost you
- <b>${amount*price}</b>
- dollars.
- </p>
- </div>`
JavaScript 代碼:
箭頭函數
上面說的是字符串和變量,如今讓咱們來看看函數。若是您之前聽過ES6,不少可能都是關於箭頭函數的。這是更緊湊的常規函數語法。他們也有一個很是重要的區別:this
變量指向的是不一樣的東西。
假設你想循環一個數組使其元素的值乘2,而且生成一個新數組。你能夠用下面的 for
循環來作,可是這樣會產生額外的變量,而且能夠很容易寫錯。
JavaScript 代碼:
- var output = [];
- for(var i=0; i<;input.length; i++) {
- output[i] = input[i] * 2;
- }
JavaScript數組有一個新方法 map
,它在每一個元素上調用一個函數來生成一個新的元素,而後將其放入新數組中。代碼以下:
JavaScript 代碼:
- var output = input.map(function(x) {
- return x * 2;
- });
這看起來更好,更簡潔。x * 2
部分是唯一實際工做的部分。 其他的是語法開銷。若是使用箭頭功能,咱們能夠這樣作:
JavaScript 代碼:
- var output = input.map((x)=>x*2);
哇!這個更加簡潔了。讓我解釋一下上面的代碼。箭頭函數能夠重寫函數,而不須要實際的function
關鍵字。而是在包含函數參數的括號後面跟 =>
符號。
JavaScript 代碼:
- //常規函數
- function (x) {
- return x * 2;
- }
- //箭頭函數樣式
- (x) => {
- return x * 2;
- }
箭頭函數讓咱們寫一樣的代碼更加簡潔。但咱們還能夠繼續簡化。刪除空格,相同的更簡短。
JavaScript 代碼:
- (x) => { return x * 2; }
咱們還可使它更簡短。若是箭頭函數只包含一個表達式,咱們能夠刪除return
,大括號和分號,寫成一個單行表達式,它會自動返回它的值。這樣就簡化到極致了。
JavaScript 代碼:
- (x) => x * 2
- var output = input.map((x)=>x*2);
箭頭函數可使您的代碼很是緊湊和強大。 可是還有一個很是重要的事情是:它修復了 this
。
this 魔咒
在 JavaScript 中,最難以想象是變量 this
老是引用函數被調用的那個對象。因此像下面的代碼肯能不會是你認爲的那樣執行。
JavaScript 代碼:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- button.addEventListener('click', function(evt) {
- this.clicked = true; //不會安裝你想的那樣執行
- });
- }
當您使用其餘對象時, this
上下文可能與預期的不一樣。當你將一個函數傳遞到其餘地方被調用時,它可能會使用不一樣的 this
調用該函數。若是您將一個事件處理程序添加到 button , this
將指向 button 。有時這就是你想要的,但在上面的代碼中不是。咱們但願 this
指向 App
對象,而不是 button 。
this
是JavaScript長期存在的問題。很常見的作法是,開發人員創造了一種 self 模式,使用臨時變量 self
保存對 this
的正確引用。看起來很噁心,但它能正常工做。
JavaScript 代碼:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- var self = this; //特別注意這一行
- button.addEventListener('click', function(evt) {
- self.clicked = true;
- });
- }
另外一種解決問題的方法是 bind 綁定這個函數。bind
方法強制 this
執行一個特定的對象,無論函數後面如何被調用。(愚人碼頭注: http://www.css88.com/archives/5611 )
JavaScript 代碼:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- var callback = (function(evt) {
- this.clicked = true
- }).bind(this);//特別注意這一行
- button.addEventListener('click',callback);
- }
再次執行, this
能正常指向 App
對象,但它不是很好。咱們須要寫額外的代碼,而且 bind
會帶來額外的開銷。箭頭函數爲咱們提供了更好的方法。
JavaScript 代碼:
- function App() {
- this.clicked = false;
- var button = document.getElementById('button');
- button.addEventListener('click',()=>{
- this.clicked = true;
- });
- }
箭頭函數自動捕獲該函數定義時做用域中的 this
變量,而不是來自函數被調用的做用域中。這意味着您能夠將函數傳遞給其餘地方,使用時要知道 this
正確的指向。在上面的代碼中,沒有任何的 hack ,就能按照咱們所指望的那樣執行。
簡而言之,箭頭函數真的很棒。我能夠儘量地使用它。它們使您的代碼更短,更容易閱讀,並且 this
也變得很明智。
Promises
箭頭函數的另外一個很強大的特性是它們能與 Promises 配合的很好。Promise 是 JavaScript 中一種新的對象,旨在幫助須要很長時間執行的事情。JavaScript 沒有線程,因此若是你想作一些可能須要很長時間的事情,那麼你必須使用回調。
例如,在Node中,您可能須要加載文件,解析它,作一個數據庫請求,而後寫一個新的文件。這些都必須按順序完成,但它們都是異步的,因此你必須嵌套你的回調。這就產生了被JS開發人員稱之爲「金字塔」式的代碼風格。你須要大量的嵌套代碼。
JavaScript 代碼:
- fs.readFile("file.txt", function(err, file) {
- db.fetchNames(function(err, names) {
- processFile(file, names, function(err, outfile) {
- fs.writeFile('file.txt',outfile, function(err, status) {
- console.log("we are done writing the file");
- })
- });
- })
- });
這段代碼很醜,很難理解,並且有不少討厭的隱藏 bug。 Promises 能夠幫助咱們擊敗 「金字塔」 式的代碼風格。
JavaScript Promise
表示一個值當前可能不可用,可是未來有值的對象。使用 then
函數能夠添加回調,當最終值 ready 時,調用這個回調。
JavaScript 代碼:
- var prom = makeSomePromise();
- //value not ready yet
- prom.then((value)=>{
- //do something with the value
- })
Promises then
回調很像傳統的回調,但 Promises 增長了一個額外的轉變 :他們能夠鏈式調用。讓咱們重溫一下上面的代碼。每一個函數必須按順序調用,每一個函數都取決於前一個函數的結果。使用Promises ,咱們能夠這樣作。
JavaScript 代碼:
- fs.readFile("file.txt")
- .then((file) => {
- return db.fetchNames().then((names)=>{
- return processFile(file,names)
- })
- })
- .then((outfile)=>{
- return fs.writeFile('file.txt',outfile);
- })
- .then(()=>{
- console.log("we are done writing the file");
- });
請注意看,箭頭函數如何使這個代碼變得漂亮乾淨。每一個 then
回調返回一個值。這個值傳遞給下一個函數,因此咱們全部的函數均可以很容易的鏈式調用。
如今你會注意到,processFile
命令須要前面兩個值的結果,可是 Promises 只傳遞一個值。咱們也不用關心readFile
和fetchNames
執行的順序。咱們只想知道什麼時候完成。Promises 能夠作到這一點。
Promise.all()
假設要從文件名數組中加載每一個文件,並在完成時通知。咱們能夠用 Promise.all()
來作到這一點。all
都是 Promise 中的一個實用方法,它獲取一系列 Promises ,並返回了一個新的 Promises ,當全部的子 Promises 完成時,它將獲得 resolves 狀態。如下是咱們如何使用 Promise.all
加載全部文件的示例。(假設readFile
是一個讀取文件返回 Promises 的函數)。
JavaScript 代碼:
- var proms = filenames.map((name)=> readFile(name));
- Promise.all(proms).then((files)=>{
- console.log(`we have ${files.length} files`);
- });
如今咱們能夠重寫咱們原來的Node示例:
JavaScript 代碼:
- Promise.all([
- fs.readFile("file.txt"),
- db.fetchNames()
- ])
- .then((vals) => processFile(vals[0],vals[1]))
- .then((outfile)=> fs.writeFile('file.txt',outfile))
- .then(()=> console.log("we are done writing the file"));
我將讀取的文件和數據庫調用合併到使用Promise.all
的單個 Promise 中。它將返回的值是一個數組,包含兩個子 Promise 的結果,因此我能夠把它們放到 processFile
中。請注意,我使用了縮寫箭頭語法使代碼更小更乾淨。
處理失敗
如今考慮若是這些 Promises 中有一個失敗會發生什麼?爲了處理第一個失敗,咱們能夠將其放入 try
/ catch
語句塊中,但下一個 then
仍然會被調用。若是第一個失敗,咱們但願一切都中止。Promises有另一個技巧:捕獲回調。
在下面的新版本的代碼中,若是有任何失敗,它將當即跳過其他的 Promises ,跳轉到結尾的 catch
回調中。catch
回調中,咱們還能夠添加更多的子句。
JavaScript 代碼:
- Promise.all([
- fs.readFile("file.txt"),
- db.fetchNames()
- ])
- .then((vals) => processFile(vals[0],vals[1]))
- .then((outfile)=> fs.writeFile('file.txt',outfile))
- .then(()=> console.log("we are done writing the file"))
- .catch((e) => {
- console.log("some error happened");
- });
編寫自定義Promises
固然 Promises 只有在咱們調用的API 實際使用 Promises 的狀況下才能工做。咱們能夠期待許多庫開始轉換爲 Promises,固然咱們也能夠編寫本身的 Promises 。咱們使用 Promise
構造函數來實現。
JavaScript 代碼:
- function makePromise(foo,bar) {
- return new Promise((resolve, reject) => {
- try {
- //do long stuff
- resolve(value);
- } catch {
- reject(error);
- }
- });
- }
它須要兩個值:resolve
和 reject
。這些都是回調函數。回調裏面你能夠作任何須要花很長時間執行的事情,即便是它涉及多個回調。當徹底完成後,調用具備最終值的 resolve()
。而後,這將發送到任何你使用 Promises 的第一個 then
子句。
若是發生錯誤,而且您想 reject 該值,而不是拋出錯誤,請使用 reject()
,傳遞所需的任何替代值。
這是一個現實中的例子。 我一直使用 AJAX 調用,但它們可能很是醜陋,像這樣。
JavaScript 代碼:
- var url = "http://api.silly.io/api/list/e3da7b3b-976d-4de1-a743-e0124ce973b8?format=json";
- var xml = new XMLHttpRequest();
- xml.addEventListener('load', function() {
- var result = JSON.parse(this.responseText);
- console.log(result);
- });
- xml.addEventListener('error',function(error) {
- console.log("something bad happened");
- });
- xml.open("GET",url);
- xml.send();
讓咱們把這個代碼包裝成一個承諾 Promise。
JavaScript 代碼:
- function doGet(url) {
- return new Promise((resolve,rej)=>{
- var xml = new XMLHttpRequest();
- xml.addEventListener('load', function() {
- var result = JSON.parse(this.responseText);
- resolve(result);
- });
- xml.addEventListener('error',function(error) {
- reject(error);
- });
- xml.open("GET",url);
- xml.send();
- });
- }
基本上是相同的代碼,但我能夠像這樣調用它。
JavaScript 代碼:
- var url = "someapi.com/dostuff?foo=bar";
- doGet(url).then((obj)=>{
- console.log(obj);
- });
哇,這樣更乾淨。實際上,咱們不須要本身編寫 AJAX Promise 包裝器,由於有一個新的Web標準 fetch 已經爲我作了這些事情。可是如今全部瀏覽器都還沒支持 fetch ,因此咱們可使用咱們本身編寫的 Promise ,直到瀏覽器支持 fetch 的時候。
第一次包裝 Promise 可能有點困難,但一旦你開始使用它們,我想你會很喜歡他們。它們能夠很是容易的將多個函數集成到一個具備邏輯意義的單個工做流中,並正確地捕獲全部錯誤。
Arrays
最後我想向你們展現一些新的 Array 功能。大多數功能對於ES6 來講不能算是新的,其實有些是至關老了。然而,它們終於獲得了各方面的支持,並與 箭頭函數和 Promise 結合的很好。因此我認爲他們是新功能。
假設你想對數組的每一個元素作一些事情。可使用 forEach
或 map
函數代替 for
循環。
JavaScript 代碼:
- var values = [1,2,3,4,5,6,7,8,9,10];
- values.forEach((x)=>console.log(x));
- var doubled = values.map((x) => x*2);
forEach
函數在數組中的每一個元素上運行回調。map
函數也在每一個元素上運行,但它將每一個回調的結果存儲到一個新的數組中。
若是要從數組中過濾出某些值,請使用filter
函數。
JavaScript 代碼:
- //查找匹配 filter 全部的值
- var evens = values.filter((x)=>x%2==0);
Array還具備基於某些標準在數組中查找單個值的功能。
JavaScript 代碼:
- //查找第一個匹配的值
- var six = values.find((x) => x >= 6);
- //找到第一個匹配的索引
- var index = values.findIndex((x) => x >= 6);
- //若是至少有一項匹配,則爲true
- var any = values.some((x) => x > 10);
最後,可使用 reduce
函數將數組減小到單個值。reduce
很是強大,能夠用來作不少事情,好比一個數組求和或 flatten 嵌套數組(愚人碼頭注:下降數組嵌套,例如將[1,[2,3],4]
轉換爲[1,2,3,4]
,這就叫 flatten)。
JavaScript 代碼:
- //將數組減小到單個值
- var sum = values.reduce((a,b) => a+b, 0);
- //flatten 嵌套數組
- var list1 = [[0, 1], [2, 3], [4, 5]];
- var list2 = [0, [1, [2, [3, [4, [5]]]]]];
- const flatten = arr => arr.reduce(
- (acc, val) => acc.concat(
- Array.isArray(val) ? flatten(val) : val
- ),
- []
- );
- flatten(list1); // returns [0, 1, 2, 3, 4, 5]
- flatten(list2); // returns [0, 1, 2, 3, 4, 5]
循環對象中的屬性,您可使用 Object.keys
獲取一個包含全部屬性名稱數組,而後用 forEach
循環它
JavaScript 代碼:
- var obj = {
- first:'Alice',
- middle:'N',
- last:'Wonderland',
- age:8,
- height:45,
- }
- Object.keys(obj).forEach((key)=>{
- console.log("key = ", key);
- console.log("value = ", obj[key]);
- });
要避免的事情
我已經介紹了 ES6 今天就可使用的五個功能,但ES6中還有許多其餘功能,這個階段你應該避免?或者是由於它們沒有提供有價值的東西,或者尚未獲得很好的支持。這些包括:
- Destructuring
- Modules
- Default Parameters
- Spread Operator
- Symbols
- Generators and Iterators
- Weak Maps and Weak Sets
- Proxies
解構容許你經過名稱從對象中引值。它在幾種狀況下有用,可是我發現最好的用法是從模塊中提取函數。不幸的是模塊仍然是一團糟並且不要在任何地方使用,因此如今應該避免使用他們。
除了解構,建議你不要使用默認參數,和擴展操做符(...
)。我發現這些比他們使用價值更麻煩,至少如今是。
Symbols,Generators(生成器),Iterators(迭代器),Weak Maps (弱映射)和 Weak Sets(弱集合),Proxies(代理)是很是有用的,可是它們在任何地方都不支持,因此我建議你等一下子再能使用它們。
還有一個新的類語法。它仍然使用JavaScript的原型繼承,但它使得定義一個類的語法更加清晰和一致。然而,若是沒有新的模塊支持,它也沒有價值,因此我建議等一下子。
JavaScript 代碼:
- class Foo extends Bar {
- constructor(stuff) {
- super(stuff);
- this.first = "first name"
- }
- doStuff() {
- }
- doMoreStuff() {
- }
- }
我能夠用它嗎?
大多數桌面和移動端的瀏覽器都支持我向你展現的全部內容。可是,根據您的用戶狀況,您可能須要支持舊的瀏覽器/舊版移動操做系統。每當你想知道他們的支持狀況,去 caniuse.com。它會告訴你每一個瀏覽器的什麼版本支持什麼。
若是您須要支持IE 10 如下的瀏覽器,那麼可使用像 TypScript 或 Babel 這樣的轉換器。
因此這些ES6的五個很棒的功能,你能夠當即開始使用。
示例代碼
咱們建立了一個使用 箭頭函數 和 Promises 的 示例代碼的目錄 ,還有許多實用的Web服務進行通訊,如實時語言翻譯,地理編碼和chatbot apis等等。