响应式原理
Vue通过对数据(data)的劫持,在其getter和setter做出对应的响应。 Object.defineProperty方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
1,obj 要定义属性的对象。 2,prop 要定义或修改的属性的名称
const obj = {}
const prop = 'name'
const descriptor = {
enumable: true, //是否可枚举
configurable, // 是否可以改变属性的值
value: undefined, // 属性默认的值
get() { // 当对象 .属性时,触发返回的值。比如 obj.name
return this.value
},
set(newValue) { // 当对象.属性 = xxx,即为对象的属性赋值时触发,比如 obj.name = 'change'
this.value = newValue
}
}
Object.defineProperty(obj, prop, descriptor)
那么如何去实现一个响应式呢?可以先理解下,何为响应式,正常来说我们在使用vue时,需要在 data 中的数据更新时,模板中的内容自动更新,即在 this.message = 'changeMessage'之后,模板中的{{message}} 会自动更新成 changeMessage。如上面所说,就是在 setter时,做出一定的动作,我们可以把这个动作先描述成一个cb,综合上面写一个observer,来把一个对象描述成可观察的。
function defineReactive(obj, key, val) {
// 这里还需要处理嵌套的,如果val是对象时,还需要调用observer进行数据的劫持
if(!val || (typeof val !== 'object')) observer(val)
Object.defineProperty(obj, key, {
enumerable: true, /* 属性可枚举 */
configurable: true, /* 属性可被修改或删除 */
get() {
// 在getter时会收集依赖,这里的依赖是指有哪些引用(比如模板语言里,或者computed,watchter一类需要响应数据变化的地方,都会为这些地方新建一个watcher来观察变化)
return val
},
set(newVal) {
if(newVal === val) return // 如果赋值响应值就不触发cb
val = newVal
cb(newVal) // 用于更新模板的函数
}
})
}
在实际过程中,我们的定义的数据可能是多个属性的,所以需要再封装一层来处理多个属性或者嵌套属性的对象
function observer(data) {
if (!data || (typeof data !== 'object')) {
return;
}
// 遍历对象的key
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key])
})
}
然后去实现一个最基础的Vue,在2.x里通过new Vue方式创建,所以Vue是一个构造函数。
class Vue {
// Vue 在实例化对象时,传入的是一个对象,new Vue({el: '#app'}, data() {return {xx: 'xx'}})...
constructor(options) {
this.$data = options.data
this.$el = options.el
// 把options上的数据同步到vue的实例化对象后,就可以添加观察者了
observer(this.$data)
}
}
// cb 我们可以先简单的写一下
function cb(val) {
document.getElementById('app').innerHTML = val
}
// 接着来使用一下
const app = new Vue({
data: {
message: 'somemessage',
name: 'balala'
}
})
app.$data.message = 'changeMessage'
// 此时去更改app.$data.message时,cb就会被调用,同时把最新的值更新到DOM中。
以上就是响应式的部分原理,但现在缺陷还很多,比如依赖的收集,不管是哪个属性在变化时都是调用整个cb,也就是说整个DOM都会被更新,没法去具体更新对应的模板。