Day23 | 物件方法介紹 - 屬性特徵列舉與原型的關係、Setter 與 Getter 運用

屬性列舉與原型的關係

我們所制定的原型與原生原型最大不同就是在屬性特徵可列舉的部分 enumerable,因可列舉的關係 for in 方式不會出現原生原型的內容但會出現我們制定原型的內容。

範例 1. 自制的原型屬性與原生原型屬性關係

1
2
3
4
5
6
7
8
9
10
11
12
13
// 原型概念須先產生建構函式
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 不同,使用此範例來拆解不同處。

|拆解|

於建構函式新增原型內容 → 透過建構函式產生新物件 & 賦予新屬性
https://ithelp.ithome.com.tw/upload/images/20221008/20119743qIfiVqIsfC.png

  • prototype 為原型中的屬性,casper 內容可見 prototype 中的 name 屬性顏色和其他 prototype 屬性顏色不同,和我們自己定義的屬性色彩 a 相同。

變數 casper 使用 hasOwnProperty 分別查看 aname 屬性:

1
2
3
4
5
6
7
8
9
10
11
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 是針對物件當前的屬性來來查看。
  • casper 變數使用 for in 方式列舉出所有屬性包含使用建構函式新增的內容 namehasOwnProperty 無法呈現出 prototype 中的 name 是因為 hasOwnProperty 只針對當前屬性來查看。

https://ithelp.ithome.com.tw/upload/images/20221008/20119743EO52jgVBBW.png

➤ 使用 for in 方式列出 casper 內的屬性

1
2
3
4
5
6
7
8
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
    • 因為可列舉的關係 for in 方式不會出現原生原型的內容但會出現我們制定原型的內容 name

➤ 我們自制原型的屬性與原生原型屬性的「 屬性特徵不同 」

  • 回到 Day21 | 物件的方法介紹-針對物件本身屬性操作的方法 中的 definedProperty ,它可針對屬性額外去定義屬性特徵 ( valuewritableconfigurableenumerable )

  • 範例中我們自制的原型屬性 name 與原型中的屬性特徵不同,差異在於:

    • 使用 Object.getOwnPropertyDescriptor(物件) 去得物件裡屬性的特定特,因為要查詢 casper 原型中的 name,所以結構如下

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

      var casper = new Person();
      casper.a = undefined;
      console.log(Object.getOwnPropertyDescriptor(casper.__proto__, 'name'));
      ![https://ithelp.ithome.com.tw/upload/images/20221008/20119743aIIWfQHhPQ.png](https://ithelp.ithome.com.tw/upload/images/20221008/20119743aIIWfQHhPQ.png)
    • 再 casper 進到物件原型中的物件原型中的其中一個方法 toString。

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

      var casper = new Person();
      casper.a = undefined;
      console.log(Object.getOwnPropertyDescriptor(casper.__proto__, 'name'));
      ![https://ithelp.ithome.com.tw/upload/images/20221008/20119743v1yFuwbyEK.png](https://ithelp.ithome.com.tw/upload/images/20221008/20119743v1yFuwbyEK.png)
  • 由上方查詢屬性特徵看到我們制定的原型與原生原型的 enumerable 列舉方式不同。

    • 我們制定的原型屬性特徵 enumerable: true ( 可列舉 ),原生原型屬性特徵 enumerable: false ( 不可列舉 )。
    • 就因可列舉的特徵,所以我們制定的原型屬性 namefor in 方式下是會被列出來的。

我們制定的原型與原生的原型最大的不同在於我們制定的原型是可被列舉,原生原型是不可被列舉

❒ 解決自制的原型屬性與原生原型屬性屬性特徵不同的方式

解決方式

使用 definedProperty 對物件屬性做調整。關於 definedProperty 可參考前面的筆記 Day21 | 物件的方法介紹-針對物件本身屬性操作的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 承上範例 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}`);

}

https://ithelp.ithome.com.tw/upload/images/20221008/201197432FJoZHFLl6.png

  • 自制原型屬性 name 使用 definedProperty 把屬性特徵 enumerable 調整為 false ( 不可被列舉 ) 與原生原型一樣 ,console.log(casper); 可見 name 顏色就與其他原生原型一樣了。

    • 另外 Object.definedProperty(物件, '屬性', 參數); 中物件放入 Person 是因為函式也是物件的一種。
  • Object.getOwnPropertyDescriptor(casper.__proto__, 'name')enumerable 屬性特徵也與原生原型一樣是 false
    https://ithelp.ithome.com.tw/upload/images/20221008/20119743XdgEjoWsos.png

  • 使用 for in 方式就看不到 name 屬性。
    https://ithelp.ithome.com.tw/upload/images/20221008/20119743q6ijGgvWkD.png

解決使用 for in 列舉的問題

使用 for in 可以一一列出物件屬性們,但 for in 不會分辨此屬性是自制原型或原生原型,由於自制原型預設是可被列舉,所以就會被列出來。

沒使用 definedProperty 調整 enumerable 屬性特徵遇到的問題

如果沒有使用 definedProperty 調整 enumerable 屬性特徵,使用 for in 就會一一列出物件屬性們,包含自制原型內的屬性。 CodePen 範例
casper 物件下的內容
// casper 物件下的內容 ↑

解決使用 for in 不列出原型屬性的方式

  1. 使用 definedProperty 調整 enumerable CodePen 範例
  2. for in 加上判斷式。CodePen 範例
    1. for in 內加上一段判斷式來確保此屬性是在當前物件下而非原型內。for in 中的 key 就只會顯示當前物件的屬性 a
1
2
3
4
5
6
7
8
9
10
11
12
13
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,賦值運算不使用函式

想變更物件屬性值,但又希望他有運算功能,可使用 Getter ( 取得特定值的方法 ) 與 Setter ( 存值的方法 ) 。

範例1. 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);

Setter 使用方式

Setter 是存值概念,會傳入參數

  • 先使用一個 set 關鍵字,後方加上屬性名稱 ( 為函式 save(參數){} ),這樣在 Setter 裡面就可以透過參數方式把值傳入。會透過函式內的參數且透過運算來改變 total 這個屬性值。
    • 一般都會直接使用 wallet.total = 新值; 來改變 total 屬性值,但 Setter 是透過運算方式改變。
  • 如何透過 Setter 把值存入?
    • 可使用 物件.屬性名稱 = 新值; ( wallet.save = 300; ),等號右邊的值會透過參數的方式傳進去。
    • ❗ 注意:這邊使用等號 =() ,所以並非用函式方式來執行 Setter,是用等號賦予值的方式來改變存值的方式。

答案

  • 答案為 250

範例 2. Getter 取值運用

1
2
3
4
5
6
7
8
9
10
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 的值是在按下 (…) 出現的值。
    https://ithelp.ithome.com.tw/upload/images/20221008/201197434oktkOVUjA.png

  • 驗證:Getter 的值是在按下 (…) 出現的值,與 wallet.save 相同為 50。
    https://ithelp.ithome.com.tw/upload/images/20221008/201197433bsNriu5KF.png

範例 3. Setter 存值和 Getter 取值一起使用

1
2
3
4
5
6
7
8
9
10
11
12
13
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);

使用方式

  • setget 屬性名稱命名可一樣也可不一樣。
  • 如果 setget 兩個一起運用,要注意 Getter 值是在點下 (…) 出現的值。

答案

1
2
set 部分 100+(300/2) = 250
get 部分 250/2 = 125
  • Setter 透過參數方式把值 300 存入經過運算, total250
  • Getter 取到的值為 125。

Getter 與 Setter 搭配 Object.defineProperty

defineProperty 架構

Object.defineProperty(物件, ‘屬性名稱’, 參數);

範例 1. 延續上方範例,使用 defineProperty 方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,賦值運算不使用函式 」 新增方式不同 。
    https://ithelp.ithome.com.tw/upload/images/20221008/20119743yjA6H1zNO5.png

    // console.log(wallet);

  • 使用 Object.getOwnPropertyDescriptor 查看物件的 'save' 屬性特徵。configurableenumerable 皆為 false,顯示不可刪除與不可列舉。
    https://ithelp.ithome.com.tw/upload/images/20221008/20119743W2xUIbz8Op.png

調整屬性特徵 configurableenumerable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 參數中加入 configurableenumerable 即可。
    • wallet 物件中的 save 變深色 &屬性特徵 configurableenumerable 也調整為 true ( 可刪除也可列舉 )。
      https://ithelp.ithome.com.tw/upload/images/20221008/20119743o3wrqMKo3U.png

❒ 加碼範例 - 陣列使用 Getter 與 Setter

取出陣列中最後一筆資料

1
2
3
4
5
6
7
8
9
10
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。
  • Getter 中 returnthis 為陣列本身。
  • 因為直接修改陣列原型,所以所有陣列都可以使用此方法,使用 console.log(a.last); 取最後一個值。

參考資訊

  • JavaScript 核心篇