我們所制定的原型與原生原型最大不同就是在屬性特徵可列舉的部分 enumerable
,因可列舉的關係 for in
方式不會出現原生原型的內容但會出現我們制定原型的內容。
// 原型概念須先產生建構函式
function Person() {}
// 於建構函式新增原型的內容
Person.prototype.name = '人類';
// 透過建構函式產生新物件
var casper = new Person();
// 新物件再賦予 a 屬性與屬性值 undefined
casper.a = undefined;
console.log(casper);
console.log(`hasOwnProperty a:${casper.hasOwnProperty('a')}`);
console.log(`hasOwnProperty name:${casper.hasOwnProperty('name')}`);
「 自制的原型屬性 」與 「 原生原型屬性 」的屬性特徵 enumerable
不同,使用此範例來拆解不同處。
|拆解|
➤ 於建構函式新增原型內容 → 透過建構函式產生新物件 & 賦予新屬性
a
相同。➤ 變數 casper
使用 hasOwnProperty
分別查看 a
與 name
屬性:
function Person() {}
Person.prototype.name = '人類';
var casper = new Person();
casper.a = undefined;
for (var key in casper) {
console.log(`casper 物件內的屬性:${key}`);
}
console.log(`hasOwnProperty a:${casper.hasOwnProperty('a')}`);
console.log(`hasOwnProperty name:${casper.hasOwnProperty('name')}`);
console.log(casper);
hasOwnProperty
是針對物件當前的屬性來來查看。for in
方式列舉出所有屬性包含使用建構函式新增的內容 name
但 hasOwnProperty
無法呈現出 prototype
中的 name
是因為 hasOwnProperty
只針對當前屬性來查看。➤ 使用 for in
方式列出 casper 內的屬性
function Person() {}
Person.prototype.name = '人類';
var casper = new Person();
casper.a = undefined;
for (var key in casper) {
console.log(`casper 物件內的屬性:${key}`);
}
for in
方式列出 casper 內的屬性有 a
與自訂原型中的 name
。
name
。➤ 我們自制原型的屬性與原生原型屬性的「 屬性特徵不同 」
回到 Day21 | 物件的方法介紹-針對物件本身屬性操作的方法 中的 definedProperty
,它可針對屬性額外去定義屬性特徵 ( value
、writable
、configurable
、enumerable
)
範例中我們自制的原型屬性 name
與原型中的屬性特徵不同,差異在於:
使用 Object.getOwnPropertyDescriptor(物件)
去得物件裡屬性的特定特,因為要查詢 casper 原型中的 name,所以結構如下
function Person() {}
Person.prototype.name = '人類';
var casper = new Person();
casper.a = undefined;
console.log(Object.getOwnPropertyDescriptor(casper.__proto__, 'name'));
再 casper 進到物件原型中的物件原型中的其中一個方法 toString。
function Person() {}
Person.prototype.name = '人類';
var casper = new Person();
casper.a = undefined;
console.log(Object.getOwnPropertyDescriptor(casper.__proto__, 'name'));
由上方查詢屬性特徵看到我們制定的原型與原生原型的 enumerable
列舉方式不同。
enumerable: true
( 可列舉 ),原生原型屬性特徵 enumerable: false
( 不可列舉 )。name
在 for in
方式下是會被列出來的。➤ 我們制定的原型與原生的原型最大的不同在於我們制定的原型是可被列舉,原生原型是不可被列舉
使用 definedProperty
對物件屬性做調整。關於 definedProperty
可參考前面的筆記 Day21 | 物件的方法介紹-針對物件本身屬性操作的方法 。
// 承上範例 1.
function Person() {}
Person.prototype.name = '人類';
// ------ 把屬性特徵 enumerable 調整為 false
// 可解決自制的原型屬性與原生原型屬性屬性特徵不同的問題
Object.defineProperty(Person.prototype, 'name', {
enumerable: false,
})
var casper = new Person();
casper.a = undefined;
console.log(casper);
console.log(Object.getOwnPropertyDescriptor(casper.__proto__, 'name'));
for (const key in casper) {
console.log(`casper 物件內的屬性:${key}`);
}
自制原型屬性 name 使用 definedProperty
把屬性特徵 enumerable
調整為 false ( 不可被列舉 ) 與原生原型一樣 ,console.log(casper);
可見 name
顏色就與其他原生原型一樣了。
Object.definedProperty(物件, '屬性', 參數);
中物件放入 Person 是因為函式也是物件的一種。Object.getOwnPropertyDescriptor(casper.__proto__, 'name')
中 enumerable
屬性特徵也與原生原型一樣是 false
。
使用 for in
方式就看不到 name
屬性。
使用 for in
可以一一列出物件屬性們,但 for in
不會分辨此屬性是自制原型或原生原型,由於自制原型預設是可被列舉,所以就會被列出來。
➤ 沒使用 definedProperty
調整 enumerable
屬性特徵遇到的問題
如果沒有使用 definedProperty
調整 enumerable
屬性特徵,使用 for in
就會一一列出物件屬性們,包含自制原型內的屬性。 CodePen 範例
// casper 物件下的內容 ↑
➤ 解決使用 for in 不列出原型屬性的方式
definedProperty
調整 enumerable
。CodePen 範例
for in
加上判斷式。CodePen 範例
for in
內加上一段判斷式來確保此屬性是在當前物件下而非原型內。for in
中的 key
就只會顯示當前物件的屬性 a
。function Person() {}
Person.prototype.name = '人類';
var casper = new Person();
casper.a = undefined;
console.log(casper);
for (const key in casper) {
// 此判斷式目的可以確保此屬性是在當前物件下而非原型內
if (Object.hasOwnProperty.call(casper, key)) {
console.log(`casper 物件內的屬性:${key}`);
}
}
想變更物件屬性值,但又希望他有運算功能,可使用 Getter
( 取得特定值的方法 ) 與 Setter
( 存值的方法 ) 。
var wallet = {
total: 100,
set save(price) {
this.total = this.total + price / 2;
}
}
wallet.save = 300;
console.log(wallet);
Setter 使用方式
Setter
是存值概念,會傳入參數
set
關鍵字,後方加上屬性名稱 ( 為函式 save(參數){}
),這樣在 Setter 裡面就可以透過參數方式把值傳入。會透過函式內的參數且透過運算來改變 total
這個屬性值。
wallet.total = 新值;
來改變 total
屬性值,但 Setter 是透過運算方式改變。物件.屬性名稱 = 新值;
( wallet.save = 300;
),等號右邊的值會透過參數的方式傳進去。=
非 ()
,所以並非用函式方式來執行 Setter
,是用等號賦予值的方式來改變存值的方式。答案
250
。var wallet = {
total: 100,
get save() {
return this.total /2;
}
}
wallet.save = 300;
console.log(wallet);
// 直接取 Getter 的值,就會是當下的值
console.log(wallet.save);
使用方式
Getter
是取值的概念,不會傳入參數,因為是取值所以會用到 return
。
答案
由開發者工具可見有一個 save: (…)
,Getter
的值是在按下 (…)
出現的值。
驗證:Getter
的值是在按下 (…)
出現的值,與 wallet.save
相同為 50。
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);
// 直接取 Getter 的值,就會是當下的值
console.log(wallet.save);
使用方式
set
與 get
屬性名稱命名可一樣也可不一樣。set
與 get
兩個一起運用,要注意 Getter
值是在點下 (…)
出現的值。答案
set 部分 100+(300/2) = 250
get 部分 250/2 = 125
Setter
透過參數方式把值 300 存入經過運算, total
為 250
。Getter
取到的值為 125。Object.defineProperty(物件, ‘屬性名稱’, 參數);
var wallet = {
total: 100
}
Object.defineProperty(wallet, 'save', {
set: function(price) {
this.total = this.total + price / 2;
},
get: function() {
return this.total /2;
}
})
wallet.save = 300;
console.log(wallet);
console.log(Object.getOwnPropertyDescriptor(wallet, 'save'));
屬性名稱帶上 'save'
,參數裡面使用 set
冒號 function
方式把 Setter 加入。使用 defineProperty
的方式在開發者工具中看見 Getter 值為不同顏色,屬性特徵與 「 Getter 與 Setter,賦值運算不使用函式 」 新增方式不同 。
// console.log(wallet);
使用 Object.getOwnPropertyDescriptor
查看物件的 'save'
屬性特徵。configurable
與 enumerable
皆為 false
,顯示不可刪除與不可列舉。
調整屬性特徵 configurable
與 enumerable
var wallet = {
total: 100,
}
Object.defineProperty(wallet, 'save', {
configurable: true,
enumerable: true,
set: function(price) {
this.total = this.total + price / 2;
},
get: function() {
return this.total /2;
}
})
wallet.save = 300;
console.log(wallet);
console.log(Object.getOwnPropertyDescriptor(wallet, 'save'));
defineProperty
參數中加入 configurable
與 enumerable
即可。
wallet
物件中的 save
變深色 &屬性特徵 configurable
與 enumerable
也調整為 true
( 可刪除也可列舉 )。var a = [1, 2, 3];
var b = ['a', 'b', 'c', 'd'];
Object.defineProperty(Array.prototype, 'last', {
get: function () {
return this[this.length - 1];
}
})
console.log(a.last);
console.log(b.last);
defineProperty
直接操作陣列的原型 Array.prototype
( 可透過陣列的建構函式來調整它 ),並加上自定義屬性名稱與帶入參數 Getter。return
的 this
為陣列本身。console.log(a.last);
取最後一個值。