前言:
- 我们知道js对象中可以存储属性和方法,对于存储的属性不只是”键值对”,对象属性实际上还有更加灵活强大的东西
- 下面我们来看一下对象属性到底还有声明nb功能呢?
属性的分类
- 对象属性有两种类别
- 数据属性:键值对形式
- 访问器属性:用于获取和设置值的函数
属性标志和属性描述符
对象属性,除了**
value
** 外,还有三个特殊的特性(attributes)writable
— 如果为true
,则值可以被修改,否则它是只可读的。enumerable
— 如果为true
,则会被在循环中列出,否则不会被列出。configurable
— 如果为true
,则此特性可以被删除,这些属性也可以被修改,否则不可以。
我们使用“常用的方式”创建的属性都是
true
如何获取这些标识/特性?
- Object.getOwnPropertyDescriptor 方法允许查询有关属性的 完整 信息。
//let descriptor = Object.getOwnPropertyDescriptor(obj, propertyName);
//obj : 需要获取信息的对象
//propertyName: 属性的名称
//返回值: 所谓的“属性描述符对象“ 包含值和所有标志
let user = {
name: "John"
};
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/* 属性描述符:
{
"value": "John",
"writable": true,
"enumerable": true,
"configurable": true
}
*/
如何修改这些标志呢?
// Object.defineProperty(obj, propertyName, descriptor)
// obj, propertyName: 对象及其属性
// descriptor: 应用的属性描述符"对象"
let user = {};
Object.defineProperty(user, "name", {
value: "John" //没有对应属性就新建一个对应属性
//其他属性不写默认 false
});
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": "John",
"writable": false,
"enumerable": false,
"configurable": false
}
*/
属性描述符的功能
1.只读writable
- 将
writable
谁行标志设置为false
,就将对应属性设置为只读属性
let user = {
name: "minyue"
}
Object.defineProperty(user, "name", {
writable: false
});
alert(user.name); //minyue
user.name = "hcm"; //在严格模式下就会出错,非严格模式忽略
- 对于一个新属性,我们需要明确列出那些
true
,未列出的默认false
- 对于一个常规定义的属性,默认都是
true
,改什么就变什么
2.不可枚举enumerable
对象的枚举,我们通过
for in
语句,是否能枚举对应的键通常情况下,对象内置的
toString
是不可枚举的,当我们在对象中,创建的属性或者方法,也不想被枚举出就可以设置enumerable:false
同样不可枚举的属性也会被 object.keys(obj) (返回键的数组)
let user = {
name: "minyue",
toString() {
return this.name;
}
};
Object.defineProperty(user, "toString", {
enumerable: false
});
for (let key in user) alert(key); //name
alert(Object.keys(user)); //name
3.不可配置
- 不可配置
configurable:false
有时会预设在内建对象和属性中 - 不可配置的属性思想:防止更改属性标志或删除属性标志
- 特殊点:不可配置的可写属性是可以修改属性的值的!!!
//例如:Math.PI 只读,不可枚举,不可配置
let descriptor = Object.getOwnPropertyDescriptor(Math, 'PI');
alert( JSON.stringify(descriptor, null, 2 ) );
/*
{
"value": 3.141592653589793,
"writable": false,
"enumerable": false,
"configurable": false
}
*/
Math.PI = 3; // Error
批量获取/修改属性标志符
Object.defineProperties(user, {
name: { value: "John", writable: false },
surname: { value: "Smith", writable: false },
// ...
});
//克隆对象:首选方法,不只是值,还有属性描述符等
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(obj));
扩展:学到这里,我们知道,属性描述符都是单一属性级别上的工作,要想限制访问整个对象,参考
属性的getter和setter
getter和setter
- 看完数据属性,下面我们来看一下访问器属性
- 访问器属性:本质上是用于获取和设置值的函数
访问器属性由 “getter” 和 “setter” 方法表示。在对象字面量中,它们用 get
和 set
表示:
let user = {
name: "John",
surname: "Smith",
get fullName() {
// 当读取 obj.propName 时,getter 起作用
return `${this.name} ${this.surname}`;
},
set fullName(value) {
// 当执行 obj.propName = value 操作时,setter 起作用
[this.name, this.surname] = value.split(" ");
}
};
user.fullName = "Alice Cooper"; // 调用setter
alert(user.fullname); // Alice Cooper
alert(user.surname); // Cooper
访问器描述符
get
—— 一个没有参数的函数,在读取属性时工作,set
—— 带有一个参数的函数,当属性被设置时调用,enumerable
—— 与数据属性的相同,configurable
—— 与数据属性的相同。
使用 defineProperty
创建一个 fullName
访问器
let user = {
name: "John",
surname: "Smith"
};
Object.defineProperty(user, 'fullName', {
get() {
return `${this.name} ${this.surname}`;
},
set(value) {
[this.name, this.surname] = value.split(" ");
}
});
alert(user.fullName); // John Smith
for(let key in user) alert(key); // name, surname
巧妙的getter和setter
- Getter/setter 可以用作“真实”属性值的包装器,以便对它们进行更多的控制。
//例如:当我们需要输入用户名不能太短时
let user = {
get name() {
return this._name;
},
set name(value) {
if (value.length < 4) {
alert("Name is too short, need at least 4 characters");
return;
}
this._name = value;
}
};
user.name = "Pete";
alert(user.name); // Pete
user.name = ""; // Name 太短了……
- 访问器属性本身就是两个函数,没有值,存值一般会有一个内部数据属性存值!!(例如这里的
_name
)
访问器属性实例
- 访问器的一大用途是,它们允许随时通过使用 getter 和 setter 替换“正常的”数据属性,来控制和调整这些属性的行为。
// 对于年龄这样的变化数据,我们希望存储birthday而不是age
// 这里我们就可以使用访问器属性,动态获取修改对象的age属性
function User(name, birthday) {
this.name = name;
this.birthday = birthday;
// 年龄是根据当前日期和生日计算得出的
Object.defineProperty(this, "age", {
get() {
let todayYear = new Date().getFullYear();
return todayYear - this.birthday.getFullYear();
}
});
}
let john = new User("John", new Date(1999, 10, 29));
alert( john.birthday ); // Mon Nov 29 1999 00:00:00 GMT+0800 (中国标准时间)
alert( john.age ); // 21