在面试中被问到这个问题。
<html>
<head>
<style type="text/css">
.div-cls {
color: red;
}
</style>
</head>
<body>
<script src="./vue.js"></script>
<div id="app">
<div id="div-1" class="div-cls">{{msg}}</div>
</div>
<script>
const app = new Vue({
data(){
return{
msg: 'hello, world'
}
},
created(){
setTimeout(() => {
console.log('liubbc msg: ', this.msg)
}, 2000)
}
}).$mount("#app");
</script>
</body>
</html>
也就是怎么可以通过this.msg就可以获取到data中定义的msg变量?
面试官想考察vue对data中属性进行了代理,以及为啥对其进行代理。
在进行vue实例化的时候对data进行了处理,步骤如下:
Vue.prototype._init -> initState -> initData
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {};
console.log('liubbc data: ', data)
if (!isPlainObject(data)) {
data = {};
warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// proxy data on instance
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
{
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
proxy(vm, "_data", key); //在这里对data中定义的变量进行代理,代理到vm._data对象下
}
}
// observe data
observe(data, true /* asRootData */);
}
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);
}
不进行代理的话(把proxy(vm, "_data", key);这一行给注释掉),怎么才能访问到data中的变量呢?
<div id="div-1" class="div-cls">{{_data.msg}}</div>
或
<div id="div-1" class="div-cls">{{$data.msg}}</div>
从代码中发现把data赋值给了vm._data,所以可以用_data.msg,另外搜索_data发现通过$data 也可以取到msg。
function stateMixin (Vue) {
// flow somehow has problems with directly declared definition object
// when using Object.defineProperty, so we have to procedurally build up
// the object here.
var dataDef = {};
dataDef.get = function () { return this._data };
var propsDef = {};
propsDef.get = function () { return this._props };
{
dataDef.set = function () {
warn(
'Avoid replacing instance root $data. ' +
'Use nested data properties instead.',
this
);
};
propsDef.set = function () {
warn("$props is readonly.", this);
};
}
Object.defineProperty(Vue.prototype, '$data', dataDef);
Object.defineProperty(Vue.prototype, '$props', propsDef);
Vue.prototype.$set = set;
Vue.prototype.$delete = del;
Vue.prototype.$watch = function (
expOrFn,
cb,
options
) {
var vm = this;
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {};
options.user = true;
var watcher = new Watcher(vm, expOrFn, cb, options);
if (options.immediate) {
try {
cb.call(vm, watcher.value);
} catch (error) {
handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
}
}
return function unwatchFn () {
watcher.teardown();
}
};
}
从上可以看出为什么对data进行代理:更加方便的操作data中的数据。如果不进行代理的话,我们访问data中的变量就得这样:this._data.msg 或 this.$data.msg 不方便。所以vue进行了代理,代理后我们就可以直接访问了: this.msg。
我们也可以对其实现原理进行模拟,代码如下:
//vue是怎么通过this.code 访问到data声明的code的
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: () => {},
set: () => {}
}
function proxy(vm, sourceKey, key) {
sharedPropertyDefinition.get = function() {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function (val) {
this[sourceKey][key] = val
}
Object.defineProperty(vm, key, sharedPropertyDefinition)
}
// 把vm的属性传给data函数使用
function getData(fn, vm) {
return fn.call(vm, vm)
}
var vm = {
props: {
b: 123
}
};
var data = function() {
console.log('vm的props');
console.log(this.props);
return {
code: 1,
value: 'text'
}
}
vm._data = getData(data, vm);
console.log('vm: ', JSON.stringify(vm)); // vm: {"props":{"b":123},"_data":{"code":1,"value":"text"}}
proxy(vm, '_data', 'code');
proxy(vm, '_data', 'value');
console.log(vm.code); // '1'
console.log(vm.value); // 'text'