?
本文檔使用 PHP中文網(wǎng)手冊(cè) 發(fā)布
概述
Object對(duì)象的方法
Object()
Object.keys(),Object.getOwnPropertyNames()
Object.observe()
其他方法
Object實(shí)例對(duì)象的方法
Object.prototype.valueOf()
Object.prototype.toString()
toString()的應(yīng)用:判斷數(shù)據(jù)類(lèi)型
對(duì)象的屬性模型
屬性的attributes對(duì)象,Object.getOwnPropertyDescriptor()
Object.defineProperty(),Object.defineProperties()
可枚舉性(enumerable)
Object.getOwnPropertyNames()
Object.prototype.propertyIsEnumerable()
可配置性(configurable)
可寫(xiě)性(writable)
存取器(accessor)
控制對(duì)象狀態(tài)
Object.preventExtensions方法
Object.isExtensible方法
Object.seal方法
Object.isSealed方法
Object.freeze方法
Object.isFrozen方法
局限性
參考鏈接
JavaScript原生提供一個(gè)Object對(duì)象(注意起首的O是大寫(xiě)),所有其他對(duì)象都繼承自這個(gè)對(duì)象。Object本身也是一個(gè)構(gòu)造函數(shù),可以直接通過(guò)它來(lái)生成新對(duì)象。
var o = new Object();
Object作為構(gòu)造函數(shù)使用時(shí),可以接受一個(gè)參數(shù)。如果該參數(shù)是一個(gè)對(duì)象,則直接返回這個(gè)對(duì)象;如果是一個(gè)原始類(lèi)型的值,則返回該值對(duì)應(yīng)的包裝對(duì)象。
var o1 = {a:1}; var o2 = new Object(o1); o1 === o2 // true new Object(123) instanceof Number // true
注意,通過(guò)new Object() 的寫(xiě)法生成新對(duì)象,與字面量的寫(xiě)法 o = {} 是等價(jià)的。
與其他構(gòu)造函數(shù)一樣,如果要在Object對(duì)象上面部署一個(gè)方法,有兩種做法。
(1)部署在Object對(duì)象本身
比如,在Object對(duì)象上面定義一個(gè)print方法,顯示其他對(duì)象的內(nèi)容。
Object.print = function(o){ console.log(o) }; var o = new Object(); Object.print(o) // Object
(2)部署在Object.prototype對(duì)象
所有構(gòu)造函數(shù)都有一個(gè)prototype屬性,指向一個(gè)原型對(duì)象。凡是定義在Object.prototype對(duì)象上面的屬性和方法,將被所有實(shí)例對(duì)象共享。(關(guān)于prototype屬性的詳細(xì)解釋?zhuān)瑓⒁?jiàn)《面向?qū)ο缶幊獭芬徽?。?/p>
Object.prototype.print = function(){ console.log(this)}; var o = new Object(); o.print() // Object
上面代碼在Object.prototype定義了一個(gè)print方法,然后生成一個(gè)Object的實(shí)例o。o直接繼承了Object.prototype的屬性和方法,可以在自身調(diào)用它們,也就是說(shuō),o對(duì)象的print方法實(shí)質(zhì)上是調(diào)用Object.prototype.print方法。。
可以看到,盡管上面兩種寫(xiě)法的print方法功能相同,但是用法是不一樣的,因此必須區(qū)分“構(gòu)造函數(shù)的方法”和“實(shí)例對(duì)象的方法”。
Object本身當(dāng)作工具方法使用時(shí),可以將任意值轉(zhuǎn)為對(duì)象。其中,原始類(lèi)型的值轉(zhuǎn)為對(duì)應(yīng)的包裝對(duì)象(參見(jiàn)《原始類(lèi)型的包裝對(duì)象》一節(jié))。
Object() // 返回一個(gè)空對(duì)象 Object(undefined) // 返回一個(gè)空對(duì)象 Object(null) // 返回一個(gè)空對(duì)象 Object(1) // 等同于 new Number(1) Object('foo') // 等同于 new String('foo') Object(true) // 等同于 new Boolean(true) Object([]) // 返回原數(shù)組 Object({}) // 返回原對(duì)象 Object(function(){}) // 返回原函數(shù)
上面代碼表示Object函數(shù)將各種值,轉(zhuǎn)為對(duì)應(yīng)的對(duì)象。
如果Object函數(shù)的參數(shù)是一個(gè)對(duì)象,它總是返回原對(duì)象。利用這一點(diǎn),可以寫(xiě)一個(gè)判斷變量是否為對(duì)象的函數(shù)。
function isObject(value) { return value === Object(value); }
Object.keys方法和Object.getOwnPropertyNames方法很相似,一般用來(lái)遍歷對(duì)象的屬性。它們的參數(shù)都是一個(gè)對(duì)象,都返回一個(gè)數(shù)組,該數(shù)組的成員都是對(duì)象自身的(而不是繼承的)所有屬性名。它們的區(qū)別在于,Object.keys方法只返回可枚舉的屬性(關(guān)于可枚舉性的詳細(xì)解釋見(jiàn)后文),Object.getOwnPropertyNames方法還返回不可枚舉的屬性名。
var o = { p1: 123, p2: 456}; Object.keys(o) // ["p1", "p2"] Object.getOwnPropertyNames(o) // ["p1", "p2"]
上面的代碼表示,對(duì)于一般的對(duì)象來(lái)說(shuō),這兩個(gè)方法返回的結(jié)果是一樣的。只有涉及不可枚舉屬性時(shí),才會(huì)有不一樣的結(jié)果。
var a = ["Hello", "World"]; Object.keys(a) // ["0", "1"] Object.getOwnPropertyNames(a) // ["0", "1", "length"]
上面代碼中,數(shù)組的length屬性是不可枚舉的屬性,所以只出現(xiàn)在Object.getOwnPropertyNames方法的返回結(jié)果中。
由于JavaScript沒(méi)有提供計(jì)算對(duì)象屬性個(gè)數(shù)的方法,所以可以用這兩個(gè)方法代替。
Object.keys(o).length Object.getOwnPropertyNames(o).length
一般情況下,幾乎總是使用Object.keys方法,遍歷數(shù)組的屬性。
Object.observe方法用于觀察對(duì)象屬性的變化。
var o = {}; Object.observe(o, function(changes) { changes.forEach(function(change) { console.log(change.type, change.name, change.oldValue); }); }); o.foo = 1; // add, 'foo', undefined o.foo = 2; // update, 'foo', 1 delete o.foo; // delete, 'foo', 2
上面代碼表示,通過(guò)Object.observe函數(shù),對(duì)o對(duì)象指定回調(diào)函數(shù)。一旦o對(duì)象的屬性出現(xiàn)任何變化,就會(huì)調(diào)用回調(diào)函數(shù),回調(diào)函數(shù)通過(guò)一個(gè)參數(shù)對(duì)象讀取o的屬性變化的信息。
該方法非常新,只有Chrome瀏覽器的最新版本才部署。
除了上面提到的方法,Object還有不少其他方法,將在后文逐一詳細(xì)介紹。
(1)對(duì)象屬性模型的相關(guān)方法
Object.getOwnPropertyDescriptor():獲取某個(gè)屬性的attributes對(duì)象。
Object.defineProperty():通過(guò)attributes對(duì)象,定義某個(gè)屬性。
Object.defineProperties():通過(guò)attributes對(duì)象,定義多個(gè)屬性。
Object.getOwnPropertyNames():返回直接定義在某個(gè)對(duì)象上面的全部屬性的名稱(chēng)。
(2)控制對(duì)象狀態(tài)的方法
Object.preventExtensions():防止對(duì)象擴(kuò)展。
Object.isExtensible():判斷對(duì)象是否可擴(kuò)展。
Object.seal():禁止對(duì)象配置。
Object.isSealed():判斷一個(gè)對(duì)象是否可配置。
Object.freeze():凍結(jié)一個(gè)對(duì)象。
Object.isFrozen():判斷一個(gè)對(duì)象是否被凍結(jié)。
(3)原型鏈相關(guān)方法
Object.create():生成一個(gè)新對(duì)象,并該對(duì)象的原型。
Object.getPrototypeOf():獲取對(duì)象的Prototype對(duì)象。
除了Object對(duì)象本身的方法,還有不少方法是部署在Object.prototype對(duì)象上的,所有Object的實(shí)例對(duì)象都繼承了這些方法。
Object實(shí)例對(duì)象的方法,主要有以下六個(gè)。
valueOf():返回當(dāng)前對(duì)象對(duì)應(yīng)的值。
toString():返回當(dāng)前對(duì)象對(duì)應(yīng)的字符串形式。
toLocalString():返回當(dāng)前對(duì)象對(duì)應(yīng)的本地字符串形式。
hasOwnProperty():判斷某個(gè)屬性是否為當(dāng)前對(duì)象自身的屬性,還是繼承自原型對(duì)象的屬性。
isPrototypeOf():判斷當(dāng)前對(duì)象是否為另一個(gè)對(duì)象的原型。
propertyIsEnumerable():判斷某個(gè)屬性是否可枚舉。
本節(jié)介紹前兩個(gè)方法,其他方法將在后文相關(guān)章節(jié)介紹。
valueOf方法的作用是返回一個(gè)對(duì)象的值,默認(rèn)情況下返回對(duì)象本身。
var o = new Object(); o.valueOf() === o // true
上面代碼比較o的valueOf方法返回值與o本身,兩者是一樣的。
valueOf方法的主要用途是,JavaScript自動(dòng)類(lèi)型轉(zhuǎn)換時(shí)會(huì)默認(rèn)調(diào)用這個(gè)方法(詳見(jiàn)上一章《數(shù)據(jù)類(lèi)型轉(zhuǎn)換》一節(jié))。
var o = new Object(); 1 + o // "1[object Object]"
上面代碼將對(duì)象o與數(shù)字1相加,這時(shí)JavaScript就會(huì)默認(rèn)調(diào)用valueOf()方法。所以,如果自定義valueOf方法,就可以得到想要的結(jié)果。
var o = new Object(); o.valueOf = function (){return 2;}; 1 + o // 3
上面代碼自定義了o對(duì)象的valueOf方法,于是1 + o就得到了3。這種方法就相當(dāng)于用o.valueOf覆蓋Object.prototype.valueOf。
toString方法的作用是返回一個(gè)對(duì)象的字符串形式。
var o1 = new Object(); o1.toString() // "[object Object]" var o2 = {a:1}; o2.toString() // "[object Object]"
上面代碼表示,對(duì)于一個(gè)對(duì)象調(diào)用toString方法,會(huì)返回字符串[object Object]。
字符串[object Object]本身沒(méi)有太大的用處,但是通過(guò)自定義toString方法,可以讓對(duì)象在自動(dòng)類(lèi)型轉(zhuǎn)換時(shí),得到想要的字符串形式。
var o = new Object(); o.toString = function (){ return 'hello' }; o + ' ' + 'world' // "hello world"
上面代碼表示,當(dāng)對(duì)象用于字符串加法時(shí),會(huì)自動(dòng)調(diào)用toString方法。由于自定義了toString方法,所以返回字符串hello world。
數(shù)組、字符串和函數(shù)都分別部署了自己版本的toString方法。
[1,2,3].toString() // "1,2,3" '123'.toString() // "123" (function (){return 123}).toString() // "function (){return 123}"
toString方法的主要用途是返回對(duì)象的字符串形式,除此之外,還有一個(gè)重要的作用,就是判斷一個(gè)值的類(lèi)型。
var o = {}; o.toString() // "[object Object]"
上面代碼調(diào)用空對(duì)象的toString方法,結(jié)果返回一個(gè)字符串“object Object”,其中第二個(gè)Object表示該值的準(zhǔn)確類(lèi)型。這是一個(gè)十分有用的判斷數(shù)據(jù)類(lèi)型的方法。
實(shí)例對(duì)象的toString方法,實(shí)際上是調(diào)用Object.prototype.toString方法。使用call方法,可以在任意值上調(diào)用Object.prototype.toString方法,從而幫助我們判斷這個(gè)值的類(lèi)型。不同數(shù)據(jù)類(lèi)型的toString方法返回值如下:
數(shù)值:返回[object Number]。
字符串:返回[object String]。
布爾值:返回[object Boolean]。
undefined:返回[object Undefined]。
null:返回[object Null]。
對(duì)象:返回"[object " + 構(gòu)造函數(shù)的名稱(chēng) + "]" 。
Object.prototype.toString.call(2) // "[object Number]" Object.prototype.toString.call('') // "[object String]" Object.prototype.toString.call(true) // "[object Boolean]" Object.prototype.toString.call(undefined) // "[object Undefined]" Object.prototype.toString.call(null) // "[object Null]" Object.prototype.toString.call(Math) // "[object Math]" Object.prototype.toString.call({}) // "[object Object]" Object.prototype.toString.call([]) // "[object Array]"
可以利用這個(gè)特性,寫(xiě)出一個(gè)比typeof運(yùn)算符更準(zhǔn)確的類(lèi)型判斷函數(shù)。
var type = function (o){ var s = Object.prototype.toString.call(o); return s.match(/\[object (.*?)\]/)[1].toLowerCase(); }; type({}); // "object" type([]); // "array" type(5); // "number" type(null); // "null" type(); // "undefined" type(/abcd/); // "regex" type(new Date()); // "date"
在上面這個(gè)type函數(shù)的基礎(chǔ)上,還可以加上專(zhuān)門(mén)判斷某種類(lèi)型數(shù)據(jù)的方法。
['Null', 'Undefined', 'Object', 'Array', 'String', 'Number', 'Boolean', 'Function', 'RegExp', 'Element', 'NaN', 'Infinite' ].forEach(function (t) { type['is' + t] = function (o) { return type(o) === t.toLowerCase(); }; }); type.isObject({}); // true type.isNumber(NaN); // false type.isElement(document.createElement('div')); // true type.isRegExp(/abc/); // true
ECMAScript 5對(duì)于對(duì)象的屬性,提出了一個(gè)精確的描述模型。
在JavaScript內(nèi)部,每個(gè)屬性都有一個(gè)對(duì)應(yīng)的attributes對(duì)象,保存該屬性的一些元信息。使用Object.getOwnPropertyDescriptor方法,可以讀取attributes對(duì)象。
var o = { p: 'a' }; Object.getOwnPropertyDescriptor(o, 'p') // Object { value: "a", // writable: true, // enumerable: true, // configurable: true // }
上面代碼表示,使用Object.getOwnPropertyDescriptor方法,讀取o對(duì)象的p屬性的attributes對(duì)象。
attributes對(duì)象包含如下元信息:
value:表示該屬性的值,默認(rèn)為undefined。
writable:表示該屬性的值(value)是否可以改變,默認(rèn)為true。
enumerable: 表示該屬性是否可枚舉,默認(rèn)為true,也就是該屬性會(huì)出現(xiàn)在for...in和Object.keys()等操作中。
configurable:表示“可配置性”,默認(rèn)為true。如果設(shè)為false,表示無(wú)法刪除該屬性,也不得改變attributes對(duì)象(value屬性除外),也就是configurable屬性控制了attributes對(duì)象的可寫(xiě)性。
get:表示該屬性的取值函數(shù)(getter),默認(rèn)為undefined。
set:表示該屬性的存值函數(shù)(setter),默認(rèn)為undefined。
Object.defineProperty方法允許通過(guò)定義attributes對(duì)象,來(lái)定義或修改一個(gè)屬性,然后返回修改后的對(duì)象。它的格式如下:
Object.defineProperty(object, propertyName, attributesObject)
Object.defineProperty方法接受三個(gè)參數(shù),第一個(gè)是屬性所在的對(duì)象,第二個(gè)是屬性名(它應(yīng)該是一個(gè)字符串),第三個(gè)是屬性的描述對(duì)象。比如,新建一個(gè)o對(duì)象,并定義它的p屬性,可以這樣寫(xiě):
var o = Object.defineProperty({}, "p", { value: 123, writable: false, enumerable: true, configurable: false }); o.p // 123 o.p = 246; o.p // 123 // 因?yàn)閣ritable為false,所以無(wú)法改變?cè)搶傩缘闹?/pre>如果一次性定義或修改多個(gè)屬性,可以使用Object.defineProperties方法。
var o = Object.defineProperties({}, { p1: { value: 123, enumerable: true }, p2: { value: "abc", enumerable: true }, p3: { get: function() { return this.p1+this.p2 }, enumerable:true, configurable:true } }); o.p1 // 123 o.p2 // "abc" o.p3 // "123abc"上面代碼中的p3屬性,定義了取值函數(shù)get。這時(shí)需要注意的是,一旦定義了取值函數(shù)get(或存值函數(shù)set),就不能將writable設(shè)為true,或者同時(shí)定義value屬性,否則會(huì)報(bào)錯(cuò)。
var o = {}; Object.defineProperty(o, "p", { value: 123, get: function() { return 456; } }); // TypeError: Invalid property. // A property cannot both have accessors and be writable or have a value,上面代碼同時(shí)定義了get屬性和value屬性,結(jié)果就報(bào)錯(cuò)。
Object.defineProperty() 和Object.defineProperties() 的第三個(gè)參數(shù),是一個(gè)屬性對(duì)象。它的writable、configurable、enumerable這三個(gè)屬性的默認(rèn)值都為false。
writable屬性為false,表示對(duì)應(yīng)的屬性的值將不得改寫(xiě)。
var o = {}; Object.defineProperty(o, "p", { value: "bar" }); o.p // bar o.p = "foobar"; o.p // bar Object.defineProperty(o, "p", { value: "foobar", }); // TypeError: Cannot redefine property: p上面代碼由于writable屬性默認(rèn)為false,導(dǎo)致無(wú)法對(duì)p屬性重新賦值,但是不會(huì)報(bào)錯(cuò)(嚴(yán)格模式下會(huì)報(bào)錯(cuò))。不過(guò),如果再一次使用Object.defineProperty方法對(duì)value屬性賦值,就會(huì)報(bào)錯(cuò)。
configurable屬性為false,將無(wú)法刪除該屬性,也無(wú)法修改attributes對(duì)象(value屬性除外)。
var o = {}; Object.defineProperty(o, "p", { value: "bar", }); delete o.p o.p // bar上面代碼中,由于configurable屬性默認(rèn)為false,導(dǎo)致無(wú)法刪除某個(gè)屬性。
enumerable屬性為false,表示對(duì)應(yīng)的屬性不會(huì)出現(xiàn)在for...in循環(huán)和Object.keys方法中。
var o = { p1: 10, p2: 13, }; Object.defineProperty(o, "p3", { value: 3, }); for (var i in o) { console.log(i, o[i]); } // p1 10 // p2 13上面代碼中,p3屬性是用Object.defineProperty方法定義的,由于enumerable屬性默認(rèn)為false,所以不出現(xiàn)在for...in循環(huán)中。
可枚舉性(enumerable)
可枚舉性(enumerable)用來(lái)控制所描述的屬性,是否將被包括在for...in循環(huán)之中。具體來(lái)說(shuō),如果一個(gè)屬性的enumerable為false,下面三個(gè)操作不會(huì)取到該屬性。
for..in循環(huán)
Object.keys方法
JSON.stringify方法
因此,enumerable可以用來(lái)設(shè)置“秘密”屬性。
var o = {a:1, b:2}; o.c = 3; Object.defineProperty(o, 'd', { value: 4, enumerable: false }); o.d // 4 for( var key in o ) console.log( o[key] ); // 1 // 2 // 3 Object.keys(o) // ["a", "b", "c"] JSON.stringify(o // => "{a:1,b:2,c:3}"
上面代碼中,d屬性的enumerable為false,所以一般的遍歷操作都無(wú)法獲取該屬性,使得它有點(diǎn)像“秘密”屬性,但還是可以直接獲取它的值。
至于for...in循環(huán)和Object.keys方法的區(qū)別,在于前者包括對(duì)象繼承自原型對(duì)象的屬性,而后者只包括對(duì)象本身的屬性。如果需要獲取對(duì)象自身的所有屬性,不管enumerable的值,可以使用Object.getOwnPropertyNames方法,詳見(jiàn)下文。
考慮到JSON.stringify方法會(huì)排除enumerable為false的值,有時(shí)可以利用這一點(diǎn),為對(duì)象添加注釋信息。
var car = { id: 123, color: red, owner: 12 }; var owner = { id: 12, name: Javi }; Object.defineProperty( car, 'ownerOb', {value: owner} ); car.ownerOb // {id:12, name:Javi} JSON.stringify(car) // '{id: 123, color: "red", owner: 12}'
上面代碼中,owner對(duì)象作為注釋?zhuān)尤隿ar對(duì)象。由于ownerOb屬性的enumerable為false,所以JSON.stringify最后正式輸出car對(duì)象時(shí),會(huì)忽略ownerOb屬性。
Object.getOwnPropertyNames方法返回直接定義在某個(gè)對(duì)象上面的全部屬性的名稱(chēng),而不管該屬性是否可枚舉。
var o = Object.defineProperties({}, { p1: { value: 1, enumerable: true }, p2: { value: 2, enumerable: false } }); Object.getOwnPropertyNames(o) // ["p1", "p2"]
一般來(lái)說(shuō),系統(tǒng)原生的屬性(即非用戶自定義的屬性)都是不可枚舉的。
// 比如,數(shù)組實(shí)例自帶length屬性是不可枚舉的 Object.keys([]) // [] Object.getOwnPropertyNames([]) // [ 'length' ] // Object.prototype對(duì)象的自帶屬性也都是不可枚舉的 Object.keys(Object.prototype) // [] Object.getOwnPropertyNames(Object.prototype) // ['hasOwnProperty', // 'valueOf', // 'constructor', // 'toLocaleString', // 'isPrototypeOf', // 'propertyIsEnumerable', // 'toString']
上面代碼可以看到,數(shù)組的實(shí)例對(duì)象([])沒(méi)有可枚舉屬性,不可枚舉屬性有l(wèi)ength;Object.prototype對(duì)象也沒(méi)有可枚舉屬性,但是有不少不可枚舉屬性。
對(duì)象實(shí)例的propertyIsEnumerable方法用來(lái)判斷一個(gè)屬性是否可枚舉。
var o = {}; o.p = 123; o.propertyIsEnumerable("p") // true o.propertyIsEnumerable("toString") // false
上面代碼中,用戶自定義的p屬性是可枚舉的,而繼承自原型對(duì)象的toString屬性是不可枚舉的。
可配置性(configurable)決定了是否可以修改屬性的描述對(duì)象。也就是說(shuō),當(dāng)configure為false的時(shí)候,value、writable、enumerable和configurable都不能被修改了。
var o = Object.defineProperty({}, 'p', { value: 1, writable: false, enumerable: false, configurable: false }); Object.defineProperty(o,'p', {value: 2}) // TypeError: Cannot redefine property: p Object.defineProperty(o,'p', {writable: true}) // TypeError: Cannot redefine property: p Object.defineProperty(o,'p', {enumerable: true}) // TypeError: Cannot redefine property: p Object.defineProperties(o,'p',{configurable: true}) // TypeError: Cannot redefine property: p
上面代碼首先生成對(duì)象o,并且定義屬性p的configurable為false。然后,逐一改動(dòng)value、writable、enumerable、configurable,結(jié)果都報(bào)錯(cuò)。
需要注意的是,writable只有在從false改為true會(huì)報(bào)錯(cuò),從true改為false則是允許的。
var o = Object.defineProperty({}, 'p', { writable: true }); Object.defineProperty(o,'p', {writable: false}) // 修改成功
至于value,只要writable和configurable有一個(gè)為true,就可以改動(dòng)。
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}) // 修改成功
可配置性決定了一個(gè)變量是否可以被刪除(delete)。
var o = Object.defineProperties({}, { p1: { value: 1, configurable: true }, p2: { value: 2, configurable: false } }); delete o.p1 // true delete o.p2 // false o.p1 // undefined o.p2 // 2
上面代碼中的對(duì)象o有兩個(gè)屬性,p1是可配置的,p2是不可配置的。結(jié)果,p2就無(wú)法刪除。
需要注意的是,當(dāng)使用var命令聲明變量時(shí),變量的configurable為false。
var a1 = 1; Object.getOwnPropertyDescriptor(this,'a1') // Object { // value: 1, // writable: true, // enumerable: true, // configurable: false // }
而不使用var命令聲明變量時(shí)(或者使用屬性賦值的方式聲明變量),變量的可配置性為true。
a2 = 1; Object.getOwnPropertyDescriptor(this,'a2') // Object { // value: 1, // writable: true, // enumerable: true, // configurable: true // } // 或者寫(xiě)成 this.a3 = 1; Object.getOwnPropertyDescriptor(this,'a3') // Object { // value: 1, // writable: true, // enumerable: true, // configurable: true // }
上面代碼中的this.a3 = 1
與a3 = 1
是等價(jià)的寫(xiě)法。this指的是當(dāng)前的作用域,更多關(guān)于this的解釋?zhuān)瑓⒁?jiàn)《面向?qū)ο缶幊獭芬徽隆?!-- /react-text -->
這種差異意味著,如果一個(gè)變量是使用var命令生成的,就無(wú)法用delete命令刪除。也就是說(shuō),delete只能刪除對(duì)象的屬性。
var a1 = 1; a2 = 1; delete a1 // false delete a2 // true a1 // 1 a2 // ReferenceError: a2 is not defined
可寫(xiě)性(writable)決定了屬性的值(value)是否可以被改變。
var o = {}; Object.defineProperty(o, "a", { value : 37, writable : false }); o.a // 37 o.a = 25; o.a // 37
上面代碼將o對(duì)象的a屬性可寫(xiě)性設(shè)為false,然后改變這個(gè)屬性的值,就不會(huì)有任何效果。
這實(shí)際上將某個(gè)屬性的值變成了常量。在ES6中,constant命令可以起到這個(gè)作用,但在ES5中,只有通過(guò)writable達(dá)到同樣目的。
這里需要注意的是,當(dāng)對(duì)a屬性重新賦值的時(shí)候,并不會(huì)拋出錯(cuò)誤,只是靜靜地失敗。但是,如果在嚴(yán)格模式下,這里就會(huì)拋出一個(gè)錯(cuò)誤,即使是對(duì)a屬性重新賦予一個(gè)同樣的值。
關(guān)于可寫(xiě)性,還有一種特殊情況。就是如果原型對(duì)象的某個(gè)屬性的可寫(xiě)性為false,那么派生對(duì)象將無(wú)法自定義這個(gè)屬性。
var proto = Object.defineProperty({}, 'foo', { value: 'a', writable: false }); var o = Object.create(proto); o.foo = 'b'; o.foo // 'a'
上面代碼中,對(duì)象proto的foo屬性不可寫(xiě),結(jié)果proto的派生對(duì)象o,也不可以再自定義這個(gè)屬性了。在嚴(yán)格模式下,這樣做還會(huì)拋出一個(gè)錯(cuò)誤。但是,有一個(gè)規(guī)避方法,就是通過(guò)覆蓋attributes對(duì)象,繞過(guò)這個(gè)限制,原因是這種情況下,原型鏈會(huì)被完全忽視。
Object.defineProperty(o, 'foo', { value: 'b' }); o.foo // 'b'
除了直接定義以外,屬性還可以用存取器(accessor)定義。其中,存值函數(shù)稱(chēng)為setter,使用set命令;取值函數(shù)稱(chēng)為getter,使用get命令。
var o = { get p() { return "getter"; }, set p(value) { console.log("setter: "+value); } }
上面代碼中,o對(duì)象內(nèi)部的get和set命令,分別定義了p屬性的取值函數(shù)和存值函數(shù)。定義了這兩個(gè)函數(shù)之后,對(duì)p屬性取值時(shí),取值函數(shù)會(huì)自動(dòng)調(diào)用;對(duì)p屬性賦值時(shí),存值函數(shù)會(huì)自動(dòng)調(diào)用。
o.p // getter o.p = 123 // setter: 123
存取器往往用于,某個(gè)屬性的值需要依賴(lài)對(duì)象內(nèi)部數(shù)據(jù)的場(chǎng)合。
var o ={ $n : 5, get next(){return this.$n++ }, set next(n) { if (n >= this.$n) this.$n = n; else throw "新的值必須大于當(dāng)前值"; } }; o.next // 5 o.next = 10; o.next //10
上面代碼中,next屬性的存值函數(shù)和取值函數(shù),都依賴(lài)于對(duì)內(nèi)部屬性$n的操作。
下面是另一個(gè)存取器的例子。
var user = {} var nameValue = 'Joe'; Object.defineProperty(user, 'name', { get: function() { return nameValue }, set: function(newValue) { nameValue = newValue; }, configurable: true }); user.name //Joe user.name = 'Bob'; user.name //Bob nameValue //Bob
上面代碼使用存取器,將user對(duì)象name綁定在nameValue屬性上了。
存取器也可以使用Object.create方法定義。
var o = Object.create(Object.prototype, { foo: { get: function () { return 'getter'; }, set: function (value) { console.log('setter: '+value); } } });
如果使用上面這種寫(xiě)法,屬性foo必須定義一個(gè)屬性描述對(duì)象。該對(duì)象的get和set屬性,分別是foo的取值函數(shù)和存值函數(shù)。
利用存取器,可以實(shí)現(xiàn)數(shù)據(jù)對(duì)象與DOM對(duì)象的雙向綁定。
Object.defineProperty(user, 'name', { get: function() { return document.getElementById("foo").value; }, set: function(newValue) { document.getElementById("foo").value = newValue; }, configurable: true });
上面代碼使用存取函數(shù),將DOM對(duì)象foo與數(shù)據(jù)對(duì)象user的name屬性,實(shí)現(xiàn)了綁定。兩者之中只要有一個(gè)對(duì)象發(fā)生變化,就能在另一個(gè)對(duì)象上實(shí)時(shí)反映出來(lái)。
JavaScript提供了三種方法,精確控制一個(gè)對(duì)象的讀寫(xiě)狀態(tài),防止對(duì)象被改變。最弱一層的保護(hù)是preventExtensions,其次是seal,最強(qiáng)的freeze。
Object.preventExtensions方法可以使得一個(gè)對(duì)象無(wú)法再添加新的屬性。
var o = new Object(); Object.preventExtensions(o); Object.defineProperty(o, "p", { value: "hello" }); // TypeError: Cannot define property:p, object is not extensible. o.p = 1; o.p // undefined
如果是在嚴(yán)格模式下,則會(huì)拋出一個(gè)錯(cuò)誤。
(function () { 'use strict'; o.p = '1' }()); // TypeError: Can't add property bar, object is not extensible
不過(guò),對(duì)于使用了preventExtensions方法的對(duì)象,可以用delete命令刪除它的現(xiàn)有屬性。
var o = new Object(); o.p = 1; Object.preventExtensions(o); delete o.p; o.p // undefined
Object.isExtensible方法用于檢查一個(gè)對(duì)象是否使用了preventExtensions方法。也就是說(shuō),該方法可以用來(lái)檢查是否可以為一個(gè)對(duì)象添加屬性。
var o = new Object(); Object.isExtensible(o) // true Object.preventExtensions(o); Object.isExtensible(o) // false
上面代碼新生成了一個(gè)o對(duì)象,對(duì)該對(duì)象使用Object.isExtensible方法,返回true,表示可以添加新屬性。對(duì)該對(duì)象使用Object.preventExtensions方法以后,再使用Object.isExtensible方法,返回false,表示已經(jīng)不能添加新屬性了。
Object.seal方法使得一個(gè)對(duì)象既無(wú)法添加新屬性,也無(wú)法刪除舊屬性。
var o = { p:"hello" }; Object.seal(o); delete o.p; o.p // "hello" o.x = 'world'; o.x // undefined
Object.seal還把現(xiàn)有屬性的attributes對(duì)象的configurable屬性設(shè)為false,使得attributes對(duì)象不再能改變。
var o = { p: 'a' }; // seal方法之前 Object.getOwnPropertyDescriptor(o, 'p') // Object {value: "a", writable: true, enumerable: true, configurable: true} Object.seal(o); // seal方法之后 Object.getOwnPropertyDescriptor(o, 'p') // Object {value: "a", writable: true, enumerable: true, configurable: false} Object.defineProperty(o, 'p', { enumerable: false }) // TypeError: Cannot redefine property: p
從上面代碼可以看到,使用seal方法之后,attributes對(duì)象的configurable就變成了false,然后如果想改變enumerable就會(huì)報(bào)錯(cuò)。
可寫(xiě)性(writable)有點(diǎn)特別。如果writable為false,使用Object.seal方法以后,將無(wú)法將其變成true;但是,如果writable為true,依然可以將其變成false。
var o1 = Object.defineProperty({}, 'p', {writable: false}); Object.seal(o1); Object.defineProperty(o1,'p',{writable:true}) // Uncaught TypeError: Cannot redefine property: p var o2 = Object.defineProperty({}, 'p', {writable: true}); Object.seal(o2); Object.defineProperty(o2,'p',{writable:false}) Object.getOwnPropertyDescriptor(o2, 'p') /* { value: '', writable: false, enumerable: true, configurable: false } */ Object.getOwnPropertyDescriptor(o2, 'p')/* { value: '', writable: false, enumerable: true, configurable: false } */
上面代碼中,同樣是使用了Object.seal方法,如果writable原為false,改變這個(gè)設(shè)置將報(bào)錯(cuò);如果原為true,則不會(huì)有問(wèn)題。
至于屬性對(duì)象的value是否可改變,是由writable決定的。
var o = { p: 'a' }; Object.seal(o); o.p = 'b'; o.p // 'b'
上面代碼中,Object.seal方法對(duì)p屬性的value無(wú)效,是因?yàn)榇藭r(shí)p屬性的writable為true。
Object.isSealed方法用于檢查一個(gè)對(duì)象是否使用了Object.seal方法。
var o = { p: 'a' }; Object.seal(o); Object.isSealed(o) // true
另外,這時(shí)isExtensible方法也返回false。
var o = { p: 'a' }; Object.seal(o); Object.isExtensible(o) // false
Object.freeze方法可以使得一個(gè)對(duì)象無(wú)法添加新屬性、無(wú)法刪除舊屬性、也無(wú)法改變屬性的值,使得這個(gè)對(duì)象實(shí)際上變成了常量。
var o = {p:"hello"};Object.freeze(o); o.p = "world"; o.p // hello o.t = "hello"; o.t // undefined
上面代碼中,對(duì)現(xiàn)有屬性重新賦值(o.p = "world")或者添加一個(gè)新屬性,并不會(huì)報(bào)錯(cuò),只是默默地失敗。但是,如果是在嚴(yán)格模式下,就會(huì)報(bào)錯(cuò)。
var o = {p:"hello"}; Object.freeze(o); // 對(duì)現(xiàn)有屬性重新賦值 (function () { 'use strict'; o.p = "world";}()) // TypeError: Cannot assign to read only property 'p' of #<Object> // 添加不存在的屬性 (function () { 'use strict'; o.t = 123;}()) // TypeError: Can't add property t, object is not extensible
Object.isFrozen方法用于檢查一個(gè)對(duì)象是否使用了Object.freeze()方法。
var o = {p:"hello"}; Object.freeze(o); Object.isFrozen(o) // true
需要注意的是,使用上面這些方法鎖定對(duì)象的可寫(xiě)性,但是依然可以通過(guò)改變?cè)搶?duì)象的原型對(duì)象,來(lái)為它增加屬性。
var o = new Object(); Object.preventExtensions(o); var proto = Object.getPrototypeOf(o); proto.t = "hello"; o.t // hello
一種解決方案是,把原型也凍結(jié)住。
var o = Object.seal( Object.create(Object.freeze({x:1}), {y: {value: 2, writable: true}}) ); Object.getPrototypeOf(o).t = "hello"; o.hello // undefined
Axel Rauschmayer, Protecting objects in JavaScript
kangax, Understanding delete
Jon Bretman, Type Checking in JavaScript
Cody Lindley, Thinking About ECMAScript 5 Parts
Bjorn Tipling, Advanced objects in JavaScript
Javier Márquez, Javascript properties are enumerable, writable and configurable
Sella Rafaeli, Native JavaScript Data-Binding: 使用存取函數(shù)實(shí)現(xiàn)model與view的雙向綁定