ES6的這些新知識你記住了沒?

DreamToHacker發表於2018-08-11

前言

本人是個新手,剛入門不久,最近學習了ES6的新語法,有點個人感想,就寫出來和大家分享一下。有哪裡不對的希望各位大佬能及時指出。哪裡與您觀念不和的地方還請評論發出您的見解,讓大家共同參考一下。共同學習共同進步!

本文參考了阮一峰老師的《ECMAScript 6入門》

1. let 和 const

作為ES6語法裡很好用,也很有新穎的當屬這兩個命令了。 let 和 const都實現了塊級作用域的概念。怎麼去理解塊級作用域這個概念呢?筆者認為,塊級作用域和函式作用域類似,都會統治一片作用域。不同之處在於let宣告的塊級作用域內部再宣告不影響外部。而函式的卻會影響。程式碼如下。

function f() { console.log('I am outside!'); }

(function () {
  if (false) {
    function f() { console.log('I am inside!'); } 
  }
  f(); //理論上塊級作用域,應該輸出I am outside!實際上輸出了I am inside!
}());

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

有一個需要注意的地方。ES6 的塊級作用域允許宣告函式的規則,只在使用大括號的情況下成立,如果沒有使用大括號,就會報錯。

// 不報錯
if (true) {
  function f() {}
}

// 報錯
if (true)
  function g() {}
複製程式碼

接下來我們比較一下var,let和const。

首先var是很熟悉的命令了。var可以提升變數。var可以宣告全域性變數和區域性變數。

let功能類似於var,不過它比var多了一個塊級作用域。剛才我們已經簡單的介紹了一下塊級作用域了。現在直接來對比一下var和let的區別,更直觀的反映一下它們的不同之處。

{
    var a = 1;
    let b = 2;
}
console.log(a);   // 1
console.log(b);   // b is not defined
複製程式碼

由以上程式碼不難看出,var把a定義到了全域性裡面,而let不一樣。因此我們可以在{}外查詢到a而查不到b。

在使用let時經常會帶來一些負面影響。比如TDZ(暫時性死區)。let命令在塊級作用域中,即使不存在變數提升,它也會影響當前塊級作用域,即繫結在了當前作用域。在作用域中引用外部的變數將會報錯。

 var a=10;
  {
      console.log(a);  //ReferenceError: a is not defined
      let a=10;
  }
複製程式碼

const宣告一個只讀的常量。一旦宣告,常量的值就不能改變。用法類似let。

const保證常量的值不被修改只是常量在棧區的值不變,如果這個值是一個基本資料型別,const就能夠保障常量的值不變;但是如果是引用型別的資料,棧區儲存的其實是對應常量的地址。地址無法改變,但是對應地址的堆區內容卻可以改變。程式碼如下。

 const m = 10;
 m = 12;
 console.log(m); 
 // TypeError: Assignment to constant variable.
 
 
 const n = [1,2,3];
 n.push(4);
 console.log(n); //[1, 2, 3, 4]
複製程式碼

2. 解構賦值

在ES6中,允許了按照一定的模式,從陣列和物件中取值,對變數賦值。這被稱為解構。但是有個要求是等號兩邊的模式要相同,這樣左邊的變數就會被賦予對應的值。如果解構不成功,變數的值就等於undefined。程式碼如下。

let [a,[b,[c]]] = [1,[2,[3]]]
console.log(a);  //  1
console.log(b);  //  2
console.log(c);  //  3

let [x, ,y] = [1,2,3];
console.log(x);  //  1
console.log(y);  //  3

let [one, ...all] = [1,2,3,4];
console.log(one); //  1
console.log(all); //  [2,3,4]

let [a,b, ...c] = [1];
console.log(a);  //  1
console.log(b);  //  undefined
console.log(c);  //  []
複製程式碼

但是要注意,以下幾種型別的會報錯(嚴格地說,不是可遍歷的結構,詳情可參見《Iterator》一章)。程式碼如下。

let [a] = 1;
let [a] = false;
let [a] = NaN;
let [a] = undefined;
let [a] = null;
let [a] = {};
複製程式碼

物件的解構賦值與陣列類似。舉幾個例子,程式碼如下。

let {a,b} = {a:"Hello",b:"World"};
console.log(a);  //  "Hello"
console.log(b);  //  "World"

//與陣列一樣,解構也可以用於巢狀的物件。
let obj = {
    P:[
    'Hello',{y:'World'}
    ]
}

let {p:[x,{y}]} = obj;
console.log(x);  //  'Hello'
console.log(y);  //  'World'
//注意,這時p是一個模式,不是變數,因此不會被賦值。
複製程式碼

物件是一個無序集合,而陣列是有序的。所以在解構賦值的時候變數必須與屬性同名才可以取到我們想要的結果。程式碼如下。

let {h,w} = {h:'Hello',w:'World'}
console.log = (h);  //  'Hello'
console.log = (w);  //  'World'

let {m,n} = { m:"123" }
console.log(m);  //  "123"
console.log(n);  //  undefined
複製程式碼

其實關於解構賦值,最常用的還是在函式中。

function f({x = 0, y = 0} = {}) {
  return [x, y];
}

f({x: 1, y: 2});  // [1, 2]
f({x: 1});        // [1, 0]
f({});            // [0, 0]
f();              // [0, 0]
//函式f的引數是一個物件,通過對這個物件進行解構,得到變數x和y的值。如果解構失敗,x和y等於預設值。

function f({x, y} = { x: 0, y: 0 }) {
  return [x, y];
}

f({x: 1, y: 2});  // [1, 2]
f({x: 1});        // [1, undefined]
f({});            // [undefined, undefined]
f();              // [0, 0]
//此時,函式f的引數指定預設值,而不是為變數x和y指定預設值,所以會得到與前一種寫法不同的結果。

//還有一種undefined會觸發函式引數的預設值。
[1, undefined, 3].map((x = 'yes') => x);     // [ 1, 'yes', 3 ]

複製程式碼

3. 模版字串

模板字串(template string)是增強版的字串,用反引號(`)標識。它可以當作普通字串使用,也可以用來定義多行字串,或者在字串中嵌入變數。若要將變數嵌入模版字串中,需要將變數名寫在${}內。舉個小例子吧。

let obj = {
    name : "掘金",
    level : 100,
    sex : "man"
}
console.log(`我在一個名字為${obj.name}的網站上發現了一個${obj.level}級的大神,可惜他是個${obj.sex`});

//上面這段話換成以前的寫法看起來會繁瑣很多。如下。

console.log("我在一個名字為"+obj.name+"的網站上發現了一個"+obj.level+"級的大神,可惜他是個"+obj.sex)
複製程式碼

大括號內部可以放入任意的 JavaScript 表示式,可以進行運算,以及引用物件屬性。模板字串之中還能呼叫函式。程式碼如下:

let x = 1;
let y = 2;

`${x} + ${y} = ${x + y}`
// "1 + 2 = 3"

`${x} + ${y * 2} = ${x + y * 2}`
// "1 + 4 = 5"

let obj = {x: 1, y: 2};
`${obj.x + obj.y}`
// "3"

function f() {
  return "welcome";
}

`I ${f()} you to read this article`
//  I Welcome you to read this article.
複製程式碼

4. 函式

4.1 箭頭函式

ES6 允許使用“箭頭”(=>)定義函式。這是個很實用的技能。會讓我們的程式碼變得簡潔很多。程式碼如下。

var f = function (x) {
  return x;
};
// 等於
var f =(x)=>{return x};
// 當只有一個或不需要引數時,且只有一個非return語句時,可以省略成如下。
var f = (x) => console.log("hello")
//如果是return語句,則不能省略,會報錯。我們可以將{}和return一起省略。
var f = x => x;
//如果箭頭函式的程式碼塊部分多於一條語句,就要使用大括號將它們括起來,並且使用return語句返回。

//如果箭頭函式只有一行語句,且不需要返回值,可以採用下面的寫法,就不用寫大括號了
let g = () => void NoNeedReturn();
複製程式碼

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

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

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

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

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

5. 陣列

5.1 陣列的擴充套件運算子

擴充套件運算子(spread)是三個點(...)。作用是將一個陣列轉為用逗號分隔的引數序列。

擴充套件運算子等同於複製了一個陣列。為什麼要說等同於,是因為陣列是複合的資料型別,直接複製的話,只是複製了指向底層資料結構的指標,而不是克隆一個全新的陣列。程式碼如下:

let arr = [1,2,3,4,5];
let ars = [...arr];
console.log(ars);    // [1,2,3,4,5]

// 修改 ars 會造成 arr 不會隨著修改。
ars[0] = 6;
console.log(ars);    // [6,2,3,4,5]
console.log(arr);    // [1,2,3,4,5]

// 修改 arr 也不會造成 ars 修改。
arr[2] = 7;
console.log(ars);    // [6,2,3,4,5]
console.log(arr);    // [1,2,7,4,5]
複製程式碼

擴充套件運算子還有個用處是合併陣列。

let arr1 = ["a","b"];
let arr2 = ["c"];
let arr3 = ["d","e","f"];
let arrs = [...arr1,...arr2,...arr3];
console.log(arrs);     //["a","b","c","d","e","f"]
複製程式碼

擴充套件運算子還有個妙用,它可以把偽陣列變成真陣列。程式碼如下:

<ul>
    <li>test 1</li>
    <li>test 2</li>
    <li>test 3</li>
    <li>test 4</li>
    <li>test 5</li>
    <li>test 6</li>
    <li>test 7</li>
    <li>test 8</li>
    <li>test 9</li>
</ul>
<script>
    let lis = document.getElementsByTagName("lis");
    let arr = [...lis];
    console.log(Array.isArray(arr))     //true
</script>
複製程式碼

擴充套件運算子也常用於函式呼叫。

function push(array, ...items) {
  array.push(...items);
}

function add(x, y) {
  return x + y;
}

const numbers = [4, 38];
add(...numbers) // 42
複製程式碼

擴充套件運算子與正常的函式引數可以結合使用,非常靈活。

function f(m, n, x, y, z) { }
const arrs = [0, 1];
f(-1, ...arrs, 2, ...[3]);
複製程式碼

5.2 Array.from()

Array.from方法用於將兩類物件轉為真正的陣列:類似陣列的物件(array-like object)和可遍歷(iterable)的物件(包括 ES6 新增的資料結構 Set 和 Map)。

<ul>
    <li>test 1</li>
    <li>test 2</li>
    <li>test 3</li>
    <li>test 4</li>
    <li>test 5</li>
    <li>test 6</li>
    <li>test 7</li>
    <li>test 8</li>
    <li>test 9</li>
</ul>
<script>
    let lis = document.getElementsByTagName("lis");
    let arr = Array.from(lis);
    console.log(Array.isArray(arr))     //true
</script>
//  此處用法極其類似前面的擴充套件運算子。
複製程式碼

眾所周知,函式內部有個類似陣列的arguments物件。我們可以用Array.from()將它們轉為真正的陣列。如下:

function f(){
    let args = Array.from(arguments);
    console.log(Array.isArray(args));    // true
}
f();   
複製程式碼

5.3 Array.of

Array.of是將一組值轉換為陣列。與 Array.from 功能相似,我們可以理解成是用來建立陣列。 主要目的是彌補構造器 Array()的不足。

Array()           // []
Array(3)          // [, , ,]
Array(3, 5, 8)    // [3, 5, 8]

Array.of()           // []
Array.of(undefined)  // [undefined]
Array.of(1)          // [1]
Array.of(1, 2)       // [1, 2] 
Array.of('a','b')       // ['a','b'] 
複製程式碼

5.4 神奇好用的Set

ES6 提供了新的資料結構 Set。它類似於陣列,但是成員的值都是唯一的,沒有重複的值。

Set 本身是一個建構函式,用來生成 Set 資料結構。 Set可以用於陣列的除重。

//將一個陣列排序併除重。
let arr = [4,2,4,3,5,2,3,1];
let newArr = [];
function f(array){
    array = array.sort();
    for(let i=0; i<arr.length; i++){
        if(newArr.includes(arr[i])){
        }else{
            newArr.push(arr[i]);
        }
    }
    return newArr;
}
f(arr);
console.log(newArr);

// 用 Set 之後程式碼量一下子少了很多。
let arr = [4,2,4,3,5,2,3,1];
console.log(new Set(arr.sort())  );
複製程式碼

5.5 特別的map

map在陣列中的作用是逐一處理原陣列元素,返回一個新陣列。其中需要注意的是map的引數是回撥函式,回撥函式中的引數和forEach是一樣的,回撥函式中要有return。程式碼如下:

let arr = [1,2,3];
let ars = arr.map((item,index,arr)=>{
    return item += 10;
})
console.log(ars);    //    [11,12,13]
複製程式碼

還可以利用map去操作物件陣列。

let arr = [
    {name:"掘金1",score:59},
    {name:"掘金2",score:82},
    {name:"掘金3",score:16},
    {name:"掘金4",score:23},
    {name:"掘金5",score:64},
    {name:"掘金6",score:76},
    {name:"掘金7",score:98}
]
let rs = arr.map((item,index,arr)=>{
    return item.score -= 5;
})
console.log(rs);  //  [ 54, 77, 11, 18, 59, 71, 93 ]
複製程式碼

map常用在我們不想建立新陣列,只想修改原陣列時。需要對陣列進行加工時也要用到。

6. class

6.1 class基礎知識

ES6 提供了更接近傳統語言的寫法,引入了 Class(類)這個概念,作為物件的模板。通過class關鍵字,可以定義類。

基本上,ES6 的class可以看作只是一個語法糖,它的絕大部分功能,ES5 都可以做到,新的class寫法只是讓物件原型的寫法更加清晰、更像物件導向程式設計的語法而已。如下:

    function NBA(name,age,score){
        this.name = name;
        this.age = age;
        this.score = age;
    }
    NBA.prototype.say = function(){
        console.log(`我是${this.name},今年${this.age}歲了,場均得分${this.score}`)
    }
    let p1 = new NBA("Curry","30","25.5");
    let p2 = new NBA("Durant","30","29");
    let p3 = new NBA("James","34","34");
    console.log(p1.say());  //我是Curry,今年30歲了,場均得分25.5
    console.log(p2.say());  //我是Durant,今年30歲了,場均得分29
    console.log(p3.say());  //我是James,今年34歲了,場均得分34


//用class改寫了一下上面的程式碼。
 class NBA{
        constructor(name,age,score){
            this.name = name;
            this.age = age;
            this.score = score;
        }
        say(){
            console.log(`我是${this.name},今年${this.age}歲了,場均得分${this.score}`)
        }
    }
    let p1 = new NBA("Curry","30","25.5");
    let p2 = new NBA("Durant","30","29");
    let p3 = new NBA("James","34","34");
    console.log(p1.say());  //我是Curry,今年30歲了,場均得分25.5
    console.log(p2.say());  //我是Durant,今年30歲了,場均得分29
    console.log(p3.say());  //我是James,今年34歲了,場均得分34
複製程式碼

6.2 class繼承

Class 可以通過extends關鍵字實現繼承,這比 ES5 的通過修改原型鏈實現繼承,要清晰和方便很多。

但是需要注意的是在子類中的構造器 constructor 中,必須要顯式呼叫父類的 super 方法,如果不呼叫,則 this 不可用。如下:

//在ES5的繼承如下:
    function NBA(name,age,score){
        this.name = name;
        this.age = age;
        this.score = age;
    }
    NBA.prototype.say = function(){
        console.log(`我是${this.name},今年${this.age}歲了,場均得分${this.score}`)
    }
    function MVP(name,age,score,year){
        NBA.call(this,name,age,score);
        this.year = year;
    }
    for(let p in NBA.prototype){
        MVP.prototype[p] = NBA.prototype[p];
    }
    MVP.prototype.showMVP = function(){
        console.log(`我是${this.name},場均得分${this.score},我是${this.year}年的MVP`)
    }
    let mvp1 = new MVP("Curry","30","25.5","2015");
    let mvp2 = new MVP("Durant","30","29","2017");
    let mvp3 = new MVP("James","34","34","2016");
    mvp1.showMVP();  //我是Curry,場均得分30,我是2015年的MVP
    mvp2.showMVP();  //我是Durant,場均得分30,我是2017年的MVP
    mvp3.showMVP();  //我是James,場均得分34,我是2016年的MVP

//使用ES6中的extends來實現繼承:
     class NBA{
        constructor(name,age,score){
            this.name = name;
            this.age = age;
            this.score = score;
        }
        say(){
            console.log(`我是${this.name},今年${this.age}歲了,場均得分${this.score}`)
        }
    }
    class MVP extends NBA{
        constructor(name,age,score,year){
            super(name,age,score);
            this.year = year;
        }
        showMVP(){
            console.log(`我是${this.name},場均得分${this.score},我是${this.year}年的MVP`)
        }
    }
    let mvp1 = new MVP("Curry","30","25.5","2015");
    let mvp2 = new MVP("Durant","30","29","2017");
    let mvp3 = new MVP("James","34","34","2016");
    mvp1.showMVP();  //我是Curry,場均得分30,我是2015年的MVP
    mvp2.showMVP();  //我是Durant,場均得分30,我是2017年的MVP
    mvp3.showMVP();  //我是James,場均得分34,我是2016年的MVP
複製程式碼

super這個關鍵字,既可以當作函式使用,也可以當作物件使用。在這兩種情況下,它的用法完全不同。

第一種情況,super作為函式呼叫時,代表父類的建構函式。ES6 要求,子類的建構函式必須執行一次super函式。

第二種情況,super作為物件時,在普通方法中,指向父類的原型物件;在靜態方法中,指向父類。

最後送大家一個好玩的東西。

將下面的程式碼在控制檯中執行。

const m_style = document.createElement('style');
const m_style_text = '*{background-color:rgba(255,0,0,.2)}* *{background-color:rgba(0,255,0,.2)}* * *{background-color:rgba(0,0,255,.2)}* * * *{background-color:rgba(255,0,255,.2)}* * * * *{background-color:rgba(0,255,255,.2)}* * * * * *{background-color:rgba(255,255,0,.2)}';
m_style.appendChild(document.createTextNode(m_style_text));
document.getElementsByTagName('head')[0].appendChild(m_style)
複製程式碼

最後,還希望各路大佬能留下寶貴的意見。筆者是個新人還請多多指教,哪裡不對儘管批評。非常感謝您的閱讀。

相關文章