深入淺出ES6知識大合集

shenyWill發表於2017-12-14

1.ES6的宣告方式

ES6一共有三種宣告方式:

  1. var:是variable的縮寫,全域性變數;
  2. let:區域性變數;
  3. const:常量

var宣告
在ES6中,var被定義為全域性變數,我們做個測試:在區塊中定義一個var的變數a,然後在區塊外看看能否列印出來。

{
    var a = 1;
}
console.log(a);
複製程式碼

你會發現,這個時候,a是可以列印出來的,這就說明var定義的是全域性變數。

let宣告
let是ES6新引入的語法,是一個區域性變數,我們也做一個測試:在區塊中定義一個let變數a,看看在區塊外能否列印出來。

{
    let a = 1;
}
console.log(a);
複製程式碼

你會發現,這個時候,瀏覽器會報 a is not defined的錯誤,說明了let確實定義的是一個區域性變數,只能函式內或者區塊內訪問。

const宣告

我們在寫程式碼的時候,可能有時候需要定義一個變數,這個變數在被定義以後,業務層就不會在對這個變數進行修改,簡單的說,我們需要定義一個只宣告一次的常量。ES6很好的為我們做到了這一點,這就是const。

注意一點,const定義的常量最好使用大寫字母,如果是兩個字母,用_分開,比如const PRODUCT_ID。我們同樣做一個測試,看看const定義的常量是否可以被更改。

const FORM_ID = 1;
FORM_ID =2;
複製程式碼

這個時候你會發現,在編譯的過程中就出錯了,會提示你"a" is read-only,意思很簡單,當你定義了const的常量以後,就不允許在去修改了。

2.變數的解構賦值

這是ES6的一個特別實用的功能,ES6允許按照一定模式,從陣列和物件中提取值,對變數進行賦值,這被稱為解構。解構賦值在實際開發中可以大量減少我們的程式碼量,並且讓我們的程式結構更清晰。

陣列的解構賦值

以前,對變數賦值只能這樣寫:

var a = 1;
var b = 2;
var c = 3;
複製程式碼

那麼現在,通過陣列解構賦值的方式我們可以這樣簡化了:

let [a,b,c] = [1,2,3];
複製程式碼

學過函式對映的同學應該都能看懂,就是按照對應的位置關係進行賦值。注意,左邊和右邊一定要統一,不然會解構失敗。

let [[a,b],c,[d,e]] = [[1,2],3,[4,5]];
複製程式碼

解構賦值可不止這麼點功能,它還是可以進行預設賦值的,那麼我們繼續往下看:

解構的預設賦值

在看例子之前,我們來解釋一下解構的預設賦值:當對變數解構賦值的時候,如果沒有賦值,或者賦值為undefined,那麼變數就會讀取預設賦值。

let [a=1] = [];
複製程式碼

這個時候,列印出來的a就是1.

let [a=1,b] = [undefined,2];
複製程式碼

這個時候列印出來的a的值同樣是1.

注意:

let [a=1,b] = [null,2];
複製程式碼

此時列印出來的a的值是null,而不是1,那麼這是為什麼呢?原因很簡單:ES6內部是使用嚴格相等運算子(===)判斷一個位置是否有值的。所以,如果一個陣列成員不嚴格等於undefined,預設值是不會生效的。

物件的解構賦值

物件的解構賦值與陣列有一個不同,陣列的元素是按次序排列的,變數的取值由它的位置決定;而物件的屬性沒有次序,變數必須與屬性同名,才能取到正確的值。下面看個例子:

let {a,b,c} = {
    c:"3",
    a:"1",
    b:"2"
}
複製程式碼

這個時候,輸出的a,b,c的值分別是1,2,3

其實,上面的寫法是物件解構賦值的簡寫,真正的寫法應該是:

let {
    a:a,
    b:b,
    c:c
} = {
    c:"3",
    a:"1",
    b:"2"
}
複製程式碼

其實物件的解構賦值的內部機制,是先找到同名屬性,然後再賦給對應的變數。真正被賦值的是後者,而不是前者。如果覺得不太懂,可以看下面的列子:

let {
    yuan:will
} = {
    yuan:1
}
複製程式碼

這個時候,當你console.log(yuan)的時候,會報錯,說 yuan is not defined,而console.log(will),會發現will的值是1,現在明白了麼,是不是很簡單。

接下來,我們來看看二次賦值的一個坑,上程式碼:

let a;
{a} = {a:1};
複製程式碼

這樣在編譯的時候就報錯了,原因就是解析器會將起首的大括號,理解成一個程式碼塊,而不是賦值語句,解決方法很簡單,用()包起來就可以了。

let a;
({a} = {a:1});
複製程式碼

當然啦,物件解構賦值也是可以設定預設值的,下面列一個很簡單的預設賦值的demo:

let {a=1,b,c} ={
    a:undefined,
    c:3
}
複製程式碼

這個時候,列印出來的a,b,c分別是1,undefined,3,所以看出來了麼,當你定義了變數,但是並沒有賦值的時候,是不報錯的,而是賦值為undefined(b)

字串的解構賦值

這個用的不多,所以我這個小菜雞也沒怎麼去特別研究,寫個例子吧:

const [A,B,C,D] = 'will';
複製程式碼

這個時候列印出來的A,B,C,D的值分別為w,i,l,l

用途

解構賦值除了對物件賦值以外,還是有蠻多其他用途的,下面列一些大神總結的吧。

  • 交換變數的值

    [x,y] = [y,x];
    複製程式碼

  • 從函式返回多個值

    函式只能返回一個值,如果要返回多個值,只能將它們放在陣列或物件裡返回。有了解構賦值,取出這些值就非常方便。

    // 返回一個陣列
    function example() { 
      return [1, 2, 3];
    }
    var [a,b,c] = example();
    
    // 返回一個物件
    function example() { 
      return { a: 1, b: 2 };
    }
    var {a,b} = example();
    複製程式碼

    這個函式方式用的還是老的,後面的我都會用es6的語法,如果大家看不懂沒關係,可以直接跳轉先看箭頭函式那一塊知識,再回頭看前面內容。


  • 函式引數的定義

    解構賦值可以方便地將一組引數與變數名對應起來。

    //引數是一組有序的值
    let example =([a,b,c]) =>{
        ...
    }
    example([1,2,3]);
    
    //引數是一組無序的值
    let example =({a,c,b}) =>{
        ...
    }
    example({a:1,b:2,c:3});
    複製程式碼

  • 提取JSON資料

    let jsonData = {
        a:1,
        b:"2",
        c:[1,2]
    }
    let {a,b,c} = jsonData;
    複製程式碼

還有一些用途我就不一一列舉了,感興趣的同學可以上網找。

3.擴充套件運算子和reset運算子

擴充套件運算子

現在有個問題問大家,現在要編寫一個方法,它的引數是不確定的,如果用es5的來寫,是不是很複雜,但是es6輕鬆就能解決這個問題,那就是擴充套件運算子!

let example =(...arg) {
    console.log(arg[0]);
    console.log(arg[1]);
    console.log(arg[2]);
    console.log(arg[3]);
}
example(1,2,3);
複製程式碼

這個時候列印出來的會是1,2,3,undefined。這樣就太好了,即使傳值少了,也不會報錯,只是列印undefined.

那麼擴充套件運算子又有什麼用處呢,下面我們來看看:

1.陣列的深度拷貝

首先來看一個es5的demo:

let arrOne = [1,2,3];
let arrTwo = arrOne;
arrTwo.push(4);
console.log(arrOne);
複製程式碼

這個時候你會發現,列印出來的arrOne是[1,2,3,4],這不是我們想要的,所以呢,我們就可以用擴充套件運算子解決,往下看:

let arrOne = [1,2,3];
let arrTwo = [...arrOne];
arrTwo.push(4);
console.log(arrOne);
複製程式碼

那麼現在列印出來的arrOne就是[1,2,3],完美解決!

2.字串轉換陣列

let str = 'will';
let arr = [...str];
console.log(arr);
複製程式碼

那麼列印出來的陣列就是['w','i','l','l'];

reset運算子

reset運算子也是通過...來表示,它和擴充套件運算子非常相似,你不需要去特別的區分。

let example =(a,...arg) => {
    for(var item of arg) {
        console.log(item);
    }
}
example(1,2,3,4,5,6);
複製程式碼

這個時候列印臺會列印出2,3,4,5,6,for..of迴圈後面會有介紹,暫時大家可以理解成一個迴圈。

4.字串模板和標籤模板

字串模板

首先,我們先看一下以前我們是怎麼實現字串拼接的:

let will = 'yuan';
let str = '歡迎您,' + yuan + ',我們的好朋友';
複製程式碼

很顯然,這樣的方式既麻煩又容易出錯,特別是在進行復雜的字串拼接的時候,ES6的字串模板就很好的解決了這個問題,我們現在來看看ES6的字串模板是怎麼做的:

let will = 'yuan';
let str = `歡迎您,${will},我們的好朋友`;
複製程式碼

除了支援變數插值,字串模板還是支援簡單的計算的:

let str =`1+2的值是:${1+2};`
複製程式碼

這樣列印出來的就是:1+2的值是:3

其實這只是很簡單的兩個案例,對於${}中的資料結構,不僅僅可以是變數,還可以放入任意的JavaScript表示式,可以進行運算,以及引入物件屬性。我們看下面一個例子。

let example =(arr) => `
    <ul>
    ${
        arr.map((data) =>{
            return`<li>${data}</li>`
        }).join('')
    }
    </ul>
`
let node = example([1,2,3]);

document.write(node);
複製程式碼

這樣列印出來的是什麼呢,會是

深入淺出ES6知識大合集
那麼提個小問題,我為什麼在後面加上join()方法呢,大家可以想想。

標籤模板

標籤模板其實並不是一個模板,和字串模板是完全不一樣的,大家千萬不要按照字串模板的思維去理解。標籤模板是函式的一種特殊的呼叫形式,緊跟在後面的字串模板是這個函式的引數,大家不理解沒關係,我們先看一個例項:

let a = 1;
let b = 2;
example`我是${a+b},你是${b-a*5}`
function example(arr,a,b){
    console.log(arr);
    console.log(a);
    console.log(b);
}
複製程式碼

先解釋一下:這個example函式的第一個引數是一個陣列,陣列則是被變數分隔開來的每個字串,後面的引數則對應每個字串模板當中的變數,那麼列印出來的結果就顯而易見了:

深入淺出ES6知識大合集

需要注意的是,第一個列印出來的arr是三個資料,後面會有一個空字串,理由很簡單,我就不解釋了。

下面,我們來看一個複雜的例子,也是標籤模板比較有用的一個用途,就是防止使用者的惡意輸入。

function SaferHTML(templateData){
    let s = templateData[0];//templateData取的是第一個引數,那麼就是arr陣列,第一個那就是<p>
    let i;
    for(i = 1;i<arguments.length;i++){ //arguments陣列包含的是全部的引數,在這個例子中,也就是[arr陣列,變數];
        var arg = String(arguments[i]);
        s += arg.replace(/&/g,'&amp;')
                .replace(/</g,'&lt;')
                .replace(/>/g,'&gt;');
        s += templateData[i];
    }
    return s;
}
let sender = '<script>alert("abc")</script>';
let message = SaferHTML`<p>${sender} has sent you a message.</p>`;
console.log(message);
複製程式碼

這個例子上面的標註都已經說明了,大家能看得懂麼?看不懂仔細鑽研哦

字串查詢

在ES6之前,我們都是使用indexOf()來查詢字串是否存在,indexOf()的強大並不純粹只是字串的查詢,所以我們缺少了純粹的字串查詢的方法,很幸運,ES6一共提供了三種方式:

  1. includes():該方法在給定文字存在於字串中的任意位置時會返回 true ,否則返回false;
  2. startsWith():該方法在給定文字出現在字串起始處時返回 true ,否則返回 false;
  3. endsWith():該方法在給定文字出現在字串結尾處時返回 true ,否則返回 false;

這三個方法都提供了兩個引數:

第一個引數:需要搜尋的文字;

第二個引數:includes() 與 startsWith() 方法會從該索引位置(引數)開始嘗試匹配;而endsWith() 方法則從字串長度減去這個索引值的位置開始嘗試匹配;

下面,我們看看例子:

var a = "Hello world!";
console.log(a.includes("o")); // true
console.log(a.startsWith("Hello")); // true
console.log(a.endsWith("!")); // true

console.log(a.includes("o", 8)); // false
console.log(a.startsWith("o", 4)); // true
console.log(a.endsWith("o", 5)); // true
複製程式碼

這個非常簡單,相信大家也都能看得懂,但是有兩點需要注意:

  1. 雖然這三個方法使得判斷子字串是否存在變得更容易,也更加純粹,但它們只返回了一個布林值。若需要找到它們在字串中的確切位置,則需要使用 indexOf() 和 lastIndexOf() ;
  2. 如果向 startsWith() 、 endsWith() 或 includes() 方法傳入了正規表示式而不是字串,會丟擲錯誤。而對於indexOf()和lastIndexOf()這兩個方法,它們會將正規表示式轉換為字串並搜尋它

字串複製

有的時候,一個字串不止使用一次,需要重複使用,那麼就需要用到字串複製了,我們來看:

let a = '*';
console.log(a.repeat(3));
console.log(a);
複製程式碼

注意一點:repeat()方法是不改變變數的,也就是並不會改變變數a的值

5.ES6增加的數字操作

在ES5中,有幾個數字操作的全域性函式,相信大家都比較熟悉,分別是isNaN(),isFinite(),parseInt(),parseFloat()。這些方法在ES5中都是window物件下的方法,在ES6中已經屬於Number物件下了,現在我們分別看一下區別:

isNaN()

首先,在ES5中,有兩種寫法:

isNaN(2) //false
window.isNaN(2) //false
複製程式碼

以上兩種寫法都可以,因為isNaN本身就是window物件下的一個方法,大部分人都會用第一種。現在來對比一下ES5和ES6下的isNaN()的區別:

ES5:會首先把非數值的引數轉換成數值型別,再進行判斷。

ES6:不會強制將引數轉換成數字,只有在引數是真正的數字型別,且值為 NaN 的時候才會返回 true。

這個如果大家不理解沒關係,我們先看例子:

isNaN('a')                //true 因為a無法裝換成數字,所以返回true
Number.isNaN('a')         //false a是一個字串,直接返回false

Number.isNaN(NaN);        // true
Number.isNaN(Number.NaN); // true
Number.isNaN(0 / 0)       // true


// 下面這幾個如果使用全域性的 isNaN() 時,會返回 true。
Number.isNaN("NaN");      // false,字串 "NaN" 不會被隱式轉換成數字 NaN。
Number.isNaN(undefined);  // false
Number.isNaN({});         // false
Number.isNaN("blabla");   // false

// 下面的都返回 false
Number.isNaN(true);
Number.isNaN(null);
Number.isNaN(37);
Number.isNaN("37");
Number.isNaN("37.37");
Number.isNaN("");
Number.isNaN(" ");
複製程式碼

相信通過上面那麼多例子,大家差不多能明白了,那麼為什麼ES6要這樣幹呢?因為在 JavaScript 中,NaN 最特殊的地方就是,我們不能使用相等運算子(== 和 ===)來判斷一個值是否是 NaN,因為 NaN == NaN 和 NaN === NaN 都會返回 false。因此,必須要有一個判斷值是否是 NaN 的方法,那麼Number.isNaN()方法就很幸運的被選中了!

isFinite()

這個方法和isNaN()在ES5和ES6中的區別特別類似:

ES5:會首先把非數值的引數轉換成數值型別,再進行判斷。

ES6:不會強制將一個非數值的引數轉換成數值,這就意味著,只有數值型別的值,且是有窮的(finite),才返回 true。

同樣我們看個例子:

isFinite('0')                    //true,會先轉換成數字0,0是有窮的,所以返回false
Number.isFinite('0')             //false,'0'是字串,直接返回false
Number.isFinite(Infinity);       //false,Infinity是window物件下的一個常量,表示一個無窮數
複製程式碼

parseInt()

這個方法是函式解析一個字串引數,並返回一個指定基數的整數,parseInt函式同樣是從window物件下移植到Number物件下,但是它的作用沒有任何變化。

parseInt()方法一共有兩個引數:

第一個引數:要被解析的值。如果引數不是一個字串,則將其轉換為字串(使用ToString抽象操作)。字串開頭的空白符將會被忽略。

第二個引數:一個介於2和36之間的整數(數學系統的基礎),表示上述字串的基數。比如引數"10"表示使用我們通常使用的十進位制數值系統。始終指定此引數可以消除閱讀該程式碼時的困惑並且保證轉換結果可預測。當未指定基數時,不同的實現會產生不同的結果,通常將值預設為10。

parseInt('12.3abc');         //返回12
Number.parseInt('12.3abc');  //返回12
複製程式碼

parseFloat()

和parseInt()一樣,被移植到Number物件下,作用保持不變。

parseFloat()只有一個引數,就是要被解析的字串。

parseFloat('12.3abc');            //返回12.3
Number.parseFloat('12.3abc');     //返回12.3
複製程式碼

上述那些方法都是es5本身就有,從全域性函式轉到了Number身上,下面,我們看看ES6新出的一些方法和新定義的一些常量,首先,我們來看ES6對Number物件的擴充套件。

Number.isInteger()

這個函式很簡單,用來判斷一個數是否為整數,但是有兩點需要注意:

1.只有傳入的本身就是數值,才會判斷是否是整數,如果傳入的是字串或者陣列等其他型別,是不會強制轉換的,是直接返回false。

2.在javascript內部對整數和浮點數採用一樣的儲存方式,因此小數點後如果都是0的浮點數,都會被認為是整數

Number.isInteger(0)          //true
Number.isInteger(-1)         //true
Number.isInteger(2.3)        //false
Number.isInteger(2.0)        //true
Number.isInteger("10");      // false
Number.isInteger(true);      // false
Number.isInteger(false);     // false
Number.isInteger([1]);       // false
複製程式碼

除了上述兩點以外,還有一點,不是這個方法產生的問題,但是輸出的結果會讓你很奇怪,我們來看案例:

Number.isInteger(Number.MAX_SAFE_INTEGER - 1.2)   //true
Number.isInteger(0.09+0.01)                       //false
複製程式碼

其實輸出這樣的結果,並不是這個方法出現了問題,而是Number.MAX_SAFE_INTEGER - 1.2最後得到的結果是9007199254740990,而0.09+0.01 得到的結果是0.09999999999999999。為什麼會這樣呢?因為計算機是用二進位制來儲存和處理數字,不能精確表示浮點數,而JavaScript中沒有相應的封裝類來處理浮點數運算,直接計算會導致運算精度丟失


Number.MAX_SAFE_INTEGER Number.MIN_SAFE_INTEGER

這兩個由於是一個系列的,所以一起介紹,在介紹這兩個常量之前,得先解釋一下什麼是安全整數和非安全整數。

由於JavaScript能夠精確表示的整數範圍在-2^53到2^53之間(不包含2^53和-2^53),超過這個範圍,JavaScript就無法精確的表示,所以這個範圍內的整數被稱為安全整數,不在這個範圍內的則稱為非安全整數。

對此,ES6對這個範圍的邊界定義了兩個常量,最大值是:Number.MAX_SAFE_INTEGER,最小值是:Number.MIN_SAFE_INTEGER。 也就是:

Number.MAX_SAFE_INTEGER === Math.pow(2,53)-1      //true
Number.MIN_SAFE_INTEGER === -Math.pow(2,53)+1     //true
複製程式碼

Number.isSafeInteger()

Number.isSafeInteger()函式就是用來判斷數值是否是安全整數,只有當傳入的數值是數值並且是安全整數,才會返回false

Number.isSafeInteger(Number.MAX_SAFE_INTEGER);      //true
Number.isSafeInteger(Number.MIN_SAFE_INTEGER);      //true
Number.isSafeInteger(Number.MAX_SAFE_INTEGER + 1);  //false
Number.isSafeInteger(Number.MIN_SAFE_INTEGER - 1);  //false
Number.isSafeInteger('1');                          //false
複製程式碼

Number.EPSILON

這是一個常量,表示極小極小的數,它的值為:2.220446049250313e-16,這個值的出現是為了用來判斷浮點數的計算誤差,如果浮點數計算得到的誤差不超過Number.EPSILON的值,就表示可以接受這樣的誤差。

Number.EPSILON    //2.220446049250313e-16
複製程式碼

ES6不僅僅是對Number物件做了擴充套件,對Math物件也做了擴充套件,下面我們來看看:

Math.trunc()

這個方法首先會把傳入的引數自動轉化成數字型別,刪除掉數字的小數部分和小數點,不管它是正數還是負數。

Math.trunc(13.37)    // 13
Math.trunc(42.84)    // 42
Math.trunc(0.123)    //  0
Math.trunc(-0.123)   // -0
Math.trunc("-1.123") // -1
Math.trunc(NaN)      // NaN
Math.trunc("foo")    // NaN
Math.trunc()         // NaN
複製程式碼

Math.sign()

這個方法首先會把傳入的引數自動轉化成數字型別,然後判斷這個數是整數還是負數還是零,這個方法一共有五個返回值,分別是:1, -1, 0, -0, NaN

Math.sign(3);     //  1
Math.sign(-3);    // -1
Math.sign("-3");  // -1
Math.sign(0);     //  0
Math.sign(-0);    // -0
Math.sign(NaN);   // NaN
Math.sign("foo"); // NaN
Math.sign();      // NaN
複製程式碼

Math.cbrt()

這個方法首先會把傳入的引數自動轉化成數字型別,然後算出這個數的立方根

Math.cbrt(NaN); // NaN
Math.cbrt(-1); // -1
Math.cbrt(-0); // -0
Math.cbrt(-Infinity); // -Infinity
Math.cbrt(0); // 0
Math.cbrt(1); // 1
Math.cbrt(Infinity); // Infinity
Math.cbrt(null); // 0
Math.cbrt(2);  // 1.25992104989487
複製程式碼

ES6除了給Math擴充套件了這三個函式外,還有很多,基本是高中數學課本的一些方法,大家有興趣可以自己去研究,這裡就不一一列舉了。

對於數字運算,ES6還增加了一種運算子,即指數運算子**

3 ** 2  //9
3 ** 3  //27

let a = 1.5;
a **= 2; //2.25
複製程式碼

6.ES6新增的陣列知識

ES5的陣列已經非常強大了,但是ES6依然增加了一些很有用的陣列方法,我們挨個看一下。

Array.from()

Array.from()方法可以通過兩種方式來建立陣列:

1.偽陣列物件(擁有一個length屬性和若干個索引屬性的物件,例如arguments)

2.可迭代物件(可以獲取物件中的元素,例如 Map和 Set )

該方法一共有三個引數:

1.想要轉換成陣列的偽陣列物件或可迭代物件

2.如果指定了該引數,新陣列中的每個元素會執行該回撥函式

3.可選引數,執行回撥函式 (第二個引數) 時 this 物件

其實,Array.from()方法只是後面有一個可選的回撥函式mapFn,讓你可以在最後生成的陣列上再執行一次 map 方法後再返回。也就是說 Array.from(obj, mapFn, thisArg) 就相當於 Array.from(obj).map(mapFn, thisArg)。 我們來看幾個例子:

1.通過偽陣列物件建立新陣列

function example() {
    console.log(Array.from(arguments))
}
example('s','d','e')                //["s", "d", "e"]
複製程式碼

2.通過可迭代物件建立新陣列

let s = new Set(['foo', window]); 
Array.from(s);                      // ["foo", window]

Array.from('1234');                 // ["1", "2", "3", "4"] 
Array.form(1234);                   // []
複製程式碼

3.給陣列傳入第二個引數

function example() {
    console.log(Array.from(arguments,value => value + 2))
}
example(1,2,3)                      // [3, 4, 5]
複製程式碼

Array.of()

Array.of() 方法建立一個具有可變數量引數的新陣列例項,而不考慮引數的數量或型別。

Array.of()和Array的建構函式非常相似,而Array.of()的出現也是為了解決Array的一個困惑(或者說是特性) => 在使用Array的建構函式的時候,當我們new一個數字的時候,生成的是這個數字長度的陣列,每個位置的值都是undefined,而我們new一個字串的時候,又會生成一個該字串為元素的陣列。

const a = new Array('2')   // ['2']
const b = new Array(2)     // [undefined,undefined]
複製程式碼

而對於Array.of(),無論傳入的是字串還是數字,都會生成一個該引數為元素的陣列。

const a = Array.of('2')   // ['2']
const b = Array.of(2)     // [2]
const b = Array.of(2,3,4) // [2,3,4]
複製程式碼

find()例項方法

陣列例項的find方法,用於找出第一個符合條件的陣列成員。它的引數是一個回撥函式,所有陣列成員依次執行該回撥函式。

這個回撥函式有三個引數,依次為當前的值、當前的位置和原陣列。我們來看例項:

注意,我看很多書裡都說這個方法會返回符合條件的第一個值,如果找不到符合條件的則返回undefined,但是我寫了幾個demo發現並不會返回,除非自己去手動return,大家可以一起探討,我們來看寫的案例。

[1,2,3,4,5].find(val => val > 3)     //4
複製程式碼

這個會返回4,原因是因為箭頭函式的特性,如果只寫一行並且沒有使用{},則預設增加return,我們再看下面例子就明白了:

let a = [1,2,3,4,5].find(val => {val > 3})   //undefined
複製程式碼

這個時候我增加了{},並沒有去return,那麼列印出來的就是undefined,說明沒有去自動返回,而是需要主動返回,我猜測find的機制只是將你的陣列迴圈出來,然後回撥函式根據迴圈出來的元素自己去處理需求,當遇到return的時候,陣列迴圈終止,返回這時候迴圈進去的value值(注意:並不會返回你自己return的值),我們來看一個例子:

let a = [1,2,3,4,5].find((val,index) => {
    if(val < 3) {
        console.log('val:' + val)
    }else {
        return {
            'val' : val,
            'index' : index
        }
    }
})

console.log(a) //分別列印val:1  val:2  3
複製程式碼

從這個例子可以看出來,find()方法是在程式終止的時候,返回終止的那一刻的value值。

findIndex()例項方法

findIndex()方法和find()方法幾乎一樣,只是返回值變成了元素在陣列的下標,就不詳細介紹了,看個例子:

let a = [1,2,3,4,5].findIndex((val,index) => {
    if(val < 3) {
        console.log('val:' + val)
    }else {
        return {
            'val' : val,
            'index' : index
        }
    }
})

console.log(a) //分別列印val:1  val:2  2
複製程式碼

fill()例項方法

fill()方法是使用給定值去填充陣列,該方法一共有三個引數,依次是:填充的值,起始填充位置,結束填充位置。

[1,2,3,4].fill('a')        // ["a", "a", "a", "a"]
[1,2,3,4].fill('a',1,3)    // [1, "a", "a", 4]
複製程式碼

includes()

這個方法與字串的includes方法特別相似,只是把對字串的匹配改成了對陣列的匹配,所以就不詳細介紹了,給大家一個例子吧。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true
複製程式碼

7.遍歷

由於遍歷是一個非常重要的知識點,所以單獨抽出來講一講,並不侷限於ES6新增的遍歷方法。

1.for迴圈

這個是最基礎也是ES5最常用的迴圈,這個太簡單太常用了,就不解釋了。

2.for..in 迴圈

for..in迴圈是專門為普通物件定義的迴圈,雖然可以用來迴圈陣列,但是強烈建議一定不要用for..in迴圈去迴圈陣列,一定不要,原因大家可以去百度,我這裡就不一一闡述了。

3.forEach 迴圈

forEach迴圈是ES5中迴圈陣列的方法,該方法有兩個引數:

第一個引數:必需,陣列中每個元素需要呼叫的函式,這個函式有三個引數,依次是:遍歷的陣列內容,陣列的索引,原陣列。

第二個引數:可選,傳遞給函式的值一般用 "this" 值。如果這個引數為空, "undefined" 會傳遞給 "this" 值。

let arr = [1,2,3,4];
arr.forEach((value,index,arr)=>{
    arr[index] = value * 2;
});
console.log(arr); // [2, 4, 6, 8]
複製程式碼

注意:使用forEach遍歷陣列的話,使用break不能中斷迴圈,使用return也不能返回到外層函式。

let arr = [1,2,3,4];
arr.forEach((value,index,arr)=>{
    if(value === 3) {
        return;
    }
    arr[index] = value * 2;
});
console.log(arr); // [2, 4, 3, 8] 繼續執行了value = 4的情況
複製程式碼

4.map迴圈

map迴圈和forEach迴圈非常相似,都是用來遍歷陣列中的每一項值的,不同之處在於:

map的回撥函式中支援return返回值;return的是啥,相當於把陣列中的這一項變為啥(並不影響原來的陣列,只是相當於把原陣列克隆一份,把克隆的這一份的陣列中的對應項改變了)

let arr = [1,2,3,4];
let newArr = arr.map((value,index,arr) => {
    return value * 2;
})
console.log(newArr); // [2, 4, 6, 8]
複製程式碼

5.filter迴圈

filter()函式其實是迴圈原陣列裡的每個元素,然後篩選出符合條件的元素,形成一個新的陣列,引數和forEach()以及map()一模一樣。

let arr = [1,2,3,4];
let newArr = arr.filter((value,index,arr) => {
    return value % 2 == 0;
})
console.log(newArr); // [2,4]
複製程式碼

注意:必須return 否則得到的是空陣列。

6.for..of 迴圈

上面的5個迴圈都是ES5具有的,而for..of迴圈是ES6新增的,也是最強大的一個迴圈,大家只需要明白幾點:

  1. 這是最簡潔、最直接的遍歷陣列元素的語法
  2. 這個方法避開了for-in迴圈的所有缺陷
  3. 與forEach()不同的是,它可以正確響應break、continue和return語句

總的來說,遍歷陣列,用for..of就對了!

首先,我們具體來看看for..of如何遍歷陣列的:

let arr = ['yuan','will','js','es6'];
for(let value of arr) {
    console.log(value); // yuan will js es6
}
複製程式碼

這個是最簡單的方式,增加break試試:

let arr = ['yuan','will','js','es6'];
for(let value of arr) {
    if(value === 'js') {
        break;
    }
    console.log(value); // yuan will
}
複製程式碼

這個結果說明是可以通過break跳出迴圈的,不僅如此,我們還可以通過陣列的keys()方法來獲取索引,通過陣列的entrie()方法來獲取鍵值對:

迴圈keys()獲取索引

let arr = ['yuan','will','js','es6'];
for(let key of arr.keys()) {
    console.log(key); // 0 1 2 3
}
複製程式碼

迴圈entries()獲取鍵值對

let arr = ['yuan','will','js','es6'];
for(let [key,value] of arr.entries()) {
    console.log('key值:' + key + '; value值:' + value);
}
複製程式碼

列印出來的結果如下:

深入淺出ES6知識大合集

for-of迴圈不僅支援陣列,還支援大多數類陣列物件,例如DOM NodeList物件。同時也支援Map物件,Set物件(ES6新增物件,後面會講到),當然也支援字串的遍歷,這些就不一一列舉了。只需要記住:未來的JS可以使用一些新型的集合類,甚至會有更多的型別陸續誕生,而for-of就是為遍歷所有這些集合特別設計的迴圈語句。

有一點一定要注意:for-of迴圈不支援普通物件,但如果你想迭代一個物件的屬性,你可以用for-in迴圈(這也是它的本職工作)或內建的Object.keys()方法:

let person = {
    id : 1,
    age : 18,
    name : 'will',
    address : 'shenzhen',
}

for (let key of Object.keys(person)) {
    console.log(key + "是:" + person[key]);
}
複製程式碼

列印出來的結果:

深入淺出ES6知識大合集

8.函式及其擴充套件

函式引數的預設值

ES5中,我們並不能給函式的引數設定預設值,這讓我們一直很痛苦,我們來看我們以前是怎麼做的:

function example(name) {
    name = name || 'yuan';
    console.log(name);
}

example('will');      // will
example();            // yuan 
example('');          // yuan =>這個結果並不是我們想要的
example(undefined);   // yuan
複製程式碼

這樣寫的缺點相信大家都能看出來也經歷過,就是引數如果我們賦值了,只是對應的布林值是false,那麼我們對應的賦值並不起作用,所以我們又重新修改了程式碼:

function example(name) {
    if (typeof name === 'undefined') {
        name = 'yuan';
    }
    console.log(name);
}

example('will');        // will
example();              // yuan
example('');            // ''
example(undefined);     // yuan
複製程式碼

這樣就達到了目的,但是顯得很麻煩,因為我們的要求很簡單,只是引數預設賦值而已,ES6為我們做到了:

function example(name = 'yuan') {
    console.log(name);
}
example('will');        // will
example();              // yuan
example('');            // ''
example(undefined);     // yuan
複製程式碼

注意:函式的引數預設賦值並不是傳值方式的,而是每次都重新計算預設值表示式的值。也就是說,引數預設值是惰性求值的。

let x = 1;
function example(p = x + 1) {
    console.log(p);
}

example();    // 2

x = 100;
example();    // 101
複製程式碼

函式的length屬性

如果一個函式的某個引數具有預設值,那麼函式將計入的length的數量為這個引數之前的沒有設定預設屬性的引數個數。看起來很難讀懂,其實很簡單,我們看個例子:

(function(a){}).length         // 1
(function(a=1){}).length       // 0
(function(a,b=1,c){}).length   // 1
複製程式碼

第一個函式,並沒有設定引數預設值,有一個引數,所以返回1;第二個函式,設定了引數預設值且沒有不設定預設值的引數,返回0;第三個,b設定了預設引數,b前面有一個沒有設定預設引數的引數a,返回1(注意,並不會去計算b後面的c)。

函式的name屬性

函式的name屬性,返回該函式的函式名。

function example() {}
console.log(example.name); // example
複製程式碼

這個屬性早就被瀏覽器廣泛支援,但是直到 ES6,才將其寫入了標準。

需要注意的是,ES6 對這個屬性的行為做出了一些修改。如果將一個匿名函式賦值給一個變數,ES5 的name屬性,會返回空字串,而 ES6 的name屬性會返回實際的函式名。

var example = function () {}

//ES5
console.log(example.name);  // ''

//ES6
console.log(example.name);  // example
複製程式碼

如果將一個具名函式賦值給一個變數,則 ES5 和 ES6 的name屬性都返回這個具名函式原本的名字。

var exa = function example () {}

//ES5
console.log(exa.name);   // example

//ES6
console.log(exa.name);   // example
複製程式碼

箭頭函式

箭頭函式是ES6更新的非常重要的一個功能,我們來看一下基本用法:

var example = value => value;
複製程式碼

上面的箭頭函式等同於:

var example = function(value) {
  return value;
};
複製程式碼

如果箭頭函式不需要引數或需要多個引數,就使用一個圓括號代表引數部分。

var example = () => 5;
// 等同於
var example = function () { return 5 };

var sum = (num1, num2) => num1 + num2;
// 等同於
var sum = function(num1, num2) {
  return num1 + num2;
};
複製程式碼

如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回。

var sum = (num1, num2) => { return num1 + num2; }
複製程式碼

由於大括號被解釋為程式碼塊,所以如果箭頭函式直接返回一個物件,必須在物件外面加上括號,否則會報錯。

// 報錯
let getTempItem = id => { id: id, name: "Temp" };

// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });
複製程式碼

如果箭頭函式只有一行語句,且不需要返回值,可以採用下面的寫法,就不用寫大括號了。

let example = () => 5;
複製程式碼

箭頭函式有幾個使用注意點。

(1)函式體內的this物件,就是定義時所在的物件,而不是使用時所在的物件。

(2)不可以當作建構函式,也就是說,不可以使用new命令,否則會丟擲一個錯誤。

(3)不可以使用arguments物件,該物件在函式體內不存在。如果要用,可以用 rest 引數代替。

(4)不可以使用yield命令,因此箭頭函式不能用作 Generator 函式。

Object.is()方法

這個方法是用來進行物件比較的方法,當然啦,也可以進行其他值的比較,我們來對比一下它和==以及===的區別:

==: 等同,比較運算子,兩邊值型別不同的時候,先進行型別轉換,再比較;

===: 恆等,嚴格比較運算子,不做型別轉換,型別不同就是不等,也就是同值相等;

Object.is(): 比較兩個值是否嚴格相等的方法,只有嚴格一樣才返回true;

let obj1 = {name:'yuan'};
let obj2 = {name:'yuan'};
obj1.name === obj2.name;            // true
Object.is(obj1.name,obj2.name);     // true
-0 === +0;                          // true
NaN === NaN;                        // false
Object.is(-0,+0);                   // false
Object.is(NaN,NaN);                 // true
複製程式碼

9.Symbol物件

Symbol型別的基本寫法

ES5 的物件屬性名都是字串,這容易造成屬性名的衝突。比如,你使用了一個他人提供的物件,但又想為這個物件新增新的方法(mixin 模式),新方法的名字就有可能與現有方法產生衝突。如果有一種機制,保證每個屬性的名字都是獨一無二的就好了,這樣就從根本上防止屬性名的衝突。這就是 ES6 引入Symbol的原因。(注:這部分內容很多都是摘抄於阮大神的書)

ES6 引入了一種新的原始資料型別Symbol,表示獨一無二的值。它是 JavaScript 語言的第七種資料型別,前六種是:undefined、null、布林值(Boolean)、字串(String)、數值(Number)、物件(Object)。

Symbol 值通過Symbol函式生成。這就是說,物件的屬性名現在可以有兩種型別,一種是原來就有的字串,另一種就是新增的 Symbol 型別。凡是屬性名屬於 Symbol 型別,就都是獨一無二的,可以保證不會與其他屬性名產生衝突。

首先,我們來看一下最簡單的Symbol型別的宣告:

let s = Symbol();
typeof s;          //symbol
複製程式碼

Symbol函式可以接受一個字串作為引數,表示對 Symbol 例項的描述,主要是為了在控制檯顯示,或者轉為字串時,比較容易區分。但是注意,Symbol函式的引數只是表示對當前 Symbol 值的描述,因此相同引數的Symbol函式的返回值是不相等的。

let s1 = Symbol('will');
let s2 = Symbol('yuan');
let s3 = Symbol('will');

console.log(s1);
console.log(s2);

console.log(s1.toString());
console.log(s2.toString());

console.log(s1 === s2);
複製程式碼

列印出來的結果是:

深入淺出ES6知識大合集

Symbol作為屬性名

由於每一個 Symbol 值都是不相等的,這意味著 Symbol 值可以作為識別符號,用於物件的屬性名,就能保證不會出現同名的屬性。這對於一個物件由多個模組構成的情況非常有用,能防止某一個鍵被不小心改寫或覆蓋。

let mySymbol = Symbol();

// 第一種寫法
let a = {};
a[mySymbol] = 'Hello!';

// 第二種寫法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三種寫法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上寫法都得到同樣結果
a[mySymbol] // "Hello!"
複製程式碼

注意,Symbol 值作為物件屬性名時,不能用點運算子。

const mySymbol = Symbol();
const a = {};

a.mySymbol = 'Hello!';
a[mySymbol] // undefined
a['mySymbol'] // "Hello!"
複製程式碼

上面程式碼中,因為點運算子後面總是字串,所以不會讀取mySymbol作為標識名所指代的那個值,導致a的屬性名實際上是一個字串,而不是一個 Symbol 值。

屬性名的遍歷

Symbol 作為屬性名,該屬性不會出現在for...in、for...of迴圈中,也不會被Object.keys()、Object.getOwnPropertyNames()、JSON.stringify()返回。這樣就很好的保護了Symoble型別的屬性,但是,它也不是私有屬性,有一個Object.getOwnPropertySymbols方法,可以獲取指定物件的所有 Symbol 屬性名。

const obj = {};

let s1 = Symbol('will');
let s2 = Symbol('yuan');

obj[s1] = 'hello';
Object.defineProperty(obj,s2,{
    value: 'world'
});

obj.s3 = 'shen';

for(let i in obj) {
    console.log(i);                                 // s3
}

console.log(Object.getOwnPropertyNames(obj));       // ['s3']

console.log(Object.getOwnPropertySymbols(obj));     // [Symbol(will), Symbol(yuan)]
複製程式碼

上面這個例子,可以發現通過for迴圈並不能迴圈出來Symbol型別的屬性名,但是Object.getOwnPropertySymbols()方法又不能拿到字串型別的屬性名,那麼我們如果要拿到一個物件全部的屬性名怎麼辦呢,可以用Reflect.ownKeys()方法:

let obj = {
    'age' : 20,
    'address' : 'shenzhen',
    [Symbol('name')] : 'will',
    [Symbol('id')] : 5
}

console.log(Reflect.ownKeys(obj));

for(let i of Reflect.ownKeys(obj)) {
    console.log(obj[i]);
}
複製程式碼

列印出來的結果就是:

深入淺出ES6知識大合集

Symbol.for(),Symbol.keyFor()

有時,我們希望重新使用同一個 Symbol 值,Symbol.for方法可以做到這一點。它接受一個字串作為引數,然後搜尋有沒有以該引數作為名稱的 Symbol 值。如果有,就返回這個 Symbol 值,否則就新建並返回一個以該字串為名稱的 Symbol 值。

let s1 = Symbol.for('foo');
let s2 = Symbol.for('foo');

s1 === s2 // true
複製程式碼

注意:Symbol.for()與Symbol()這兩種寫法,都會生成新的 Symbol。它們的區別是,前者會被登記在全域性環境中供搜尋,後者不會。Symbol.for()不會每次呼叫就返回一個新的 Symbol 型別的值,而是會先檢查給定的key是否已經存在,如果不存在才會新建一個值。也就是說,通過Symbol()生成的Symbol並不會被檢索到,我們看個例子:

let s1 = Symbol('will');
let s2 = Symbol.for('will');
let s3 = Symbol.for('will');

console.log(s1 === s2);      //false
console.log(s2 === s3);      //true
複製程式碼

Symbol.keyFor方法返回一個已登記的 Symbol 型別值的key

let s1 = Symbol('will');
let s2 = Symbol.for('will');

console.log(Symbol.keyFor(s1));   // undefined
console.log(Symbol.keyFor(s2));   // 'will'
複製程式碼

Symbol型別主要用於函式的屬性,Symbol還有很多的方法,但是用的不是很多,所以就不一一列舉了,大家想了解更加詳細的,可以去看阮一峰大神的書。

10.Set資料結構

Set是ES6提供的一種新的資料結構,它非常類似於陣列,但是它的成員值都是唯一的,就算新增重複的值,也會自動刪除掉,並且計算Set的size的時候不會計算重複的值。

Set接受一個陣列來進行初始化(也可以不接受任何引數)

const set1 = new Set();

const set2 = new Set([1,2,3,4,5]);

const set3 = new Set([1,2,3,4,5,4,3,2,1]);

console.log (set1);
console.log (set2);
console.log (set3);
複製程式碼

列印結果:

深入淺出ES6知識大合集

由於Set是沒有重複值的,所以我們可以利用這個特性進行陣列去重:

let arr = [1,2,3,4,5,6,4,3,4,5,2];

let newArr = [...new Set(arr)];          // 方法一

let newArr1 = Array.from(new Set(arr));  // 方法二
複製程式碼

注意:Set內部判斷值是否重複的演算法是“Same-value equality”,類似於===,不同之處在於NaN對於這種演算法來言是相等的。

let set1 = new Set(['1',1,2,NaN,NaN]);

console.log(set1);
複製程式碼

列印結果是:

深入淺出ES6知識大合集

Set和陣列一樣,也是可以增刪查改的,不同的是方法不一樣,以下說明:

1.add(value) : 新增某個值,返回 Set 結構本身

2.delete(value) : 刪除某個值,返回一個布林值,表示刪除是否成功

3.has(value) : 返回一個布林值,表示該值是否為Set的成員

4.clear() : 清除所有成員,沒有返回值

以下是demo:

let set = new Set();

set.add(1).add(2).add(3);

console.log(set);
console.log(set.has(3));

set.delete(3);

console.log(set.has(3));

set.clear();

console.log(set);
複製程式碼

列印結果:

深入淺出ES6知識大合集

和陣列一樣,set資料結構也是可以進行遍歷的,但是注意一點,set是沒有鍵名的(也可以認為鍵名和鍵值一樣),所以在遍歷keys和values的時候,返回值一樣。

let set = new Set([1, 2, 3, 4, 5]);

for (let i of set) {
    console.log(i);                         // 1 2 3 4 5
}

for (let i of set.keys()) {
    console.log(i);                         // 1 2 3 4 5
}

for (let i of set.values()) {   
    console.log(i);                         // 1 2 3 4 5
}

for (let [key,value] of set.entries()) {
    console.log(key + ':' + value);         // 1:1 2:2 3:3 4:4 5:5
}
複製程式碼

我們在陣列中,經常會存放物件,而WeakSet為我們做到了,不過用的很少,可以稍作了解即可。

WeakSet 是一個建構函式,可以使用new命令,建立 WeakSet 資料結構,但是需要注意兩點,一是在new的時候不允許放入值,否則會報錯,二是WeakSet裡面的值也是不允許重複的,這裡的不允許重複指的是不能指向同一記憶體塊。

let objSet = new WeakSet();

let obj1 = {name:'will',age:18};

let obj2 = {name:'will',age:18}; //由於obj和obj2是指向不同記憶體塊,所以在WeakSet裡面是都會新增進去的

let obj3 = obj1;                  //由於obj和obj3是指向不同同一記憶體塊,所以在WeakSet裡面是隻會新增以此

objSet.add(obj1);
objSet.add(obj2);
objSet.add(obj3);

console.log(objSet);  //WeakSet {{…}, {…}}
複製程式碼

11.Map資料結構

Map是ES6提供的一種新的資料結構,類似於物件,也是鍵值對的集合,但是“鍵”的範圍不限於字串,各種型別的值(包括物件)都可以當作鍵。也就是說,Object 結構提供了“字串—值”的對應,Map 結構提供了“值—值”的對應,是一種更完善的 Hash 結構實現。如果你需要“鍵值對”的資料結構,Map 比 Object 更合適。

Map建構函式接受一個陣列作為引數(當然也可以不傳引數)

let map1 = new Map();

let map2 = new Map([['name','yuan'],['age',18]]);

console.log(map1);
console.log(map2);
複製程式碼

列印結果:

深入淺出ES6知識大合集

Map可以將物件作為鍵值:

let map = new Map();

let obj = {name:'yuan','age':18};

map.set(obj,'will');

console.log(map);
複製程式碼

列印結果:

深入淺出ES6知識大合集

Map同樣有增刪查改方法,以下說明:

  1. set(key,value) : 設定鍵名key對應的鍵值為value,然後返回整個 Map 結構。如果key已經有值,則鍵值會被更新,否則就新生成該鍵
  2. get(key) : 取key對應的鍵值,如果找不到key,返回undefined
  3. has(key) : 返回一個布林值,表示某個鍵是否在當前 Map 物件之中
  4. delete(key) : 刪除某個鍵,返回true。如果刪除失敗,返回false
  5. clear() : 清除所有成員,沒有返回值
  6. size : 這個是屬性,不是方法,返回 Map 結構的成員總數

以下是demo:

let map1 = new Map();

map1.set('string', true);           // 鍵是字串

map1.set({name:'yuan'},false);      // 鍵是物件

map1.set(1,true);                   // 鍵是數字

map1.set(undefined,false);          // 鍵是undefined

console.log(map1);

console.log(map1.get(1));           // true
console.log(map1.get(undefined));   // false

console.log(map1.has(undefined));   // true
console.log(map1.has('string'));    // true
console.log(map1.has('1'));         // false

map1.delete(1);

console.log(map1.size);             // 3

map1.clear();

console.log(map1)
複製程式碼

列印結果:

深入淺出ES6知識大合集

有一點需要注意:Map 的鍵實際上是跟記憶體地址繫結的,只要記憶體地址不一樣,就視為兩個鍵。如果 Map 的鍵是一個簡單型別的值(數字、字串、布林值),則只要兩個值嚴格相等,Map 將其視為一個鍵,比如0和-0就是一個鍵,布林值true和字串true則是兩個不同的鍵。另外,undefined和null也是兩個不同的鍵。雖然NaN不嚴格相等於自身,但 Map 將其視為同一個鍵。

let map = new Map();

let obj1 = {name:'yuan'};
let obj2 = {name:'yuan'};

map.set(obj1,1);
map.set(obj2,2);

console.log(map.get(obj1));     // 1
console.log(map.get(obj2));     // 2

map.set(-0,true);
console.log(map.get(+0));       // true

map.set(true,5);
console.log(map.get('true'));   // undefined

map.set(undefined,6);
map.set(null,7);
console.log(map.get(undefined));// 6

map.set(NaN,8);
console.log(map.get(NaN));      // 8
複製程式碼

ES6的基本知識差不都就這些了,類似promise,class等沒有做講述,只是講述了基礎的知識,上述內容有部分摘抄於阮一峰大神的ES6書籍,也有部分借鑑了網上各位大神的資料,我只是做了部分的歸納和總結,有很多不足之處或者不完善的地方,希望多多包容!

相關文章