屬性名表達式:
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的屬性:
- for…in循環:隻周遊對象自身的和繼承的可枚舉的屬性。
- Object.keys():傳回對象自身的所有可枚舉的屬性的鍵名。
- JSON.stringify():隻串化對象自身的可枚舉的屬性。
- Object.assign():忽略enumerable為false的屬性,隻拷貝對象自身的可枚舉的屬性。
屬性的周遊:
ES6一共有5種方法可以周遊對象的屬性:
- for…in:for…in循環周遊對象自身的和繼承的可枚舉屬性(不含Symbol屬性)。
- Object.keys():傳回一個數組,包括對象自身的所有可枚舉屬性的鍵名(不含Symbol屬性)。
- Object.getOwnPropertyNames(obj):傳回一個數組,包含對象自身的所有可枚舉屬性的鍵名(不含Symbol屬性)。
- Object.getOwnPropertySymbols(obj):傳回一個數組,包含對象自身的所有Symbol屬性的鍵名。
- 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 }