屬性描述對象

JavaScript 提供了一個內部數據結構,用來描述對象的屬性,控制它的行爲,好比該屬性是否可寫、可枚舉等等。這個內部數據結構稱爲「屬性描述對象」(attributes object)。每一個屬性都有本身對應的屬性描述對象,保存該屬性的一些元信息。javascript

元屬性

屬性描述對象的各個屬性稱爲「元屬性」,由於它們能夠看做是控制屬性的屬性。html

value

value 屬性是目標屬性的值。java

var obj = { p : 123 };

Object.getOwnPropertyDescriptor(obj, 'p').value   // 123

Object.defineProperty(obj, 'p', { value: 246 });
obj.p     // 246

改寫 value 屬性時,只要 writableconfigurable 有一個爲 true,就容許改動。只有 writableconfigurable 兩個都爲 false 時,value 屬性纔不可改寫。數據結構

var obj = Object.defineProperty({}, 'p', {
  value: 1,
  writable: false,
  configurable: false
});

Object.defineProperty(obj, 'p', {value: 2})
// TypeError: Cannot redefine property: p

obj.p = 4
obj.p       // 1

value 屬性不可改寫時,直接屬性賦值,不報錯,但不會成功。在嚴格模式下會報錯,即便對屬性從新賦予一個一樣的值。函數

writable

writable 屬性是一個布爾值,決定了目標屬性的值 value 是否能夠被改變。this

var obj = {};

Object.defineProperty(obj, 'a', {
  value: 37,
  writable: false
});

obj.a         // 37
obj.a = 25;
obj.a         // 37

若是原型對象的某個屬性的 writablefalse,那麼子對象將沒法自定義這個屬性。prototype

var proto = Object.defineProperty({}, 'foo', {
  value: 'a',
  writable: false
});

var obj = Object.create(proto);

obj.foo = 'b';
obj.foo    // 'a'

可是,有一個規避方法,就是經過覆蓋屬性描述對象,繞過這個限制。緣由是這種狀況下,原型鏈會被徹底忽視。code

Object.defineProperty(obj, 'foo', {
  value: 'b'
});

obj.foo   // "b"

enumerable

enumerable (可枚舉性)屬性是一個布爾值,表示目標屬性是否可枚舉。htm

若是一個屬性的 enumerablefalse 時,下面四個操做將不會取到該屬性。對象

  • for..in 循環
  • Object.keys() 方法
  • JSON.stringify() 方法
  • Object.assign():只拷貝對象自身的可枚舉的屬性。
var obj = {};

Object.defineProperty(obj, 'x', {
  value: 123,
  enumerable: false
});

obj.x                   // 123
for (var key in obj) {console.log(key);}  // undefined
Object.keys(obj)        // []
JSON.stringify(obj)     // "{}"

JSON.stringify 方法會排除 enumerablefalse 的屬性,若是對象的 JSON 格式輸出要排除某些屬性,就能夠利用這一點把這些屬性的 enumerable 設爲 false

configurable

configurable (可配置性)返回一個布爾值,決定了是否能夠修改屬性描述對象。也就是說,configurablefalse 時,valuewritableenumerableconfigurable 都不能被修改了。

var obj = Object.defineProperty({}, 'p', {
  value: 1,
  writable: false,
  enumerable: false,
  configurable: false
});

Object.defineProperty(obj, 'p', {value: 2})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {writable: true})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {enumerable: true})
// TypeError: Cannot redefine property: p

Object.defineProperty(obj, 'p', {configurable: true})
// TypeError: Cannot redefine property: p

注意,writable 只有在 false 改成 true 時會報錯,true 改成 false 是容許的。

var obj = Object.defineProperty({}, 'p', {
  writable: true,
  configurable: false
});

Object.defineProperty(obj, 'p', {writable: false})  // 修改爲功

可配置性決定了目標屬性是否能夠被刪除(delete)。configurabletrue 時,屬性能夠被刪除,爲 false 時,屬性不可被刪除。

var obj = Object.defineProperties({}, {
  p1: { value: 1, configurable: true },
  p2: { value: 2, configurable: false }
});

delete obj.p1    // true
delete obj.p2    // false

obj.p1    // undefined
obj.p2    // 2

存取器

除了直接定義之外,屬性還能夠用存取器(accessor)定義。其中,存值函數稱爲 setter,使用屬性描述對象的 set 屬性;取值函數稱爲 getter,使用屬性描述對象的 get 屬性。

一旦對目標屬性定義了存取器,那麼存取的時候,都將執行對應的函數。利用這個功能,能夠實現許多高級特性,好比某個屬性禁止賦值。

var obj = Object.defineProperty({}, 'p', {
  get: function () { return 'getter'; },
  set: function (value) { console.log('setter: ' + value); }
});

obj.p          // "getter"
obj.p = 123    // "setter: 123"

一旦定義了取值函數 get 或存值函數 set,就不能同時定義 writable 屬性或 value 屬性,不然會報錯。若是經過 Object.defineProperty() 重定義 writable 屬性或 value 屬性,那麼取值函數 get 和存值函數 set 將會被 valuewritable 覆蓋。

var obj = Object.defineProperty({}, 'p', {
  get: function () { return 'getter'; },
  set: function (value) { console.log('setter: ' + value); },
  configurable: true
});
    
Object.getOwnPropertyDescriptor(obj,'p')
// { get: ƒ (),
//   set: ƒ (value),
//   enumerable: false,
//   configurable: true
// }

// 重定義
Object.defineProperty(obj, 'p', { writable:true });
Object.getOwnPropertyDescriptor(obj,'p')
// {value: undefined, writable: true, enumerable: false, configurable: true}

JavaScript 還提供了存取器的另外一種寫法。與定義屬性描述對象是等價的,並且使用更普遍。

var obj = {
  get p() {
    return 'getter';
  },
  set p(value) {
    console.log('setter: ' + value);
  }
};

注意,取值函數 get 不能接受參數,存值函數 set 只能接受一個參數(即屬性的值)。

存取器每每用於,屬性的值依賴對象內部數據的場合。

var obj ={
  $n : 5,
  get next() { return this.$n },
  set next(n) {
    if (n >= this.$n) this.$n = n;
    else throw new Error('新的值必須大於當前值');
  }
};

obj.next         // 5

obj.next = 10;
obj.next         // 10

obj.next = 5;    // Uncaught Error: 新的值必須大於當前值

與屬性描述對象相關的方法

1. Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor() 能夠獲取某個屬性的屬性描述對象。它的第一個參數是對象,第二個參數是對象的某個屬性名。返回的是該屬性的屬性描述對象。

var obj = { p1: 'a',  p2: 'b'};

Object.getOwnPropertyDescriptor(obj, 'p1')
// { value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

只能用於對象自身的(非繼承的)屬性。繼承的或不存在的屬性返回 undefined

Object.getOwnPropertyDescriptor(obj, 'toString')   // undefined

2. Object.getOwnPropertyDescriptors()

Object.getOwnPropertyDescriptors() 能夠獲取參數對象的全部屬性的屬性描述對象。ES2017 引入標準。

Object.getOwnPropertyDescriptors(obj)
// { p1: {value: "a", writable: true, enumerable: true, configurable: true}
//   p2: {value: "b", writable: true, enumerable: true, configurable: true}
// }

3. Object.defineProperty()

Object.defineProperty() 方法容許經過屬性描述對象,定義或修改一個屬性,而後返回修改後的描述對象。

Object.defineProperty(object, propertyName, attributesObject)

Object.defineProperty() 方法接受三個參數,依次以下。

  • object:屬性所在的對象
  • propertyName:字符串,表示屬性名
  • attributesObject:屬性描述對象
var obj = Object.defineProperty({}, 'p', {
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false
});
obj.p         // 123
obj.p = 246;
obj.p         // 123

注意,上例中第一個參數是{ }(一個新建的空對象),p屬性直接定義在這個空對象上面,而後返回這個對象,這是 Object.defineProperty() 的常見用法。

若是屬性已經存在,Object.defineProperty() 方法至關於更新該屬性的屬性描述對象。

4. Object.defineProperties()

Object.defineProperties() 方法能夠定義或修改多個屬性。接受兩個參數。

var obj = Object.defineProperties({}, {
  p1: { value: 123, enumerable: true },
  p2: { value: 'abc', enumerable: true },
  p3: { get: function () { return this.p1 + this.p2 },
    enumerable:true,
    configurable:true
  }
});

obj.p1  // 123
obj.p2  // "abc"
obj.p3  // "123abc"

元屬性默認值

Object.defineProperty()Object.defineProperties() 參數裏面的屬性描述對象,writableconfigurableenumerable 這三個屬性的默認值都爲 false

var obj = {};
Object.defineProperty(obj, 'foo', {});
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
//   value: undefined,
//   writable: false,
//   enumerable: false,
//   configurable: false
// }

5. Object.prototype.propertyIsEnumerable()

實例對象的 propertyIsEnumerable() 方法返回一個布爾值,用來判斷某個屬性是否可枚舉。

var obj = {};
obj.p = 123;

obj.propertyIsEnumerable('p')           // true
obj.propertyIsEnumerable('toString')    // false

注意,這個方法只能用於判斷對象自身的屬性,對於繼承的屬性一概返回 false

參考連接:JavaScript 教程 屬性描述對象

相關文章
相關標籤/搜索