摘要:中有兩種屬性數據屬性和訪問器屬性數據屬性數據屬性包含一個數據值的位置。表示能否通過刪除屬性從而重新定義屬性可配置的能否修改屬性的特性能否把屬性修改為訪問器屬性。以上代碼在對外上定義了兩個數據屬性和和一個訪問器屬性。
屬性類型:
ECMA-262第5版在定義只有內部才用的特性(attribute)時,描述了屬性(property)的各種特征。ECMA-262定義這些特性是為了實現JavaScript引擎用的,因此在JavaScript中不能直接訪問它們。為了表示特性是內部值,該規范把它們放在兩對兒方括號中,例如[[Enumerable]]。
ECMAScript中有兩種屬性:數據屬性和訪問器屬性
數據屬性:數據屬性包含一個數據值的位置。在這個位置可以讀取和寫入值。數據屬性有4個描述其行為的特性。
[[Configurable]]
表示能否通過delete刪除屬性從而重新定義屬性(可配置的)
能否修改屬性的特性
能否把屬性修改為訪問器屬性。
默認值為true。
[[Enumerable]]
表示能否通過for-in循環遍歷屬性(可枚舉的)
默認值為true
[[Writable]]
表示能否修改屬性的值。(可寫的)
默認值為true
[[Value]]
包含這個屬性的數據值。
讀取屬性值的時候,從這個位置讀;
寫入屬性值的時候,把新值保存在這個位置。
默認值為undefined
對于直接在對象上定義的屬性,它們的[[Configurable]]、[[Enumerable]]、[[Writable]]特性都被設置為true,而[[Value]]特性被設置為指定的值。例如:
var person = { name:"Nicholas" };
這里創建了一個名為name的屬性,為它指定的值是"Nicholas"。也就是說,[[Value]]特性將被設置為"Nicholas",而對這個值的任何修改都將反映在這個位置。
要修改屬性默認的特性,必須使用ECMAScript5的Object.defineProperty()方法。這個方法接受三個參數:屬性所在的對象、屬性的名字和一個描述符對象。其中,描述符(descriptor)對象的屬性必須是:confirgurable、enumerable、writable和value。設置其中的一或多個值,可以修改對應的特性值。例如:
var person = {}; Object.defineProperty(person,"name",{ writable:false, value:"Nicholas" }); console.log(person.name); // "Nicholas" person.name = "Greg"; console.log(person.name); // "Nicholas"
創建了一個名為name的屬性,它的值"Nicholas"是只讀的。這個屬性的值是不可修改的,如果嘗試為它指定新值,則在非嚴格模式下,賦值操作將被忽略;在嚴格模式下,賦值操作會導致拋出錯誤。
類似的規則也適用與不可配制的屬性。例如:
var person = {}; Object.defineProperty(person,"name",{ configurable:false, value:"Nicholas" }); console.log(person.name); // "Nicholas" delete person.name; console.log(person.name); // "Nicholas"
把configurable設置為false,表示不能從對象中刪除屬性。如果對這個屬性調用delete,則在非嚴格模式下什么也不會發生,而在嚴格模式下會導致錯誤。
而且,一旦把屬性定義為不可配置的(false),就不能再把它變回可配置了。此時,再調用Object.defineProperty()方法修改除writable之外的特性,都會導致錯誤:
var person = {}; Object.defineProperty(person,"name",{ configurable:false, value:"Nicholas" }); // 拋出錯誤 "Uncaught TypeError: Cannot redefine property: name" Object.defineProperty(person,"name",{ configurable:true, value:"Nicholas" });也就是說,可以多次調用Object.defineProperty()方法修改同一個屬性,但如果把configurable特性設置為false之后,就會有限制了。 調用Object.defineProperty()方法時,如果不顯式指定,configurable、enumerable和writable特性的默認值都是false。
多數情況下,可能都沒有必要利用Object.defineProperty()提供的這些高級功能。不過,理解這些概念對理解JavaScript對象卻非常有用。
兩種定義方法的描述符默認情況Test: 訪問器屬性IE8是第一個實現Object.defineProperty()方法的瀏覽器版本。然而,這個版本的實現存在諸多限制:只能在DOM對象上使用這個方法,而且只能創建訪問器屬性。由于實現不徹底,建議不要在IE8中使用Object.defineProperty()方法。
訪問器屬性不包含數據值;它們包含一對兒getter和setter函數(不過,這兩個函數都不是必需的)。
在讀取訪問器屬時,會調用getter函數,這個函數負責返回有效的值;
在寫入訪問器屬性時,會調用setter函數并傳入新值,這個函數負責決定如何處理數據。
訪問器屬性有4個特性:
[[Configurable]]
表示能否通過delete刪除屬性從而重新定義屬性
能否修改屬性的特性
或者能否把屬性修改為數據屬性
對于直接在對象上定義的屬性,這個特性的默認值為true
[[Enumerable]]
表示能否通過for-in循環遍歷屬性
對于直接在對象上定義的屬性,這個特性的默認值為true。
[[Get]]
在讀取屬性時調用的函數
默認值為undefined
[[Set]]
寫入屬性時調用的函數
默認值為undefined
訪問器屬性不能直接定義,必須使用Object.defineProperty()來定義。
var book = { _year:2004, edition:1 }; Object.defineProperty(book,"year",{ get:function () { return this._year; }, set:function (newValue) { if ((newValue > 2004)) { this._year = newValue; this.edition += newValue - 2004; } } }) book.year = 2005; alert(book.edition); // 2
創建一個book對象,并給它定義兩個默認的屬性:_year、edition。_year前面的下劃線是一種常用的記號,用于表示只能通過對象方法訪問的屬性。而訪問器屬性year則包含一個getter函數和一個setter函數。
getter函數返回_year的值
setter函數通過計算來確定正確的版本
因此,把year屬性修改為2005會導致_year變成2005,而edition變為2。這是使用訪問器屬性的常見方式,即設置一個屬性的值會導致其他屬性發生變化。
不一定非要同時指定getter和setter。只指定getter意味著屬性是不能寫,嘗試寫入屬性會被忽略。在嚴格模式下,嘗試寫入只指定了getter函數的的屬性會拋出錯誤。類似地,沒有指定setter函數的屬性也不能讀,否則在非嚴格模式下會返回undefined,在嚴格模式下會拋出錯誤。
// 下劃線表示只能通過對象方法訪問的屬性 var obj = { _x:"obj._x", _y:"obj._y", _z:"obj._z" }; // Object.defineProperty(obj,"x",{ // x屬性,只讀不能寫 get:function () { return this._x; } }); console.log(obj.x); // "obj._x" 可以讀取,調用obj.x實際上調用了obj._x的getter函數 obj.x = "Rewrite x"; // 嘗試修改x屬性 console.log(obj.x); // "obj._x" 寫入失敗 // Object.defineProperty(obj,"y",{ // y屬性,只寫不能讀 set:function (newValue) { this._y = newValue; } }); console.log(obj.y); // "undefined" 讀取失敗 obj.y = "Rewrite obj.y"; // 嘗試修改屬性 console.log(obj._y); // "Rewrite obj.y" 可以寫入 // Object.defineProperty(obj,"z",{ // z屬性可讀可寫 get:function () { return this._z; }, set:function (newValue) { this._z = newValue; } }); console.log(obj.z); // "obj._z" obj.z = "Rewrite obj.z"; // 修改z屬性 console.log(obj._z); // "Rewrite obj._z"
支持ES5這個方法的瀏覽器有IE9+(IE8部分實現)、Firefox4+、Safari5+、Opera12+和Chrome。在這個方法之前,創建訪問器屬性一般使用兩個標準方法:_defineGetter_()和_defineSetter_()。這兩個方法最初是由Firefox引入的,后來Safari3、Chrome1He Opera9.5也給出了相同的實現。使用這兩個遺留的方法,可以像下面這樣重寫前面的例子。
var book = { _year:2004, edition:1 }; // 定義訪問器的舊有方法 book.__defineGetter__("year",function(){ return this._year; }); book.__defineSetter__("year",function(newValue){ if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } }); book.year = 2005; console.log(book.edition); // 2
在不支持Object.defineProperty()方法的瀏覽器中不能修改[[Configurable]]和[[Enumerable]]。
定義多個屬性Object.definePropertys()方法。var book = {}; Object.defineProperties(book,{ _year:{ value:2004 }, edition:{ value:1 }, year:{ get:function () { return this._year; }, set:function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } }, });
以上代碼在book對外上定義了兩個數據屬性(_year和edition)和一個訪問器屬性(year)。最終的對象與上一節中定義的對象相同。唯一區別是這里的屬性都是在同一時間創建的。
讀取屬性的特性Object.getOwnPropertyDescriptor()使用ES5的Object.getOwnPropertyDescriptor()方法,可以取得給定屬性的描述符,這個方法接收兩個參數:屬性所在的對象和要讀取其描述符的屬性名稱。返回值是一個對象,
如果是訪問器屬性,這個對象的屬性有configurable、enumerable、get和set;
如果是數據屬性,這個對象的屬性有configurable、enumerable、writable、value。
var book = {}; Object.defineProperties(book,{ _year:{ value:2004 }, edition:{ value:1 }, year:{ get:function () { return this._year; }, set:function (newValue) { if (newValue > 2004) { this._year = newValue; this.edition += newValue - 2004; } } } }); var descriptor = Object.getOwnPropertyDescriptor(book,"_year"); alert(descriptor.value); // 2004 alert(descriptor.configurable); // false alert(typeof descriptor.get); // "undefined" var descriptor = Object.getOwnPropertyDescriptor(book,"year"); alert(descriptor.value); // "undefined" alert(descriptor.enumerable); // false alert(descriptor.get); // "function"
對于數據屬性_year,value等于最初的值,configurable是false,而get等與undefined。
對于訪問器屬性year,value等于undefined,enumerable是false,而get是一個指向getter函數的指針。
Objecct.create方法的第二個參數是一個關聯數組,其鍵為屬性名,其值為屬性描述符(屬性對象)。
下面是個具體例子。其中屬性值是通過value屬性指定的。大部分屬性默認值是false,這個例子中會將它們顯式地指定為true。
var obj = {x:2,y:3}; // 與下面代碼等價 var obj = Object.create(Object.prototype,{ x:{ value:2, writable:true, enumerable:true, configurable:true }, y:{ value:3, writable:true, enumerable:true, configurable:true } });
可以通過Object.create來顯式地指定屬性的屬性。
只要將get屬性與set屬性指定為相應的函數,就能夠定義一個只能夠通過訪問器getter和setter來訪問值的屬性。訪問器與value屬性是互相排斥的,也就是說,如果指定了value屬性的值,訪問器(同時包括get和set)就會失效;反之,如果指定了訪問器(get或set中的某一個),value屬性就會失效。
(get、set)與value互斥從內部來看,將屬性作為右值訪問時使用的是getter函數,而將屬性作為左值進行賦值時使用的是setter函數。
只要能寫出正確的getter訪問器函數,就能依以此為基礎設計出一個不可變對象。
var obj = Object.create(Object.prototype,{ x:{ get:function () { alert("get called"); }, set:function () { alert("set called"); } } }); alert(obj.x); // "get called" 當要讀取屬性時,將會調用getter函數 // undefined 由于getter函數沒有返回值,默認返回undefined obj.x = 1; // "set called" 當要寫入屬性值時,將調用setter函數 alert(obj.x); //"get called" 驗證了setter函數并沒有修改屬性,依舊是原屬性 // undefined 調用getter函數,沒有指定返回值,默認返回undefined
訪問器函數也可以通過對象字面量來表述。下面代碼中的對象與上圖是一樣的。
var obj = { get x() { alert("get called"); }, set x(v) { alert("set called"); } };
getter函數與setter函數中的this引用指向的是當前對象,不過下面這樣的代碼是無法運行的。這是因為其中的每個訪問器函數都會不斷調用訪問器函數。(?!)
// 無法運行,有無限loop的致命錯誤 var obj = Object.create(Object.prototype,{ x:{ get:function () {return this.x}, set:function (v) {this.x = v} } }); alert(obj.x); // 讀取obj.x,隱式調用了x屬性的getter函數,函數的this引用指向obj,則this.x等價于調用obj.x。出現循環錯誤
下面的代碼雖然能夠運行,但還是有些問題
// 使用了隱藏的屬性_x的訪問器的例子(姑且算是能夠運行),x作為訪問器屬性 var obj = Object.create(Object.prototype,{ x:{ get:function () {return this._x}, set:function (v) {return this._x = v}, x:{writable:true} } }); obj._x = "obj._x"; alert(obj.x); // obj.x obj.x = "Rewrite obj._x"; alert(obj.x); // "Rewrite obj._x"
這段代碼的問題指出在于屬性_x是可以從外部改寫的(不符合訪問器屬性規范)
不借助與規范的更恰當的方法是通過閉包來隱藏這個變量。
function createObject() { var _x = 0; // 變量名也可以用x,不過容易產生混亂,所以這里仍使用_x // 返回了一個定義了訪問器的對象 return { get x() {return _x}, set x(v) {_x = v} }; }; var obj = createObject(); // 生成對象 alert(obj.x); // 讀取(在內部調用getter) // 0 obj.x = 1; // 改寫(在內部調用setter) alert(obj.x); // 1
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/83419.html
摘要:高程讀書筆記第六章理解對象創建自定義對象的方式有創建一個實例,然后為它添加屬性和方法。創建了自定義的構造函數之后,其原型對象默認只會取得屬性至于其他方法都是從繼承而來的。 JS高程讀書筆記--第六章 理解對象 創建自定義對象的方式有創建一個Object實例,然后為它添加屬性和方法。還可用創建對象字面量的方式 屬性類型 ECMAScript在定義只有內部采用的特性時,描述了屬性的各種特征...
摘要:與執行環境相關的變量對象中有執行環境定義的所有變量和函數作用域鏈代碼在一個環境中執行,便會創建變量對象的一個作用域鏈。 執行環境 執行環境是什么? javascript的解釋器每次開始執行一個函數時,都會為每個函數創建一個執行環境(execution context)。 執行環境定義了變量或者函數有權訪問的其他數據,決定了他們各自的行為。 與執行環境相關的變量對象(...
摘要:高程讀書筆記第五章類型創建實例的方式有兩種。第一種是使用操作符后跟構造函數,另一種方式是使用對象字面量表示法。 JS高程讀書筆記--第五章 Object類型 創建Object實例的方式有兩種。第一種是使用new操作符后跟Object構造函數,另一種方式是使用對象字面量表示法。 在通過對象字面量定義對象時,實際上不會調用Object構造函數 訪問對象屬性時可以使用點表示法和方括號表示法。...
摘要:高程第六章繼承理解與實踐昨日細細的讀了一遍高程現在寫篇文章來鞏固下認知吧讀首先是從中讀到了什么我自己也在讀書的時候用筆記下了各個部分的點現在等于閱讀筆記回憶下書本理解基礎第五版中規定了兩種屬性數據屬性訪問器屬性數據屬性包含一個數據值的位 JavaScript高程第六章:繼承-理解與實踐昨日細細的讀了一遍JavaScript高程,現在寫篇文章來鞏固下認知吧. 讀 首先是從中讀到了什么,我...
摘要:創建一個新對象將構造函數的作用域賦給新對象因此就指向了這個新對象執行構造函數中的代碼為這個新對象添加屬性返回新對象。 本章內容 理解對象屬性 理解并創建對象 理解繼承 ECMA-262把對象定義為:無序屬性的集合,其屬性可以包含基本值、對象或者函數 理解對象 創建對象 創建自定義對象的最簡單方式就是創建一個Object的實例,再為它添加屬性和方法。 var person = new...
閱讀 2145·2023-04-26 00:23
閱讀 820·2021-09-08 09:45
閱讀 2442·2019-08-28 18:20
閱讀 2548·2019-08-26 13:51
閱讀 1602·2019-08-26 10:32
閱讀 1398·2019-08-26 10:24
閱讀 2035·2019-08-26 10:23
閱讀 2201·2019-08-23 18:10