天天看點

ES2015新特性ECMAScript概述ES2015(ES6來泛指ES2015之後的新标準)新特性ES2016ES2017

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結尾

函數擴充

參數預設值
  • 帶預設值的參數的重要特點是與函數内部的arguments對象分離,也就是說,帶預設值的參數不再與arguments對象相等。
function(first, second = '2') {
	console.log(first === arguments[0]) // true
	console.log(second === arguments[1]) // false
}
           
剩餘參數

使用擴充符…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;

           
塊級函數

在語句塊中聲明的函數。

在ES5中,這樣聲明函數會報錯,因為沒有塊級作用域

在ES6中,可以聲明塊級函數,其中,在塊級作用域中,函數聲明會被提升到頂部。一旦代碼塊執行完畢,該塊級函數就會被銷毀。

對象增強

動态屬性名即計算屬性名

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

對象擴充方法
  • 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資料結構

資料集合,其中的資料不重複。

構造

操作方法(均為執行個體方法)
  • 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隻能是字元串(其他資料類型會被隐式轉換為字元串類型);

構造
操作方法(均為執行個體方法)
  • 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,) {
}
           

繼續閱讀