属性描述符
对象属性除了拥有值以外,还有其他特性,比如:
let user = {
name: 'Tom',
age: 18,
}
let nameDesc = Object.getOwnPropertyDescriptor(user, 'name');
console.log(nameDesc);
// { value: 'Tom', writable: true, enumerable: true, configurable: true }
这里的Object.getOwnPropertyDescriptor方法可以获取对象指定属性的所有可配置项:
-
value:值
-
writable:是否可写
-
enumerable:是否在迭代属性时被读取
-
configurable:是否可以删除属性并修改这些配置
使用Object.defineProperty方法可以修改属性的配置项:
Object.defineProperty(user, 'name', {
value: 'Jerry',
writable: false,
enumerable: false,
configurable: false,
});
nameDesc = Object.getOwnPropertyDescriptor(user, 'name');
console.log(nameDesc);
// {
// value: 'Jerry',
// writable: false,
// enumerable: false,
// configurable: false
// }
如果属性不存在,会被创建:
Object.defineProperty(user, 'sex', {
value: 'male'
});
console.log(user.sex);
let sexDesc = Object.getOwnPropertyDescriptor(user, 'sex');
console.log(sexDesc);
// male
// {
// value: 'male',
// writable: false,
// enumerable: false,
// configurable: false
// }
此时特殊配置项默认都是false。
writable
可以将属性设置为只读:
let user2 = {
name: 'Tom',
}
Object.defineProperty(user2, 'name', {
writable: false
})
user2.name = 'Jerry';
console.log(user2.name);
// Tom
这里并没有报错,只不过重新修改属性的代码没有生效。原因是 JS 只会在严格模式下在此情况下报错,否则不会报错,但重新指定属性内容的代码无效。
enumerable
有时候我们可能不希望某个对象属性被遍历,比如:
let user3 = {
name: 'Tom',
toString () {
return `${this.name}`;
}
}
for(let key in user3){
console.log(key);
}
// name
// toString
可以通过属性配置实现这一点:
let user4 = {
name: 'Tom',
toString () {
return `${this.name}`;
}
}
Object.defineProperty(user4, 'toString', {
enumerable: false
})
for(let key in user4){
console.log(key);
}
// name
configurable
如果configurable被设置为false,属性就不能重复被Object.defineProperty修改配置,且无法被delete删除:
let user5 = {
name: 'Tom',
}
Object.defineProperty(user5, 'name', {
configurable: false,
writable: false,
})
delete user5.name;
console.log(user5.name);
// Tom
Object.defineProperty(user5, 'name', {
writable: true,
})
// TypeError: Cannot redefine property: name
可以利用这一点设置一些只读属性:
let MyMath = {
pi: 3.14
}
Object.defineProperty(MyMath, 'pi', {
writable: false,
configurable: false,
})
MyMath.pi = 3.15;
console.log(MyMath.pi);
// 3.14
defineProperties
可以使用defineProperties一次定义多个属性的可配置项:
let user6 = {
}
Object.defineProperties(user6,{
name: {
value: 'Tom',
writable: false,
enumerable: true,
configurable: true,
},
age: {
value: 18,
writable: true,
enumerable: true,
configurable: true,
}
})
getOwnPropertyDescriptors
用普通的方式复制属性时,只能复制值,不会复制属性的配置项:
let user7 = {
name: 'Tom',
age: 18,
sex: 'male',
toString () {
return `${this.name} ${this.age} ${this.sex}`;
}
};
Object.defineProperties(user7, {
name: {
writable: false,
enumerable: true,
configurable: true,
},
toString: {
enumerable: false,
}
});
let user8 = {};
for(let key in user7){
user8[key] = user7[key];
}
let user8propertyDesc = Object.getOwnPropertyDescriptors(user8);
console.log(user8propertyDesc);
// {
// name: {
// value: 'Tom',
// writable: true,
// enumerable: true,
// configurable: true
// },
// age: { value: 18, writable: true, enumerable: true, configurable: true },
// sex: {
// value: 'male',
// writable: true,
// enumerable: true,
// configurable: true
// }
// }
不仅仅是无法复制属性配置,如果属性被设置为enumerable:false,无法被遍历,值也不会被复制。
可以借助getOwnPropertyDescriptors实现属性复制,该方法会返回对象所有属性的配置项:
let user9 = {};
Object.defineProperties(user9, Object.getOwnPropertyDescriptors(user7));
let user9propertyDesc = Object.getOwnPropertyDescriptors(user9);
console.log(user9propertyDesc);
Getter/Setter
JS 中属性被分为普通的值属性以及访问器属性,利用访问器属性可以对现有代码进行重构,以“虚构”出一个新的属性,比如:
let name = {
firstName: 'Tom',
lastName: 'Jerry',
get fullName () {
return `${this.firstName} ${this.lastName}`;
},
set fullName (value) {
let names = value.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
}
console.log(name.fullName);
// Tom Jerry
name.fullName = 'Jeck Chen';
console.log(name.firstName);
console.log(name.lastName);
// Jeck
// Chen
这个示例中并没有一个fullName这样的值属性,而是通过一个 Getter 方法和一个 Setter 方法利用已有的两个值属性实现了一个新的访问器属性fullName,这个访问器属性可以像普通属性那样读写。
访问器属性描述符
访问器属性同样有属性描述符,可以配置的内容:
-
get,Getter 方法
-
set,Setter 方法
-
enumerable
-
configurable
通过属性描述符的方式添加访问器属性:
let name2 = {
firstName: 'Tom',
lastName: 'Jerry',
};
Object.defineProperty(name2, 'fullName', {
get () {
return `${this.firstName} ${this.lastName}`;
},
set (value) {
let names = value.split(' ');
this.firstName = names[0];
this.lastName = names[1];
}
});
console.log(name2.fullName);
// Tom Jerry
需要注意的是,一个属性不能即是值属性又是访问器属性:
Object.defineProperty(name2, 'attr', {
get() {
return this.firstName;
},
value: 'Tom',
});
// TypeError: Invalid property descriptor. Cannot both specify accessors and a value or writable attribute, #<Object>
包装器
除了利用 Getter/Setter 添加新属性外,对已有属性进行重构,并为其添加新的特性也是常见用途:
let person = {
name: undefined
};
person.name = 'Tom';
console.log(person.name);
这里的name属性读写没有任何限制,如果我们希望写入name时进行检查,过短的姓名不应该被写入:
let person2 = {
_name: undefined,
get name () {
return this._name;
},
set name (value) {
if(!value){
throw new Error('Name cannot be empty');
}
if(value.trim().length <= 4){
throw new Error('Name is too short');
}
this._name = value;
}
};
person2.name = 'Tom';
// Error: Name is too short
这里将原属性名前添加_,表示这是一个私有属性,不应该直接被访问,原属性名name用于定义访问器属性,读取该访问器属性的时候检查参数是否满足要求。
The End.
本文的完整示例可以从获取。

文章评论