初學JavaScript之——JavaScript對象學習

對象

對象是動態的——可以新增屬性也可以刪除屬性,但是對象經常用來模擬靜態對象以及靜態類型語言中的“結構體”。對象的屬性是可以增加和刪除的。
對象特性
* 對象原型(prototype):指向另外一個對象,本對象的屬性繼承自它的原型對象。
* 對象的類(class):是一個表示對象類型的字符串。
*對象的擴展標記(extensible flag):指明是否可以向該對象添加新屬性。

屬性特性
* 可寫 (是否可以設置屬性值)
* 可枚舉 (是否可以通過for in 循環返回該屬性)
* 可配置 (是否可以刪除或者修改屬性)

對象的創建

對象直接量
創建對象最簡單的方法是通過對象直接量。

var empty = {}; //沒有任何屬性的對象
 var point = {x:0, y:0}; // 兩個屬性
 var point2 = {x:point.x, y:point.y+1}; 
 var student = {
     name:"Allen",                //屬性名可以不用引號
     age:23,
     school:{                      //這個屬性值是一個對象,
       "primary school" :"試驗小學",//屬性名中有空格,必須字符串
       "middle-school" :"育才中學" ,//屬性名中有連接符,必須用字符串
}
}
/*最後注意一點,屬性名儘量避免使用空格、連接符、或者關鍵字,如果必須使用的話,儘量用引號引起來*/

通過new 創建對象

var mObj = new Object(); // 創建一個空對象,和{}一樣
var arr = new Array(); // 創建一個空數組,和[]一樣
var mDate = new  Date(); // 創建一個表示當前時間的Date對象

通過 Object.create()
在這裏,我們先弄清楚一個名詞——原型,每一個JS對象(除了null)都和另一個對象相關聯。“另一個”對象就是我們所說的原型,每一個對象都從原型繼承屬性。
所有通過對象直接量創建的對象都具有同一個原型對象,並可以通過JS代碼Object.prototype獲得對原型對象的引用。通過關鍵字new和構造函數調用創建的對象的原型就是構造函數的prototype屬性的值。因此,同使用{}創建對象一樣,通過new Object()創建對象也繼承自Object.prototype。

ECMAScript 5 定義了一個名爲Object.create()的方法,它創建一個新的對象,其中第一個參數就是對象原型,第二個參數是可選參數,用以對對象的屬性進行進一步描述。
Object.create()是靜態函數,而不是提供給某一個對象調用的方法。

var obj = Object.create({x:1,y:2}); // obj繼承了屬性x,y
var obj2 = Object.create(obj); //obj2的原型是obj,繼承了屬性x,y;
// 如果想創建一個普通的空對象(比如通過{}或者new Object()創建的對象 ),需要傳入Object.prototype
var obj3 = Object.create(Object.prototype);//obj3和{}和new Object()一樣

現在我們來看看如何自己寫一個函數來通過原型來創建對象,看例子

function createObject(obj){
        // 先對obj對象進行判斷,看是否爲null,如果是拋出異常
        if(obj == null) throw TypeError();
        // Object.create是否可用,可用則返回
        if(Object.create)  return Object.create(obj);
        // 不可用時,檢測對象的類型
        var objType = typeof obj;
        // 如果對象類型不是Object 也不是函數的話,拋出異常
        if(objType !=="object" && objType !=="function") 
        throw TypeError();
        // 定義一個空構造函數 並且將函數的原型設置成obj
        function mfunc(){};
        mfunc.prototype = obj;
        return new mfunc();
    }

屬性

對象訪問屬性的方法有兩個,通過(.)和([])運算符來獲取屬性值。這裏不做解釋。
下來我們詳細講解一下通過([])獲取屬性值,這種獲取屬性值的方法很像數組,只是數組的索引是數字,而它的索引是字符串。這種數組就是我們將要說明的關聯數組,也成散列、映射或字典。由於JS是弱類型語言,在任何對象中程序都可以創建任意數量的屬性。通過([])來訪問對象的屬性時,屬性名是字符串,這樣更利於我們在程序運行時進行修改和創建屬性。
我們舉個例子來說明它的靈活性吧,假設我們去超市購買東西,我們把需要買的東西放入購物車,這輛購物車的作用就是顯示我們放入的商品名稱,以及商品的單價和數量。你有沒有思路呢?學過類似C語言的同學,可以想想如何來實現。簡單說一下我的思路吧(用類似C語言的方式的思路,大神勿噴,可以留下你的思路),此處的購物車我們用一個類似字典的對象來表示,字典的Key就是我們的商品名稱,Value則是一個對象,對象中的屬性包含:商品價格、數量。現在想想用JavaScript如何,思路其實同我上面說的類似,直接看代碼吧。

var shoppingCart = {};//這裏是購物車
// 添加商品的方法
function addGoods(name,price,count){
    shoppingCart[name] = {m_price:price,m_count:count};
}
(一)繼承(屬性的查詢和設置)

JS對象有自有屬性和繼承屬性,我覺得通過舉例更容易說明這兩個屬性的關係。
查詢對象的屬性舉例:
假設要查詢對象obj的屬性x,如果obj中不存在x,那麼將會繼續在obj的的原型對象中查詢屬性x,如果原型對象也沒有x,但是這個原型對象也有原型(原型不爲null),那麼繼續在這個原型對象的原型上執行查詢,直到找到x或者查找到一個原型爲null的對象爲止。
給對象的屬性賦值舉例:
假設給對象obj的屬性x賦值,如果obj中已經有屬性x(這個屬性不是繼承來的),那麼這個賦值操作只改變這個已有屬性x的值。如果obj中不存在屬性x,那麼賦值操作給obj添加一個新屬性x。如果之前obj繼承自屬性x,那麼這個繼承的屬性就會被新創建的同名屬性覆蓋。
小總結:對象的屬性賦值,不會修改原型的屬性值的,只會創建屬性或者對自有屬性賦值。但是需要注意的是,屬性賦值操作首先是檢查原型鏈,以此判斷是否允許賦值,如果繼承的屬性是隻讀的,則賦值操作是不允許的
看一個例子:

var obj = {x:1,y:2};
var obj2 = Object.create(obj);
console.log(obj);   // ==> {x:1,y:2}
console.log(obj2);   // ==> {x:1,y:2}
//----看點 1-----
// 現在對obj2 屬性x值進行修改,看看obj 和 obj2的變化
obj2.x = 100;
console.log(obj);   // ==> {x:1,y:2}
console.log(obj2);   // ==> {x:100,y:2}
/*可以看出————對象的屬性不會修改原型的屬性*/
// -------------------------------------------------------
//----看點 2-----
// 這個時候我們對obj 屬性x進行修改,看看變化
obj.x = 111;
console.log(obj);   // ==> {x:111,y:2}
console.log(obj2);   // ==> {x:100,y:2}
/*可以看出————對象屬性賦值的時候只會創建屬性或者對自有屬性進行賦值,因爲在之間對obj2的x屬性進行賦值的時候,obj2的自有屬性中沒有x屬性,所以obj2會創建新的屬性x並賦值,同時覆蓋掉繼承屬性x的值*/
// -------------------------------------------------------
//----看點 3-----
// 我們現在對obj 屬性y進行修改,看看變化
obj.y = 222;
console.log(obj);   // ==> {x:111,y:222}
console.log(obj2);   // ==> {x:100,y:222}
/*這裏就可以體現我們上面說到的對象屬性的查詢過程,因爲obj的y屬性修改爲222,當我們查詢obj2的y屬性的時候,obj2的自有屬性中沒有y值,所以obj2就會到它的原型(obj)中查找,原型中的y屬性經過修改變成222,所以查詢的obj2屬性y的結果就是222*/
// -------------------------------------------------------
//----看點 4-----
// 我們來看看訪問一個不存在的屬性,
console.log(obj2.m_string); // ==> undefined
console.log(obj2.m_string.length); // ==>  這裏會報錯
/*當訪問的屬性不存在的時候,系統會返回一個undefined,而null和undefined值是沒有屬性的,因此上述第二行代碼會報錯。*/
// 我們可以通過判斷的方法來避免錯誤的出現
var strLength;
if(obj2 && obj2.m_string)  strLength = obj2.m_string.length;
//還有一種更簡練的方法
strLength = obj2 && obj2.m_string && obj2.m_string.length;

經過上面的例子,你是否搞懂繼承了?

(二)屬性的刪除、檢測、枚舉

刪除屬性
說先我們來了解一下delete運算符,該運算符可以刪除對象的屬性。但是delete只是斷開屬性和宿主對象的關係,而不會去操作屬性中的屬性。delete表達式刪除成功時會返回true。
需要注意的是,delete運算符只能刪除自有屬性,不能刪除繼承屬性,也不能刪除不可配置的屬性。

// 刪除屬性的操作
var mframe = {point:{x:11,y:22},size:{width:100,heght:50}};
// 這裏用p對象引用mfram的point屬性
var p = mframe.point;
// 當我們執行刪除操作之後,來打印p看看結果
delete mframe.point;
console.log(p);  // ==> {x:11,y:22};
/*執行代碼之後,我們知道mframe已經沒有了point屬性,但是由於已經刪除的屬性的引用依然存在,因此在JS的某些實現中,可能因爲這種不嚴謹的代碼而造成內存泄漏,所以在銷燬對象的時候,要遍歷屬性中的屬性,依次刪除*/

檢測屬性
檢測屬性主要是來判斷某屬性是否存在於某個對象中,我們用代碼來介紹一下檢測屬性。

// in運算符來檢測屬性,左側是 屬性名(字符串),右側是對象
var mpoint = {x:1,y:2};
console.log("x" in mpoint);   //==> true
console.log("z" in mpoint);   //==> false
console.log("toString" in mpoint);  //==> true,toString是繼承屬性;
// -------------------------------------------------------
// hasOwnProperty()方法用來檢測給定的名字是否是對象的自有屬性,對於繼承屬性它將返回falseconsole.log(mpoint.hasOwnProperty("x")); //==> true
console.log(mpoint.hasOwnProperty("z")); //==> false
console.log(mpoint.hasOwnProperty("toString")); //==> false
// -------------------------------------------------------
// propertyIsEnumerable()是hasOwnProperty()的加強版,只有檢測到是自有屬性且這個屬性是可枚舉的纔會返回ture.這裏就不做舉例了

枚舉屬性
我們經常會遍歷對象的屬性,通常使用for/in循環來遍歷。for/in 循環可以遍歷對象中所有可枚舉的屬性(包括自有屬性和繼承屬性)。
有許多實用工具庫給Object.prototype添加了新的方法或屬性,這些屬性可以被所有對象繼承並使用,但是在ECMAScript 5標準之前,這些新添加的方法是不能定義爲不可枚舉的,因此使用for/in循環進行枚舉的時候,這些屬性會被枚舉出來。爲了避免這種情況,我們需要在for/in 中進行過濾,有兩種常見的方法:

for(p in obj){
    if(!obj.hasOwnProperty(p)) continue;  // 跳過繼承的屬性
}
//---------------------------------
for(p in obj){
    if(typeof obj[p] === "function") continue;  // 跳過方法
}

除了for/in循環之外,ECMAScript 5 定義了兩個用以枚舉屬性名稱的函數。
* Object.keys(),它返回一個數組,這個數組由對象中可枚舉的自有屬性的名稱組成。
* Object.getOwnPropertyNames(),它返回對象的所有自有屬性的名稱(包括自有屬性中的可枚舉屬性和不可枚舉屬性).

屬性的getter和setter

JavaScript的屬性值可以用getter和setter方法代替,由getter和setter定義的屬性稱爲存取器屬性。存取器屬性是可以繼承的。
用例子說明:

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)},  
};
console.log(p);  // ==> (1,1)  r = 1.41421356  theta = 0.785398
p.r = 2;
console.log(p);  // ==> (1.41421356,1.41421356)  r = 2  theta = 0.785398

屬性特性

數據屬性特性分爲:值(value),可寫性(writable),可枚舉性,可配置性。
存取器屬性特性分爲:讀取(get),寫入(set),可枚舉性,可配置性。
我們創建的屬性默認都是可寫、可枚舉、可配置的。
我們通過例子來介紹一個方法(Object.definePeoperty()):

// 我們可以調用Object.definePeoperty()來設置對象屬性的特性
// 我們可以調用Object.definePeoperty()來設置對象屬性的特性
/*
 *參數1:傳入的對象   (Object)
 *參數2:要創建或者修改的屬性的名稱 (String)
 *參數3:屬性的特性(對於新創建的自有屬性四個屬性默認都爲false或者undefined,對於已有屬性來說,默認的特性值是沒有做任何修改的)
 *(注意:此方法 只能修改自有屬性或者是創建自有屬性,但是不能修改繼承屬性)。
 */
//----看點1
// 簡單介紹一下 方法的使用
var obj = {};
Object.defineProperty(obj,"x",{value:2017,
                               writable:true,
                               enumerable:true,
                               configurable:true});
document.write(obj.x+"<br/>");  // ==> 2017
// 這個方法同樣是有返回值的,是將修改的對象返回
var obj1 = Object.defineProperty({},"x",{value:2017,
                                         writable:true,
                                         enumerable:true,
                                         configurable:true});
document.write(obj1.x+"<br/>");  // ==> 2017
//-----------------------------------------------------------
//----看點2
// 對於新創建的自有屬性,四個屬性的默認值都爲false或者undefined
var obj2 = Object.defineProperty({},"x",{});
document.write(obj2.x+"<br/>");  // ==> undefined
obj2.x = 2017;
document.write(obj2.x+"<br/>");  // ==> undefined
//-----------------------------------------------------------
//----看點3
// 對於已有屬性來說,默認的特性值是沒有做任何修改的
var obj3 = {x:2017}; // x 屬性是可寫、可枚舉、可配置的
Object.defineProperty(obj3,"x",{});  // 屬性描述中沒有做任何修改
obj3.x = 2000;
document.write(obj3.x+"<br/>");  // ==> 2000 說明x屬性依然是可寫的
//-----------------------------------------------------------
//----看點4
// 此方法 只能修改自有屬性或者是創建自有屬性,但是不能修改繼承屬性
var obj4 = Object.create(obj3);
var obj4_x = obj4.x;   // 用一個對象來引用obj4.x屬性
document.write(obj4.hasOwnProperty("x")+ "<br/>"); //==> false x不是自有屬性
Object.defineProperty(obj4,"x",{}); // 創建x屬性並且覆蓋繼承的屬性
document.write(obj4.hasOwnProperty("x")+ "<br/>"); //==> ture 
// 從上面可以看出,通過此方法,obj4 創建了x屬性,並且覆蓋了繼承的x屬性
document.write(obj4.x+"---"+obj4_x+ "<br/>");//undefined---2000
obj4.x = 123123; // 嘗試修改x自有屬性,發現無效,原因是writable 爲false
document.write(obj4.x+"---"+obj4_x+ "<br/>");//undefined---2000
// 從上面可以看出,新創建的自有屬性x,屬性描述都是默認值,所以value爲undefined
//-----------------------------------------------------------
//----看點5
// 看看 數據類型屬性,是可以修改成存取器屬性的
var obj5 = {x:888};
Object.defineProperty(obj5,"x",{get:function(){return 2017}});
document.write(obj5.x + "<br/>"); // ==> 2017
obj5.x = 123;  // 由於存儲器屬性沒有set方法,所以是隻讀屬性,不能進行修改
document.write(obj5.x + "<br/>"); // ==> 2017
//-----------------------------------------------------------
//----看點6
/*
 *Object.defineProperties() 可以修改一個對象的多個屬性描述
 *參數1:對象
 *參數2:一個映射列表(也成對象,字典),包括屬性名,屬性描述
 */
var obj6 = Object.defineProperties({},{x:{value:100,writable:true,enumerable:true,configurable:true},
"y":{value:200}});
document.write(obj6.x +"---"+ obj6.y + "<br/>"); // ==> 2017

我們來看看如果給Object複製屬性,而且這些屬性的特性也一併複製。

// 複製屬性的特性
/*
 *給Object.prototype(原型)添加一個不可枚舉的extend()方法。
 *這個方法繼承自調用它的對象,將作爲參數傳入的對象的屬性一一複製,
 *除了值之外,也要複製屬性的所有特性,除非在目標對象中存在同名的屬性,
 *參數對象的所有自有對象(包括不可枚舉的屬性)也要意義複製。
 */
Object.defineProperty(Object.prototype,
       "extend",
       {
        writable:true,
        enumerable:false, // 不可枚舉
        configurable:true,
        value:function(obj){ // 值爲一個函數
            //獲取所有的自有屬性,包括不可枚舉的
            var names = Object.getOwnPropertyNames(obj);
            // 遍歷
            for(var i = 0; i < names.length; i++){
                // 如果屬性中已經存在,則跳過
                if(names[i] in this) continue;
                // 獲取obj中的屬性的描述符
                var desc = Object.getOwnPropertyDescriptor(obj,names[i]);
                // 用它給this創建一個屬性
                Object.defineProperty(this,names[i],desc);
            }
        }
});

對象的三個屬性

對象的三個屬性是原型(prototype)、類(class)和可擴展性(extensible)。
* 原型屬性
原型屬性是在實例對象創建之初就設置好的,之前我們提到的,通過對象直接量創建的對象,原型是Object.prototype。通過new創建的對象,原型是構造函數的prototype。通過Object.create()創建的對象,原型是第一個參數。可以通過Object.getPrototypeOf()來查詢它的原型。也可以通過isPrototypeOf()方法來檢測一個對象是否是另一個對象的原型(或處於原型鏈中),例如p.isPrototypeOf(o)來檢測p是否是o的原型。
* 類屬性
對象的類屬性(class)是一個字符串,用來表示對象的類型信息。
因爲JS沒有提供設置這個屬性的方法,我們只能通過間接的方法來查詢它,默認的toString()方法(繼承自Object.prototype)返回[object class]這種格式的字符串,所以我們需要提取返回來的字符串的第8個位置到倒數第二個位置之間的字符串。(有個棘手的問題是,很多對象重寫了toString()方法,爲了能夠調用正確toString()版本,必須簡介地調用Function.call()方法)。看例子:

//這個函數用來獲取對象的class屬性
function classof(obj){
    if(obj === null) return "Null";
    if(obj ===undefined) return "Undefined";
    return Object.prototype.toString.call(obj).slice(8,-1);
}
// 簡單的輸出函數
function printClassName(obj){
    document.write(classof(obj)+"<br/>");
}
printClassName(null);    //==>Null
printClassName(1);       //==>Number
printClassName("");      //==>String
printClassName(false);   //==>Boolean
printClassName({});      //==>Object
printClassName([]);      //==>Array
printClassName(/./);     //==>RegExp
printClassName(new Date());//==>Date
printClassName(window);  //==>Window
function f(){}   // 定義一個自定義構造函數
printClassName(new f());  //==>Object
  • 可擴展性
    對象的可擴展性用以表示是否可以給對象添加新屬性。所有的內置對象和自定義對象都是現實可擴展的。我們可以通過(Object.esExtensible())來判斷該對象是否是可擴展的。如果我們想將一個對象轉爲不可擴展的,需要調用Object.preventExtensions()。需要注意的是,一旦對象轉成不可擴展的,就無法再將其轉化回可擴展的了,而且這個方法隻影響對象本身的可擴展性。
    Object.seal()方法是將對象設置成不可擴展的,同時還將對象的所有自有屬性設置成不可配置的,但是不更改對象屬性的可寫屬性,也就是將對象封閉。
    Object.isSeal()方法是來檢測對象是否封閉。
    Object.freeze()`是嚴格鎖定對象,不僅將對象設置爲不可擴展的和將其屬性設置成不可配置的之外,還可以將它自有的所有數據屬性設置成只讀的(讀取器屬性的不受影響)。

序列化對象

對象序列化是指將對象的狀態轉換爲字符串,也可以將字符串還原爲對象。
Json.stringify()用來序列化JS對象的。
Json.parse()用來還原JS對象。
注意:JSON的語法是JavaScript語法的子集,它並不能代表JavaScript裏的所有值。支持對象、數組、字符串、無窮大數字、true、false、null,並且它們可以序列化和還原。NaN、Infinity和- Infinity序列化的結果是null。而函數、RegExp、Error對象和undefined值不能序列化和還原。序列化只能序列化對象可枚舉的自有屬性,對於不能序列化的屬性會將屬性省略。這兩個方法接受第二個參數和第三個參數,大家可以看看文檔。

var mObj = {
            x:1,
            y:{z:[false,null,"string"]},
         func:function(){document.write("這是一個函數func")}
};
var sObj = JSON.stringify(mObj);
document.write(sObj +"<br/>");  // ==> {"x":1,"y":{"z":[false,null,"string"]}}   對象中的方法給省略掉了 
var oObj = JSON.parse(sObj); // ==> {x:1,y:{z:[false,null,"string"]}}

對象方法

所有JS對象都從Object.prototype繼承屬性,這些繼承屬性主要是方法,因爲我們對方法更加感興趣。這些方法也可以被重寫。
之前提到過很多對象方法了。這裏就不具體講解了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章