ES6學習

初次發佈文章,有什麼錯誤的地方請大佬們多多指教javascript

變量的解構賦值


數組的結構賦值---默認值

注意,ES6 內部使用嚴格相等運算符(===),判斷一個位置是否有值。因此,只有當一個數組成員嚴格等於undefined,默認值纔會生效。java

let [x=1]=[undefined]  //x=1
let [y=1]=[null]    //y=null

上面代碼中,若是一個數組成員是null,默認值就不會生效,由於null不嚴格等於undefinednode

若是默認值是一個表達式,那麼這個表達式是惰性求值的,即只有在用到的時候,纔會求值。栗子:git

fn=()=> { return 'aaa' }
let [y=fn()]=[]   //y='aaa'
let [x=fn()]=[1]  //x=1

上面代碼中,由於x能取到值,因此函數fn根本不會執行。上面的代碼其實等價於下面的代碼。github

let x;
if([1][0]===undefined){
    x=fn()
}else{
    x=[1][0]
}

默認值能夠引用解構賦值的其餘變量,但該變量必須已經聲明。算法

let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError: y is not defined

上面最後一個表達式之因此會報錯,是由於xy作默認值時,y尚未聲明。編程

對象的解構賦值

對象的解構與數組有一個重要的不一樣。數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值。栗子:數組

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"

let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined

若是變量名與屬性名不一致,必須寫成下面這樣。瀏覽器

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz // "aaa"

與數組同樣,解構也能夠用於嵌套結構的對象。注意,下面栗子第二三loc是模式,不是變量,所以不會被賦值,第個纔是賦值數據結構

const node = {
  loc: {
    start: {
      line: 1,
      column: 5
    }
  }
};

let { loc, loc: { start }, loc: { start: { line }} } = node;
line // 1
loc  // Object {start: Object}
start // Object {line: 1, column: 5}
對象解構賦值----注意點

(1)若是要將一個已經聲明的變量用於解構賦值,必須很是當心。

// 錯誤的寫法
let x;
{x} = {x: 1};
// SyntaxError: syntax error

上面代碼的寫法會報錯,由於 JavaScript 引擎會將{x}理解成一個代碼塊,從而發生語法錯誤。只有不將大括號寫在行首,避免 JavaScript 將其解釋爲代碼塊,才能解決這個問題,即用()

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

(2)因爲數組本質是特殊的對象,所以能夠對數組進行對象屬性的解構。

let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
first // 1
last // 3
字符串的解構賦值

字符串也能夠解構賦值。這是由於此時,字符串被轉換成了一個相似數組的對象

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

相似數組的對象都有一個length屬性,所以還能夠對這個屬性解構賦值。

let {length : len} = 'hello';
len // 5
函數參數的解構賦值

undefined就會觸發函數參數的默認值。

[1, undefined, 3].map((x = 'yes') => x);
// [ 1, 'yes', 3 ]
用途

(1)交換變量的值

let x = 1;
let y = 2;

[x, y] = [y, x];

上面代碼交換變量xy的值,這樣的寫法不只簡潔,並且易讀,語義很是清晰。


字符串的擴展

字符串遍歷器

ES6 爲字符串添加了遍歷器接口(詳見《Iterator》一章),使得字符串能夠被for...of循環遍歷。

for (let codePoint of 'foo') {
  console.log(codePoint)
}
// "f"
// "o"
// "o"
模板字符串

模板字符串(template string)是加強版的字符串,用反引號(`)標識。它能夠看成普通字符串使用,也能夠用來定義多行字符串,或者在字符串中嵌入變量。模板字符串中嵌入變量,須要將變量名寫在${}之中。

// 字符串中嵌入變量
let name = "Bob", time = "today";
`Hello ${name}, how are you ${time}?`


// 多行字符串
`In JavaScript this is
 not legal.`

模板字符串之中還能調用函數。

function fn() {
  return "Hello World";
}

`foo ${fn()} bar`
// foo Hello World bar

「標籤模板」的一個重要應用,就是過濾 HTML 字符串,防止用戶輸入惡意內容。


字符串的新增方法

實例方法:includes(), startsWith(), endsWith()

注:includes()也能夠用於數組
傳統上,JavaScript 只有indexOf方法,能夠用來肯定一個字符串是否包含在另外一個字符串中。ES6 又提供了三種新方法。

  • includes( tager,index):返回布爾值,表示是否找到了參數字符串。
  • startsWith(tager,index):返回布爾值,表示參數字符串是否在原字符串的頭部。
  • endsWith(tager,n):返回布爾值,表示參數字符串是否在原字符串的尾部。

其中tager表示要查找的字符串,index表示開始搜索的位置(僅用於includes和startsWith),可省略(默認從0開始)。endsWith的行爲與其餘兩個方法有所不一樣。它針對前n個字符

let s = 'Hello world!';

s.startsWith('world') // true
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
實例方法:repeat()

repeat方法返回一個新字符串,表示將原字符串重複n次。

'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
實例方法:padStart(),padEnd()

ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。padStart()用於頭部補全,padEnd()用於尾部補全。

'x'.padStart(4, 'ab') // 'abax'

'x'.padEnd(5, 'ab') // 'xabab'

若是原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串。

'xxx'.padStart(2, 'ab') // 'xxx'

若是用來補全的字符串與原字符串,二者的長度之和超過了最大長度,則會截去超出位數的補全字符串

'abc'.padStart(10, '0123456789')
// '0123456abc'

若是省略第二個參數,默認使用空格補全長度。

'x'.padStart(4) // '   x'
'x'.padEnd(4) // 'x   '
實例方法:trimStart(),trimEnd()

ES2019 對字符串實例新增了trimStart()trimEnd()這兩個方法。它們的行爲與trim()一致,trimStart()消除字符串頭部的空格,trimEnd()消除尾部的空格。它們返回的都是新字符串,不會修改原始字符串

const s = '  abc  ';

s.trim() // "abc"
s.trimStart() // "abc  "
s.trimEnd() // "  abc"

數值的擴展(number)

Number.isFinite(), Number.isNaN()

ES6 在Number對象上,新提供了Number.isFinite()Number.isNaN()兩個方法。Number.isFinite()用來檢查一個數值是否爲有限的(finite),即不是Infinity

Number.isFinite(15); // true
Number.isFinite(0.8); // true
Number.isFinite(NaN); // false
//Number.isFinite()和isFinite()的區別是isFinite()會將值轉成number類型再執行isFinite(),而Number.isFinite()只針對數值
isFinite(15); // true
isFinite('15'); // true          Number('15')---15
isFinite(true); // true          Number(true)---1
isFinite(false); // true         Number(false)---0
isFinite(null); // true          Number(null)-----0
isFinite(NaN); // false          Number(NaN)-----NaN
isFinite(undefined); // false    Number(NaN)-----NaN
isFinite('str'); // false        Number('str')-----NaN

注意,若是參數類型不是數值Number.isFinite一概返回false

Number.isNaN()用來檢查一個值是否爲NaN

Number.isNaN(NaN) // true
Number.isNaN(15) // false
Number.isNaN('15') // false
Number.isNaN(true) // false
Number.isNaN(9/NaN) // true
Number.isNaN('true' / 0) // true
Number.isNaN('true' / 'true') // true

若是參數類型不是NaNNumber.isNaN一概返回false

它們與傳統的全局方法isFinite()isNaN()的區別在於,傳統方法先調用Number()將非數值的值轉爲數值,再進行判斷,而這兩個新方法只對數值有效

Number.parseInt(), Number.parseFloat()

ES6 將全局方法parseInt()parseFloat(),移植到Number對象上面,行爲徹底保持不變。

// ES5的寫法
parseInt('12.34') // 12
parseFloat('123.45#') // 123.45

// ES6的寫法
Number.parseInt('12.34') // 12
Number.parseFloat('123.45#') // 123.45

這樣作的目的,是逐步減小全局性方法,使得語言逐步模塊化。


函數的擴展

基本用法

參數變量是默認聲明的,因此不能用letconst再次聲明。

function foo(x = 5) {
  let x = 1; // error
  const x = 2; // error
}

使用參數默認值時,函數不能有同名參數。

// 不報錯
function foo(x, x, y) {
  // ...
}

// 報錯--有默認值
function foo(x, x, y = 1) {
  // ...
}

另外,一個容易忽略的地方是,參數默認值不是傳值的,而是每次都從新計算默認值表達式的值。也就是說,參數默認值是惰性求值的。

let x = 99;
function foo(p = x + 1) {
  console.log(p);
}

foo() // 100

x = 100;
foo() // 101

上面代碼中,參數p的默認值是x + 1。這時,每次調用函數foo,都會從新計算x + 1,而不是默認p等於 100。

與解構賦值默認值結合使用
function foo({x, y = 5} = {}) {
  console.log(x, y);
}

foo() // undefined 5
參數默認值的位置

一般狀況下,定義了默認值的參數,應該是函數的尾參數。由於這樣比較容易看出來,到底省略了哪些參數。若是非尾部的參數設置默認值,實際上這個參數是無法省略的。

function f(x = 1, y) {
  return [x, y];
}

f(, 1) // 報錯
f(undefined, 1) // [1, 1]
// 例二
function f(x, y = 5, z) {
  return [x, y, z];
}

f(1, ,2) // 報錯
f(1, undefined, null) // [1, 5, null]

注:若是傳入undefined,將觸發該參數等於默認值,null則沒有這個效果,至關於賦值爲null

做用域

一旦設置了參數的默認值,函數進行聲明初始化時,參數會造成一個單獨的做用域(context)。等到初始化結束,這個做用域就會消失。這種語法行爲,在不設置參數默認值時,是不會出現的

let x = 1;

function f(y = x) {
  let x = 2;
  console.log(y);
}

f() // 1

上面代碼中,函數f調用時,參數y = x造成一個單獨的做用域。這個做用域裏面,變量x自己沒有定義,因此指向外層的全局變量x。函數調用時,函數體內部的局部變量x影響不到默認值變量x
若是此時,全局變量x不存在(沒有let x=1),就會報錯。

下面這樣寫,也會報錯。

var x = 1;

function foo(x = x) {
  // ...
}

foo() // ReferenceError: x is not defined

上面代碼中,參數x = x造成一個單獨做用域。實際執行的是let x = x,因爲暫時性死區的緣由,這行代碼會報錯」x 未定義「。

下面是一個更復雜的例子。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  var x = 3;
  y();
  console.log(x);
}

foo() // 3
x // 1

上面代碼中,函數foo的參數造成一個單獨做用域。這個做用域裏面,首先聲明瞭變量x,而後聲明瞭變量yy的默認值是一個匿名函數。這個匿名函數內部的變量x指向同一個做用域的第一個參數x,。函數foo內部又聲明瞭一個內部變量x,該變量與第一個參數x因爲不是同一個做用域,因此不是同一個變量,所以執行y後,內部變量x和外部全局變量x的值都沒變。

若是將var x = 3var去除,函數foo的內部變量x就指向第一個參數x,與匿名函數內部的x是一致的,因此最後輸出的就是2,而外層的全局變量x依然不受影響。

var x = 1;
function foo(x, y = function() { x = 2; }) {
  x = 3;
  y();
  console.log(x);
}

foo() // 2
x // 1

箭頭函數

ES6 容許使用「箭頭」(=>)定義函數。

var f = () => 5;
// 等同於
var f = function () { return 5 };

因爲大括號被解釋爲代碼塊,因此若是箭頭函數直接返回一個對象,必須在對象外面加上括號,不然會報錯。

// 報錯
let getTempItem = id => { id: id, name: "Temp" };

// 不報錯
let getTempItem = id => ({ id: id, name: "Temp" });

使用注意點
箭頭函數有幾個使用注意點。

(1)函數體內的this對象,就是定義時所在的對象,而不是使用時所在的對象。

(2)不能夠看成構造函數,也就是說,不可使用new命令,不然會拋出一個錯誤。

(3)不可使用arguments對象,該對象在函數體內不存在。若是要用,能夠用 rest 參數代替。

(4)不可使用yield命令,所以箭頭函數不能用做 Generator 函數。

解釋:什麼不能用做構造函數?

new操做的主要步驟:

  1. 建立一個空對象
  2. 將對象的——proto——指向構造函數的prototype(箭頭函數沒有prototype)
  3. 綁定this(箭頭函數沒有單獨的this,this來自上下文所以不能使用call或者apply將this指向新建立的對象
  4. 返回obj對象
尾調用優化

尾調用(Tail Call)是函數式編程的一個重要概念,自己很是簡單,一句話就能說清楚,就是指某個函數的最後一步是調用另外一個函數。

咱們知道,函數調用會在內存造成一個「調用記錄」,又稱「調用幀」(call frame),保存調用位置和內部變量等信息,尾調用因爲是函數的最後一步操做,因此不須要保留外層函數的調用幀。
尾調用必定是 return +方法名()

function f() {
  let m = 1;
  let n = 2;
  return g(m + n);
}
f();

// 等同於
function f() {
  return g(3);
}
f();

// 等同於
g(3);

注意,只有再也不用到外層函數的內部變量,內層函數的調用幀纔會取代外層函數的調用幀,不然就沒法進行「尾調用優化」。

function addOne(a){
  var one = 1;
  function inner(b){
    return b + one;
  }
  return inner(a);
}

上面的函數不會進行尾調用優化,由於內層函數inner用到了外層函數addOne的內部變量one

注意,目前只有 Safari 瀏覽器支持尾調用優化,Chrome 和 Firefox 都不支持。

尾遞歸

函數調用自身,稱爲遞歸。若是尾調用自身,就稱爲尾遞歸。
遞歸很是耗費內存,由於須要同時保存成千上百個調用幀,很容易發生「棧溢出」錯誤(stack overflow)。但對於尾遞歸來講,因爲只存在一個調用幀,因此永遠不會發生「棧溢出」錯誤。

function factorial(n) {
  if (n === 1) return 1;
  return n * factorial(n - 1);
}

factorial(5) // 120

上面代碼是一個階乘函數,計算n的階乘,最多須要保存n個調用記錄,複雜度 O(n) 。

若是改寫成尾遞歸,只保留一個調用記錄,複雜度 O(1) 。

function factorial(n, total) {
  if (n === 1) return total;
  return factorial(n - 1, n * total);
}

factorial(5, 1) // 120
catch 命令的參數省略

JavaScript 語言的try...catch結構,之前明確要求catch命令後面必須跟參數,接受try代碼塊拋出的錯誤對象。

try {
  // ...
} catch (err) {
  // 處理錯誤
}

上面代碼中,catch命令後面帶有參數err

不少時候,catch代碼塊可能用不到這個參數。可是,爲了保證語法正確,仍是必須寫。ES2019 作出了改變,容許catch語句省略參數。

try {
  // ...
} catch {
  // ...
}

數組的擴展

擴展運算符

擴展運算符(spread)是三個點(...)。它比如 rest 參數的逆運算,將一個數組轉爲用逗號分隔的參數序列。

console.log(...[1, 2, 3])
// 1 2 3

擴展運算符後面還能夠放置表達式。

const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];
擴展運算符的應用

複製或者合併數組
這些方法都是淺拷貝,使用的時候須要注意。

//ES5
const a1 = [1, 2];
const a2 = a1.concat();
const a3 = a1.splice();
//ES6
const a1 = [1, 2];
const a2 = [...a1];

與解構賦值結合

// ES5
let list=[1,2,3,4,5,6]
a = list[0], rest = list.slice(1)
// ES6
[a, ...rest] = list   
//a=1 
//rest=[]

字符串/對象/數組
擴展運算符還能夠將字符串轉爲真正的數組。

[...'hello']
// [ "h", "e", "l", "l", "o" ]
let obj={a: 1, b: 2} 
{...obj}  //{a: 1, b: 2}
let arr=[1,2,3,4]
[...arr]   //[1,2,3,4]

實現了 Iterator 接口的對象(Map、Set、Generator函數)
任何定義了遍歷器(Iterator)接口的對象(參閱 Iterator 一章),均可以用擴展運算符轉爲真正的數組。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

上面代碼中,querySelectorAll方法返回的是一個NodeList對象。它不是數組,而是一個相似數組的對象(相似數組對象必須有length屬性)。

Array.from()

Array.from的語法
Array.from(arr).map(fn(),this)
Array.from(arr,()=>{})
第一個參數arr要轉換的值(必須),第二個參數是map方法,若是第二個參數不是箭頭函數用.的方式寫,可接受第三個參數,修改map方法裏面的this指向,若是第二個參數是箭頭函數用,方式寫第二個參數,箭頭函數沒有本身的this,因此不須要第三個參數

Array.from方法用於將兩類對象轉爲真正的數組:相似數組的對象(array-like object)和可遍歷(iterable)的對象(包括 ES6 新增的數據結構 Set 和 Map)。

下面是一個相似數組的對象,Array.from將它轉爲真正的數組。
常見的相似數組的對象是 DOM 操做返回的 NodeList 集合,以及函數內部的arguments對象

let arrayLike = {
    '0': 'a',
    '1': 'b',
    '2': 'c',
    length: 3
};

// ES5的寫法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']

// ES6的寫法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']

只要是部署了 Iterator 接口的數據結構,Array.from都能將其轉爲數組。

Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']

let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']

值得提醒的是,擴展運算符(...)也能夠將某些數據結構轉爲數組。

// arguments對象
function foo() {
  const args = [...arguments];
}

// NodeList對象
[...document.querySelectorAll('div')]

擴展運算符背後調用的是遍歷器接口(Symbol.iterator),若是一個對象沒有部署這個接口,就沒法轉換。Array.from方法還支持相似數組的對象。所謂相似數組的對象,本質特徵只有一點,即必須有length屬性。所以,任何有length屬性的對象,均可以經過Array.from方法轉爲數組,而此時擴展運算符就沒法轉換。

Array.from還能夠接受第二個參數,做用相似於數組的map方法,用來對每一個元素進行處理,將處理後的值放入返回的數組。

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

Array.from([1, 2, 3], (x) => x * x)
// [1, 4, 9]
Array.of()

Array.of方法用於將一組值,轉換爲數組。這個方法的主要目的,是彌補數組構造函數Array()的不足。由於參數個數的不一樣,會致使Array()的行爲有差別。Array()在只有一個參數的時候表示的是長度。

Array.of()  //[]
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array(3) // [, , ,]    
Array.of(3).length // 1
數組實例的 copyWithin()

數組實例的copyWithin()方法,在當前數組內部,將指定位置的成員複製到其餘位置(會覆蓋原有成員),而後返回當前數組。也就是說,使用這個方法,會修改當前數組

Array.prototype.copyWithin(target, start = 0, end = this.length)

它接受三個參數。

  • target(必需):從該位置開始替換數據。若是爲負值,表示倒數
  • start(可選):從該位置開始讀取數據(開始複製),默認爲 0。若是爲負值,表示從末尾開始計算。
  • end(可選):到該位置前中止讀取數據(中止複製,不包括),默認等於數組長度。若是爲負值,表示從末尾開始計算。

這三個參數都應該是數值,若是不是,會自動轉爲數值。

let arr=[1, 2, 3, 4, 5]
arr.copyWithin(0, 2,4)
//從下標爲2的開始複製即從3開始複製,到下標爲4的結束即到5結束(不包括5)
//[3, 4, 3, 4, 5]
數組實例的 find() 和 findIndex()

find()語法

語法一:target.find(fn,this)
語法二:target.find((val,index,arr)=>{}),fn爲箭頭函數

  • fn表示方法
  • this表示方法裏用到的this指向(可省略)
  • val表示當前值
  • index表示下標(可省略)
  • arr表示原數組(可省略)

數組實例的find方法,用於找出第一個符合條件的數組成員。直到找出第一個返回值爲true的成員,而後返回該成員。若是沒有符合條件的成員,則返回undefined

[1, 4, -5, 10].find((n) => n < 0)
// -5

findIndex()

數組實例的findIndex方法的用法與find方法很是相似,返回第一個符合條件的數組成員的位置,若是全部成員都不符合條件,則返回-1

[1, 5, 10, 15].findIndex(function(value, index, arr) {
  return value > 9;
}) // 2
數組實例的 fill()

fill(target,start,end)
fill方法使用給定值,填充一個數組。
注意,若是填充的類型爲對象,那麼被賦值的是同一個內存地址的對象,而不是深拷貝對象。

['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']

let arr = new Array(3).fill({name: "Mike"});
arr[0].name = "Ben";
arr
// [{name: "Ben"}, {name: "Ben"}, {name: "Ben"}]
數組實例的 entries(),keys() 和 values()

能夠用for...of循環進行遍歷,惟一的區別是keys()是對鍵名的遍歷、values()是對鍵值的遍歷,entries()是對鍵值對的遍歷。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"
數組實例的 includes()

語法:includes(target,start),第二個參數表示開始搜索的位置
includes()也能夠用於字符串
Array.prototype.includes方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes方法相似。ES2016 引入了該方法。

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
數組實例的 flat(),flatMap()

flat()語法

  • flat(num),num爲拉平的的層數,默認爲1,若是無論有多少層嵌套,都要轉成一維數組,能夠用Infinity關鍵字做爲參數

數組的成員有時仍是數組,Array.prototype.flat()用於將嵌套的數組「拉平」,變成一維的數組。該方法返回一個新數組,對原數據沒有影響。
若是原數組有空位,flat()方法會跳過空位。

[1, 2, , [4, 5]].flat()
// [1, 2, 4, 5]

flatMap()語法

  • flatMap(fn),fn爲方法

flatMap()方法對原數組的每一個成員執行一個函數,而後對返回值組成的數組執行flat()方法。該方法返回一個新數組,不改變原數組。flatMap()只能展開一層數組。

// 至關於 [[2, 4], [3, 6], [4, 8]].flat()
[2, 3, 4].flatMap((x) => [x, x * 2])
// [2, 4, 3, 6, 4, 8]

對象的擴展

屬性的簡潔表示法

ES6 容許在大括號裏面,直接寫入變量和函數,做爲對象的屬性和方法。

const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}

// 等同於
const baz = {foo: foo};

變量foo直接寫在大括號裏面。這時,屬性名就是變量名, 屬性值就是變量值

除了屬性簡寫,方法也能夠簡寫(對象的方法)。

const o = {
  method() {
    return "Hello!";
  }
};

// 等同於

const o = {
  method: function() {
    return "Hello!";
  }
};

CommonJS 模塊輸出一組變量,就很是合適使用簡潔寫法。

function a(){}
function b(){}
module.exports = { a, b };

注意,簡寫的對象方法不能用做構造函數,會報錯。

const obj = {
  f() {
    this.foo = 'bar';
  }
};

new obj.f() // 報錯

只有function這種寫法的能夠做爲構造函數,非function寫法的屬於簡寫,沒有構造器的方法

屬性名錶達式

JavaScript 定義對象的屬性,有兩種方法。

// 方法一
obj.foo = true;

// 方法二
obj['a' + 'bc'] = 123;

上面代碼的方法一是直接用標識符做爲屬性名,方法二是用表達式做爲屬性名,這時要將表達式放在方括號以內。
屬性的可枚舉性和遍歷

屬性的遍歷

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

(1)for...in

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

(2)Object.keys(obj)

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

(3)Object.getOwnPropertyNames(obj)

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

(4)Object.getOwnPropertySymbols(obj)

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

(5)Reflect.ownKeys(obj)

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

super 關鍵字

咱們知道,this關鍵字老是指向函數所在的當前對象,ES6 又新增了另外一個相似的關鍵字super,指向當前對象的原型對象(不改變this的指向)。

const proto = {
  foo: 'hello'
};

const obj = {
  foo: 'world',
  find() {
    return super.foo;
  }
};

Object.setPrototypeOf(obj, proto);
obj.find() // "hello"

上面代碼中,對象obj.find()方法之中,經過super.foo引用了原型對象protofoo屬性。

注意,super關鍵字表示原型對象時,只能用在對象的方法之中,用在其餘地方都會報錯。

// 報錯
const obj = {
  foo: super.foo
}

// 報錯
const obj = {
  foo: () => super.foo
}

// 報錯
const obj = {
  foo: function () {
    return super.foo
  }
}

上面三種super的用法都會報錯,由於對於 JavaScript 引擎來講,這裏的super都沒有用在對象的方法之中。第一種寫法是super用在屬性裏面,第二種和第三種寫法是super用在一個函數裏面,而後賦值給foo屬性。目前,只有對象方法的簡寫法可讓 JavaScript 引擎確認,定義的是對象的方法。

const proto = {
  x: 'hello',
  foo() {
    console.log(this.x);
  },
};

const obj = {
  x: 'world',
  foo() {
    super.foo();
  }
}

Object.setPrototypeOf(obj, proto);

obj.foo() // "world"

上面代碼中,super.foo指向原型對象protofoo方法,可是綁定的this卻仍是當前對象obj,所以輸出的就是world

對象的擴展運算符

解構賦值
注意,解構賦值的拷貝是淺拷貝,即若是一個鍵的值是複合類型的值(數組、對象、函數)、那麼解構賦值拷貝的是這個值的引用,而不是這個值的副本。

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

另外,擴展運算符的解構賦值,不能複製繼承自原型對象的屬性

let o1 = { a: 1 };
let o2 = { b: 2 };
o2.__proto__ = o1;
let { ...o3 } = o2;
o3 // { b: 2 }
o3.a // undefined

上面代碼中,對象o3複製了o2,可是隻複製了o2自身的屬性,沒有複製它的原型對象o1的屬性。

擴展運算符

對象的擴展運算符(...)用於取出參數對象的全部可遍歷屬性,拷貝到當前對象之中。

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

因爲數組是特殊的對象,因此對象的擴展運算符也能夠用於數組。

let foo = { ...['a', 'b', 'c'] };
foo
// {0: "a", 1: "b", 2: "c"}

對象的擴展運算符等同於使用Object.assign()方法。

let a=['a','b','c']
let aClone = { ...a };
// 等同於
let aClone = Object.assign({}, a);
//{0: "a", 1: "b", 2: "c"}

上面的例子只是拷貝了對象實例的屬性,若是想完整克隆一個對象,還拷貝對象原型的屬性,能夠採用下面的寫法。

// 寫法一
const clone1 = {
  __proto__: Object.getPrototypeOf(obj),
  ...obj
};

// 寫法二
const clone2 = Object.assign(
  Object.create(Object.getPrototypeOf(obj)),
  obj
);

// 寫法三
const clone3 = Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
)

上面代碼中,寫法一的__proto__屬性在非瀏覽器的環境不必定部署,所以推薦使用寫法二和寫法三。

鏈判斷運算符?.

編程實務中,若是讀取對象內部的某個屬性,每每須要判斷一下該對象是否存在。或者使用三元運算符?:,判斷一個對象是否存在。

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';
  
  //鏈式
  const firstName = message?.body?.user?.firstName || 'default';
//三目運算
const fooInput = myForm.querySelector('input[name=foo]')
const fooValue = fooInput ? fooInput.value : undefined
//鏈式
const fooValue = myForm.querySelector('input[name=foo]')?.value

這樣的層層判斷很是麻煩,所以 ES2020 引入了「鏈判斷運算符」(optional chaining operator)?.,簡化上面的寫法。
上面代碼使用了?.運算符,直接在鏈式調用的時候判斷,左側的對象是否爲nullundefined。若是是的,就再也不往下運算,而是返回undefined

鏈判斷運算符有三種用法。

  • obj?.prop // 對象屬性
  • obj?.[expr] // 同上
  • 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()

上面代碼中,特別注意後兩種形式,若是a?.b()裏面的a.b不是函數,不可調用,那麼a?.b()是會報錯的。a?.()也是如此,若是a不是nullundefined,但也不是函數,那麼a?.()會報錯。

Null 判斷運算符??

讀取對象屬性的時候,若是某個屬性的值是nullundefined,有時候須要爲它們指定默認值。常見作法是經過||運算符指定默認值。

const headerText = headerText || 'Hello, world!';

上面的代碼都經過||運算符指定默認值,可是這樣寫是錯的。開發者的原意是,只要屬性的值爲nullundefined,默認值就會生效,可是屬性的值若是爲空字符串或false0,默認值也會生效。

爲了不這種狀況,ES2020 引入了一個新的 Null 判斷運算符??。它的行爲相似||,可是隻有運算符左側的值爲nullundefined時,纔會返回右側的值。

const headerText = headerText ?? 'Hello, world!';

這個運算符的一個目的,就是跟鏈判斷運算符?.配合使用,爲nullundefined的值設置默認值。

const animationDuration = response.settings?.animationDuration ?? 300;

??有一個運算優先級問題,它與&&||的優先級孰高孰低。如今的規則是,若是多個邏輯運算符一塊兒使用,必須用括號代表優先級,不然會報錯。

(lhs && middle) ?? rhs;

對象的新增方法

Object.is()

ES5 比較兩個值(字符串)是否相等,只有兩個運算符:相等運算符(==)和嚴格相等運算符(===)。它們都有缺點,前者會自動轉換數據類型,後者的NaN不等於自身,以及+0等於-0。JavaScript 缺少一種運算,在全部環境中,只要兩個值是同樣的,它們就應該相等。

ES6 提出「Same-value equality」(同值相等)算法,用來解決這個問題。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() ---淺拷貝--合併對象

基本用法

Object.assign方法用於對象的合併,將源對象(source)的全部可枚舉屬性,複製到目標對象(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}

Object.assign方法的第一個參數是目標對象,後面的參數都是源對象。

注意,若是目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。

其餘類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。可是,除了字符串會以數組形式,拷貝入目標對象,其餘值都不會產生效果。

const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

數組的處理
Object.assign能夠用來處理數組,可是會把數組視爲對象。

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

上面代碼中,Object.assign把數組視爲屬性名爲 0、一、2 的對象,所以源數組的 0 號屬性4覆蓋了目標數組的 0 號屬性1

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

Object.keys()

ES5 引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵名。

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

Object.values()
Object.values方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值。

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

Object.entries()
Object.entries()方法返回一個數組,成員是參數對象自身的(不含繼承的)全部可遍歷(enumerable)屬性的鍵值對數組。

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操做,用於將一個鍵值對數組轉爲對象。

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

該方法的主要目的,是將鍵值對的數據結構還原爲對象,所以特別適合將 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 }

ES2017 引入了跟Object.keys配套的Object.valuesObject.entries,做爲遍歷一個對象的補充手段,供for...of循環使用。

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

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

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}
相關文章
相關標籤/搜索