《前端面試手記》之ES6重難點整理

👇 內容速覽 👇javascript

  • let和const
  • Set和Map
  • Generator和yield
  • Promise、async/await介紹
  • Proxy代理器
  • ...

🔍查看所有教程 / 閱讀原文🔍css

let和const

ES6新增了letconst,它們聲明的變量,都處於「塊級做用域」。而且不存在「變量提高」,不容許重複聲明。前端

同時,const聲明的變量所指向的內存地址保存的數據不得改變:java

  • 對於簡單類型的數據(數值、字符串、布爾值),值就保存在變量指向的那個內存地址,所以等同於常量。
  • 對於複合類型的數據(主要是對象和數組),變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的(即老是指向另外一個固定的地址),不能保證指向的數據結構不可變。

若是要保證指向的數據結構也不可變,須要自行封裝:webpack

/**
 * 凍結對象
 * @param {Object} obj 
 * @return {Object}
 */
function constantize(obj) {
  if(Object.isFrozen(obj)) {
    return obj
  }

  Reflect.ownKeys(obj).forEach(key => {
    // 若是屬性是對象,遞歸凍結
    typeof obj[key] === 'object' && (obj[key] = constantize(obj[key]))
  });

  return Object.freeze(obj)
}

/********測試代碼 **********/

const obj = {
  a: 1,
  b: {
    c: 2,
    d: {
      a: 1
    }
  },
  d: [
    1,
    2
  ]
}

const fronzenObj = constantize(obj)
try {
  fronzenObj.d = []
  fronzenObj.b.c = 3
} catch(error) {
  console.log(error.message)
}

Set和Map

題目:解釋下 SetMap
  • Set元素不容許重複
  • Map相似對象,可是它的鍵(key)能夠是任意數據類型

①Set經常使用方法css3

// 實例化一個set
const set = new Set([1, 2, 3, 4]);

// 遍歷set
for (let item of set) {
  console.log(item);
}

// 添加元素,返回Set自己
set.add(5).add(6);

// Set大小
console.log(set.size);

// 檢查元素存在
console.log(set.has(0));

// 刪除指定元素,返回bool
let success = set.delete(1);
console.log(success);

set.clear();

其餘遍歷方法:因爲沒有鍵名,values()keys()返回一樣結果。git

for (let item of set.keys()) {
  console.log(item);
}

for (let item of set.values()) {
  console.log(item);
}

for (let item of set.entries()) {
  console.log(item);
}

②Map經常使用方法es6

Map接口基本和Set一致。不一樣的是增長新元素的API是:set(key, value)github

const map = new Map();

// 以任意對象爲 Key 值
// 這裏以 Date 對象爲例
let key = new Date(); 
map.set(key, "today");

console.log(map.get(key));

Generator與yield

generator函數是es6提供的新特性,它的最大特色是:控制函數的執行。讓咱們從網上最火的一個例子來看:web

function* foo(x) {
  var y = 2 * (yield x + 1);
  var z = yield y / 3;
  return x + y + z;
}

var b = foo(5);
b.next(); // { value:6, done:false }
b.next(12); // { value:8, done:false }
b.next(13); // { value:42, done:true }

通俗的解釋下爲何會有這種輸出:

  1. 給函數foo傳入參數5,但因爲它是generator,因此執行到第一個yield前就中止了。
  2. 第一次調用next(),此次傳入的參數會被忽略暫停**。
  3. 第二次調用next(12),傳入的參數會被看成上一個yield表達式的返回值。所以,y = 2 * 12 = 24。執行到第二個yield,返回其後的表達式的值 24 / 3 = 8。而後函數在此處暫停。
  4. 第三次調用next(13),沒有yield,只剩return了,按照正常函數那樣返回return的表達式的值,而且donetrue

難點:在於爲何最後的value是42呢?

首先,x的值是剛開始調用foo函數傳入的5。而最後傳入的13被看成第二個yield的返回值,因此z的值是13。對於y的值,咱們在前面第三步中已經計算出來了,就是24。

因此,x + y + z = 5 + 24 + 13 = 42

看懂了上面的分析,再看下面這段代碼就很好理解了:

function* foo(x) {
  var y = 2 * (yield x + 1);
  var z = yield y / 3;
  return x + y + z;
}

var a = foo(5);
a.next(); // Object{value:6, done:false}
a.next(); // Object{value:NaN, done:false}
a.next(); // Object{value:NaN, done:true}

只有第一次調用next函數的時候,輸出的value是6。其餘時候因爲沒有給next傳入參數,所以yield的返回值都是undefined,進行運算後天然是NaN

Promise介紹

簡單概括下 Promise:三個狀態、兩個過程、一個方法

  • 三個狀態:pendingfulfilledrejected
  • 兩個過程(單向不可逆):

    • pending->fulfilled
    • pending->rejected
  • 一個方法thenPromise本質上只有一個方法,catchall方法都是基於then方法實現的。

請看下面這段代碼:

// 構造 Promise 時候, 內部函數當即執行
new Promise((resolve, reject) => {
  console.log("new Promise");
  resolve("success");
});
console.log("finifsh");

//  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
  });

async/await介紹

async函數返回一個Promise對象,可使用then方法添加回調函數。

當函數執行的時候,一旦遇到await就會先返回,等到異步操做完成,再接着執行函數體內後面的語句。

這也是它最受歡迎的地方:能讓異步代碼寫起來像同步代碼,而且方便控制順序

能夠利用它實現一個sleep函數阻塞進程:

function sleep(millisecond) {
  return new Promise(resolve => {
    setTimeout(() => resolve, millisecond)
  })
}

/**
 * 如下是測試代碼
 */
async function test() {
  console.log('start')
  await sleep(1000) // 睡眠1秒
  console.log('end')
}

test() // 執行測試函數

雖然方便,可是它也不能取代Promise,尤爲是咱們能夠很方便地用Promise.all()來實現併發,而async/await只能實現串行。

function sleep(second) {
  return new Promise(resolve => {
    setTimeout(() => {
      console.log(Math.random());
      resolve();
    }, second);
  });
}

async function chuanXingDemo() {
  await sleep(1000);
  await sleep(1000);
  await sleep(1000);
}


async function bingXingDemo() {
  var tasks = [];
  for (let i = 0; i < 3; ++i) {
    tasks.push(sleep(1000));
  }

  await Promise.all(tasks);
}

運行bingXingDemo(),幾乎同時輸出,它是併發執行;運行chuanXingDemo(),每一個輸出間隔1s,它是串行執行。

ES6對象和ES5對象

題目:es6 class 的new實例和es5的new實例有什麼區別?

ES6中(和ES5相比),classnew實例有如下特色:

  • class的構造參數必須是new來調用,不能夠將其做爲普通函數執行
  • es6class不存在變量提高
  • 最重要的是:es6內部方法不能夠枚舉。es5的prototype上的方法能夠枚舉。

爲此我作了如下測試代碼進行驗證:

console.log(ES5Class()) // es5:能夠直接做爲函數運行
// console.log(new ES6Class()) // 會報錯:不存在變量提高

function ES5Class(){
  console.log("hello")
}

ES5Class.prototype.func = function(){ console.log("Hello world") }

class ES6Class{
  constructor(){}
  func(){
    console.log("Hello world")
  }
}

let es5 = new ES5Class()
let es6 = new ES6Class()

// 推薦在循環對象屬性的時候,使用for...in
// 在遍歷數組的時候的時候,使用for...of
console.log("ES5 :")
for(let _ in es5){
  console.log(_)
}

// es6:不可枚舉
console.log("ES6 :")
for(let _ in es6){
  console.log(_)
}

參考/推薦《JavaScript建立對象—從es5到es6》

Proxy代理器

他能夠實現js中的「元編程」:在目標對象以前架設攔截,能夠過濾和修改外部的訪問。

它支持多達13種攔截操做,例以下面代碼展現的setget方法,分別能夠在設置對象屬性和訪問對象屬性時候進行攔截。

const handler = {
  // receiver 指向 proxy 實例
  get(target, property, receiver) {
    console.log(`GET: target is ${target}, property is ${property}`)
    return Reflect.get(target, property, receiver)
  },
  set(target, property, value, receiver) {
    console.log(`SET: target is ${target}, property is ${property}`)
    return Reflect.set(target, property, value)
  }
}

const obj = { a: 1 , b: {c: 0, d: {e: -1}}}
const newObj = new Proxy(obj, handler)

/**
 * 如下是測試代碼
 */

newObj.a // output: GET...
newObj.b.c // output: GET...

newObj.a = 123 // output: SET...
newObj.b.c = -1 // output: GET...

運行這段代碼,會發現最後一行的輸出是 GET ...。也就是說它觸發的是get攔截器,而不是指望的set攔截器。這是由於對於對象的深層屬性,須要專門對其設置Proxy

更多請見《阮一峯ES6入門:Proxy》

EsModule和CommonJS的比較

目前js社區有4種模塊管理規範:AMD、CMD、CommonJS和EsModule。 ES Module 是原生實現的模塊化方案,與 CommonJS 有如下幾個區別:

  • CommonJS 支持動態導入,也就是 require(${path}/xx.js),後者目前不支持,可是已有提案:import(xxx)
  • CommonJS 是同步導入,由於用於服務端,文件都在本地,同步導入即便卡住主線程影響也不大。然後者是異步導入,由於用於瀏覽器,須要下載文件,若是也採用同步導入會對渲染有很大影響
  • commonJs輸出的是值的淺拷貝,esModule輸出值的引用
  • ES Module 會編譯成 require/exports 來執行的

更多系列文章

⭐在GitHub上收藏/訂閱⭐

《前端知識體系》

《設計模式手冊》

《Webpack4漸進式教程》

⭐在GitHub上收藏/訂閱⭐

相關文章
相關標籤/搜索