天天看點

《六》ES6中對象的擴充對象的擴充運算符:鍊判斷運算符:Null判斷運算符:Object.is():Object.assign():Object.getOwnPropertyDescriptors():Object.keys()、Object.values()、Object.entries():Object.fromEntries():

屬性名表達式:

JavaScript定義對象的屬性,有兩種方法:一是直接用辨別符作為屬性名,二是用表達式作為屬性名,這時要将表達式放在方括号之内。

// 方法一
obj.foo = true;

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

但是,如果使用字面量方式定義對象,在ES5中隻能使用方法一定義屬性。

var obj = {
  foo: true,
  abc: 123
};
           

ES6允許字面量定義對象時,用方法二作為對象的屬性名,即把表達式放在方括号内。

let propKey = 'foo';

let obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
           

屬性的簡潔表示法:

ES6允許在大括号裡面,直接寫入變量和函數,作為對象的屬性和方法。這樣的書寫更加簡潔。

let name = 'Lee'
const Person = {
  name,
  hello() { console.log('我的名字是', this.name); }
};

// 等同于
const Person = {
  name : 'Lee',
  hello : fucntion() { console.log('我的名字是', this.name); }
};
           

屬性的可枚舉性和周遊:

屬性的可枚舉性:

對象的每個屬性都有一個描述對象Descriptor,用來控制該屬性的行為。Object.getOwnPropertyDescriptor()方法可以擷取該屬性的描述對象。描述對象的enumerable屬性,稱為"可枚舉性",如果該屬性為false,就表示某些操作會忽略目前屬性。

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

目前,有四個操作會忽略enumerable為false的屬性:

  1. for…in循環:隻周遊對象自身的和繼承的可枚舉的屬性。
  2. Object.keys():傳回對象自身的所有可枚舉的屬性的鍵名。
  3. JSON.stringify():隻串化對象自身的可枚舉的屬性。
  4. Object.assign():忽略enumerable為false的屬性,隻拷貝對象自身的可枚舉的屬性。

屬性的周遊:

ES6一共有5種方法可以周遊對象的屬性:

  1. for…in:for…in循環周遊對象自身的和繼承的可枚舉屬性(不含Symbol屬性)。
  2. Object.keys():傳回一個數組,包括對象自身的所有可枚舉屬性的鍵名(不含Symbol屬性)。
  3. Object.getOwnPropertyNames(obj):傳回一個數組,包含對象自身的所有可枚舉屬性的鍵名(不含Symbol屬性)。
  4. Object.getOwnPropertySymbols(obj):傳回一個數組,包含對象自身的所有Symbol屬性的鍵名。
  5. Reflect.ownKeys(obj):傳回一個數組,包含對象自身的所有鍵名,不管鍵名是Symbol還是字元串,也不關是否可枚舉。

以上5種方法周遊對象的鍵名,都遵守同樣的屬性周遊的次序規則:首先周遊所有的數值鍵,按照數值升序排列;其次周遊所有字元串鍵,按照加入時間升序排列;最後周遊所有Symbol鍵,按照加入時間升序排列。

Reflect.ownKeys({ [Symbol()]:0, b:0, 10:0, 2:0, a:0 })
// ['2', '10', 'b', 'a', Symbol()]
           

對象的擴充運算符:

對象的擴充運算符用于取出參數對象的所有可周遊屬性,拷貝到目前對象之中。

對象的擴充運算符等同于使用Object.assign()方法,也是淺拷貝。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
           

如果對象的擴充運算符後面是一個空對象,則沒有任何效果。

{...{}, a: 1}
// { a: 1 }
           

如果對象的擴充運算符後面不是對象,則會自動将其轉換為對象。如果對象的擴充運算符後面是字元串,它會自動轉成一個類似數組的對象,是以傳回的不是空對象。如果對象的擴充運算符後面是數值、布爾值、null、undefined,由于轉成的對象沒有自身屬性,是以傳回一個空對象。

{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

// 等同于 {...Object(1)}
{...1} // {}

// 等同于 {...Object(true)}
{...true} // {}

// 等同于 {...Object(undefined)}
{...undefined} // {}

// 等同于 {...Object(null)}
{...null} // {}
           

由于數組是特殊的對象,是以對象的擴充運算符也可以用于數組。

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

鍊判斷運算符:

如果讀取對象内部的某個屬性,往往需要判斷一下該對象是否存在。比如,要讀取message.body.user.firstName,安全的寫法是寫成下面這樣:

const firstName = (message
  && message.body
  && message.body.user
  && message.body.user.firstName) || 'default';
           

這樣的層層判斷非常麻煩,是以ES2020引入了鍊判斷運算符

?.

,直接在鍊式調用的時候判斷左側的對象是否為null或undefined,如果是的,就不再往下運算,而是傳回undefined。

const firstName = message?.body?.user?.firstName || 'default';
           

Null判斷運算符:

讀取對象屬性的時候,如果某個屬性的值是null或undefined,有時候需要為它們指定預設值,常見的做法是通過

||

運算符指定預設值。

const headerText = response.settings.headerText || 'Hello, world!';
           

但是這樣寫是錯的。開發者的原意是,隻要屬性的值為null或undefined,預設值就會生效,但是屬性的值如果為空字元串或false或0,預設值也會生效。

為了避免這種情況,ES2020引入了一個新的Null判斷運算符

??

,它的行為類似

||

,但是隻有運算符左側的值為null或undefined時,才會傳回右側的值。

const headerText = response.settings.headerText ?? 'Hello, world!';
           

Object.is():

ES5比較兩個值是否相等,隻有兩個運算符:相等運算符和全等運算符。它們都有缺點,前者會自動轉換資料類型;後者的NaN不等于自身,以及+0等于-0。

ES6的Object.is()方法用來比較兩個值是否嚴格相等,與全等運算符的行為基本一緻,不同之處隻有兩個:一是+0不等于-0,二是NaN等于自身。

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
           

Object.assign():

Object.assign()方法用于對象的合并,将源對象的所有可枚舉屬性,複制到目标對象。Object.assign()方法的第一個參數是目标對象,後面的參數都是源對象。如果隻有一個參數,Object.assign()方法會直接傳回該參數。

Object.assign()方法實行的是淺拷貝,而不是深拷貝。

Object.assign()方法拷貝的屬性是有限制的,隻拷貝源對象的自身可枚舉屬性,不拷貝繼承屬性,也不拷貝不可枚舉屬性。

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
           

如果第一個參數不是對象,則會先轉成對象,再傳回。由于undefined和null無法轉成對象,是以如果它們作為參數,就會報錯。這是因為隻有字元串的包裝對象會産生可枚舉屬性。

typeof Object.assign(2) // "object"
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
           

如果非對象參數出現在源對象的位置,那麼隻有字元串會以數組形式,拷貝入目标對象,其他值都不會産生效果。

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" }

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
           

如果目标對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆寫前面的。

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.getOwnPropertyDescriptors():

ES2017引入了Object.getOwnPropertyDescriptors()方法,傳回指定對象所有自身屬性(非繼承屬性)的描述對象。Object.getOwnPropertyDescriptors()方法傳回一個對象,所有原對象的屬性名都是該對象的屬性名,對應的屬性值就是該屬性的描述對象。

const obj = {
  foo: 123,
  get  bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }
           

Object.keys()、Object.values()、Object.entries():

Object.keys()方法傳回一個數組,成員是參數對象自身的(不含繼承的)所有可周遊屬性的鍵名。

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

Object.values()方法傳回一個數組,成員是參數對象自身的(不含繼承的)所有可周遊屬性的鍵值。

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

Object.entries()方法傳回一個數組,成員是參數對象自身的(不含繼承的)所有可周遊屬性的鍵值對數組。

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 }
           
es6