前言
相信對於對象屬性大家都或多或少的知道一些,那麼本文從屬性說開去,看看大家對屬性的瞭解是否有遺漏的部分。
屬性的定義與使用
也許你覺得定義屬性很簡單啊,我直接.prop = xxx,就可以定義個對象了啊,從未深入瞭解,這在大多數情況下沒有任何問題。但在某些情況下就不夠用了。
我們知道的使用方式是這樣的:
let param = { id:'', number:13 } param.query = 'sfwefw' param['say']= () => { console.log(111) } console.log(param.number)
那麼我們先追本溯源看下對象定義屬性官方的玩法吧。 官方對屬性分爲兩種,一種是數據屬性,另一種訪問器屬性。(這些屬性值爲了區別於我們理解的普通屬性,我們用兩對括號體現)
簡單表格統計下他們的特徵
屬性 | 內容 | 特徵 |
---|---|---|
數據屬性 | configurable,enumerable,writable,value | 其中123均爲布爾型,默認爲true,分別代表可刪除、可枚舉、可修改,第四個爲true |
訪問器屬性 | configurable,enumerable,getter,setter | 後面兩個是非必須的 |
雖然似乎說的很明白,但還是一臉懵逼,感覺對自己沒什麼影響啊。那麼幹貨來了,我通過幾個經典的高頻點來延伸的幫助大家理解這部分。
for in 循環遍歷的屬性
作爲經常使用對象的我們,想必對這個語法並不陌生,雖然我們一般情況下很少直接這樣用,因爲更多業務場景下是屬性的精準使用,不會通過循環的方式,原因有以下幾個方面。當然你可以跳過這部分。
1 如果默認使用屬性循環來展示數據,有很多不必要展示的數據都要過濾篩選掉,比較低效麻煩 2 屬性的循環訪問不一定符合我們需要展示的順序,這點纔是致命的,導致我們在業務需要的時候更多的時候是固定順序固定訪問對象屬性 3 如果對對象屬性期望按照順序,會大大的增加數據改造的成本,增加不可複用的解耦成本
回到正文,重頭戲來了,作爲常識需要瞭解到兩點。
第一點,for in循環可以訪問到對象具有的所有可枚舉屬性; 第二點 對象具有的屬性可能是多來源的,可能是自己新建的,可能是構造函數新建的,可能是來源於構造函數的繼承;可能是來源於原型,可能是來源於原型式的繼承。其中我們可以通過hasOwnProperty來判斷這個屬性是否是自有屬性(構造函數來的是判斷不出的)。
構造函數得到的屬性以及基本屬性賦值
//正常的構造函數以及對象屬性賦值,call .apply構造函數繼承方式的屬性都可以正常獲取,並且屬於對象自有屬性 let Animal = function (){ this.bigtype = 'animal' } let Person = function () { this.name = '人類' this.sex = '男女' Animal.call(this) } let zhangsan = new Person('zhangsan','male') zhangsan.type = 'animal' for(let p in zhangsan){ console.log(zhangsan.hasOwnProperty(p),`${p}:${zhangsan[p]}`) }
特別說明:爲什麼構造函數之後對象的屬性都是自有屬性呢? 這個要和new關鍵字有關了,其關鍵的四個步驟是創建新的對象,然後構造函數的作用域指向新對象(this指向新對象),執行構造函數中的代碼,返回新對象。所以自然通過this賦值的都是新對象的屬性了。
原型鏈方法賦值以及原型鏈繼承方式
無論是通過原型修改屬性還是原型鏈繼承的其他原型,其均不屬於對象自己,均是向上追溯的原型對象的,所以hasOwnProperty均爲false.
需要注意的是 :1 如果你需要繼承其他原型,又需要修改原型的某個值,要先繼承在修改值,不然你修改的值就丟失了。2 繼承原型要在實例化對象之前,寫在調用之前是無效的。
let Animal = function (){ this.bigtype = 'animal' } let Person = function () { this.name = '人類' this.sex = '男女' // Animal.call(this) } Person.prototype = new Animal() Person.prototype.belong = 'zhang' let zhangsan = new Person('zhangsan','male') for(let p in zhangsan){ console.log(zhangsan.hasOwnProperty(p),`${p}:${zhangsan[p]}`) }
參考代碼
還什麼方法可以拿到屬性
沒錯,我們一般情況下使用for,in循環獲取屬性,但有些屬性我們也希望得到。通過上面的for in的例子,你可以通過for in +hasOwnProperty 的方式得到對象可枚舉非原型屬性以及可枚舉原型屬性。那麼還有其他方法麼?肯定有的。下面進行表格說明。
方法 | 內容 | 備註 |
---|---|---|
for in | 可枚舉,自身以及繼承屬性 | 對象以及繼承,可枚舉,不含 Symbol 屬性 |
Object.keys(obj) | 返回一個數組,包括對象自身的(不含繼承的)所有可枚舉屬性鍵名 | 對象自身可枚舉,不含 Symbol 屬性 |
Object.getOwnPropertyNames(obj) | 返回一個數組,包含對象自身的所有屬性(不含 Symbol 屬性,但是包括不可枚舉屬性)的鍵名 | 對象自身,包括不可枚舉屬性 |
Object.getOwnPropertySymbols(obj) | 返回一個數組,包含對象自身的所有 Symbol 屬性的鍵名 | 對象自身,symbol |
Reflect.ownKeys(obj) | 返回一個數組,包含對象自身的所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉。 | 對象自身,全部屬性 |
屬性中的this是什麼
來源 | 指向 |
---|---|
對象 | 對象自身 |
構造函數 | 返回新對象 |
原型 | 原型 |
純函數調用 | 外部環境全局,瀏覽器或者node |
訪問器get,set使用
一般我們也用不到這個,但vue的數據雙向綁定就是基於這個實現的,其在data屬性中定義的數據,全部對其屬性的屬性定義中追加了虛擬dom的事件,所以能夠實現雙向綁定。也正因爲這個屬性的兼容問題,導致了vue不支持ie低版本哦。
參考源碼:vue框架使用get,set源碼地址
Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify() } }) }