ES2015新特性

ECMAScript概述

JavaScript = ECMAScript + 運行環境的API
其中,運行環境包括瀏覽器和Node環境

ES2015(ES6來泛指ES2015之後的新標準)新特性

解決原有用語法上的不足

let、const與塊級作用域

  • let、const聲明的變量只在{}包裹的塊級作用域內有效
// 將會打印出三次foo;
for(let i = 0; i < 3; i ++) {
  let i = 'foo';
  console.log(i);
}
// 這段代碼相當於如下
// 第一步,聲明循環變量
let i = 0;
// 第二步,判斷循環條件,執行循環體,由於循環體是一個塊級作用域,因此與循環變量儘管同名,但在循環體內只有一個i變量;
if(i < 3) {
  let i = 'foo';
  console.log(i);
}
// 第三步,循環變量的值增加
i ++;
  • 不會有作用域提升效果,也就是隻有執行到聲明語句時,纔有這個變量;
  • const用來聲明常量值,意味着該變量只讀;對於引用類型而言,該常量值爲引用地址,只要不更改引用地址就行;

在程序中,不用var,主要用const,再配合let

for of 遍歷

作爲遍歷所有數據結構的統一方式,在Iterator對象調用next方法後返回的結果對象中如果done屬性爲false,則執行循環體,否則退出循環。

  • 可以在遍歷過程中使用break關鍵字終止循環
  • 可以遍歷數組、類數組集合、Set、Map等等,但不能遍歷對象,原因在於對象沒有實現默認的Iterator接口。
  • 對Map數據結構的遍歷時,得到的元素是鍵值對構成的二元數組,因此可以使用數組解構賦值的方式來使用鍵值對。

對原有語法的增強

解構賦值

更加簡潔地從一個複雜的數據結構中提取值,包括數組、對象這些數據結構。

數組解構

將一個已知數組中的值賦值給變量時,爲了提升效率,不再利用索引一個一個賦值,而是一次性賦值。
解構時要根據位置(即數字索引)來賦值。

  • 支持使用…擴展符解構
  • 支持在解構時,爲變量提供默認值
const arr = [1, 2, 3];
// 原始做法
const foo = arr[0];
const bar = arr[1];
const baz = arr[2];
// 解構做法
const [foo, bar, baz] = arr; // foo = 1, bar = 2, baz = 3;
// 或者只賦值某一部分
const [, , baz] = arr; // baz = 3; 
// 使用...擴展符解構,...rest這種方式只能用於數組末尾
const [foo, ...rest] = arr; // foo = 1, rest = [2, 3];
// 解構時使用默認值,如果解構賦值時沒有得到值,則使用默認值
const [foo, bar, baz, more = 'default'] = arr; // more = 'default'; 
對象解構

使用屬性名(索引)來解構賦值。

const obj = { name: 'mm', age: 18 };
const { name: foo = 'ff', age: bar } = obj; // foo = 'mm', bar = 18, 其中foo = 'ff'爲默認賦值

模板字符串

使用``來標識字符串

  • 支持在字符串中加入換行符(而不是用\n)
  • 支持插值表達式,不需要再用醜陋的拼接方式
  • 支持標籤函數,標籤函數用於對模板字符串進行提取,並進行額外加工;
const str = console.log`hello world`; // [ 'hello world' ];
const name = 'tom';
const gender = true;
// 自定義標籤函數
// 標籤函數接收的第一個參數爲,模板字符串被插值表達式分隔而成的字符串數組
// 標籤函數接收的其他參數爲,模板字符串中插值表達式的值
function myTag(strings, name, gender) {
  console.log(strings, name, gender); // strings = ['hello, ', ' is a ', '.'], name = 'tom', gender = true;
  const sex = gender ? 'man': 'woman';
  return strings[0] + name + strings[1] + sex + strings[2]; // 'hello, tom is a men.';
}
const result = myTag`hello, ${name} is a ${gender}.`;

字符串方法擴展

  • includes(str):判斷字符串是否包含str
  • startsWith(str):判斷字符串是否以str開頭
  • endsWith(str):判斷字符串是否以str結尾

函數擴展

參數默認值
  • 帶默認值的參數要放在函數形參的末尾
剩餘參數

使用擴展符…rest作爲形參來接收函數的實參,其中rest爲數組,剩餘參數…rest要放在形參的末尾

使用擴展符來傳遞實參
const arr = ['foo', 'baz', 'bar'];
// 通過...arr傳遞實參
console.log(...arr);
箭頭函數

即lambda表達式形式

  • 簡化了回調函數的編寫
  • 突出了函數的本質(類型到類型的映射關係,適用於函數式編程範式)
  • 不會改變this指向,箭頭函數內部沒有this機制,因此箭頭函數體的this綁定的是函數上下文中的this,即箭頭函數定義的詞法作用域中的this;在使用保存this指向的場景下,都可以使用箭頭函數來簡化
const person1 = {
  name: 'tom',
  sayHi: () => {
    setTimeout(function() {
      console.log(this.name); 
    }, 1000)
  }
}
person1.sayHi(); // undefined;

const person2 = {
  name: 'tom',
  // 注意在方法的定義上,不要使用箭頭函數,否則內部的this找不到
  sayHi: function() {
    // 使用_this來保存this指向,通過閉包機制訪問到this.name;
    let _this = this;
    setTimeout(function() {
      console.log(_this.name); 
    }, 1000)
  }
}
person2.sayHi(); // tom;

const person3 = {
  name: 'tom',
  sayHi: function() {
    setTimeout(() => {
      console.log(this.name); 
    }, 1000)
  }
}
person3.sayHi(); // tom;

對象增強

動態屬性名即計算屬性名

在對象用字面量方式定義時,可以利用[表達式]的方式去定義動態屬性名,稱之爲計算屬性名

對象擴展方法
  • Object.assign(target, source1, source2, …rest):將source1、source2等對象的屬性複製到target對象,並返回target對象。同名屬性將會被覆蓋,覆蓋以從右向左的順序。注意:淺複製
    • 一般常用於更新對象,但不修改源對象的非侵入式修改,適合函數式編程範式。
    • 這也是JavaScript擴展對象的一種方式 Mixin
    • assign方法無法複製源對象的訪問器屬性,該方法會將訪問器屬性執行之後,將執行結果作爲數據屬性複製到目標對象上。
  • Object.is(value1, value2): 比較兩個值是否全等。主要是爲了補充+0與-0、NaN與NaN的比較。因爲在符號===的情況下,與Object.is方法剛好相反的。
    • Object.is(+0, -0); // false
    • Object.is(NaN, NaN); // true
  • Object.keys(obj):返回目標對象obj所有可枚舉的屬性名組成數組
  • Object.getOwnPropertyNames(obj):返回目標對象obj不考慮可枚舉性的屬性名組成的數組
  • Object.getOwnPropertySymbols(obj):返回目標對象obj所有Symbol類型的屬性名組成的數組

全新的對象及其API

Proxy代理對象

用處

用於攔截程序中對對象的訪問、設置等各種行爲,使用代理對象對這些行爲進行響應。每一種特定的行爲都有相應的陷阱來攔截該行爲,並指定對應的處理程序來響應該行爲。響應程序可以自定義,也可以使用Reflect API來使用Proxy默認的響應行爲。
通過Proxy代理之後,對該對象的操作轉而需要通過Proxy來進行。

構造
const person = {
  name: 'tom',
  age: 18
}
// new Proxy(target, options)傳入代理的目標對象和代理配置對象,配置對象內部定義了各種攔截器
const personProxy = new Proxy(person, {
  // 攔截訪問對象屬性值的行爲,target爲要訪問的目標對象,property爲要訪問的屬性名
  get (target, property) {
    // 不管訪問目標對象的什麼屬性,一律返回100; 這裏就是自定義的響應;
    return 100;
  }
  // 攔截設置對象屬性值的行爲,增加校驗行爲
  set (target, property, value) {
    // 可以針對age屬性做一下校驗
    if(property === 'age') {
      if(!Number.isInteger(value)) {
        throw new TypeError(`${value} is not an Integer!`);
      }
      target[property] = value;
    }
    target[property] = value;
  }
})
// 訪問person.name的行爲轉而變爲訪問personProxy.name的行爲;
const result = peronProxy.name; // 100;
與Object.defineProperty的對比
  • Object.defineProperty只能攔截對屬性的讀寫操作,Proxy可以攔截更多的操作,這取決於Proxy內部的陷阱函數
  • 監視數組的操作
    • Object.defineProperty,如Vue中是通過重寫數組的操作方法來攔截
    • Proxy 監視數組的操作更加靈活
  • 非侵入式攔截對象操作
const list = [];
const listProxy = new Proxy(list, {
  set(target, property, value) {
    console.log('set', property, value);
    target[property] = value;
    return true; // 表示寫入成功
  }
})
// push操作可以被Proxy的set陷阱攔截,並且property自動更新;但如果是利用Object.defineProperty時,就需要重寫數組的push方法了,原來的push方法無法被攔截到;
list.push(66); // set, 0, 66;
list.push(88); // set, 1, 88;
Reflect

靜態類,封裝一系列的對對象的底層操作API,是Proxy對象攔截行爲的默認響應,每一個API都與Proxy中的陷阱函數相對應。我們在定義Proxy中的陷阱函數時,可以先自定義好需要的邏輯,然後使用Reflect的API將默認響應返回。
Reflect的意義在於,統一了針對對象的各種操作,不管是利用Object的靜態方法還是實例方法,還是一些操作符如in, has等,都可以通過Reflect API找到對應的方法;Reflect API可以不和Proxy一起使用,也可以單獨使用,目的是取代之前定義的對象操作行爲,統一經過Reflect API來操作。

class類

靜態成員

使用static關鍵字聲明靜態成員,內部的this指向類本身(構造函數)

繼承

使用extends關鍵字實現類型繼承

  • 在子類構造函數中需要先調用super()函數初始化this值,才能在之後的代碼中使用this。super引用父類構造函數,是構造函數借用。

Iterator對象

用於提供統一的迭代數據結構的接口,是for of 實現遍歷的前提。也就是說,只有數據結構實現了Iterator接口,才能夠被for of方式來遍歷。
數據結構如果實現了[Symbol.iterator]屬性(屬性值爲函數),該函數返回一個Iterator對象。Iterator對象的next()方法用來獲取數據結構中的下一個元素。這也是for of 方式實現遍歷的原理。

爲對象實現Iterator接口

只要實現了Iterator接口,就可以使用for of 遍歷;

const obj {
  store: ['foo', 'bar', 'baz'],
  // 實現iterable接口,用於返回Iterator對象
  [Symbol.iterator]: function() {
    const self = this; // 保存對象的引用;
    let index = 0;
    // 實現Iterator對象,用於迭代對象內部的值
    return {
      next: function() {
        const result = {
          value: self.store[index],
          done: index >= self.store.length
        }
        index ++;
        return result;
      }
    }
  }
}
迭代器模式管理數據結構的遍歷

Iterator對象是爲了實現迭代器模式而創建。迭代器模式是爲了將遍歷代碼與數據結構解耦,統一使用迭代器來進行遍歷。這樣如果數據結構頻繁變化時,可以只修改迭代器即可,遍歷代碼改動很小。也就是說,迭代器模式將數據結構與遍歷代碼搭建了一個統一的橋樑

Generator生成器

意義

提供更好的異步編程寫法

yield關鍵字
使用generator實現Iterator
const obj {
  store: ['foo', 'bar', 'baz'],
  // 實現iterable接口,用於返回Iterator對象,這裏使用了生成器函數
  [Symbol.iterator]: function* () {
      for(const item of this.store) {
        yield item;
      }
  }
}
redux-saga使用generator寫法

全新的數據類型

Set數據結構

數據集合,其中的數據不重複。

構造

const set = new Set();
操作方法(均爲實例方法)
  • add(item):增加一個元素item到Set
  • forEach((item, index) => {}):遍歷Set中的元素
  • size屬性:返回Set中元素的數量
  • has(item):判斷Set中item是否存在
  • delete(item):刪除Set中item元素
  • clear():清理Set所有元素
WeakSet

Map數據結構(對象的增強或補充)

key-value數據集合,Map與對象的區別是,key可以是任意數據類型,而對象的key只能是字符串(其他數據類型會被隱式轉換爲字符串類型);

構造
const map = new Map();
操作方法(均爲實例方法)
  • get(key)
  • set(key, value)
  • forEach((value, key) => {})
  • 其他類似於對象的方法,如keys()、values()、entries()等
WeakMap

弱引用集合。

  • WeakMap的鍵必須是一個對象,使用非對象的鍵會報錯
  • 集合中保存的是這些對象的弱引用,如果在弱引用之外的不存在其他的強引用,則垃圾回收機制會將弱引用對象收回,同時從WeakMap中刪除對應的鍵值對。
  • 最大的用於是保存DOM元素。當DOM元素從頁面移除時(外部強引用不存在了),WeakMap內部對應的DOM元素也會被垃圾回收機制自動清除。

Symbol類型

意義
  • 創建獨一無二的值const s = Symbol(); // 不能使用new創建,而是直接用Symbol(),每次使用Symbol()創建出來的值都不相同。
    • 創建私有屬性
    • 避免第三方庫的命名衝突
創建
  • Symbol(str)傳入可選的str字符串,用於描述這個Symbol類型的值。描述文本有利於開發調試,讓開發者知道該值的相關信息。
  • Symbol的描述文本存儲在內部的[[ Descriptor ]]屬性,只有調用Symbol的toString()方法纔可以讀取到這個內部屬性。使用console.log()時隱式調用了toString()方法。
const s1 = Symbol('name');
console.log(s1); // "Symbol('name')"
類型判斷

可以使用typeof操作符來判斷一個值是否是Symbol類型

const s1 = Symbol('symbol test');
console.log(typeof s1); // 'symbol'
對象私有屬性的創建
  • 對象的屬性名現在可以接受string類型和Symbol類型了
  • 通過Symbol類型可以創建對象的私有屬性了,因爲外部無法找到相同的Symbol類型值。
    const name = Symbol(); // name變量中保存了新創建的Symbol類型值,但name變量不暴露給外部即可實現私有屬性
    const obj = {
      [name]: 'foo',
      say() {
        console.log(this.[name]);
      }
    }
    
Symbol共享體系

在代碼中查找所創建的Symbol值。全局Symbol註冊表是一個類似全局作用域的共享體系,同樣需要注意避免衝突。

Symbol.for(str):
  • 用於查找描述爲str的Symbol值並返回該Symbol類型的值。如果沒找到,則使用str參數創建一個Symbol值並返回。
  • 要求傳入str是字符串類型,如果不是則隱式轉換爲字符串類型。
  • 爲了查找Symbol值,其內部維護了一個描述文本與值的全局Symbol註冊表,我們可以通過描述文本找到對應的Symbol值。
Symbol.keyFor(symbol):
  • 用於查找symbol對應的描述文本。與Symbol.for()方法相反。
  • symbol參數要求是一個Symbol值。
  • 如果查找不到,則返回undefined。
Symbol與類型轉換
  • Symbol類型無法轉換爲字符串類型與數字類型,調用toString()只是返回該值的字符串描述而已。如果Symbol類型與字符串作拼接或參與數學運算都會報錯。
  • Symbol可以參與邏輯運算,因爲Symbol等價布爾值爲true
Symbol公共屬性來暴露一些JavaScript內部邏輯

Symbol內置了一些常量屬性來暴露JavaScript的內部邏輯,同時也指定了這些操作的接口。不同的數據類型對這些方法實現情況不同,有的時候需要自定義。如對象類型並沒有[Symbol.iterator]接口。開發者可以根據情況對這些接口進行改寫,這一方式也被稱爲元編程。

Symbol.iterator // 返回迭代器接口的方法
Symbol.hasInstance // 使用instanceof操作符時調用的方法
Symbol.match // 在調用String.prototype.match()方法時調用的方法,用於比較字符串是否匹配子串
Symbol.replace // 在調用String.prototype.replace()方法時調用的方法,用於替換字符串的子串
Symbol.search // 在調用String.prototype.search()方法時調用的方法,用於查詢字符串的子串
Symbol.split // 在調用String.prototype.split()方法時調用的方法,用於分隔字符串
Symbol.isConcatSpreadable // 布爾值,用於表示當傳遞一個集體到Array.prototype.concat()方法的參數時,是否應該將集合內的元素規整到同一層級
Symbol.toPrimitive // 一個返回對象對應的原始類型的值的方法,即有時候特定的操作會觸發將對象轉換爲原始類型的值。
Symbol.toStringTag // 一個在調用Object.prototype.toString()方法時使用的字符串,用於創建對象描述,對象描述指的是[object object]、[object array]之類的。如果定義[Symbol.toStringTag]: value, 則返回的對象描述爲[object value]
Symbol.unscopables // 一個定義了一些不可被with語句引用的對象屬性名稱的對象集合
獲取對象內部的Symbol類型屬性名

使用Object.getOwnPropertySymbols(obj); // 傳入目標對象,返回該對象的Symbol類型的屬性名來獲取目標對象的Symbol類型的屬性名;
使用傳統的for in循環或者Object.keys(obj)只能獲取到字符串類型的屬性名;
同時,使用JSON.stringify(obj)也不會將Symbol類型屬性序列化;

ES Modules

ES2016

Array.prototype.includes

鑑於Array.prototype.indexOf(NaN)無法判斷數組中的NaN元素,因此擴展出了includes方法

指數運算符

原來:Math.pow(2, 10); // 2^10
現在:2 ** 10;

ES2017

Async/Await寫法

Object的擴展方法

Object.values(obj)

返回目標對象obj屬性值組成數組

Object.entries(obj)

返回目標對象obj鍵值對組成的數組,其中鍵值對也是一個二元數組。

Object.getOwnPropertyDescriptors(obj)

返回目標對象obj上所有屬性以及對應的屬性描述符對象,其結構如下:

{
  屬性名1: 屬性描述符對象,
  屬性名2: 屬性描述符對象,
  ...
}

字符串填充

爲給定的字符串在開始處(padStart)或結束處(padEnd)填充指定的字符串,直到填充的字符數達到了指定的字符數。

String.prototype.padStart(num, str)

在給定字符串的開始處填充str(可能會反覆填充str)直到填充後的字符串長度達到num,返回填充後的字符串;

String.prototype.padEnd(num, str)

在給定字符串的結尾處填充str(可能會反覆填充str)直到填充後的字符串長度達到num,返回填充後的字符串;

函數最後一個形式參數後添加逗號

function func(arg1, arg2, arg3,) {
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章