JavaScript中,除了五種原始類型(即數字,字符串,布爾值,null,undefined)以外的都是對象了,因此,不把對象學明白怎麼繼續往下學習呢?前端
一.概述程序員
對象是一種複合值,它將不少值(原始值或其餘對象)聚合在一塊兒,可經過屬性名訪問這些值。而屬性名能夠是包含空字符串在內的任意字符串。JavaScript對象也能夠稱做一種數據結構,正如咱們常常據說的「散列(hash)」、「散列表(hashtable)」、「字典(dictionary)」、「關聯數組(associative array)」。面試
JavaScript中對象能夠分爲三類:編程
①內置對象,例如數組、函數、日期等;json
②宿主對象,即JavaScript解釋器所嵌入的宿主環境(好比瀏覽器)定義的,例如HTMLElement等;數組
③自定義對象,即程序員用代碼定義的;瀏覽器
對象的屬性能夠分爲兩類:數據結構
①自有屬性(own property):直接在對象中定義的屬性;編程語言
②繼承屬性(inherited property):在對象的原型對象中定義的屬性(關於原型對象下面會詳談);函數
二.對象的建立
既然學習對象,又怎能不懂如何建立對象呢?面試前端崗位的同窗,可能都被問過這個基礎問題吧:
建立JavaScript對象的兩種方法是什麼?(或者:說說建立JavaScript對象的方法?)
這個問題我就被問過兩次。「建立對象的兩種方法」這種說法網上有不少,可是據我所看書籍來講是有三種方法的!下面咱們就來具體談談這三種方法:
1.對象直接量
對象直接量由若干名/值對組成的映射表,名/值對中間用冒號分隔,名/值對之間用逗號分隔,整個映射表用花括號括起來。屬性名能夠是JavaScript標識符也能夠是字符串直接量,也就是說下面兩種建立對象obj的寫法是徹底同樣的:
var obj = {x: 1, y: 2}; var obj = {'x': 1, 'y':2};
2.經過new建立對象
new運算符後跟隨一個函數調用,即構造函數,建立並初始化一個新對象。例如:
1 var o = new Object(); //建立一個空對象,和{}同樣 2 var a = new Array(); //建立一個空數組,和[]同樣 3 var d = new Date(); //建立一個表示當前時間的Date對象
關於構造函數相關的內容之後再說。
3.Object.create()
ECMAScript5定義了一個名爲Object.create()的方法,它建立一個新對象,其中第一個參數是這個對象的原型對象(好像還沒解釋原型對象...下面立刻就說),第二個可選參數用以對對象的屬性進行進一步的描述,第二個參數下面再說(由於這第三種方法是ECMAScript5中定義的,因此之前你們才常常說建立對象的兩種方法的吧?我的以爲應該是這個緣由)。這個方法使用很簡單:
1 var o1 = Object.create({x: 1, y: 2}); //對象o1繼承了屬性x和y 2 var o2 = Object.create(null); //對象o2沒有原型
下面三種的徹底同樣的:
1 var obj1 = {}; 2 var obj2 = new Object(); 3 var obj3 = Object.create(Object.prototype);
爲了解釋爲啥這三種方式是徹底同樣的,咱們先來解釋下JavaScript中的原型對象(哎,讓客官久等了!),記得一位大神說過:
Javascript是一種基於對象(object-based)的語言,你遇到的全部東西幾乎都是對象。可是,它又不是一種真正的面向對象編程(OOP)語言,由於它的語法中沒有class(類)。
面向對象的編程語言JavaScript,沒有類!!!那麼,它是怎麼實現繼承的呢?沒錯,就是經過原型對象。基本上每個JavaScript對象(null除外)都和另外一個對象相關聯,「另外一個」對象就是所謂的原型對象(原型對象也能夠簡稱爲原型,並無想象的那麼複雜,它也只是一個對象而已)。每個對象都從原型對象繼承屬性,而且一個對象的prototype屬性的值(這個屬性在對象建立時默認自動生成,並不須要顯示的自定義)就是這個對象的原型對象,即obj.prototype就是對象obj的原型對象。
原型對象先說到這,回到上面的問題,有了對原型對象的認識,下面就是不須要過多解釋的JavaScript語言規定了:
①全部經過對象直接量建立的對象的原型對象就是Object.prototype對象;
②經過關鍵字new和構造函數建立的對象的原型對象就是構造函數prototype屬性的值,因此經過構造函數Object建立的對象的原型就是Object.prototype了;
如今也補充了第三種建立對象的方法Object.create()第一個參數的含義。
三.屬性的查詢和設置
學會了如何建立對象還不夠啊,由於對象只有擁有一些屬性才能真正起到做用滴!那麼,就繼續往下學習對象的屬性吧!
能夠經過點(.)或方括號([])運算符來獲取和設置屬性的值。對於點(.)來講,右側必須是一個以屬性名命名的標識符(注意:JavaScript語言的標識符有本身的合法規則,並不一樣於帶引號的字符串);對於方括號([])來講,方括號內必須是一個字符串表達式(字符串變量固然也能夠嘍,其餘能夠轉換成字符串的值好比數字什麼的也是均可以滴),這個字符串就是屬性的名字。正以下面例子:
1 var obj = {x: 1, y: 2}; 2 obj.x = 5; 3 obj['y'] = 6
概述中說過,JavaScript對象具備」自有屬性「,也有「繼承屬性」。當查詢對象obj的屬性x時,首先會查找對象obj自有屬性中是否有x,若是沒有,就會查找對象obj的原型對象obj.prototype是否有屬性x,若是沒有,就會進而查找對象obj.prototype的原型對象obj.prototype.prototype是否有屬性x,就這樣直到找到x或者查找到的原型對象是undefined的對象爲止。能夠看到,一個對象上面繼承了不少原型對象,這些原型對象就構成了一個」鏈「,這也就是咱們平時所說的「原型鏈」,這種繼承也就是JavaScript中「原型式繼承」(prototypal inheritance)。
對象o查詢某一屬性時正如上面所說會沿着原型鏈一步步查找,可是其設置某一屬性的值時,只會修改自有屬性(若是對象沒有這個屬性,那就會添加這個屬性並賦值),並不會修改原型鏈上其餘對象的屬性。
四.存取器屬性getter和setter
上面咱們所說的都是很普通的對象屬性,這種屬性稱作「數據屬性」(data property),數據屬性只有一個簡單的值。然而在ECMAScript 5中,屬性值能夠用一個或兩個方法替代,這兩個方法就是getter和setter,有getter和setter定義的屬性稱作「存取器屬性」(accessor property)。
當程序查詢存取器屬性的值時,JavaScript調用getter方法(無參數)。這個方法的返回值就是屬性存取表達式的值。當程序設置一個存取器屬性的值時,JavaScript調用setter方法,將賦值表達式右側的值當作參數傳入setter。若是屬性同時具備getter和setter方法,那麼它就是一個讀/寫屬性;若是它只有getter方法,那麼它就是一個只讀屬性,給只讀屬性賦值不會報錯,可是並不能成功;若是它只有setter方法,那麼它是一個只寫屬性,讀取只寫屬性老是返回undefined。看個實際的例子:
1 var p = {
2 x: 1.0, 3 y: 2.0, 4 get r(){ return Math.sqrt(this.x*this.x + this.y*this.y); }; 5 set r(newvalue){ 6 var oldvalue = Math.sqrt(this.x*this.x + this.y*this.y); 7 var ratio = newvalue/oldvalue; 8 this.x *= ratio; 9 this.y *= ratio; 10 }, 11 get theta(){ return Math.atan2(this.y, this.x); },
print: function(){ console.log('x:'+this.x+', y:'+this.y); } 12 };
正如例子所寫,存取器屬性定義一個或兩個和屬性同名的函數,這個函數定義並無使用function關鍵字,而是使用get和set,也沒有使用冒號將屬性名和函數體分隔開。對比一下,下面的print屬性是一個函數方法。注意:這裏的getter和setter裏this關鍵字的用法,JavaScript把這些函數當作對象的方法來調用,也就是說,在函數體內的this指向這個對象。下面看下實例運行結果:
正如控制檯的輸出,r、theta同x,y同樣只是一個值屬性,print是一個方法屬性。
ECMAScript 5增長的這種存取器,雖然比普通屬性更爲複雜了,可是也使得操做對象屬性鍵值對更加嚴謹了。
五.刪除屬性
程序猿擼碼通常都是實現增、刪、改、查功能,前面已經說了增、改、查,下面就說說刪除吧!
delete運算符能夠刪除對象的屬性,它的操做數應該是一個屬性訪問表達式。可是,delete只是斷開屬性和宿主對象的聯繫,而不會去操做屬性中的屬性:
1 var a = {p:{x:1}}; 2 var b = a.p; 3 delete a.p;
執行這段代碼後b.x的值依然是1,因爲已刪除屬性的引用依然存在,因此有時這種不嚴謹的代碼會形成內存泄露,因此在銷燬對象的時候,要遍歷屬性中的屬性,依次刪除。
delete表達式返回true的狀況:
①刪除成功或沒有任何反作用(好比刪除不存在的屬性)時;
②若是delete後不是一個屬性訪問表達式;
1 var obj = {x: 1,get r(){return 5;},set r(newvalue){this.x = newvalue;}}; 2 delete obj.x; //刪除對象obj的屬性x,返回true 3 delete obj.x; //刪除不存在的屬性,返回true 4 delete obj.r; //刪除對象obj的屬性r,返回true 5 delete obj.toString; //沒有任何反作用(toString是繼承來的,並不能刪除),返回true 6 delete 1; //數字1不是屬性訪問表達式,返回true
delete表達式返回false的狀況:
①刪除可配置性(可配置性是屬性的一種特性,下面會談到)爲false的屬性時;
1 delete Object.prototype; //返回false,prototype屬性是不可配置的 2 //經過var聲明的變量或function聲明的函數是全局對象的不可配置屬性 3 var x = 1; 4 delete this.x; //返回false 5 function f() {} 6 delete this.f; //返回false
六.屬性的特性
上面已經說到了屬性的可配置性特性,由於下面要說的檢測屬性和枚舉屬性還要用到屬性的特性這些概念,因此如今就先具體說說屬性的特性吧!
除了包含名字和值以外,屬性還包含一些標識它們可寫、可枚舉、可配置的三種特性。在ECMAScript 3中沒法設置這些特性,全部經過ECMAScript 3的程序建立的屬性都是可寫的、可枚舉的和可配置的,且沒法對這些特性作修改。ECMAScript 5中提供了查詢和設置這些屬性特性的API。這些API對於庫的開發者很是有用,由於:
①能夠經過這些API給原型對象添加方法,並將它們設置成不可枚舉的,這讓它們更像內置方法;
②能夠經過這些API給對象定義不能修改或刪除的屬性,藉此「鎖定」這個對象;
在這裏咱們將存取器屬性的getter和setter方法當作是屬性的特性。按照這個邏輯,咱們也能夠把屬性的值一樣看作屬性的特性。所以,能夠認爲屬性包含一個名字和4個特性。數據屬性的4個特性分別是它的值(value)、可寫性(writable)、可枚舉性(enumerable)和可配置性(configurable)。存取器屬性不具備值特性和可寫性它們的可寫性是由setter方法是否存在與否決定的。所以存取器屬性的4個特性是讀取(get)、寫入(set)、可枚舉性和可配置性。
爲了實現屬性特性的查詢和設置操做,ECMAScript 5中定義了一個名爲「屬性描述符」(property descriptor)的對象,這個對象表明那4個特性。描述符對象的屬性和它們所描述的屬性特性是同名的。所以,數據屬性的描述符對象的屬性有value、writable、enumerable和configurable。存取器屬性的描述符對象則用get屬性和set屬性代替value和writable。其中writable、enumerable和configurable都是布爾值,固然,get屬性和set屬性是函數值。經過調用Object.getOwnPropertyDescriptor()能夠得到某個對象特定屬性的屬性描述符:
從函數名字就能夠看出,Object.getOwnPropertyDescriptor()只能獲得自有屬性的描述符,對於繼承屬性和不存在的屬性它都返回undefined。要想得到繼承屬性的特性,須要遍歷原型鏈(不會遍歷原型鏈?不要急,下面會說到的)。
要想設置屬性的特性,或者想讓新建屬性具備某種特性,則須要調用Object.definePeoperty(),傳入須要修改的對象、要建立或修改的屬性的名稱以及屬性描述符對象:
能夠看到:
①傳入Object.defineProperty()的屬性描述符對象沒必要包含全部4個特性;
②可寫性控制着對屬性值的修改;
③可枚舉性控制着屬性是否可枚舉(枚舉屬性,下面會說的);
④可配置性控制着對其餘特性(包括前面說過的屬性是否能夠刪除)的修改;
若是要同時修改或建立多個屬性,則須要使用Object.defineProperties()。第一個參數是要修改的對象,第二個參數是一個映射表,它包含要新建或修改的屬性的名稱,以及它們的屬性描述符,例如:
var p = Object.defineProperties({},{ x: {value: 1, writable: true, enumerable: true, configurable: true}, y: {value: 2, writable: true, enumerable: true, configurable: true}, r: {get: function(){return 88;}, set: function(newvalue){this.x =newvalue;},enumerable: true, configurable: true}, greet: {value: function(){console.log('hello,world');}, writable: true, enumerable: true, configurable: true} });
相信你也已經從實例中看出:Object.defineProperty()和Object.defineProperties()都返回修改後的對象。
前面咱們說getter和setter存取器屬性時使用對象直接量語法給新對象定義存取器屬性,但並不能查詢屬性的getter和setter方法或給已有的對象添加新的存取器屬性。在ECMAScript 5中,就能夠經過Object.getOwnPropertyDescriptor()和Object.defineProperty()來完成這些工做啦!但在ECMAScript 5以前,大多數瀏覽器(IE除外啦)已經支持對象直接量語法中的get和set寫法了。因此這些瀏覽器還提供了非標準的老式API用來查詢和設置getter和setter。這些API有4個方法組成,全部對象都擁有這些方法。__lookupGetter__()和__lookupSetter__()用以返回一個命名屬性的getter和setter方法。__defineGetter__()和__defineSetter__()用以定義getter和setter。這四個方法都是以兩條下劃線作前綴,兩條下劃線作後綴,以代表它們是非標準方法。下面是它們用法:
七.檢測屬性
JavaScript對象能夠看作屬性的集合,那麼咱們有時就須要判斷某個屬性是否存在於某個對象中,這就是接下來要說的檢測屬性。
檢測一個對象的屬性也有三種方法,下面就來詳細說說它們的做用及區別!
1.in運算符
in運算符左側是屬性名(字符串),右側是對象。若是對象的自有屬性或繼承屬性中包含這個屬性則返回true,不然返回false。
爲了試驗,咱們先給對象Object.prototype添加一個可枚舉屬性m,一個不可枚舉屬性n;而後,給對象obj定義兩個可枚舉屬性x,一個不可枚舉屬性y,而且對象obj是經過對象直接量形式建立的,繼承了Object.prototype。下面看實例:
從運行結果能夠看出:in運算符左側是屬性名(字符串),右側是對象。若是對象的自有屬性或繼承屬性(不論這些屬性是否可枚舉)中包含這個屬性則返回true,不然返回false。
2.hasOwnProperty()
對象的hasOwnProperty()方法用來檢測給定的名字是不是對象的自有屬性(不論這些屬性是否可枚舉),對於繼承屬性它將返回false。下面看實例:
3.propertyIsEnumerable()
propertyIsEnumerable()是hasOwnProperty()的加強版,只有檢測到是自有屬性且這個屬性可枚舉性爲true時它才返回true。仍是實例:
八.枚舉屬性
相對於檢測屬性,咱們更經常使用的是枚舉屬性。枚舉屬性咱們一般使用for/in循環,它能夠在循環體中遍歷對象中全部可枚舉的自有屬性和繼承屬性,把屬性名稱賦值給循環變量。繼續上實例:
我原來認爲for/in循環跟in運算符有莫大關係的,如今看來它們的規則並不相同啊!固然,若是這裏不想遍歷出繼承的屬性,那就在for/in循環中加一層hasOwnProperty()判斷:
for(prop in obj){ if(obj.hasOwnProperty(prop)){ console.log(prop); } }
除了for/in循環以外,ECMAScript 5還定義了兩個能夠枚舉屬性名稱的函數:
①Object.getOwnpropertyNames(),它返回對象的全部自有屬性的名稱,不管是否可枚舉;
②Object.keys(),它返回對象對象中可枚舉的自有屬性的名稱;
仍是實例:
九.對象的三個特殊屬性
每一個對象都有與之相關的原型(prototype)、類(class)和可擴展性(extensible attribute)。這三個就是對象的特殊屬性(它們也只是對象的屬性而已,並無想象的複雜哦)。
1.原型屬性
正如前面所說,對象的原型屬性是用來繼承屬性的(有點繞...),這個屬性如此重要,以致於咱們常常把「o的原型屬性」直接叫作「o的原型」。原型屬性是在實例建立之初就設置好的(也就是說,這個屬性的值是JavaScript默認自動設置的,後面咱們會說如何本身手動設置),前面也提到:
①經過對象直接量建立的對象使用Object.prototype做爲它們的原型;
②經過new+構造函數建立的對象使用構造函數的prototype屬性做爲它們的原型;
③經過Object.create()建立的對象使用第一個參數(若是這個參數爲null,則對象原型屬性值爲undefined;若是這個參數爲undefined,則會報錯:Uncaught TypeError: Object prototype may only be an Object or null: undefined)做爲它們的原型;
那麼,如何查詢一個對象的原型屬性呢?在ECMAScript 5中,將對象做爲參數傳入Object.getPrototypeOf()能夠查詢它的原型,例如:
可是在ECMAScript 3中,沒有Object.getPrototypeOf()函數,但常用表達式obj.constructor.prototype來檢測一個對象的原型,由於每一個對象都有一個constructor屬性表示這個對象的構造函數:
①經過對象直接量建立的對象的constructor屬性指向構造函數Object();
②經過new+構造函數建立的對象的constructor屬性指向構造函數;
③經過Object.create()建立的對象的constructor屬性指向與其原型對象的constructor屬性指向相同;
要檢測一個對象是不是另外一個對象的原型(或處於原型鏈中),可使用isPrototypeOf()方法。例如:
還有一個非標準但衆多瀏覽器都已實現的對象的屬性__proto__(一樣是兩個下劃線開始和結束,以代表其爲非標準),用以直接查詢/設置對象的原型。
2.類屬性
對象的類屬性(class attribute)是一個字符串,用以表示對象的類型信息。ECMAScript 3 和ECMAScript 5 都未提供設置這個屬性的方法,並只有一種間接的方法能夠查詢它。默認的toString()方法(繼承自Object.prototype)返回了這種格式的字符串:[object class] 。所以,要想得到對象的類,能夠調用對象的toString()方法,而後提取已返回字符串的第8到倒數第二個位置之間的字符。不過,不少對象繼承的toString()方法重寫了(好比:Array、Date等),爲了能調用正確的toString()版本,必須間接地調用Function.call()方法。下面代碼能夠返回傳遞給它的任意對象的類:
1 function classof(obj){ 2 if(o === null){ 3 return 'Null'; 4 } 5 if(o === undefined){ 6 return 'Undefined'; 7 } 8 return Object.prototype.toString.call(o).slice(8, -1); 9 }
classof()函數能夠傳入任何類型的參數。下面是使用實例:
總結:從運行結果能夠看出經過三種方式建立的對象的類屬性都是'Object'。
3.可擴展性
對象的可擴展性用以表示是否能夠給對象添加新屬性。全部內置對象和自定義對象都是顯示可擴展的(除非將它們轉換爲不可擴展),宿主對象的可擴展性是由JavaScript引擎定義的。ECMAScript 5中定義了用來查詢和設置對象可擴展性的函數:
①(查詢)經過將對象傳入Object.isExtensible(),來判斷該對象是不是可擴展的。
②(設置)若是想將對象轉換爲不可擴展,須要調用Object.preventExtensions(),將待轉換的對象做爲參數傳進去。注意:
a.一旦將對象轉換爲不可擴展的,就沒法再將其轉換回可擴展的了;
b.preventExtensions()隻影響到對象自己的可擴展性,若是給一個不可擴展的對象的原型添加屬性,這個不可擴展的對象一樣會繼承這些新屬性;
進一步,Object.seal()和Object.preventExtensions()相似,除了能將對象設置爲不可擴展的,還能夠將對象的全部自有屬性都設置爲不可配置的。對於那些已經封閉(sealed)起來的對象是不能解封的。可使用Object.isSealed()來檢測對象是否封閉。
更進一步,Object.freeze()將更嚴格地鎖定對象——「凍結」(frozen)。除了將對象設置爲不可擴展和將其屬性設置爲不可配置以外,還能夠將它自有的全部數據屬性設置爲只讀(若對象的存取器屬性有setter方法,存取器屬性將不受影響,仍可經過給屬性賦值調用它們)。使用Object.isFrozen()來檢測對象是否總結。
總結:Object.preventExtensions()、Object.seal()和Object.freeze()都返回傳入的對象,也就是說,能夠經過嵌套的方式調用它們:
1 var obj = Object.seal(Object.create(Object.freeze({x:1}),{y:{value: 2, writable: true}));
這條語句中使用Object.create()函數傳入了兩個參數,即第一個參數是建立出的對象的原型對象,第二個參數是在建立對象是直接給其定義的屬性,而且附帶定義了屬性的特性。
十.對象的序列化
前面說完了對象的屬性以及對象屬性的特性,東西仍是蠻多的,不知道你是否已看暈。不過,下面就是比較輕鬆的話題了!
對象序列化(serialization)是指將對象的狀態轉換爲字符串,也能夠將字符串還原爲對象。ECMAScript 5提供了內置函數JSON.stringify()和JSON.parse()用來序列化和還原對象。這些方法都使用JSON做爲數據交換格式,JSON的全稱是「JavaScript Object Notation」——JavaScript對象表示法,它的語法和JavaScript對象與數組直接量的語法很是相近:
其中,最後的jsonObj是obj的深拷貝(關於什麼是深拷貝,什麼是淺拷貝,能夠參考:http://www.zhihu.com/question/23031215,第二個答案)。
JSON的語法是JavaScript的子集,它並不能表示JavaScript裏的全部值。支持對象、數組、字符串、無窮大數字、true、false和null,而且它們能夠序列化和還原。注意:
①NaN、Infinity和-Infinity序列化的結果是null;
②JSON.stringify()只能序列化對象可枚舉的自有屬性;
③日期對象序列化的結果是ISO格式的日期字符串(參照Date.toJSON()函數),但JSON.parse()依然保留它們的字符串形態,而不能將它們還原爲原始日期對象;
④函數、RegExp、Error對象和undefined值不能序列化和還原;
固然,JSON.stringify()和JSON.parse()均可以接受第二個可選參數,經過傳入須要序列化或還原的屬性列表來定製自定義的序列化或還原操做,這個咱們之後再詳談。
下面是在下衷心送給各位看官的五個歡迎(O(∩_∩)O~):歡迎轉載、歡迎收藏、歡迎評論、歡迎點贊、歡迎推薦!