Vue2响应式原理


??vue2响应式原理

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

深入响应式原理

Object.defineProperty()

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

使用方式:Object.defineProperty(对象名,属性名,配置项)


上述写法是给person对象添加一个age属性,属性的值是18

但是这种写法:

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

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

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

现在有一个需求:定义一个新的变量number,当number的值修改后,personage的值也相应被修改;而personage的值被修改后,number的值也相应被修改。

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


数据代理

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

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


    

姓名:{{name}}

年龄:{{age}}

控制台输出vm,我们可以看到nameage这两个属性

这两个属性都是通过Object.defineProperty()添加到vm上,所以可以发现他们都有对应的getter/setter

也就是说:当读取vm中的name时,会调用getter,把data.namename;当修改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无法检测到对象和数组内部的变化,所以遇到属性为对象时,会递归观察该属性;遇到数组时,会重写pushpopshift等方法。

监测对象中的数据

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

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

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


上述代码只是一个例子,只会对一层对象进行处理,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()