---
# System prepended metadata

title: JS屬性特徵
tags: [Javascript]

---

---
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);
```

![](https://i.imgur.com/XEYKDD4.jpg)


也可以使用`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`

![](https://i.imgur.com/FlRiDq9.jpg)

也可以在陣列原型上使用

```javascript=
var lists = [1, 2, 3];

Object.defineProperty(Array.prototype, 'latest', {
    get: function() {
        return this[this.length - 1];
    }
})

console.log(lists.latest); // 3
```