Object.defineProperty()讲解

由可枚举属性引起的

JavaScript属性分为可枚举属性和不可枚举属性?什么是“枚举”,它又有什么用呢?

var apple = {
  name : "苹果",
  price :18
}
for(var prop in obj){
    console.log(prop)
}
// 输出:name ,price
// 这里的可枚举性就是说for的这种写法可以得到这个对象的属性名
 
var stu = {};
Object.defineProperties(stu, {
    name: {
        value: "张三",
        enumerable: false
    },
    age: {
        value: 18,
        enumerable: true
    }
});
 
for(var prop in stu){
    console.log(prop);
}
// 输出: age
//name属性不能便利出来,因为他的 “enumerable” 值为false
 
//以上代码请在chrome或者火狐里面运行,IE9以下运行第二段代码会出错

我们知道,for...in语句以任意顺序遍历一个对象的可枚举属性
既 stu.age 可枚举,stu.name 不可枚举,而他们是否可枚举是通过 enumerable 来设置的。

属性描述符

我们通常会直接创建对象,然后设置对象的属性,例如上面的 obj 对象,设置了 name ,price 属性。其实这些属性也有一定的限定的,这些限定属性性质的我们称为“属性描述符”。
这里拿 price 属性来说,我们能输出 fruit.price 等于 18 ,其实是通过 price 的描述符 value 来设置的。例如上面的stu.age 的设置;
price 也有“enumerable”描述符,他之所以能被 for in 到 ,是因为对自身添加属性的 “enumerable” 默认为 true。

除了上面说到过的 “value” 和 “enumerable” 还有什么描述符呢?

每个属性都有
Configurable描述符、Enumerable描述符、Writable描述符、Value描述符
或者,每个属性都有
Configurable描述符、Enumerable描述符、Get描述符、Set描述符
前者称为 数据描述符,他们的值决定了该js对象的属性的某些性质及是否可读写。
后者称为 读取描述符,他们的值决定了该js对象的属性的某些性质及读写的行为。

描述符必须是这两种形式之一,不能同时是两者。


那么这些属性是各代表着什么?
数据描述符具有以下键值

值(默认值) 作用
configurable false --------------------- 为 true 时,该属性描述符才能够被改变,表示对象的属性是否可以被删除,以及除writable特性外的其他特性是否可以被修改。
enumerable false true时,该属性在对象中才是可枚举的
value undefined 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。读取属性的时候就是通过这里开始读
writable false 表示能否修改属性的值赋值运算符(assignment operator)基于右值(right operand)的值,给左值(left operand)赋值。")改变

读取描述符具有以下键值

值(默认值) 作用
configurable false --------------------- 为 true 时,该属性描述符才能够被改变
enumerable false true时,该属性在对象中才是可枚举的
get undefined 在读取属性时调用的函数
set undefined 在设置属性时调用的函数

知道这些描述符所控制的性质,那又是什么时候去哪里设置的呢?这就关乎到Object.defineProperty() 了,它有三个参数,Object.defineProperty(obj, prop, descriptor),其中descriptor就是设置属性性质描述符。

defineProperty 和 defineProperties
  1. 语法
    Object.defineProperty(obj, prop, descriptor)
    Object.defineProperties(obj, props)
  2. 定义:
    Object.defineProperty() 直接在一个对象上定义一个新属性,或者修改现有属性,并返回该对象。
    Object.definePropertys() 直接在一个对象上定义一个或多个新的属性,或者修改现有属性,并返回该对象。
  3. 参数
    obj 要在其上定义属性的对象。
    prop 要定义或修改的属性的名称。
    descriptor 将被定义或修改的属性描述符。
  4. 返回值
    返回被操作的对象,即返回obj参数
  5. 注意点
    1)当把configurable值设置为false后,就不能修改任何属性了,包括自己本身这个属性
    2)想用访问器属性模拟默认行为的话,必须得在里面新顶一个属性,不然的话会造成循环引用
    3)可枚举属性for/in, Object.keys(), JSON.stringify(), Object.assign() 方法才生效(for/in 是对所有可枚举属性,而其他三种是对自身可枚举属性)
  6. 用途
    1)vue通过getter-setter函数来实现双向绑定
    2)俗称属性挂载器
    3)专门监听对象数组变化的Object.observe()(es7)也用到了该方法



知道了Object.defineProperty()这个东东是用来生成或修改一个对象属性,知道对象属性的性质是靠descriptor这个参数来设置之后,我们来看看他是怎么运用的。
var person = {}为例,我们要怎样去修改默认的属性值呢?

  • 设置该属性为数据描述符
var person = {}

Object.defineProperty(person,'a',{
    configurable:true, //可修改默认属性
    enumerable:true, //可枚举
    writable:true, //可修改这个属性的值
    value:1 //定义一个初始的值为1
})

console.log(person) //{a: 1}
person.a=2
console.log(person) //{a: 2}

for(var k in person){
    console.log(k) //a
}

现在我们来修改下enumerable和writable值

Object.defineProperty(person,'a',{
    configurable:true,
    enumerable:false,
    writable:false,
    value:1
})

console.log(person) //{a: 1}
person.a=2
console.log(person) //{a: 1} 因为writable值被设置为false了,所以不可以写,严格模式下会报错

for(var k in person){
    console.log(k)// 没有可枚举属性,因为a的enumerable的值被设置为false
}

我们试试吧configurable的值改为false

Object.defineProperty(person,'a',{
    configurable:false,//为false的时候不允许修改默认属性了
})
===============================
# 改为false之后再试试修改其他属性
Object.defineProperty(person,'a',{
    configurable:true,
    enumerable:true,
    writable:true,
    value:1
})
//woa,控制台直接报错了!连想把false值改回true都不行!也就是说,这个改动是一次性了!
//也就是说,你可以使用Object.defineProperty()方法无限修改同一个属性,但是当把configurable改为false之后就什么都不能再修改了


  • 设置该属性为读取描述符
var person = {
    a:1
}
Object.defineProperty(person,'a',{
    get(){
        return 3 //当访问这个属性的时候返回3
    },
    set(val){
        console.log(val)//当设置这个属性的时候执行,val是设置的值
    }
})

person.a// 3,我们明明写的是a:1,怎么返回的3呢?这就是get()的威力了
person.a = 5// 5,相应的设置的时候执行了set()函数

我们来模拟一个访问和设置的默认行为

var person = {
    a:1
}
// 注:里面的this指向ogj(person)
Object.defineProperty(person,'a',{
    get(){
        return this.a 
    },
    set(val){
        this.a = val 
    }
})
//我们想当然的这么写.
person.a//Uncaught RangeError: Maximum call stack size exceeded

什么,溢出了?这是为什么?
哦~原来是这么写的话会造成循环引用,狂call不止
我们看下流程:
person.a → get.call(person) → this.a → person.a → get.call(person) → this.a......
我们来修改下

var person = {
    a:1
}
Object.defineProperty(person,'a',{
    get(){
        return this._a || 1 //定义一个新的属性和一个默认值
    },
    set(val){
        this._a = val 
    }
})
person.a// 1
person.a=2// 2
person.a// 2
这样就好了

这就是数据描述符和读取描述符的应用方式。在平时简单的开发中可能用不上,但是知道了这些之后对一些框架的封装的理解还是很有帮助的,例如vue数据双向绑定原理上利用的就是Object.defineProperty方法。



拓展

每个对象都有的一些方法:

  • Object.getOwnPropertyDescriptors(obj)
    定义:获取一个对象的所有自身属性(自身属性)的描述符
    使用:Object.getOwnPropertyDescriptors(apple)

  • Object.getOwnPropertyDescriptor(obj, prop)
    定义:返回指定对象上一个自有属性对应的属性描述符
    使用:Object.getOwnPropertyDescriptor(apple, 'price')


Object.getOwnPropertyDescriptor 的应用:
一般直接添加属性时,属性描述符默认值都为 true,当用 Object.defineProperty() 方法来添加对象属性时,此时的属性描述符默认值为false
以文章开头的 apple 和 stu 的案例:

// 直接添加属性
Object.getOwnPropertyDescriptor(apple, 'name')
//name:{value: "苹果", writable: true, enumerable: true, configurable: true}
// 属性描述符添加属性
Object.getOwnPropertyDescriptor(stu, 'name')
//name:{value: "张三", writable: false, enumerable: false, configurable: false}


// 又或者
var stu2 = Object.create({}, { name: { value: '李四' } })
Object.getOwnPropertyDescriptor(stu2, 'name')
//name:{value: "李四", writable: false, enumerable: false, configurable: false}

Object.create() 的第二个参数为 Object.defineProperties 的第二个参数,既设置属性及属性描述符, 详情移步到 Object.create()


参考资料:
1.MDN
3.关于 Object.defineProperty() 小结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • 官方中文版原文链接 感谢社区中各位的大力支持,译者再次奉上一点点福利:阿里云产品券,享受所有官网优惠,并抽取幸运大...
    HetfieldJoe阅读 2,584评论 9 22
  • 概述 JavaScript提供了一个内部数据结构,用来描述一个对象的属性的行为,控制它的行为。这被称为“属性描述对...
    zjh111阅读 723评论 0 0
  • 什么是属性描述符 创建一个对象最常用的方式是声明的形式,大概语法是这样的: 也可以采用构造形式,创建一个Objec...
    冰果2016阅读 2,880评论 0 5
  • 本篇主要介绍JS中常用Object的属性方法。 delete 操作 in 运算符 obj.hasOwnProper...
    boySpray阅读 1,979评论 0 2
  • 《射雕英雄传》这部电视剧,以前看过一次,至于以前有多前,已经记不清楚。大概是小学,或许是初中,高中确定是没看过。也...
    臭小妈妈阅读 202评论 0 0