JavaScript在過去幾年中發生了很大的變化。這些是您今天能夠開始使用的12項新功能!es6
該語言的新增內容稱爲ECMAScript 6.它也稱爲ES6或ES2015 +。編程
自1995年JavaScript構思以來,它一直在緩慢發展。每隔幾年就會發生新增事件。 ECMAScript於1997年成立,旨在指導JavaScript的發展方向。它已經發布了ES3,ES5,ES6等版本。數組
如您所見,ES3,ES5和ES6之間存在10年和6年的差距。此後每一年進行小幅增量變動。而不是像ES6那樣一次作大規模的改變。promise
全部現代瀏覽器和環境都支持ES6!瀏覽器
Chrome,MS Edge,Firefox,Safari,Node等等都支持JavaScript ES6的大多數功能。所以,您將在本教程中學習的全部內容均可以當即開始使用。app
讓咱們開始使用ECMAScript 6!函數
您能夠在瀏覽器控制檯上測試全部這些代碼段!學習
使用ES6,咱們從使用var聲明變量到使用let / const。測試
var出了什麼問題?this
var的問題是變量泄漏到其餘代碼塊中,例如for循環或if塊。
ES5 var x = 'outer'; function test(inner) { if (inner) { var x = 'inner';// scope whole function return x; } return x;// gets redefined because line 4 declaration is hoisted } test(false);// undefined 😱 test(true);// inner
對於test(false)你會指望返回outer,可是你卻獲得undefined。
爲何?
由於即便沒有執行if-block,第4行中的表達式「var x」也會被提高。
var是函數做用域。它甚至在被聲明以前就能夠在整個功能中使用。
聲明已被掛載。所以,您能夠在聲明變量以前使用它。
初始化不會被提高。若是您使用var ,那麼總會將變量聲明在頂部。
在應用掛載規則後,咱們能夠更好地瞭解發生的狀況:
ES5 var x = 'outer'; function test(inner) { var x;// HOISTED DECLARATION if (inner) { x = 'inner';// INITIALIZATION NOT HOISTED return x; } return x; }
ES6 let x = 'outer'; function test(inner) { if (inner) { let x = 'inner'; return x; } return x;// gets result from line 1 as expected } test(false);// outer test(true);// inner
用let代替var會使事情按預期工做。若是未調用if塊,則變量x不會從塊中提高。
在ES6中,let將變量提高到塊的頂部(不是像ES5那樣位於函數的頂部)。
可是,在變量聲明以前引用塊中的變量會致使「ReferenceError(系統報錯)」。
let被限制爲塊級做用域。在聲明以前不能使用它。
「Temporal dead zone」 是從塊開始到聲明變量的區域。
IIFE(當即執行函數)
讓咱們在解釋IIFE以前展現一個例子。 看看這裏:
ES5 { var private = 1; } console.log(private);// 1
如你所見,private漏掉了。 您須要使用IIFE(當即調用的函數表達式)來包含它:
ES5 (function(){ var private2 = 1;})(); console.log(private2);// Uncaught ReferenceError
若是你看過jQuery / lodash或其餘開源項目,你會發現他們有IIFE來避免污染全局環境,只是在全局定義,如_,$或jQuery。
在ES6上更清潔,當咱們只使用塊和let時,咱們也不須要再使用IIFE:
ES6 { let private3 = 1; } console.log(private3);// Uncaught ReferenceError
Const
若是你根本不想改變變量,你也可使用const。
用'let和const代替var`。
對全部引用使用const;避免使用var。
若是必須從新分配引用,請使用let而不是const。
當咱們有模板文字時,咱們不須要作更多的嵌套鏈接。看一看:
ES5 var first = 'Adrian'; var last = 'Mejia'; console.log('Your name is ' + first + ' ' + last + '.');
如今你可使用反引號和字符串插值$ {}:
const first = 'Adrian'; const last = 'Mejia'; console.log(`Your name is ${first} ${last}.`);
咱們沒必要再鏈接字符串+ n了:
var template = '<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" >\n' + ' <div class="view">\n' + ' <input class="toggle" type="checkbox" [checked]="todo.isDone">\n' + ' <label></label>\n' + ' <button class="destroy"></button>\n' + ' </div>\n' + ' <input class="edit" value="">\n' + '</li>'; console.log(template);
在ES6上咱們能夠再次使用反引號來解決這個問題:
const template = `<li *ngFor="let todo of todos" [ngClass]="{completed: todo.isDone}" > <div class="view"> <input class="toggle" type="checkbox" [checked]="todo.isDone"> <label></label> <button class="destroy"></button> </div> <input class="edit" value=""> </li>`; console.log(template);
兩段代碼都會有徹底相同的結果。
ES6解構很是有用和簡潔。請遵循如下示例:
從數組中獲取元素
ES5 var array = [1, 2, 3, 4]; var first = array[0]; var third = array[2]; console.log(first, third);// 1 3
es6的寫法:
ES6 const array = [1, 2, 3, 4]; const [first,,third] = array; console.log(first, third);// 1 3
交換 values
ES5 var a = 1; var b = 2; var tmp = a; a = b; b = tmp; console.log(a, b);// 2 1
es6的寫法:
ES6 let a = 1; let b = 2; [a, b] = [b, a]; console.log(a, b);// 2 1
ES5 function margin() { var left=1, right=2, top=3, bottom=4; return { left: left, right: right, top: top, bottom: bottom }; } var data = margin(); var left = data.left; var bottom = data.bottom; console.log(left, bottom);// 1 4
在第3行中,您還能夠將其返回到這樣的數組中(並保存一些輸入):
return [left, right, top, bottom];
可是,調用者須要考慮返回數據的順序。
var left = data[0]; var bottom = data[3];
使用ES6,調用者只選擇他們須要的數據(第6行):
ES6 function margin() { const left=1, right=2, top=3, bottom=4; return { left, right, top, bottom }; } const { left, bottom } = margin(); console.log(left, bottom);// 1 4
注意:第3行,咱們還有其餘一些ES6功能正在進行中。咱們能夠將{left:left}壓縮爲{left}。看看它與ES5版本相比有多簡潔。那不是很酷嗎?
ES5 var user = { firstName: 'Adrian', lastName: 'Mejia' }; function getFullName(user) { var firstName = user.firstName; var lastName = user.lastName; return firstName + ' ' + lastName; } console.log(getFullName(user));// Adrian Mejia
es6(但更簡潔)相同:
ES6 const user = { firstName: 'Adrian', lastName: 'Mejia' }; function getFullName({ firstName, lastName }) { return ${firstName} ${lastName};} console.log(getFullName(user));// Adrian Mejia
深拷貝
ES5 function settings() { return { display: { color: 'red' }, keyboard: { layout: 'querty'} };} var tmp = settings(); var displayColor = tmp.display.color;var keyboardLayout = tmp.keyboard.layout; console.log(displayColor, keyboardLayout);// red querty
與es6(但更簡潔)相同:
ES6 function settings() { return { display: { color: 'red' }, keyboard: { layout: 'querty'} }; } const { display: { color: displayColor }, keyboard: { layout: keyboardLayout } } = settings(); console.log(displayColor, keyboardLayout);// red querty
這也稱爲對象解構。
如您所見,這很是有用,並鼓勵良好的編碼風格。
最佳作法:
使用ECMAScript 6,咱們從「構造函數」到「類。
在JavaScript中,每一個對象都有一個原型,這是另外一個對象。全部JavaScript對象都從其原型繼承其方法和屬性。
在ES5中,咱們使用構造函數來建立面向對象編程(OOP),以建立對象,以下所示:
ES5 var Animal = (function () { function MyConstructor(name) { this.name = name; } MyConstructor.prototype.speak = function speak() { console.log(this.name + ' makes a noise.'); }; return MyConstructor;})(); var animal = new Animal('animal'); animal.speak();// animal makes a noise.
在ES6中,咱們有一些語法糖。咱們能夠用更少的樣板和新的關鍵字來作一樣的事情,好比class和constructor。另外,請注意咱們如何定義方法
constructor.prototype.speak = function()vsspeed():
ES6 class Animal { constructor(name) { this.name = name;} speak() { console.log(this.name + ' makes a noise.'); } } const animal = new Animal('animal'); animal.speak();// animal makes a noise.
正如咱們所看到的,兩種風格(ES5 / 6)在幕後產生相同的結果,並以相同的方式使用。
最佳作法:
創建在以前的Animal類上。假設咱們想要擴展它並定義一個「Lion」類
在ES5中,它更多地涉及原型繼承。
ES5 var Lion = (function () { function MyConstructor(name){ Animal.call(this, name); } // prototypal inheritance MyConstructor.prototype = Object.create(Animal.prototype); MyConstructor.prototype.constructor = Animal; MyConstructor.prototype.speak = function speak() { Animal.prototype.speak.call(this); console.log(this.name + ' roars 🦁'); }; return MyConstructor;})(); var lion = new Lion('Simba');lion.speak();// Simba makes a noise. // Simba roars.
我不會詳細介紹全部細節,但請注意:
在ES6中,咱們有一個新的關鍵字extends和super![superman shield](undefined)。
class Lion extends Animal { speak() { super.speak(); console.log(this.name + ' roars 🦁'); }} const lion = new Lion('Simba'); lion.speak();// Simba makes a noise. // Simba roars.
看起來這個ES6代碼與ES5相比看起來有多清晰,它們徹底相同。
最佳作法:
使用內置的方式繼承extends。
咱們從回調地獄👹逃出來了。
ES5 function printAfterTimeout(string, timeout, done){ setTimeout(function(){ done(string); }, timeout);} printAfterTimeout('Hello ', 2e3, function(result){ console.log(result);// nested callback printAfterTimeout(result + 'Reader', 2e3, function(result){ console.log(result); }); });
咱們有一個函數接收回調,當done時執行。咱們必須一個接一個地執行它。這就是咱們在回調中第二次調用'printAfterTimeout`的緣由。
若是您須要第3次或第4次回調,這可能會很快變得混亂。讓咱們看看咱們如何經過Promises來作到這一點:
ES6 function printAfterTimeout(string, timeout){ return new Promise((resolve, reject) => { setTimeout(function(){ resolve(string); }, timeout); });} printAfterTimeout('Hello ', 2e3).then((result) => { console.log(result); return printAfterTimeout(result + 'Reader', 2e3); }).then((result) => { console.log(result); });
正如你所看到的,使用promises,咱們可使用then在另外一個函數完成後執行某些操做。 再也不須要保持嵌套功能。
ES6沒有刪除函數表達式,但它添加了一個名爲箭頭函數的新表達式。
在ES5中,咱們對this有一些疑問:
ES5 var _this = this;// need to hold a reference $('.btn').click(function(event){ _this.sendData();// reference outer this }); $('.input').on('change',function(event){ this.sendData();// reference outer this }.bind(this));// bind to outer this
你須要使用一個臨時的this來引用一個函數或使用bind。在ES6中,您可使用箭頭函數來實現這個功能!
ES6 // this will reference the outer one $('.btn').click((event) => this.sendData()); // implicit returns const ids = [291, 288, 984]; const messages = ids.map(value => `ID is ${value}`);
咱們從for轉到forEach而後轉到for ... of:
ES5 // for var array = ['a', 'b', 'c', 'd']; for (var i = 0;i < array.length;i++) { var element = array[i]; console.log(element); } // forEach array.forEach( function (element) { console.log(element); });
ES6 for ...也容許咱們進行迭代。
ES6 // for ...of const array = ['a', 'b', 'c', 'd']; for (const element of array) { console.log(element); // a,b,c,d }
咱們檢查是否認義了變量以將值賦給「默認參數」。你以前作過這樣的事嗎?
ES5 function point(x, y, isFlag){ x = x || 0; y = y || -1; isFlag = isFlag || true; console.log(x,y, isFlag); } point(0, 0) // 0 -1 true 😱 point(0, 0, false) // 0 -1 true 😱😱 point(1) // 1 -1 true point() // 0 -1 true
檢查的常見模式是變量具備值或指定默認值。然而,請注意有一些問題:
第8行,咱們傳遞0,0並獲得'0,-1`
第9行,咱們傳遞false但獲得'true`。
若是您將布爾值做爲默認參數或將值設置爲零,則它不起做用。你知道爲何嗎???我會在ES6例子後告訴你;)
使用ES6,如今您能夠用更少的代碼作得更好!
ES6 function point(x = 0, y = -1, isFlag = true){ console.log(x,y, isFlag); } point(0, 0) // 0 0 true point(0, 0, false) // 0 0 false point(1) // 1 -1 true point() // 0 -1 true
注意第5行和第6行咱們獲得了預期的結果。ES5示例不起做用。咱們必須首先檢查undefined,由於false,null,undefined和0是假值。咱們能夠逃脫數字:
ES5 function point(x, y, isFlag){ x = x || 0; y = typeof(y) === 'undefined' ? -1 : y; isFlag = typeof(isFlag) === 'undefined' ? true : isFlag; console.log(x,y, isFlag); } point(0, 0) // 0 0 true point(0, 0, false) // 0 0 false point(1) // 1 -1 true point() // 0 -1 true
如今,當咱們檢查undefined時,它按預期工做。
在ES5上,獲取任意數量的參數是成熟的:
ES5 function printf(format) { var params = [].slice.call(arguments, 1); console.log('params: ', params); console.log('format: ', format); }
咱們可使用rest運算符...來作一樣的事情。
ES6 function printf(format, ...params) { console.log('params: ', params); console.log('format: ', format); }
咱們從apply()轉到了擴展運算符。
提醒:咱們使用apply()將數組轉換爲參數列表。例如,Math.max()接受一個參數列表,可是若是咱們有一個數組,咱們可使用apply來使它工做。
正如咱們在前面所看到的,咱們可使用apply來傳遞數組做爲參數列表:
ES5 Math.max.apply(Math, [2,100,1,6,43]) // 100
在ES6中,您可使用擴展運算符:
ES6 Math.max(...[2,100,1,6,43]) // 100
另外,咱們從concat數組開始使用spread(展開)運算符:
ES5 var array1 = [2,100,1,6,43]; var array2 = ['a', 'b', 'c', 'd']; var array3 = [false, true, null, undefined]; console.log(array1.concat(array2, array3));
在ES6中,您可使用spread運算符展平嵌套數組:
ES6 const array1 = [2,100,1,6,43]; const array2 = ['a', 'b', 'c', 'd']; const array3 = [false, true, null, undefined]; console.log([...array1, ...array2, ...array3]);
JavaScript經歷了不少變化。本文介紹了每一個JavaScript開發人員應該瞭解的大多數核心功能。此外,咱們還介紹了一些最佳實踐,以使您的代碼更簡潔,更容易推理。