MiniVue系列---响应式数据(一)

105 阅读2分钟

响应式原理

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都会被更新,没法去具体更新对应的模板。