要了解JavaScript對象,我們可以從對象創建、屬性操作、對象方法這幾個方面入手。概括起來,包括以下幾模塊:
1.創建對象
1.1 對象直接量
對象直接量是創建對象最簡單的方式,由若干名/值對組成映射表:
var point = {x: 0, y: 0 };
屬性名也沒有什麼限制,可以是js的關鍵字或者任意字符串,如果是這兩種情況,屬性需要用雙引號引起來:
var empty = {}; va point = {x: 0, y: 0 }; var book = { "main title": "Javascript", "sub-title": "The definitive Guide", "for": "all audience", author: { firstName: "Davide", lastName: "Flanagan" } };
對象直接量創建對象十分簡單,但一般都不會這樣使用。代碼可復用性低,如果想要在其他地方使用該對象並且屬性值不一樣,那這麼辦?是不是又得重新創建一份代碼?
1.2 通過new創建對象
通過new創建對象之前,先要創建一個函數,new把這個函數當做構造函數(constructor)。例如通過new創建一個Person對象:
function Person(){ //構造函數 } var person = new Person();
Javscript語言核心中的原始類型都包含內置構造函數:
var a = new Array(); var d = new Date(); var r = new RegExp(“js”);
1.3 Object.create()
在了解Object的create方法之前,我們想看看什麼是原型。每一個Javascript對象(null除外)都和另一個對象相關聯。“另一個”對象就是我們所說的原型。每一個對象都從原型繼承屬性。
所有通過對象直接量創建的對象都具有同一個原型對象Object.prototype。關鍵字new和構造函數創建的對象原型就是構造函數的prototype屬性的值。通過new Array()創建對象的原型為Array.prototype,通過new Date()創建的對象原型為Date.prototype。原型暫介紹到這裡。
Object.create方法包含兩個參數,第一個參數是對象的原型,第二個參數可選,用於描述對象屬性。使用很簡單,只需傳入所需的原型對象即可:
var o1 = Object.create({x: 1, y: 2 }); //原型為Object.prototype
如果想創建一個沒有原型的對象,可通過傳入null作為參數。這樣創建的對象不會繼承任何屬性,也沒有像toString這樣的方法:
var o2 = Object.create(null); //沒有原型
如果想創建一個普通的空對象,直接傳入Object.prototype:
var o3 = Object.create(Object.prototype);
如果是自定義的對象,和創建空對象一樣。直接傳入對象名.prototype:
function Person(){ } var o4 = Object.create(Person.prototype);
2.屬性管理
2.1 屬性查詢和設置
對象的屬性可通過點(.)或方括號([])運算符獲取。如果使用點獲取屬性,屬性名必須是簡單的表示符。不能是保留字,比如,o.for或者o.class。
ar author = book.author; //正確 var name = author.surname; //正確 var title = book[“main title”]; //正確 var className = book.class; //錯誤
object[“property”]這種語法看起來更像數組,只是這個數組的元素是通過字符串索引而不是數字索引。這種數組就是我們所說的關聯數組,也稱為散列、映射或字典。Javascript對象都是關聯數組。
既然對象是關聯數組,那麼Javascript也為我們提供了屬性的遍歷方式for/in。下面的例子利用for/in計算portfolio的總計值:
function getvalue(portfolio){ var total = 0.0; for(stock in portolio){ var shares = portolio[stock]; var price = getquote(stock); total += shares * price; } return total; }
繼承:Javascript對象具有自有屬性(own property),也有一些屬性是從原型對象繼承而來。我們先看看一個實現繼承功能的函數inherit:
function inherit(p){ if (p == null) throw TypeError(); //p是一個對象,大不能是null if(Object.create){ return Object.create(p); //直接使用Object.create方法 } var t = typeof p; if(t !== "object" && t !== "function") throw TypeError(); function f() {}; f.prototype = p; //將其原型屬性設置為p return new f(); }
假設要查詢對象o的屬性x,如果o中不存在x,將會繼續在o的原型對象中查詢屬性x。如果原型對象中也沒有x,但這個原型對象也有原型,那麼繼續在這個原型對象的原型上執行查詢,直到找到x或者查詢到一個原型為null的對象為止。
var o = {}; //o從Object.prototype繼承對象屬性 o.x = 1; //給o定義x屬性 var p = inherit(o); //p繼承o和Object.prototype p.y = 2; //p定義屬性y var q = inherit(p); //q繼承p、o和Object.prototype q.z = 3; //給q定義屬性z var s = q.toString(); //toString繼承自Object.prototype q.x + q.y // => 3:x和y分別繼承自o和p
2.2 刪除屬性
delete運算符可以刪除對象的屬性:
delete book.author; delete book[“main title”];
delete只能刪除自有屬性,不能刪除繼承屬性。要刪除繼承屬性,必須從定義這個屬性的原型對象上刪除它,而且這會影響到所有的繼承自這個原型的對象。刪除成功會返回true。
ar o = {x: 1}; delete o.x; //刪除x,返回true delete o.x; //x已經不存在了,什麼都沒做,返回true。 delete o.toString; //什麼都沒做,返回true。 delete不能刪除可配置型為false的屬性。某些內置對象的屬性是不可配置的,比如通過變量聲明和函數聲明創建的全局對象的屬性: delete Object.prototype //不能刪除,屬性是不可配置的 var x = 1; delete this.x; //不能刪除這個屬性 function f() {} delete this.f; //不能刪除全局函數
2.3 檢測屬性
判斷某個屬性是否存在於某個對象中,可通過in運算符、hasOwnProperty()和propetyIsEnumerable()方法來檢測。
in運算符:運算符左側是屬性名,右側是對象。如果對象的自有屬性或者繼承屬性包含屬性則返回true:
var o = {x: 1}; "x" in o; //true:x是o的屬性 "y" in o; //false:y不是o的屬性 "toString" in o; //true:o繼承toString屬性
hasOwnProperty()方法:檢測給定的名字是否是對象的自有屬性。對於繼承屬性它將返回false:
var o = {x: 1}; o.hasOwnProperty("x"); //true:o有一個自由屬性x o.hasOwnProperty("y"); //false:o中不存在屬性y o.hasOenProperty("toString"); //false:toString是繼承屬性
propertyIsEnumerable()方法:是hasOwnProperty的增強版,只有檢測到自有屬性並且這個屬性是可枚舉行為true時才返回true:
var o = inherit({y: 2}); o.x = 1; o.propertyIsEnumerable("x"); //true: o有一個可枚舉屬的自有屬性x o.propertyIsEnumerable("y"); //false:y是繼承來的 Object.prototype.propertyIsEnumerable("toString"); //false:不可枚舉
2.4 枚舉屬性
通常使用for/in循環遍歷對象屬性,遍歷的屬性包括自有屬性和繼承屬性。對象繼承的內置方法是不可枚舉的,但在代碼中給對象添加的屬性都是可枚舉的。例如:
var o = {x: 1, y: 2, z: 3}; //三個可枚舉的自有屬性 o.propertyIsEnumeable("toString"); //false,不可枚舉 for (p in o) //遍歷屬性 console.log(p); //輸出x、y和z,不會輸出toString
有時候我們只想遍歷自有屬性,並且屬性不為函數:
for(p in o){ if(!o.hasOwnProperty(p)) continue; if(typeof o[p] === "function") continue; }
我們可通過枚舉遍歷功能實現可枚舉屬性的復制:
/* * 把p中的可枚舉屬性復制到o中,並返回o * 如果o和p含同名屬性,則覆蓋o中的屬性 * 這個函數並不處理getter和setter以及復制屬性 */ function extend(o, p){ for(prop in p){ //遍歷p中的所有屬性 o[prop] = p[prop]; //將屬性添加到o中 } return o; }
ES5定義了兩個用以枚舉屬性名稱的函數。第一個是Object.keys(),返回由對象中可枚舉屬自有屬性名稱組成的數組。第二個枚舉函數是Object.getOwnPropertyNames(),和Object.keys()類似,它返回對象的所有自有屬性,而不僅僅是可枚舉屬性。
3.屬性封裝
3.1 屬性getter和setter
對象屬性由名字、值和一組特性(attribute)構成的。在ES5中,屬性值可以用一個或兩個方法替代,這兩個方法就是getter和setter。由getter和setter定義的屬性稱做“存取器屬性”,它不同於“數據屬性”,數據屬性只有一個簡單的值。
和數據屬性不同,存取器屬性不具有可寫性(writeable atribute)。如果屬性同時具有getter和setter方法,那麼它是一個讀/寫屬性。如果它只有getter方法,那麼它是一個只讀屬性,如果它只有setter方法,那麼它是一個只寫屬性。讀取只寫屬性總是返回undefined。
存取器屬性定義語法也比較簡單,函數定義沒有使用function關鍵字,而是使用get或set:
var o = { //普通的數據屬性 data_prop: 1, //存取器屬性都是成對定義的函數 get accessor_prop(){/* 這裡是函數體 */}, set accessor_prop(value){} };
思考下面這個表示2D笛卡爾點坐標的對象。它有兩個普通屬性x和y分別表示x坐標和y坐標,它還有兩個等價的存取器屬性用來表示點的極坐標:
var p = { //x和y是普通的可讀寫數據屬性 x: 1.0, y: 1.0, //r是可讀寫的存取器屬性,它有getter和setter get r(){return Math.sqrt(this.x * this.x + this.y * this.y); }, set r(newValue){ var oldValue = Math.sqrt(this.x * this.x + this.y * this); var ratio = newValue / oldValue; this.x *= ratio; this.y *= ratio; }, //theta是只讀存取器屬性,只有getter方法 get theta() { return Math.atan2(this.y, this.x); } };
和數據屬性一樣,存取器屬性是可以繼承的,因此可以將上述代碼中的p對象當做另一個“點”的原型。可以給性對象定義它的x和y屬性,但r和theta屬性繼承而來:
var q = inherit(p); q.x = 1, q.y = 1; console.log(q.r); cosole.log(q.theta);
3.2 屬性特性
我們可以將存取器屬性的getter和setter方法看成屬性的特性。按照這個邏輯,我們也可把屬性的值同樣看著屬性的特性。因此,可以認為一個屬性包含一個名字和4個特性。
數字屬性的4個特性分別是它的值(value)、可寫性(writeable)、可枚舉性(enumerable)和可配置型(configurable)。
存取器屬性不具有值(value)特性和可寫性,因此包含:讀取(get)、寫入(set)、可枚舉性、可配置性。
ES5定義了一個名為“屬性描述符”的對象,這個對象代表那4個特性。數據屬性的描述符對象的屬性有value、writable、enumerable和configurable。存取器屬性的描述符對象則用get屬性和set屬性代替value和writable。其中writable、enumerable、configurable都是布爾值,get屬性和set屬性是函數值。
通過調用Object.getOwnPropertyDescriptor()可以獲取某個對象特定屬性的屬性描述符:
//返回{value: 1, writable: true, enumerable: true, configurable: true} Object.getOwnProeprtyDescriptor({x: 1},"x"); //查詢上文中定義的random對象的octet屬性 //返回{get: /*func */, set: undefined, enumerable: true, configurable: true} Object.getOwnPropertyDesciptor(random, "octet"); //對於繼承屬性和不存在屬性,返回undefined Object.getOwnPropertyDesciptor({}, "x"); Object.getOwnPropertyDesciptor({}, "toString");
從函數名就可以看出,Object.getOwnPropertyDesciptor()只能得到自有屬性的描述符。要想獲得繼承屬性的特性,需要遍歷原型鏈(Object.getPrototypeOf())。
想要設置屬性的特性,或者讓新建屬性具有某些特性,則需要調用Object.defineProperty(),包含三個參數:對象、屬性名、屬性描述符對象:
// 屬性是存在的,但不可枚舉 o.x; //=> 1 Object.keys(o) //=> [] //現在對屬性x做修改,讓它變成只讀 Object.defineProperty(o, "x", {writable: true }); //視圖更改這個屬性的值 o.x = 2; //操作失敗但不報錯,而在嚴格模式中拋出類型錯誤異常 //屬性依然是可配置的,因此可通過這種方式對它進行修改: Object.defineProperty(o, "x", {value: 2 }); o.x //=> 2 //現在將x從數據屬性修改為存取器屬性 Object.defineProperty(o, "x", { get: function() {return 0;} }); o.x // => 0
如果要同時修改或創建多個屬性,則需要使用Object.defineProperties()。第一個參數是要修改的對象,第二個參數㐊一個映射表。例如:
var p = Object.defineProperties({}, { x: { value: 1, writable: true, enumerable: true, configurable: true}, y: { value: 2, writable: true, enumerable: true, configurable: true}, r: { get: function(){ return Math.sqrt(this.x * this.x + this.y * this.y); }, enumerable: true, configurable: true } });
getter和setter的老式API: 在ES5采納之前,大多數Javascript的實現已經可以支持對象直接量語法中get和set寫法。這些實現提供了非標准的老式API用來查詢和設置getter和setter。這些API由四個方法組成,所有對象都擁有這些方法。
__lookupGetter__()和__lookupSetter__()用以返回一個命名屬性的getter和setter方法。
__defineGetter__()和__defineSetter__()用以定義getter和setter,第一個參數是屬性名字,第二個參數是getter和setter方法。
var o = {}; o.__defineGetter__("x", function(){return 0;}); o.__defineSetter__("y", function(value){console.log("set value:" + value);});
4.對象的三個屬性
每一個對象都有與之相關的原型(prototype)、類(class)、可擴展性(extensible attribute)。接下來講述這些屬性有什麼作用。
4.1 原型屬性
對象的原型屬性是用來繼承屬性的,我們經常把“o的原型屬性”直接叫做“o的原型”。在之前“創建對象”介紹了三種方式創建對象。通過對象直接量創建的對象使用Object.prototype作為它們的原型。通過new創建的對象使用構造函數的prototype屬性作為它們的原型。通過Object.create()創建的對象使用第一個參數作為它們的原型。
在ES5中,可通過Object.getPrototypeOf()查詢對象原型。在ES3中,沒有與之等價的函數,而是使用表達式o.constructor.prototype檢查對象的原型。
要想檢測一個對象是否是另一個對象的原型(或處於原型鏈中),使用isPrototypeOf()方法。例如,可以通過p.isPrototypeOf(o)來檢測p是否是o的原型:
var p = {x: 1}; //定義一個原型對象 var o = Object.create(p); //使用這個原型創建一個對象 p.isPrototypeOf(o); //=> true,o繼承自p Object.prototype.isPrototypeOf(o) //=> true, p繼承自Object.prototype
Mozilla實現的Javascript對外暴露了一個專門命名為__proto__屬性,用以直接查詢/設置對象原型。但IE和Opera不支持__proto__屬性,所以不建議直接使用__proto__屬性。
4.2 類屬性
對象的類屬性是一個字符串,用以表示對象的類型信息。ES3和ES5都為提供設置這個屬性的方法,只有一種間接方式查詢它。默認的toString()方法返回這種格式的字符串:[object class]。
可通過調用toString()方法,然後提取已返回字符串的第八個到倒數第二個位置之間的字符。但有個麻煩是,很多對象繼承的toString()方法重寫了,為了能夠調用正確的toString()版本,必須間接調用Function.call()方法。下面例子的classof函數可返回任意對象的類:
function classof(o){ if(o === null) return "Null"; if(o === undefined) return "Undefined"; return Object.prototype.toString.call(o).slice(8, -1); }
4.3 可擴展性
對象的可擴展性用以表示是否可以給對象添加新屬性。所有內置對象和自定義對象都是顯式可擴展的。在ES5中,可將對象轉換為不可擴展的。
Object.seal()方法除了能夠將對象設置為不可擴展的,還可以將對象的所有自有屬性都設置為不可配置的。也就是說,不能給對象添加新屬性,而且已有屬性也不能刪除和配置。
Object.isSealed()方法用來檢測對象是否封閉。
Object.freeze()方法將更嚴格的鎖定對象,除了擁有Object.seal()方法的功能外,還可以將自有的所有數據屬性設置為只讀(如果對象的存取器屬性有setter方法,存取器屬性不受影響, 仍可以通過給屬性賦值調用它們)。
Object.isFrozen()用來檢測對象是否凍結。
5.序列化對象
對象序列化是指將對象的狀態轉換為字符串,也可以將字符串還原為對象。ES5提供了內置函數JSON.stringify()和JSON.parse()用來序列化和還原Javascript對象。這些方法都使用JSON作為數據交換格式。例如:
o = {x: 1, y: {z: [false, null, ""]}}; //定義一個測試對象 s = JSON.stringify(o); //{"x":1,"y":{"z":[false,null,""]}} p = JSON.parse(s); //p是o的深拷貝
JSON的語法是Javscript語法的子集,它並不能表示Javascript裡的所有值。支持對象、數組、字符串、無窮大數字、true、false和null,並且它們可以序列化和還原。NaN、Inifinity和-Inifinity序列化結果都是null。函數、RegExp、Error對象和undefined值不能序列化和還原。
這裡在附加說一下對象的方法:
toString()方法:它將返回一個表示調用這個方法的對象值的字符串。很多對象都重寫了toString()方法,比如Array.toString()、Date.toString()以及Function.toStrring()。
toJSON()方法:Object.prototype實際上沒有定義toJSON()方法,但由於需要執行序列化的對象來說,JSON.stringify()方法會調用toJSON()方法。如果在帶序列化的對象中存在這個方法,則調用它。
valueOf()方法:valueOf()方法和toString()方法非常相似,但往往Javascript需要將對象轉換為某種原始值而非字符串的時候才調用它,尤其是轉換為數字的時候。有些內置類自定義了valueOf()方法,比如,Date.valueOf()。
以上這篇全面了解JavaScript對象進階就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持。