通过Object.defineProperty()来分析Vue2中的数据代理
Object.defineProperty
Object :对象 / define:定义/ Property:属性
Object.defineProperty 作用是什么?
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
语法:
// obj: 要定义属性的对象
// prop:要定义或修改的属性的名称
// descriptor:要定义或修改的属性描述符
Object.defineProperty(obj, prop, descriptor)
注意:该方法只能是在Object上调用 而不是在Object的实例上调用
Object.defineProperty() ✔
a.defineProperty() ×
1.增加和修改
1.修改属性
let a = {
name:"小明",
age:18
}
let b = {
name:"小红",
age:12
}
Object.defineProperty(a,"name",{
value:"小芳"
})
console.log(a);
2.增加属性
let a = {
name:"小明",
age:18
}
let b = {
name:"小红",
age:12
}
Object.defineProperty(a,"hobby",{
value:"上网"
})
console.log(a);
2.属性描述符(decriptor)
对象里目前存在的属性描述符(descriptor)有两种主要形式:数据描述符和存取描述符。一个描述符只能是这两者其中之一;不能同时是两者。
这两种描述符都是对象。它们共享以下可选键值(默认值是指在使用 Object.defineProperty()
定义属性时的默认值:
configurable
当且仅当该属性的 configurable
键值为 true
时,该属性的描述符才能够被改变,同时该属性也能从对应的对象上被删除。 默认为 false
。(configurable
特性表示对象的属性是否可以被删除,以及除 value
和 writable
特性外的其他特性是否可以被修改。)
先回忆一下如何删除对象上的一个属性:
let a = {
name:"小明",
age:18
}
delete a.name;
console.log(a); //只剩下 age:18
1.没有加configurable 去删除属性 发现删除失败
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"hobby",{
value:"上网"
})
delete a.hobby;
console.log(a);
2.加configurable后 去删除属性 发现删除成功
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"hobby",{
value:"上网",
configurable:true
})
delete a.hobby;
console.log(a);
enumerable
当且仅当该属性的 enumerable
键值为 true
时,该属性才会出现在对象的枚举属性中。 默认为 false
。(enumerable
定义了对象的属性是否可以在 for...in
循环和 Object.keys()
中被枚举。)
1.没有加enumerable 用for...in 去遍历对象 发现hobby属性遍历失败
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"hobby",{
value:"上网",
})
for(let key in a){
console.log(a[key]);
}
console.log(a);
1.加enumerable后 用for...in 去遍历对象 发现hobby属性遍历成功
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"hobby",{
value:"上网",
enumerable:true,
})
for(let key in a){
console.log(a[key]);
}
console.log(a);
数据描述符还具有以下可选键值:
value
该属性对应的值。可以是任何有效的 JavaScript 值(数值,对象,函数等)。 默认为 undefined。
writable
当且仅当该属性的 writable
键值为 true
时,属性的值,也就是上面的 value
,才能被赋值运算符
改变。 默认为 false
。 (当 writable
属性设置为 false
时,该属性被称为“不可写的”。它不能被重新赋值。)
```
1.当没有配置 writable时 改写属性失败
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"hobby",{
value:"上网"
})
a.hobby = "吃饭";
console.log(a);
2.配置 writable后 改写属性成功
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"hobby",{
value:"上网",
writable:true
})
a.hobby = "吃饭";
console.log(a);
存取描述符还具有以下可选键值:
get
属性的 getter 函数(getter是啥意思 就是get 加后面函数体里面的内容 总称 getter),如果没有 getter,则为 undefined
。当访问该属性时,会调用此函数。
执行时不传入任何参数,但是会传入 this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。
该函数的返回值会被用作属性的值。
默认为 undefined
。
```
let a = {
name:"小明",
age:18
}
const aihao = "上网"
Object.defineProperty(a, "hobby", {
get(){
//get 函数(getter)何时执行?—— 有人读取 a 对象的 hobby 属性时执行
// getter是啥意思 就是get 加后面函数体里面的内容 总称 getter
//点击打印后的hobby后的 ... 就调用了get 输出了this
//get 函数(getter)中的this是谁? —— 当前对象(a)
console.log("getter函数中的this==>"this);
return aihao
}
})
console.log(a);
console.log(a.hobby);
```
set
属性的 setter 函数(setter是啥意思 就是set 加后面函数体里面的内容 总称 setter),如果没有 setter,则为 undefined
。当属性值被修改时,会调用此函数。
该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this
对象。 默认为 undefined
。
```
let a = {
name:"小明",
age:18
}
let online = "上网"
Object.defineProperty(a, "hobby", {
get(){
return aihao
},
// set函数(setter)何时会被调用? —— 有人修改 a 对象的 hobby 属性时执行
// set函数(setter)中的 this 是谁? —— 当前对象(a)
set(value){
console.log('有人修改了person的age属性,值为:', value, this);
online = value
}
})
a.hobby = "吃饭";
console.log(a);
```
扩展知识一
有细心的小伙伴应该有发现 (descriptor属性描述符)中的writable
,configurable
,enumerable
这些描述符中的可选键对Object实例上原本的属性 好像不奏效
例如:删除a对象上的name属性 此时在Object.defineProperty中描述符
configurable应该是默认值false 也就是无法删除 但结果却删除了
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"name",{
value:"小芳"
})
delete a.name;
console.log(a);
//原因是 object={属性:属性值} object.属性 = 属性值 object["属性"] = 属性值
这种方式写出来的就相当于
let a = {};
a.age = 1;
Object.defineProperty(a,"age",{
value:1,
writale:true,
configurable:true,
enumerable:true
})
//而Object.defineProperty(a, "age", { value : 1 });
这种在a对象上新增age属性的方式就等同于
Object.defineProperty(a, "age", {
value: 1,
writable: false,
configurable: false,
enumerable: false
});
现在我们改下代码:
let a = {
name:"小明",
age:18
}
Object.defineProperty(a,"name",{
value:"小芳",
configurable:false,
})
delete a.name;//name属性 删除失败
console.log(a);
扩展知识二
在最开始写描述符时有这么一句话 对象里目前存在的属性描述符(descriptor)有两种主要形式:数据描述符和存取描述符。一个描述符只能是这两者其中之一;不能同时是两者。
描述符的可选键值:
- 共享可选键值:
configurable
,enumerable
- 数据描述符可选键值:
value
,writable
- 存取描述符可选键值:
get
,set
如果一个描述符同时拥有 value
或 writable
和 get
或 set
键,则会产生一个异常。
Object.defineProperty(a, "hobby", {
enumerable:true,
writable:true,
get(){
return aihao
},
})
//报错 TypeError 类型错误:无效的属性描述符。
//不能同时指定访问器和值或可写属性,在函数.defineProperty
无用的小知识
我们在用Object.defineProperty()去添加一个新属性 发现属性打印出来的颜色跟原本的属性颜色不同
当我们在描述项中加入 enumerable:true(可以被枚举) 发现新属性和原本属性的颜色变为一样了
数据代理
什么是数据代理?
通过一个对象(a) 代理 对另一个对象(b) 中属性 (age) 的操作(读/写)
请看代码:
let a = { num: 10 }
let b = { age: 100 }
Object.defineProperty(a, 'age', {
get() {
return b.age
},
set(newVal) {
b.age = newVal
}
})
console.log(a);
console.log(b);
更详细的来说就是
:
- a对象通过Object.defineProperty()增加了一个属性age,这个age属性是b对象身上的。
- 通过get来读取b.age的值,return返回给a.age,这样读取a.age就相当于读取b.age。
- 设置a.age的值时调用set方法,去改变b.age的值,这样b.age的值被修改。因为get,导致a.age的值也被"修改"。
有'印象'的理解
:你可以把他想象成ntr(本人纯爱党),b身上的属性只能b来修改和读取,但b的朋友a现在也可以对b的属性进行修改和读取。
注意
:
a增加的属性age只是为了语义化,你可以给添加的属性起名叫a,b,c.因为get返回的值是b.age也就是你只要读取a.age 返回的是b对象身上age属性的值.
Vue上的数据代理
就是通过vm
实例对象来对_data
中属性的操作(读/写)。
老规矩先上代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vue数据代理</title>
</head>
<body>
<div id="app">
<p>动物类:{{ animal }}</p>
<p>名称:{{ name }}</p>
<p>{{ _data.animal }}</p>
<p>{{ _data.name }}</p>
</div>
<script src="../vue.js"></script>
<script>
const vm = new Vue({
el: '#app',
data: {
animal: '动物',
name: '小猫'
}
})
console.log(Vue);
console.log(vm)
</script>
</body>
</html>
打印一下运行结果:发现 _data.name也可以访问到
再打印一下vm这个实例对象 :
发现vm身上有animal和name属性,_data身上也有animal和name属性,这又是为什么呢?
以下代码均为模拟 不是源码!!!
1.我们先要知道:在我们`new Vue(options)`时传入的那个`data`,被`Vue`放在了`vm`上,
名为`_data`。
const vm = new Vue({//构造函数和普通函数的区别 就是看调用的时候有没有new
el: '#app',
data: {
val: 100
}
})
2.我们new Vue({})传入了一个对象 这个对象就是options,我们把data这个属性赋值给了
this._data 也就相当于 vm._data = options.data
function Vue (options) {
// 如何把 data 放到 vm 实例身上
//在构造函数中,this 指实例
this._data = options.data
}
3.问题来了 我们在vm身上只加了_data属性,(_data属性也是个对象身上有val属性)没有在
vm身上加val属性 那么vm身上的val属性是怎么来的?
通过Object.defineProperty()
Object.defineProperty(vm, 'val', {
get () {
console.log('我被调用了~~~~')
return _data.val
},
set (newVal) {
console.log('我也被调用了~~~~')
_data.val = newVal
}
})
我们在vm身上通过Object.defineProperty()来添加val属性,这个value属性读取的值是
_data.val设置value属性 设置的是_data.val
相信大家看到这里 稍微有点感觉,那趁热打铁再来:
也不是源码,更深的模拟
function Vue(options) {
// 构造函数里面的 this 代表的是 实例
this._data = options.data || {}
// 该方法的作用:将 _data 中的成员转换成 getter 和 setter,并注入到 Vue 实例中
this._proxyData(this._data)//我们把 Object.defineProperty封装在一个方法里
}
// 将 data 中的数据转换成 getter 和 setter 注入到 vue 实例 vm 身上
Vue.prototype._proxyData = function (_data) {//方法都写在原型对象上
//Object.keys和for...in差不多 只不过返回的是对象身上的每个属性名组成的数组
// 遍历 data 中的所有的属性,将 data 中每个属性,都放到 vm 上
Object.keys(_data).forEach(key => {//属性名组成的数组用forEach数组方法进行遍历
// 把 data 中的数据都注入到实例中
Object.defineProperty(this, key, {//key就是_data对象中的每一个属性名
enumerable: true,//可枚举
configurable: true,//可删除
get() {
return _data[key]//返回_data对象属性名为[key]的值
},
set(newValue) {//修改传入的值
if (newValue === _data[key]) return//如果修改的值和原本的值一样直接返回,提高性能
_data[key] = newValue//修改_data对象属性名为[key]的值
}
})
})
}
const vm = new Vue({
data: {
name: '小明',
age: 10
}
})
console.log(vm)
Vue中数据代理的好处
那么我们为什么要用数据代理,绕来绕去直接_data不好么?
还真不好 我们要用那么多数据每个数据都得_data.属性名来读取/修改,直接属性名的方式可以更加方便的操作data中的数据。
{{_data.name}}
{{name}}
你会选择使用哪种?
转载自:https://juejin.cn/post/7147334971840331806