---
tags: Javascript
---
# JS屬性特徵
## 屬性特徵是甚麼
我們可以使用`Object.defineProperty`調整屬性特徵,但`defineProperty`不能對子屬性造成限制,屬於**淺層保護**
可以調整特徵為:
1. 值
1. 可否寫入
1. 可否被刪除
1. 可否被列舉
此種用法比較常在大型專案使用到,例如[Vue](https://github.com/vuejs/vue/blob/f7d85976371efb8f868f069625727acd0ec412cf/src/core/observer/index.js#L157)
列舉以下範例說明,可調整物件屬性的特徵,如果`writable`為`false`,對此屬性進行賦值,將會失敗,會有靜默的錯誤,可以在**嚴格模式**下查看,如想一次定義大量屬性特徵,可以使用`defineProperties`
```javascript=
var example = {
a: 1,
b: 2,
c: 3
}
// 可設定物件屬性特徵
Object.defineProperty(example, 'a', {
value: 4, // 設值
writable: false, // 可否寫入
configurable: true, // 可否被刪除
enumerable: true // 可否被列舉
})
// 賦值
example.a = 10;
// 刪除
delete example.a
// 列舉
for (var key in example){
console.log(`列舉${key}`)
}
(function(){
'use strict';
example.a = 10; // 報錯: 不可對於不可寫入的值進行賦值
}())
// 定義大量屬性範例
Object.definePropreties(example, {
a: {
configurable: false, // 可否被刪除
},
b: {
writable: false, // 可否寫入
}
})
```
## 屬性延伸方法
### 防止擴充`preventExtensions`
[`Object.preventExtensions()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Object/preventExtensions) 可用來避免物件被新增新的屬性,物件如果可以被增加新的屬性,我們稱它可以被擴充`(extensible)`。
`Object.preventExtensions()` 標註物件使它無法被擴充,所以在它被標註為無法擴充當下,它將無法再增加新的屬性。
```javascript=
var example = {
a: 1,
b: 2,
c: 3
}
Object.preventExtensions(example);
console.log(`是否可被擴充${Object.isExtensible(example)}`); // 是否可被擴充false
// Object.defineProperty throws 當為無法擴充的物件增加屬性
Object.defineProperty(example, 'new', { value: 8675309 }); // throws a TypeError
```
### 封裝`seal`
[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/seal): `Object.seal()`方法封閉一個對象,阻止添加新屬性並將所有現有屬性標記為不可配置。當前屬性的值只要原來是可寫的就可以改變。
簡單來說就是無法新增刪除擴充,也無法配置特性,但如果原屬性值可寫入就可調整該屬性值
```javascript=
var example = {
a: 1,
b: 2,
c: 3
}
Object.seal(example);
console.log(`是否可被擴充${Object.isExtensible(example)}`); // 是否可被擴充false
Object.defineProperty(example, 'new', { value: 8675309 }); // 錯誤不可擴充
// 可改變屬性值
example.a = 111;
console.log(example.a); // 輸出: 111
```
### 凍結`freeze`
[MDN](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze): `Object.freeze()` 方法可以凍結一個對象。一個被凍結的對象再也不能被修改;凍結了一個對象則不能向這個對象添加新的屬性,不能刪除已有屬性,不能修改該對像已有屬性的可枚舉性、可配置性、可寫性,以及不能修改已有屬性的值。此外,凍結一個對像後該對象的原型也不能被修改。 `freeze()` 返回和傳入的參數相同的對象。
簡單來說就是,物件加上`seal`,並且無法更改值,原型也不能被修改。
```javascript=
var example = {
a: 1,
b: 2,
c: 3
}
Object.freeze(example);
console.log(`是否可被擴充${Object.isExtensible(example)}`); // 是否可被擴充false
Object.defineProperty(example, 'new', { value: 8675309 }); // Cannot define property new, object is not extensible
example.a = 111; // 嚴格模式下會拋出錯誤
console.log(example.a); // 輸出: 1
```
## 屬性列舉與原型關係
如果我們使用建構函式建立一個物件,使用`for`將物件屬性輸出出來看的時候,會發現原型的屬性也一併輸出出來,
為什麼會如此可以查看他們原型的屬性,`joe`的name屬性特徵,可以發現它是可以被列舉的,因此才會輸出
```javascript=
function Person() {};
Person.prototype.name = "人類";
// 可將name設為不可列舉
Object.defineProperty(Person.prototype, 'name', {
enumerable: false // 可否被列舉
})
var joe = new Person();
joe.a = 1;
// 會輸出a name
for (var key in joe){
console.log(`key:${key}`);
}
console.log(Object.getOwnPropertyDescriptor(joe.__proto__, 'name')); // 查看name屬性特徵,可以發現可列舉
console.log(Object.getOwnPropertyDescriptor(joe.__proto__.__proto__, 'toString')); // 查看toString屬性特徵,可發現不可列舉
// 如果只需要當前物件屬性,可以在迴圈使用hasOwnProperty確認,
// 或是將屬性enumerable設為false
for (var key in joe){
if (joe.hasOwnProperty(key)){
console.log(`key:${key}`);
}
}
```
## Getter和Setter
[`set`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/set) 語法會在物件屬性被嘗試定義時,將其屬性綁定到要呼叫的函式內。
[`get`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Functions/get) 語法會將物件屬性,綁定到屬性被檢索時,所呼叫的函式。
從以下範例可以看到可以透過`set`跟`get`賦值運算不使用函式
```javascript=
var wallet = {
total: 100,
set save(price) {
this.total = this.total + price - 5;
},
get save() {
return this.total / 2;
}
}
wallet.save = 500;
console.log(wallet);
```

也可以使用`Object.defineProperty`的方式賦予
```javascript=
var wallet = {
total: 100,
}
Object.defineProperty(wallet, 'save', {
set: function(price) {
this.total = this.total + price - 5;
},
get: function() {
return this.total / 2;
}
})
wallet.save = 500;
console.log(wallet);
console.log(Object.getOwnPropertyDescriptor(wallet, 'save'));
```
輸出如下可以看到屬性特徵被設為不可刪除及不可列舉,如有需要也可在`defineProperty`將他更改為`true`

也可以在陣列原型上使用
```javascript=
var lists = [1, 2, 3];
Object.defineProperty(Array.prototype, 'latest', {
get: function() {
return this[this.length - 1];
}
})
console.log(lists.latest); // 3
```