前言
JavaScript發明之始,從技術上來講就是一門面向對象的語言,但在ES6之前,JS的很多特性和傳統的面嚮對象語言有所不同,比如沒有類的概念(ES6有了class)。今天結合《JS高編》第六章開始回顧和深入學習面向對象部分,包括對象、原型、原型鏈、繼承等部分。
一、理解對象
談JS的對象之前,先複習一下面向對象的基礎概念和特點吧。
面向對象OOP(Object-oriented programming),結合維基百科和百度百科的闡述,再談談我的理解。
官方解釋:
面向對象就是基於對象概念,以對象為中心,以類和繼承為構造機制,來認識、理解、刻畫客觀世界和設計、構建相應的軟件系統
我的理解:
在JavaScript的世界中,萬物皆對象。任何事和物你都可以將其定義為一個對象,程序員界有個笑話就是單身狗可以new一個對象嘛......我的粗淺理解,如果我是一個上帝,這個世界的任何人和事相對於我而言都是一個對象。有了控制對象的權力,我就可以對他們進行任何操作。針對事,我可以發佈一個號令,發佈一個政策,告訴別人怎麼執行,什麼時候開始,什麼時候結束。針對人,我可以把他們分為男人、女人,這就是類。然後我可以限制他們的兒子是男人還是女人,是男人那就必須有和爸爸一樣的性別特徵,這就是繼承。我還可以控制他們什麼時間做什麼事等等,整個過程我都是圍繞某個對象來展開的,那麼這個過程叫做面向對象。
特點:
1.類
2.繼承
3.封裝
4.多態
具體的在後面學習和複習時再談。
二、對象的屬性類型
let obj = {
name:"勾鑫宇",
age:23
}
1.數據屬性:[[Configurable]],[[Enumerable]],[[Writable]],[[Value]]
2.訪問器屬性:[[Configurable]],[[Enumerable]],[[Get]],[[Set]]
書上講到屬性類型時,只是簡單提了一下是為了表示對象的特性,描述了屬性的特徵,並且在JS中不能直接訪問。光看介紹不太理解到底是幹什麼的,但是看了數據屬性的內容之後,發現不難理解。
我的理解,數據屬性就是我們可以從根源去控制一個對象的屬性是否能被修改、刪除、循環等,並可以通過訪問器屬性在別人不知道的情況下進行數據處理。通過Object.defineProperty()這個方法,我們可以去設置這些限制對象屬性操作的值,從而限制別人對某個對象屬性的操作。舉個例子,上面的obj這個對象的name屬性的值是“勾鑫宇”,從現在起我不想任何人能夠修改它的值,那麼我就通過數據屬性來將這個屬性設置為不可修改,別人用obj.name = "張三"來修改就不會生效了。而我如果想在修改name屬性的值後同時讓age也跟着改變,那麼此時就可以用訪問器屬性來進行數據處理。
我們是通過Object.defineProperty()這個方法來進行兩種屬性的設置。那麼首先了解一下Object.defineProperty()這個方法,它接收三個參數:
Object.defineProperty(對象名,屬性名,描述符對象)
//舉例
Object.defineProperty(obj,"name",{
writable:false,//設置不可修改
enumerable:false//設置不可循環到該屬性
})
可以在對象的constructor中找到該方法
同時,我們可以通過Object.getOwnPropertyDescriptor()方法來查看這四個特性的設置情況。接受兩個參數:
Object.getOwnPropertyDescriptor(對象名,屬性名)
數據屬性
數據屬性包含一個數據值的位置。在這個位置可以讀取和寫入值,有4個描述其行為的特性。
下面就具體來對每個數據屬性進行分析:
1.[[Writable]]:英文意思譯為“可寫的”,可理解為“可修改的”。這個屬性用來設置對象的某個屬性是否能被修改,默認為true。
//舉例
let obj = {
name:"勾鑫宇",
age:23
}
Object.defineProperty(obj,"name",{
writable:false,//設置不可修改
})
//這時再進行修改就不會生效,嚴格模式下會報錯
obj.name = "張三"
console.log(obj.name)//輸出的還是勾鑫宇
嚴格模式報錯
2.[[Enumerable]]:英文譯為“可數的,可枚舉的”,是否支持for-in循環來返回屬性,默認為true。
//舉例
let obj = {
name:"勾鑫宇",
age:23,
gender:male
}
Object.defineProperty(obj,"name",{
enumerable:false,//設置不可通過for-in循環返回
})
//循環測試
for(let i in obj){
console.log(i)//輸出結果為age,gender,沒有name屬性,效果就像隱藏了這個屬性。
}
//但這時我們的name屬性還是存在的
console.log(obj)
3.[[value]]:這個就不説翻譯了,大家都知道,就是值。這個特性是設置我們對象某個屬性的值,讀值、寫值都在這裏,默認值為undefined。
//舉例
let obj = {
name:"勾鑫宇",
age:23,
gender:male
}
Object.defineProperty(obj,"name",{
value:"張三",//設置name的值為張三
})
console.log(obj.name)//輸出為張三
//設置value不影響後面再次修改值,value相當於修改了一次你最先定義的值而已。
obj.name = "傻逼"
concole.log(obj.name)//輸出為“傻逼”
4.[[Configurable]]:英文譯為“可配置的”,這個和前面的Writable有什麼區別呢?放到最後講是有原因的。前面有設置修改,設置循環,設置值,但是還沒有設置是否可刪除。Configurable就是做這個事情的。它表示能否通過delete刪除屬性從而重新定義屬性,默認值為true。
//舉例
let obj = {
name:"勾鑫宇",
age:23,
gender:male
}
Object.defineProperty(obj,"name",{
configurable:false,//不允許刪除屬性
})
delete obj.name//報錯"Uncaught TypeError: Cannot delete property 'name' of #<Object>"
這個屬性還有最重要的一個特點,就是當你設置為false過後,就不能再設為true了,即使你設置了也無效。書上説得個時候你再設置value,enumerable都不會生效,只能設置writable,那麼我們來試試。
//接着上面再把configurable修改為true
Object.defineProperty(obj,"name",{
configurable:true,
})
//此時為會報錯“Uncaught TypeError: Cannot redefine property: name”
//接着再次調用
Object.defineProperty(obj,"name",{
value:'張三'
})
console.log(obj)//此時打印出來的是“張三”,而並書上所説的不能修改value的值。
//設置enumerable
Object.defineProperty(obj,"name",{
enumerable:false//報錯"Uncaught TypeError: Cannot redefine property: name"
})
測試了很多遍,value值在configurable為false的情況下仍然是可以修改的。
//設置writable
Object.defineProperty(obj,"name",{
writable:false//不會報錯
})
//再次修改writable
Object.defineProperty(obj,"name",{
writable:true//報錯“Uncaught TypeError: Cannot redefine property: name”
})
//修改value
Object.defineProperty(obj,"name",{
value:"張三"//報錯“Uncaught TypeError: Cannot redefine property: name”
})
上面設置writable説明在configurable和writable同時為false的情況下,就不能再修改任何值了。
Configurable還能控制是否能修改為訪問器屬性,這個在訪問器屬性的時候再講。
訪問器屬性
訪問器屬性不包含數據值,包含一對getter和setter函數,讀取訪問器屬性時調用getter,寫入時調用setter,並負責處理數據。
訪問器屬性同樣有4個特性值可以設置:
1.[[Configurable]]:和數據屬性的功能一樣,只是有一點區別就是能否修改為數據屬性。
2.[[Enumerable]]:和數據屬性的功能一樣。
3.[[Get]]:讀取屬性時調用,默認值為undefined。
get函數就是能夠讓你讀取對象中的某個屬性,前提是這個屬性本身是隻能通過對象方法來訪問的,也就是説定義時要有下劃線記號,否則本身就能直接訪問的話,用get也沒有意義了。
//舉例
let obj = {
_name:"勾鑫宇",//下劃線是一種記號,表示只能通過對象方法訪問
age:23,
gender:male
}
console.log(obj.name)//輸出為undefined
//用get方法來讀取這個屬性,並返回給對象
Object.defineProperty(obj,"name",{
get(){
return this._name
}
})
console.log(obj.name)//輸出“勾鑫宇”
4.[[Set]]:set函數就是寫入屬性的時候調用,默認值為undefined。
set函數會接收一個參數,這個參數就是我們修改對象或添加對象的屬性值。
//舉例
let obj = {
_name:"勾鑫宇",
age:23,
gender:male
}
//用set方法來寫入這個屬性
Object.defineProperty(obj,"name",{
set(val){
this._name = 'hh'+val
}
})
obj.name = "張三"
console.log(obj.name)//輸出為undefined
這個時候我們就沒法進行下去了,因為無論怎樣,都是輸出undefined。原因就是因為我們沒有使用get函數去讀取我們寫入的屬性值,記住name在初始定義時就必須是_name。
//同時使用get和set
let obj = {
_name:"勾鑫宇",
age:23,
gender:male
}
//用set方法來寫入這個屬性
Object.defineProperty(obj,"name",{
get(){
return this._name
},
set(val){
this. = 'hh'+val
}
})
obj.name = "張三"
console.log(obj.name)//輸出'hh張三'
從上面的代碼就可以看出這個set和get函數的強大之處,那就是可以進行數據處理。我們可以通過set函數來進行對象裏不同屬性的關聯,也可以實現屬性值的各種計算。
//舉例
let obj = {
_name:"勾鑫宇",
age:23,
gender:male
}
//用set方法來進行不同屬性間的關聯
Object.defineProperty(obj,"name",{
get(){
return this._name
},
set(val){
if(val === '張三'){
this._name = val;
this.age = 18
}
}
})
//修改屬性值
obj.name = '張三';
console.log(obj)//輸出 {name:'張三',age:18,gender:male}
除了Object.defineProperty()方法,還有Object.defineProperties()方法,顧名思義,複數形式就是可以同時定義多個屬性。它接受兩個參數:
Object.defineProperties(obj,{
name:{
writable:false
},
age:{
configurable:true
}
...
})
這就是對屬性類型的一個學習和理解,如有錯誤,請使勁點我。