[翻譯] 讓你的程式碼更簡短,更整潔,更易讀的ES6小技巧

Yukiiiiiiiiiii發表於2018-02-09

讓你的程式碼更簡短,更整潔,更易讀的ES6小技巧

寫在文章前面

這篇文章翻譯自ES6 tips and tricks to make your code cleaner, shorter, and easier to read!. 文章就程式碼整潔方面對es6進行了總結。如有錯誤歡迎指出。

template literals 模板字串

模板字串使字串的使用變得比以前更簡單了,他們以反引號開始(`),並且能過使用${變數}來插入變數。我們來比較一下下面兩行程式碼。

var fName = 'Peter', sName = 'Smith', age = 43, job= 'photographer';
var a = 'Hi, I\'m ' + fName + ' ' + sName + ', I\'m ' + age + ' and work as a ' + job + '.';
var b = `Hi, I'm ${ fName } ${ sName }, I'm ${ age } and work as a ${ job }.`;
複製程式碼

一切都變得很美好了是不是,程式碼更易讀了是不是?你可以在大括號內放入任何東西:變數,等式,或者函式的呼叫。 我將會在後面的整個文章的示例中使用這些方式。

塊級作用域語法

JavaScript是使用函式作用域的,這就是為什麼我們是為什麼我們越來越頻繁的使用匿名的立即執行函式表示式(iife)來實現整個JavaScript檔案的封裝。我們這麼做是為了把所有的變數隔離在檔案內從而避免變數衝突。

現在我們有了塊級作用域和兩個嶄新的塊級作用域的變數宣告

let declaration let命令

這個命令和var很相似但卻又有著顯著的不同。因為他是有塊級作用域的,宣告一個相同名字的新變數可以完全不影響外部的變數。

var a = 'car' ;
{
    let a = 5;
    console.log(a) // 5
}
console.log(a) // car
複製程式碼

因為他是被限制在塊級作用域的,他解決了那道非常經典的面試題:“下面這個程式碼的輸出是什麼,如何修改讓他執行之後成為你想的那個樣子?”

for (var i = 1; i < 5; i++){
    setTimeout(() => { console.log(i); }, 1000);
}
複製程式碼

這個例子中,輸出是“5 5 5 5 5”因為變數i在每次迭代中都會改變。

如果我們把var變為let,一切都變了。 現在,每次迴圈都會建立一個全新的塊級作用域吧i限制在當前的迴圈,他可以理解為這樣:

{let i = 1; setTimeout(() => { console.log(i) }, 1000)} 
{let i = 2; setTimeout(() => { console.log(i) }, 1000)} 
{let i = 3; setTimeout(() => { console.log(i) }, 1000)} 
{let i = 4; setTimeout(() => { console.log(i) }, 1000)} 
{let i = 5; setTimeout(() => { console.log(i) }, 1000)} 
複製程式碼

varlet的另外一個區別是 let 不會像 var一樣被變數提升

{ 
    console.log(a); // undefined
    console.log(b); // ReferenceError
    var a = 'car';
    let b = 5;
}
複製程式碼

因為他有更為侷限的作用域,以及更能被預測的行為,因此一些人甚至認為你應該使用let來代替var, 除非當你真的特別需要變數提升或者更寬鬆的作用域範圍,你再使用var

Const

在以前,如果你想在JavaScript中宣告一個常量, 習慣性的做法是使用全大寫來命名。然鵝,這不是真的去保護了這個變數不能被更改---只是讓其他的開發者知道,這是一個常量,它不應該被更改。

現在我們有了const命令.

const沒有讓變數完全不可變,只是鎖定他的賦值,當你有一個複雜的變數(陣列或者物件)的時候,值還是可以被修改的。

{
    const d = [1, 2, 3, 4];
    const dave = { name: 'David Jones', age: 32};
    d.push(5); 
    dave.job = "salesman";
    console.log(d);  // [1, 2, 3, 4, 5]
    console.log(dave);  // { age: 32, job: "salesman", name: 'David Jones'}
}
複製程式碼

Problem with block scoping functions函式塊級作用域化帶來的問題

函式的宣告也可以限制在塊級作用域中。

{
    bar(); // works
    function bar() { /* do something */ }
}
bar();  // doesn't work
複製程式碼

但是當你在一個if語句中宣告一個函式的時候問題來了。

想一下這種情況:

if ( something) {
    function baz() { console.log('I passed') }
} else {
    function baz() { console.log('I didn\'t pass') } 
} 
baz();
複製程式碼

在ES6之前,這兩個函式宣告都被變數提升,而且結果一定是I didn't pass 不論條件中的something是什麼。但現在我們會得到輸出ReferenceError, 因為 baz一直被限定在塊級作用域內。

Spread 擴充套件運算子

ES6介紹了...操作符,這個操作符指的就是‘擴充套件運算子‘。他的主要用途有兩個:1. 將一個陣列或者物件放到一個新的陣列或者物件中 2. 將陣列中的多個引數合併在一起

第一個用途可能是你將會使用的最多的。所以我們先來看他。

let a = [3, 4, 5];
let b = [1, 2, ...a, 6];
console.log(b);  // [1, 2, 3, 4, 5, 6]
複製程式碼

如果我們想把一個陣列內的一組引數傳遞給函式,這個時候擴充套件運算子就十分的有用了。

function foo(a, b, c) { 
console.log(`a=${a}, b=${b}, c=${c}`)
} 
let data = [5, 15, 2];
foo( ...data); // a=5, b=15, c=2
複製程式碼

一個物件也可以擴充套件的,它會把每個鍵值對寫入新的物件中。( 物件擴充套件已經在提議的第四階段,而且將會在es2018中正式出現 。但這種特性目前只被chrome60及以後的版本,Firefox55及以後,node 6.4.0及以後的版本所支援)【譯者注:在2ality部落格中的es2018一文中得知,在剛剛結束的TC39會議中,ECMA2018的特性被敲定了。】

let car = { type: 'vehicle ', wheels: 4};
let fordGt = { make: 'Ford', ...car, model: 'GT'};
console.log(fordGt); // {make: 'Ford', model: 'GT', type: 'vehicle', wheels: 4}
複製程式碼

擴充套件運算子的另一個特點是,他可以生成一個新的陣列或者物件. 下面的這個例子,就是b就是新建的陣列,但c只是引用同一個陣列。

let a = [1, 2, 3];
let b = [ ...a ];
let c = a;
b.push(4);
console.log(a);  // [1, 2, 3]
console.log(b);  // [1, 2, 3, 4] 不同的陣列
c.push(5);
console.log(a);  // [1, 2, 3, 5] 
console.log(c);  // [1, 2, 3, 5] 同一個陣列
複製程式碼

第二個用法是把變數聚集到一個陣列裡面。當你不知道一個函式到底有多少的傳參的時候會這個方法會變得非常的有用。

function foo(...args) {
    console.log(args); 
} 
foo( 'car', 54, 'tree');  //  [ 'car', 54, 'tree' ] 
複製程式碼

Default Parameter 引數預設值

函式現在可以使用預設的引數值來定義了。不傳參或者未定義值都被初始化為預設值。但是需要注意的是,null和false都會被強轉為0.

function foo( a = 5, b = 10) {
    console.log( a + b);
} 
foo();  // 15
foo( 7, 12 );  // 19
foo( undefined, 8 ); // 13
foo( 8 ); // 18
foo( null ); // 10 as null is coerced to 0
複製程式碼

預設值的型別可以不僅僅是值型別---還可以是表示式或者函式。

function foo( a ) { return a * 4; }
function bar( x = 2, y = x + 4, z = foo(x)) {
    console.log([ x, y, z ]);
}
bar();  // [ 2, 6, 8 ]
bar( 1, 2, 3 ); //[ 1, 2, 3 ] 
bar( 10, undefined, 3 );  // [ 10, 14, 3 ]
複製程式碼

Destructuring解構

解構是拆開等號左邊的陣列或者物件的過程。這個陣列或者物件可以來自一個變數,一個函式,或者一個等式

let [ a, b, c ] = [ 6, 2, 9];
console.log(`a=${a}, b=${b}, c=${c}`); //a=6, b=2, c=9

function foo() { return ['car', 'dog', 6 ]; } 
let [ x, y, z ] = foo();
console.log(`x=${x}, y=${y}, z=${z}`);  // x=car, y=dog, z=6
複製程式碼

物件型別的結構,可以在花括號內列出物件的鍵來提取鍵值對。

function bar() { return {a: 1, b: 2, c: 3}; }
let { a, c } = bar();
console.log(a); // 1
console.log(c); // 3
console.log(b); // undefined
複製程式碼

有時,你可能想提取出值然後費賠給新的變數,這個可以通過在等號左側使用一個“key:variable”(鍵:變數名)來完成。

function baz() { 
    return {
        x: 'car',
        y: 'London',
        z: { name: 'John', age: 21}
    }; 
}
let { x: vehicle, y: city, z: { name: driver } } = baz();
console.log(
    `I'm going to ${city} with ${driver} in their ${vehicle}.`
); // I'm going to London with John in their car. 
複製程式碼

此外,物件的結構允許給多個變數賦值。

let { x: first, x: second } = { x: 4 };
console.log( first, second ); // 4, 4
複製程式碼

物件字面量和屬性的簡潔表達法

當你從許多引數建立物件字面量的時候,ES6允許你在鍵與變數名字相同的情況下省略該鍵。

let a = 4, b = 7;
let c = { a: a, b: b };
let concise = { a, b };
console.log(c, concise) // {a: 4, b: 7}, {a: 4, b: 7}
複製程式碼

這個還可以與解構一起用來使你的程式碼更乾淨整潔。

function foo() {
    return {
        name: 'Anna', 
        age: 56,
       job: { company: 'Tesco', title: 'Manager' }
    };
} 
// pre ES6
let a = foo(), name = a.name, age = a.age, company = a.job.company;
// ES6 destructuring and concise parameters 
let { name, age, job: {company}} = foo();

複製程式碼

簡潔表示法還可以用於解構物件並把它傳入函式。方法1和2是你在es6之前要怎麼做, 方法三是使用解構和簡潔表達法。

let person = {
    name: 'Anna', 
    age: 56,
    job: { company: 'Tesco', title: 'Manager' }
};
// method 1
function old1( person) {
    var yearOfBirth = 2018 - person.age;
    console.log( `${ person.name } works at ${ person.job.company } and was born in ${ yearOfBirth }.`);
}
// method 2
function old1( person) {
    var age = person.age,
        yearOfBirth = 2018 - age, 
        name = person.name,
        company = person.job.company;
    console.log( `${ name } works at ${ company } and was born in ${ yearOfBirth }.`);
} 
// method 3
function es6({ age, name, job: {company}}) {
    var yearOfBirth = 2018 - age;
    console.log( `${ name } works at ${ company } and was born in ${ yearOfBirth }.`);
} 

複製程式碼

通過使用ES6,我們能提取出agename,和 company,而不需要任何其他的變數宣告。

動態屬性名稱

ES6新增了使用動態分配的鍵建立或新增屬性的功能。

let  city= 'sheffield_';
let a = {
    [ city + 'population' ]: 350000
};
a[ city + 'county' ] = 'South Yorkshire';
console.log(a); // {sheffield_population: 350000, sheffield_county: 'South Yorkshire' }

複製程式碼

箭頭函式

箭頭函式有兩個比較重要的特點: 他們的結構以及他們的this 指向

他們比傳統的函式有更簡單的結構因為他們不需要關鍵字function 而且他們可以自動返回在箭頭後面的一部分,無論箭頭後面的是什麼。

var foo = function( a, b ) {
    return a * b;
} 
let bar = ( a, b ) => a * b;
複製程式碼

如果函式有多於一個的計算式,可以使用花括號來包起來,然後函式返回塊作用域返回的任何內容。

箭頭函式一個最重要的用途之一就是應用在陣列的相關函式中,像.map.forEach,.sort等等。

let arr = [ 5, 6, 7, 8, 'a' ];
let b = arr.map( item => item + 3 );
console.log(b); // [ 8, 9, 10, 11, 'a3' ]
複製程式碼

在擁有一個更短的表達方式的同時,箭頭函式還修復了有關於this繫結行為經常出現的問題。ES6之前解決這個問題通常是使用一個self變數來儲存這個指向。

var clickController = {
    doSomething: function (..) {
        var self = this;
        btn.addEventListener(
            'click', 
            function() { self.doSomething(..) }, 
            False
       );
   } 
};
複製程式碼

這個this的賦值是一定要做的,因為this的繫結是動態的。這就意味著this在eventlistener內部和在doSomething內部指的並不是同一個東西。

在箭頭函式內部,this的繫結是語義上的就是指當前的,而不是動態的。這也是箭頭函式的主要設計特點。

雖然這種詞法上的this很棒,但是有些時候,他卻不是我們想要的那樣。

let a = {
    oneThing: ( a ) => {
         let b = a * 2;
         this.otherThing(b);
    }, 
    otherThing: ( b ) => {....} 
};
a.oneThing(6);
複製程式碼

當我們使用a.oneThing(6), 這個this.otherThing(6) 會丟擲引用失敗的錯誤,因為this沒有指向物件a,而是指向了環境作用域。如果你正在使用ES6的程式碼使用ES6的語法,這個是你需要注意的事情。

for...of loops (for...of迴圈)

ES6新新增了一種方式來迭代陣列中的每個值,這個方式是與已經存在的for...in的通過索引的迴圈方式不同。

let a = ['a', 'b', 'c', 'd' ];
// ES6 
for ( var val of a ) {
    console.log( val );
} // "a" "b" "c" "d"
// pre-ES6 
for ( var idx in a ) {
    console.log( idx );
}  // 0 1 2 3
複製程式碼

使用新的for ... of迴圈,在每個迴圈內部儲存了一個let val = a[idx]

陣列,字串,generator以及從collection 在標準JavaScript中都是可迭代的。普通的物件無法正常的使用for...of來迭代,除非你自己定義一個迭代器。

Number Literals 數字字面量

ES5程式碼很好處理了十進位制和十六進位制的數字格式,但並未指定八進位制的格式。實際上,八進位制在嚴格模式中是被禁止使用的。

ES6 新增了一個全新的格式,在最開始的0後面新增一個o來宣告一個八進位制的數。與此同時,在es6中還新增了二進位制格式。

Number( 29 )  // 29
Number( 035 ) // 35 in old octal form. 
Number( 0o35 ) // 29 in new octal form 
Number( 0x1d ) // 29 in hexadecimal 
Number( 0b11101 ) // 29 in binary form
複製程式碼

更多

ES6還提供了我們很多很多其他的方式來使我們的程式碼更簡潔,更易讀,以及更穩定。我的目標時寫一篇這篇文章的延續,來包括一些ES6中不太知名的部分。

如果你已經等不及了,可以讀一讀Kyle Simpson的YOU DONT KNOW JAVASCRIPT ON ES6,或者看一下這個超讚的小網站

相關文章