PJCHENder 未整理筆記

[JS] JavaScript 物件(Object)

2017-10-25

@(JavaScript)

[TOC]

[JS] JavaScript 物件(Object)

常用

1
2
3
4
5
6
7
8
9
10
11
12
Object.keys(obj)                            // 列出物件的所有鍵(需為可列舉的屬性)
Object.values(obj) // 取得物件的所有值
Object.entries(obj) // 把物件的 key 和 value 攤成陣列

let copyObj = { ...obj, weight: 67 }; // 複製物件,並以更新值取代

obj.hasOwnProperty(propertyName) // 檢驗是否為該物件原本就具有的屬性(繼承而來的不會顯示)

Object.create(obj) // 可以用來繼承 obj 的物件
Object.is(value1, value2) // 比較 value1 和 value2 是否為相同的 value

delete object.property // 刪除物件中的屬性

觀念

  • 在 JavaScript 中,差不多所有事物都是物件,除了 nullundefined 以外,其它所有原始類型都可以看成是物件。

Snippets

依據條件指定物件屬性(conditionally assign object key)

keywords: dynamic object key, dynamic object property
1
2
3
4
5
6
7
8
// Will result in { foo: 'foo', bar: 'bar'}
const item = {
foo: 'foo',
... true && { bar: 'bar' },
... false && { falsy: 'falsy' },
}

console.log(item)

將物件扁平化

keywords: array to object, object flatten
1
2
3
4
5
6
7
8
let arr = [{ a: 10 }, { b: 20 }, { c: 30 }]

let obj = arr.reduce((acc, current, index, array) => {
return Object.assign(acc, current)
}, {})
console.log(obj) // { a: 10, b: 20, c: 30 }

console.log({...arr}) // { 0: { a: 10 }, 1: { b: 20 }, 2: { c: 30 } }

簡潔的列出物件的屬性

keywords: object to array, Object.entries()
1
2
3
4
5
6
7
8
9
10
11
const cars = {
BMW: 3,
Tesla: 2,
Toyota: 1
}

Object.entries(cars); // [ [ 'BMW', 3 ], [ 'Tesla', 2 ], [ 'Toyota', 1 ] ]

for(let [key, value] of Object.entries(cars)) {
console.log(key, value);
}

移除物件屬性(immutable, pure function)

1
2
3
4
5
6
7
8
9
10
11
12
13
const obj = {
foo: 'bar',
stack: 'overflow'
};

// delete foo
const { foo, ...newObject } = obj;
console.log(newObject); // { stack: 'overflow' }

// delete foo through dynamic key
const keyToDelete = 'stack';
const { [keyToDelete]: value, ...newObject } = obj;
console.log(newObject); // { foo: 'bar' }

資料來源:Remove a property in an object immutably @ Stack Overflow

物件的擴展(object literal extension)

基本使用

在 ES6 中,我們可以不對物件屬性賦值,它會自動以屬性名稱當作屬性值

1
let obj = {name, country}     // 等同於 obj = {name: name, country: country}

當我們寫:

1
2
3
4
let name = "Aaron";
let country = "Taiwan";
let obj = {name, country}; // 等同於 obj = {name: name, country: country}
console.log(obj); // [Object]{name: "Aaron", country: "Taiwan"}

所以賦值的情況如下:

Imgur

因此,我們可以想像,如果我們寫成這樣:

1
2
3
let website = "pjchender";
let obj = {abc: website};
console.log(obj); // [object]{abc: "pjchender}

要留意的是如果有直接在 object literal 中賦值,會覆蓋掉在更上面時所做的宣告

1
2
3
4
5
6
7
8
9
let name = "PJCHENder";              // 會被覆蓋
let country = "Taiwan";

let obj_es6 = {
name: "Aaron",
country,
}

console.log(obj_es6); // [Object]{name: "Aaron", country: "Taiwan"}

簡寫物件中的函式/方法

1
2
3
4
5
6
7
let obj = {
location () { // 等同於 location: function () { ... }
// ...
}
}

obj.location()

允許將表達式作為屬性名稱(動態賦予屬性名稱)

keywords: dynamic object property key

允許將表達式作為屬性的名稱,只需要使用 [ ] 就可以了:

1
2
3
4
5
6
7
8
9
let websiteName = "pjchender";
let a = 2;
let b = 3;

let obj_es = {
[websiteName]: "welcome",
[a+b]: "sumNumber"
}
console.log(obj_es); // [Object]{5: "sumNumber", pjchender: "welcome"}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let data = [
{
name: 'Aaron',
height: '171'
},{
name: 'Lynn',
height: '160'
}
]

let obj = data.map(item => {
return {[item.name]: item.height}
})

console.log(obj) // [ { Aaron: '171' }, { Lynn: '160' } ]

物件的解構賦值(object destructuring)

概念

陣列的解構賦值強調的順序,而物件的解構賦值強調的則是屬性名稱,屬性名稱必須相互對應才能夠取得到值。 在物件解構賦值中,冒號前是用來對應物件的屬性名稱,冒號後才是真正建立的變數名稱和被賦值的對象

1
2
3
4
let {website, country}                   = {website: 'pjchender', country: 'Taiwan'};
let {website: website, country: country} = {website: 'pjchender', country: 'Taiwan'}; // 等同於
console.log(website); // pjchender
console.log(country); // Taiwan
1
2
3
4
5
// 變數物件後面的值才是真正被建立和賦值的對象
let {website: wb, country: ct} = {website: 'pjchender', country: 'Taiwan'};

console.log(website, country); // website in not defined
console.log(wb, ct) // "pjchender", "Taiwan"

Imgur

為變數建立預設值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let {name, country = 'Taiwan'} = {}
name // undefined
country // 'Taiwan'

var {x = 3} = {};
x // 3

var {x, y = 5} = {x: 1};
x // 1
y // 5

var {x:y = 3} = {};
y // 3

var {x:y = 3} = {x: 5};
y // 5

var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"

物件的解構賦值 @ 阮一峰

物件解構賦值的用途和使用時機

快速取出 JSON 物件的屬性

1
2
3
4
5
6
7
8
9
10
11
12
let data_JSON = {
id: 74,
website: "pjchender",
country: "Taiwan",
detail:{
add: "Tainan",
phone: "0933333333"
}
}

let {id, website, country, detail} = data_JSON;
console.log(id, website, country, detail);

在函式時取得想要的物件屬序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// @airbnb 5.1
let user = {
firstName: 'Aaron',
lastName: 'Chen',
detail: {
height: 171,
weight: 68
}
}

// good
function getFullName (user) {
const { firstName, lastName } = user
return `${firstName} ${lastName}`
}

// best
function getFullName ({firstName, lastName}) {
return `${firstName} ${lastName}`
}

在 callback function 中取得所需的屬性

1
2
3
4
5
6
7
8
9
10
let arr = [
{ name: 'Aaron', age: 28 },
{ name: 'John', age: 32 },
{ name: 'Albert', age: 26 },
]

// 直接取得物件的屬性
arr.forEach(({name, age}) => console.log(`${name} is ${age} year's old`));
// 只取得想要使用的屬性
arr.map(({name}) => name); // [ 'Aaron', 'John', 'Albert' ]

物件常用方法

避免改變原本的物件(immutable)

使用展開語法(spread syntax)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 使用避免改變原本物件的方法
let car = { type: 'vehicle ', wheels: 4};

// 使用展開語法(spread syntax)
let fordGtWithSpread = {
make: 'Ford', // 添加新的屬性
...car,
wheels: 3 // 覆蓋原有的屬性
};
console.log(fordGt); // {make: "Ford", type: "vehicle ", wheels: 3}

// 使用 Object.assign
let fordGtWithObjectAssign = Object.assign({}, car, {make: 'Ford', wheels: 3});
console.log(fordGtWithObjectAssign); //{type: "vehicle ", wheels: 3, make: "Ford"}

檢驗物件使否有該屬性

keywords: Object.getOwnPropertyNames(<obj>), <obj>.hasOwnProperty(<key>), Object.keys(<obj>)
  • Object.getOwnPropertyNames(<obj>):列出該物件所就具有的屬性(不可列舉的仍會顯示,但繼承而來的不會顯示)。
  • <obj>.hasOwnProperty(<key>):檢驗是否為該物件原本就具有的屬性(不可列舉的仍會顯示,但繼承而來的不會顯示)。
  • Object.keys(<obj>):以陣列的方式列出該物件可列舉的屬性鍵
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 定義一個 function constructor
function Person(firstName, lastName){
this.firstName = firstName;
this.lastName = lastName;
this.say_hello = function(){
return `Hello, ${firstName} ${lastName}`
}
}

// 該 function constructor 繼承一個 getFullName 方法
Person.prototype.getFullName = function(){
return `Your name is ${this.firstName} ${this.lastName}`
}

// 利用 function constructor 建立一個 aaron 物件
aaron = new Person("Aaron", "Chen");

// 為 aaron 物件定義一個 not enumerable 的屬性 'height'
Object.defineProperty(aaron, 'height', {
value: 171,
enumerable: false
})

// obj.hasOwnProperty(<key>) 只要是非繼承而來的屬性鍵都回傳 true,不論是否為 enumerable
aaron.hasOwnProperty('firstName') // true, enumerable
aaron.hasOwnProperty('height') // true, non-enumerable
aaron.hasOwnProperty('getFullName') // false, inheritance

// Object.getOwnPropertyNames(<obj>) 會列出所有非繼承而來的屬性鍵,不論是否為 enumerable
Object.getOwnPropertyNames(aaron) // [ 'firstName', 'lastName', 'say_hello', 'height' ]

// Object.keys(<obj>) 列出所有 enumerable 和 non-inheritance 的屬性鍵
Object.keys(aaron) // [ 'firstName', 'lastName', 'say_hello' ]

Object.assign({}, obj ):複製物件

keywords: copy object

用來複製一個或多個物件自身所有可數的屬性到另一個標的物件,如果在標的物件裡的屬性名稱(key)和來源物件的屬性名稱相同,將會被覆寫。若來源物件之間又有相同的屬性名稱,則後者會將前者覆寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* Object.assign(target, ...sources)
* @airbnb 3.8
**/
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };

var clonedObj = { ...obj1 }; // Object { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 }; // Object { foo: "baz", x: 42, y: 13 }

// bad: 使用 Object.assign() 會觸發 setters,而 spread syntax 不會
objCopy = Object.assign({}, obj, {height: 173})
console.log(objCopy) // { name: 'Aaron', height: 173}

Object.create(obj):根據指定的 prototype 來建立新的物件

透過 Object.create(obj) 的方式,我們可以省去先建立建構函式(constructors)才建立物件的麻煩:

1
2
3
4
5
6
7
8
9
let person = {
firstName: 'Aaron',
lastName: 'Chen',
greet: function () {
console.log(`${this.firstName} ${this.lastName}`)
}
}

let john = Object.create(person) // John 會繼承 person 這個物件

Object.getOwnPropertyDescriptors(obj, prop)

透過 Object.getOwnPropertyDescriptors 可以取得一個物件中的所有屬性,包含 gettersetter 在內。

Object.getOwnPropertyDescriptions @ MDN

Object.defineProperty(obj, prop, descriptor):定義物件中的屬性內容

keywords: configurable, enumerable, writable, value, set, get
1
Object.defineProperty(obj, prop, descriptor)

Object.defineProperty() @ MDN - Web technology for developers

物件中的**屬性描述器(Property descriptors)**主要分成兩種-資料描述器(data descriptors)提取描述器(accessor descriptors)

資料描述器(data descriptors)是有值的屬性,這個屬性可能可以被修改(writable);**提取描述器(accessor descriptors)**則是透過 getter-setter 的函式來描述該屬性。描述器只能是 data descriptors 或 accessor descriptors 其中一種,不能同時是兩種。

不論是資料描述器或提取描述器都是物件,並且都包含相同的鍵:

  • configurable: object descriptor 的設定能否被改變或刪除。預設 false
  • enumerable: 該描述器是否會在被列舉的(for...in, Object.keys())過程中顯示。預設 false

data descriptor 包含以下選擇性(optional)的鍵:

  • value:屬性的值。預設 undefined
  • writable:能否透過 = 改變該屬性值。預設 false

accessor descriptor 包含以下選擇性(optional)的鍵:

  • get:可以作為該屬性的 getter ,預設是 undefined
  • set:可以作為該屬性的 setter ,預設是 undefined

為了要確保這些預設值都有被保留,你可以凍結(freeze)Object.prototype、明確的定義所有的項目、或者使用 Object.create(null)

1
2
3
4
5
6
7
// data descriptor with default value
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: undefined
});

建立物件屬性(Creating a property)

當所定義的屬性不存在物件中時,Object.defineProperty() 會建立一個新的屬性:

資料描述器(Data Descriptor)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Data Descriptor
* 一般物件以 Object Literal 定義物件屬性時的設定會像下述情況
**/
var o = {}; // Creates a new object

Object.defineProperty(o, 'a', {
value: 37,
writable: true, // 設為 false 則無法改變該屬性值
enumerable: true, // 如果設為 false,則 o => { }
configurable: true
});

console.log(o) // { a: 37 }
console.log(o.a) // 37
提取描述器(Accessor Descriptor)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* Accessor Descriptor
**/
let o = {}

var bValue = 38;
Object.defineProperty(o, 'b', {
get() { return bValue; },
set(newValue) { bValue = newValue; },
enumerable: true,
configurable: true
});
console.log(o.b) // 38
o.b = 18;
console.log(o.b) // 18

改變物件屬性(Modifying a property)

當物件屬性已經存在時,Object.defineProperty() 會根據 descriptor 中的 value 和物件當時的 configuration 來試著改變物件屬性。如果舊的描述器中的 configurablefalse,則稱作 non-configurable,此時沒有屬性可以被更改,也沒辦法將描述器在 data 和 accessor 中轉換。

non-configurable:當物件的 configurable 被設成 false 時,稱作 “non-configurable”,此時沒辦法修改該物件的 descriptor。

和 descriptor 其他相關的 methods

  • Object.preventExtensions(obj)
  • Object.isExtensible(obj)
  • obj.propertyIsEnumerable(prop)
  • Object.getOwnPropertyDescriptor(obj, prop)
  • Object.prototype.propertyIsEnumerable(prop)
1
2
3
4
5
Object.preventExtensions(<obj>)               // 用來避免物件被新增新的屬性
Object.isExtensible(<obj>) // 檢驗物件可否擴充(新增屬性)
<obj>.propertyIsEnumerable(<prop>) // 檢驗該物件的某 key 能否在疊代時顯示
Object.getOwnPropertyDescriptor(<obj>, <prop>) // 取得某物件屬性的 descriptor
Object.prototype.propertyIsEnumerable(<prop>) // 檢驗該物件的屬性鍵是否為 enumerable

Extensible:當一個物件不是 extensible 的時候,表示沒辦法為該物件新增屬性。

封裝物件的方法

keywords: Object.preventExtensions(obj), Object.seal(obj), Object.freeze(obj)
方法 extensible writable enumerable configurable
Object.preventExtensions(obj) false true true true
Object.seal(obj) false true true false
Object.freeze(obj) false false true false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var o = {
name: 'Foobar'
}

/**
* 使用 Object.preventExtensions(obj) 時:
* 不可新增屬性(non-extensible)
* writable: true, enumerable: true, configurable: true
**/
Object.isExtensible(o) // true

Object.preventExtensions(o)
Object.isExtensible(o) // false
Object.isSealed(o) // false
Object.isFrozen(o) // false
Object.getOwnPropertyDescriptor(o, 'name')
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 使用 Object.seal(obj) 時:
* 不可新增屬性(non-extensible)
* writable: true, enumerable: true, configurable: false
**/

Object.isExtensible(o) // true

Object.seal(o)
Object.isExtensible(o) // false
Object.isSealed(o) // true
Object.isFrozen(o) // false
Object.getOwnPropertyDescriptor(o, 'name')
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 使用 Object.freeze(obj) 時:
* 不可新增屬性(non-extensible)
* writable: false, enumerable: true, configurable: false
**/

Object.isExtensible(o) // true

Object.freeze(o)
Object.isExtensible(o) // false
Object.isSealed(o) // true
Object.isFrozen(o) // true
Object.getOwnPropertyDescriptor(o, 'name')

繼承相關

keywords: Object.getPrototypeOf(obj), obj.constructor
1
2
Object.getPrototypeOf(obj)          // 取得某一物件的 __proto__
obj.constructor // 取得某物件的建構式名稱

getter/setter

在使用 object literal notation 在定義 getters 和 setters 時,你只需要在方法前使用 getset 即可,其中 get 不需要填入任何參數;set 則可以填入取得的新值作為參數:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let obj = {
name: 'Aaron',
interest: [],
get name () {
return 'The name property is forbidden to get'
},
set setInterest (newVal) {
this.interest.push(newVal)
}
}

obj.name = 'Aaron'
obj.setInterest = 'Programming' // setter

console.log(obj)
/*
{
name: [Getter],
interest: [ 'Programming' ],
setInterest: [Setter]
}
*/

console.log(obj.name) // "The name property is forbidden to get"
console.log(obj.interest) // [ 'Programming' ]

要直接在物件中使用 getset 或者是透過 Object.defineProperty() 來定義 getter 和 setter 取決於寫程式的風格和當前的任務。如果你在定義 prototype 時使用的是 object literal notation (object initializer),那麼可能會像這裡一樣,直接在物件中使用 setget;但如果你在定義物件的當下沒有設定,或者想要稍後設定,那麼可以使用 Object.defineProperty()

刪除物件屬性 delete

1
2
3
4
5
6
7
obj = {
firstName: 'Aaron',
lastName: 'Chen'
}

delete obj.firstName;
console.log ("firstName" in obj) // yields "false"

物件屬性的順序

物件中屬性的順序取決於屬性的類型和它的值,一般來說數值會在最前面、接著是字串、最後是 Symbol:

1
2
3
4
5
6
7
8
9
10
var obj = {
'2': 'integer: 2',
'foo': 'string: foo',
'01': 'string: 01',
1: 'integer: 1',
[Symbol('first')]: 'symbol: first'
};

Object.keys(obj); // ["1", "2", "foo", "01"];
Reflect.ownKeys(obj); // ["1", "2", "foo", "01", Symbol(first)]

Code Style

使用 ES6 提供的縮寫

@ airbnb 3.5

1
2
3
4
5
6
7
8
// good
const atom = {
value: 1,

addValue (value) {
return atom.value + value;
},
};
1
2
3
4
5
6
7
8
9
// good,使用縮寫的放在最上面
const obj = {
lukeSkyWalker,
lucySkyWalker,
episodeOne: 1,
twoJediWalkIntoACantina: 2,
episodeThree: 3,
mayTheFourth: 4,
};

不要直接呼叫 Object.prototype 的方法

@ airbnb 3.7

1
2
3
4
5
6
7
8
9
10
11
12
// bad
console.log(object.hasOwnProperty(key));

// good
console.log(Object.prototype.hasOwnProperty.call(object, key));

// best
const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope.
/* or */
import has from 'has';
// ...
console.log(has.call(object, key));

參考

掃描二維條碼,分享此文章