iT邦幫忙

2022 iThome 鐵人賽

DAY 11
0
Modern Web

前端蛇行撞牆記系列 第 11

Day11 前端蛇行撞牆記 - 屬性描述器

  • 分享至 

  • xImage
  •  

前言

當初在學習這一塊最困擾的地方就是為什麼我用Object.create()建立一個新物件的時候,再console這個新物件居然是空的!後來才知道他只是用來繼承原本物件的prototype而已,property那些需要自己再定義。

不過也有看到別人這麼寫:

const obj = { name: "jade", age: 18 };

const newObj = Object.create(obj, { name: { value: "Ann" } });

console.log(newObj);
// {}

可是這麼做console出來也是一個空物件,可是我不是已經有給他value了嗎?

後來才學到原來屬性還有個屬性描述器可以去定義!

所以來整理一下我所學到了屬性描述器,明天會再介紹要怎麼去定義他們~

屬性描述器

先來認識一下屬性(property)是什麼?一般來說屬性就是key, value的結合,但實際上這些屬性的背後還有可以設定的空間,我們把這些屬性設定叫做屬性描述器。

屬性描述器有六大類:

  • value: 屬性的值
  • writable: true, 定義屬性是否可以被寫入
  • configurable: true, 定義屬性是否可以被刪除,或修改其他屬性
  • enumerable: true, 定義屬性是否可以被列舉、迭代
  • get()
  • set()

而這六大類其實裡面還有分兩小類:

  • data descriptor(資料描述器):value, writable
  • accessor descriptor(取值描述器):get, set

這兩種描述器是不相容的。
因為資料描述器就是這個屬性的value(值)、writable(可否更改的)
而取值描述器get、 set是可以讓屬性去改變值,所以兩個不能同時存在。(後面有範例)

configurable, enumerable

configurable

  • 代表屬性描述器是否可以被刪除、更改

如果當configurable:false 表示其他描述器都不能被更改(但有個例外嘿)

const ann = {};

Object.defineProperties(ann, {
  name: {
    value: "ann",
    writable: true,
    enumerable: true,
    configurable: false,
  },
});

console.log(delete ann.name); // false

有一個特例是:就算configurable: false的狀態下,writable還是可以被更改成false。

表示就算不能動其他屬性描述器的情況下,也是被允許可以修改value的。

先設定name property的 configurable: false 的情況下,再重新設定一次把 writable: falsevalue: "hey" 都修改了,這樣不會報錯,value也的確被改到了。

const ann = {};

Object.defineProperty(ann, "name", {
  writable: true,
  enumerable: true,
  configurable: false, => 已經設定不能修改
  value: "ann"
});

// 再重新定義一次name:
Object.defineProperty(ann, "name", {
  writable: false, => 偷偷修改
  enumerable: true,
  configurable: false,
  value: "hey", => 偷偷修改
});

console.log(ann.name);
// hey
// 被改到了

但如果其他的一改變就會報錯,偷偷把enumberable改成false..就報錯拉!

Object.defineProperty(ann, "name", {
  writable: true,
  enumerable: false, => 修改
  configurable: false,
  value: "hey",
});

console.log(ann.name);
// TypeError: Cannot redefine property: name

enumberable

  • 決定是否可以被列舉,被for...in列出。

這個後面會講到,常常在用定義屬性描述器時如果忘記去定義enumberable: true的話,這個object就看起來是個空物件,但他其實還是有,只是沒有被列舉出來而已。

data descriptor:value, writable

value

  • 決定這個屬性的value是什麼,也就是我們最熟悉的key, value那個value!

writable

  • 決定是否value可以被更改

如果一開始name的屬性設定writable: false 之後再去更改value,就會被忽略。

const ann = {};

Object.defineProperty(ann, "name", {
  value: "ann",
  writable: false,
  enumerable: true,
  configurable: false,
});

ann.name = "hey";

console.log(ann);
// ann 一樣,沒有被改到

accessor descriptor:get, set

  • 我們又稱get, set是 取值描述器(accessor descriptor)
  • get, set分別是 取值器、設值器。
    • set當作存一個值進去,再讓get拿出來
    • get是得到,要return;set是設定,不用return
  • value, writable跟get, set不能一起寫,有value就沒有get/set,反之。範例:
const a = {};

Object.defineProperty(a, "name", {
  value: "ann",
  writable: true,
  enumerable: true,
  configurable: false,
  get() {
    return this.name;
  },
  set(name) {
    return this.name;
  },
});

console.log(a);

//  TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
  • 如何寫入:

    • 定義的時候直接寫,注意function名稱相同表示同個屬性
    const jade = {
      name: "Jade",
      lastName: "Chen",
    
      get fullName() {
        return this.name + this.lastName;
      },
    
      set fullName(value) {
        [this.name, this.lastName] = value.split(" ");
      },
    };
    
    jade.fullName = "Ann Pang";
    
    console.log(jade.lastName);
    
    • 在已經存在的object要加上屬性描述器就要使用Object.defineProperty()去定義
      就不需要像上面一樣在get, set後面命名屬性名稱,因為這已經是defineProperty了,fullname也寫上去了。
    const jade = {
        name: "jade",
        lastName: "Chen"
    }
    
    Object.defineProperty(a, "fullName", {
        get() {
            return this.name + this.lastName;
        }
        set(value) {
            [this.name, this.lastName] = value.split(" ");
        }
    })
    
  • 還可以加上條件判斷,如果新輸入的名字長度太短就不給改變

    const jade = {
      name: "jade",
      get checkName() {
        return this.name;
      },
      set checkName(value) {
        if (value.length < 4) {
          console.log("名字不能小於四個字母!");
          return;
        } else {
          this.name = value;
        }
      },
    };
    
    jade.checkName = "Pete";
    
    console.log(jade.name);
    // 名字不能小於四個字母!
    // jade
    

雖然介紹了上面這幾種方式,但要知道這些設定只能淺層設定而已,就是只會影響到自有的物件

讓我們來看看,故意將name的value設定成一個object,那用平常的做法就改變不到那個value的物件。

const ann = {};
const inner = { innerName: "hey" };

Object.defineProperty(ann, "name", {
  value: inner,
  writable: false,
  enumerable: true,
  configurable: false,
});

ann.name = "jade";

console.log(ann.name);
// { innerName: "hey" }
// 沒有被改到

可是如果我們再加一層進去更改那個value的話,嘿嘿就改到了!

ann.name.innerName = "jade";

console.log(ann.name);
// { innerName: 'jade' }

結論

  • 屬性描述器有六種,裡面又有分為資料描述器、取值描述器,而這兩個不能同時存在。
  • get, set如果是一起與其他屬性定義的話,要在後面加上屬性名稱,例如:get fullName() {...}的方式。
  • 但如果是使用Object.defineProperty()的話就不用,因為前面會先寫好要定義哪個property

今天先這樣拉~明天見!

參考資料:JavaScript - 屬性描述器 (1)
【新手大哉問】property、key、value、reference,到底是什麼啦?
属性的 getter 和 setter
JS對象屬性中get/set與getter/setter是什麼
JavaScript - 屬性描述器 (2)
008 深入瞭解JavaScript核心:函式、物件、原型鏈 (下)


上一篇
Day10 前端蛇行撞牆記 - array-like 可以用陣列方法嗎?
下一篇
Day12 前端蛇行撞牆記 - 屬性描述器的定義方式
系列文
前端蛇行撞牆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言