1.給 JavaScript 對(duì)象添加屬性
在 JavaScript
中給對(duì)象添加屬性,很平常的一件事情.
let obj = {}
obj.name = '李四'
obj.sayHi = function () {
console.log(`${this.name} SayHi~`)
}
obj.hobbies = ['看書','打游戲']
這沒(méi)什么大不了的,JS
本身就是一個(gè)動(dòng)態(tài)語(yǔ)言,可以非常自由的給對(duì)象添加一些屬性.
且屬性可以是任意的 JS
數(shù)據(jù)對(duì)象.
2.介紹 Object.defineProperty
因?yàn)槲覀冎?code>JavaScript的動(dòng)態(tài)特性,可以很簡(jiǎn)單的給 JS.obj
添加屬性.
但是為毛又要多出來(lái)一個(gè) Object.defineProperty
呢?
為什么總喜歡把簡(jiǎn)單的事情復(fù)雜化呢????
我們先來(lái)看看 Object.defineProperty
方法在 MDN
上的定義.
Object.defineProperty() 方法會(huì)直接在一個(gè)對(duì)象上定義一個(gè)新屬性,或者修改一個(gè)對(duì)象的現(xiàn)有屬性, 并返回這個(gè)對(duì)象搪花。
語(yǔ)法:
Object.defineProperty(obj, prop, descriptor)
Object.defineProperty(obj,prop,descriptor)參數(shù)說(shuō)明
-
obj
需要定義屬性的對(duì)象 -
prop
定義的屬性名稱 -
descriptor
被定義的屬性描述(此參數(shù)是一個(gè)對(duì)象)-
value
:設(shè)置到屬性的值 -
writable
: 設(shè)置屬性是否可寫,boolean
類型,默認(rèn)值false
-
configurable
: 設(shè)置屬性是否可以配置,boolean
類型,默認(rèn)值false
.主要有兩個(gè)功能- 設(shè)置屬性是否可以被
delete
刪除 - 設(shè)置屬性是否可以被再次配置.
- 設(shè)置屬性是否可以被
-
enumerable
: 設(shè)置屬性是否可以被for in
&Object.keys()
枚舉出來(lái).默認(rèn)值是false
-
-
set
: 給屬性賦值的setter
-
get
: 獲取屬性值的getter
3. 使用 Object.defineProperty 來(lái)給對(duì)象設(shè)置屬性
讓我們暫時(shí)忘記JavaScript
方便的令人發(fā)指的屬性增加語(yǔ)法,來(lái)使用蹩腳
的 Object.defineProperty
給對(duì)象賦值
let obj = {}
// String
Object.defineProperty(obj, 'name', {
value: '李四'
})
// Number
Object.defineProperty(obj, 'age', {
value: 22
})
// Object
Object.defineProperty(obj, 'carInfo', {
value: {
carBrand: '寶馬',
carNumber: '京A12345',
toString() {
return `汽車品牌:${this.carBrand}
車牌號(hào):${this.carNumber}
`
}
}
})
// Array
Object.defineProperty(obj, 'hobbies', {
value: ['看書', '玩游戲']
})
// Function
Object.defineProperty(obj, 'sayHi', {
value: function () {
console.log(`姓名:${this.name}
年齡:${this.age}
carInfo:${this.carInfo.toString()}
愛(ài)好:${this.hobbies.join(',')}
`)
}
})
obj.sayHi()
結(jié)果
姓名:李四
年齡:22
carInfo:汽車品牌:寶馬
車牌號(hào):京A12345
愛(ài)好:看書,玩游戲
發(fā)現(xiàn)除了語(yǔ)法麻煩點(diǎn),基本使用和簡(jiǎn)單的對(duì)象賦值沒(méi)有特別大區(qū)別.
4.Object.defineProperty-descriptor參數(shù)詳解
descriptor - value
設(shè)置到定義屬性的值,在不配置
wriable
為true
的情況下,是只讀的.
let obj2 = {}
Object.defineProperty(obj2, 'value', {
value: 'this is value',
})
obj2.value = 'this is an other value' // 由于 writable 默認(rèn)值是false,所以這里的修改無(wú)效,輸出仍然是 this is value
console.log(obj2.value)
結(jié)果:
this is value
descriptor - writable
let obj2 = {}
Object.defineProperty(obj2, 'value', {
value: 'this is value',
writable: true // 配置了writable 就可以寫了.
})
obj2.value = 'this is an other value'
console.log(obj2.value)
結(jié)果
this is an other value
descriptor-enumerable
默認(rèn)值是
false
,不能被for in
&Object.keys()
枚舉出來(lái).
Object.defineProperty(obj2, 'value', {
value: 'this is value',
writable: true,
})
console.log("keys:" + Object.keys(obj2))
結(jié)果
keys:
Object.defineProperty(obj2, 'value', {
value: 'this is value',
writable: true,
enumerable: true // 設(shè)置此屬性可以被枚舉
})
結(jié)果:
keys:value
descriptor-configurable
默認(rèn)值為
false
. 標(biāo)識(shí)此屬性不能被配置.
主要體現(xiàn)在:
- 此屬性不能被
delete
符號(hào)刪除 - 不能再次修改特性
false|true
Object.defineProperty(obj2, 'canDelete', {
value: '被刪除了嗎?',
// configurable: false
})
delete obj2.canDelete
console.log(obj2.canDelete) // 沒(méi)有被刪除
結(jié)果
被刪除了嗎?
Object.defineProperty(obj2, 'canDelete', {
value: '被刪除了嗎?',
configurable: true
})
delete obj2.canDelete
console.log(obj2.canDelete) // undefined 被刪除了.
結(jié)果
undefined
Object.defineProperty(obj2, 'canDelete', {
value: '被刪除了嗎?',
configurable: false,
enumerable: false // 定義時(shí),配置不能被枚舉
})
結(jié)果
[ 'value' ] // canDelete 沒(méi)有被枚舉出來(lái).
再一次定義
Object.defineProperty(obj2, 'canDelete', {
value: '被刪除了嗎?',
configurable: false, // 第一次定義為 false
enumerable: false
})
// 第二次定義 enumerable : true
Object.defineProperty(obj2, 'canDelete', {
value: '被刪除了嗎?',
configurable: true, // 第二次定義為true
enumerable: true
})
都是 canDelete 屬性
運(yùn)行直接報(bào)錯(cuò):
Cannot redefine property: canDelete
descriptor-set&get
有點(diǎn)類似于 Java/.Net
里的屬性訪問(wèn)器.
注意:在使用 set / get 的時(shí)候,就不能搭配 value & writable 兩個(gè)屬性了.否則直接報(bào)錯(cuò).
let obj3 = {}
let defaultValue = undefined
Object.defineProperty(obj3, 'name', {
value: '在有set/get的時(shí)候能設(shè)置嗎?',
writable: true,
set: function (newVal) {
defaultValue = newVal
},
get: function () {
return defaultValue
}
})
報(bào)錯(cuò)信息:
Invalid property descriptor. Cannot both specify accessors and a value or writable attribute.
正確代碼
let obj3 = {}
let defaultValue = undefined
Object.defineProperty(obj3, 'name', {
set: function (newVal) {
console.log('get被觸發(fā)')
defaultValue = newVal
},
get: function () {
console.log('set被觸發(fā)')
return defaultValue
}
})
obj3.name = '李四-obj3'
console.log(obj3.name)
結(jié)果
get被觸發(fā)
set被觸發(fā)
李四-obj3
對(duì)于 let defaultValue = undefined
這句代碼有點(diǎn)疑問(wèn).
可能從 Java/.Net
轉(zhuǎn)過(guò)來(lái)的程序員,會(huì)覺(jué)得 set
& get
里面應(yīng)該這么寫.
Object.defineProperty(obj3, 'property', {
set: function (newVal) {
this.property = newVal
},
get: function () {
return this.property
}
})
但實(shí)際運(yùn)行起來(lái),發(fā)現(xiàn)get&set
出現(xiàn)了死遞歸,出現(xiàn)了函數(shù)棧溢出的問(wèn)題.
obj3.property = '可以設(shè)置值嗎?' // set 死遞歸
Maximum call stack size exceeded
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:91:17)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
at Object.set (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:92:19)
console.log(obj3.property) // get 死遞歸
Maximum call stack size exceeded
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:94:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
at Object.get (/Users/relax/Desktop/代碼/前端學(xué)習(xí)/ES6/Object/Obj.js:95:17)
道理也比較簡(jiǎn)單:
當(dāng)我們調(diào)用
this.property
時(shí),其實(shí)又在執(zhí)行get/set
.于是就造成了死遞歸.這也是為什么要在外面定義一個(gè)let defaultValue = undefined
的原因.
只能說(shuō):現(xiàn)在版本的 js
對(duì)屬性的 get/set
支持的還不是很友好.
補(bǔ)充一點(diǎn)
Object.defineProperty
的 descriptor
參數(shù)可以定義屬性的 get
/set
.屬于ES5的功能.
其實(shí),在ES6中提供的 Proxy
對(duì)象,也能提供這樣一個(gè)功能.
let obj = {
id: 1,
level: 10,
name: '李四'
}
let objProxy = new Proxy(obj, {
// obj 當(dāng)前被代理的對(duì)象
// prop 當(dāng)前正在執(zhí)行g(shù)et的屬性.
get(obj, prop) {},
// obj,被代理的對(duì)象
// prop,當(dāng)前正在執(zhí)行set的屬性
// value,set的值.
set(obj, prop, value) {}
})
// 使用的時(shí)候記得使用代理返回的objProxy對(duì)象,而不是obj對(duì)象.
objProxy.name = '李四'
let name = objProxy.name