0%

JS核心篇-屬性特徵

物件屬性特徵

透過Oject.defineProperty 來定義物件的屬性,且可以調整這些屬性的特徵。
那屬性的特徵有哪些呢?
1.值 2.可否寫入 3.可否被刪除 4. 可否被列舉
那可否被列舉是什麼屬性呢?
就是說當我們想利用for來遍歷某個物件內的所有屬性時,
若有個屬性是不可被列舉的話,則它就不會被遍歷到。

Oject.defineProperty 用法

Oject.defineProperty(物件, 屬性, 用法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var person = {
a: 1,
b: 2,
c: 3
}
console.log(person)

Oject.defineProperty(person, 'a', {
value: 4,
writable: true,
configurable: true,
enumerable:true
})

console.log(person) // 此時,會看到person的a屬性被改為4了,那writable, configurable, enumerable 預設值皆為true。

另外,
writable 是可否被重新寫入
configurable 是可否被刪除
enumerable 是可否被列舉

Oject.defineProperty 淺層保護

如果,我們今天對person這個物件新增的子物件d,並將它的writable設為false

1
2
3
4
5
6
Object.defineProperty(person, 'd', {
value: {},
writable: false
})
person.d = 6
console.log(person)

因為,在JS中的物件是傳參考的特性,所以,更動物件是代表要轉換它原本所指向的物件位址,
那不可寫入的話,就代表我們不對該物件做傳換到另外一個位址的動作。
所以,你會發現d這個子物件還是維持原本的空物件狀態,

那如果我們對d新增屬性呢?

1
2
3
4
5
6
Oject.defineProperty(person, 'd', {
value: {},
writable: false
})
person.d.a = 6
console.log(person)

你會發現d確實新增了a屬性,而且它的值是6,而這個就是Oject.defineProperty 淺層保護的體現囉。

Object.defineProperties 插入多個屬性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var person = {
a: 1,
b: 2,
c: 3
}
Object.defineProperties(person, {
a: {
value: 4,
writable: false
},
b: {
value: 6
}
})
person.a = 10
console.log(person)

物件屬性不可寫入?物件擴充的修改與調整

本小節會介紹preventExtentions, seal, freeze 這三個方法。
中文分別代表: 防止擴充、封裝、凍結

Object.preventExtentions(物件)

這個方法的用意,就是不讓指定物件擴充新的屬性。
但是,它是無法防止該物件的巢狀屬性,擴充新的屬性。

1
2
3
4
5
6
7
8
9
10
var person = {
a: 1,
b: 2,
c: {}
}

Object.preventExtensions(person)
person.d = 10 // 不能擴充指定物件的屬性
person.c.a = 'caa' // 可以擴充指定物件的巢狀屬性的新屬性
console.log(person)

Object.seal(物件)

1.
讓指定物件屬性無法新增刪除,也無法重新配置特徵,但是,可以調整目前屬性值
當你使用Object.seal時,程式會自動幫這個指定的物件加上preventExtentions的特性。

1
2
Object.seal(person)
console.log('是否可以被擴充', Object.isExtensible(person)) // false

你可以看到我們沒有特別為person物件加上Object.preventExtensions,但是,它就又不能被擴充的特性。
這就是Object.seal原本就具備的屬性。
2.
Objec.seal是不允許指定物件新增屬性的。
3.
有關於Object.seal指定物件的巢狀屬性部分,他們的value還是可以被更改,但是,他們的屬性特徵是不能被修改的,
也就是不能修改這些巢狀屬性原本的writable, configurable, enumerable 的設定。
4.
但是,指定物件的巢狀屬性還是可以新增他們自己的屬性的。

Object.freeze(物件)

1.
指定物件會自動加上seal特性,且無法調整值
2.
Object.freeze是不允許指定物件新增屬性的。
3.
有關於Object.freeze指定物件的巢狀屬性部分,他們的value是不可以被更改,但是,他們的屬性特徵是不能被修改的,
也就是不能修改這些巢狀屬性原本的writable, configurable, enumerable 的設定。
4.
但是,指定物件的巢狀屬性還是可以新增他們自己的屬性的。

以上這三種方法,都是針對指定物件本身的操作,跟上一小節的Object.defineProperty是針對物件內的屬性做操作是不太一樣的喔。

屬性列舉與原型的關係

這邊要講的是我們自訂的原型 和 原生的原型 有些許不同。

1
2
3
4
5
6
7
8
function Person () {}
Person.prototype.name = '人類'

const ming = new Person ()
ming.a = 10
for(var key in ming) {
console.log(key)
}

以上你可以看到,key除了會引出ming自己的a屬性之外,也會印出Person的name屬性。
這個原因是因為,我們自訂的原型的屬性的enumerabletrue,所以,它會在for遍歷的時候,被印出來。
而原生的原型像是ObjecttoString屬性的enumerablefalse,所以,不會在for遍歷的時候被印出來。
如果我們不希望自訂的原型的屬性,在該子物件執行for遍歷的時候被印出來的話,
我們就要利用Object.defineProperties將該原型中的屬性的enumerable設為false

1
2
3
4
5
6
7
8
9
10
function Person () {}
Person.prototype.name = '人類'
Object.defineProperty(Person.prototype, 'name', {
enumerable: false // 設為false
})
const ming = new Person ()
ming.a = 10
for(var key in ming) {
console.log(key)
}

經過以上的處理,就不會將自訂原型的屬性name印出來囉。

Getter 與 Setter,賦值運算不使用函式

setter: 存值的方法
getter: 取得特定值的方法

setter

1
2
3
4
5
6
7
8
var wallet = {
total: 100,
set save (price) {
this.total = this.total + price/2;
}
}
wallet.save = 300
console.log(wallet.total)

你可以看到,我們新增了一個setter的屬性叫save,可以改變total的屬性值。
但這邊要特別注意的是,呼叫setter的方式並不像我們一般在呼叫函式那樣子喔,
而是直接賦值給它,而這個賦值的內容,就是我們要傳入setter的參數。

getter

1
2
3
4
5
6
7
8
9
10
11
var wallet = {
total: 100,
set save (price) {
this.total = this.total + price/2;
},
get save () {
return this.total/2;
}
}
wallet.save = 300
console.log(wallet.save) // 125

那這邊要注意的是,getter是不用傳入參數的,畢竟它只是要取得數值的功能而已。

利用Object.defineProperty 來設置setter和getter

1
2
3
4
5
6
7
8
9
10
11
12
var wallet = {
total: 100,
}
Object.defineProperty(wallet, 'save', {
set: function (price) {
this.total += price/2
},
get: function () {
return this.total
}
})
console.log(Object.getOwnPropertyDescriptor(wallet, 'save'))

經過Object.defineProperty所設定的settergetter,它們的屬性特徵configurableenumerable
都是false,若你按照原本直接在物件實字的內容裡面直接設定settergetter的話,
它們的屬性特徵configurableenumerable 就都是true
而如果像要改變這種狀況的話,就直接在Object.defineProperty裡面,直接將屬性特徵configurableenumerable
設成true就可以了。

課程提供的getter的使用範例

1
2
3
4
5
6
7
var a = [1, 2, 3]
Object.defineProperty(Array.prototype, 'latest', {
get: function () {
return this[this.length - 1]
}
})
console.log(a.latest)

我們直接對Array的原型新增一個叫latest的getter,接著,所有的陣列都因為原型練的關係
都擁有latest這個getter,可取得陣列的最後一個元素值。