JavaScript ES6功能概述(ECMAScript 6和ES2015 +)
JavaScript在過去幾年中發生了很大的變化。這些是您今天可以開始使用的12項新功能!
該語言的新增內容稱為ECMAScript 6.它也稱為ES6或ES2015 +。
自1995年JavaScript構思以來,它一直在緩慢發展。每隔幾年就會發生新增事件。 ECMAScript於1997年成立,旨在指導JavaScript的發展方向。它已經發布了ES3,ES5,ES6等版本。
如您所見,ES3,ES5和ES6之間存在10年和6年的差距。此後每年進行小幅增量變更。而不是像ES6那樣一次做大規模的改變。
所有現代瀏覽器和環境都支援ES6!
Chrome,MS Edge,Firefox,Safari,Node等等都支援
讓我們開始使用ECMAScript 6!
您可以在瀏覽器控制檯上測試所有這些程式碼段!
塊級作用域
使用ES6,我們從使用var宣告變數到使用let / const。
var出了什麼問題?
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不會從塊中提升。
hoisting和 “temporal dead zone”
在ES6中,let將變數提升到塊的頂部(不是像ES5那樣位於函式的頂部)。
但是,在變數宣告之前引用塊中的變數會導致“ReferenceError(系統報錯)”。
let被限制為塊級作用域。在宣告之前不能使用它。
“Temporal dead zone” 是從塊開始到宣告變數的區域。
讓我們在解釋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。
用'let和const代替var`。
對所有引用使用const;避免使用var。
如果必須重新分配引用,請使用let而不是const。
模板文字(字串拼接)當我們有模板文字時,我們不需要做更多的巢狀連線。看一看:
ES5
var first = 'Adrian';
var last = 'Mejia';
console.log('Your name is ' + first + ' ' + last + '.');```
現在你可以使用backtick()和字串插值$ {}:
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的寫法:
ES6let a = 1;let b = 2;[a, b] = [b, a];console.log(a, b);// 2 1
### 多次返回值的解構
ES5function 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,我們從“建構函式”went到“類”went。
在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)在幕後產生相同的結果,並以相同的方式使用。
最佳做法:
- 始終使用class語法並避免直接操作prototype。為什麼?因為它使程式碼更簡潔,更容易理解。
- 避免使用空建構函式。如果未指定,則類具有預設建構函式。
繼承
建立在之前的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.
我不會詳細介紹所有細節,但請注意:
- 第3行,我們用引數顯式呼叫Animal建構函式。
- 第7-8行,我們將Lion原型分配給Animal的原型。
- 第11行,我們從父類Animal中呼叫speak方法。
在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。
Promises
我們從回撥地獄?逃出來了。
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…of
我們從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);
}
預設引數
我們檢查是否定義了變數以將值賦給“預設引數”。你之前做過這樣的事嗎?
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時,它按預期工作。
Rest parameters
我們從引數到休息引數和傳播運算子。
在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開發人員應該瞭解的大多數核心功能。此外,我們還介紹了一些最佳實踐,以使您的程式碼更簡潔,更容易推理。