基石:ES5基礎(二) 對象& 屬性特徵

閒白

我認爲ES語言之因此強大,有兩個方面。node

第一方面就是真正的面嚮對象語言原型設計理念(真香),可以使ES在衆多面向對象語言中可以脫穎而出。編程

第二方面就是函數式編程繼承人,經過強大的ES的函數使得他可以更加靈活,高效。數組

在我還未涉足Vue,React以及node.js以前,有必要完全梳理一下關於ES對象的各類特色,trick,以及坑。避免到時候翻閱源碼時候迷失自我,風中凌亂...bash

我知道,還差的太多,因此各位大佬!必定要給我補充。感謝!感謝!感謝!

首先本文總結一下對象的特色,以及一部分方法。其中還有一些對象的方法並未剖析,部分會移步 原型 篇總結中。框架

閒言少敘,開始今天的正文函數式編程

扔出問題:

有了解過[[Put]] [[Get]] [[Set]] [[delete]]內部屬性嗎?函數

判斷屬性&對象的關係 和 遍歷對象一般有哪些方法?post

講一下你對屬性特徵的理解?性能

如何設置一個對象爲禁止修改ui

[[Put]] [[Get]] [[Set]] [[Delete]]

添加 ([[Put]])

當咱們給對象添加一個屬性時,其實ES隱式的調用了[[Put]]內部方法。就像這樣

var spy = {};
spy.age = 23; // 調用[[Put]]方法
複製代碼

這個操做不只建立了對象的自有屬性age,而且指定值爲23,同時也定義了一些屬性特徵(見下文)。

實際上觸發[[Put]]時,他的行爲會取決於不少因素。例如對象中是否存在這個屬性。

若是已存在這個屬性,[[Put]]會大體檢查以下內容。

  1. 屬性是不是訪問器,若是是而且存在setter就會調用setter
  2. 屬性的數據特徵中wirtable屬性是否爲false?若是是,在非嚴格模式下靜默,嚴格模式下拋錯
  3. 設置該值

若是對象不存在該值,[[Put]]會更加複雜,咱們在下一章 原型 會詳細剖析。

獲取 ([[Get]])

當咱們進行屬性訪問時,在ES規範中明確表示,當進行屬性訪問時,實際是隱式的調用[[Get]]方法。

[[Get]]操做首先會檢查對象中是否存在目標屬性,若是找到就會返回這個屬性。

若是沒有找到,他頗有可能會遍歷整個原型鏈。若是仍是沒有找到屬性,那麼[[Get]]操做就會返回undefined

實際上在底層[[Get]]操做對屬性獲取進行了更復雜的處理。

修改 ([[Set]])

當咱們給修改對象已經存在的屬性時,ES會隱式的調用內部[[Set]]方法

var spy = {};
spy.currently = weak;
spy.use = Red Bull;
spy.currently = "硬拉500lb" // 調用[[Set]]方法
複製代碼

這個操做會將一個屬性當前的值進行替換。

刪除 ([[Delete]])

當須要刪除對象一個屬性時,咱們一般是調用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

基石-ES5基礎(一)數據類型&類型轉換/判斷

所以咱們經過這種方式沒法斷言屬性的存在性,一般咱們使用in操做符來判斷該屬性是否存在對象中,實際上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循環

一般使用for-in循環遍歷可枚舉屬性。所謂可枚舉屬性就是內部特徵設置[[Enumberable]]true。一般狀況下[[Enumberable]]默認值爲true(除特殊狀況,見下文).

Object.keys

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]]:表示是否能夠配置該屬性

當設置爲不可配置後,其特色能夠總結爲:

  1. 該屬性不能夠被刪除
  2. 該屬性值不能夠被修改
  3. 該屬性只讀
  4. 該屬性沒法將[[Configurable]]再次設置爲true

Object.defineProperty()設置特徵屬性

一般咱們使用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]]

內部特徵[[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()封印的特色能夠總結爲:

  1. 沒法添加新屬性
  2. 沒法刪除屬性
  3. 沒法改變其屬性類型(數據屬性和訪問器屬性)

該方法調用後,[[Extensible]]特徵設置爲false;同時全部屬性的[[Configurable]]特徵屬性被設置爲false。

  • 如何實現一個seal()?

對象凍結

建立不能夠拓展對象的最後一種方法是凍結他。可使用Object.freeze()來凍結一個對象,使用Object.isFrozen()來判斷一個對象是否被凍結。

使用freeze()來凍結一個對象的特色能夠總結爲:

  1. 不能添加或刪除屬性
  2. 不能改變屬性類型
  3. 不能寫入任何數據屬性
  4. 全部屬性數據屬性都爲只讀

當一個對象凍結以後,被凍結的對象一般也被認爲是不能夠拓展對象和被封印對象。使用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.

最後,我相信不少哥哥對開篇提出來的問題早就一目瞭然,只不過是我幫您加深了一下印象。

若是本篇文章您以爲有什麼地方有問題,必定要給我指正。若是您以爲還不錯,而且期待後續文章,點贊關注走一波~

下篇文章咱們來好好總結一下對象的原型,敬請期待!

遺留問題

  • 如何實現一個seal()?
  • 如何實現一個freeze()?
  • 禁止修改對象在工業界的用處是什麼?
  • 屬性特徵在各大框架中的使用
  • 爲何要這樣設計[[Put]] [[Set]],底層是怎麼實現的?他們到底有什麼用?是爲了配合原型嗎?

參考書籍

《紅寶書》- Zakas

《JavaScript面向對象精要》 - Zakas

《你不知道的js (上) 》

相關文章
相關標籤/搜索