深刻 JS 對象屬性

譯者:前端小智
做者:Dr.Axel
來源:2ality

爲了保證的可讀性,本文采用意譯而非直譯。html

想閱讀更多優質文章請猛戳GitHub博客,一年百來篇優質文章等着你!前端

爲了回饋讀者,《大遷世界》不按期舉行(每月一到三次),現金抽獎活動,保底200,外加用戶讚揚,但願你能成爲大遷世界的小錦鯉,快來試試吧

屬性決定JS中對象的狀態,本文章主要分析這些屬性是如何工做的。git

JS幾種不一樣的屬性

JS有三種不一樣的屬性:數據屬性,訪問器屬性和內部屬性。github

1.1 數據屬性(properties

對象的普通屬性將字符串名稱映射到值。例如,下面對象obj有一個數據屬性,名稱爲 prop,對應的值爲 123數據結構

var obj = {
    prop: 123
};

能夠用如下方式讀取屬性的值:函數

console.log(obj.prop); // 123
console.log(obj["prop"]); // 123

固然也能夠用如下方式來設置屬性的值:工具

obj.prop = "abc";
obj["prop"] = "abc";

1.2 訪問器屬性

另外,能夠經過函數處理獲取和設置屬性值。 這些函數稱爲訪問器函數。 處理獲取的函數稱爲getter。 處理設置的函數稱爲setter學習

var obj = {
  get prop () {
    return 'Getter';
  },
  set prop (value) {
    console.log('Setter: ' + value);
  }
}

訪問 obj 屬性:spa

> obj.prop
 'Getter'
> obj.prop = 123;
  Setter: 123

1.3 內部屬性

一些屬性只是用於規範,這些屬於「內部」的內部,由於它們不能直接訪問,可是它們確實影響對象的行爲。內部屬性有特殊的名稱都寫在兩個方括號,如:prototype

  • 內部屬性[[Prototype]]指向對象的原型。它能夠經過Object.getPrototypeOf()讀取。它的值只能經過建立具備給定原型的新對象來設置,例如經過object.create()__proto__
  • 內部屬性[[Extensible]]決定是否能夠向對象添加屬性。能夠經過Object.isExtensible() 方法判斷一個對象是不是可擴展的(是否能夠在它上面添加新的屬性)。能夠經過Object.preventExtensions()方法讓一個對象變的不可擴展,也就是永遠不能再添加新的屬性。

2. 屬性特性(attribute)

屬性的全部狀態,包括數據和元數據,都存儲在特性(attribute)中。它們是屬性具備的字段,就像對象具備屬性同樣。特性(attribute)鍵一般用雙括號編寫:

如下特性是屬於數據屬性:

  • [[Value]]:該屬性的屬性值,默認爲undefined
  • [[Writable]]:是一個布爾值,表示屬性值(value)是否可改變(便是否可寫),默認爲true

如下特性是屬於訪問器屬性:

[[Get]]:是一個函數,表示該屬性的取值函數(getter),默認爲undefined

[[Set]]:是一個函數,表示該屬性的存值函數(setter),默認爲undefined

全部的屬性都具備如下的特性:

[[Enumerable]]:是一個布爾值,表示該屬性是否可遍歷,默認爲true。若是設爲false,會使得某些操做(好比for...in循環、Object.keys())跳過該屬性。

[[Configurable]] :是一個布爾值,表示可配置性,默認爲true。若是設爲false,將阻止某些操做改寫該屬性,好比沒法刪除該屬性,也不得改變該屬性的屬性描述對象(value屬性除外)。也就是說,configurable屬性控制了屬性描述對象的可寫性。

3. 屬性描述

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

{
    value: 123,
    writable: false,
    enumerable: true,
    configurable: false
}

我們也能夠經過訪問器屬性實現相同的目標,屬性描述對象以下所示:

{
  get: function () { return 123 },
  enumerable: true,
  configurable: false
}

3.1 使用屬性描述符的函數

下面的函數容許我們使用屬性描述符:

Object.defineProperty(obj, propName, propDesc):該方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現有屬性, 並返回這個對象。

obj:要在其上定義屬性的對象。
prop:要定義或修改的屬性的名稱。
descriptor:將被定義或修改的屬性描述符。

var obj = Object.defineProperty({}, "foo", {
  value: 123,
})

Object.defineProperties(obj, propDescObj): 該方法直接在一個對象上定義一個或多個新的屬性或修改現有屬性,並返回該對象。

obj: 將要被添加屬性或修改屬性的對象

props: 該對象的一個或多個鍵值對定義了將要爲對象添加或修改的屬性的具體配置

var obj = Object.defineProperties({}, {
  foo: { value: 123, enumerable: true },
  bar: { value: "abc", enumerable: true }
});

Object.create(proto, propDescObj?): 方法建立一個新對象,使用現有的對象來提供新建立的對象的__proto__

proto:新建立對象的原型對象。
propDescObj:可選。若是沒有指定爲 undefined,則是要添加到新建立對象的可枚舉屬性(即其自身定義的屬性,而不是其原型鏈上的枚舉屬性)對象的屬性描述符以及相應的屬性名稱。這些屬性對應Object.defineProperties()的第二個參數。

var obj = Object.create(Object.prototype, {
  foo: { value: 123, enumerable: true },
  bar: { value: "abc", enumerable: true }
})

Object.getOwnPropertyDescriptor(obj, propName): 該方法返回指定對象上一個自有屬性對應的屬性描述符。(自有屬性指的是直接賦予該對象的屬性,不須要從原型鏈上進行查找的屬性)

obj:須要查找的目標對象
prop:目標對象內屬性名稱

var o, d;

o = { get foo() { return 17; } };
d = Object.getOwnPropertyDescriptor(o, "foo");
// d {
//   configurable: true,
//   enumerable: true,
//   get: /*the getter function*/,
//   set: undefined
// }

4.可枚舉性(Enumerable)

本節說明哪些操做受可枚舉性影響,哪些操做不受可見性影響。 下面,假設有如下定義:

var proto = Object.defineProperties({}, {
  foo: { value: 1, enumerable: true },
  bar: { value: 2, enumerable: false }
})

var obj = Object.create(proto, {
  baz: { value: 1, enumerable: true },
  gux: { value: 2, enumerable: false}
})

請注意,對象(包括proto)一般至少具備原型Object.prototype:

> Object.getPrototypeOf({}) === Object.prototype
  true

Object.prototype是定義標準方法(如toStringhasOwnProperty)的地方。

4.1 受可枚舉性影響的操做

可枚舉性僅影響兩個操做:for-in循環和Object.keys()

for-in循環遍歷全部可枚舉屬性的名稱,包括繼承的屬性(請注意,Object.prototype的全部非可枚舉屬性都不會顯示):

> for (var x in obj) console.log(x);
    baz
    foo

Object.keys() 返回全部自有(非繼承)可枚舉屬性的名稱:

> Object.keys(obj)
 [ 'baz' ]

若是須要全部屬性的名稱,則須要使用Object.getOwnPropertyNames()

4.2 忽略可枚舉性的操做

除了上達兩個,其餘操做都忽略了可枚舉性,還有一些操做會考慮繼承:

> "toString" in obj
true
> obj.toString
[Function: toString]

還有一些僅讀取自有屬性:

> Object.getOwnPropertyNames(obj)
[ 'baz', 'qux' ]

> obj.hasOwnProperty("qux")
true
> obj.hasOwnProperty("toString")
false

> Object.getOwnPropertyDescriptor(obj, "qux")
{ value: 2,
  writable: false,
  enumerable: false,
  configurable: false }
> Object.getOwnPropertyDescriptor(obj, "toString")
undefined

建立,刪除和定義屬性僅影響原型鏈中的第一個對象:

obj.propName = value
obj["propName"] = value

delete obj.propName
delete obj["propName"]

Object.defineProperty(obj, propName, desc)
Object.defineProperties(obj, descObj)

5. 最佳實踐

通常規則是系統建立的屬性是不可枚舉的,而用戶建立的屬性是可枚舉的:

> Object.keys([])
[]
> Object.getOwnPropertyNames([])
[ 'length' ]
> Object.keys(['a'])
[ '0' ]

這特別適用於原型對象中的方法:

> Object.keys(Object.prototype)
[]
> Object.getOwnPropertyNames(Object.prototype)
[ hasOwnProperty',
  'valueOf',
  'constructor',
  'toLocaleString',
  'isPrototypeOf',
  'propertyIsEnumerable',
  'toString' ]

所以,對於我們的代碼,應該忽略可枚舉性。一般不該該向內置原型和對象添加屬性,但若是這樣作,我們就應該使它們不可枚舉以免破壞內置代碼。

正如我們所看到的,非可枚舉性主要受益於for-in而且確保使用它的遺留代碼不會中斷。 不可枚舉的屬性建立了一種錯覺,即for-in僅迭代用戶建立的對象自有的屬性。 在我們的代碼中,若是能夠,應該避免使用for-in

原文:https://2ality.com/2012/08/in...

推薦:

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

交流

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索