vue源码分析之数据代理

Vue大家都不陌生了,用的人也很多,但大家对里面的数据代理,模板解析渲染,v-开头的事件指令和一般指令,数据强制绑定,双向数据绑定如何实现的是否也产生过好奇,接下来几篇,我们来一起探讨一下这些东西,首先从比较简单的数据代理开始。
数据代理就是通过一个对象代理来对另一个对象中的属性实现读和写的操作。好处就是很大程度上方便了我们操作数据。
在vue中,我们是不是经常通过this.msg就拿到了data中的msg属性值。
其实this就是指向new Vue构造函数创建出来的vm实例,通过数据代理来实现对data对象中所有属性的读写操作,我们先写个vue的实例对象,就像下面这样:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="./vue.js"></script>
</head>
<body>
    <div id="test">
        {{msg}}
    </div>

    <script>
        const vm = new Vue({
            el: '#test',
            data:{
                msg: '天气不错'
            }
        })
        console.log(vm)
    </script>
</body>
</html>

当我们用vm.msg也可以拿到data对象中的msg属性。
打印出来的vm是这样的:
vue源码分析之数据代理
vm拿到了data对象中的属性和值,并且

vm._data.msg = '今天是阴天啊'

通过vm我们也能完成对data对象里面数据的更改。
那么问题来了,vm实例是如何完成对data对象里的属性实现读写的?
在这里,我们就要先了解一下ES5中的Object.defineProperty()方法,看下MDN的解释:
vue源码分析之数据代理
我们需要给这个方法传递三个参数,第一个是要在哪个对象上面定义属性,第二个是要定义或修改的属性的名称,第三个可以理解为是一个配置对象,在里面可以配置下列属性:
vue源码分析之数据代理
其中跟我们讲的数据代理牵扯最大的就是get和set方法。
了解了Object.defieProperty()方法后,我们自己来实现一个简单的数据代理,直接贴代码:

let a = {
    data: {
        b: 123
    }
};
//目的:我们想通过a.b直接拿到b里面的数据。

//参数target:代理者,参数sourceKey:被代理者
function proxy(target,sourceKey){
    let data = target[sourceKey];
    //拿到被代理者的所有属性名
    let keys = Object.keys(data);
    //遍历所有属性名,把所有属性名通过defindeProperty方法添加到代理者身上,并定义get和set方法,实现对数据的操作
    for (let i = 0, l = keys.length; i < l; i++) {
        let key = keys[i];
        Object.defineProperty(target, key, {
            enumerable: true,
            configurable: true,
            get() {
                return this[sourceKey][key];
            },
            set(val) {
                this[sourceKey][key] = val;
            }
        });
    }
}

proxy(a, "data");

console.log(a.b); // 123

通过proxy函数,我们顺利的完成了a对b的数据代理,是不是很简单?