JavaScript面向對象(1)——談談對象

       很多同學甚至在相當長的時間裏,都忽略了JavaScript也可以進行面向對象編程這個事實。一方面是因爲,在入門階段我們所實現的各種頁面交互功能,都非常順理成章地使用過程式程序設計解決了,我們只需要寫一些方法,然後將事件綁定在頁面中的DOM節點上便可以完成。尤其像我這類一開始C++這類語言沒好好學,第一門主力語言就是JavaScript的同學來說,過程化程序設計的思維似乎更加根深蒂固。另一方面,就算是對於Java、C++等語言的程序員來說,JavaScript的面向對象也是一個異類:JavaScript中沒有class的概念(在ES5及之前版本中沒有,ES6會單獨介紹),其基於prototype的繼承模式也與傳統面嚮對象語言不同,而JavaScript的弱類型特性更會令這裏面的很多人抓狂。當然,在熟悉了之後,這種靈活性也會帶來很多好處。總之,封裝、繼承、多態、聚合這些面向對象的基本特性JavaScript都有其自己的實現方式,這些知識的學習是從入門級JS程序員進階的必經之路。

JavaScript面向對象(1)——談談對象

JavaScript面向對象(2)——談談函數(函數、對象、閉包)

JavaScript面向對象(3)——原型與基於構造函數的繼承模式(原型鏈)

JavaScript面向對象(4)——最佳繼承模式(深拷貝、多重繼承、構造器借用、組合寄生式繼承)



       很久沒有更新博客了,這段時間裏方向發生了很大的變化,現在在準備出國讀研的事情,技術學習少了很多很多。這段時間一直想把方向的轉變過程中所做的思考、面對的問題發出來~  工作室送老剛過去不久,每一級同學都有着各自面臨着的問題,或許我的思考過程可以帶來啓示? 當然,假如日後證明我選錯了,這些思慮就可以當作經驗教訓來複盤了哈哈哈哈。 

       毫無疑問的,JavaScript是一種面向對象的編程語言,它的面向對象的實現機制又是那麼的特殊。最近想把這一整塊的知識整理一下,先從JavaScript的一些語言特性說起,談談變量,談談對象,談談函數,談談JavaScript面向對象的實現。


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


       要來談談JavaScript中的對象,我們要從JavaScript的數據類型來說起。在JavaScript中,除了字符串、數字、true、false、null、undefined之外,所有的值都是對象,對象在JavaScript中可是一等公民。對象自身,則是一系列屬性的無序集合,也就是鍵值對的集合,這樣的數據結構在其他地方被叫做“散列”、“字典”、“關聯數組”等~ 其中鍵是字符串,而值則可以是JS中的任意數據類型。

       JS對象是引用類型的,是可變的,也是動態的。我們將對象賦值給別的變量,並不會創建一個新的對象副本,只是賦了一個對象的引用;而對象可變,在語句修改了對象的內容之後,所有引用了這個對象的變量都會變化。而JS對象的動態性可以讓我們對屬性進行操作,新增、刪除等等都可以隨時進行。這個特性在之後的面向對象實現中很重要,在傳統面嚮對象語言中我們必須預先設計好類,將方法和行爲提前設計好;在JS中,我們可以在使用對象的時候直接對它進行擴充,就是利用了它的動態性。在從幾個方面討論對象之前,關於對象與屬性,還有一些需要知道的概念:

               對象特性:除了屬性之外,對象擁有的一些特性,其中包括:

                                 對象的原型:指向另一個對象,相當於父對象。

                                 對象的類:一個標識對象類型的字符串。

                                 對象的擴展標記:指明瞭是否可以向該對象添加新屬性。

               對象分類:按照對象的來源,共有三類JavaScript對象:

                                內置對象:由ECMAScript規範所定義的對象和類,包括數組、函數、日期、正則表達式、錯誤等,輸入JavaScript語言核心的範疇。

                                宿主對象:由JavaScript解釋器所嵌入的宿主環境所定義的。比如文檔對象就是由瀏覽器所定義的。(還記得剛接觸NodeJs第一次使用repl的時候,直接敲了個alert偷笑~ alert是瀏覽器所定義的Window對象提供的,在NodeJs環境中並不存在這個對象。)

                                自定義對象:由運行中的JavaScript語句所創建的對象

               屬性特性:除了名字和值之外,每個屬性還有幾個與之相關的特性值:可寫、可枚舉、可配置。它們決定了該屬性是否可寫、是否可以通過for/in循環返回、是否何以刪除或修改。

               屬性分類:自有屬性和繼承屬性。 自由屬性是直接在對象中定義的屬性,而繼承屬性是在對象的原型對象中定義的屬性。


一、創建對象的三種方法

               1、通過對象直接量創建:大括號內是一個對象,鍵-值用冒號分隔,鍵-值間用逗號分隔。嗯,沒錯,就是JavaScript對象表示法(JSON)了。

    

var object = {
	"name" : "zhuwq",
	"age" : 21,
	"callHim" : function(){
		//call me
	},
	"girlFriend" : {
		"name" : undefined,
		"age" : undefined
	}
}

               2、通過new關鍵字創建對象:與其他面嚮對象語言一樣,可以使用new關鍵字調用構造函數初始化一個對象。JavaScript內置對象都內置構造函數:

var object = new Object(),
	array = new Array(),
	date = new Date(),
	regExp = new RegExp();

                     使用自定義構造函數來初始化新對象更加重要,會在之後詳述。     

               3、使用Object.create()方法:對於這種對象創建方法,原型的概念非常重要:

                     Object.create()方法是ES5開始才提供的,它可以通過將原型對象(原型會在之後單獨討論)作爲參數傳入的方式創建一個新對象:

var obj = { "name" : "obj"};
var o1 = Object.create(obj);
obj.a;         //  "obj"
var o2 = Object.create(null);             // 創建一個沒有原型的新對象
var o3 = Object.create(Object.prorotype); // 創建一個普通的空對象 與var o3 = {}; 以及var o3 = new Object();相同

二、屬性的操作

               1、屬性的訪問和修改:可以通過點(.)和方括號([])運算符來訪問對象的屬性:
var obj = { "name" : "obj"};
obj.name;   //"obj"
obj["name"]; //"obj"
                                             對於點來說,右邊必須是一個以屬性命名的簡單標識符;而對方括號來說,方括號中必須是一個返回字符串或者可以轉換爲字符串的表達式。這就使得在某些情況下,通過方括號這種類似於關聯數組的訪問方式可以完成更加靈活的工作:
var addr = {};
for(var i = 0;i < 5;i++){
	addr["name"+i] = i;
}
addr; //{ name0: 0, name1: 1, name2: 2, name3: 3, name4: 4 }
                                            在很多無法預知屬性名的場景下,只有使用數組寫法才能完成程序。
                                            還可以使用for/in循環遍歷對象屬性:
var object = {
	"name" : "zhuwq",
	"age" : 21,
	"girlFriend" : {
		"name" : undefined,
		"age" : undefined
	}
}
var message = '';
for(pro in object){
	message += pro + ":" + object[pro] + ",";
}
message; //name:zhuwq,age:21,girlFriend:[object Object],
               2、屬性的添加:在訪問屬性的時候,直接向一個不存在的屬性賦值就會直接添加該屬性:
var obj ;= {};
obj.a = "a";
obj["b"] = "b";
obj; // { "a": "a", "b": "b" }
               3、屬性的刪除:直接使用delete運算符即可
var obj = {"a":"a","b":"b"};
delete obj.a; //true
obj; // {"b" : "b"}
               4、存取器屬性:我們通常所使用的對象屬性叫做數據屬性,而從ES5開始,存取器屬性被添加了進來。它使得對象的屬性值可以被一兩個方法所替代:當程序設置一個存取器屬性的值時調用setter方法,當程序查詢一個存取器屬性的值時調用getter方法。同樣的,可以只添加getter方法或只添加setter方法使這個屬性成爲一個只讀或只寫屬性。定義存取器方法時,不使用functon關鍵字而是使用get或set關鍵字定義與屬性名同名的方法,函數體和屬性名之間無需冒號分隔,但一個函數體與下一項之間依然需要逗號分隔:
var rectangle = {
    width: 1.0,
    height: 2.0,
    name: undefined,
    get area(){
        return this.width * this.height;
    },
    set nameValue(newName){
        this.name = newName;
    },
    get nameValue(){
        return "This object's name is " + this.name;
    }
}
rectangle.area;  // 2
rectangle.nameValue = "zhuwq";
rectangle.nameValue;  // This object's name is zhuwq
                 在使用存取器屬性的時候,要注意存取器屬性和數據屬性不能同名,這樣會報RangeError的錯誤。剛接觸存儲器屬性的時候有可能會因爲一些思維慣性將存取器屬性作爲操作同名數據屬性的方法來使用,需要注意。  同樣的,存取器屬性也可以繼承。

三、關於屬性的特性

               在文章的最開始已經介紹過,JavaScript對象中的屬性除了其名字和值以外,還有三個特性,分別標識了這些屬性是否可寫、可枚舉、可配置。在ES3的時代,這些特性是無法修改的,並且所有通過程序創建的屬性都是可寫可枚舉可配置的。從ES5開始,JavaScript包含了配置屬性特性的API。數據屬性的特性包括值、可寫性、可枚舉性、可配置性;而存取器屬性的特性包括讀取性、寫入性、可枚舉性、可配置性。
               
               1、檢測屬性:在某些時候我們需要檢測對象中是否存在某個屬性,以及屬性是自有屬性還是繼承屬性。這裏有幾種方法:
//構建對象
var object1 = {
    x : 1
}
var object2 = Object.create(object1);
object2.y = 2;

//檢測方法1:  使用in運算符 不區分自由屬性和繼承屬性
"x" in object2; // true
"y" in object2; // true
"z" in object2; // false

//檢測方法2:  使用hasOwnProperty()方法 檢測是否爲自有屬性
object2.hasOwnProperty("x"); // false 繼承自object1
object2.hasOwnProperty("y"); // true
object2.hasOwnProperty("z"); // false 無該屬性

//檢測方法3:  使用propertyIsEnumerable()方法 檢測是否爲可枚舉自有屬性
object2.propertyIsEnumerable("x");        //false 繼承自object1
object2.propertyIsEnumerable("y");        //true
object2.propertyIsEnumerable("z");        //false 無該屬性
object2.propertyIsEnumerable("toString"); //false 不可枚舉
                 還有一種情況需要注意區分:在對象沒有該屬性或者該屬性值爲undefined時,訪問該屬性都會返回undefined。這時候需要靈活使用in運算符和!==運算符按需區分這兩種情況。

               2、屬性特性的查詢:ES5中定義了一個“屬性描述符”對象,通過將對象和屬性名作爲參數傳入Object.getOwnPropertyDescriptor()方法可以獲得該對象該屬性的屬性描述符。需要注意的是,這裏只能獲得自有屬性的屬性描述符,要查詢繼承屬性的特性,需要使用Object.getPrototypeOf()等方法獲取到原型對象再進行查詢。:
var rectangle = {
    width: 1.0,
    set nameValue(newName){
        this.name = newName;
    },
    get nameValue(){
        return "This object's name is " + this.name;
    }
}
Object.getOwnPropertyDescriptor(rectangle,"width"); // { value: 1, writable: true, enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(rectangle,"nameValue");// { get: [Function: get nameValue], set: [Function: set nameValue], enumerable: true, configurable: true }
Object.getOwnPropertyDescriptor(rectangle,"aaa"); // undefined
Object.getOwnPropertyDescriptor(rectangle,"toString"); // undefined
               3、屬性特性的創建和修改:有Object.defineProperty()和Object.defineProperties()兩個方法,分別用來創建或修改單個屬性特性和多個屬性特性:
//通過將對象、屬性、屬性描述符對象傳入Object.defineProperty()對象來配置單個屬性的屬性特性
var obj = {};
Object.defineProperty(obj,"b",{
    value: 1,
    writable: true,
    enumerable: true,
    configurable: true
});
obj.b; //1
Object.defineProperty(obj,"b",{
    value: 1,
    writable: false,
    enumerable: true,
    configurable: true
});
obj.b = 2; 
obj.b; //1   這裏僅展示了可寫性的配置
//通過將對象、屬性及其描述符的集合對象傳入Object.defineProperties()對象來配置多個屬性的屬性特性
var obj = {};
Object.defineProperties(obj,{
    b: {value: 1,writable: false, enumerable: true, configurable: true},
    c: {get: function(){ return "get c"}, set: undefined,enumerable: true, configurable: true}
});
obj.b = 2; 
obj.b; //1
obj.c; // get c
               當然了,這兩個操作的成功與否,與對象的可擴展性、該屬性的可配置性、可寫性都有關聯。操作失敗是則會拋出類型錯誤異常。

四、對象的三個特性

               1、原型屬性:原型與原型鏈是JavaScript面向對象中的核心部分,會在之後有單獨一篇講述。對象的原型屬性是在對象創建的時候就已經設置好了的,根據三種創建對象的方式,會有三種原型的設置。在ES5中,我們可以通過Object.getPropertyOf()方法來查詢一個對象的原型。
               2、類屬性: 類屬性是一個標識對象類型的字符串,到ES5中也沒有提供修改這個屬性的方法。我們可以通過調用對象的toString方法來獲取對象的class屬性。因爲很多對象繼承的toString方法重寫了,這裏提供一個《JavaScript權威指南》中提供的方法,各種數據類型都可以直接傳入:
function classOf(obj){
    if (obj === null) return "Null";
    if (obj === undefined) return "undefined";
    return Object.prototype.toString.call(obj).slice(8,-1);
}
               3、可擴展性:對象的可擴展性決定了是否可以給對象添加新屬性。在JavaScript中所有的內置對象和自定義對象都是可擴展的。有一些方法可以將對象轉爲不可擴展的,需要注意的是,一旦轉爲不可擴展對象,就無法再轉變回來了。進行將對象封閉,可以避免外界的干擾。有三種方法可以從不同程度將對象進行鎖定,這裏按程度遞增介紹:
//通過Object.esExtensible()方法查詢對象是否可擴展
var obj = {};
Object.esExtensible(obj); // true

//通過Object.preventExtensions()方法將對象轉爲不可擴展
Object.preventExtensions(obj);
Object.esExtensible(obj); // false

var obj = {a:1};
//通過Object.seal()方法將對象轉爲不可擴展並將所有自有屬性設爲不可配置
//可以使用Object.isSealed()方法判斷是否已經封閉
Object.isSealed(obj); // false
Object.seal(obj);
Object.isSealed(obj); // true
Object.esExtensible(obj); //false
Object.defineProperty(obj,"b",{
    value: 1,
    writable: false,
    enumerable: true,
    configurable: true
}); // TypeError: Cannot define property:b, object is not extensible.

var obj = {a:1};
//通過Object.freeze()方法將對象轉爲不可擴展、將所有自有屬性設爲不可配置、將所有數據屬性設爲只讀
//可以使用Object.isFrozen()方法判斷是否已經封閉
Object.isFrozen(obj); // false
Object.freeze(obj);
Object.isFrozen(obj); // true
Object.esExtensible(obj); //false
Object.defineProperty(obj,"b",{
    value: 1,
    writable: false,
    enumerable: true,
    configurable: true
});  // TypeError: Cannot define property:b, object is not extensible.
b.a = 2;
b.a; // 1


五、對象方法

               所有的JavaScript對象都從Object.prototype繼承屬性,大部分是方法。上文中已經提到過一些:hasOwnProperty(),propertuIsEnumerable(),isPrototypeOf(),Object.create(),Object.getPrototypeOf()等等。和它們相同的從Object.prototype中繼承的方法還有toString(),toLocalString,valueOf()等,都非常有用。但很多對象會對這些方法進行重寫,例如如果對Array對象調用自身的toString方法,將會得到一個數組元素列表,每個元素都轉換成了字符串;若是調用原始的toString方法,則只會得到"[array Array]"字符串。  關於這些方法,這裏不做贅述。





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