Object.defineProperty
Object.defineProperty的基本使用
作用:给一个对象添加属性
参数:接受三个参数:
- 给哪个对象添加属性
- 添加的属性名
- 配置项
let person = {
name: '张三',
sex: '男',
};
Object.defineProperty(person, 'age', {
value: '18'
})
console.log(person);
此时,会发现新添加age属性的颜色与person本身的属性颜色不同,会稍微淡一些,这是表示新添加的age属性是不可以被枚举的,也就是说不可以被遍历。
验证:使用Object.keys()来遍历,Object.keys可以将传入对象里面所有属性的属性名提取出来变为一个数组。
console.log(Object.keys(person));
使用for… in… 也是同样的结果(既能遍历数组也能遍历对象)
for (let key in person){
//输出person对象每一个属性的属性值
console.log(person[key]);
}
那么如何能够让使用Object.defineProperty添加的属性也能遍历呢?只需要在添加时加上enumerable属性即可
- enumberable: 控制属性是否可以枚举;默认值是false
- writable:控制属性是否可以被修改,默认值为fasle
- configurable:控制属性是否可以被删除,默认值为false
Object.defineProperty的应用
先有一需求,有一个对象person,和一个变量number,现在需要给这个对象person添加属性age,属性值为number
方法一:直接添加
let number = 18;
let person = {
name: '张三',
sex: '男',
};
person.age = number;
console.log(person);
这样当然可以,但是当我们修改number后,age并不会随之而改变
除非再次给赋值: person.age = number;
那么如何不赋值就能让person的age随之number的改变而改变呢?这时就需要使用Object.defineProperty了
方法二:使用Object.defineProperty
Object.defineProperty的配置项可以写一个配置set和get
get:当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
let number = 18;
let person = {
name: '张三',
sex: '男',
};
Object.defineProperty(person, 'age', {
get: function () {
return number;
}
})
console.log(person);
此时,输出person,会发现age属性被折叠起来了,并且鼠标悬浮会有‘Invoke property getter ’的提示,意思为age属性的值由getter映射而来。并且,这个对象身上还有一个为age服务的getter
此时,修改number后,age也会随之而改变;因为当每一次访问age,都会触发getter的调用
set:当有人修改person的age属性时,set函数(setter)就会被调用,且会收到修改的具体值
let number = 18;
let person = {
name: '张三',
sex: '男',
};
// person.age = number;
// console.log(person);
Object.defineProperty(person, 'age', {
get: function () {
console.log('有人读取age属性了!')
return number;
},
set(value) {
console.log('有人修改了age属性,且值是' + value);
}
})
console.log(person);
此时,修改person.age = 19时,会发现,person的age属性并没有被改变,仍然是18
这是因为person的age属性依赖于number,number并没有被改变,因此person.age也就没有被改变,所以set应该写为
set(value) {
console.log('有人修改了age属性,且值是' + value);
number = value;
}
这个时候number和person.age相互影响,修改一个,另一个也会随之更改。
数据代理
定义:通过一个对象代理另一个对象中属性的操作(读/写)就叫数据代理
为什么要使用数据代理
之所以使用代理,就是不希望用户能够直接访问某个对象,直接操作对象的某个成员(因为这样是不可控的,我们不知道用户在访问操作哪一个对象)
通过代理,我们可以拦截用户的访问(称为数据劫持),拦截住后我们就可以对数据进行一些处理,比如做一些数据的验证或者像Vue一样做一些视图更新的额外操作,之后再允许用户的访问操作(因为我们拦截了用户的每一次访问,这样用户操作对象就完全是在我们可控的范围内)
理解数据代理
有两个对象obj1,obj2;现在想要让对象obj2也访问到对象obj1中的属性x,并且可以修改这个属性x;这就是数据代理,通过obj2去代理obj1对象的中属性,并对其进行操作。
代码实现:
let obj1 = { x: 100};
let obj2 = { y: 200};
Object.defineProperty(obj2, 'x', {
get() {
return obj1.x;
},
set(value) {
obj1.x = value;
}
})
console.log(obj2);
当修改了obj2身上的x,obj1身上的x也会随之改变
vue中的数据代理
<body>
<div id="root">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data: {
name: '北京大学',
address: '北京',
}
})
console.log(vm);
</script>
</body>
将Vue实例vm输出:
会发现data中的变量会在实例vm上,当鼠标移入data中的变量时,会有‘Invoke property getter ’的提示,因此,实例vm上属于data中的变量实际上是由Object.defineProperty()方法定义上去的。
也就是说当访问实例vm身上的name时,getter工作,将某个地方的name取出来给vm,当修改实例vm身上name时,setter工作,将某个地方的name也修改掉;这个地方就是data,修改的也就是data里的name。
验证两条线
getter
修改代码中data的名称,vm身上的data随之而改变
setter
修改vm身上的name
此时,data中的name是多少呢?在判断data中的name时,首先要拿到data中的name。是通过data.name吗?
显然不是,data只是配置对象中的一个属性,并不是一个变量。那么该如何拿到data中的name呢?
我们知道new Vue时传入的配置对象都是为vm实例所服务的,那么vm实例必然会将这些配置的属性存下来,而vm在存data时则使用_data这个属性名,将其存在vm自身。验证一下:
setter这条线验证完毕
数据代理的目的就是为使编码更方便,否则,页面中的插值语法里写的不是{{ name }},而是{{ _data.name }}
总结:
-
Vue中的数据代理
- 通过vm对象来代理data对象中属性的操作(读/写) Vue中数据代理的好处
- 更加方便的操作data中的数据 基本原理
- 通过Object.defineProperty()把data对象中所有属性添加到vm上。为每一个添加到vm上的属性,都指定一个getter/setter。在getter/setter内部去操作(读/写)data中对应的属性。
补充
当我们输出vm时,会发现_data中的属性也有getter和setter方法,这不是数据代理,而是数据劫持;使用getter和setter方法去修改_data中的属性是为了实现响应式
想要实现红色的线,就必须让Vue可以监测到data中的name,而数据劫持就是为了做这个功能的