JS 描述屬性對象2019-08-21(2)

概述

JavaScript 提供了一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來描述對象的屬性,控制它的行為,比如該屬性是否可寫、可遍歷等等。這個內(nèi)部數(shù)據(jù)結(jié)構(gòu)稱為“屬性描述對象”(attributes object)每個屬性都有自己對應(yīng)的屬性描述對象,保存該屬性的一些元信息。.
屬性描述對象提供6個元屬性:
(1)value
value是該屬性的屬性值,默認(rèn)為undefined。

(2)writable
writable是一個布爾值,表示屬性值(value)是否可改變(即是否可寫),默認(rèn)為true。

(3)enumerable
enumerable是一個布爾值,表示該屬性是否可遍歷,默認(rèn)為true。如果設(shè)為false,會使得某些操作(比如for...in循環(huán)、Object.keys())跳過該屬性。

(4)configurable
configurable是一個布爾值,表示可配置性,默認(rèn)為true。如果設(shè)為false,將阻止某些操作改寫該屬性,比如無法刪除該屬性,也不得改變該屬性的屬性描述對象(value屬性除外)。也就是說,configurable屬性控制了屬性描述對象的可寫性。

(5)get
get是一個函數(shù),表示該屬性的取值函數(shù)(getter),默認(rèn)為undefined。

(6)set
set是一個函數(shù),表示該屬性的存值函數(shù)(setter),默認(rèn)為undefined。

屬性描述對象的一個例子:

{
  value: 123,
  writable: false,
  enumerable: true,
  configurable: false,
  get: undefined,
  set: undefined
}


1、Object.getOwnPropertyDescriptor()

Object.getOwnPropertyDescriptor()方法可以獲取屬性描述對象。它的第一個參數(shù)是目標(biāo)對象,第二個參數(shù)是一個字符串,對應(yīng)目標(biāo)對象的某個屬性名
例:

var obj = { p: 'a' };

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

上面代碼中,用Object.getOwnPropertyDescriptor()方法獲取obj.p的屬性描述對象。
Object.getOwnPropertyDescriptor()方法只能用于對象自身的屬性,不能用于繼承的屬性。


2、Object.getOwnPropertyNames()

Object.getOwnPropertyNames方法返回一個數(shù)組成員是參數(shù)對象自身的全部屬性的屬性名,不管該屬性是否可遍歷。

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

Object.getOwnPropertyNames(obj)
// ["p1", "p2"]

上面代碼中,obj.p1是可遍歷的,obj.p2是不可遍歷的。但Object.getOwnPropertyNames會將它們都返回。


3、Object.defineProperty() 、 Object.defineProperties()

Object.defineProperty()方法允許通過屬性描述對象,定義或修改一個屬性,然后返回修改后的對象,它的用法如下:

Object.defineProperty(object, propertyName, attributesObject)

Object.defineProperty方法接受三個參數(shù),依次如下。
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

上面代碼中,Object.defineProperty()方法定義了obj.p屬性。由于屬性描述對象的writable屬性為false,所以obj.p屬性不可寫。注意,這里的Object.defineProperty方法的第一個參數(shù)是{}(一個新建的空對象),p屬性直接定義在這個空對象上面,然后返回這個對象,這是Object.defineProperty()的常見用法。

如果要一次性定義或修改多個屬性,可以使用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.defineProperties()同時定義了obj對象的三個屬性。其中,p3屬性定義了取值函數(shù)get,即每次讀取該屬性,都會調(diào)用這個取值函數(shù)
注意,一旦定義了取值函數(shù)get(或存值函數(shù)set),就不能將writable屬性設(shè)為true,或者同時定義value屬性,否則會報錯。
Object.defineProperty()Object.defineProperties()參數(shù)里面的屬性描述對象,writableconfigurableenumerable三個屬性的默認(rèn)值都為false


4、Object.prototype.propertyIsEnumerable()
實例對象的propertyIsEnumerable()方法返回一個布爾值,用來判斷某個屬性是否可遍歷。注意,這個方法只能用于判斷對象自身的屬性,對于繼承的屬性一律返回false。

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

obj.propertyIsEnumerable('p') // true
obj.propertyIsEnumerable('toString') // false
//obj.p是可遍歷的,而obj.toString是繼承的屬性。


5、元屬性
屬性描述對象的各個屬性稱為“元屬性”,因為它們可以看作是控制屬性的屬性。



5.1、value
value屬性是目標(biāo)屬性的值

通過value屬性,讀取或改寫obj.p的例子:

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

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

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

5.2、writable
writable屬性是一個布爾值,決定了目標(biāo)屬性的值(value)是否可以被改變。
例:

var obj = {};

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

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

//obj.a的writable屬性是false。然后,改變obj.a的值,不會有任何效果。

注意,正常模式下,對writable為false的屬性賦值不會報錯,只會默默失敗。但是,嚴(yán)格模式下會報錯,即使對a屬性重新賦予一個同樣的值。

'use strict';
var obj = {};

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

obj.a = 37;
// Uncaught TypeError: Cannot assign to read only property 'a' of object

5.3、enumerable
enumerable(可遍歷性)返回一個布爾值,表示目標(biāo)屬性是否可遍歷
如果一個屬性的enumerable為false,下面三個操作不會取到該屬性:

  • for..in循環(huán)
  • Object.keys方法
  • JSON.stringify方法

5.4、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

上面代碼中,obj.pconfigurablefalse。然后,改動valuewritableenumerableconfigurable,結(jié)果都報錯。
注意,writable只有在false改為true會報錯,true改為false是允許的

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

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

至于value,只要writableconfigurable有一個為true,就允許改動。

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

Object.defineProperty(o1, 'p', {value: 2})
// 修改成功

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

Object.defineProperty(o2, 'p', {value: 2})
// 修改成功

另外,writablefalse時,直接目標(biāo)屬性賦值,不報錯,但不會成功。

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

obj.p = 2;
obj.p // 1

上面代碼中,obj.pwritablefalse,對obj.p直接賦值不會生效。如果是嚴(yán)格模式,還會報錯。

可配置性決定了目標(biāo)屬性是否可以被刪除(delete

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

上面代碼中,obj.p1configurabletrue,所以可以被刪除,obj.p2就無法刪除。


6、存取器
除了直接定義以外,屬性還可以用存取器(accessor)定義
存值函數(shù)稱為setter,使用屬性描述對象的set屬性
取值函數(shù)稱為getter,使用屬性描述對象的get屬性

一旦對目標(biāo)屬性定義了存取器,那么存取的時候,都將執(zhí)行對應(yīng)的函數(shù)。

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

上面代碼中,obj.p定義了getset屬性。obj.p取值時,就會調(diào)用get;賦值時,就會調(diào)用set
注意,取值函數(shù)get不能接受參數(shù),存值函數(shù)set只能接受一個參數(shù)(即屬性的值)


7、對象的拷貝
需要將一個對象的所有屬性,拷貝到另一個對象,可以用下面的方法實現(xiàn):

var extend = function (to, from) {
  for (var property in from) {
    to[property] = from[property];
  }

  return to;
}

extend({}, {
  a: 1
})
// {a: 1}

上面這個方法的問題在于,如果遇到存取器定義的屬性,會只拷貝值

extend({}, {
  get a() { return 1 }
})
// {a: 1}

為了解決這個問題,我們可以通過Object.defineProperty方法來拷貝屬性

var extend = function (to, from) {
  for (var property in from) {
    if (!from.hasOwnProperty(property)) continue;
    Object.defineProperty(
      to,
      property,
      Object.getOwnPropertyDescriptor(from, property)
    );
  }

  return to;
}

extend({}, { get a(){ return 1 } })
// { get a(){ return 1 } })

上面代碼中,hasOwnProperty那一行用來過濾掉繼承的屬性,否則可能會報錯,因為Object.getOwnPropertyDescriptor讀不到繼承屬性的屬性描述對象。


8、控制對象的狀態(tài)
有時需要凍結(jié)對象的讀寫狀態(tài),防止對象被改變。JavaScript 提供了三種凍結(jié)方法:

  • 最弱的一種是Object.preventExtensions
  • 其次是Object.seal
  • 最強的是Object.freeze

8.1Object.preventExtensions()
Object.preventExtensions方法可以使得一個對象無法再添加新的屬性

var obj = new Object();
Object.preventExtensions(obj);

Object.defineProperty(obj, 'p', {
  value: 'hello'
});
// TypeError: Cannot define property:p, object is not extensible.

obj.p = 1;
obj.p // undefined

上面代碼中,obj對象經(jīng)過Object.preventExtensions以后,就無法添加新屬性了。

8.2、Object.isExtensible()
Object.isExtensible方法用于檢查一個對象是否使用了Object.preventExtensions方法。也就是說,檢查是否可以為一個對象添加屬性。

var obj = new Object();

Object.isExtensible(obj) // true
Object.preventExtensions(obj);
Object.isExtensible(obj) // false

上面代碼中,對obj對象使用Object.preventExtensions方法以后,再使用Object.isExtensible方法,返回false,表示已經(jīng)不能添加新屬性了。

8.3、Object.seal()
Object.seal方法使得一個對象既無法添加新屬性,也無法刪除舊屬性。

var obj = { p: 'hello' };
Object.seal(obj);

delete obj.p;
obj.p // "hello"

obj.x = 'world';
obj.x // undefined

上面代碼中,obj對象執(zhí)行Object.seal方法以后,就無法添加新屬性和刪除舊屬性了。

Object.seal實質(zhì)是把屬性描述對象的configurable屬性設(shè)為false,因此屬性描述對象不再能改變了。

var obj = {
  p: 'a'
};

// seal方法之前
Object.getOwnPropertyDescriptor(obj, 'p')
// Object {
//   value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: true
// }

Object.seal(obj);

// seal方法之后
Object.getOwnPropertyDescriptor(obj, 'p')
// Object {
//   value: "a",
//   writable: true,
//   enumerable: true,
//   configurable: false
// }

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

上面代碼中,使用Object.seal方法之后,屬性描述對象的configurable屬性就變成了false,然后改變enumerable屬性就會報錯。

Object.seal只是禁止新增或刪除屬性,并不影響修改某個屬性的值。

var obj = { p: 'a' };
Object.seal(obj);
obj.p = 'b';
obj.p // 'b'

上面代碼中,Object.seal方法對p屬性的value無效,是因為此時p屬性的可寫性由writable決定。

8.4、Object.isSealed()
Object.isSealed方法用于檢查一個對象是否使用了Object.seal方法。

var obj = { p: 'a' };

Object.seal(obj);
Object.isSealed(obj) // true

這時,Object.isExtensible(檢查是否可以在它上面添加新屬性)方法也返回false

var obj = { p: 'a' };

Object.seal(obj);
Object.isExtensible(obj) // false

8.5、Object.freeze()
Object.freeze方法可以使得一個對象無法添加新屬性、無法刪除舊屬性、也無法改變屬性的值,使得這個對象實際上變成了常量。

var obj = {
  p: 'hello'
};

Object.freeze(obj);

obj.p = 'world';
obj.p // "hello"

obj.t = 'hello';
obj.t // undefined

delete obj.p // false
obj.p // "hello"

上面代碼中,對obj對象進(jìn)行Object.freeze()以后,修改屬性、新增屬性、刪除屬性都無效了。這些操作并不報錯,只是默默地失敗。如果在嚴(yán)格模式下,則會報錯。

8.6、Object.isFrozen()
Object.isFrozen方法用于檢查一個對象是否使用了Object.freeze方法

var obj = {
  p: 'hello'
};

Object.freeze(obj);
Object.isFrozen(obj) // true

使用Object.freeze方法以后,Object.isSealed將會返回trueObject.isExtensible返回false

var obj = {
  p: 'hello'
};

Object.freeze(obj);

Object.isSealed(obj) // true
Object.isExtensible(obj) // false

Object.isFrozen的一個用途是,確認(rèn)某個對象沒有被凍結(jié)后,再對它的屬性賦值

var obj = {
  p: 'hello'
};

Object.freeze(obj);

if (!Object.isFrozen(obj)) {
  obj.p = 'world';
}

上面代碼中,確認(rèn)obj沒有被凍結(jié)后,再對它的屬性賦值,就不會報錯了

8.7、 局限性
以上的三個方法鎖定對象的可寫性有一個漏洞:可以通過改變原型對象,來為對象增加屬性。

var obj = new Object();
Object.preventExtensions(obj);

var proto = Object.getPrototypeOf(obj);
proto.t = 'hello';
obj.t
// hello

上面代碼中,對象obj本身不能新增屬性,但是可以在它的原型對象上新增屬性,就依然能夠在obj上讀到。

一種解決方案是,把obj的原型也凍結(jié)住:

var obj = new Object();
Object.preventExtensions(obj);

var proto = Object.getPrototypeOf(obj);
Object.preventExtensions(proto);

proto.t = 'hello';
obj.t // undefined

另外一個局限是,如果屬性值是對象,上面這些方法只能凍結(jié)屬性指向的對象,而不能凍結(jié)對象本身的內(nèi)容。

var obj = {
  foo: 1,
  bar: ['a', 'b']
};
Object.freeze(obj);

obj.bar.push('c');
obj.bar // ["a", "b", "c"]

上面代碼中,obj.bar屬性指向一個數(shù)組,obj對象被凍結(jié)以后,這個指向無法改變,即無法指向其他值,但是所指向的數(shù)組是可以改變的

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 概述 JavaScript 提供了一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來描述對象的屬性,控制它的行為,比如該屬性是否可寫、可遍歷等...
    oWSQo閱讀 484評論 0 1
  • 參考文章:屬性描述對象 概述 JavaScript 提供了一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來描述對象的屬性,控制它的行為,比如...
    chrisghb閱讀 282評論 0 0
  • 概述 JavaScript提供了一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來描述一個對象的屬性的行為,控制它的行為。這被稱為“屬性描述對...
    zjh111閱讀 735評論 0 0
  • 概述 JavaScript提供了一個內(nèi)部數(shù)據(jù)結(jié)構(gòu),用來描述一個對象的屬性的行為,控制它的行為。這被稱為“屬性描述對...
    許先生__閱讀 499評論 0 1
  • (注1:如果有問題歡迎留言探討,一起學(xué)習(xí)!轉(zhuǎn)載請注明出處,喜歡可以點個贊哦!)(注2:更多內(nèi)容請查看我的目錄。) ...
    love丁酥酥閱讀 1,873評論 4 7