1. 程式人生 > >ES6核心特性

ES6核心特性

摘要: 聊JS離不開ES6啊

前言

ES6 雖提供了許多新特性,但我們實際工作中用到頻率較高並不多,根據二八法則,我們應該用百分之八十的精力和時間,好好專研這百分之二十核心特性,將會收到事半功倍的奇效!寫文章不容易,請大家多多支援與關注!本文首發地址GitHub部落格

一、開發環境配置

這部分著重介紹:babel 編譯ES6語法,如何用webpack實現模組化。

1. babel

為啥需要babel?

ES6 提供了許多新特性,但並不是所有的瀏覽器都能夠完美支援。下圖是各個瀏覽器對ES6相容性一覽表(以export為例)

由上圖可知,有些瀏覽器對於ES6並不是很友好,針對 ES6 的相容性問題,很多團隊為此開發出了多種語法解析轉換工具(比如babel,jsx,traceur 等),可以把我們寫的 ES6 語法轉換成 ES5,相當於在 ES6 和瀏覽器之間做了一個翻譯官。其中Babel是一個廣泛使用的轉碼器,可以將ES6程式碼轉為ES5程式碼,從而在現有環境執行。

如何配置babel?

  • 首先要先安裝node.js,執行npm init,然後會生成package.json檔案
  • npm install --save-dev babel-core babel-preset-es2015 babel-preset-latest
  • 建立並配置.babelrc檔案//存放在專案的根目錄下,與node_modules同級
  • npm install -g babel-cli
  • babel -version

Babel的配置檔案是.babelrc,存放在專案的根目錄下。該檔案用來設定轉碼規則和外掛,具體內容如下:

//.babelrc檔案
{
    "presets": ["es2015", "latest"],
    "plugins": []
}

驗證babel配置是否成功

·建立./src/index.js
·內容:[1,2,3].map(item=>item+1);
·執行babel.
/src/index.js

執行後得到以下部分,說明已經成功配置了babel

"use strict";
[1, 2, 3].map(function (item) {
  return item + 1;
});

2. webpack

為啥要使用WebPack?

現今的很多網頁其實可以看做是功能豐富的應用,它們擁有著複雜的JavaScript程式碼和一大堆依賴包,模快化工具就應運而生了,其中webpack 功能強大深受人們喜愛。
Webpack的工作方式是:把你的專案當做一個整體,通過一個給定的主檔案(如:index.js),Webpack將從這個檔案開始找到你的專案的所有依賴檔案,使用loaders處理它們,最後打包為一個(或多個)瀏覽器可識別的JavaScript檔案。

如何配置webpack?

·npm install webpack babel-loader --save-dev
·建立並配置 webpack.config.js//webpack.config.js檔案與package.json同級
·配置 package.json中的scripts
·執行 npm start
//配置 webpack.config.js  針對.js結尾的檔案除了node_modules都用babel解析
module.exports = {
    entry: './src/index.js',
    output: {
        path: __dirname,
        filename: './build/bundle.js'
    },
    module: {
        rules: [{
            test: /\.js?$/,
            exclude: /(node_modules)/,
            loader: 'babel-loader'
        }]
    }
}
//配置 package.json中的scripts
"scripts": {
    "start": "webpack",
    "test": "echo \"Error: no test specified\" && exit 1"
  }

二、塊級作用域

ES5 只有全域性作用域和函式作用域(例如,我們必須將程式碼包在函式內來限制作用域),這導致很多問題:

情況1:內層變數覆蓋外層變數

var tmp = new Date();
function f() {
  console.log(tmp); //undefined
  if (false) {   
    var tmp = "hello world";
  }
}

情況2:變數洩露,成為全域性變數

var s = 'hello';
for (var i = 0; i < s.length; i++) {
  console.log(s[i]);
}
console.log(i); // 5

ES6 提供 let 和 const 來代替 var 宣告變數,新的宣告方式支援用大括號表示的塊級作用域,這會帶來一些好處:

不再需要立即執行的函式表示式(IIFE)

在 ES5 中,我們需要構造一個立即執行的函式表示式去保證我們不汙染全域性作用域。在 ES6中, 我們可以使用更簡單的大括號({}),然後使用 const 或者 let 代替 var 來達到同樣的效果。

迴圈體中的閉包不再有問題

在 ES5 中,如果迴圈體內有產生一個閉包,訪問閉包外的變數,會產生問題。在 ES6,你可以使用 “let” 來避免問題。

防止重複宣告變數

ES6 不允許在同一個作用域內用 let 或 const 重複宣告同名變數。這對於防止在不同的 js 庫中存在重複宣告的函式表示式十分有幫助。

三、陣列的擴充套件

Array.from() : 將偽陣列物件或可遍歷物件轉換為真陣列

如果一個物件的所有鍵名都是正整數或零,並且有length屬性,那麼這個物件就很像陣列,稱為偽陣列。典型的偽陣列有函式的arguments物件,以及大多數 DOM 元素集,還有字串。

...
<button>測試1</button>
<br>
<button>測試2</button>
<br>
<button>測試3</button>
<br>
<script type="text/javascript">
let btns = document.getElementsByTagName("button")
console.log("btns",btns);//得到一個偽陣列
btns.forEach(item=>console.log(item)) Uncaught TypeError: btns.forEach is not a function
</script>

針對偽陣列,沒有陣列一般方法,直接遍歷便會出錯,ES6新增Array.from()方法來提供一種明確清晰的方式以解決這方面的需求。

Array.from(btns).forEach(item=>console.log(item))將偽陣列轉換為陣列

Array.of(v1, v2, v3) : 將一系列值轉換成陣列

當呼叫 new Array( )構造器時,根據傳入引數的型別與數量的不同,實際上會導致一些不同的結果, 例如:

let items = new Array(2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // undefined
console.log(items[1]) ;
let items = new Array(1, 2) ;
console.log(items.length) ; // 2
console.log(items[0]) ; // 1
console.log(items[1]) ; // 2

當使用單個數值引數來呼叫 Array 構造器時,陣列的長度屬性會被設定為該引數。 如果使用多個引數(無論是否為數值型別)來呼叫,這些引數也會成為目標陣列的項。陣列的這種行為既混亂又有風險,因為有時可能不會留意所傳引數的型別。

ES6 引入了Array.of( )方法來解決這個問題。該方法的作用非常類似Array構造器,但在使用單個數值引數的時候並不會導致特殊結果。Array.of( )方法總會建立一個包含所有傳入引數的陣列,而不管引數的數量與型別

let items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]); // 2

Array.of基本上可以用來替代Array()或newArray(),並且不存在由於引數不同而導致的過載,而且他們的行為非常統一。

陣列例項的 find() 和 findIndex()

陣列例項的find方法,用於找出第一個符合條件的陣列成員。它的引數是一個回撥函式,所有陣列成員依次執行該回調函式,直到找出第一個返回值為true的成員,然後返回該成員。如果沒有符合條件的成員,則返回undefined。

[1, 4, -5, 10].find((n) => n < 0) // -5

陣列例項的findIndex方法的用法與find方法非常類似,返回第一個符合條件的陣列成員的位置,如果所有成員都不符合條件,則返回-1。

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2

陣列例項的includes()

Array.prototype.includes方法返回一個布林值,表示某個陣列是否包含給定的值。該方法的第二個引數表示搜尋的起始位置,預設為0。如果第二個引數為負數,則表示倒數的位置,如果這時它大於陣列長度(比如第二個引數為-4,但陣列長度為3),則會重置為從0開始。

[1, 2, 3].includes(2)   // true
[1, 2, 3].includes(3, -1); // true
[1, 2, 3, 5, 1].includes(1, 2); // true

沒有該方法之前,我們通常使用陣列的indexOf方法,檢查是否包含某個值。indexOf方法有兩個缺點,一是不夠語義化,它的含義是找到引數值的第一個出現位置,所以要去比較是否不等於-1,表達起來不夠直觀。二是,它內部使用嚴格相等運算子(===)進行判斷,這會導致對NaN的誤判

[NaN].indexOf(NaN) // -1
[NaN].includes(NaN) // true

陣列例項的 entries(),keys() 和 values()

ES6 提供entries(),keys()和values(),用於遍歷陣列。它們都返回一個遍歷器物件,可以用for…of迴圈進行遍歷,唯一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

四、箭頭函式

ES6 允許使用“箭頭”(=>)定義函式。它主要有兩個作用:縮減程式碼和改變this指向,接下來我們詳細介紹:

縮減程式碼

const double1 = function(number){
   return number * 2;   //ES5寫法
}
const double2 = (number) => {
 return number * 2;    //ES6寫法
}
const double4 = number => number * 2; //可以進一步簡化

多個引數記得加括號

const double6 = (number,number2) => number + number2;

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

 const double = (number,number2) => {
   sum = number + number2 
   return sum;
 }

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

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

此外還有個好處就是簡化回撥函式

// 正常函式寫法
[1,2,3].map(function (x) {
  return x * x;
});
// 箭頭函式寫法
[1,2,3].map(x => x * x);//[1, 4, 9]

改變this指向

長期以來,JavaScript 語言的this物件一直是一個令人頭痛的問題,在物件方法中使用this,必須非常小心。箭頭函式”繫結”this,很大程度上解決了這個困擾。我們不妨先看一個例子:

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    return this.members.map(function(member){
      return `${member}隸屬於${this.teamName}小組`;    // this不知道該指向誰了
    })
  }
}
console.log(team.teamSummary());//["Henry隸屬於undefined小組", "Elyse隸屬於undefined小組"]

teamSummary函式裡面又嵌了個函式,這導致內部的this的指向發生了錯亂。
那如何修改:

方法一、let self = this

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    let self = this;
    return this.members.map(function(member){
      return `${member}隸屬於${self.teamName}小組`;
    })
  }
}
console.log(team.teamSummary());//["Henry隸屬於es6小組", "Elyse隸屬於es6小組"]

方法二、bind函式

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    return this.members.map(function(member){
      // this不知道該指向誰了
      return `${member}隸屬於${this.teamName}小組`;
    }.bind(this))
  }
}
console.log(team.teamSummary());//["Henry隸屬於es6小組", "Elyse隸屬於es6小組"]

方法三、 箭頭函式

const team = {
  members:["Henry","Elyse"],
  teamName:"es6",
  teamSummary:function(){
    return this.members.map((member) => {
      // this指向的就是team物件
      return `${member}隸屬於${this.teamName}小組`;
    })
  }
}
console.log(team.teamSummary());//["Henry隸屬於es6小組", "Elyse隸屬於es6小組"]

使用注意點

  • 函式體內的this物件,就是定義時所在的物件,而不是使用時所在的物件。
  • 不可以當作建構函式,也就是說,不可以使用new命令,否則會丟擲一個錯誤。
  • 不可以使用arguments物件,該物件在函式體內不存在。如果要用,可以用 rest 引數代替。
  • 不可以使用yield命令,因此箭頭函式不能用作 Generator 函式。

五、rest 引數

ES6 引入 rest 引數(形式為…變數名),用於獲取函式的多餘引數,這樣就不需要使用arguments物件了。

rest 引數搭配的變數是一個數組,該變數將多餘的引數放入陣列中。
我們舉個例子:如何實現一個求和函式?

傳統寫法:

function addNumbers(a,b,c,d,e){
  var numbers = [a,b,c,d,e];
  return numbers.reduce((sum,number) => {
    return sum + number;
  },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

ES6寫法:

 function addNumbers(...numbers){
  return numbers.reduce((sum,number) => {
    return sum + number;
  },0)
 }
 console.log(addNumbers(1,2,3,4,5));//15

也可以與解構賦值組合使用

var array = [1,2,3,4,5,6];
var [a,b,...c] = array;
console.log(a);//1
console.log(b);//2
console.log(c);//[3, 4, 5, 6]

rest 引數還可以與箭頭函式結合

const numbers = (...nums) => nums;
numbers(1, 2, 3, 4, 5)// [1,2,3,4,5]  

注意:①每個函式最多隻能宣告一個rest引數,而且 rest引數必須是最後一個引數,否則報錯。

②rest引數不能用於物件字面量setter之中

let object = {
    set name(...value){   //報錯
        //執行一些邏輯
    }
}

六、展開運算子

與剩餘引數關聯最密切的就是擴充套件運算子。剩餘引數允許你把多個獨立的引數合併到一個數組中;而擴充套件運算子則允許將一個數組分割,並將各個項作為分離的引數傳給函式。

當用在字串或陣列前面時稱為擴充套件運算子,個人覺得可以理解為rest引數的逆運算,用於將陣列或字串進行拆解。有些時候,函式不允許傳入陣列,此時使用展開運算子就很方便,不信的話,咱們看個例子:Math.max()方法,它接受任意數量的引數,並會返回其中的最大值。

let value1 = 25,                
let value2 = 50;
console.log(Math.max(value1, value2));    //    50

但若想處理陣列中的值,此時該如何找到最大值?Math.max()方法並不允許你傳入一個數組。其實你可以像使用rest引數那樣在該陣列前新增…,並直接將其傳遞給 Math.max()

let values = [25,50,75,    100]
//等價於console.log(Math.max(25,50,75,100));
console.log(Math.max(...values));    //100

擴充套件運算子還可以與其他引數混用

let values = [-25,-50,-75,-100]
console.log(Math.max(...values,0));    //0

擴充套件運算子拆解字串與陣列

var array = [1,2,3,4,5];
console.log(...array);//1 2 3 4 5
var str = "String";
console.log(...str);//S t r i n g

還可以實現拼接

var defaultColors = ["red","greed"];
var favoriteColors = ["orange","yellow"];
var fallColors = ["fire red","fall orange"];
console.log(["blue","green"