第六章、物件
物件是JavaScript的基本資料型別。
物件是一種複合值:它將很多值(原始值或者其他物件)聚合在一起,可以通過名字訪問這些值。
物件也可看做是屬性的無序集合,每個屬性都是一個名/值對。
屬性名是字串,因此我們可以把物件看成從字串到值的對映。
"雜湊"(hash),"雜湊表"(hashtable),"字典"(dictionary),"關聯陣列"(accociative array).
JavaScript物件還可以從一個稱為原型的物件繼承屬性。
物件的方法通常是繼承的屬性。這種"原型式繼承"(prototypal inheritance)是JavaScript的核心特徵。
JavaScript物件是動態的——可以新增屬性也可以刪除屬性——但它們常用來模擬靜態物件以及靜態型別語言中的"結構體"(struct).
除了字串,數字,true,false,null和undefined之外,JavaScript中的值都是物件。
物件是可變的,我們通過引用而非值來操作物件。
物件最常見的用法是建立(create),設定(set),查詢(query),刪除(delete),檢測(test)和列舉(enumerate)
屬性包括名字和值。
屬性名可以是包含空字串在內的任意字串,但物件中不能存在兩個同名的屬性。
值可以是任意JavaScript值,或者可以是一個getter或setter函式
建立物件
可以通過物件直接量,關鍵字new和Object.create()函式來建立物件。
1.物件直接量
物件直接量是一個表示式,這個表示式的每次運算都建立並初始化一個新的物件。
var empty = {}; //沒有任何屬性的物件
var point = {x:0,y:0}; //兩個屬性
var point2 = {x:point.x,y:ponit.y+1}; //更復雜的值
var book = {
"main title":"JavaScript", //屬性名字裡有空格,必須使用字串表示
"sub-title":"The Definitive Guide", //屬性名字裡有連字元,必須使用字串表示
"for":"all audiences", //"for"是保留字,因此必須使用引號
author:{
firstname:"David", //這個屬性的值是一個物件
surname:"Flanagan" //注意,這裡的屬性名都沒有引號
}
}複製程式碼
2.通過new建立物件
new運算子建立並初始化一個新物件。
關鍵字new後跟隨一個函式呼叫。
這裡的函式稱做建構函式(constructor),建構函式用以初始化一個新建立的物件。
內建建構函式
var o = new Object(); //建立一個空物件,和{}一樣
var d = new Date(); //建立一個表示當前時間的Date函式
var r = new RegExp("js"); //建立一個可以進行模式匹配的RegExp物件複製程式碼
3.原型
每一個JavaScript物件(null除外)都和另一個物件相關聯。
"另一個"物件就是我們熟知的原型,每一個物件都從原型繼承屬性。
所有通過物件直接量建立的物件都具有同一個原型物件,並可以通過JavaScript程式碼Object.prototype獲得對原型物件的引用。
沒有原型的物件為數不多,Object.prototype就是其中之一。它不繼承任何屬性。其他原型物件都是普通物件,普通物件都具有原型。
所有的內建建構函式(以及大部分自定義的建構函式)都具有一個繼承自Object.prototype的原型。例如,Date.prototype的屬性繼承自Object.prototype,因此由new Date()建立的Date物件的屬性同時繼承自Date.prototype和Object.prototype.這一系列連結的原型物件就是所謂的"原型鏈"(prototype chain),
4.Object.create()
它建立一個新物件,其中第一個引數是這個物件的原型。
Object.create()是一個靜態函式,而不是提供給某個物件呼叫的方法。
使用它的方法很簡單,只須傳人所需的原型物件
eg:
var o1 = Object.create({x:1,y:2}); //o1繼承了屬性x和y複製程式碼
- 可以通過傳人蔘數null來建立一個沒有原型的新物件
eg:
var o2 = Object.create(null); //o2不繼承任何屬性和方法複製程式碼
- 如果想建立一個普通的空物件
eg:
var o3 = Object.create(Object.prototype); //o3和{}和new Object{}一樣複製程式碼
- 可以通過任意原型建立新物件
eg:通過原型繼承建立一個新物件
//inherit()返回一個繼承自原型物件p的屬性的新物件
//這裡使用ECMAScript5中的Object.create()函式(如果存在的話)
//如果不存在Object.create(),則退化使用其他方法
function inherit(p){
if(p == null){
throw TypeError(); //p是一個物件,但不能是null
}
if(Object.create){ //如果Object.create()存在
return Object.create(p); //直接使用它
}
var t = typeof p; //否則進行進一步檢測
if(t !== "object" && t !== "function"){
throw TypeError();
}
function f(){}; //定義一個空建構函式
f.prototype = p; //將其原型屬性設定為p
return new f(); //使用f()建立p的繼承物件
} 複製程式碼
當函式讀取繼承物件的屬性時,實際上讀取的是繼承來的值。如果給繼承物件的屬性賦值,則這些屬性只會影響這個繼承物件自身,而不是原始物件
var o = {x:'don't change this value'};
library_function(inherit(o)); //防止對o的意外修改複製程式碼
屬性的查詢和設定
- 可以通過點(.)或方括號([])運算子來獲取屬性的值。
eg:
var author = book.author; //得到book的"author"屬性
var name = author.surname; //得到author的"surname"屬性
var title = book["main title"]; //得到book的"main title"屬性複製程式碼
- 通過點和方括號也可以建立屬性或給屬性賦值,但需要將它們放在賦值表示式的左側
eg:
book.edition = 6; //給book建立一個名為"edition"的屬性
book["main title"] = "ECMAScript"; //給"main title"屬性賦值複製程式碼
1.作為關聯陣列的物件
object.property //使用點運算子和一個識別符號
object["property"]
//第二種語法使用方括號和一個字串,看起來更像陣列,只是這個陣列元素是通過字串索引而不是數字索引。這種陣列就是我們所說的關聯陣列(associative array),也稱做雜湊、對映或字典(dictionary)複製程式碼
- 當通過[]來訪問物件的屬性時,屬性名通過字串來表示。字串是JavaScript的資料型別,在程式執行時可以修改和建立它們。
eg:
var addr = "";
for(var i = 0;i < 4;i++){
addr += customer["address" + i] + '\n';
}複製程式碼
使用陣列寫法和用字串表示式來訪問物件屬性的靈活性。
function addstock(portfolio,stockname,shares){
portfolio[stockname] = shares;
}複製程式碼
- 當使用for/in迴圈遍歷關聯陣列時,就可以清晰地體會到for/in的強大之處。
eg:
function getvalue(portfolio){
var total = 0.0;
for(stock in portfolio){ //遍歷portfolio中的每隻股票
var shares = portfolio[stock]; //得到每隻股票的份額;
var price = getquote(stock); //查詢股票價格
total += shares * price; //將結果累加至total中
}
return total; //返回total的值
}複製程式碼
2.繼承
- JavaScript物件具有"自有屬性"(own property),也有一些屬性是從原型物件繼承而來的。
eg:
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
var s = q.toString(); //toString繼承自Object.prototype
q.x + q.y //=>3:x和y分部繼承自o和p複製程式碼
現在假設給物件o的屬性x賦值,如果o中已經有屬性x(這個屬性不是繼承來的),那麼這個賦值操作只改變這個已有屬性x的值。如果o中不存在屬性x,那麼賦值操作給o新增一個新屬性x。如果之前o繼承自屬性x,那麼這個繼承的屬性就被新建立的同名屬性覆蓋了。
屬性賦值操作首先檢查原型鏈,以此判斷是否允許賦值操作。
在JavaScript中,只有在查詢屬性時才會體會到繼承的存在,而設定屬性則和繼承無關
eg:
var unitcircle = {r:1}; //一個用來繼承的物件
var c = inherit(unitcircle); //c繼承屬性r
c.x = 1; c.y =1; //c定義兩個屬性
c.r = 2; //c覆蓋繼承來的屬性
unitcircle.r; //=>1,原型物件沒有修改複製程式碼
屬性賦值要麼失敗,要麼建立一個屬性,要麼在原始物件中設定屬性
3.屬性訪問錯誤
屬性訪問並不總是返回或設定一個值。
查詢一個不存在的屬性並不會報錯,返回undefined
eg:
book.subtitle; //=>undefined:屬性不存在複製程式碼
- 如果物件不存在,那麼試圖查詢這個不存在的物件的屬性就會報錯。
eg:
//丟擲一個型別錯誤異常,undefined沒有length屬性
var len = book.subtitle.length;複製程式碼
eg:避免出錯的方法
//一種冗餘但很易懂的方法
var len = undefined;
if(book){
if(boo.subtitle){
len = book.subtitle.length;
}
}
//一種更簡練的常用方法,獲取subtitle的length屬性或undefined
var len = book && book.subtitle && book.subtitle.length複製程式碼
刪除屬性
- delete運算子可以刪除物件的屬性。
eg:
delete book.author; //book不再有屬性author
delete book["main title"]; //book也不再有屬性"main title"複製程式碼
- delete運算子只能刪除自有屬性,不能刪除繼承屬性(要刪除繼承屬性必須從定義這個屬性的原型物件上刪除它,而且這會影響到所有繼承自這個原型的物件)。
eg:
var a = {p:{x:1}};
b = a.p;
delete a.p; //=>b.x=1複製程式碼
- 當delete表示式刪除成功或沒有任何副作用(比如刪除不存在的屬性)時,他返回true。如果delete後不是一個屬性訪問表示式,delete同樣返回true
eg:
var o = {x:1}; //o有一個屬性x,並繼承屬性toString
delete o.x; //刪除x,返回true
delete o.x; //什麼都沒做(x已經不存在了),返回true
delete o.toString; //什麼也沒做(toString是繼承來的),返回true
delete 1; //無意義,返回true複製程式碼
- delete不能刪除那些可配置性為false的屬性
eg:
delete Object.prototype; //不能刪除,屬性是不可配置的
var x = 1; //宣告一個全域性變數
delete this.x; //不能刪除這個屬性
function f(){}; //宣告一個全域性函式
delete this.f; //也不能刪除全域性函式複製程式碼
- 當在非嚴格模式中刪除全域性物件的可配置屬性時
eg:
this.x = 1; //建立一個可配置的全域性屬性(沒有用var)
delete x; //將它刪除複製程式碼
檢測屬性
檢測集合中成員的所屬關係——判斷某個屬性是否存在於某個物件中。可以通過in運算子,hasOwnPreperty()和propertyIsEnumerable()方法來完成這個工作,甚至僅通過屬性查詢也可以做到這一點。
- in運算子的左側是屬性名(字串),右側是物件。如果物件的自有屬性或繼承屬性中包含這個屬性則返回true
eg:
var o ={x:1};
"x" in o; //true:"x"是o的屬性
"toString" in o; //true:o繼承toString屬性
"y" in o; //false:'y'不是o的屬性複製程式碼
- 物件的hasOwnProperty()方法用來檢測給定的名字是否是物件的自有屬性。對於繼承屬性它將返回false
eg:
var o = {x:1}
o.hasOwnProperty("x"); //true:o有一個自有屬性x
o.hasOwnProperty("y"); //false:o中不存在屬性y
o.hasOwnProperty("toString"); //false:toString是繼承屬性複製程式碼
- propertyIsEnumerable()是hasOwnproperty()的增強版,只有檢測到是自有屬性且這個屬性的可列舉性(enumerable attribute)為true時它才返回true.
eg:
var o = inherit({y:2});
o.x = 1;
o.propertyIsEnumerable("x"); //true:o有一個可列舉的自有屬性x
o.propertyIsEnumerable("y"); //false:y是繼承來的
Object.prototype.propertyIsEnumerable("toString"); //false:不可列舉複製程式碼
- 除了使用in運算子之外,另一種更簡便的方法是使用"!=="判斷一個屬性是否是undefined
eg:
var o = {x:1}
o.x !== undefined; //true:o中有屬性x
o.y !== undefined; //false:o中沒有屬性y
o.toString !== undefined; //true:o繼承了toString屬性複製程式碼
- in可以區分不存在的屬性和存在但值為undefined的屬性
eg:
var o ={x:undefined} //屬性被顯示賦值為undefined
o.x !== undefined; //false:屬性存在,但值為undefined
o.y !== undefined; //false:屬性不存在
"x" in o; //true:屬性存在
"y" in o; //false:屬性不存在
delete o.x; //刪除了屬性x
"x" in o; //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
} 複製程式碼
過濾for/in迴圈返回的屬性。
for(p in o){
if(!o.hasOwnProperty(p)){
continue; //跳過繼承屬性
}
for(p in o){
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;
}
<!-- 把p中的可列舉屬性複製到o中,並返回o
如果o和p中含有同名屬性,o中的屬性將不受影響
這個函式並不處理getter和setter以及複製屬性
-->
function merge(o,p){
for(prop in p){ //遍歷p中是所有屬性
if(o.hasOwnProperty[prop]){ //過濾掉已經在o中存在的屬性
continue;
}
o[prop] = p[prop]; //將屬性新增至o中
}
return o;
}
<!-- 如果o中的屬性在p中沒有同名屬性,則從o中刪除這個屬性
返回o -->
function restrict(o,p){
for(prop in o){ //遍歷o中的所有屬性
if(!(prop in p)){ //如果在p中不存在,則刪除之
delete o[prop];
}
}
return o;
}
<!-- 如果o中的屬性在p中存在同名屬性,則從o中刪除這個屬性
返回o -->
function subtract(o,p){
for(prop in p){ //迴圈遍歷p中的所有屬性
delete o[prop]; //從o中刪除(刪除一個不存在的屬性不會報錯)
}
return o;
}
<!-- 返回一個新物件,這個物件同時擁有o的屬性和p的屬性
如果o和p中有重名屬性,使用p中的屬性值 -->
function union(o,p){
return extend(extend(extend({},o),p))
}複製程式碼
-------------此處很難理解----------------------
屬性getter和setter
物件屬性是由名字、值和一組特性(attribute)構成的。
在ECMAScript5中,屬性值可以用一個或兩個方法替代,這兩個方法就是getter和setter.
由getter和setter定義的屬性稱做"存取器屬性"(accessor property),它不同於"資料屬性"(data property),資料屬性只有一個簡單的值。
和資料屬性不同,存取器屬性不具有可寫性(writable attribute).如果屬性同時具有getter和setter方法,那麼它是一個讀/寫屬性。
eg:
var o = {
//普通的資料屬性
data_prop:value,
//存取器屬性都是成對定義的函式
get accessor_prop(){
//這裡是函式體
}
set accessor_prop(value){
//這裡是函式體
}
}複製程式碼
- 在函式體內的this指向表示這個點的物件
eg:
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.y);
var ratio = newvalue/oldvalue;
this.x *= ratio;
this.y *= ratio;
},
//theta是隻讀存取器屬性,它只有getter方法
get theta(){
return Math.atan2(this.y,this.x);
}
}複製程式碼
- 和資料屬性一樣,存取器屬性是可以繼承的
eg:
var q inherit(p); //建立一個繼承getter和setter的新物件
q.x = 1,q.y = 1; //給q新增屬性
console.log(q.r); //可以使用繼承的存取器屬性
console.log(q.theta);
//這個物件產生嚴格自增的序列號
var serialnum = {
//這個資料屬性包含下一個序列號
//$符號暗示這個屬性是一個私有屬性
$n:0,
//返回當前值,然後自增
get next(){
return this.$n++;
},
//給n設定新的值,但只有當它比當前值大時才設定成功
set next(n){
if(n >= this.$n){
this.$n = n;
}else{
throw "序列號的值不能比當前值小";
}
}
}
var random = {
get octet(){
return Math.floor(Math.random()*256);
},
get uint16(){
return Math.floor(Math.random()*65536);
},
get int16(){
return Math.floor(Math.random()*65536)-32768
}
}複製程式碼
屬性的特性
資料屬性的4個特性分別是它的值(value),可寫性(writable),可列舉性(enumerable)和可配置性(configurable)
存取器屬性的4個特性是讀取(get),寫入(set),可列舉性和可配置性。
通過呼叫Object.getOwnPropertyDescriptor()可以獲得某個物件特定屬性的屬性描述符,只能得到自有屬性的描述符:
eg:
返回Object {value: 1, writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor({x:1},"x");複製程式碼
要想獲得繼承屬性的特性,需要遍歷原型鏈Object.getProtorypeOf()
要想設定屬性的特性,或者想讓新建屬性具有某種特性,則需要呼叫Object.definePeoperty(),傳人要修改的物件,要建立或修改的屬性的名稱以及屬性描述符物件
eg:
var o = {}; //建立一個空物件
//新增一個不可列舉的資料屬性x,並賦值為1
Object.defineProperty(o,"x",{
value:1,
writable:true,
enumerable:false,
configurable:true
});
//屬性是存在的,但不可列舉
o.x; //=>1
Object.keys(o) //=>[]
//現在對屬性x做修改,讓它變為只讀
Object.defineProperty(o,"x",{writable:false});
//試圖更改這個屬性的值
o.x = 2; //操作失敗但不報錯,而在嚴格模式中丟擲型別錯誤異常
o.x //=>1
//屬性依然是可配置的,因此可以通過這種方式對它進行修改
Object.defineProperty(o,"x",{value:2});
o.x //=>2
//現在將x從資料屬性修改為存取器屬性
Object.defineProperty(o,"x",{get:function(){
return o;
}});
o.x //=>0複製程式碼
- 如果要同時修改或建立多個屬性,則需要使用Object.defineProperties().第一個引數是要修改的物件,第二個引數是一個對映表,它包含要新建或修改的屬性的名稱,以及它們的屬性描述符
eg:
var p = Object.defineProperties({},{
x:{value:1,writable:true,enumerable:true,configurable:true},
y:{value:1,writable:true,enumerable:true,configurable:true},
r:{
get:function(){
return Math.sqrt(this.x*this.x+this.y*this.y)
},
enumerable:true,
configurable:true
}
});複製程式碼
- 複製屬性的特性
eg:
/**
* 給Object.prototype新增一個不可列舉的extend()方法
* 這個方法繼承自呼叫它的物件,將作為引數傳人的物件的屬性一一複製
* 除了值以外,也複製屬性的所有特性,除非在目標物件中存在同名的屬性
* 引數物件的所有自有物件(包括不可列舉的屬性)也會一一複製
*/
Object.defineProperty(Object.prototype,"extend",{ //定義Object.prototype.extend
wirtable:true,
enumerable:false, //將其定義為不可列舉
configurable:true,
value:function(o){ //值就是這個函式
//得到所有的自有屬性,包括不可列舉屬性
var names = Object.getOwnPropertyNames(o);
//遍歷它們
for(var i = 0;i < names.length;i++){
//如果屬性已經存在,則跳過
if(names[i] in this){
continue;
}
//獲得O中的屬性的描述符
var desc = Object.getOwnPropertyDescriptor(o,names[i]);
//用它給this建立一個屬性
Object.defineProperty(this,names[i],desc);
}
}
})複製程式碼
物件的三個屬性
每個物件都有與之相關的原型(prototype),類(class)和可擴充套件性(wxtensible attribute)
1.原型屬性
物件的原型屬性是用來繼承屬性的
原型屬性是在例項物件建立之初就設定好的,
A.通過物件直接量建立的物件使用Object.prototype作為它們的原型。
B.通過new建立的物件使用建構函式的prototype屬性作為它們的原型。
C.通過Object.create()建立的物件使用第一個引數(也可以是null)作為它們的原型。
將物件作為引數傳人Object.getPrototypeOf()可以查詢它的原型
表示式o.constructor.prototype來檢測一個物件的原型
通過new表示式建立的物件,通常繼承一個constructor屬性,這個屬性指代建立這個物件的建構函式
通過物件直接量Object.create()建立的物件包含一個名為constructor的屬性,這個屬性指代Object()建構函式
constructor.prototype是物件直接量的真正的原型
檢測一個物件是否是另一個物件的原型(或處於原型鏈中),isPrototypeOf()
eg:
var p = {x:1}; //定義一個原型物件
var o = Object.create(p); //使用這個原型建立一個物件
p.isPrototypeOf(o); //=>true:o繼承自p
Object.prototype.isPrototypeOf(o); //=>true:p繼承自Object.prototype複製程式碼
2.類屬性
物件的類屬性是一個字串,用以表示物件的型別資訊。
- 預設toString()方法
返回:
[object class]複製程式碼
- 獲得物件的類
classof()函式
function classof(o){
if(o == null){
return "Null";
}
if(o == undefined){
return "Undefined";
}
return Object.prototype.toString.call(o).slice(8,-1);
}複製程式碼
3.可擴充套件性
物件的可擴充套件性用以表示是否可以給物件新增新屬性。
通過將物件傳人Object.esExtensible(),來判斷該物件是否是可擴充套件的。
通過呼叫Object.preventExtensions()將物件轉換為不可擴充套件的。一旦將物件轉換為不可擴充套件的,就無法再將其轉換為可擴充套件的了。
可擴充套件性的目的是將物件"鎖定",以避免外界的干擾
Object.seal() 封閉
Object.freeze() 凍結
eg:
var o = Object.seal(Object.create(Object.freeze({x:1}),
{y:{value:2,writable:true}}))複製程式碼
序列化物件
物件序列化(serialization)是指將物件的狀態轉換為字串,也可將字串還原為物件
JSON.stringify() 序列化物件
JSON.parse() 還原物件
JSON作為資料交換格式,JSON的全稱是"JavaScript Object Notation"——JavaScript物件表示法
eg:
o = {
x:1,
y:{
z:[false,null,""]
}
}; //定義一個測試物件
s = JSON.stringify(o); //=>s:'{"x":1,"y":{"z":[false,null,""]}}'
p = JSON.parse(s); //p是o的深拷貝複製程式碼
- JSON的語法是JvaScript語法的子集,它並不能表示JavaScript裡的所有值。
物件方法
所有的JavaScript物件都從Object.prototype繼承屬性(除了那些不通過原型顯式建立的物件)。
1.toString()方法
var s = {x:1,y:1}.toString(); //=>"[object Object]"複製程式碼
2.toLocaleString()方法
這個方法返回一個表示這個物件的本地化字串
3.toJSON方法
JSON.stringify()方法會呼叫toJSON()方法
4.valueOf()方法
當JavaScript需要將物件轉換為某種原始值而非字串的時候才會呼叫它,尤其是轉換為數字的時候。
|版權宣告:本文為summer博主原創文章,未經博主允許不得轉載。