1、對象再認識
(1)對象屬性和特性
什麼是屬性(Property),什麼是特性(Attribute),這有什麼區別?我不想也不會從語意學上去區分,對於這系列文章來說,屬性就是組成對象的一個部分,廣義上也包括對象的方法,而特性則是指被描述主體所具有的特徵,換句話說,屬性是我們可以通過編碼來訪問的具體存在,而特性則主要是為了便於理解概念的抽象存在,當然,特性也可以通過相應的屬性來具體外化。這一小節所講的對象屬性的特性就是對對象屬性特徵的一個描述,主要來自於ECMA-262規範的第5版,該規範使用兩個中括弧的形式來描述不能直接存取的內部特性。
A、屬性類型(先給屬性分下類):
- 資料屬性:直接存取屬性值的屬性
- 訪問器屬性:通過getter/setter方法來訪問屬性值的屬性
- 內部屬性:不能通過代碼直接存取的屬性,只是為了規範說明目的而存在,在規範中也使用兩個中括弧的形式來描述
B、對象內部屬性
內部屬性不能通過代碼直接存取,它主要是為了描述規範,也是給ECMAScript實現者參考的,而對於開發人員來說,瞭解這些可以便於理解一些內部機制。比如在給一個屬性賦值時,在實現中會調用[[Put]]內部方法,而讀取一個屬性值時,則調用[[Get]]方法。
所有對象公用的內部屬性 |
個別對象特有的內部屬性 |
名稱 |
規範 |
名稱 |
規範 |
對象 |
[[Prototype]] |
Object/Null |
[[PrimitiveValue]] |
primitive |
Boolean|Date|Number|String |
[[Class]] |
String |
[[Construct]] |
SpecOp(a List of any) → Object |
new |
[[Extensible]] |
Boolean |
[[Call]] |
SpecOp(any, a List of any) → any|Reference |
call |
[[Get]] |
SpecOp (propName) →any |
[[HasInstance]] |
SpecOp(any) → Boolean |
Function |
[[GetOwnProperty]] |
SpecOp (propName) →Undefined|Property Descriptor |
[[Scope]] |
Lexical Environment |
Function |
[[GetProperty]] |
SpecOp (propName) →Undefined|Property Descriptor |
[[FormalParameters]] |
List of Strings |
Function |
[[Put]] |
SpecOp (propName, any, Boolean) |
[複製代碼 代碼如下:] |
ECMAScript code |
Function |
[[CanPut]] |
SpecOp (propName) → Boolean |
[[TargetFunction]] |
Object |
Function.prototype.bind |
[[HasProperty]] |
SpecOp (propName) → Boolean |
[[BoundThis]] |
any |
Function.prototype.bind |
[[Delete]] |
SpecOp (propName, Boolean) → Boolean |
[[BoundArguments]] |
List of any |
Function.prototype.bind |
[[DefaultValue]] |
SpecOp (Hint) → primitive |
[[Match]] |
SpecOp(String, index) → MatchResult |
RegExp |
[[DefineOwnProperty]] |
SpecOp (propName, PropDesc, Boolean) → Boolean |
[[ParameterMap]] |
Object |
|
說明:
- 每一個對象都有一個原型對象[[Prototype]],一般我們不能在代碼中直接存取這個內部屬性,但可以通過Object.getPrototypeOf(object)來擷取原型對象(在Firefox中可以通過__proto__來直接存取)。
- 在Object.prototype.toString方法中,按照規範內建對象會返回包含[[Class]]的值“[object class]”,而內建對象的[[Class]]值就是相應的名稱(比如Array對象的[[Class]]值為'Array'),因此可以通過Object.prototype.toString.call(value) == '[object Array]'來判斷value是否是一個Array。
- 給一個屬性賦值時,後台調用[[Put]]來實現,擷取一個屬性值時,後台調用[[Get]]來擷取。
- 使用new操作符調用一個函數時,後台調用[[Construct]],而使用call方法來調用函數時,後台會調用[[Call]]。
- [[HasInstance]]方法返回給定參數是否是通過調用函數來建立的,和Object的方法isPrototypeOf(obj)類似。
- 當一個函數被執行時,就會建立一個[[Scope]]對象,可以理解為[[Scope]]就是我們前面所說的使用中的物件,也就是說this、arguments、形參、函數內部定義的變數和函數都是的[[Scope]]對象的屬性。
C、屬性特性(用來描述屬性的特性)
內部特性 |
配置屬性 |
屬性類型 |
資料類型 |
預設值 |
含義 |
備忘 |
[[Configurable]] |
configurable |
資料屬性 訪問器屬性 |
Boolean |
true |
能否通過delete刪除屬性從而重新定義屬性 能否修改屬性的特性 能否把屬性修改為訪問器特性 |
一旦把屬性定義為不可配置的,就不能再變為可配置的 如果為false,不能做刪除、也不能修改屬性特性,但是允許修改屬性值 非strict 模式下會忽略相應操作,strict 模式下則拋出異常 |
[[Enumerable]] |
enumerable |
資料屬性 訪問器屬性 |
Boolean |
true |
能否通過for-in迴圈返回屬性 |
為true時可以通過for-in來枚舉,否則不能通過for-in枚舉 |
[[Writable]] |
writable |
資料屬性 |
Boolean |
true |
能否修改屬性的值 |
為false時不能修改屬性值,非strict 模式下會忽略相應操作,strict 模式下則拋出異常 |
[[Value]] |
value |
資料屬性 |
任意類型 |
undefined |
屬性值 |
|
[[Get]] |
get |
訪問器屬性 |
Undefined/Function |
undefined |
讀取屬性時調用的函數 |
為一個函數時,會無參數調用這個函數,並將傳回值作為屬性值返回 |
[[Set]] |
set |
訪問器屬性 |
Undefined/Function |
undefined |
寫入屬性時調用的函數 |
為一個函數時,會將傳入的值作為參數調用這個函數,賦給屬性 |
說明:
- 配置屬性是指使用下面要講的屬性定義方法時用以定義相關特性的配置項名稱。
- 對於訪問器屬性,[[Get]]、[[Set]]不一定都有,沒有[[Get]]的屬性不能讀(返回undefined,strict 模式下拋出異常),沒有[[Set]]的屬性不能寫(會忽略,strict 模式下拋出異常)。
- 注意區分對象內部屬性和對象屬性的特性。
D、屬性定義方法(用來定義屬性的方法)
最常見的定義屬性的方法就是直接在對象上添加屬性,比如obj.name = 'linjisong',這種情況下定義的屬性所具有的內部特性都是預設的,如果想定義一個值不能被修改的屬性要怎麼做呢?在ES中給我們提供了幾個方法用於實作類別似的功能。
方法名 |
功能說明 |
參數和傳回值 |
說明 |
調用樣本 |
defineProperty() |
定義一個屬性 |
(1)目標對象 (2)屬性的名字 (3)屬性描述符對象 |
使用屬性定義方法時 [[Enumerable]] [[Configurable]] [[Writable]] 預設值為false |
// 建立一個包含一個預設屬性job的對象(job屬性可以修改、刪除、在for-in中枚舉) var person = {job:'it'}; // 添加一個不能被修改、刪除的name屬性 Object.defineProperty(person, 'name', { value:'linjisong',//這裡的配置屬性和上面特性列表中的配置屬性一致 enumerable:true }); // 定義多個屬性(資料屬性year和訪問器屬性age) Object.defineProperties(person, { year:{ value : 2012, configurable:true, writable:true }, age:{ get : function(){ return this.year-1983; } } }); var job = Object.getOwnPropertyDescriptor(person, 'job'); console.info(job.configurable);//true,直接添加屬性時預設為true var name = Object.getOwnPropertyDescriptor(person, 'name'); console.info(name.configurable);//false,使用屬性定義方法添加屬性時預設為false console.info(person.name);//linjisong person.name = 'oulinhai';//由於不能修改,所以值不會改變,在strict 模式下會拋出異常 console.info(person.name);//linjisong person.year = 2015; console.info(person.year);//2015 console.info(person.age);//32,在修改year的同時,也修改了age屬性 |
defineProperties() |
定義一組屬性 |
(1)目標對象 (2)多個屬性描述符組成的一個對象 |
getOwnPropertyDescriptor() |
擷取屬性的特性 |
(1)目標對象 (2)屬性的名字 (3)返回一個包括了屬性特性的對象 |
|
註:這些方法設定或擷取的屬性特殊和屬性的類型有關,比如資料屬性只能設定[[Confirurable]]、[[Enumerable]]、[[Writable]]、[[Value]]。
(2)防篡改對象
所謂防篡改對象,就是給對象一定層級的保護以防止在這個層級上對對象的變更,在ES5規範中,定義了依次升高的三種保護層級:
保護層級 |
描述 |
操作方法 |
判斷方法 |
說明 |
不可擴充 |
不能給對象添加新屬性和方法,但可以修改已有屬性和方法 |
preventExtensions() |
isExtensible():不能擴充時返回false |
|
密封 |
不可擴充,並且已有成員的[[Configurable]]設定為false,不能刪除屬性,但可以修改屬性值 |
seal() |
isSeal():被密封時返回true |
isSeal()為true時一定有isExtensible()為false |
凍結 |
密封,其[[Writable]]設定為false,但如果定義了[[Set]],訪問器屬性仍然可寫 |
freeze() |
isFrozen():被凍結時返回true |
isFrozen()為true時一定有isSeal()為true,isExtensible()為false |
註:一旦定義成了防篡改對象,就不能撤銷。
(3)對象的其它方法
名稱 |
描述 |
create(prototype[,descriptors]) |
建立一個具有指定原型且可選擇性地包含指定屬性的對象 |
getOwnPropertyNames(object) |
返回對象的屬性(方法)的名稱 |
getPrototypeOf(object) |
返回對象的原型 |
keys(object) |
返回對象的可枚舉屬性(方法)的名稱 |
這裡的create(prototype[,descriptors])是一個非常有意思的方法,規範中這樣描述它的行為:
[code]
①如果prototype不是Null或Object,拋出TypeError異常
②var obj = new Object()
③設定obj的內部屬性[[Prototype]]為prototype
④如果descriptors存在且不為undefined,使用Object.defineProperties(obj,descriptors)來添加屬性
⑤返回obj