从vue中的data访问说起

demo

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <title>demo</title>
</head>
<style></style>

<body></body>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
<script>
    var data = {
        foo: "000"
    };
    var vm = new Vue({
        data: data
    });
    console.warn(data.foo); //输出"000"
    console.warn(vm.foo); //输出"000"
    console.warn(vm._data.foo); //输出"000"
    console.warn(vm.$data.foo); //输出"000"
    vm.foo = "111";
    console.warn(data.foo); //输出"111"
    console.warn(vm.foo); //输出"111"
    data.foo = "222";
    console.warn(data.foo); //输出"222"
    console.warn(vm.foo); //输出"222"
</script>

</html>

上面例子中 不管是修改了data.foo还是vm.foo,另一个值都会跟着改变。两个直接锁死?

var data = {
    foo:"000"
};
Object.freeze(data);
var vm = new Vue({
    data:data
});
data.foo = "111";
console.log(vm.foo);//输出"000"

但是一旦Object.freeze(data),vm.foo和data.foo却就此陌路了,直接互不相干。

从源码说起

function Vue(options){
    this._init(options);
}
initMixin(Vue);
function initMixin(Vue){
    Vue.prototype._init = function(options){
        var vm = this;
        vm.$options = mergeOptions(vm.constructor,options || {});
        initState(vm);
        return vm;
    }
}
function initState(vm){
    var opts = vm.$options;
    if(opts.data){
        initData(vm);
    }else{
        observe(vm.data={},true);
    }
}
function initData(vm){
    var data = vm.$options.data;
    
    //data对象和vm._data指向了同一个引用,所以data和vm._data会互相影响
    //本例中data只有一个属性'foo'
    //data.foo变化时,vm._data.foo也会变化
    //vm._data.foo变化时,data.foo也会发生变化。
    vm._data = data;

    //data对象中的所有属性都添加到vm实例中
    //本例中,data只有一个foo属性
    //将foo属性添加到vm实例中,所以有vm.foo
    //访问vm.foo时,实际是返回vm._data.foo的值
    //修改vm.foo时,其实是修改vm._data.foo的值   
    var keys = Object.keys(data);
    var i = keys.length;
    while(i--){
        var key = keys[i];
        proxy(vm,"_data",key);
    }
}


function noop(a,b,c){}
var sharedPropertyDefinition = {
    enumerable: true,
    configurable: true,
    get: noop,
    set: noop
  };
//target    是    vm实例
//sourceKey 是    '_data'
//key       是    data对象中的属性,'foo'
//所以target[sourceKey] 就是 vm._data
function proxy(target,sourceKey,key){
    sharedPropertyDefinition.get = function proxyGetter(){
        return this[sourceKey][key];
    }
    sharedPropertyDefinition.set = function proxySetter(val){
        this[sourceKey][key] = val;
    }
    Object.defineProperty(target,key,sharedPropertyDefinition)
}


function mergeOptions(parent,child){
	var options = {};
	var key,parentVal,childVal;
	for(key in parent){
		parentVal = parent[key];
		var res = Object.create(parentVal||null);
		options[key] = res;
	}
	for(key in child){
        parentVal = parent[key];
        childVal = child[key];
        options[key] = childVal===undefined?parentVal:childVal;
	}
	return options;
}
function extend(to,_from){
	for(var key in _from){
		to[key] = _from[key];
	}
	return to;
}

先来看下第28行的vm._data =data。
赋值操作=让data对象和vm._data指向了同一个引用。
因此,data对象和vm._data二者会互相影响。

再到第39行proxy(vm,‘_data’,key)看看。
它的作用是将data对象中的所有属性都添加到vm实例上。
本例中,就是将foo属性添加到vm实例上,所以有vm.foo。并且,
访问vm.foo,实际是返回vm._data.foo;
修改vm.foo,其实就是修改vm._data.foo。

综上来说,data对象上的所有属性 与 vm实例上的数据 之间的关系是通过vm._data这座桥梁建立起来的。
在这里插入图片描述

_或者$开头的属性

<script>
    var data = {
        foo: "000",
        _test: '测试'
    };
    var vm = new Vue({
        data: data
    });
    console.warn(data.foo); //输出"000"
    console.log(vm._test);  // undefined
</script>

以 _ 或 $ 开头的 property 不会被 Vue 实例代理,因为它们可能和 Vue 内置的 property、API 方法冲突。你可以使用例如 vm.$data._property 的方式访问这些 property。

console.log(vm.$data._test)  // 测试

实例创建之后,可以通过 vm.$data 访问原始数据对象。Vue 实例也代理了 data 对象上所有的 property,因此访问 vm.a 等价于访问 vm. $data.a。


版权声明:本文为weixin_43867717原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。