個人前端知識梳理-ES6篇

本篇記錄部分es6相關內容和我的理解。javascript

======================相關文章和開源庫=======================前端

系列文章vue

1.前端知識梳理-HTML,CSS篇
java

2.前端知識梳理-ES5篇
node

3.前端知識梳理-ES6篇
es6

4.前端知識梳理-VUE篇
sql

我的維護的開源組件庫編程

1.bin-ui,一個基於vue的pc端組件庫json

2.樹形組織結構組件
數組

3.bin-admin ,基於bin-ui的後臺管理系統集成方案

4.bin-data ,基於bin-ui和echarts的數據可視化框架

5.其他生態連接

========================================================

一、let,const

var 會變量提高;let 聲明的變量只在它所在的代碼塊有效;

它們是繼 var 以後, 新的變量定義方法。與 let 相比,const 更容易被理解,用於定義常量,即不可變量

二、塊級做用域

沒有塊級做用域。這個問題之因此爲人所熟知,是由於它引起了諸如歷遍監聽事件須要使用閉包解決等問題。

<button>一</button>
<button>二</button>
<button>三</button>
<button>四</button>
<div id="output"></div>
<script>
  var buttons = document.querySelectorAll('button')
  var output = document.querySelector('#output')
  for (var i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener('click', function() {
      output.innerText = buttons[i].innerText
    })
  }
</script>
複製代碼

從直觀的角度看這段代碼並無語義上的錯誤,可是當咱們點擊任意一個按鈕時,就會報出這樣的錯誤信息:

出現這個錯誤的緣由是由於 buttons[i] 不存在,即爲 undefined。

爲何會出現按鈕不存在結果呢?經過排查,咱們能夠發現,每次咱們點擊按鈕時,事件監聽回調函數中獲得的變量 i 都會等於 buttons.length, 也就是這裏的 4。而 buttons[4] 偏偏不存在,因此致使了錯誤的發生。

再而致使 i 獲得的值都是 buttons.length 的緣由就是由於 JavaScript 中沒有塊級做用域,而使對 i 的變量引用(Reference)一直保持在上一層做用域(循環語句所在層)上, 而當循環結束時 i 則正好是buttons.length。

而在 ES6 中,咱們只需作出一個小小的改動,即可以解決該問題(假設所使用的瀏覽器已經支持所須要的特性):

for (/* var */ let i = 0; i < buttons.length; i++) {
  // ...
}
複製代碼

經過把 for 語句中對計數器 i 的定義語句從 var 換成 let,便可。由於 let 語句會使該變量處於一個塊級做用域中, 從而讓事件監聽回調函數中的變量引用獲得保持。咱們不妨看看改進後的代碼通過 babel 的編譯會變成什麼樣子:

// ...
var _loop = function (i) {
  buttons[i].addEventListener('click', function () {
    output.innerText = buttons[i].innerText
  })
}

for (var i = 0; i < buttons.length; i++) {
  _loop(i)
}
// ...
複製代碼

三、箭頭函數(Arrow Function)

繼 let 和 const 以後,箭頭函數就是使用率最高的新特性了。

箭頭函數,顧名思義即是使用箭頭(=>)進行定義的函數,屬於匿名函數(Lambda)一類。

箭頭函數有好幾種使用語法:

// 1.means return `foo + ' world'`
foo => foo + ' world' 
// 2.  
(foo, bar) => foo + bar
// 3. 
foo => {
  return foo + ' world'
}
// 4.
(foo, bar) => {
  return foo + bar
}
複製代碼

箭頭函數與上下文綁定

事實上,箭頭函數在 ES2015 標準中,並不僅是做爲一種新的語法出現。用於對函數內部的上下文 (this)綁定爲定義函數所在的做用域的上下文。

let obj = {
  hello: 'world',
  foo() {
    let bar = () => {
      return this.hello
    }
    return bar
  }
}
window.hello = 'ES6'
window.bar = obj.foo()
window.bar() //=> 'world'
複製代碼

上面代碼中的 obj.foo 等價於:

// ...
foo(){
  let bar = (function() {
    return this.hello
  }).bind(this)

  return bar
}
// ...
複製代碼

四、模板字符串

它的出現可讓很是多的字符串使用變得尤其輕鬆。

模板字符串要求使用 ` 代替本來的單/雙引號來包裹字符串內容。它有兩大特色:

  • 支持變量注入
  • 支持換行

支持變量注入

模板字符串之因此稱之爲「模板」,就是由於它容許咱們在字符串中引用外部變量,而不須要像以往須要不斷地相加、相加、相加……

let name = 'Will Wen Gunn'
let title = 'Founder'
let company = 'LikMoon Creation'

let greet = `Hi, I'm ${name}, I am the ${title} at ${company}` 複製代碼

支持換行

在 Node.js 中,若是咱們沒有支持換行的模板字符串,若須要拼接一條SQL,則頗有多是這樣的:

var sql =
  "SELECT * FROM Users " +
  "WHERE FirstName='Mike' " +
  "LIMIT 5;"
複製代碼

或者是這樣的:

var sql = [
  "SELECT * FROM Users",
  "WHERE FirstName='Mike'",
  "LIMIT 5;"
].join(' ')
複製代碼

不管是上面的哪種,都會讓咱們感到很不爽。但若使用模板字符串,彷彿打開了新世界的大門~

let sql = ` SELECT * FROM Users WHERE FirstName='Mike' LIMIT 5; `
複製代碼

五、對象字面量擴展語法

方法屬性省略 function

這個新特性能夠算是比較有用但並非很顯眼的一個。

let obj = {
  // before
  foo: function() {
    return 'foo'
  },
  // after
  bar() {
    return 'bar'
  }
}
複製代碼

六、表達式解構

來了來了來了,至關有用的一個特性。

// Matching with object
function search(query) {
  // ...
  // let users = [ ... ]
  // let posts = [ ... ]
  // ...
  return {
    users: users,
    posts: posts
  }
}
let { users, posts } = search('iwillwen')
// Matching with array
let [ x, y ] = [ 1, 2 ]
// missing one
[ x, ,y ] = [ 1, 2, 3 ]
function g({name: x}) {
  console.log(x)
}
g({name: 5})
複製代碼

七、函數參數表達、傳參

這個特性有很是高的使用頻率,一個簡單的語法糖解決了從前須要一兩行代碼才能實現的功能。

默認參數值

這個特性在類庫開發中至關有用,好比實現一些可選參數:

import fs from 'fs'
import readline from 'readline'
import path from 'path'
function readLineInFile(filename, callback = noop, complete = noop) {
  let rl = readline.createInterface({
    input: fs.createReadStream(path.resolve(__dirname, filename))
  })
  rl.on('line', line => {
    //... do something with the current line
    callback(line)
  })
  rl.on('close', complete)
  return rl
}
function noop() { return false }
readLineInFile('big_file.txt', line => {
  // ...
})
複製代碼

後續參數

咱們知道,函數的 call 和 apply 在使用上的最大差別即是一個在首參數後傳入各個參數,一個是在首參數後傳入一個包含全部參數的數組。 若是咱們在實現某些函數或方法時,也但願實現像 call 同樣的使用方法,在 ES2015 以前,咱們可能須要這樣作:

function fetchSomethings() {
  var args = [].slice.apply(arguments)
  // ...
}
function doSomeOthers(name) {
  var args = [].slice.apply(arguments, 1)
  // ...
}
複製代碼

而在 ES2015 中,咱們能夠很簡單的使用 … 語法糖來實現:

function fetchSomethings(...args) {
  // ...
}
function doSomeOthers(name, ...args) {
  // ...
}
複製代碼

要注意的是,...args 後不可再添加

雖然從語言角度看,arguments ...args 是能夠同時使用 ,但有一個特殊狀況則不可:arguments 在箭頭函數中, 會跟隨上下文綁定到上層,因此在不肯定上下文綁定結果的狀況下,儘量不要再箭頭函數中再使用 arguments,而使用 ...args

雖然 ECMA 委員會和各種編譯器都無強制性要求用 ...args 代替 arguments,但從實踐經驗看來,...args 確實能夠在絕大部份場景下能夠代替 arguments 使用, 除非你有很特殊的場景須要使用到 arguments.calleearguments.caller。因此我推薦都使用 ...args 而非 arguments

解構傳參

在 ES2015 中,... 語法還有另一個功能:無上下文綁定的 apply。什麼意思?看看代碼你就知道了。

function sum(...args) {
  return args.map(Number)
    .reduce((a, b) => a + b)
}
console.log(sum(...[1, 2, 3])) //=> 6
複製代碼

八、新的數據結構

ES2015 以前,JavaScript 中有哪些基本的數據結構。

  • String 字符串
  • Number 數字(包含整型和浮點型)
  • Boolean 布爾值
  • Object 對象
  • Array 數組

其中又分爲值類型引用類型,Array 實際上是 Object 的一種子類。

Set 和 WeakSet

在 ES2015 中,ECMA 委員會爲 ECMAScript 增添了集(Set)和「弱」集(WeakSet)。它們都具備元素惟一性,若添加了已存在的元素,會被自動忽略。

let s = new Set()
s.add('hello').add('world').add('hello')
console.log(s.size) //=> 2
console.log(s.has('hello')) //=> true
複製代碼

在實際開發中,咱們有不少須要用到集的場景,如搜索、索引創建等。

WeakSet 在 JavaScript 底層做出調整(在非降級兼容的狀況下),檢查元素的變量引用狀況。若是元素的引用已被所有解除,則該元素就會被刪除, 以節省內存空間。這意味著沒法直接加入數字或者字符串。另外 WeakSet 對元素有嚴格要求,必須是 Object,固然了,你也能夠用 new String('...') 等形式處理元素。

let weaks = new WeakSet()
weaks.add("hello") //=> Error
weaks.add(3.1415) //=> Error

let foo = new String("bar")
let pi = new Number(3.1415)
weaks.add(foo)
weaks.add(pi)
weaks.has(foo) //=> true
foo = null
weaks.has(foo) //=> false
複製代碼

Map 和 WeakMap

從數據結構的角度來講,映射(Map)跟本來的 Object 很是類似,都是 Key/Value 的鍵值對結構。 可是 Object 有一個讓人很是不爽的限制:key 必須是字符串或數字。在通常狀況下,咱們並不會趕上這一限制,但若咱們須要創建一個對象映射表時,這一限制顯得尤其棘手。

而 Map 則解決了這一問題,可使用任何對象做爲其 key,這能夠實現從前不能實現或難以實現的功能,如在項目邏輯層實現數據索引等。

map.set(object, 'hello')
map.set('hello', 'world')
map.has(object) //=> true
map.get(object) //=> hello
複製代碼

而 WeakMap 和 WeakSet 很相似,只不過 WeakMap 的鍵和值都會檢查變量引用,只要其一的引用全被解除,該鍵值對就會被刪除。

let weakm = new WeakMap()
let keyObject = { id: 1 }
let valObject = { score: 100 }

weakm.set(keyObject, valObject)
weakm.get(keyObject) //=> { score: 100 }
keyObject = null
weakm.has(keyObject) //=> false
複製代碼

九、類(Classes)

類,做爲自 JavaScript 誕生以來最大的痛點之一,終於在 ES2015 中獲得了官方的妥協,「實現」了 ECMAScript 中的標準類機制。 爲何是帶有雙引號的呢?由於咱們不難發現這樣一個現象:

// $ node
> class Foo {}
// [Function: Foo]
複製代碼

回想一下在 ES2015 之前的時代中,咱們是怎麼在 JavaScript 中實現類的?

function Foo() {}
var foo = new Foo()
複製代碼

是的,ES6 中的類只是一種語法糖,用於定義原型(Prototype)的。

語法

與大多數人所期待的同樣,ES2015 所帶來的類語法確實與不少 C 語言家族的語法類似。

class Person {
  constructor(name, gender, age) {
    this.name = name
    this.gender = gender
    this.age = age
  }
  isAdult() {
    return this.age >= 18
  }
}
let me = new Person('iwillwen', 'man', 19)
console.log(me.isAdult()) //=> true
複製代碼

與 JavaScript 中的對象字面量不同的是,類的屬性後不能加逗號,而對象字面量則必需要加逗號。

然而,讓人很不爽的是,ES2015 中對類的定義依然不支持默認屬性的語法:

// 理想型 可是沒實現
class Person {
  name: String
  gender = 'man'
  // .
}複製代碼

繼承

ES2015 的類繼承總算是爲 JavaScript 類繼承之爭拋下了一根定海神針了。

class Animal {
  yell() {
    console.log('yell')
  }
}
class Person extends Animal {
  constructor(name, gender, age) {
    super() // must call `super` before using `this` if this class has a superclass

    this.name = name
    this.gender = gender
    this.age = age
  }

  isAdult() {
    return this.age >= 18
  }
}

class Man extends Person {
  constructor(name, age) {
    super(name, 'man', age)
  }
}
let me = new Man('iwillwen', 19)
console.log(me.isAdult()) //=> true
me.yell()
複製代碼

一樣的,繼承的語法跟許多語言中的很相似,ES2015 中若要是一個類繼承於另一個類而做爲其子類,只須要在子類的名字後面加上 extends {SuperClass} 便可。

ES2015 中的類機制支持 static 類型的方法定義,好比說 Man 是一個類,而我但願爲其定義一個 Man.isMan() 方法以用於類型檢查,咱們能夠這樣作:

class Man {
  // ...
  static isMan(obj) {
    return obj instanceof Man
  }
}
let me = new Man()
console.log(Man.isMan(me)) //=> true
複製代碼

就目前來講,ES2015 的類機制依然很雞肋:

1.不支持私有屬性(private)

2.不支持前置屬性定義,但可用 get 語句和 set 語句實現

3.不支持多重繼承

4.沒有相似於協議(Protocl)或接口(Interface)等的概念

十、原生的模塊化

在 JavaScript 的發展歷史上,曾出現過多種模塊加載庫,如 RequireJS、SeaJS、FIS 等,而由它們衍生出來的 JavaScript 模塊化標準有 CommonJS、AMD、CMD 和 UMD 等。

其中最爲典型的是 Node.js 所遵循的 CommonJS 和 RequireJS 的 AMD。

基本用法

import name from "module-name"
import * as name from "module-name"
import { member } from "module-name"
import { member as alias } from "module-name"
import { member1 , member2 } from "module-name"
import { member1 , member2 as alias2 , [...] } from "module-name"
import defaultMember, { member [ , [...] ] } from "module-name"
import defaultMember, * as alias from "module-name"
複製代碼

如上所示,ES2015 中有不少種模塊引入方式,咱們能夠根據實際須要選擇一種使用。

全局引入

全局引入是最基本的引入方式,這跟 CommonJS、AMD 等模塊化標準並沒有兩樣,都是把目標模塊的全部暴露的接口引入到一個命名空間中。

import name from 'module-name'
import * as name from 'module-name'
複製代碼

這跟 Node.js 所用的 CommonJS 相似: var name = require('module-name')

局部引入

與 CommonJS 等標準不一樣的是,ES2015 的模塊引入機制支持引入模塊的部份暴露接口,這在大型的組件開發中顯得尤其方便,如 React 的組件引入即是使用了該特性。

import { A, B, C } from 'module-name'
A()
B()
C()複製代碼

接口暴露

ES2015 的接口暴露方式比 CommonJS 等標準都要豐富和健壯,可見 ECMA 委員會對這一部份的重視程度之高。

ES2015 的接口暴露有幾種用法:

暴露單獨接口

// module.js
export function method() { /* ... */ }複製代碼

基本的 export 語句能夠調用屢次,單獨使用可暴露一個對象到該模塊外。

暴露覆蓋模塊

若須要實現像 CommonJS 中的 module.exports = {} 以覆蓋整個模塊的暴露對象,則須要在 export 語句後加上 default。

// module.js
export default {
  method1,
  method2
}
// main.js
import M from './module'
M.method1()複製代碼

十一、Promise

Promise的含義

Promise是異步編程的一種解決方案,比傳統的解決方案(回調函數和事件)更合理更強大。

所謂Promise,簡單說就是一個容器,裏面保存着某個將來纔會結束的事件 (一般是一個異步操做)的結果。從語法上說,Promise是一個對象,從它能夠獲取異步操做的消息。

Promise對象有如下2個特色:

1.對象的狀態不受外界影響。Promise對象表明一個異步操做,有三種狀態:Pending(進行中)、 Resolved(已完成)和Rejected(已失敗)。只有異步操做的結果,能夠決定當前是哪種狀態, 任何其餘操做都沒法改變這個狀態。這也是Promise這個名字的由來,它的英語意思就是「承諾」, 表示其餘手段沒法改變。

2.一旦狀態改變,就不會再變,任什麼時候候均可以獲得這個結果。Promise對象的狀態改變,只有兩種可能: 從Pending變爲Resolved;從Pending變爲Rejected。只要這兩種狀況發生,狀態就凝固了,不會再變了, 會一直保持這個結果。就算改變已經發生了,你再對Promise對象田靜回調函數,也會當即獲得這個結果。 這與事件(Event)徹底不一樣,事件的特色是,若是你錯過了它,再去監聽,是得不到結果的。

有了Promise對象,就能夠把異步操做以同步操做的流程表達出來,避免了層層嵌套的回調函數。此外, Promise對象提供了統一的接口,使得控制異步操做更加容易。

基本用法

ES6規定,Promise對象是一個構造函數,用來生成Promise實例

var promise = new Promise(function(resolve,reject){
  // ... some code
  if(/* 異步操做成功 */){
    resolve(value);
  }else{
    reject(error);
  }
});
複製代碼

1 Promise構造函數接受一個函數做爲參數,該函數的兩個參數分別是resolvereject。它們是兩個函數,由JavaScript引擎提供,不是本身部署。

2 resolve函數的做用,將Promise對象的狀態從「未完成」變成「成功」(即從Pending變爲Resolved),在異步操 做成功時調用,並將異步操做的結果,做爲參數傳遞出去;

3 reject函數的做用是,在異步操做失敗時調用,並將異步操做報出的錯誤,做爲參數傳遞出去。

Promise實例生成之後,能夠用then方法分別制定Resolved狀態和Rejected狀態的回調函數:

promise.then(function(value){
        // sucess
    },function(error){
        // failure
    });
複製代碼

then方法能夠接受2個回調函數做爲參數,第二個函數是可選的,不必定要提供。這兩個函數都接受Promise對象傳出的值做爲參數。

下面是一個Promise對象的簡單例子:

function timeout(ms){
  return new Promise((resolve,reject)=>{
    setTimeout(resolve,ms,'done');
  });
}

timeout(100).then((value)=>{
  console.log(value);
});
複製代碼

上面代碼中,timeout方法返回一個Promise實例,表示一段事件之後纔會發生的結果。過了指定的時間(ms參數)之後,Promise實例的狀態變爲Resolved, 就會觸發then方法綁定的回調函數。

Promise新建後就會當即執行

let promise = new Promise(function(resolve,rejeact){
  console.log('Promise');
  resolve();
});

promise.then(function(){
  console.log('Resolved');
});

console.log('Hi');

// Promise
// Hi
// Resolved
複製代碼

上面代碼中,Promise新建後當即執行,因此首先輸出的是」Promise」,而後then方法指定的回調函數,將在當前腳本全部同步任務執行完纔會執行,因此」Resolved」最後輸出。

下面是異步加載圖片的例子:

function loadImageAsync(url){
  return new Promise(function(resolve,reject){
    var image = new Image();
    image.onload = function(){
      resolve(image);
    };
    image.onerror = function(){
      reject(new Error('Could not load image at' + url));
    };
    image.src = url;
  });
}複製代碼

下面是一個用Promise對象實現Ajax操做的例子:

var getJSON = function(url){
  var promise = new Promise(function(resolve,reject){
    var client = new XMLHttpRequest();
    client.open('GET',url);
    client.onreadystatechange = handler;
    client.responseType = 'json';
    client.setRequestHeader('Accept','application/json');
    client.send();

    function handler(){
      if(this.readyState !== 4){
        return;
      }
      if(this.status === 200){
        resolve(this.response);
      }else{
        reject(new Error(this.statusText));
      }
    }
  });

  return promise;
};
getJSON('/posts.jons').then(function(json){
  consoloe.log(json);
},function(error){
  console.log('出錯了');
});
複製代碼
相關文章
相關標籤/搜索