ES6經常使用但被忽略的方法(第二彈)

函數擴展

函數默認值

  1. 函數能夠經過定義時給對應的參數賦一個默認值,默認值當函數調用時若是沒有傳入對應的值便會使用默認值。設置默認值還能夠搭配解構賦值一塊兒使用。
function detanx(x = 1) { ... }
// 搭配解構賦值
function detanx({name, age = 10} = {name: 'detanx', age: 10}) {...}
// 不徹底解構,
function detanx({name, age = 10} = {name: 'detanx'}) {...}
detanx({age: 12}) // undefined 12
複製代碼
  • 不徹底解構時,即便後面的對象中有對應的鍵值也不會被賦值。
  1. 函數有默認值的參數在參數個數中間,而且後面的參數不實用默認值,這時要使用默認值的參數能夠傳入undefined
function detanx(x =  null, y = 6) {
  console.log(x, y);
}
detanx(undefined, null); // null null
複製代碼
  1. 默認值會改變length獲取函數參數返回的長度。
  • 指定了默認值之後,函數的length屬性,將返回沒有指定默認值的參數個數。也就是說,指定了默認值後,length屬性將失真。
(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2
複製代碼
  • 默認值的參數不是尾參數,那麼length屬性也再也不計入後面的參數
(function (a = 0, b, c) {}).length // 0
(function (a, b = 1, c) {}).length // 1
複製代碼
  • 使用擴展運算符表示接收的參數,length0.
(function(...args) {}).length // 0
複製代碼
  1. 設置默認值後做用域改變
  • 設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域(context)。
var x = 1;
function detanx(x, y = x) {
  console.log(y);
}
f(2) // 2

function detanx(x = x, y = x) {
  console.log(x, y);
}
f(undefined,2) //  ReferenceError: Cannot access 'x' before initialization
複製代碼

函數默認值應用

  1. 指定某一個參數不得省略,若是省略就拋出一個錯誤。
function throwIfMissing() {
  throw new Error('Missing parameter');
}
function foo(mustBeProvided = throwIfMissing()) {
  return mustBeProvided;
}
foo()
// Error: Missing parameter
複製代碼
  • 參數mustBeProvided的默認值等於throwIfMissing函數的運行結果(注意函數名throwIfMissing以後有一對圓括號),這代表參數的默認值不是在定義時執行,而是在運行時執行。若是參數已經賦值,默認值中的函數就不會運行。

嚴格模式變化

  • ES5 開始,函數內部能夠設定爲嚴格模式。ES2016 作了一點修改,規定只要函數參數使用了默認值、解構賦值、或者擴展運算符,那麼函數內部就不能顯式設定爲嚴格模式,不然會報錯。
// 報錯
function doSomething(a, b = a) {
  'use strict';
  // code
}
// 報錯
const doSomething = function ({a, b}) {
  'use strict';
};
// 報錯
const doSomething = (...a) => {
  'use strict';
};
const obj = {
  // 報錯
  doSomething({a, b}) {
    'use strict';
  }
};
複製代碼
  • 這種限制有個問題就是在聲明時都是先執行參數再執行函數體,只有在進入函數體發現用了嚴格模式纔會報錯。
  • 規避這種限制的方法。
// 第一種是設定全局性的嚴格模式,這是合法的。
'use strict';
function doSomething(a, b = a) {
  // code
}
// 第二種是把函數包在一個無參數的當即執行函數裏面。
const doSomething = (function () {
  'use strict';
  return function(value = 42) {
    return value;
  };
}());
複製代碼

箭頭函數

  • 使用注意點
    1. 函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。
    2. 不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。
    3. 不可使用arguments對象,該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。
    4. 不可使用yield命令,所以箭頭函數不能用做 Generator 函數。
  • 箭頭函數也能夠像普通函數同樣嵌套使用。
  • 簡化編寫代碼,但會下降代碼的可讀性。
// 柯里化
const curry = (fn, arr = []) => (...args) => (
  arg => arg.length === fn.length
    ? fn(...arg)
    : curry(fn, arg)
)([...arr, ...args]);

let curryTest = curry( (a, b) => a + b );
curryTest(1, 2) //返回3
curryTest(1)(2) //返回3
複製代碼

數組擴展

擴展運算符

  • 應用
    1. 接收不肯定參數數量
    function add(...values) {
      let sum = 0;
    
      for (var val of values) {
        sum += val;
      }
    
      return sum;
    }
    
    add(2, 5, 3) // 10
    複製代碼
    1. 替代apply方法
    • 以前在某個函數須要接收數組參數時須要這樣寫fn.apply(this, arr)
    // ES5 的寫法
    const args = [0, 1, 2];
    function f(x, y, z) {
      // ...
    }
    f.apply(null, args);
    
    // ES6的寫法
    function f(x, y, z) {
      // ...
    }
    f(...args);
    複製代碼
    1. 複製、合併數組、轉換字符串爲數組
    const a = [1]
    const b = [2];
    // 複製數組
    const c = [...a]; // [1]
    // 合併數組
    [...a, ...b, ...c] // [1, 2, 1]
    // 轉換字符串爲數組
    [...'detanx'] // ['d', 'e', 't', 'a', 'n', 'x']
    複製代碼
    1. 實現了 Iterator 接口的對象
    • 任何定義了遍歷器(Iterator)接口的對象(參閱 Iterator 一章),均可以用擴展運算符轉爲真正的數組。
    Number.prototype[Symbol.iterator] = function*() {
      let i = 0;
      let num = this.valueOf();
      while (i < num) {
        yield i++;
      }
    }
    console.log([...5]) // [0, 1, 2, 3, 4]
    複製代碼
    • 例如函數的arguments對象是一個類數組;querySelectorAll方法返回的是一個NodeList對象。它也是一個相似數組的對象。
    let nodeList = document.querySelectorAll('div');
    let array = [...nodeList];
    複製代碼

Array.from

  • Array.from的做用和擴展運算符相似,均可以將類數組轉爲數組,但它還能夠接收第二參數,做用相似於數組的map方法,用來對每一個元素進行處理,將處理後的值放入返回的數組。
// 生成0-99的數組
Array.from(new Array(100), (x, i) => i);
複製代碼

copyWithin()

  • 數組實例的copyWithin()方法,在當前數組內部,將指定位置的成員複製到其餘位置(會覆蓋原有成員),而後返回當前數組。也就是說,使用這個方法,會修改當前數組。
Array.prototype.copyWithin(target, start = 0, end = this.length)
複製代碼
- target(必需):從該位置開始替換數據。若是爲負值,表示倒數。
- start(可選):從該位置開始讀取數據,默認爲 0。若是爲負值,表示從末尾開始計算。
- end(可選):到該位置前中止讀取數據,默認等於數組長度。若是爲負值,表示從末尾開始計算。
複製代碼
  • 這三個參數都應該是數值,若是不是,會自動轉爲數值。
// 將3號位複製到0號位
[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
// [4, 2, 3, 4, 5]

// -2至關於3號位,-1至關於4號位
[1, 2, 3, 4, 5].copyWithin(0, -2, -1)
// [4, 2, 3, 4, 5]

// 將3號位複製到0號位
[].copyWithin.call({length: 5, 3: 1}, 0, 3)
// {0: 1, 3: 1, length: 5}

// 將2號位到數組結束,複製到0號位
let i32a = new Int32Array([1, 2, 3, 4, 5]);
i32a.copyWithin(0, 2);
// Int32Array [3, 4, 5, 4, 5]

// 對於沒有部署 TypedArray 的 copyWithin 方法的平臺
// 須要採用下面的寫法
[].copyWithin.call(new Int32Array([1, 2, 3, 4, 5]), 0, 3, 4);
// Int32Array [4, 2, 3, 4, 5]
複製代碼

find() 和 findIndex()

  • 兩個方法均可以接收兩個參數,第一個參數是回調函數(能夠接收三個參數,分別是當前值,當前位置,當前的原數組),第二個參數用來綁定回調函數的this。區別是find沒有找到返回的是undefinedfindIndex返回的是-1。這兩個方法均可以發現NaN,彌補了數組的indexOf方法的不足。
[1, 4, -5, 10].find((n) => n > 10) // undefined
[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 15;
}) // -1

// 綁定this
function f(v){
  return v > this.age;
}
let person = {name: 'John', age: 20};
[10, 12, 26, 15].find(f, person);    // 26

// 找出NaN
[NaN].indexOf(NaN)
// -1
[NaN].findIndex(y => Object.is(NaN, y))
// 0
複製代碼

fill

  • fill方法使用給定值,填充一個數組。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]

new Array(3).fill(7)
// [7, 7, 7]
複製代碼
  • 上面代碼代表,fill方法用於空數組的初始化很是方便。數組中已有的元素,會被所有抹去。node

  • fill方法還能夠接受第二個和第三個參數,用於指定填充的起始位置和結束位置。es6

- ['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
複製代碼

上面代碼表示,fill方法從 1 號位開始,向原數組填充 7,到 2 號位以前結束。數組

注意,若是填充的類型爲對象,那麼被賦值的是同一個內存地址的對象,而不是深拷貝對象。bash

let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]

let arr = new Array(3).fill([]);
arr[0].push(5);
arr
// [[5], [5], [5]]
複製代碼

includes

  • 某個數組是否包含給定的值,與字符串的includes方法相似。與indexOf的區別是includes直接返回一個布爾值,indexOf須要再判斷返回值是否爲-1。當咱們須要一個變量是否爲多個枚舉值之一時就可使用includes
const name = 'detanx';
const nameArr = ['detanx', 'det', 'tanx']
// 使用includes作判斷
if(nameArr.indexOf(name) !== -1) { ... }
=>
if(nameArr.includes(name)) { ... }
// 判斷name是不是 detanx,tanx,det
if(name === 'detanx' || name === 'det' || name === 'tanx') {...}
=>
nameArr.includes(name);
複製代碼

數組空位

  • 數組的空位指:數組的某一個位置沒有任何值(空位不是undefined,一個位置的值等於undefined,依然是有值的。)。好比,Array構造函數返回的數組都是空位。
Array(3) // [, , ,]
複製代碼
  1. forEach()filter()reduce()every()some()都會跳過空位。
// forEach方法
[,'a'].forEach((x,i) => console.log(i)); // 1

// filter方法
['a',,'b'].filter(x => true) // ['a','b']

// every方法
[,'a'].every(x => x==='a') // true

// reduce方法
[1,,2].reduce((x,y) => x+y) // 3

// some方法
[,'a'].some(x => x !== 'a') // false
複製代碼
  1. map()會跳過空位,但會保留這個值。
// map方法
[,'a'].map(x => 1) // [,1]
複製代碼
  1. join()toString()會將空位視爲undefined,而undefinednull會被處理成空字符串。
// join方法
[,'a',undefined,null].join('#') // "#a##"

// toString方法
[,'a',undefined,null].toString() // ",a,,"
複製代碼
  1. Array.from()...entries()keys()values()find()findIndex()會將空位處理成undefined
Array.from(['a',,'b'])
// [ "a", undefined, "b" ]

[...['a',,'b']]
// [ "a", undefined, "b" ]

// entries()
[...[,'a'].entries()] // [[0,undefined], [1,"a"]]

// keys()
[...[,'a'].keys()] // [0,1]

// values()
[...[,'a'].values()] // [undefined,"a"]

// find()
[,'a'].find(x => true) // undefined

// findIndex()
[,'a'].findIndex(x => true) // 0
複製代碼
  1. copyWithin()、fill()、for...of會將空位視爲正常的數組位置。
[,'a','b',,].copyWithin(2,0) // [,"a",,"a"]

new Array(3).fill('a') // ["a","a","a"]

let arr = [, ,];
for (let i of arr) {
  console.log(1);
}
// 1
// 1
複製代碼
  • 空位的處理規則很是不統一,因此建議避免出現空位。

對象擴展

對象屬性枚舉

  • 對象的每一個屬性都有一個描述對象(Descriptor),用來控制該屬性的行爲。 Object.getOwnPropertyDescriptor方法能夠獲取該屬性的描述對象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
//  {
//    value: 123,
//    writable: true,
//    enumerable: true,
//    configurable: true
//  }
複製代碼
  • 描述對象的enumerable屬性,稱爲「可枚舉性」,若是該屬性爲false,就表示某些操做會忽略當前屬性。數據結構

  • 目前,有四個操做會忽略enumerablefalse的屬性。app

    1. for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
    2. Object.keys():返回對象自身的全部可枚舉的屬性的鍵名。
    3. JSON.stringify():只串行化對象自身的可枚舉的屬性。
    4. Object.assign(): 忽略enumerablefalse的屬性,只拷貝對象自身的可枚舉的屬性。
  • Symbol類型的屬性只能經過getOwnPropertySymbols方法獲取。ide

鏈判斷運算符

  • ES5咱們判斷一個深層級的對象是否有某一個key須要一層一層去判斷,如今咱們能夠經過?.的方式去獲取。
// es5
// 錯誤的寫法(當某一個key不存在undefined.key就會代碼報錯)
const  firstName = message.body.user.firstName;

// 正確的寫法
const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';

// es6
const firstName = message?.body?.user?.firstName || 'default';
複製代碼
  • 鏈判斷運算符有三種用法。
    1. obj?.prop // 對象屬性
    2. obj?.[expr] // 對象屬性
    3. func?.(...args) // 函數或對象方法的調用
a?.b
// 等同於
a == null ? undefined : a.b

a?.[x]
// 等同於
a == null ? undefined : a[x]

a?.b()
// 等同於
a == null ? undefined : a.b()

a?.()
// 等同於
a == null ? undefined : a()
複製代碼
  • 注意點
    1. 短路機制
    • ?.運算符至關於一種短路機制,只要不知足條件,就再也不往下執行。
    // 當name不存在時,後面的就再也不執行
    user?.name?.firstName
    複製代碼
    1. delete 運算符
    delete a?.b
    // 等同於
    a == null ? undefined : delete a.b
    複製代碼
    • 上面代碼中,若是aundefinednull,會直接返回undefined,而不會進行delete運算。
    1. 括號的影響
    • 若是屬性鏈有圓括號,鏈判斷運算符對圓括號外部沒有影響,只對圓括號內部有影響。
    (a?.b).c
    // 等價於
    (a == null ? undefined : a.b).c
    複製代碼
    1. 報錯場合
    • 如下寫法是禁止的,會報錯。
    // 構造函數
    new a?.()
    new a?.b()
    
    // 鏈判斷運算符的右側有模板字符串
    a?.`{b}`
    a?.b`{c}`
    
    // 鏈判斷運算符的左側是 super
    super?.()
    super?.foo
    
    // 鏈運算符用於賦值運算符左側
    a?.b = c
    複製代碼
    1. 右側不得爲十進制數值
    • 爲了保證兼容之前的代碼,容許foo?.3:0被解析成foo ? .3 : 0,所以規定若是?.後面緊跟一個十進制數字,那麼?.再也不被當作是一個完整的運算符,而會按照三元運算符進行處理,也就是說,那個小數點會歸屬於後面的十進制數字,造成一個小數。

Null運算判斷符??

  • 讀取對象屬性的時候,若是某個屬性的值是nullundefined,有時候須要爲它們指定默認值。常見作法是經過||運算符指定默認值。
  • ||運算符當左邊爲空字符串或者false的時候也會設置默認值,??運算符只有當左邊是nullundefined纔會設置默認值。
// || 運算
const a = '';
b = a || 1;
console.log(b) // 1

// ?? 運算
b = a ?? 1;
console.log(b) // ''
複製代碼
  • ??有一個運算優先級問題,它與&&||的優先級孰高孰低,因此在一塊兒使用時必須用括號代表優先級,不然會報錯。

Object.is()

  • 用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
複製代碼
  • 不一樣之處只有兩個:一是+0不等於-0,二是NaN等於自身。
+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
複製代碼

Object.assign

  • 用於對象的合併,將源對象(source)的全部自身屬性(不拷貝繼承屬性)、自身的Symbol值屬性和可枚舉屬性(屬性的enumerabletrue),複製到目標對象(target)。第一個參數爲目標對象,後面的都是源對象。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
// 拷貝Symbol值屬性
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
複製代碼
  • 傳入nullundefined會報錯。
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
複製代碼
  • 注意點
    1. Object.assign方法實行的是淺拷貝,而不是深拷貝。
    2. 遇到同名屬性,Object.assign的處理方法是替換,而不是添加。
    3. 數組的處理
      • Object.assign能夠用來處理數組,可是會把數組視爲對象
      Object.assign([1, 2, 3], [4, 5])
      // [4, 5, 3]
      複製代碼
    4. 取值函數的處理
    • Object.assign只能進行值的複製,若是要複製的值是一個取值函數,那麼將求值後再複製。
    const source = {
      get foo() { return 1 }
    };
    const target = {};
    Object.assign(target, source)
    // { foo: 1 }
    複製代碼

Object.keys(),Object.values(),Object.entries()

  1. Object.keys()
  • ES5 引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵名。咱們若是隻須要拿對象的鍵名就可使用這個放。
let obj = { a: 1, b: 2, c: 3 };

for (let key of Object.keys(obj)) {
    console.log(key); // 'a', 'b', 'c'
    console.log(obj[key]) // 1, 2, 3
}
複製代碼
  1. Object.values()
  • 同Object.keys方法,不一樣的是一個返回鍵一個返回值。想經過這個方法拿鍵名就沒有辦法了。
let obj = { a: 1, b: 2, c: 3 };

for (let value of Object.values(obj)) {
    console.log(value); // 1, 2, 3
}

// 數值的鍵名
const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]
複製代碼
  • 屬性名爲數值的屬性,是按照數值大小,從小到大遍歷的,所以返回的順序是bca
  • Object.values會過濾屬性名爲 Symbol 值的屬性。
  1. Object.entries()
  • Object.entries()方法返回一個數,除了返回值類型不同以外,其餘的行爲和Object.values基本一致。
for (let [key, value] of Object.entries(obj)) {
    console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
複製代碼
  • 對於遍歷對象我如今經常使用如下寫法
Object.entries(obj).forEach([key, value] => {
    console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
})
複製代碼

Object.fromEntries()

  • Object.fromEntries()方法是Object.entries()的逆操做,用於將一個鍵值對數組轉爲對象。主要目的,是將鍵值對的數據結構還原爲對象。
Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }
複製代碼
  • Map結構就是一個鍵值對的數組,因此很適用於Map結構轉爲對象。
// 例一
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }
複製代碼
相關文章
相關標籤/搜索