1. 程式人生 > >學習ES6筆記──工作中常用到的ES6語法

學習ES6筆記──工作中常用到的ES6語法

一、let和const

在JavaScript中咱們以前主要用關鍵var來定義變數,ES6之後,新增了定義變數的兩個關鍵字,分別是let和const。對於變數來說,在ES5中var定義的變數會提升到作用域中所有的函式與語句前面,而ES6中let定義的變數則不會,let宣告的變數會在其相應的程式碼塊中建立一個暫時性死區,直至變數被宣告。let和const都能夠宣告塊級作用域,用法和var是類似的,let的特點是不會變數提升,而是被鎖在當前塊中。

一個非常簡單的例子:

function test() {
if(true) {
  console.log(a)//TDZ,俗稱臨時死區,用來描述變數不提升的現象
  let a = 1
}
}
test()  // a is not defined

function test() {
    if(true) {
      let a = 1
    }
    console.log(a)
}    
test() // a is not defined

唯一正確的使用方法:先宣告,再訪問。

function test() {
    if(true) {
      let a = 1
      console.log(a)
    }
}
test() // 1

const宣告常量,一旦宣告,不可更改,而且常量必須初始化賦值。const雖然是常量,不允許修改預設賦值,但如果定義的是物件Object,那麼可以修改物件內部的屬性值。

const type = {
  a: 1
}
type.a = 2 //沒有直接修改type的值,而是修改type.a的屬性值,這是允許的。
console.log(type) // {a: 2}

const和let的異同點

相同點:const和let都是在當前塊內有效,執行到塊外會被銷燬,也不存在變數提升(TDZ),不能重複宣告。不同點:const不能再賦值,let宣告的變數可以重複賦值。const實際上保證的,並不是變數的值不得改動,而是變數指向的那個記憶體地址所儲存的資料不得改動。對於簡單型別的資料(數值、字串、布林值),值就儲存在變數指向的那個記憶體地址,因此等同於常量。但對於複合型別的資料(主要是物件和陣列),變數指向的記憶體地址,儲存的只是一個指向實際資料的指標,const只能保證這個指標是固定的(即總是指向另一個固定的地址),至於它指向的資料結構是不是可變的,就完全不能控制了。因此,將一個物件宣告為常量必須非常小心。

塊級作用域的使用場景除了上面提到的常用宣告方式,我們還可以在迴圈中使用,最出名的一道面試題:迴圈中定時器閉包的考題在for迴圈中使用var宣告的迴圈變數,會跳出迴圈體汙染當前的函式。

for(var i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i) //5, 5, 5, 5, 5
  }, 0)
}
console.log(i) //5 i跳出迴圈體汙染外部函式

//將var改成let之後
for(let i = 0; i < 5; i++) {
  setTimeout(() => {
    console.log(i) // 0,1,2,3,4
  }, 0)
}
console.log(i)//i is not defined i無法汙染外部函式

在實際開發中,我們選擇使用var、let還是const,取決於我們的變數是不是需要更新,通常我們希望變數保證不被惡意修改,而使用大量的const。使用const宣告,宣告一個物件的時候,也推薦使用const,當你需要修改宣告的變數值時,使用let,var能用的場景都可以使用let替代。

symbolES6 以前,我們知道5種基本資料型別分別是Undefined,Null,Boolean,Number以及String,然後加上一種引用型別Object構成了JavaScript中所有的資料型別,但是ES6出來之後,新增了一種資料型別,名叫symbol,像它的名字表露的一樣,意味著獨一無二,意思是每個 Symbol型別都是獨一無二的,不與其它 Symbol 重複。可以通過呼叫 Symbol() 方法將建立一個新的 Symbol 型別的值,這個值獨一無二,不與任何值相等。

var mySymbol=Symbol();
console.log(typeof mySymbol) //"symbol"

二、字串

ES6字串新增的方法

UTF-16碼位:ES6強制使用UTF-16字串編碼。關於UTF-16的解釋請自行百度瞭解。

codePointAt():該方法支援UTF-16,接受編碼單元的位置而非字串位置作為引數,返回與字串中給定位置對應的碼位,即一個整數值。

String.fromCodePoiont():作用與codePointAt相反,檢索字串中某個字元的碼位,也可以根據指定的碼位生成一個字元。

normalize():提供Unicode的標準形式,接受一個可選的字串引數,指明應用某種Unicode標準形式。

在ES6中,新增了3個新方法。每個方法都接收2個引數,需要檢測的子字串,以及開始匹配的索引位置。

模板字串字串是JavaScript中基本型別之一,應該算是除了物件之外是使用最為頻繁的型別吧,字串中包含了例如substr,replace,indexOf,slice等等諸多方法,ES6引入了模板字串的特性,用反引號來表示,可以表示多行字串以及做到文字插值(利用模板佔位符)。

// 以前的多行字串我們這麼寫:
console.log("hello world 1\n\
hello cala");
// "hello world
// hello cala"

//有了模板字串之後
console.log(`hello world
string text line 2`);
// "hello world
// hello cala"

可以用${}來表示模板佔位符,可以將你已經定義好的變數傳進括弧中,例如:

var name="cala";
var age=22;
console.log(`hello,I'am ${name},my age is ${age}`)
//hello,I'am cala,my age is 22

includes(str, index):如果在字串中檢測到指定文字,返回true,否則false。

let t = 'abcdefg'
if(t.includes('cde')) {
  console.log(2)
}
//true

startsWith(str, index):如果在字串起始部分檢測到指定文字,返回true,否則返回false。

let t = 'abcdefg'
if(t.startsWith('ab')) {
  console.log(2)
}
//true

endsWith(str, index):如果在字串的結束部分檢測到指定文字,返回true,否則返回false。

let t = 'abcdefg'
if(t.endsWith('fg')) {
  console.log(2)
}
//true

如果你只是需要匹配字串中是否包含某子字串,那麼推薦使用新增的方法,如果需要找到匹配字串的位置,使用indexOf()。

三、函式

函式的預設引數在ES5中,我們給函式傳引數,然後在函式體內設定預設值,如下面這種方式。

function a(num, callback) {
  num = num || 6
  callback = callback || function (data) {console.log('ES5: ', data)}
  callback(num * num)
}
a() //ES5: 36,不傳參輸出預設值

//你還可以這樣使用callback
a(10, function(data) {
  console.log(data * 10) // 1000, 傳參輸出新數值
})

在ES6中,我們使用新的預設值寫法

function a(num = 6, callback = function (data) {console.log('ES6: ', data)}) {
  callback(num * num)
}

a() //ES6: 36, 不傳參輸出預設值

a(10, function(data) {
  console.log(data * 10) // 1000,傳參輸出新數值
})

四、箭頭函式(=>)

(箭頭函式比較重要,現在簡單提一下,遲一點有空專門寫一篇箭頭函式的文章。)

const arr = [5, 10]
const s = arr.reduce((sum, item) => sum + item)
console.log(s) // 15

箭頭函式中this的使用跟普通函式也不一樣,在JavaScript的普通函式中,都會有一個自己的this值,主要分為:普通函式:1、函式作為全域性函式被呼叫時,this指向全域性物件2、函式作為物件中的方法被呼叫時,this指向該物件3、函式作為建構函式的時候,this指向建構函式new出來的新物件4、還可以通過call,apply,bind改變this的指向箭頭函式:1、箭頭函式沒有this,函式內部的this來自於父級最近的非箭頭函式,並且不能改變this的指向。2、箭頭函式沒有super3、箭頭函式沒有arguments4、箭頭函式沒有new.target繫結。5、不能使用new6、沒有原型7、不支援重複的命名引數。

箭頭函式的簡單理解

1、箭頭函式的左邊表示輸入的引數,右邊表示輸出的結果。

const s = a => a
console.log(s(2)) // 2

2、在箭頭函式中,this屬於詞法作用域,直接由上下文確定,對於普通函式中指向不定的this,箭頭函式中處理this無疑更加簡單,如下:

//ES5普通函式
function Man(){
  this.age=22;
  return function(){
    this.age+1;
  }
}
var cala=new Man();
console.log(cala())//undefined

//ES6箭頭函式
function Man(){
  this.age=22;
  return () => this.age+1;
}
var cala=new Man();
console.log(cala())//23

3、箭頭函式中沒有arguments(我們可以用rest引數替代),也沒有原型,也不能使用new 關鍵字,例如:

//沒有arguments
var foo=(a,b)=>{return arguments[0]*arguments[1]}
console.log(foo(3,5))
//arguments is not defined

//沒有原型
var Obj = () => {};
console.log(Obj.prototype); 
// undefined

//不能使用new 關鍵字
var Obj = () => {"hello world"};
var o = new Obj(); 
// TypeError: Obj is not a constructor

4、箭頭函式給陣列排序

const arr = [10, 50, 30, 40, 20]
const s = arr.sort((a, b) => a - b)
console.log(s) // [10,20,30,40,50]

尾呼叫優化尾呼叫是指在函式return的時候呼叫一個新的函式,由於尾呼叫的實現需要儲存到記憶體中,在一個迴圈體中,如果存在函式的尾呼叫,你的記憶體可能爆滿或溢位。

ES6中,引擎會幫你做好尾呼叫的優化工作,你不需要自己優化,但需要滿足下面3個要求:1、函式不是閉包2、尾呼叫是函式最後一條語句3、尾呼叫結果作為函式返回

尾呼叫實際用途——遞迴函式優化在ES5時代,我們不推薦使用遞迴,因為遞迴會影響效能。但是有了尾呼叫優化之後,遞迴函式的效能有了提升。

//新型尾優化寫法
"use strict";  
function a(n, p = 1) {
  if(n <= 1) {
    return 1 * p
  }
  let s = n * p
  return a(n - 1, s)
}
//求 1 x 2 x 3的階乘
let sum = a(3)
console.log(sum) // 6

五、ES6物件新增方法

Object.assign()Object.assign()方法用於將所有可列舉屬性的值從一個或多個源物件複製到目標物件。它將返回目標物件。Object.assign 方法只會拷貝源物件自身的並且可列舉的屬性到目標物件。該方法使用源物件的[[Get]]和目標物件的[[Set]],所以它會呼叫相關 getter 和 setter。因此,它分配屬性,而不僅僅是複製或定義新的屬性。如果合併源包含getter,這可能使其不適合將新屬性合併到原型中。為了將屬性定義(包括其可列舉性)複製到原型,應使用Object.getOwnPropertyDescriptor()和Object.defineProperty() 。String型別和 Symbol 型別的屬性都會被拷貝。合併物件

var o1 = { a: 1 };
var o2 = { b: 2 };
var o3 = { c: 3 };
var obj = Object.assign(o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }
console.log(o1);  // { a: 1, b: 2, c: 3 }, 注意目標物件自身也會改變。

合併具有相同屬性的物件

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };
var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

六、Map和Set

Map和Set都叫做集合,但是他們也有所不同。Set常被用來檢查物件中是否存在某個鍵名,Map集合常被用來獲取已存的資訊。Set是有序列表,含有相互獨立的非重複值。Array和Set對比都是一個儲存多值的容器,兩者可以互相轉換,但是在使用場景上有區別。如下:Array的indexOf方法比Set的has方法效率低下Set不含有重複值(可以利用這個特性實現對一個數組的去重)Set通過delete方法刪除某個值,而Array只能通過splice。兩者的使用方便程度前者更優Array的很多新方法map、filter、some、every等是Set沒有的(但是通過兩者可以互相轉換來使用)Object和Map對比Object是字串-值,Map是值-值Object鍵為string型別,Map的鍵是任意型別手動計算Object尺寸,Map.size可以獲取尺寸Map的排序是插入順序Object有原型,所以對映中有一些預設的鍵。可以理解為Map=Object.create(null)

Set操作集合

let set = new Set()
// Set轉化為陣列
let arr = Array.from(set)
let arr = [...set]
// 例項屬性(繼承自Set)
set.constructor === Set 
set.size 
// 操作方法
set.add(1) // 新增一個值
set.delete(1) //刪除一個值
set.has(1) //判斷是否有這個值(Array中的indexOf)
set.clear() //清除所有值
// 獲取用於遍歷的成員方法(Set的遍歷順序就是插入順序)
set.keys() // 返回鍵名的遍歷器
set.values() // 返回鍵值得遍歷器
set.entries() // 返回鍵值對的遍歷器
set.forEach() // 迴圈遍歷每個值(和Array的方法一致)
for (let key of set.keys()){}
for (let val of set.values()){}
for (let entry of set.entries()){}
// 使用陣列方法來處理set值
set = new Set(arr)
set = new Set([...set].map((x) => x = x * 2))
set = new Set([...set].filter((x) => x > 2))

Map的方法集合

let map = new Map()
// 例項屬性(繼承自Map)
map.constructor === Map
map.size
// 操作方法
map.set(1,2)
map.get(1)
map.delete(1)
map.has(1)
map.clear()
// 遍歷方法
map.keys()
map.values()
map.entries()
map.forEach()
// Map和陣列的轉換
map = new Map([['key','val'],[2,1]]) // 要求雙成員陣列
let arr = [...map]
// 值得注意的是Map的鍵是跟記憶體繫結的
map.set([1], 's')
map.get([1])
let arr = [1]
let arr1 = [1]
map.set(arr, 's')
map.get(arr)
map.set(arr1, 's')
map.get(arr1)

七、迭代器(Iterator)

1、entries() 返回迭代器:返回鍵值對

//陣列
const arr = ['a', 'b', 'c'];
for(let v of arr.entries()) {
  console.log(v)
}
// [0, 'a'] [1, 'b'] [2, 'c']

//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.entries()) {
  console.log(v)
}
// ['a', 'a'] ['b', 'b'] ['c', 'c']

//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.entries()) {
  console.log(v)
}
// ['a', 'a'] ['b', 'b']

2、values() 返回迭代器:返回鍵值對的value

//陣列
const arr = ['a', 'b', 'c'];
for(let v of arr.values()) {
  console.log(v)
}
//'a' 'b' 'c'

//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.values()) {
  console.log(v)
}
// 'a' 'b' 'c'

//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.values()) {
  console.log(v)
}
// 'a' 'b'

3、keys() 返回迭代器:返回鍵值對的key

//陣列
const arr = ['a', 'b', 'c'];
for(let v of arr.keys()) {
  console.log(v)
}
// 0 1 2

//Set
const arr = new Set(['a', 'b', 'c']);
for(let v of arr.keys()) {
  console.log(v)
}
// 'a' 'b' 'c'

//Map
const arr = new Map();
arr.set('a', 'a');
arr.set('b', 'b');
for(let v of arr.keys()) {
  console.log(v)
}
// 'a' 'b'

雖然上面列舉了3種內建的迭代器方法,但是不同集合的型別還有自己預設的迭代器,在for of中,陣列和Set的預設迭代器是values(),Map的預設迭代器是entries()。

for of迴圈解構

物件本身不支援迭代,但是我們可以自己新增一個生成器,返回一個key,value的迭代器,然後使用for of迴圈解構key和value。

const obj = {
  a: 1,
  b: 2,
  *[Symbol.iterator]() {
    for(let i in obj) {
      yield [i, obj[i]]
    }
  }
}
for(let [key, value] of obj) {
  console.log(key, value)
}
// 'a' 1, 'b' 2

字串迭代器

const str = 'abc';
for(let v of str) {
  console.log(v)
}
// 'a' 'b' 'c'

ES6給陣列添加了幾個新方法:find()、findIndex()、fill()、copyWithin()

1、find():傳入一個回撥函式,找到陣列中符合當前搜尋規則的第一個元素,返回它,並且終止搜尋。

const arr = [1, "2", 3, 3, "2"]
console.log(arr.find(n => typeof n === "number")) // 1

2、findIndex():傳入一個回撥函式,找到陣列中符合當前搜尋規則的第一個元素,返回它的下標,終止搜尋。

const arr = [1, "2", 3, 3, "2"]
console.log(arr.findIndex(n => typeof n === "number")) // 0

3、fill():用新元素替換掉陣列內的元素,可以指定替換下標範圍。

arr.fill(value, start, end)

4、copyWithin():選擇陣列的某個下標,從該位置開始複製陣列元素,預設從0開始複製。也可以指定要複製的元素範圍。

arr.copyWithin(target, start, end)

const arr = [1, 2, 3, 4, 5]
console.log(arr.copyWithin(3)) // [1,2,3,1,2] 從下標為3的元素開始,複製陣列,所以4, 5被替換成1, 2

const arr1 = [1, 2, 3, 4, 5]
console.log(arr1.copyWithin(3, 1)) // [1,2,3,2,3] 從下標為3的元素開始,複製陣列,指定複製的第一個元素下標為1,所以4, 5被替換成2, 3

const arr2 = [1, 2, 3, 4, 5]
console.log(arr2.copyWithin(3, 1, 2)) // [1,2,3,2,5] 從下標為3的元素開始,複製陣列,指定複製的第一個元素下標為1,結束位置為2,所以4被替換成2

ES6中類class、Promise與非同步程式設計、代理(Proxy)和反射(Reflection)API,這幾塊內容比較複雜,以後有機會再詳細寫。

PS: 寫的太匆忙了,難免有錯漏的地方。