學習es6中發現很容易忽略的知識點!!

一、前言

  • 這是我在學習ES6的過程當中,碰到的很容易忽略的知識點!

知識點1:你知道for循環中的let有一個單獨的做用域嗎?

請看下題輸出什麼javascript

let i = 5;
for (let i = 0; console.log(i), i < 3; i++) {
  let i = 'abc';
  console.log(i);
}
// 輸出
// 0
// abc
// 1
// abc
// 2
// abc
// 3

複製代碼

上面的代碼,首先最外層有一個let i = 5; for循環體裏有let i = 0; 若是這兩個i在同一個做用域裏確定會報錯,這就說明這兩個i不在贊成做用域,接着,咱們再看for循環體裏的i和裏面的i是否在同一個做用域,經過輸出內容,明顯它們不在同一個做用域裏。java

知識點2:對象的解構賦值能夠取到繼承的屬性嗎?

是的算法

const obj1 = {};
const obj2 = { name: 'xiaoming' };
Object.setPrototypeOf(obj1, obj2);

const { name } = obj1;
name // "xiaoming"
複製代碼

知識點3:將已聲明的變量結構賦值須要加括號?

// 錯誤的寫法
let x;
{x} = {x: 1};
複製代碼

上面代碼的寫法會報錯,由於 JavaScript 引擎會將{x}理解成一個代碼塊,從而發生語法錯誤,須要將整個結構語句放在圓括號裏。數組

// 正確的寫法
let x;
({x} = {x: 1});
複製代碼

知識點4:函數的length屬性是指函數的默認參數個數,若是默認參數裏面有默認值,會出現什麼問題?

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
複製代碼

上面代碼中,length屬性的返回值,等於函數的參數個數減去指定了默認值的參數個數。promise

(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
複製代碼

若是設置了默認值的參數不是尾參數,那麼length屬性也再也不計入後面的參數了。瀏覽器

爲何要提這個知識點呢,由於函數柯里化通用函數裏,須要用到函數默認參數的個數,也就是length屬性,因此若是參數設置了默認值,會影響函數柯里化的結果。bash

知識點5:你不知道的Array.from用法

Array.from方法用於將兩類對象轉爲真正的數組:數據結構

  • 相似數組的對象(array-like object)
  • 可遍歷(iterable)的對象(包括 ES6 新增的數據結構 Set 和 Map)

定義提到Array.from能夠將類數組對象(注:類數組對象能夠理解爲有length屬性的對象)能夠轉換爲數組,那麼以下:函數

Array.from({ length: 3 });
// [ undefined, undefined, undefined ]
複製代碼

Array.from還能夠接受第二個參數,做用相似於數組的map方法。學習

Array.from(arrayLike, x => x * x);
// 等同於
Array.from(arrayLike).map(x => x * x);

Array.from([1, 2, 3], (x) => x * x)
複製代碼

若是map函數裏面用到了this關鍵字,還能夠傳入Array.from的第三個參數,用來綁定this

知識點5:數組的includes方法和indexOf方法均可以判斷是否數組中存在某一元素,它們有什麼區別?

if ([1, 2, 3].indexOf(2) !== -1) {
  // ...
}
複製代碼

indexOf方法有兩個缺點,一是不夠語義化,它的含義是找到參數值的第一個出現位置,因此要去比較是否不等於-1,表達起來不夠直觀。二是,它內部使用嚴格相等運算符(===)進行判斷,這會致使對NaN的誤判。

[NaN].indexOf(NaN)
// -1
複製代碼

includes使用的是不同的判斷算法,就沒有這個問題。

[NaN].includes(NaN)
// true
複製代碼

知識點6:數組的join()和toString()方法,若是數組中包括undefined或者null,會被處理成空字符串

[1, undefined, 2].join(); // "1,,2"
[1, undefined, 2].toString()]() // "1,,2"

[1, null, 2].join(); // "1,,2"
[1, null, 2].toString()]() // "1,,2"
複製代碼

知識點7:遍歷對象操做跟可枚舉性的恩怨情仇

可枚舉性

對象的每一個屬性都有一個描述對象(Descriptor),用來控制該屬性的行爲。Object.getOwnPropertyDescriptor方法能夠獲取該屬性的描述對象。

let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }
複製代碼

描述對象的enumerable屬性,稱爲「可枚舉性」,若是該屬性爲false,就表示某些操做會忽略當前屬性。

屬性的遍歷

ES6 一共有 5 種方法能夠遍歷對象的屬性。

  • for...in

for...in循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。

  • Object.keys(obj)

Object.keys返回一個數組,包括對象自身的(不含繼承的)全部可枚舉屬性(不含 Symbol 屬性)的鍵名。

  • Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一個數組,包含對象自身的全部屬性(不含 Symbol 屬性,可是包括不可枚舉屬性)的鍵名。

  • Object.getOwnPropertySymbols(obj)

Object.getOwnPropertySymbols返回一個數組,包含對象自身的全部 Symbol 屬性的鍵名。

  • Reflect.ownKeys(obj)

Reflect.ownKeys返回一個數組,包含對象自身的全部鍵名,無論鍵名是 Symbol 或字符串,也不論是否可枚舉。

以上的 5 種方法遍歷對象的鍵名,都遵照一樣的屬性遍歷的次序規則

  • 首先遍歷全部數值鍵,按照數值升序排列。
  • 其次遍歷全部字符串鍵,按照加入時間升序排列。
  • 最後遍歷全部 Symbol 鍵,按照加入時間升序排列。
Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
複製代碼

上面代碼中,Reflect.ownKeys方法返回一個數組,包含了參數對象的全部屬性。這個數組的屬性次序是這樣的,首先是數值屬性2和10,其次是字符串屬性b和a,最後是 Symbol 屬性。

知識點7:對象和數組的擴展運算符後面能夠跟表達式?

是的

const obj = {
  ...(x > 1 ? {a: 1} : {}),
  b: 2,
};
複製代碼
const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];
複製代碼

知識點8:WeakSet 和Set 的區別?

區別有兩點

  • 第一點,WeakSet的成員只能是對象類型,而不能是其餘類型的值。
const ws = new WeakSet();
ws.add({a: 1}) // 正確
ws.add([1,2]) // 正確
wa.add(1) //錯誤
複製代碼
  • 第二點,WeakSet中的對象都是弱引用。

同上,WeakMap和Map的區別也有兩點

  • 第一點,WeakMap只接受對象做爲鍵名(null除外),不接受其餘類型的值做爲鍵名。
  • 第二點,WeakMap的鍵名所指向的對象,不計入垃圾回收機制

知識點9:對象的屬性賦值,通常狀況下這個值會賦值到對象上,但有一種狀況會賦值到這個對象的原型上

爲何下面的代碼輸出1

const handler = {
  set: function(obj, prop, value, receiver) {
    console.log(1)
  }
};
const proxy = new Proxy({}, handler);
const myObj = {};
Object.setPrototypeOf(myObj, proxy);

myObj.foo = 'bar';
複製代碼

緣由在於,若是myObj的原型部署了set方法,而且myObj上沒有foo屬性,此時,給myObjfoo屬性賦值的話,會先看myObj對象上是否有foo屬性,沒有的話回去它的原型去找,此時就觸發了proxyset方法,因此打印了1

知識點10:Promise中的resolve函數若是報錯,Promise對象拋出的錯誤不會傳遞到外層代碼

const someAsyncThing = function() {
  return new Promise(function(resolve, reject) {
    // 下面一行會報錯,由於x沒有聲明
    resolve(x + 2);
  });
};

someAsyncThing().then(function() {
  console.log('everything is great');
});

setTimeout(() => { console.log(123) }, 2000);
// Uncaught (in promise) ReferenceError: x is not defined
// 123
複製代碼
  • 上面代碼中,someAsyncThing函數產生的 Promise 對象,內部有語法錯誤。瀏覽器運行到這一行,會打印出錯誤提示ReferenceError: x is not defined,可是不會退出進程、終止腳本執行,2秒以後仍是會輸出123
  • 這就是說,Promise內部的錯誤不會影響到 Promise外部的代碼,通俗的說法就是Promise 會吃掉錯誤

知識點11:如何經過generator函數控制流管理

代碼以下,如何自動執行如下的generator函數(這種流程管理的代碼適合同步任務,我認爲能夠稱做js的職責責任鏈模式,很是方便流程管理)

function* runTask(value1) {
  try {
    var value2 = yield step1(value1);
    var value3 = yield step2(value2);
  } catch (e) {
  
  }
}
複製代碼

自動執行代碼以下

scheduler(runTask(initialValue));

function scheduler(task) {
  var taskObj = task.next(task.value);
  // 若是Generator函數未結束,就繼續調用
  if (!taskObj.done) {
    task.value = taskObj.value
    scheduler(task);
  }
}
複製代碼