JavaScript權威指南(第6版)學習筆記三
第六章對象
對象可以看成其屬性的無序集合,每個屬性都是一個名/值對。JavaScript對象是動態的,可以新增也可以刪除屬性,可以通過引用而非值來操作對象。如果變量x是指向一個對象的引用,那麽執行代碼var y=x;變量y也會指向同一個對象的引用,而非這個對象的副本,所以通過變量y修改這個對象也會對x產生影響。
對象最常見的用法是創建(create)、設置(set)、查找(query)、刪除(delete)、檢測(test)和枚舉(enumerate)它的屬性。
屬性包括名字和值。屬性名可以是包含空字符串在內的任意字符串,值可以是任意值。除了名字和值之外,屬性還有一些與之相關的值,稱為屬性特性(property attribute):
(1)可寫:表示是否可以設置該屬性的值。
(2)可枚舉:表示是否可以通過for/in循環返回該屬性。
(3)可配置:表示是否可以刪除或修改該屬性。
一、創建對象
1、對象直接量
var empty={}; //沒有任何屬性的對象 var point={x:0,y:0}; var point2={x:point.x,y=point.y}; var book={ "main title":"JavaScript", //屬性名有空格,必須用字符串表示 "sub-tit":"The definitive guide", //屬性名有連字符,必須用字符串表示 "for":"all audiences", //屬性名為保留字,必須用引號 author:{ firstname:"David", surname:"Flanagan" } };
2、通過new創建對象
通過關鍵字new後跟隨一個構造函數的調用實現。
var o=new Object(); //創建一個空對象,和直接量{}直接給定一樣 var a=new Array(); //創建一個空數組,和[]一樣 var d=new Date(); //創建一個表示此刻時間的Date對象 var r= new RegExp("js"); //創建一個可以進行模式匹配的RegExp對象
3、原型
每一個JavaScript對象都和另一個對象相關聯,這另一個對象就是原型,每一個對象都從原型繼承屬性。
二、屬性的查詢和設置
var author=book.author; //獲得book的"author"屬性 var name=author.surname; var title=book["main title"];//獲得book的"main title"屬性
1、作為關聯數組的對象。
下面兩個JavaScript表達式的值相同:
object.property
object["property"]
其中第二種語法更像數組,只不過索引是字符串,這裏就是所說的關聯數組(associalive array)的意思,也稱為散列、映射或字典。JavaScript對象都是關聯數組。
在C、Java等強類型語言中,對象只能擁有固定數目的屬性,並且這些屬性名稱必須提前定義。由於JavaScript是弱類型語言,因此不必遵循這些要求,任何對象中程序都可以創建任意數量的屬性。
在JavaScript中,當通過點運算符(.)訪問屬性時,屬性名用一個標識符來表示(非數據類型),所以這種情況下程序無法修改屬性名。而當通過[]訪問對象的屬性時,屬性名通過字符串來表示,字符串是數據類型,可以對屬性名進行修改或者創建,如下代碼:
var addr=""; //讀取costomer對象的屬性address0、address1 //address2、address3; //並將各屬性的值連接起來 for (i=0;i<4;i++){ addr+=customer["address"+i]+‘\n‘;}
這段代碼也可以通過點運算符來重寫,但是很多場景只能使用數組寫法來完成。假設有個程序利用網絡資源計算當前用戶股票市場投資額。程序允許用戶輸入每只股票的名稱和購股份額。該程序使用portfolio對象來存儲這些信息,屬性名就是股票名稱,屬性值就是購股數量。下面這個函數用來給portfolio對象添加新的股票:
function addstock(portfolio,stockname,shares){ //這裏顯然只能用數組寫法 //如果用“portfolio.stockname”的寫法 //則是給對象指定了一個名為“stockname”的屬性 //實際需求並非如此 portfolio[stockname]=shares; }
2、繼承
JavaScript對象具有“自有屬性”(own property),也有一些屬性是從原型對象繼承而來的。
如果查詢對象o的屬性x,如果o中不存在x,則會在o的原型對象中繼續查詢屬性x,如果o的原型屬性中也沒有x,則會繼續在這個原型對象的原型對象中查找,直到找到x或者找到一個原型是null的對象為止。
var o={}; //o從object.prototype繼承對象的方法 o.x=1; var p=inherit(o); //p繼承o和object.prototype p.y=2; var q=inherit(p); //q繼承o/p/object.prototype q.z=3; var s=q.toString(); //toString()方法繼承自object.prototype q.x+q.y; //輸出3
針對上述代碼,如果給對象o的屬性x賦值,如果屬性x已經存在,則是修改它的值;如果屬性不存在,則賦值操作會給對象o添加一個新屬性x。
屬性賦值操作首先檢查原型鏈,以此判定是否允許賦值操作。比如如果o繼承自一個只讀屬性x,那麽賦值操作是不允許的;賦值操作也不會影響原型對象的屬性。在JavaScript中,只有在查詢屬性時候才體現出繼承的作用,而設置屬性則和繼承無關。
var unitcircle={r:1}; //一個用來繼承的對象 var c=inherit(unitcircle); //c繼承unitcircle c.x=1;c.y=1; //設置c的屬性 c.r=2; //覆蓋原來繼承的屬性 unitcircle.r; //輸出1,原型對象的屬性沒有改變
屬性賦值要麽失敗,要麽創建一個屬性,要麽在原始對象中設置屬性,但有一個例外:如果o繼承自屬性x,而這個屬性是一個具有setter方法的accessor屬性,那麽這是將調用setter方法而不是給o創建一個屬性x。需要註意的是,setter方法是由對象o調用的,而不是定義這個屬性的原型對象調用的,這個操作只針對o本身,並不會修改原型鏈。
3、屬性訪問錯誤
當查詢一個不存在的屬性,即在對象自身屬性或繼承屬性中均未找到,則返回undefined,而不會報錯。但是如果對象本身就不存在,那麽則會報錯。另外一種情況,null和undefined值均沒有屬性,因此查詢這些值的屬性會報錯。
三、刪除屬性
delete book.author; //刪除book的author屬性 delete book["main title"]; //刪除book的“main title”屬性
delete運算符只能刪除自有屬性,不能刪除繼承屬性,要刪除這個繼承屬性則必須從定義這個屬性的原型對象上去刪除,而且這會影響到所有繼承這個原型的對象。
當delete刪除成功或沒有任何副作用,返回true;如果delete後不是一個屬性訪問表達式,同樣返回true(因為沒有意義)。
o={x:1}; delete o.x; //返回true delete o.x; //x已經沒有了,無意義操作,返回true delete o.toString; //無意義操作,toString是繼承來的,返回true delete 1; //無意義操作,返回true
delete運算符不能刪除可配置性為false的屬性,如通過變量聲明和函數聲明創建的全局對象的屬性。
delete Object.prototype; //不能刪除,該屬性不可配置 var x=1; delete this.x; //不能刪除這個全局對象的屬性(全局變量) function f() {} delete this.f; //不能刪除全局函數
//非嚴格模式中刪除全局對象的可配置屬性時 //可省略對全局對象的引用,直接用屬性名即可 //用var聲明的則是不可配置 this.x=1; //創建一個可配置的全局屬性 delete x; //刪除x //嚴格模式中,必須顯式指定對象及其屬性 delete x; //嚴格模式下會報語法錯誤 delete this.x; //正常工作
四、檢測屬性
可以通過in運算符、hasOwnProperty()和propertyIsEnumerable()方法來判斷一個屬性是否存在於某個對象中。
var o={x:1} "x" in o; //true "y" in o; //false "toString" in o; //true,繼承屬性 //hasOwnProperty()檢測屬性是否為自有屬性 var o={x:1} o.hasOwnProperty("x"); //true o.hasOwnProperty("y"); //false o.hasOwnProperty("toString"); //false
propertyIsEnumerable()方法只有檢測到這個屬性是自有屬性並且數可枚舉的才返回true.
var o=inherit({y:2}) o.x=1; o.propertyIsEnumerable("x"); //true o.propertyIsEnumerable("y"); //false Object.prototype.propertyIsEnumerable("toString"); //false,不可枚舉
五、枚舉屬性
for/in循環可以在循環體內遍歷對象中所有可枚舉的屬性,把屬性名賦值給變量。對象繼承的內置方法是不可枚舉的,但在代碼中給對象添加的屬性都是可枚舉的。
var o={x:1,y:2,z:3} o.propertyisEnumerable("toString"); //false for(p in o) console.log(p); //輸出x,y,z.不會輸出toString
//用來枚舉屬性的對象工具函數 /* *把p中的可枚舉屬性復制到o中,並返回o *如果o和p有同名屬性,則覆蓋o中屬性 *這個函數並不處理getter和setter以及復制屬性 */ function extend(o,p){ for (pro in p) { o[pro]=p[pro]; //將屬性添加至o中 } return o; } /* *把p中的可枚舉屬性復制到o中,並返回o *如果o和p有同名屬性,o中的屬性不受影響 *這個函數並不處理getter和setter以及復制屬性 */ function merge(o,p) { for(prop in p) { if(o.hasOwnProperty[prop]) continue; //如果有同名屬性,跳過 o[prop]=p[prop]; } return o; } /* *如果o中屬性在p中沒有同名,則刪除o中的屬性 */ function restric(o,p) { for(prop in p) { if(!o.hasOwnProperty(prop)) //可簡單用"!(prop in o)替代 delete o[prop]; } return o; } /* *如果o中屬性在p中有同名,則刪除o中的屬性 */ function restric(o,p) { for(prop in p) { if(prop in o) delete o[prop]; } return o; } /*返回一個新對象,這個對象同時擁有o和p的屬性 *如果o和p中有重名屬性,使用p中屬性 */ function union(o,p) { x={}; for(prop in p) { if (prop in o) x[prop]=p[prop]; } return x; } //更簡潔的寫法 function union1(o,p) { return restric(extend({},o),p); }
六、屬性getter和setter
由getter和setter定義的屬性稱為“存取器屬性”(accessor property),它不同於“數據屬性”(data property),數據屬性只有一個簡單的值。當程序查詢存取器屬性的值時,會調用getter方法;當程序設置存取器屬性的值時,則調用setter方法。
定義存取器屬性最簡單的方法是使用對象直接量語法的一種擴展寫法:
var o= { //普通的數據屬性 data_prop:value, //存取器屬性都是成對定義的函數 get accessor_prop() { /*函數體內容*/ }, set accessor_prop(value) { /*函數體內容*/ } }
存取器屬性定義為一個或兩個和屬性同名的函數,需要註意的是這個函數定義沒有使用function關鍵字,而是使用get和set。
var p={ //x和y是普通的可讀寫的數據屬性 x:1.0, y:1.0, //r是可讀寫的存取器屬性 //函數體結束後需要加逗號 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.y); var ratio=newvalue/oldvalue; this.x *=ratio; this.y *=ratio; }, //theta是只讀存取器屬性,只有getter方法 get theta() { return Math.atan2(this.y,this.x); } };
和數據屬性一樣,存取器屬性是可以繼承的,針對上述代碼的示例如下:
var q=inherit(p); //創建一個繼承getter和setter的新對象 q.x=1,q.y=1; console.log(q.r); console.log(q.theta);
七、對象的三個屬性
每個對象都有與之相關的原型(prototype)、類(class)和可擴展性(extensible attribute)三個屬性。
1、原型屬性
原型屬性是在實例對象創建之初就設置好的。通過對象直接量創建的對象使用Object.prototype作為它們的原型,通過new創建的對象使用構造函數的prototype屬性作為原型。通過Object.create()創建的對象使用第一個參數作為原型。
要檢測一個對象是否是另一個對象的原型(或處於原型鏈中),要使用isPrototypeOf()方法。
var p={x:1}; //定義一個原型對象 var o=Object.create(p); //使用這個原型p創建一個對象o p.isPrototypeOf(o) //true Object.prototype.isPrototypeOf(o) //p繼承自object.prototype
2、類屬性
對象的類屬性(class attribute)是一個字符串,用以表示對象的類型信息。要想獲得對象的類,可以調用對象的toString()方法。
3、可擴展性
對象的可擴展性用以表示是否可以給對象添加新屬性。所有內置對象和自定義對象都是顯式可擴展的。
JavaScript權威指南(第6版)學習筆記三