我認爲ES語言之因此強大,有兩個方面。node
第一方面就是真正的面嚮對象語言
原型設計理念(真香),可以使ES在衆多面向對象語言中可以脫穎而出。編程
第二方面就是函數式編程
繼承人,經過強大的ES的函數使得他可以更加靈活,高效。數組
在我還未涉足Vue,React以及node.js以前,有必要完全梳理一下關於ES對象的各類特色,trick,以及坑。避免到時候翻閱源碼時候迷失自我,風中凌亂...bash
首先本文總結一下對象的特色,以及一部分方法。其中還有一些對象的方法並未剖析,部分會移步 原型 篇總結中。框架
閒言少敘,開始今天的正文。函數式編程
有了解過[[Put]] [[Get]] [[Set]] [[delete]]
內部屬性嗎?函數
判斷屬性&對象的關係 和 遍歷對象一般有哪些方法?post
講一下你對屬性特徵
的理解?性能
如何設置一個對象爲禁止修改?ui
當咱們給對象添加一個屬性時,其實ES隱式的調用了[[Put]]內部方法。就像這樣
var spy = {};
spy.age = 23; // 調用[[Put]]方法
複製代碼
這個操做不只建立了對象的自有屬性age
,而且指定值爲23,同時也定義了一些屬性特徵(見下文)。
實際上觸發[[Put]]
時,他的行爲會取決於不少因素。例如對象中是否存在這個屬性。
若是已存在這個屬性,[[Put]]
會大體檢查以下內容。
setter
就會調用setter
。wirtable
屬性是否爲false
?若是是,在非嚴格模式下靜默,嚴格模式下拋錯若是對象不存在該值,[[Put]]
會更加複雜,咱們在下一章 原型 會詳細剖析。
當咱們進行屬性訪問時,在ES規範中明確表示,當進行屬性訪問時,實際是隱式的調用[[Get]]
方法。
[[Get]]
操做首先會檢查對象中是否存在目標屬性,若是找到就會返回這個屬性。
若是沒有找到,他頗有可能會遍歷整個原型鏈。若是仍是沒有找到屬性,那麼[[Get]]
操做就會返回undefined
。
實際上在底層[[Get]]
操做對屬性獲取進行了更復雜的處理。
當咱們給修改對象已經存在的屬性時,ES會隱式的調用內部[[Set]]
方法
var spy = {};
spy.currently = weak;
spy.use = Red Bull;
spy.currently = "硬拉500lb" // 調用[[Set]]方法
複製代碼
這個操做會將一個屬性當前的值進行替換。
當須要刪除對象一個屬性時,咱們一般是調用delete
方法進行屬性刪除,由於將屬性值設置爲null
並不能從對象中完全移除他,只是調用[[Set]]
將原來的值替換爲null
。
var spy = {
age : 23,
grade : 「大三」,
}
age = null;
delete age.grade;
console.log("age" in spy); //true;
console.log("grade" in spy); // false;
複製代碼
delete操做符針對單個對象屬性調用[[delete]]
的內部方法。能夠理解爲在哈希表中刪除一個鍵值對,不過要注意有些屬性沒法刪除(下文會說到,封印和凍結)
當在開發過程當中,是否有這樣判斷過屬性是否存在?
if(spy.age){
// do something
}
複製代碼
這樣是有隱患的,看過上一篇文章的朋友都知道在條件判斷時,當屬性的值爲null undefined 0 false NaN或者' '
其中任何一個時候都會形成判斷結果爲false
。
所以咱們經過這種方式沒法斷言屬性的存在性,一般咱們使用in
操做符來判斷該屬性是否存在對象中,實際上in
操做符就是檢查哈希表中是否存在一個鍵。
使用in操做符來判斷一個屬性是否存在時,他的好處主要體如今減少性能損耗上。是由於它只判斷屬性是否存在,而不會評估屬性的值。
可是in
操做符不只會檢查本身的屬性,還會檢查原型鏈的屬性。所以當咱們須要判斷是對象本身的屬性時,咱們一般使用hasOwnProperty()
來進行配合。
例如
var spy = {
age : 23,
grade : 「大三」,
}
console.log("age" in spy); // true;
console.log("toString" in spy); // true;
spy.hasOwnProperty("age"); // true;
spy.hasOwnProperty("toString"); // false;
複製代碼
一般使用for-in循環遍歷可枚舉屬性。所謂可枚舉屬性就是內部特徵設置[[Enumberable]]
爲true
。一般狀況下[[Enumberable]]
默認值爲true
(除特殊狀況,見下文).
ES5新增了Object.keys
方法。他能夠獲取可枚舉屬性的名字的數組。
用例你們都太熟了,簡單手寫一下Object.keys
方法(只是爲了表示該方法的含義)。
function keys (object){
var result = [];
for(var key in object){
if(object.hasOwnProperty(key){
result[key] = object[key];
})
}
return result;
}
複製代碼
經過簡單模擬的keys函數能夠理解Object.keys
方法和for-in
循環的區別:
Ojbcet.keys將鍵值對輸出成數組形式,同時不會遍歷原型鏈。
在ES5以前,咱們沒法控制一個屬性是否可枚舉的,其實是根本沒有辦法訪問屬性的任何內部特徵。
爲了使ES可以更增強大,在ES5中添加了屬性特徵來支持額外的功能。
簡單的來講分爲通用特徵、數據屬性特徵以及訪問器屬性特徵。
請注意:咱們沒法同時對一個屬性設置數據屬性特徵和訪問器屬性特徵
通用特徵是數據和訪問器屬性都具備的特徵
[[Enumberable]]
:是否能夠遍歷到該屬性
[[Configurable]]
:表示是否能夠配置該屬性
當設置爲不可配置後,其特色能夠總結爲:
[[Configurable]]
再次設置爲true
一般咱們使用var test = new Object()
建立對象時,其特徵屬性默認都是true
。但若是咱們使用Object.defineProperty()設置屬性特徵時,默認屬性特徵值爲false
。
Object.defineProperty(object,"prop",{
enumberable: false;
//configurable-->false;
})
複製代碼
所以咱們在使用Object.defineProperty()
設置屬性特徵時,須要將其全部特徵屬性指定一個值。即便想更改他屬性特徵值爲flase
,我認爲仍是要顯示的指明,這樣可以增長代碼的可讀性。
一樣Object.defineProperties()
能夠爲一個對象設置同時定義多個屬性,惟一不一樣的是在接受參數上有細微調整,在此不作贅述。
使用Object.PropertyDescriptor()
方法獲取自有屬性特徵。
一般咱們默認建立的對象就是數據屬性的對象,數據屬性擁有兩個私有特徵。
[[Value]]
:任何屬性的值都保存在[[Value]]
中,哪怕值是一個函數。
[[Writable]]
:指示屬性是否可寫。
若是使用Object.defineProperty()
方法建立一個數據屬性時,即便該屬性不存在也能夠。
var spy = {};
Object.deineProperty(spy,"name",{
value: "S!",
writable: true,
enumberable : true,
configurable : true,
})
複製代碼
一般這樣作是不必的,咱們能夠直接定義對象的屬性。但若是你但願該對象是不能夠寫的,你會須要這樣操做。
例如上文說到的當[[Put]]
內部方法在調用時,其實是定義了其屬性的特徵爲數據屬性,將屬性值設置在[[value]]
屬性中。
訪問器屬性不須要存儲值,而是操做值的訪問和設置。能夠理解成設置一層代理函數(也可能夠理解爲攔截函數),同時訪問器屬性也有兩個私有特徵。
[[Get]]
: 對讀取屬性進行額外操做的函數
[[Set]]
: 對設置屬性進行額外操做的函數
var spy = {
_sex : male,
};
Object.defineProperty(spy,"sex",{
get : function () {
console.log("獲取spy的性別");
return this.name;
}
set : function () {
console.log("spy不能變性");
return "scram!";
}
})
console.log(spy.sex) //調用get函數
console.log(spy.sex == "feme"); //調用set函數
複製代碼
注意當咱們訪問設置了訪問器屬性特徵的屬性時,會執行get()
和set()
。
在使用訪問器屬性特徵時,依然遵循默認值爲false
。所以若是隻定義get()
或 只定義set()
,代表該屬性是隻讀的 或 只寫的。
一般建立的對象默認都是能夠拓展的,意味着新的屬性能夠隨時被添加。一般有三種方法可以幫助咱們鎖定對象的屬性。
內部特徵[[Extensible]]
是一個布爾值,他表示該對象自己是否能夠被修改。咱們能夠經過使用Object.preventExtensions( )
建立一個不能夠擴展的對象。該方法接受一個參數,是你但願使其變爲不可拓展的對象。
var spy = {
age : 23,
grade : 「大三」,
}
console.log(Object.preventExtensions(spy)); // true;
spy.health = strong;
console.log("health" in spy) // false;
console.log(Object.isExtensible(spy)); // false;
複製代碼
一旦在對象上使用Object.preventExtensions( )
方法,就永遠不能再給他添加新的屬性。
可使用Object.isExtensible
來檢查[[Extensilbe]]
屬性的值
封印是建立不能夠擴展對象的第二種方法。一旦對象封印後,其全部屬性都不能夠配置,但能夠讀寫他的屬性。經過使用Object.seal()
來封印一個對象,使用Object.isSealed()
判斷對象是否被封印。
使用seal()
封印的特色能夠總結爲:
該方法調用後,[[Extensible]]
特徵設置爲false
;同時全部屬性的[[Configurable]]
特徵屬性被設置爲false。
建立不能夠拓展對象的最後一種方法是凍結他。可使用Object.freeze()
來凍結一個對象,使用Object.isFrozen()
來判斷一個對象是否被凍結。
使用freeze()
來凍結一個對象的特色能夠總結爲:
當一個對象凍結以後,被凍結的對象一般也被認爲是不能夠拓展對象和被封印對象。使用Object.isExtensible()
返回false
,同理使用Object.isSealed()
返回true
.
經過以上三種方式禁止修改對象,只是建立了淺不變性。隻影響目標對象和他的直接關係(也就是沒法修改指針指向),可是咱們能夠直接修改目標對象,例如
var obj = {
a : 1,
arr : [1,2,3]
}
Object.freeze(obj);
obj.arr.push(1);
console.log(obj.arr); // [1,2,3,1];
obj.a = 2;
console.log(obj.a); // 1;
複製代碼
使用Object.isExtensible()
返回false
,同理使用Object.isSealed()
返回true
.
若是本篇文章您以爲有什麼地方有問題,必定要給我指正。若是您以爲還不錯,而且期待後續文章,點贊關注走一波~
《紅寶書》- Zakas
《JavaScript面向對象精要》 - Zakas
《你不知道的js (上) 》