在我们平常的使用中,属性对我们来说只是一个简单的”键值对”,但对象属性实际上还有更多强大的东西。
先来看一个例子
let user = {
name: 'Steven'
}
复制代码
声明了一个user对象,name就是它的一个属性,’Steven’是属性值(value)
但其实,这只是表面的东西,里面其实还有属性标志和属性描述符。
那么什么是属性标志和属性描述符呢?
属性标志
对象属性(properties),除 value 外,还有三个特殊的特性(attributes),也就是所谓的“标志”:
- writable — 是否可写,如果为 true,则值可以被修改,否则它是只可读的。
- enumerable — 是否可枚举,如果为true,则会被在循环(for..in)或Object.keys()中列出,否则不会被列出。
- configurable — 是否可配置,如果为true,则此特性可以被删除,这些属性也可以被修改,否则不可以。
当我们创建一个属性时,它们默认为true。
好,现在我们知道了什么是属性标志,那么如何获取呢?
属性标志的获取
方法是: Object.getOwnPropertyDescriptor(obj, propertyName)
第一个参数是需要从中获取信息的对象
第二参数是属性的名称
let user = {
name: 'Steven'
}
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
console.log(descriptor);
/* 输出结果,属性描述符
{
value: 'Steven',
writable: true,
enumerable: true,
configurable: true
}
*/
复制代码
这里输出的结果就是属性描述符了,可以看到它们全都都是true,现在我们来修改它们
属性标志的操作方法
Object.defineProperty
修改单个属性标志的方法是Object.defineProperty(obj, propertyName, descriptor)
obj:修改的对象
propertyName:修改的属性
descriptor:要应用的属性描述符对象
看个例子就明白怎么用了:
let user = {
name: 'Steven'
}
Object.defineProperty(user, 'name', {
value: '史蒂文',
writable: false,
enumerable: false,
configurable: false
})
let descriptor = Object.getOwnPropertyDescriptor(user, 'name');
console.log(descriptor);
/* 输出结果,属性描述符
{
value: '史蒂文',
writable: false,
enumerable: false,
configurable: false
}
*/
复制代码
这里我一次性修改了四个属性描述符,当然你可以只修改你想修改的那一个或多个都行。
可以看出,四个值都被成功修改了。如果想一次性修改多个属性,就可以用下面的方法。
Object.defineProperties
Object.defineProperties(obj, descriptors)允许一次定义多个属性。
obj:要修改的对象
descriptors:多个属性描述符
用法如下:
let user = {
name: 'Steven',
age: 22
}
Object.defineProperties(user, {
name: {value: '史蒂文', writable: false},
age: {value: 18, writable: false}
})
复制代码
这里只修改了 value 和 writable。所以,我们是可以一次性设置多个属性的。
而要一次获取所有属性描述符,我们可以使用Object.getOwnPropertyDescriptors(obj)方法。
这里就不在演示了。
当然了,这两个方法不仅仅可以修改已有的属性,也可以动态的添加没有的属性。
let user = {
name: 'Steven',
age: 22
}
Object.defineProperty(user, 'job', {
value: 'teacher',
writable: true,
enumerable: true,
configurable: true
})
console.log(user);
// { name: 'Steven', age: 22, job: 'teacher' }
复制代码
可以看到成功添加了一个job属性。
属性的getter 和 setter
有两种类型的对象属性。
第一种是 数据属性。我们已经知道如何使用它们了。到目前为止,我们使用过的所有属性都是数据属性。
第二种类型的属性是新东西。它是 访问器属性(accessor properties)。它们本质上是用于获取和设置值的函数,但从外部代码来看就像常规属性。
getter 和 setter
访问器属性由 “getter” 和 “setter” 方法表示,在对象字面量中,它们用 get 和 set 表示:
let obj = {
get userName() {
// 当读取 obj.userName 时,getter 起作用
},
set userNmae() {
// 当执行 obj.userNmae = value 操作时,setter 起作用
}
}
复制代码
当读取 obj.userName 时,getter 起作用,当 obj.userNmae 被赋值时,setter 起作用。
例如,我们有一个具有 firstName 和 lastName 属性的对象 user:
let user = {
firstName: 'Lebron',
lastName: 'James',
}
复制代码
现在我们想添加一个 fullName 属性,该属性值应该为 “Lebron James”。当然,我们不想复制粘贴已有的信息,因此我们可以使用get来实现:
let user = {
firstName: 'Lebron',
lastName: 'James',
get fullName() {
return `${this.firstName} ${this.lastName}`
},
}
console.log(user.fullName); // Lebron James
复制代码
访问 get 属性 ,不需要通过像函数一样user.fullName()来调用,只需user.fullName就行了,因为背后有 getter 在运行。
同理,我们添加一个 set 属性
let user = {
firstName: 'Lebron',
lastName: 'James',
get fullName() {
return `${this.firstName} ${this.lastName}`
},
set fullName(value) {
[this.firstName, this.lastName] = value.split(" ");
}
}
user.fullName = 'Steven Jobs'
console.log(user.fullName); // Steven Jobs
复制代码
这样就有了一个”虚拟”的 fullName 属性,并且可读可写
访问器描述符
访问器属性的描述符与数据属性的不同。
对于访问器属性,没有 value 和 writable,但是有 get 和 set 函数。
所以访问器描述符可以有:
- get —— 一个没有参数的函数,在读取属性时工作
- set —— 带有一个参数的函数,当属性被设置时调用
- enumerable —— 与数据属性的相同
- configurable —— 与数据属性的相同
注意:一个属性要么是访问器(具有 get/set 方法),要么是数据属性(具有 value),但不能两者都是。
应用
实现对象的深克隆
通常,当我们克隆一个对象时,我们使用赋值的方式来复制属性,像这样:
let user = {
name: 'Steven',
age: 22
}
// 让 name 不可被修改
Object.defineProperty(user, 'name', {
writable: false
})
let clone = {}
for(let key in user) {
clone[key] = user[key]
}
// 查看拷贝后的属性描述符
let desc = Object.getOwnPropertyDescriptor(clone, 'name')
console.log(desc);
/*{
value: 'Steven',
writable: true, // 这里我们明明改成了false,复制后还是默认为true
enumerable: true,
configurable: true
}*/
复制代码
这样并不完美,无法拷贝属性描述符,更完美的拷贝方式如下:
let clone = Object.defineProperties({}, Object.getOwnPropertyDescriptors(user));
let desc = Object.getOwnPropertyDescriptor(clone, 'name')
console.log(desc);
/*{
value: 'Steven',
writable: false,
enumerable: true,
configurable: true
}*/
复制代码
可以看到属性描述符也拷贝过来了!
Vue2.0数据响应式实现原理
这里笔者也在学习中,有很多大佬都已经分析过,可以看看他们的文章
当面试官问你Vue响应式原理,你可以这么回答他
当然,还有很多应用场景,等待我们继续学习~