Vue2响应式原理

????vue2响应式原理

vue的特性: 数据驱动视图双向数据绑定 。vue官方文档也提供了响应式原理的解释:

深入响应式原理

Object.defineProperty()

Object.defineProperty()
的作用是直接在一个对象上定义一个新属性,或者修改一个属性

使用方式:

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

 <script>	
let person = {
name: '张三',
sex: '男',
}
Object.defineProperty(person,'age',{
value: 18
}) //不能参与遍历
</script>

上述写法是给

person
对象添加一个
age
属性,属性的值是18

但是这种写法:

  • 不能进行枚举,即无法在遍历的时候获取到
    age
    属性的值
  • 不能被修改
  • 不能删除

所以

Object.defineProperty()
还有其他配置项

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

现在有一个需求:定义一个新的变量

number
,当
number
的值修改后,
person
age
的值也相应被修改;而
person
age
的值被修改后,
number
的值也相应被修改。

这个时候需要借助新的配置项

get
set

 <script>	
let number = 18
let person = {
name: '张三',
sex: '男',
}
//能够实现number的值修改后,person中age的值也相应被修改
Object.defineProperty(person,'age',{
//当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
get:function(){
return number
},
//当有人修改person的age属性时,set函数(setter)就会被调用,且接收到修改的具体值
set(value){
number = value
}
})
</script>

数据代理

数据代理就是通过一个对象代理另一个对象中属性的操作

vue
就是通过
vm
对象来代理
data
对象中属性的操作

 <body>
<div id="app">
<h2>姓名:{{name}}</h2>
<h2>年龄:{{age}}</h2>
</div>

</body>
<script>
const vm = new Vue({
el: '#app',
data: {
name: '张三',
age: 18
}
})
</script>

控制台输出

vm
,我们可以看到
name
age
这两个属性

这两个属性都是通过

Object.defineProperty()
添加到
vm
上,所以可以发现他们都有对应的
getter/setter

也就是说:当读取

vm
中的
name
时,会调用
getter
,把
data.name
name
;当修改
vm
中的
name
时,会调用
setter
,修改
data.name
中的值(这里跟第一个例子是同一个道理)

但是我们会发现

vm
上没有
data
(疑惑:明明在创建
vue
实例对象的时候,设置了
data
,为什么取不到)

其实我们以为的这个

data
其实是
_data
,可以验证一下:

 let data = {
name: '张三',
age: 18
}
const vm = new Vue({
el: '#app',
data
})

控制台进行一下判断:

所以我们获取数据的时候,也可以通过

vm._data.age
来获取

vue
为了编码更方便,进行了数据代理,遍历
data
中的所有属性,把每个属性都添加到
vm
中,指定
getter/setter

所以可以直接通过

vm.age
来获取数据

基本原理:

  • 通过
    Object.defineProperty()
    data
    对象中所有属性添加到
    vm
  • 为每一个添加
    vm
    上的属性,都指定一个
    getter/setter
  • getter/setter
    内部去操作(读/写)
    data
    中对应的属性

实现双向绑定

双向绑定就是数据发生变化时,视图也跟着变。核心是 数据劫持发布者-订阅者模式

数据劫持实质就是使用

defineProperty
重写
getter/setter
。当数据改变时,
set
就会劫持这个数据的变化,更新视图(view)

但是由于

defineProperty
无法检测到对象和数组内部的变化,所以遇到属性为对象时,会递归观察该属性;遇到数组时,会重写
push
pop
shift
等方法。

监测对象中的数据

最开始会想认为利用

getter/setter
,但是这样会造成死循环。只要有人获取
name
的值,就会调用
get
,然后又会获取一次
person.name
,造成死循环。
set
同理。

 //错误的代码!!!!!!!!!
let person = {
name: '张三',
}
Object.defineProperty(person, 'name', {
get: function() {
return person.name
},
set(value) {
person.age = name
}
})

正确的做法是:监听数据的每一个属性,当监听到属性值发生变化时,通知订阅者去更新视图,重新进行模板解析。

 <script>
let data = {
name: '张三',
}

//创建一个观察者实例对象,用于监视data中属性的变化
const obs = new Observer(data)

//准备一个vm实例对象
let vm = {}
vm._data = data = obs

function Observer(obj) {
//汇总对象中所有的属性形成一个数组
const keys = Object.keys(obj)

//遍历
keys.forEach((k) => {
Object.defineProperty(this, k, {
get() {
return obj[k]
},
set(val) {
obj[k] = val
}
})
})
}
</script>

上述代码只是一个例子,只会对一层对象进行处理,

vue
的操作是递归,直到数据类型是简单数据类型。

如需给后添加的属性做响应式,可以使用

  • Vue.set(object,propertyName,value)
  • vm.$set(object,propertyName,value)
 data: {
student:{
name: '张三',
age: 18,
friends:[
{name:'小明',age:20},
{name:'李四',age:15}
]
}
}
Vue.set(this.student, 'sex', '男')
this.$set(this.student, 'sex', '男')

监测数组中的数据

这里可以去看一下

vue
的官方文档:

通过包裹数组更新元素的方法实现,本质就是做了两件事

(1)调用原生对应的方法对数组进行更新

(2)重新解析模板,进而更新页面

所以在

vue
修改数组中的某个元素一定要用如下方法:

  1. 使用API:
    push()
    ,
    pop()
    ,
    shift()
    ,
    splice()
    ,
    sort()
    ,
    reverse()
  2. Vue.set()
    ,
    vm.$set()

标签: Javascript

添加新评论