JavaScript物件導向(1)——談談對象__Java

來源:互聯網
上載者:User

       很多同學甚至在相當長的時間裡,都忽略了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規範所定義的對象和類,包括數組、函數、日期、Regex、錯誤等,輸入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); // 建立一個普通的Null 物件 與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; //trueobj; // {"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;  // 2rectangle.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 繼承自object1object2.hasOwnProperty("y"); // trueobject2.hasOwnProperty("z"); // false 無該屬性//檢測方法3:  使用propertyIsEnumerable()方法 檢測是否為可枚舉自有屬性object2.propertyIsEnumerable("x");        //false 繼承自object1object2.propertyIsEnumerable("y");        //trueobject2.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"); // undefinedObject.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; //1Object.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; //1obj.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); // falseObject.seal(obj);Object.isSealed(obj); // trueObject.esExtensible(obj); //falseObject.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); // falseObject.freeze(obj);Object.isFrozen(obj); // trueObject.esExtensible(obj); //falseObject.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]"字串。  關於這些方法,這裡不做贅述。





聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.