目录
6. 数据代理
学习数据代理前需要js的一些知识:Object.defineProperty(),属性标志,属性描述符,getter,setter。。。
建议学习以下文章:
6.1 回顾Object.defineProperty()方法
定义:Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法:Object.defineProperty(obj, prop, descriptor)
参数一:obj:要定义属性的对象。
参数二:prop:要定义或修改的属性的名称或
Symbol
。参数三:descriptor:要定义或修改的属性描述符(数据描述符和存取描述符)。
- 属性描述符有两种主要形式:数据描述符和存取描述符。数据描述符是一个具有值的属性,该值可以是可写的,也可以是不可写的。存取描述符是由
getter
函数和setter
函数所描述的属性。一个描述符只能是这两者其中之一;不能同时是两者。两种描述符都是对象。
两种描述符共享以下可选键值:
enumerable
—> 默认值是false
,如果为true
, 控制属性则可以枚举(枚举是指对象中的属性是否可以被遍历),可以在for.. .in
、Object.keys()
中遍历出来。configurable
—> 默认值是false
,如果为true
, 控制属性则可以被删除,否则不可以。
数据描述符还具有以下可选键值:
value
—> 该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为undefined
。writable
—> 默认值是false
,如果为true
,控制属性则可以被修改,否则只是可读的。
存取描述符还具有以下可选键值:
get
—> 属性的 getter 函数,如果没有 getter,则为undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值。 默认为undefined
。set
—> 属性的 setter 函数,如果没有 setter,则为undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的this
对象。 默认为undefined
。
示例代码
<script type="text/javascript">
let number = 18;
let person = {
name: '张三',
sex: '男',
}
Object.defineProperty(person, 'age', {
// value: 18,
// enumerable: true, // 控制属性是否可以枚举(枚举是指对象中的属性是否可以被遍历),默认值是false
// writable: true, // 控制属性是否可以被修改,默认值是false
// configurable: true, // 控制属性是否可以被删除,默认值是false
// 当有人读取person的age属性时,get函数(getter)就会背调用
get() {
console.log('有人读取了age属性');
return number
},
// 当有人修改person的age属性时,set函数(setter)就会背调用
set(value) {
console.log('有人修改了age属性,且值是:', value);
number = value;
}
});
// console.log(person);
// console.log(Object.keys(person));
</script>
6.2 什么是数据代理
数据代理:通过一个对象代理对另一个对象中属性的操作(读/写),就是数据代理。
先来看个案例:
let obj1 = {
x: 100 }
let obj2 = {
y: 200 }
这时候提一个需求:我们想要访问 obj1
中的 x
的值,但我们最好不要直接去访问 obj1
,而是想要通过 obj2
这个代理对象去访问。
这时候就可以用 Object.defineProperty()
方法,给 obj2
添加上存取描述符(也叫访问器属性,就是getter
和setter
)
示例代码
let obj1 = {
x: 100 }
let obj2 = {
y: 200 }
Object.defineProperty(obj2, "x", {
// 当读取obj2的x属性时,get函数(getter)就会被调用,且返回值就是x的值
get() {
return obj1.x;
},
// 当修改obj2的x属性时,set函数(setter)就会被调用,且会收到一个具体修改的值value
set(value) {
obj1.x = value;
}
})
// 验证以上代码
console.log(obj1.x); // 输出结果:100
console.log(obj2.x);// 输出结果:100
console.log(obj2); // 输出结果:{ y: 200} obj2下还有 x:300及get和set方法
obj2.x = 300;
console.log(obj1.x); //输出结果:300
这就是数据代理,简单吧!
6.3 Vue中的数据代理
- Vue中的数据代理:通过vm对象来代理data对象中属性的操作(读/写)
- Vue中数据代理的好处:更加方便的操作data中的数据
- 基本原理:
- 通过
Object.defineProperty()
把data对象中所有属性添加到vm上。 - 为每一个添加到vm上的属性,都指定一个
getter/setter
。 - 在
getter/setter
内部去操作(读/写)data中对应的属性。
- 通过
下面用一个案例来详细解释这个过程
<div id="app">
<h1>网站名称:{
{name}}</h1>
<h1>网站地址:{
{addrss}}</h1>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data() {
return {
name: '百度',
addrss: 'https://www.baidu.com/',
}
}
});
console.log(vm); // 输出结果:Vue实例对象
</script>
打印的 vm 在浏览器中如下:
从以上图中可以看到,写在配置项中的 data 数据被绑定到了 vm 实例对象中,结果就是,Vue 将 _data 中的name、addrss 使用数据代理到 vm 本身上。
那 _data 是个啥?
解释一波,_data 就是 vm 身上的 _data 属性,具体见上图的红色框下面第二行。
那这个 _data 又是哪里来的呢?
在解释一波,_data 就是下面代码中 data 内的数据,只是在 new Vue 时进行了一些处理。
<script type="text/javascript">
const vm = new Vue({
el: '#app',
data() {
return {
name: '百度',
addrss: 'https://www.baidu.com/',
}
}
});
</script>
new Vue 时, Vue 通过一系列处理, 将匹配项上的 data 数据绑定到了 _data 这个属性上,并对这个属性进行了处理(数据劫持),但这个属性就是来源于配置项中的 data,我们可以来验证一下。
<script>
let data1 = {
name: '百度',
addrss: 'https://www.baidu.com/',
}
const vm = new Vue({
el: '#app',
// 我们在 Vue 初始化的配置项中写了 data 属性。
data: data1
})
console.log(vm._data === data1); // 输出结果:true
</script>
输出结果为true,说明两者就是同一个。
好了,再回到数据代理上来,将 vm._data 中的值,再代理到 vm 本身上来,用 vm.name 代替 vm._data.name。这就是 Vue 的数据代理。
我们来验证一下:
console.log(vm._data.name === vm.name); // 输出结果:true
console.log(vm._data.address === vm.address); // 输出结果:true
输出结果都为true。
这一切都是通过 Object.defineProperty()
方法来完成的,来模拟一下这个过程
Object.defineProperty(vm, 'name', {
get() {
return vm._data.name;
},
set(value) {
vm._data.name = value
}
})
到这可能就会有疑问了,明明可以通过 vm._data.name 访问 name 的值,为啥要费力去这样操作?
在Vue的插值语法中,{ { name }} 取到的值就相当于 { { vm.name }},不用数据代理的话,在插值语法中就要这样去写了。
<div id="app">
<h1>网站名称:{
{_data.name}}</h1>
<h1>网站地址:{
{_data.addrss}}</h1>
</div>
{ { _data.name }} 这种写法是不是感觉怪怪的。其实 { { name }} 这种写法更利于开发者开发,Vue 也是这样设计的,只不过我们在研究原理时会觉得有些复杂。
好了,不在磨磨唧唧了,直接上示意图:
7.事件处理
7.1 事件的基本使用
事件的基本使用:
- 使用
v-on:xxx
或@xxx
绑定事件,其中 xxx 是事件名。 - 事件的回调需要配置在 methods 对象中,最终会在 vm 上。
- methods中配置的函数,不要用箭头函数,否则 this 就不是 vm 了。
- methods中配置的函数,都是被Vue所管理的函数,this 的指向是 vm 或组件实例对象。
@click="demo"
和@click="demo($event)"
效果一致,但后者可以传参。
<div id="app">
<h1>欢迎来到{
{name}}</h1>
<button v-on:click="showInfo">点我提示信息(不简写)</button