Vue的响应式系统是基于Object.defineProperty实现的,所以先来了解Object.defineProperty是非常有必要的。
在MDN上,可以非常明确地看到其作用和用法。Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法:Object.defineProperty(obj, prop, descriptor)
其中,obj是要在其上定义属性的对象。prop是要定义或修改的属性的名称。descriptor是将被定义或修改的属性描述符。
前两个参数比较好理解,至于第三个参数属性描述符,参见MDN:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty
Vue中使用的是存取描述符,有以下四个属性:
-
configurable:为true时,该属性能够修改,默认为false -
enumerable:当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为false。 -
get:给属性提供getter方法 -
set:给属性提供setter方法
知道了Object.defineProperty之后,我们来实现Vue的响应式系统。
- 首先,我们定义Vue函数,以及函数
cb来模拟视图更新:
//test.js
function Vue(options){
}
function cb(){
console.log('更新啦!')
}
引入js,
//index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./test.js"></script>
</head>
<body>
</body>
<script>
const vm = new Vue({
data:{
name:'a',
}
})
</script>
</html>
我们给new的Vue对象传入一个包含data属性的对象,我们要达到的效果是当data中的任一属性值发生变化时,视图更新,即调用cb函数。
- 接下来,定义
defineReactive函数,
//test.js
function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get:function(){
return val
},
set:function(newVal){
if(newVal===val){
return
}
val = newVal
cb()
}
})
}
这段代码中,形参obj表示需要捆绑的对象,key表示obj的一个属性名,val表示属性值。在经过defineReactive处理后,我们在访问obj的属性时,会调用getter方法,在更改属性时会调用setter方法。
- 然后,我们需要为data的每一个属性都进行响应式处理。
//test.js
function observe(obj){
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}
其中,Object.keys(obj)返回obj的属性名组成的数组。
- 整合起来,就是这样的:
//test.js
function Vue(options){
this._data = options.data
observe(this._data)
}
function cb(){
console.log('更新啦!')
}
function observe(obj){
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key])
})
}
function defineReactive(obj,key,val){
Object.defineProperty(obj,key,{
configurable:true,
enumerable:true,
get:function(){
return val
},
set:function(newVal){
if(newVal===val){
return
}
val = newVal
cb()
}
})
}
- 测试,我们在
index.html中更改Vue对象中name的值,如果调用了cb函数,即为成功。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="./test.js"></script>
</head>
<body>
</body>
<script>
const vm = new Vue({
data:{
name:'a',
}
})
vm._data.name='b'
</script>
</html>

控制台.jpg
Bingo!