Object.defineProperty 解析

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è)置屬性是否可以被再次配置.
    • 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è)置到定義屬性的值,在不配置 wriabletrue 的情況下,是只讀的.

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.definePropertydescriptor 參數(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市走敌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件出吹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡辙喂,警方通過(guò)查閱死者的電腦和手機(jī)捶牢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)加派,“玉大人叫确,你說(shuō)我怎么就攤上這事跳芳∩纸酰” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,782評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵飞盆,是天一觀的道長(zhǎng)娄琉。 經(jīng)常有香客問(wèn)我,道長(zhǎng)吓歇,這世上最難降的妖魔是什么孽水? 我笑而不...
    開(kāi)封第一講書人閱讀 58,709評(píng)論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮城看,結(jié)果婚禮上女气,老公的妹妹穿的比我還像新娘。我一直安慰自己测柠,他們只是感情好炼鞠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著轰胁,像睡著了一般谒主。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赃阀,一...
    開(kāi)封第一講書人閱讀 51,578評(píng)論 1 305
  • 那天霎肯,我揣著相機(jī)與錄音,去河邊找鬼。 笑死观游,一個(gè)胖子當(dāng)著我的面吹牛搂捧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播懂缕,決...
    沈念sama閱讀 40,320評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼异旧,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了提佣?” 一聲冷哼從身側(cè)響起吮蛹,我...
    開(kāi)封第一講書人閱讀 39,241評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拌屏,沒(méi)想到半個(gè)月后潮针,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,686評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡倚喂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評(píng)論 3 336
  • 正文 我和宋清朗相戀三年每篷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片端圈。...
    茶點(diǎn)故事閱讀 39,992評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡焦读,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舱权,到底是詐尸還是另有隱情矗晃,我是刑警寧澤,帶...
    沈念sama閱讀 35,715評(píng)論 5 346
  • 正文 年R本政府宣布宴倍,位于F島的核電站张症,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鸵贬。R本人自食惡果不足惜俗他,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望阔逼。 院中可真熱鬧兆衅,春花似錦、人聲如沸嗜浮。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,912評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)周伦。三九已至夕春,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間专挪,已是汗流浹背及志。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,040評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工片排, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人速侈。 一個(gè)月前我還...
    沈念sama閱讀 48,173評(píng)論 3 370
  • 正文 我出身青樓率寡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親倚搬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子冶共,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容