JS粗讀筆記(亂七八糟一鍋燉版四)
阿新 • • 發佈:2019-02-16
console物件
table()
對於某些複合型別的資料,console.table方法可以將其轉為表格顯示。
var languages = [ { name: "JavaScript", fileExtension: ".js" }, { name: "TypeScript", fileExtension: ".ts" }, { name: "CoffeeScript", fileExtension: ".coffee"
}];console.table(languages);
上面程式碼的language變數,轉為表格顯示如下。
複合型資料轉為表格顯示的條件是,必須擁有主鍵。對於陣列來說,主鍵就是數字鍵。對於物件來說,主鍵就是它的最外層鍵。
count()
count方法用於計數,輸出它被呼叫了多少次。
上面程式碼每次呼叫greet函式,內部的console.count方法就輸出執行次數。 該方法可以接受一個字串作為引數,作為標籤,對執行次數進行分類。 dir(),dirxml() dir方法用來對一個物件進行檢查(inspect),並以易於閱讀和列印的格式顯示。
上面程式碼顯示dir方法的輸出結果,比 log方法更易讀,資訊也更豐富。
該方法對於輸出DOM物件非常有用,因為會顯示DOM物件的所有屬性。
dirxml方法主要用於以目錄樹的形式,顯示DOM節點。
如果引數不是DOM節點,而是普通的JavaScript物件,console.dirxml等同於console.dir assert() assert方法接受兩個引數,第一個引數是表示式,第二個引數是字串。只有當第一個引數為false,才會輸出第二個引數,否則不會有任何結果。
time(),timeEnd() 這兩個方法用於計時,可以算出一個操作所花費的準確時間。
time方法表示計時開始,timeEnd方法表示計時結束。它們的引數是計時器的名稱。呼叫timeEnd方法之後,console視窗會顯示“計時器名稱: 所耗費的時間”。 profile(),profileEnd()
開啟瀏覽器的開發者工具,在profile面板中,可以看到這個效能偵錯程式的執行結果 group(),groupend(),groupCollapsed() console.group和console.groupend這兩個方法用於將顯示的資訊分組。它只在輸出大量資訊時有用,分在一組的資訊,可以用滑鼠摺疊/展開。
console.groupCollapsed方法與console.group方法很類似,唯一的區別是該組的內容,在第一次顯示時是收起的(collapsed),而不是展開的。 屬性描述物件 Object.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor方法可以讀出物件自身屬性的屬性描述物件。
上面程式碼表示,使用Object.getOwnPropertyDescriptor方法,讀取o物件的p屬性的屬性描述物件。 Object.defineProperty(),Object.defineProperties() Object.defineProperty方法允許通過定義屬性描述物件,來定義或修改一個屬性,然後返回修改後的物件。它的格式如下。 Object.defineProperty(object, propertyName, attributesObject) 上面程式碼中,Object.defineProperty方法接受三個引數,第一個是屬性所在的物件,第二個是屬性名(它應該是一個字串),第三個是屬性的描述物件。比如,新建一個o物件,並定義它的p屬性,寫法如下。
如果一次性定義或修改多個屬性,可以使用Object.defineProperties方法。
這時需要注意的是,一旦定義了取值函式get(或存值函式set),就不能將writable設為true,或者同時定義value屬性,會報錯。 如果一個屬性的enumerable為false,下面三個操作不會取到該屬性。
Object.prototype.propertyIsEnumerable() 物件例項的propertyIsEnumerable方法用來判斷一個屬性是否可列舉。 屬性描述物件 概述 JavaScript提供了一個內部資料結構,用來描述一個物件的屬性的行為,控制它的行為。這被稱為“屬性描述物件”(attributes object)。每個屬性都有自己對應的屬性描述物件,儲存該屬性的一些元資訊。 下面是屬性描述物件的一個例項。
屬性描述物件提供6個元屬性。 (1)value value存放該屬性的屬性值,預設為undefined。 (2)writable writable存放一個布林值,表示屬性值(value)是否可改變,預設為true。 (3)enumerable enumerable存放一個布林值,表示該屬性是否可列舉,預設為true。如果設為false,會使得某些操作(比如for...in迴圈、Object.keys())跳過該屬性。 (4)configurable configurable存放一個布林值,表示“可配置性”,預設為true。如果設為false,將阻止某些操作改寫該屬性,比如,無法刪除該屬性,也不得改變該屬性的屬性描述物件(value屬性除外)。也就是說,configurable屬性控制了屬性描述物件的可寫性。 (5)get get存放一個函式,表示該屬性的取值函式(getter),預設為undefined。 (6)set set存放一個函式,表示該屬性的存值函式(setter),預設為undefined。 Object.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor方法可以讀出物件自身屬性的屬性描述物件。
上面程式碼表示,使用Object.getOwnPropertyDescriptor方法,讀取o物件的p屬性的屬性描述物件。 Object.defineProperty(),Object.defineProperties() Object.defineProperty方法允許通過定義屬性描述物件,來定義或修改一個屬性,然後返回修改後的物件。它的格式如下。
上面程式碼中,Object.defineProperty方法接受三個引數,第一個是屬性所在的物件,第二個是屬性名(它應該是一個字串),第三個是屬性的描述物件。比如,新建一個o物件,並定義它的p屬性,寫法如下。
如果屬性已經存在,Object.defineProperty方法相當於更新該屬性的屬性描述物件。 需要注意的是,Object.defineProperty方法和後面的Object.defineProperties方法,都有效能損耗,會拖慢執行速度,不宜大量使用。 如果一次性定義或修改多個屬性,可以使用Object.defineProperties方法。
上面程式碼中的p3屬性,定義了取值函式get。這時需要注意的是,一旦定義了取值函式get(或存值函式set),就不能將writable設為true,或者同時定義value屬性,會報錯。
上面程式碼同時定義了get屬性和value屬性,結果就報錯。 Object.defineProperty()和Object.defineProperties()的第三個引數,是一個屬性物件。它的writable、configurable、enumerable這三個屬性的預設值都為false。
上面程式碼中,定義obj物件的foo屬性時,只定義了可配置性configurable為true。結果,其他元屬性都是預設值。 writable屬性為false,表示對應的屬性的值將不得改寫。
上面程式碼由於writable屬性預設為false,導致無法對p屬性重新賦值,但是不會報錯(嚴格模式下會報錯)。不過,如果再一次使用Object.defineProperty方法對value屬性賦值,就會報錯。 configurable屬性為false,將無法刪除該屬性,也無法修改attributes物件(value屬性除外)。
上面程式碼中,由於configurable屬性預設為false,導致無法刪除某個屬性。 enumerable屬性為false,表示對應的屬性不會出現在for...in迴圈和Object.keys方法中。
上面程式碼中,p3屬性是用Object.defineProperty方法定義的,由於enumerable屬性預設為false,所以不出現在for...in迴圈中。 元屬性 屬性描述物件的屬性,被稱為“元屬性”,因為它可以看作是控制屬性的屬性。 可列舉性(enumerable) JavaScript的最初版本,in 運算子和基於它的for...in迴圈,會遍歷物件例項的所有屬性,包括繼承的屬性。 var obj = {};'toString' in obj // true 上面程式碼中,toString不是obj物件自身的屬性,但是in運算子也返回true,導致被for...in迴圈遍歷,這顯然不太合理。後來就引入了“可列舉性”這個概念,只有可列舉的屬性,才會被for...in迴圈遍歷,同時還規定原生繼承的屬性都是不可列舉的,這樣就保證了for...in迴圈的可用性。 可列舉性(enumerable)用來控制所描述的屬性,是否將被包括在for...in迴圈之中。具體來說,如果一個屬性的enumerable為false,下面三個操作不會取到該屬性。
上面程式碼中,owner物件作為註釋部分,加入car物件。由於ownerInfo屬性不可列舉,所以JSON.stringify方法最後輸出car物件時,會忽略ownerInfo屬性。 這提示我們,如果你不願意某些屬性出現在JSON輸出之中,可以把它的enumerable屬性設為false。 可配置性(configurable) 可配置性(configurable)決定了是否可以修改屬性描述物件。也就是說,當configurable為false的時候,value、writable、enumerable和configurable都不能被修改了。
上面程式碼首先定義物件o,並且定義o的屬性p的configurable為false。然後,逐一改動value、writable、enumerable、configurable,結果都報錯。 需要注意的是,writable只有在從false改為true會報錯,從true改為false則是允許的。
至於value,只要writable和configurable有一個為true,就允許改動。
另外,configurable為false時,直接對該屬性賦值,不報錯,但不會成功。
上面程式碼中,o物件的p屬性是不可配置的,對它賦值是不會生效的。 可配置性決定了一個變數是否可以被刪除(delete)。
上面程式碼中的物件o有兩個屬性,p1是可配置的,p2是不可配置的。結果,p2就無法刪除。 需要注意的是,當使用var命令宣告變數時,變數的configurable為false。
而不使用var命令宣告變數時(或者使用屬性賦值的方式宣告變數),變數的可配置性為true。
上面程式碼中的this.a3 = 1與a3 = 1是等價的寫法。window指的是瀏覽器的頂層物件。 這種差異意味著,如果一個變數是使用var命令生成的,就無法用delete命令刪除。也就是說,delete只能刪除物件的屬性。
可寫性(writable) 可寫性(writable)決定了屬性的值(value)是否可以被改變。
上面程式碼將o物件的a屬性可寫性設為false,然後改變這個屬性的值,就不會有任何效果。 注意,正常模式下,對可寫性為false的屬性賦值不會報錯,只會默默失敗。但是,嚴格模式下會報錯,即使是對a屬性重新賦予一個同樣的值。 關於可寫性,還有一種特殊情況。就是如果原型物件的某個屬性的可寫性為false,那麼派生物件將無法自定義這個屬性。
上面程式碼中,物件proto的foo屬性不可寫,結果proto的派生物件o,也不可以再自定義這個屬性了。在嚴格模式下,這樣做還會丟擲一個錯誤。但是,有一個規避方法,就是通過覆蓋屬性描述物件,繞過這個限制,原因是這種情況下,原型鏈會被完全忽視。
Object.getOwnPropertyNames() Object.getOwnPropertyNames方法返回直接定義在某個物件上面的全部屬性的名稱,而不管該屬性是否可列舉。
上面程式碼可以看到,陣列的例項物件([])沒有可列舉屬性,不可列舉屬性有length;Object.prototype物件也沒有可列舉屬性,但是有不少不可列舉屬性。 Object.prototype.propertyIsEnumerable() 物件例項的propertyIsEnumerable方法用來判斷一個屬性是否可列舉。 var o = {};o.p = 123;o.propertyIsEnumerable('p') // trueo.propertyIsEnumerable('toString') // false 上面程式碼中,使用者自定義的p屬性是可列舉的,而繼承自原型物件的toString屬性是不可列舉的。 存取器(accessor) 除了直接定義以外,屬性還可以用存取器(accessor)定義。其中,存值函式稱為setter,使用set命令;取值函式稱為getter,使用get命令。 存取器提供的是虛擬屬性,即該屬性的值不是實際存在的,而是每次讀取時計算生成的。利用這個功能,可以實現許多高階特性,比如每個屬性禁止賦值。
上面程式碼中,o物件內部的get和set命令,分別定義了p屬性的取值函式和存值函式。定義了這兩個函式之後,對p屬性取值時,取值函式會自動呼叫;對p屬性賦值時,存值函式會自動呼叫。
注意,取值函式Getter不能接受引數,存值函式Setter只能接受一個引數(即屬性的值)。另外,物件也不能有與取值函式同名的屬性。比如,上面的物件o設定了取值函式p以後,就不能再另外定義一個p屬性。 存取器往往用於,屬性的值需要依賴物件內部資料的場合。 利用存取器,可以實現資料物件與DOM物件的雙向繫結。 Object.preventExtensions() Object.preventExtensions方法可以使得一個物件無法再新增新的屬性。 不過,對於使用了preventExtensions方法的物件,可以用delete命令刪除它的現有屬性。 Object.isExtensible() Object.isExtensible方法用於檢查一個物件是否使用了Object.preventExtensions方法。也就是說,檢查是否可以為一個物件新增屬性。
Object.seal() Object.seal方法使得一個物件既無法新增新屬性,也無法刪除舊屬性。 Object.seal實質是把屬性描述物件的configurable屬性設為false,因此屬性描述物件不再能改變了。 同樣是使用了Object.seal方法,如果writable原為false,改變這個設定將報錯;如果原為true,則不會有問題。 Object.isSealed() Object.isSealed方法用於檢查一個物件是否使用了Object.seal方法。 Object.freeze() Object.freeze方法可以使得一個物件無法新增新屬性、無法刪除舊屬性、也無法改變屬性的值,使得這個物件實際上變成了常量。 Object.isFrozen() Object.isFrozen方法用於檢查一個物件是否使用了Object.freeze()方法 prototype 物件 Object.getPrototypeOf() Object.getPrototypeOf方法返回一個物件的原型。這是獲取原型物件的標準方法
Object.setPrototypeOf() Object.setPrototypeOf方法可以為現有物件設定原型,返回一個新物件。 Object.setPrototypeOf方法接受兩個引數,第一個是現有物件,第二個是原型物件 Object.create() Object.create方法用於從原型物件生成新的例項物件,可以替代new命令。 它接受一個物件作為引數,返回一個新物件,後者完全繼承前者的屬性,即原有物件成為新物件的原型
上面程式碼中,Object.create方法在A的基礎上生成了B。此時,A就成了B的原型,B就繼承了A的所有屬性和方法。 下面三種方式生成的新物件是等價的。
除了物件的原型,Object.create方法還可以接受第二個引數。該引數是一個屬性描述物件,它所描述的物件屬性,會新增到新物件。
Object.create方法生成的物件,繼承了它的原型物件的建構函式
上面程式碼中,b物件的原型是a物件,因此繼承了a物件的建構函式A。 Object.prototype.isPrototypeOf() 物件例項的isPrototypeOf方法,用來判斷一個物件是否是另一個物件的原型。
上面程式碼表明,只要某個物件處在原型鏈上,isProtypeOf都返回true Object.prototype.__proto__ __proto__屬性(前後各兩個下劃線)可以改寫某個物件的原型物件。
上面程式碼通過__proto__屬性,將p物件設為obj物件的原型。 根據語言標準,__proto__屬性只有瀏覽器才需要部署,其他環境可以沒有這個屬性,而且前後的兩根下劃線,表示它本質是一個內部屬性,不應該對使用者暴露。因此,應該儘量少用這個屬性,而是用Object.getPrototypeof()(讀取)和Object.setPrototypeOf()(設定),進行原型物件的讀寫操作。 推薦使用第三種Object.getPrototypeOf方法,獲取原型物件。 Object 物件與繼承 物件本身的屬性之中,有的是可以列舉的(enumerable),有的是不可以列舉的,Object.getOwnPropertyNames方法返回所有鍵名。只獲取那些可以列舉的屬性,使用Object.keys方法。 Object.prototype.hasOwnProperty() 物件例項的hasOwnProperty方法返回一個布林值,用於判斷某個屬性定義在物件自身,還是定義在原型鏈上。
hasOwnProperty方法是JavaScript之中唯一一個處理物件屬性時,不會遍歷原型鏈的方法。 in 運算子和 for…in 迴圈 in運算子返回一個布林值,表示一個物件是否具有某個屬性。它不區分該屬性是物件自身的屬性,還是繼承的屬性。 'length' in Date // true'toString' in Date // true in運算子常用於檢查一個屬性是否存在。 物件的拷貝 如果要拷貝一個物件,需要做到下面兩件事情。
(index) | name | fileExtension |
0 | “JavaScript” | “.js” |
1 | “TypeScript” | “.ts” |
2 | “CoffeeScript” | “.coffee” |
function greet(user) { console.count(); return 'hi ' + user;}greet('bob')// : 1// "hi bob"greet('alice')// : 2// "hi alice"greet('bob')// : 3// "hi bob"
上面程式碼每次呼叫greet函式,內部的console.count方法就輸出執行次數。 該方法可以接受一個字串作為引數,作為標籤,對執行次數進行分類。 dir(),dirxml() dir方法用來對一個物件進行檢查(inspect),並以易於閱讀和列印的格式顯示。
console.log({f1: 'foo', f2: 'bar'})// Object {f1: "foo", f2: "bar"}console.dir({f1: 'foo', f2: 'bar'})// Object// f1: "foo"// f2: "bar"// __proto__: Object
上面程式碼顯示dir方法的輸出結果,比
console.dir(document.body)
dirxml方法主要用於以目錄樹的形式,顯示DOM節點。
console.dirxml(document.body)
如果引數不是DOM節點,而是普通的JavaScript物件,console.dirxml等同於console.dir assert() assert方法接受兩個引數,第一個引數是表示式,第二個引數是字串。只有當第一個引數為false,才會輸出第二個引數,否則不會有任何結果。
console.assert(true === false, '判斷條件不成立')// Assertion failed: 判斷條件不成立
time(),timeEnd() 這兩個方法用於計時,可以算出一個操作所花費的準確時間。
console.time('Array initialize');<span style="white-space:pre"> </span> var array= new Array(1000000);<span style="white-space:pre"> </span> for (var i = array.length - 1; i >= 0; i--) { <span style="white-space:pre"> </span>array[i] = new Object();};<span style="white-space:pre"> </span>console.timeEnd('Array initialize');// Array initialize: 1914.481ms
time方法表示計時開始,timeEnd方法表示計時結束。它們的引數是計時器的名稱。呼叫timeEnd方法之後,console視窗會顯示“計時器名稱: 所耗費的時間”。 profile(),profileEnd()
console.profile方法用來新建一個性能測試器(profile),它的引數是效能測試器的名字。
console.profile('p')// Profile 'p' started.
console.profileEnd方法用來結束正在執行的效能測試器。
console.profileEnd()// Profile 'p' finished.
開啟瀏覽器的開發者工具,在profile面板中,可以看到這個效能偵錯程式的執行結果 group(),groupend(),groupCollapsed() console.group和console.groupend這兩個方法用於將顯示的資訊分組。它只在輸出大量資訊時有用,分在一組的資訊,可以用滑鼠摺疊/展開。
console.group('Group One');<span style="white-space:pre"> </span> console.group('Group Two');// <span style="white-space:pre"> </span> some codeconsole.groupEnd(); // Group Two 結束console.groupEnd(); // Group One 結束
console.groupCollapsed方法與console.group方法很類似,唯一的區別是該組的內容,在第一次顯示時是收起的(collapsed),而不是展開的。 屬性描述物件 Object.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor方法可以讀出物件自身屬性的屬性描述物件。
var o = { p: 'a' };
Object.getOwnPropertyDescriptor(o, 'p')<span style="white-space:pre"> </span>// Object { value: "a",// writable: true,// enumerable: true,// configurable: true// }
上面程式碼表示,使用Object.getOwnPropertyDescriptor方法,讀取o物件的p屬性的屬性描述物件。 Object.defineProperty(),Object.defineProperties() Object.defineProperty方法允許通過定義屬性描述物件,來定義或修改一個屬性,然後返回修改後的物件。它的格式如下。 Object.defineProperty(object, propertyName, attributesObject) 上面程式碼中,Object.defineProperty方法接受三個引數,第一個是屬性所在的物件,第二個是屬性名(它應該是一個字串),第三個是屬性的描述物件。比如,新建一個o物件,並定義它的p屬性,寫法如下。
var o = Object.defineProperty({}, 'p', { value: 123, writable: false, enumerable: true, configurable: false});<span style="white-space:pre"> </span>o.p// 123o.p = 246;o.p// 123// 因為writable為false,所以無法改變該屬性的值
如果一次性定義或修改多個屬性,可以使用Object.defineProperties方法。
var o = Object.defineProperties({}, { p1: { value: 123, enumerable: true }, p2: { value: 'abc', enumerable: true }, p3: { get: function () { return this.p1 + this.p2 }, enumerable:true, configurable:true }});
o.p1 // 123o.p2 // "abc"o.p3 // "123abc"<span style="color:#ff0000;">
</span>
這時需要注意的是,一旦定義了取值函式get(或存值函式set),就不能將writable設為true,或者同時定義value屬性,會報錯。 如果一個屬性的enumerable為false,下面三個操作不會取到該屬性。
- for..in迴圈
- Object.keys方法
- JSON.stringify方法
var o = Object.defineProperties({}, { p1: { value: 1, enumerable: true }, p2: { value: 2, enumerable: false }});Object.getOwnPropertyNames(o)// ["p1", "p2"]
Object.prototype.propertyIsEnumerable() 物件例項的propertyIsEnumerable方法用來判斷一個屬性是否可列舉。 屬性描述物件 概述 JavaScript提供了一個內部資料結構,用來描述一個物件的屬性的行為,控制它的行為。這被稱為“屬性描述物件”(attributes object)。每個屬性都有自己對應的屬性描述物件,儲存該屬性的一些元資訊。 下面是屬性描述物件的一個例項。
{ value: 123, writable: false, enumerable: true, configurable: false, get: undefined, set: undefined}
屬性描述物件提供6個元屬性。 (1)value value存放該屬性的屬性值,預設為undefined。 (2)writable writable存放一個布林值,表示屬性值(value)是否可改變,預設為true。 (3)enumerable enumerable存放一個布林值,表示該屬性是否可列舉,預設為true。如果設為false,會使得某些操作(比如for...in迴圈、Object.keys())跳過該屬性。 (4)configurable configurable存放一個布林值,表示“可配置性”,預設為true。如果設為false,將阻止某些操作改寫該屬性,比如,無法刪除該屬性,也不得改變該屬性的屬性描述物件(value屬性除外)。也就是說,configurable屬性控制了屬性描述物件的可寫性。 (5)get get存放一個函式,表示該屬性的取值函式(getter),預設為undefined。 (6)set set存放一個函式,表示該屬性的存值函式(setter),預設為undefined。 Object.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor方法可以讀出物件自身屬性的屬性描述物件。
var o = { p: 'a' };<span style="white-space:pre"> </span> Object.getOwnPropertyDescriptor(o, 'p')<span style="white-space:pre"> </span>// Object { value: "a",// writable: true,// enumerable: true,// configurable: true// }
上面程式碼表示,使用Object.getOwnPropertyDescriptor方法,讀取o物件的p屬性的屬性描述物件。 Object.defineProperty(),Object.defineProperties() Object.defineProperty方法允許通過定義屬性描述物件,來定義或修改一個屬性,然後返回修改後的物件。它的格式如下。
Object.defineProperty(object, propertyName, attributesObject)
上面程式碼中,Object.defineProperty方法接受三個引數,第一個是屬性所在的物件,第二個是屬性名(它應該是一個字串),第三個是屬性的描述物件。比如,新建一個o物件,並定義它的p屬性,寫法如下。
var o = Object.defineProperty({}, 'p', { value: 123, writable: false, enumerable: true, configurable: false});o.p// 123o.p = 246;o.p// 123// 因為writable為false,所以無法改變該屬性的值
如果屬性已經存在,Object.defineProperty方法相當於更新該屬性的屬性描述物件。 需要注意的是,Object.defineProperty方法和後面的Object.defineProperties方法,都有效能損耗,會拖慢執行速度,不宜大量使用。 如果一次性定義或修改多個屬性,可以使用Object.defineProperties方法。
var o = Object.defineProperties({}, { p1: { value: 123, enumerable: true }, p2: { value: 'abc', enumerable: true }, p3: { get: function () { return this.p1 + this.p2 }, enumerable:true, configurable:true }});o.p1 // 123o.p2 // "abc"o.p3 // "123abc"
上面程式碼中的p3屬性,定義了取值函式get。這時需要注意的是,一旦定義了取值函式get(或存值函式set),就不能將writable設為true,或者同時定義value屬性,會報錯。
var o = {};Object.defineProperty(o, 'p', { value: 123, get: function() { return 456; }});// TypeError: Invalid property.// A property cannot both have accessors and be writable or have a value,
上面程式碼同時定義了get屬性和value屬性,結果就報錯。 Object.defineProperty()和Object.defineProperties()的第三個引數,是一個屬性物件。它的writable、configurable、enumerable這三個屬性的預設值都為false。
var obj = {};Object.defineProperty(obj, 'foo', { configurable: true });Object.getOwnPropertyDescriptor(obj, 'foo')// {// value: undefined,// writable: false,// enumerable: false,// configurable: true// }
上面程式碼中,定義obj物件的foo屬性時,只定義了可配置性configurable為true。結果,其他元屬性都是預設值。 writable屬性為false,表示對應的屬性的值將不得改寫。
var o = {};Object.defineProperty(o, 'p', { value: "bar"});o.p // baro.p = 'foobar';o.p // barObject.defineProperty(o, 'p', { value: 'foobar',});// TypeError: Cannot redefine property: p
上面程式碼由於writable屬性預設為false,導致無法對p屬性重新賦值,但是不會報錯(嚴格模式下會報錯)。不過,如果再一次使用Object.defineProperty方法對value屬性賦值,就會報錯。 configurable屬性為false,將無法刪除該屬性,也無法修改attributes物件(value屬性除外)。
var o = {};Object.defineProperty(o, 'p', { value: 'bar',});delete o.po.p // "bar"
上面程式碼中,由於configurable屬性預設為false,導致無法刪除某個屬性。 enumerable屬性為false,表示對應的屬性不會出現在for...in迴圈和Object.keys方法中。
var o = { p1: 10, p2: 13,};<span style="white-space:pre"> </span>Object.defineProperty(o, 'p3', { value: 3,});for (var i in o) { console.log(i, o[i]);}// p1 10// p2 13
上面程式碼中,p3屬性是用Object.defineProperty方法定義的,由於enumerable屬性預設為false,所以不出現在for...in迴圈中。 元屬性 屬性描述物件的屬性,被稱為“元屬性”,因為它可以看作是控制屬性的屬性。 可列舉性(enumerable) JavaScript的最初版本,in 運算子和基於它的for...in迴圈,會遍歷物件例項的所有屬性,包括繼承的屬性。 var obj = {};'toString' in obj // true 上面程式碼中,toString不是obj物件自身的屬性,但是in運算子也返回true,導致被for...in迴圈遍歷,這顯然不太合理。後來就引入了“可列舉性”這個概念,只有可列舉的屬性,才會被for...in迴圈遍歷,同時還規定原生繼承的屬性都是不可列舉的,這樣就保證了for...in迴圈的可用性。 可列舉性(enumerable)用來控制所描述的屬性,是否將被包括在for...in迴圈之中。具體來說,如果一個屬性的enumerable為false,下面三個操作不會取到該屬性。
- for..in迴圈
- Object.keys方法
- JSON.stringify方法
var car = { id: 123, color: 'red', ownerId: 12};<span style="white-space:pre"> </span>var owner = { id: 12, name: 'Jack'};<span style="white-space:pre"> </span>Object.defineProperty(car, 'ownerInfo', { value: owner, enumerable: false});<span style="white-space:pre"> </span>car.ownerInfo// {id: 12, name: "Jack"}JSON.stringify(car)// "{"id": 123,"color": "red","ownerId": 12}"
上面程式碼中,owner物件作為註釋部分,加入car物件。由於ownerInfo屬性不可列舉,所以JSON.stringify方法最後輸出car物件時,會忽略ownerInfo屬性。 這提示我們,如果你不願意某些屬性出現在JSON輸出之中,可以把它的enumerable屬性設為false。 可配置性(configurable) 可配置性(configurable)決定了是否可以修改屬性描述物件。也就是說,當configurable為false的時候,value、writable、enumerable和configurable都不能被修改了。
var o = Object.defineProperty({}, 'p', { value: 1, writable: false, enumerable: false, configurable: false});Object.defineProperty(o,'p', {value: 2})// TypeError: Cannot redefine property: pObject.defineProperty(o,'p', {writable: true})// TypeError: Cannot redefine property: pObject.defineProperty(o,'p', {enumerable: true})// TypeError: Cannot redefine property: pObject.defineProperties(o,'p',{configurable: true})// TypeError: Cannot redefine property: p
上面程式碼首先定義物件o,並且定義o的屬性p的configurable為false。然後,逐一改動value、writable、enumerable、configurable,結果都報錯。 需要注意的是,writable只有在從false改為true會報錯,從true改為false則是允許的。
var o = Object.defineProperty({}, 'p', { writable: true, configurable: false});Object.defineProperty(o,'p', {writable: false})// 修改成功
至於value,只要writable和configurable有一個為true,就允許改動。
var o1 = Object.defineProperty({}, 'p', { value: 1, writable: true, configurable: false});Object.defineProperty(o1,'p', {value: 2})// 修改成功var o2 = Object.defineProperty({}, 'p', { value: 1, writable: false, configurable: true});Object.defineProperty(o2,'p', {value: 2})// 修改成功
另外,configurable為false時,直接對該屬性賦值,不報錯,但不會成功。
var o = Object.defineProperty({}, 'p', { value: 1, configurable: false});o.p = 2;o.p // 1
上面程式碼中,o物件的p屬性是不可配置的,對它賦值是不會生效的。 可配置性決定了一個變數是否可以被刪除(delete)。
var o = Object.defineProperties({}, { p1: { value: 1, configurable: true }, p2: { value: 2, configurable: false }});delete o.p1 // truedelete o.p2 // falseo.p1 // undefinedo.p2 // 2
上面程式碼中的物件o有兩個屬性,p1是可配置的,p2是不可配置的。結果,p2就無法刪除。 需要注意的是,當使用var命令宣告變數時,變數的configurable為false。
var a1 = 1;Object.getOwnPropertyDescriptor(this,'a1')// Object {// value: 1,// writable: true,// enumerable: true,// configurable: false// }
而不使用var命令宣告變數時(或者使用屬性賦值的方式宣告變數),變數的可配置性為true。
a2 = 1;Object.getOwnPropertyDescriptor(this,'a2')// Object {// value: 1,// writable: true,// enumerable: true,// configurable: true// }// 或者寫成window.a3 = 1;Object.getOwnPropertyDescriptor(window, 'a3')// Object {// value: 1,// writable: true,// enumerable: true,// configurable: true// }
上面程式碼中的this.a3 = 1與a3 = 1是等價的寫法。window指的是瀏覽器的頂層物件。 這種差異意味著,如果一個變數是使用var命令生成的,就無法用delete命令刪除。也就是說,delete只能刪除物件的屬性。
var a1 = 1;a2 = 1;delete a1 // falsedelete a2 // truea1 // 1a2 // ReferenceError: a2 is not defined<strong>
</strong>
可寫性(writable) 可寫性(writable)決定了屬性的值(value)是否可以被改變。
var o = {};Object.defineProperty(o, 'a', { value: 37, writable: false});o.a // 37o.a = 25;o.a // 37
上面程式碼將o物件的a屬性可寫性設為false,然後改變這個屬性的值,就不會有任何效果。 注意,正常模式下,對可寫性為false的屬性賦值不會報錯,只會默默失敗。但是,嚴格模式下會報錯,即使是對a屬性重新賦予一個同樣的值。 關於可寫性,還有一種特殊情況。就是如果原型物件的某個屬性的可寫性為false,那麼派生物件將無法自定義這個屬性。
var proto = Object.defineProperty({}, 'foo', { value: 'a', writable: false});var o = Object.create(proto);o.foo = 'b';o.foo // 'a'
上面程式碼中,物件proto的foo屬性不可寫,結果proto的派生物件o,也不可以再自定義這個屬性了。在嚴格模式下,這樣做還會丟擲一個錯誤。但是,有一個規避方法,就是通過覆蓋屬性描述物件,繞過這個限制,原因是這種情況下,原型鏈會被完全忽視。
Object.defineProperty(o, 'foo', { value: 'b'});o.foo // 'b'
Object.getOwnPropertyNames() Object.getOwnPropertyNames方法返回直接定義在某個物件上面的全部屬性的名稱,而不管該屬性是否可列舉。
var o = Object.defineProperties({}, { p1: { value: 1, enumerable: true }, p2: { value: 2, enumerable: false }});Object.getOwnPropertyNames(o)// ["p1", "p2"]
一般來說,系統原生的屬性(即非使用者自定義的屬性)都是不可列舉的。
// 比如,陣列例項自帶length屬性是不可列舉的Object.keys([]) // []Object.getOwnPropertyNames([]) // [ 'length' ]// Object.prototype物件的自帶屬性也都是不可列舉的Object.keys(Object.prototype) // []Object.getOwnPropertyNames(Object.prototype)// ['hasOwnProperty',// 'valueOf',// 'constructor',// 'toLocaleString',// 'isPrototypeOf',// 'propertyIsEnumerable',// 'toString']
上面程式碼可以看到,陣列的例項物件([])沒有可列舉屬性,不可列舉屬性有length;Object.prototype物件也沒有可列舉屬性,但是有不少不可列舉屬性。 Object.prototype.propertyIsEnumerable() 物件例項的propertyIsEnumerable方法用來判斷一個屬性是否可列舉。 var o = {};o.p = 123;o.propertyIsEnumerable('p') // trueo.propertyIsEnumerable('toString') // false 上面程式碼中,使用者自定義的p屬性是可列舉的,而繼承自原型物件的toString屬性是不可列舉的。 存取器(accessor) 除了直接定義以外,屬性還可以用存取器(accessor)定義。其中,存值函式稱為setter,使用set命令;取值函式稱為getter,使用get命令。 存取器提供的是虛擬屬性,即該屬性的值不是實際存在的,而是每次讀取時計算生成的。利用這個功能,可以實現許多高階特性,比如每個屬性禁止賦值。
var o = { get p() { return 'getter'; }, set p(value) { console.log('setter: ' + value); }};
上面程式碼中,o物件內部的get和set命令,分別定義了p屬性的取值函式和存值函式。定義了這兩個函式之後,對p屬性取值時,取值函式會自動呼叫;對p屬性賦值時,存值函式會自動呼叫。
o.p // "getter"o.p = 123 // "setter: 123"
注意,取值函式Getter不能接受引數,存值函式Setter只能接受一個引數(即屬性的值)。另外,物件也不能有與取值函式同名的屬性。比如,上面的物件o設定了取值函式p以後,就不能再另外定義一個p屬性。 存取器往往用於,屬性的值需要依賴物件內部資料的場合。 利用存取器,可以實現資料物件與DOM物件的雙向繫結。 Object.preventExtensions() Object.preventExtensions方法可以使得一個物件無法再新增新的屬性。 不過,對於使用了preventExtensions方法的物件,可以用delete命令刪除它的現有屬性。 Object.isExtensible() Object.isExtensible方法用於檢查一個物件是否使用了Object.preventExtensions方法。也就是說,檢查是否可以為一個物件新增屬性。
var o = new Object();Object.isExtensible(o) // trueObject.preventExtensions(o);Object.isExtensible(o) <strong>
</strong>
Object.seal() Object.seal方法使得一個物件既無法新增新屬性,也無法刪除舊屬性。 Object.seal實質是把屬性描述物件的configurable屬性設為false,因此屬性描述物件不再能改變了。 同樣是使用了Object.seal方法,如果writable原為false,改變這個設定將報錯;如果原為true,則不會有問題。 Object.isSealed() Object.isSealed方法用於檢查一個物件是否使用了Object.seal方法。 Object.freeze() Object.freeze方法可以使得一個物件無法新增新屬性、無法刪除舊屬性、也無法改變屬性的值,使得這個物件實際上變成了常量。 Object.isFrozen() Object.isFrozen方法用於檢查一個物件是否使用了Object.freeze()方法 prototype 物件 Object.getPrototypeOf() Object.getPrototypeOf方法返回一個物件的原型。這是獲取原型物件的標準方法
// 空物件的原型是Object.prototypeObject.getPrototypeOf({}) === Object.prototype// true// 函式的原型是Function.prototypefunction f() {}Object.getPrototypeOf(f) === Function.prototype// true// f 為 F 的例項物件,則 f 的原型是 F.prototypevar f = new F();Object.getPrototypeOf(f) === F.prototype// true<strong style="font-size: 24px; line-height: 1.5; white-space: pre-wrap;">
</strong>
Object.setPrototypeOf() Object.setPrototypeOf方法可以為現有物件設定原型,返回一個新物件。 Object.setPrototypeOf方法接受兩個引數,第一個是現有物件,第二個是原型物件 Object.create() Object.create方法用於從原型物件生成新的例項物件,可以替代new命令。 它接受一個物件作為引數,返回一個新物件,後者完全繼承前者的屬性,即原有物件成為新物件的原型
var A = { print: function () { console.log('hello'); }};var B = Object.create(A);B.print() // helloB.print === A.print // true
上面程式碼中,Object.create方法在A的基礎上生成了B。此時,A就成了B的原型,B就繼承了A的所有屬性和方法。 下面三種方式生成的新物件是等價的。
var o1 = Object.create({});var o2 = Object.create(Object.prototype);var o3 = new Object();
除了物件的原型,Object.create方法還可以接受第二個引數。該引數是一個屬性描述物件,它所描述的物件屬性,會新增到新物件。
var o = Object.create({}, { p1: { value: 123, enumerable: true }, p2: { value: 'abc', enumerable: true }});// 等同於var o = Object.create({});o.p1 = 123;o.p2 = 'abc';
Object.create方法生成的物件,繼承了它的原型物件的建構函式
function A() {}var a = new A();var b = Object.create(a);b.constructor === A // trueb instanceof A // true
上面程式碼中,b物件的原型是a物件,因此繼承了a物件的建構函式A。 Object.prototype.isPrototypeOf() 物件例項的isPrototypeOf方法,用來判斷一個物件是否是另一個物件的原型。
var o1 = {};var o2 = Object.create(o1);var o3 = Object.create(o2);o2.isPrototypeOf(o3) // trueo1.isPrototypeOf(o3) // true
上面程式碼表明,只要某個物件處在原型鏈上,isProtypeOf都返回true Object.prototype.__proto__ __proto__屬性(前後各兩個下劃線)可以改寫某個物件的原型物件。
var obj = {};var p = {};obj.__proto__ = p;Object.getPrototypeOf(obj) === p // true
上面程式碼通過__proto__屬性,將p物件設為obj物件的原型。 根據語言標準,__proto__屬性只有瀏覽器才需要部署,其他環境可以沒有這個屬性,而且前後的兩根下劃線,表示它本質是一個內部屬性,不應該對使用者暴露。因此,應該儘量少用這個屬性,而是用Object.getPrototypeof()(讀取)和Object.setPrototypeOf()(設定),進行原型物件的讀寫操作。 推薦使用第三種Object.getPrototypeOf方法,獲取原型物件。 Object 物件與繼承 物件本身的屬性之中,有的是可以列舉的(enumerable),有的是不可以列舉的,Object.getOwnPropertyNames方法返回所有鍵名。只獲取那些可以列舉的屬性,使用Object.keys方法。 Object.prototype.hasOwnProperty() 物件例項的hasOwnProperty方法返回一個布林值,用於判斷某個屬性定義在物件自身,還是定義在原型鏈上。
Date.hasOwnProperty('length')// trueDate.hasOwnProperty('toString')// false
hasOwnProperty方法是JavaScript之中唯一一個處理物件屬性時,不會遍歷原型鏈的方法。 in 運算子和 for…in 迴圈 in運算子返回一個布林值,表示一個物件是否具有某個屬性。它不區分該屬性是物件自身的屬性,還是繼承的屬性。 'length' in Date // true'toString' in Date // true in運算子常用於檢查一個屬性是否存在。 物件的拷貝 如果要拷貝一個物件,需要做到下面兩件事情。
- 確保拷貝後的物件,與原物件具有同樣的prototype原型物件。
- 確保拷貝後的物件,與原物件具有同樣的屬性。
function copyObject(orig) { var copy = Object.create(Object.getPrototypeOf(orig)); copyOwnPropertiesFrom(copy, orig); return copy;}function copyOwnPropertiesFrom(target, source) { Object .getOwnPropertyNames(source) .forEach(function(propKey) { var desc = Object.getOwnPropertyDescriptor(source, propKey); Object.defineProperty(target, propKey, desc); }); return target;}