ES6知識點

ES6

var、let 及 const 區別?

  • 全局申明的var變量會掛載在window上,而let和const不會
  • var聲明變量存在變量提高,let和const不會
  • let、const 的做用範圍是塊級做用域,而var的做用範圍是函數做用域
  • 同一做用域下let和const不能聲明同名變量,而var能夠
  • 同一做用域下在let和const聲明前使用會存在暫時性死區
  • const
    • 一旦聲明必須賦值,不能使用null佔位
    • 聲明後不能再修改
    • 若是聲明的是複合類型數據,能夠修改其屬性

Proxy

Proxy 是 ES6 中新增的功能,它能夠用來自定義對象中的操做。 Vue3.0 中將會經過 Proxy 來替換本來的 Object.defineProperty 來實現數據響應式。es6

let p = new Proxy(target, handler)

target 表明須要添加代理的對象,handler 用來自定義對象中的操做,好比能夠用來自定義 set 或者 get 函數。ajax

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
    },
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    }
  }
  return new Proxy(obj, handler)
}

let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`監聽到屬性${property}改變爲${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 控制檯輸出:監聽到屬性a改變
p.a // 'a' = 2

自定義 set 和 get 函數的方式,在本來的邏輯中插入了咱們的函數邏輯,實現了在對對象任何屬性進行讀寫時發出通知。數組

固然這是簡單版的響應式實現,若是須要實現一個 Vue 中的響應式,須要咱們在 get 中收集依賴,在 set 派發更新,之因此 Vue3.0 要使用 Proxy 替換本來的 API 緣由在於 Proxy 無需一層層遞歸爲每一個屬性添加代理,一次便可完成以上操做,性能上更好,而且本來的實現有一些數據更新不能監聽到,可是 Proxy 能夠完美監聽到任何方式的數據改變,惟一缺陷可能就是瀏覽器的兼容性很差了。promise

map

map 做用是生成一個新數組,遍歷原數組,將每一個元素拿出來作一些變換而後返回一個新數組,原數組不發生改變。瀏覽器

map 的回調函數接受三個參數,分別是當前索引元素,索引,原數組安全

var arr = [1,2,3];
var arr2 = arr.map(item => item + 1)    
arr   //[ 1, 2, 3 ]
arr2  // [ 2, 3, 4 ]
['1','2','3'].map(parseInt)
// -> [ 1, NaN, NaN ]
  • 第一個 parseInt('1', 0) -> 1
  • 第二個 parseInt('2', 1) -> NaN
  • 第三個 parseInt('3', 2) -> NaN

filter

filter 的做用也是生成一個新數組,在遍歷數組的時候將返回值爲 true 的元素放入新數組,咱們能夠利用這個函數刪除一些不須要的元素app

filter 的回調函數接受三個參數,分別是當前索引元素,索引,原數組異步

reduce

reduce 能夠將數組中的元素經過回調函數最終轉換爲一個值。
若是咱們想實現一個功能將函數裏的元素所有相加獲得一個值,可能會這樣寫代碼async

const arr = [1, 2, 3]
let total = 0
for (let i = 0; i < arr.length; i++) {
  total += arr[i]
}
console.log(total) //6

可是若是咱們使用 reduce 的話就能夠將遍歷部分的代碼優化爲一行代碼模塊化

const arr = [1, 2, 3]
const sum = arr.reduce((acc, current) => acc + current, 0)
console.log(sum)

對於 reduce 來講,它接受兩個參數,分別是回調函數和初始值,接下來咱們來分解上述代碼中 reduce 的過程

  • 首先初始值爲 0,該值會在執行第一次回調函數時做爲第一個參數傳入
  • 回調函數接受四個參數,分別爲累計值、當前元素、當前索引、原數組,後三者想必你們均可以明白做用,這裏着重分析第一個參數
  • 在一次執行回調函數時,當前值和初始值相加得出結果 1,該結果會在第二次執行回調函數時當作第一個參數傳入
  • 因此在第二次執行回調函數時,相加的值就分別是 1 和 2,以此類推,循環結束後獲得結果 6。

Es6中箭頭函數與普通函數的區別?

  • 普通function的聲明在變量提高中是最高的,箭頭函數沒有函數提高
  • 箭頭函數沒有屬於本身的thisarguments
  • 箭頭函數不能做爲構造函數,不能被new,沒有property
  • 不可使用 yield 命令,所以箭頭函數不能用做 Generator 函數
  • 不可使用 new 命令,由於:
    • 沒有本身的 this,沒法調用 call,apply
    • 沒有 prototype 屬性 ,而 new 命令在執行時須要將構造函數的 prototype 賦值給新的對象的 __proto__

Promise

Promise 翻譯過來就是承諾的意思,這個承諾會在將來有一個確切的答覆,而且該承諾有三種狀態,這個承諾一旦從等待狀態變成爲其餘狀態就永遠不能更改狀態了。

  • 等待中(pending)
  • 完成了(resolved)
  • 拒絕了(rejected)

當咱們在構造 Promise 的時候,構造函數內部的代碼是當即執行的。

new Promise((resolve, reject) => {
  console.log('new Promise')
  resolve('success')
})
console.log('finifsh')

// 先打印new Promise, 再打印 finifsh

Promise 實現了鏈式調用,也就是說每次調用 then 以後返回的都是一個 Promise,而且是一個全新的 Promise,緣由也是由於狀態不可變。若是你在 then 中 使用了 return,那麼 return 的值會被 Promise.resolve() 包裝。

Promise.resolve(1)
  .then(res => {
    console.log(res) // => 1
    return 2 // 包裝成 Promise.resolve(2)
  })
  .then(res => {
    console.log(res) // => 2
  })

固然了,Promise 也很好地解決了回調地獄的問題

ajax(url)
  .then(res => {
      console.log(res)
      return ajax(url1)
  }).then(res => {
      console.log(res)
      return ajax(url2)
  }).then(res => console.log(res))

其實它也是存在一些缺點的,好比沒法取消 Promise,錯誤須要經過回調函數捕獲。

async 和 await

一個函數若是加上 async ,那麼該函數就會返回一個 Promise

async function test() {
  return "1"
}
console.log(test()) 
// -> Promise {<resolved>: "1"}

async 就是將函數返回值使用 Promise.resolve() 包裹了下,和 then 中處理返回值同樣,而且 await 只能配套 async 使用。

async function test() {
  let value = await sleep()
}

async 和 await 能夠說是異步終極解決方案了,相比直接使用 Promise 來講,優點在於處理 then 的調用鏈,可以更清晰準確的寫出代碼,畢竟寫一大堆 then 也很噁心,而且也能優雅地解決回調地獄問題。

固然也存在一些缺點,由於 await 將異步代碼改形成了同步代碼,若是多個異步代碼沒有依賴性卻使用了 await 會致使性能上的下降。

async function test() {
  // 如下代碼沒有依賴性的話,徹底可使用 Promise.all 的方式
  // 若是有依賴性的話,其實就是解決回調地獄的例子了
  await fetch(url)
  await fetch(url1)
  await fetch(url2)
}

看一個使用 await 的例子:

let a = 0
let b = async () => {
  a = a + await 10
  console.log('2', a)
}
b()
a++
console.log('1', a)

//先輸出  ‘1’, 1
//在輸出  ‘2’, 10
  • 首先函數 b 先執行,在執行到 await 10 以前變量 a 仍是 0,由於 await 內部實現了 generator ,generator 會保留堆棧中東西,因此這時候 a = 0 被保存了下來
  • 由於 await 是異步操做,後來的表達式不返回 Promise 的話,就會包裝成 Promise.reslove(返回值),而後會去執行函數外的同步代碼
  • 同步代碼 a++ 與打印 a 執行完畢後開始執行異步代碼,將保存下來的值拿出來使用,這時候 a = 0 + 10

上述解釋中提到了 await 內部實現了 generator,其實 await 就是 generator 加上 Promise 的語法糖,且內部實現了自動執行 generator

代碼分析題

function wait() {
  return new Promise(resolve =>
    setTimeout(resolve,  1000)
  )
}

async function main() {
  console.time();
  const x = wait();
  const y = wait();
  const z = wait();
  await x;
  await y;
  await z;
  console.timeEnd();
}
main();

答案: 輸出耗時: 1秒多一點點。
緣由: 3個wait函數在賦值的時候就已經開始執行了。

稍微改造一下就能夠獲得3 * 1000 ms以上的結果

function wait () {
  return new Promise(
    resolve => setTimeout(resolve,  1000)
  )
}

async function main () {
  console.time()
  const x = await wait()
  const y = await wait()
  const z = await wait()
  console.timeEnd()
}

main()

Generator 生成器

function *foo(x) {
  let y = 2 * (yield (x + 1))
  let z = yield (y / 3)
  return (x + y + z)
}
let it = foo(5)
console.log(it.next())   // => {value: 6, done: false}
console.log(it.next(12)) // => {value: 8, done: false}
console.log(it.next(13)) // => {value: 42, done: true}
  • 首先 Generator 函數調用和普通函數不一樣,它會返回一個迭代器

  • 當執行第一次 next 時,傳參會被忽略,而且函數暫停在 yield (x + 1) 處,因此返回 5 + 1 = 6

  • 當執行第二次 next 時,傳入的參數等於上一個 yield 的返回值,若是你不傳參,yield 永遠返回 undefined。此時 let y = 2 * 12,因此第二個 yield 等於 2 * 12 / 3 = 8

  • 當執行第三次 next 時,傳入的參數會傳遞給 z,因此 z = 13, x = 5, y = 24,相加等於 42

生成器原理

當yeild產生一個值後,生成器的執行上下文就會從棧中彈出。但因爲迭代器一直保持着隊執行上下文的引用,上下文不會丟失,不會像普通函數同樣執行完後上下文就被銷燬

ES Module

ES Module 是原生實現的模塊化方案,與 CommonJS 有如下幾個區別

  • CommonJS 支持動態導入,也就是 require(${path}/xx.js),後者目前不支持,可是已有提案
  • CommonJS 是同步導入,由於用於服務端,文件都在本地,同步導入即便卡住主線程影響也不大。然後者是異步導入,由於用於瀏覽器,須要下載文件,若是也採用同步導入會對渲染有很大影響
  • CommonJS 在導出時都是值拷貝,就算導出的值變了,導入的值也不會改變,因此若是想更新值,必須從新導入一次。可是 ES Module 採用實時綁定的方式,導入導出的值都指向同一個內存地址,因此導入值會跟隨導出值變化
  • ES Module 會編譯成 require/exports 來執行的
// 引入模塊 API
import XXX from './a.js'
import { XXX } from './a.js'
// 導出模塊 API
export function a() {}
export default function() {}

私有方法和私有屬性(阿里一面)

阮老師 | ES6入門

現有的解決方案

私有方法和私有屬性,是隻能在類的內部訪問的方法和屬性,外部不能訪問。這是常見需求,有利於代碼的封裝,但 ES6 不提供,只能經過變通方法模擬實現。

一種作法是在命名上加以區別,即在函數名或屬性名前加_,但這並不安全,只是一種團隊規範。

另外一種方法就是索性將私有方法移出類,放到模塊裏,由於模塊內部的全部方法都是對外可見的。

class Widget {
  foo (baz) {
    bar.call(this, baz);
  }

  // ...
}

function bar(baz) {
  return this.snaf = baz;
}

上面代碼中,foo是公開方法,內部調用了bar.call(this, baz)。這使得bar實際上成爲了當前模塊的私有方法。

還有一種方法是利用Symbol值的惟一性,將私有方法的名字命名爲一個Symbol值。

const bar = Symbol('bar');
const snaf = Symbol('snaf');

export default class myClass{

  // 公有方法
  foo(baz) {
    this[bar](baz);
  }

  // 私有方法
  [bar](baz) {
    return this[snaf] = baz;
  }

  // ...
};

上面代碼中,bar和snaf都是Symbol值,通常狀況下沒法獲取到它們,所以達到了私有方法和私有屬性的效果。可是也不是絕對不行,Reflect.ownKeys()依然能夠拿到它們。

const inst = new myClass();

Reflect.ownKeys(myClass.prototype)
// [ 'constructor', 'foo', Symbol(bar) ]

Proxy

Proxy 能夠理解成,在目標對象以前架設一層「攔截」,外界對該對象的訪問,都必須先經過這層攔截,所以提供了一種機制,能夠對外界的訪問進行過濾和改寫。Proxy 這個詞的原意是代理,用在這裏表示由它來「代理」某些操做,能夠譯爲「代理器」。

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

Proxy 支持的攔截操做一覽,一共 13 種。

  • get(target, propKey, receiver)
    • 攔截對象屬性的讀取,好比proxy.foo和proxy['foo']。
  • set(target, propKey, value, receiver)
    • 攔截對象屬性的設置,好比proxy.foo = v或proxy['foo'] = v,返回一個布爾值。
  • has(target, propKey)
    • 攔截propKey in proxy的操做,返回一個布爾值。
  • deleteProperty(target, propKey)
    • 攔截delete proxy[propKey]的操做,返回一個布爾值。
  • ownKeys(target)
    • 攔截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循環,返回一個數組。該方法返回目標對象全部自身的屬性的屬性名,而Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。
  • getOwnPropertyDescriptor(target, propKey)
    • 攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。
  • defineProperty(target, propKey, propDesc)
    • 攔截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。
  • preventExtensions(target)
    • 攔截Object.preventExtensions(proxy),返回一個布爾值。
  • getPrototypeOf(target)
    • 攔截Object.getPrototypeOf(proxy),返回一個對象。
  • isExtensible(target)
    • 攔截Object.isExtensible(proxy),返回一個布爾值。
  • setPrototypeOf(target, proto)
    • 攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值。若是目標對象是函數,那麼還有兩種額外操做能夠攔截。
  • apply(target, object, args)
    • 攔截 Proxy 實例做爲函數調用的操做,好比proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
  • construct(target, args)
    • 攔截 Proxy 實例做爲構造函數調用的操做,好比new proxy(...args)。
相關文章
相關標籤/搜索