这是我参与更文挑战的第7天,活动详情查看: 更文挑战
属性描述符
属性分为:数据属性和访问器属性
可以使用一些特性来描述属性的特征。
可以通过Object.defineProperty()方法来设置一个属性是数据属性还是访问器属性。
数据属性
有四个特性来描述一个属性。
configurable:属性是否可以delete删除并重新定义,是否可以修改它的特性,以及是否可以变成访问器属性。enumerable:属性是否可以通过for-in遍历。writable:属性的值是否可以被修改。value:读取和写入属性值的位置,属性值存放在这。
测试这些属性。
-
测试configurable
const obj = {}; Object.defineProperty(obj, "b", { configurable: false, writable: true, value: 'b', enumerable: true })设置
Object.defineProperty(对象,属性名,描述符对象)会在对象上添加这个属性。delete obj.b设置了configurable以后,无法删除这个属性。
Object.defineProperty(obj, "b", { writable: false, configurable: true, enumerable: false, value: "bbb" }) // Uncaught TypeError: Cannot redefine property: b无法重新设置属性描述符,会直接报错。
Object.defineProperty(obj, "b", { writable: false, })但是如果
configurable: false并且writable: true时,重新设置为writable: false时,不会报错,并且修改成功。 -
测试enumerable
const herb = { [Symbol.for("name")]: "herb", age: 18 } Object.defineProperty(herb, "sex", { enumerable: false, value: "男", writable: true, configurable: true })设置了符号属性,和age属性,还有用
Object.defineProperty设置的数据属性。来看一下遍历情况。for(const prop in herb) { console.log(prop); } // agename和sex都没有遍历出来,其中符号属性是不会被for-in遍历的,sex设置了enumerable: false,所以无法被遍历。Object.keys(herb); // ["age"] Object.values(herb); // [18] Object.entries(herb); // [["age", 18]]都不会遍历出来。
Object.getOwnPropertyDescriptor(herb, Symbol.for("name")); // { // value: "herb", // writable: true, // enumerable: true, // configurable: true // }即使符号属性被设置了
enumerable: true,也无法被这些方法遍历。可能是因为这些方法都是ES6之前的。{...herb} // { // age: 18, // Symbol(name): "herb" // }利用展开运算符以后,因为符号属性是
enumerable: true所以可以被遍历出来。Object.defineProperty(herb, Symbol.for("name"), { enumerable: false }){...herb} // { // age: 18 // }就不能遍历出来了。
Object.assign({}, herb); // { // age: 18 // }因为
Symbol.for("name")是不可枚举的,所以Object.assign()也无法找到它。 -
测试writable、value
const clothes = { color: "white", size: "big", } Object.defineProperty(clothes, "color", { writable: false }) Object.defineProperty(clothes, "brand", { value: "Adidas", writable: false })color是先定义在字面量里的,再通过方法修改属性描述符。brand是直接通过方法创建的属性。
clothes.color = "red"; clothes.color // white clothes.brand = "nike"; clothes.brand // Adidas clothes.size = "small"; clothes.size // small只要定义了
writable:false就不能再赋予新的值,是只读的。注意:
- 在对象字面量里定义的属性,
configurable、enumerable、writable默认都为true,value是设置的属性值。 - 如果没有在字面里定义属性,而是通过调用
Object.defineProperty(对象,属性名,描述符对象)来设置属性的话,configurable、enumerable、writable如果没有设置,那么自动为false。
- 在对象字面量里定义的属性,
访问器属性
访问器属性不包含value和writable,新增两个getter和setter函数。
有四个特性来描述一个属性。
configurable:属性是否可以delete删除并重新定义,是否可以修改它的特性,以及是否可以变成访问器属性。enumerable:属性是否可以通过for-in遍历。get:获取函数,读取属性时调用。set:设置函数,写入属性时调用。
-
在类中设置访问器属性。
class Person { constructor(name, age, sex) { Object.defineProperty(this, "age", { configurable: true, enumerable: true, get() { return this[Symbol.for("age")]; }, set(newValue) { this[Symbol.for("age")] = newValue > 120 || newValue < 0 ? 0 : newValue; } }) this.name = name; this.age = age; this.sex = sex; } } const herb = new Person("herb", 19, "男")在类里面调用了
Object.defineProperty,实际上是给this对象中的age属性设置了get和set,相当于每创建一个对象都会创建一遍get和set方法。herb.age // 19 herb.age = 130 // herb.age == 0get从[Symbol.for("age")]中读取age的值,修改age的属性值时,调用set再把新值设置给[Symbol.for("age")]。 -
在构造函数原型上设置访问器属性。
function Dog(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } Object.defineProperty(Dog.prototype, "age", { get() { return this._age; }, set(newValue) { this._age = newValue > 100 || newValue < 0 ? 0 : newValue; } }) const xx = new Dog("小小", 1, "母");在构造函数的原型上定义一个访问器属性,创建多个对象就不用重复配置
get和set。虽然不知道什么原理,但是只要对象的原型链上配置了访问器属性,对象读写该属性就会调用对应的
get和set方法。例如:把上述代码改成
Object.defineProperty( Object.getPrototypeOf(Dog.prototype), "age", {...} ),在Dog的原型的原型上设置访问器属性,上述代码依然成立。会顺着原型链寻找。xx.hasOwnProperty('age') // false Dog.prototype.hasOwnProperty('age'); // true奇怪的是,虽然构造函数中有
this.age = age这句代码,但是xx对象上依然没有age属性,可能是原型上配置的访问器属性,影响了这一切。xx.age会触发原型上的get方法,方法体中的this指向xx对象,this._age意味着可以在每个对象上都赋上一个独立的属性,而不会和其他对象产生冲突。 -
ES6新增的语法糖,可以直接在类的原型上配置访问器属性。
class Person { constructor(name, age, sex) { this.name = name; this.age = age; this.sex = sex; } get age() { return this[Symbol.for("age")]; } set age(newValue) { this[Symbol.for("age")] = newValue > 120 || newValue < 0 ? 0 : newValue; } }和示例2中的写法应该没有区别。hhhh
get和set关键字后面跟着要修饰的属性名,配置起来比较方便。const herb = new Person("herb", 19, "男"); herb.hasOwnProperty("age"); // false Object.getOwnPropertyDescriptor(herb, "age"); // undefined Object.getOwnPropertyDescriptor(herb.__proto__, "age"); // { // configurable: true, // enumerable: false, // get: ƒ age(), // set: ƒ age(newValue), // __proto__: Object // }该语法糖没办法设置这个访问器属性的
configurable和enumerable,默认是不能枚举的,但是可以修改和删除。符合原型上属性的特点。 -
还可以直接在对象上配置访问器属性。
const date = { get currentTime() { let time = new Date(); return `${time.toLocaleDateString()} ${time.toLocaleTimeString()}`; } }date.currentTime; // 可以获取当前时间 date // { // currentTime: (...), // get currentTime: ƒ currentTime(), // __proto__: Object // }get是直接定义在对象上的,不用调用defineProperty单独配置了,可读性更好了。并且配置好的访问器属性
configurable和enumerable都是true。符合对象上属性的特点。const obj = { get a() { return this._a; }, set a(v) { this._a = v; }, a: "a" }在对象字面量初始化时,后面的会覆盖前面的。虽然配置了
a的访问器属性,但被其数据属性所覆盖。obj // { // a: "a" // } -
只定义
getconst temp = { get a() { return "a" } }temp.a // "a" temp.a = "123" // 严格模式下会报错 temp.a == "123" // false只定义了
get相当于这个属性是只读的,不能被修改。 -
只定义
setconst temp = { set a(v) {} }temp.a // undefined temp.a = 1 temp.a // undefiend即使修改了也读取不到。
定义多个属性
Object.defineProperties(obj, {
a: {
value: "a",
writable: true
},
b: {
value: "b",
enumerable: false
},
c: {
get() { return this._c },
set(v) { this._c = v }
}
})
获取属性的特性
Object.getOwnPropertyDescriptor(obj, "prop");
获取某个属性的属性描述符,返回一个对象。
Object.getOwnPropertyDescriptors(obj);
就加了个s,可以获取对象上所有属性的属性描述符。
不管是符号属性,还是不可枚举的属性,只要是这个对象上的,不包括原型上的,都会显示出来。
特殊情况
const sky = {
get weather() {
console.log("sky->get->weather");
return this._weather;
},
set weather(newValue) {
console.log("sky->set->weather");
this._weather = newValue;
}
};
sky.weather = "sun";
const thing = {
time: "morning",
activity: "football",
get weather() {
console.log("thing->get->weather");
return this._weather;
},
set weather(newValue) {
console.log("thing->set->weather");
this._weather = newValue;
}
};
thing.weather = "rain";
两个对象都对同一个属性名设置了get和set。
Object.assign(thing, sky);
// sky->get->weather
// thing->set->weather
当混合两个对象时,会把sky中的属性覆盖到thing上,相当于thing.weather = sky.weather,分别调用sky 的get和thing的set。
{...thing, ...sky}
// thing->get->weather
// sky->get->weather
展开对象时,依次获取对象上各自的属性,并赋值到新对象上,这一过程都调用其get方法。因为新对象上并没有对应的访问器属性,所以不会调用set。