本 post 仿自 MDN
在看 vue 的时候,遇到这个方法,所以拿出来看了下,棒棒哒!
Object.defineProperty() static method
Object.defineProperty() 方法会直接在一个对象上定义(add)一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
Syntax
Object.defineProperty(obj, propName, descriptor)
params
Obj: 要在其上定义属性的对象。
prop: 要定义或修改的属性的名称。
descriptor: 将被定义或修改的属性描述符。
Note: 在ES6中,由于 Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty 是定义key为Symbol的属性的方法之一。
Decription
该方法允许精确添加或修改对象的属性。通过赋值来添加的普通属性会创建在属性枚举期间显示的属性(for…in 或 Object.keys 方法), 这些值可以被改变,也可以被删除。这种方法允许这些额外的细节从默认值改变。默认情况下,使用Object.defineProperty()添加的属性值是不可变的。
Property Descriptor
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。存取描述符是由getter-setter函数对描述的属性。描述符必须是这两种形式之一;不能同时是两者。
数据描述符和存取描述符均具有以下可选键值:
- configurable: 当且仅当该属性的 configurable 为 true 时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为 false。
// 不可配置
var o = {};
Object.defineProperty(o, 'demo', {
configurable: true,
enumerable: false,
value: 13
});
// delete o.demo --- false
// o.demo ---- 13
- enumerable: 当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中(也就是说可以遍历出来,可以使用Object.keys获得key)。默认为 false。
// 不可枚举
var o = {};
Object.defineProperty(o, 'demo', {
configurable: true,
enumerable: false,
value: 13
});
// o {demo: 13}
// o.demo 13
// Object.keys(o) --- []
// for (var k in o) {console.log(o[k])} --- undefined
数据描述符同时具有以下可选键值:
value: 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。默认为 undefined。
writable: 当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为 false。
- Object 本身有个 length 属性成员 应该就是 不可写的(只读的)
var o = {};
Object.defineProperty(o, 'demo', {
configurable: true,
enumerable: true,
value: 13,
writable: false
})
// o.demo --- 13
// o.demo = 10 --- 10
// o.demo --- 13
存取描述符同时具有以下可选键值:
get: 一个给属性提供 getter 的方法,如果没有 getter 则为 undefined。该方法返回值被用作属性值。默认为 undefined。
set: 一个给属性提供 setter 的方法,如果没有 setter 则为 undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为 undefined。
如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
记住,这些选项不一定是自身属性,如果是继承来的也要考虑。为了确认保留这些默认值,你可能要在这之前冻结 Object.prototype,明确指定所有的选项,或者将proto属性指向null。
// 使用 __proto__
var obj = {};
var descriptor = Object.create(null); // 没有继承的属性
// 默认没有 enumerable,没有 configurable,没有 writable
// 默认全为 false
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);
// 显式
// 与上述的写法效果是一样的
Object.defineProperty(obj, 'key', {
enumerable: false,
configurable: false,
writable: false,
value: 'static'
});
// 循环使用同一对象
function withValue(value) {
// 对象没有的话就赋值一次
var d = withValue.d || (withValue.d = {
enumerable: false,
configurable: false,
writable: false,
value: null
});
d.value = value;
return d;
}
Object.defineProperty(obj, 'key', withValue('static'));
// 如果 freeze 可用, 防止代码添加或删除对象原型的属性
// (value, get, set, enumerable, writable, configurable)
(Object.freeze||Object)(Object.prototype);
examples
创建(新增)属性
新增属性的时候 也是 2(公共属性) + 2(描述符) mode
如果对象中不存在指定的属性,Object.defineProperty()就创建这个属性。当描述符中省略某些字段时,这些字段将使用它们的默认值。拥有布尔值的字段的默认值都是false。value,get和set字段的默认值为undefined。一个没有get/set/value/writable定义的属性被称为“通用的”,并被“键入”为一个数据描述符
var o = {}; // 创建一个新对象
// 在对象中添加一个属性与数据描述符的示例
// 一个普通的属性成员添加 boolean 值为 true
Object.defineProperty(o, 'a', {
configurable: true,
enumerable: true,
value: 13,
writable: true
});
// 在对象中添加一个属性与存取描述符的示例
var bValue;
Object.defineProperty(o, "b", {
get: function () {
return bValue;
},
set: function (newValue) {
bValue = newValue;
},
enumerable: true,
configurable: true
});
o.b = 38;
// o.b的值现在总是与bValue相同,除非重新定义o.b
// o.b的值现在总是与bValue相同,除非重新定义o.b
修改属性
如果属性已经存在,Object.defineProperty()将尝试根据描述符中的值以及对象当前的配置来修改这个属性。如果旧描述符将其configurable 属性设置为false,则该属性被认为是“不可配置的”,并且没有属性可以被改变(除了单向改变 writable 为 false)。当属性不可配置时,不能在数据和访问器属性类型之间切换。
当试图改变不可配置属性(configurable,enumerable)(除了writable 属性之外)的值时会抛出{jsxref(“TypeError”)}},除非当前值和新值相同。
writable 属性
严格模式下会报错, 非严格模式下,赋值 writable: false 属性不会报错
当writable属性设置为false时,该属性被称为“不可写”。它不能被重新赋值。
var o = {}; // Creates a new object
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // logs 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // logs 37. The assignment didn't work.
// strict mode
// 在严格模式下, writable 为 false ,赋值的时候会 throw errors;
(function () {
'use strict';
var o = {};
Object.defineProperty(o, 'a', {
value: 2,
writable: false;
});
o.b = 3; // Object.defineProperty.html:46 Uncaught TypeError: Cannot assign to read only property 'b' of object '#<Object>'
return o.b;
}());
Enumerable 特性
enumerable定义了对象的属性是否可以在 for…in 循环和 Object.keys() 中被枚举。
var o = {}; // Creates a new object
Object.defineProperty(o, 'a', {
value: 37,
writable: false
});
console.log(o.a); // logs 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // logs 37. The assignment didn't work.
// strict mode
// 在严格模式下, writable 为 false ,赋值的时候会 throw errors;
(function () {
'use strict';
var o = {};
Object.defineProperty(o, 'a', {
value: 2,
writable: false;
});
o.b = 3; // Object.defineProperty.html:46 Uncaught TypeError: Cannot assign to read only property 'b' of object '#<Object>'
return o.b;
}());
Enumerable 特性
enumerable定义了对象的属性是否可以在 for…in 循环和 Object.keys() 中被枚举。
var o = {};
Object.defineProperty(o, 'a', {
value: 1,
enumerable: true
});
Object.defineProperty(o, 'b', {
value: 2,
enumerable: false
});
Object.defineProperty(o, 'c', {
value: 3
});
o.d = 4;
for (var k in o) {
console.log(o[k]);
}
// 1 , 4
Object.keys(o); // ["a", "d"]
o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
o.propertyIsEnumerable('d'); // true
Configurable 特性
configurable特性表示:
在 chrome 下, writable 特性也不能修改
- 对象的属性是否可以被删除,
- 以及除writable特性外的其他特性是否可以被修改。
var o = {};
Object.defineProperty(o , 'a', {
get: function () {
return 1;
},
configurable: false
});
Object.defineProperty(o, 'a', {
configurable: true
}); // VM8936:1 Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o, 'a', {
enumerable: true
}); // Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o, 'a', {
set: function () {}
}); // VM8999:2 Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o, 'a', {
get: function () {
return 1;
}
}); // Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o, 'a', {
value: 12
}); // VM9003:1 Uncaught TypeError: Cannot redefine property: a
Object.defineProperty(o, 'a', {
writable: true
}); // VM9023:1 Uncaught TypeError: Cannot redefine property: a
console.log(o.a); // 1
delete o.a; // false
console.log(o.a); // 1
如果o.a的configurable属性为true,则不会抛出任何错误,并且该属性将在最后被删除。
添加多个属性和默认值
考虑特性被赋予的默认特性值非常重要,通常,使用点运算符和Object.defineProperty()为对象的属性赋值时,数据描述符中的属性默认值是不同的,如下例所示。
var o = {};
o.a = 1;
// 等价于
Object.defineProperty(o, 'a', {
configurable: true,
enumerable: true,
value: 1,
writable: true
});
// 另一方面,
Object.defineProperty(o, 'a',{ value: 1});
// 等价于
// 因为 配置对象中, 所有的boolean 值默认为 false
Object.defineProperty(o, 'a', {
configurable: false,
enumerable: false,
value: 1,
writable: false
});
一般的 Setters 和 Getters
下面的例子展示了如何实现一个自存档对象。 当设置temperature 属性时,archive 数组会获取日志条目。
// 1. temp archive 变量
// 2. 对象 set 时候 保存
// 3. 将保存的东西 获取到
function Archive () {
// 感觉 temperature 就像共享变量一样
var temperature = null;
var archiver = [];
Object.defineProperty(this, 'temperature', {
get: function () {
console.log('gettor!');
return temperature;
},
set: function (value) {
temperature = value;
archiver.push({val: temperature});
}
});
this.getArchiver = function () { return archiver};
}
var instance = new Archive();
console.log(instance.temperature);
instance.temperature = 11;
instance.temperature = 12;
instance.getArchiver();
// gettor
// [{val: 11},{val: 12}]
或者
var pattern = {
get: function () {
return 'I alway return this string,whatever you have assigned';
},
set: function () {
this.myname = 'this is my name string';
}
};
function TestDefineSetAndGet () {
Object.defineProperty(this, 'myproperty', pattern);
}
var instance = new TestDefineSetAndGet();
// writable 只能和 value 一起使用
// 所以说 存取器描述符定义 的属性都是可以 读写的
instance.myproperty = 'test';
console.log(instance.myproperty);
// 'I alway return this string,whatever you have assigned'
console.log(instance.myname);
// 'this is my name string'
set 和 get 简单理解:
- 在 访问对象属性的时候,对应的属性 gettor 会被调用,得到属性值
- 在对对象属性 写(赋值)的时候,对应的 settor 会对调用,属性被重新赋值
- settor 和 gettor 是两个钩子函数, 很好用
- set 和 get 中 this 指向 被定义的对象