Object.defineProperty语法
Object.defineProperty() 的作用就是直接在一个对象上定义一个新属性,或者修改一个已经存在的属性Object.defineproperty 可以接收三个参数 Object.defineproperty(object, prop, desc)object 需要定义或修改的对象prop 需要定义或修改的属性名options 配置项,也叫属性描述符
- 传统的对象属性的赋值是可以删除的,但是通过
Object.defineProperty 定义的属性可以通过属性描述符进行更精准的控制对象属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const person = { name: 'suxi', age: 18 };
person.sex = '男'; person.age = 17;
Object.defineProperty(person, 'score', { value: 99 });
console.log(person.score); person.score = 100; console.log(person.score);
|
属性的特性以及内部属性
js 有三种类型的属性- 命名数据属性:拥有一个确定的值的属性,这也是最常见的属性
- 命名访问器属性:通过
getter 和 setter 进行读取和赋值的属性 - 内部属性:由
JavaScript 引擎内部使用的属性,不能通过 JavaScript 代码直接访问到,不过可以通过一些方法间接读取和设置。比如,每个对象都有一个内部属性 [[Prototype]],你不能直接访问这个属性,但可以通过 Object.getPrototypeOf() 方法间接的读取到它的值。虽然内部属性通常用一个双中括号包围的名称来表示,但实际上这并不是它们的名字,它们是一种抽象操作,是不可见的,根本没有上面两种属性有的那种字符串类型的属性
属性描述符
- 通过
Object.defineProperty 为对象定义属性,有两种形式,且不能混合使用,分别为数据描述符,存取描述符,下面描述两者之间的区别
数据描述符
- 数据描述符特有的两个属性
注意:当使用了 writable 和 value 属性,不允许使用 getter 和 setter 这两个方法
writable: 描述对象属性是否可写- 当我们在一个对象上定义某个属性时,
writable 默认是 true 意味这个属性是可写的 - 当我们通过属性描述符定义一个属性时,
writable 默认是 false,正如上面无法修改 score 属性的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let person = {};
Object.defineProperty(person, 'name', { value: 'suxi', writable: false, });
console.log(person.name); person.name = 'peiqi'; console.log(person.name);
Object.defineProperty(person, 'age', { value: 19, writable: true });
console.log(person.age); person.age = 20; console.log(person.age);
|
注意:如果描述符中的某些属性被省略,会使用以下默认规则
| 属性名 | 默认值 |
|---|
value | undefined |
get | undefined |
set | undefined |
writable | false |
enumerable | false |
configurable | false |
存取描述符
- 由一对
getter setter 函数功能来描述的属性get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined 该方法返回值被用作属性值,默认为 undefinedset: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined 该方法将接受唯一参数并将参数的新值分配给该属性,默认值为 undefined
注意:当使用了 getter 和 setter 方法,不允许使用 value 和 writable 这两个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let person = {}; let temp = null;
Object.defineProperty(person, 'name', { get: function() { console.log("我要读取属性"); return temp; }, set: function(value) { console.log("我要设置属性"); temp = value; } });
console.log(person.name, temp); person.name = 'suxi'; console.log(person.name, temp); temp = 'peiqi'; console.log(person.name, temp);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let person = {}; let temp = null;
Object.defineProperty(person, 'name', { get: function() { console.log("我要读取属性"); return temp; } });
console.log(person.name, temp); person.name = 'suxi'; console.log(person.name, temp); temp = 'peiqi'; console.log(person.name, temp);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let person = {}; let temp = null;
Object.defineProperty(person, 'name', { set: function(value) { console.log("我要设置属性"); temp = value; } });
console.log(person.name, temp); person.name = 'suxi'; console.log(person.name, temp); temp = 'peiqi'; console.log(person.name, temp);
|
存取器描述
- 当使用存取器描述属性时,允许设置以下特性属性
1 2 3 4 5 6 7 8
| let obj = {};
Object.defineProperty(obj, 'key', { get: function() {}, set: function(value) {}, configurable: true | false, enumerable: true | false, })
|
- 当设置或获取对象的某个属性的值的时候,可以提供
getter / setter 方法getter 是一种获取属性值的方法setter 是一种设置属性值的方法
注意:get 或 set 不是必须成对出现,任意写其一就可以,如果不设置方法,则 get 或 set 的默认值为 undefined
其他描述符
- 数据描述符和存取描述均具有以下描述符
configurable 描述属性是否配置,以及可否可以通过 delete 删除(是否可配置)- 当我们在一个对象上直接定义某个属性是,
configurable 默认是 true - 当我们使用属性描述符定义一个属性时,
configurable 默认是 false
enumerable 描述属性是否会出现在 for in 或 Object.keys() 的遍历中(是否可枚举)- 当我们在一个对象上直接定义某个属性是,
enumerable 默认是 true - 当我们使用属性描述符定义一个属性时,
enumerable 默认是 false
configurable 特性- 在
configruable 为 false 但 writable 为 true 的情况下可以用两种方式修改其值,但不能删除 - 在
configruable 为 true 但 writable 为 false 的情况下可以通过属性描述符修改属性值 - 在
configruable 为 false 但 writable 为 true 的情况下可以通过属性描述符修改 writable 为 false 但不能从 false 修改为 true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| let person = {};
Object.defineProperty(person, 'name', { value: 'suxi', configurable: false });
console.log(person.name);
console.log(delete person.name); console.log(person.name);
Object.defineProperty(person, 'name', { value: 'peiqi' });
|
1 2 3 4 5 6 7 8 9 10 11 12
| let person = {};
Object.defineProperty(person, 'name', { configurable: false, writable: true, value: 'suxi' });
console.log(person.name); person.name = 'peiqi';
console.log(person.name);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| let person = {}; Object.defineProperty(person, 'name', { value: 'suxi', configurable: true, writable: false });
console.log(person.name);
Object.defineProperty(person, 'name', { value: 'peiqi' })
console.log(person.name);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| let person = {}; Object.defineProperty(person, 'name', { configurable: false, writable: true, value: 'suxi' });
console.log(person.name); person.name = 'peiqi'; console.log(person.name);
Object.defineProperty(person, 'name', { writable: false }); console.log(person.name);
person.name = 'zhangsan'; console.log(person.name);
Object.defineProperty(person, 'name', { writable: true });
|
enumerable特性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| let person = {};
Object.defineProperty(person, 'name', { value: 'jack', configurable: false, writable: false });
person.gender = 'male';
Object.defineProperty(person, 'age', { value: 10, enumerable: true });
console.log(Object.keys(person));
for (let key in person) { console.log(key); }
console.log(JSON.stringify(person););
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| let person = {}; person.gender = 'male';
Object.defineProperty(person, "gender", { value: 'male', writable: true, configurable: true, enumerable: true });
Object.defineProperty(person, 'age', { value: 24 })
Object.defineProperty(person, 'age', { value: 24, configurable: false, writable: false, enumerable: false })
|
不变性
对象常量
- 结合
writable 和 configurable 都为 false 的情况下,可以创建一个真正的常量属性(不可修改、不可重新定义、不可删除)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| let person = {};
Object.defineProperty(person, 'name', { value: 'suxi', configurable: false, writable: false });
delete person.name; console.log(person.name); person.name = 'lisi'; console.log(person.name);
Object.defineProperty(person, "name", { value: 'zhangsan' });
|
- 静止扩展
- 如果你需要禁止一个对象添加新属性并且保留已有属性,就可以使用
Object.preventExtensions()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| let person = { name: 'lisi' };
Object.preventExtensions(person);
person.gendar = 'male';
console.log(person.gendar);
Object.defineProperty(person, 'name', { value: 'suxi', writable: false, configurable: false });
console.log(person.name);
person.gendar = 'male';
console.log(person.gendar);
|
- 密封
Object.seal() 会创建一个密封对象,这个方法实际上会在一个现有对象上调用 Object.preventExtensions 并把所有现有属性标记为 configurable: false- 密封之后不仅不能添加新属性,也不能重新配置或者删除任何现有属性,虽然可以改属性的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let person = { name: 'lisi' };
Object.seal(person);
person.gendar = 'male'; console.log(person.gendar); console.log(Object.keys(person)); person.name = 'zhangsan';
console.log(person.name);
Object.defineProperty(person, 'name', { value: 'zhangsan', configurable: true })
|
- 冻结
Object.freeze() 会创建一个冻结对象,这个方法实际上会在一个现有对象调用 Object.seal() 并把所有现有属性标记为 writable: false 这样就无法修改它们的值了- 这个方法是可以用在对象上级别最高的不可变性,它会禁止对于对象及其任意直接属性的修改(但这个对象引用的其他对象不受影响)
- 可以深度冻结一个对象,并在这些对象上调用
Object.freeze() 但是一定要小心,这么做可能会无意中冻结其他共享对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let person = { name: 'jack' };
Object.freeze(person);
person.gendar = '111'; console.log(person.gendar);
console.log(Object.keys(person));
person.name = 'age'; console.log(person.name);
Object.defineProperty(person, 'name', { value: 'rose' });
|
Object.defineProperty缺陷
- 无法检测到对象属性的新增或删除
- 无法监听到数组变化
Object.defineProperties
Object.defineProperties 和 Object.defineProperty 关系和它们的名字一样是单数和复数的关系,Object.defineProperty 一次只能定义一个属性,而 Object.defineProperties 一次可以定义多个属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| let obj = Object.defineProperties({ agee: 18 }, { name: { value: 'lisi', writable: false, configurable: false, enumerable: false }, age: { get() { return this.agee; }, set(value) { this.agee = value; } } });
console.log(obj);
|
总结
- 属性定义,通过
Object.defineProperty()- 如果对象没有对应属性名的属性:如果该对象是可拓展的,则创建一个属性,否则拒绝
- 如果对象已经有了这个属性,则安装下面的步骤重新配置这个属性
- 如果这个已有的属性是不可配置的,则进行下面的操作会被拒绝
- 将一个数据属性转换为访问器属性,反之亦然
- 改变
configurable 和 enumerable - 改变
writable 由 false 变为 true - 在
writable 为 false 改变 value - 改变
getter 和 setter
- 否则这个已有的属性可以被重新配置
- 属性赋值,通过
object.prop = ''- 如果在原型链上存在一个名为
p 的只读属性(只读的数据属性或者没有 setter 的访问器属性),则拒绝 - 如果在原型链上存在一个名为
p 的且拥有 setter 的访问器属性,则调用这个 setter - 如果没有名为
p 的自身属性,则如果这个对象是可扩展的,就创建一个新属性,否则,如果这个对象是不可扩展的,则拒绝 - 如果已经存在一个可写的名为
p 的自身属性,则调用 Object.defineProperty 该操作只会更改 p 属性的值,其他的特性(比如可枚举性)都不会改变
- 作用以及影响
- 属性的定义操作和赋值操作各自有自己的作用和影响
- 赋值可能会调用原型上的
setter,定义会创建一个自身属性 - 原型链中的同名只读属性可能会阻止赋值操作,但不会阻止定义操作,如果原型链中存在一个同名的只读属性,则无法通过赋值的方式在原对象上添加这个自身属性,必须使用定义操作才可以
1 2 3 4 5 6 7 8 9 10 11
| let proto = { get bar() { console.log("getter"); return 'a'; } };
let obj = Object.create(proto);
obj.bar = 'hello'; console.log(obj.bar);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| let proto = { get bar() { console.log('getter'); return 'a'; } }
let obj = Object.create(proto); obj.bar = 'hello'; console.log(obj.bar);
Object.defineProperty(obj, 'bar', { value: 'hellox' });
console.log(proto.bar); console.log(obj.bar);
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| let proto = Object.defineProperties({}, { foo: { value: 'a', writable: false, configurable: true } });
let obj = Object.create(proto); console.log(obj); obj.foo = 'hello'; console.log(obj.foo);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| let proto = Object.defineProperties({}, { foo: { value: 'a', writable: false, configurable: true } });
let obj = Object.create(proto);
Object.defineProperty(obj, "foo", { value: 'sfaf' });
console.log(obj.foo); console.log(proto.foo); obj.foo = 'hello'; console.log(obj.foo);
|
- 赋值运算符不会改变原型链上的属性
- 不能通过
obj.foo 赋值来改变 proto.foo 的值,这种操作只会在 obj 上新建一个自身属性
1 2 3 4 5 6 7 8 9 10 11
| let proto = { foo: 'a' };
let obj = Object.create(proto); console.log(obj); obj.foo = 'b';
console.log(obj.foo); console.log(proto.foo); console.log(obj.hasOwnProperty('foo'));
|
- 对象字面量的属性是通过定义操作添加的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| let obj = { name: 'jack' };
let obj = new Object();
Object.defineProperties(obj, { name: { value: 'jack', writable: true, configurable: true, enumerable: true } });
|
- 再次提醒,记住下面两种形式的区别
1 2 3 4 5 6 7 8 9 10 11 12 13
| let obj = {}; obj.name = 'jack';
let obj = {};
Object.defineProperty(obj, 'name', { value: 'jack', writable: true, configurable: true, enumerable: true });
|
1 2 3 4 5 6 7 8 9 10 11
| Object.defineProperty(obj, 'name', { value: 'jack' });
Object.defineProperty(obj, 'name', { value: 'jack', writable: false, configurable: false, enumerable: false })
|
十分钟理解Object.defineProperty