从这一节开始,我们开始讲解Vue的数据代理,想把数据代理搞清楚,就需要对这个方法Object.defineProperty特别熟悉。

        这个方法在Vue的底层中,有很多地方都用到了它,比如Vue中的数据劫持,数据代理,计算属性。

        我们从这个方法的名字可以看出,这个方法就是给一个对象去定义属性的。接下来我们就操作一下这个方法。看看它有什么神奇的地方。

        首先我们要知道这个方法有哪些参数?

Object.defineProperty(设置对象,设置属性,配置项)

        我们接下来就定义一个对象,并给这个对象添加值

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
}

Object.defineProperty(person,'age',{
value:18
})
console.log(person);


</script>
</body>
</html>

执行结果:

10.Vue数据代理--Object.defineProperty方法_vue.js

这个时候肯定有人会问这种方法肯定没人用,太麻烦了,明明用下面的方法更简单呀:

let person = {
name:'张三',
sex:'男',
age:18
}

直接就把值设置好了。这里就要说一句了,上面的方法看似麻烦,但是它更高级一点,那么高级在哪里呢?下面就继续深入的说一下。

10.Vue数据代理--Object.defineProperty方法_javascript_02

它想表达的就是,这个age是不可以被枚举的。也就是说这个age的属性是不参与遍历的。

        接下来我们就验证一下这个说法。

        我们首先通过正常赋值的方式,然后通过Object.keys()去遍历

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
age:18
}

// Object.defineProperty(person,'age',{
// value:18
// })
console.log(Object.keys(person));


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_javascript_03

         

        我们可以看到3个key全部被输出了。

        但如果我们通过对象赋值的方式去添加呢?

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
}

Object.defineProperty(person,'age',{
value:18
})
console.log(Object.keys(person));


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_html_04

         我们可以看到它只输出了2个key,而通过对象赋值的那个key没有被输出。这就叫做不可枚举。

        或者我们通过另外的方式也可以验证,我们通过for去输出对象的属性值

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
}

Object.defineProperty(person,'age',{
value:18
})
for(let key in person){
console.log(person[key]);
}


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_vue.js_05

        我们同样可以看到最后一个值没有被输出。

那么如果我们想让这样设置的值可以参与遍历呢?我们可以通过配置项enumerable:true来实现。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
}

Object.defineProperty(person,'age',{
value:18,
enumerable:true //控制属性是否可以枚举,默认值是false
})
console.log(Object.keys(person));


</script>
</body>
</html>

 

10.Vue数据代理--Object.defineProperty方法_vue.js_06

我们可以看到,这次3个key都可以参与遍历了。

那么这个时候有的同学还是会有疑惑,我直接在person里写个age,是不是和配置完可遍历的配置项之后就一样了?

        其实还是不一样的。我们直接在person里去写age,这个值是可以修改的。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
value:18
}

// Object.defineProperty(person,'age',{
// value:18,
// enumerable:true
// })
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_html_07

         而通过Object设置的值是修改不了的。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
}

Object.defineProperty(person,'age',{
value:18,
enumerable:true
})
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_前端_08

那么如果我们想让它可以修改呢?我们可以通过配置项writable:true来实现。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
}

Object.defineProperty(person,'age',{
value:18,
enumerable:true,//控制属性是否可以枚举,默认值是false
writable:true //控制属性是否可以被修改,默认值是false
})
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_html_09

         除了上面的遍历和修改,删除属性,也是同样可配置的,直接通过属性赋值,是可任意修改和删除的,而通过Object设置的值是可以通过设置项实现删除。

直接赋值,是可以删除的。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男',
age:18
}

// Object.defineProperty(person,'age',{
// value:18,
// enumerable:true,//控制属性是否可以枚举,默认值是false
// writable:true //控制属性是否可以被修改,默认值是false
// })
console.log(person);


</script>
</body>
</html>

 

10.Vue数据代理--Object.defineProperty方法_vue.js_10

 通过Object赋值是不允许删除的

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男'
}

Object.defineProperty(person,'age',{
value:18,
enumerable:true,//控制属性是否可以枚举,默认值是false
writable:true //控制属性是否可以被修改,默认值是false
})
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_javascript_11

通过配置项configurable:true就可以实现删除了。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let person = {
name:'张三',
sex:'男'
}

Object.defineProperty(person,'age',{
value:18,
enumerable:true,//控制属性是否可以枚举,默认值是false
writable:true, //控制属性是否可以被修改,默认值是false
configurable:true //控制属性是否可以被删除,默认值是false
})
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_默认值_12

        那么讲到这里,我们就能明白了,Object.defineProperty这个方法高级在哪里了。这个方法虽然看着复杂,但是它可以对追加的属性进行很多高级的限制,比如是否可以遍历,是否可以修改,删除。

        上面我们讲到的那4个配置项都是最基本的配置项,其实Object.defineProperty还能传一个高级的配置项。

        下面我们要讲的就是数据代理的核心方法:

接下来我们提一个需求,我们给person赋值age为18,但是不是直接给age赋值,而是通过另外一个变量number。

那么有些同学就会这样写:

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

let number = 18;
let person = {
name:'张三',
sex:'男',
age:number
}

// Object.defineProperty(person,'age',{
// value:18,
// enumerable:true,//控制属性是否可以枚举,默认值是false
// writable:true, //控制属性是否可以被修改,默认值是false
// configurable:true //控制属性是否可以被删除,默认值是false
// })
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_默认值_13

这样写也能实现,但是有个问题,这个number后期可能会变成19,那么person里的age会跟着变嘛?答案是不会的。

10.Vue数据代理--Object.defineProperty方法_前端_14

那么就会有这种感觉,number和person中的age确实有关系,但是这种关系仅限于这两个变量一开始定义的时候。而以后这个number再怎么变,这个person中的age也不会跟着变化,这是因为代码走完一遍以后,number再怎么变化都与person中的age没有关系了。因为代码已经执行完了。

那么我们怎么才能实现修改完number之后,person中的age可以自动变化呢?这就用到了Object.defineProperty中的高级设置get方法了。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

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函数就会被调用,且返回值就是age的值
get:function(){
return 18;
}
})
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_前端_15

 这时我们可以看到age是三个点,这个意思就是,age是多少,暂时还不知道,需要去问get函数,get函数返回多少就是多少。而这里的get在每次读取的时候都会调用。这里的三个点在鼠标点击之后就可以看到具体的值了。

 

10.Vue数据代理--Object.defineProperty方法_默认值_16

get在每次读取的时候都会调用,这个我们可以去验证一下:

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

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函数就会被调用,且返回值就是age的值
get:function(){
console.log('读取了age');
return 18;
}
})
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_vue.js_17

 那么除了上面的get方法,还有和它相辅相成的方法set,当有人修改属性的时候,set函数就会被调用,且会收到修改的具体值。

<!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>回顾Object.defineproperty方法</title>
</head>
<body>
<script>

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函数就会被调用,且返回值就是age的值
get(){
console.log('读取了age');
return number;
} ,
//当有人修改person的age属性时,set函数就会被调用,且会收到修改的具体值
set(value){
console.log('有人修改了age属性,且值是',value);
number = value;
}
})
console.log(person);


</script>
</body>
</html>

10.Vue数据代理--Object.defineProperty方法_默认值_18

 至此,我们就实现了,即使在页面加载完毕之后,后续修改number的值,person.age的值也会跟着变化的需求。而这也是Vue数据代理的核心思想