vue判断数据是对象_Vue 响应式系统(一)- 实例对象代理访问数据

关于Vue的响应式系统在近几年已经被无数的人提及过。 为了避免炒剩饭从本章开始我们将以源码级的角度分析Vue响应式更新过程每个细节点。

定位initState函数

function initState(vm) {

vm._watchers = [];

var opts = vm.$options;

if (opts.props) {

initProps(vm, opts.props);

}

if (opts.methods) {

initMethods(vm, opts.methods);

}

if (opts.data) {

initData(vm);

} else {

observe(vm._data = {}, true /* asRootData */ );

}

if (opts.computed) {

initComputed(vm, opts.computed);

}

if (opts.watch && opts.watch !== nativeWatch) {

initWatch(vm, opts.watch);

}

}

initState 函数是很多选项初始化的汇总,在 initState 函数内部使用 initProps 函数初始化props 属性;使用 initMethods 函数初始化 methods 属性;使用 initData 函数初始化 data选项;使用 initComputed 函数和 initWatch 函数初始化 computed 和 watch 选项。 为了内容更加符合标题,接下来以 initData 为切入点为大家讲解 Vue 的响应系统。

如下是 initState 函数中用于初始化 data 选项的代码:

if (opts.data) {

initData(vm);

} else {

observe(vm._data = {}, true /* asRootData */ );

}

在此判断 opts.data 是否存在,即 data 选项是否存在,如果存在则调用 initData(vm) 函数初始化 data 选项,否则通过 observe 函数观测一个空的对象,并且 vm._data 引用了该空对象。其中 observe 函数是将 data 转换成响应式数据的核心入口。

由于没有先讲Vue中的选项合并处理,在此还是给大家解释下。 opts.data是否有值取决你是否有定义data 选项。

如下:

var vm = new Vue({

el: "app",

data: {

message: "this is test code "

}

})

此时 vm.$options.data 就有值并且最终被处理成了一个函数,且该函数的执行结果才是真正的数据。后续会开个章节讲解下Vue选项处理合并策略。

initData

function initData(vm) {

var data = vm.$options.data;

data = vm._data = typeof data === 'function' ?

getData(data, vm) :

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 instancevar 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);

}

}

// observe dataobserve(data, true /* asRootData */ );

}

接下来我们来了解下initData 函数看下它做了什么,首先定义 data 变量,它是vm.$options.data的引用。在刚刚我们讲到 vm.$options.data其实是在选项合并阶段被处理成了一个函数,且该函数的执行结果才是真正的数据。在上面的代码中依然存在一个使用 typeof 语句判断data数据类型的操作,那么这里的判断还有必要吗 ?

答案是有,这是因为 beforeCreate 生命周期钩子函数是在 选项合并阶段之后 initData 之前被调用的,如果在 beforeCreate 生命周期钩子函数中修改了 vm.$options.data 的值,那么在 initData 函数中对于 vm.$options.data 类型的判断就是有存在的必要了。

在回归到源码,如果 vm.$options.data 的类型为函数,则调用 getData 函数获取真正的数据。

function getData(data, vm) {

// #7573 disable dep collection when invoking data getterspushTarget();

try {

return data.call(vm, vm)

} catch (e) {

handleError(e, vm, "data()");

return {}

} finally {

popTarget();

}

}

可以看到 getData 函数的作用其实就是通过调用 data 函数获取真正的数据对象并返回,即:data.call(vm, vm),而且我们注意到 data.call(vm, vm) 被包裹在 try...catch 语句块中,这是为了捕获 data 函数中可能出现的错误。如果有错误发生那么则返回一个空对象作为数据对象。pushTarget、popTarget 函数暂时不解释在依赖收集阶段再来介绍。

再回到 initData 函数中:

data = vm._data = typeof data === 'function' ? getData(data, vm) : data || {};

当通过getData拿到最终的数据对象后,将该对象赋值给 vm._data 属性,同时重写了 data 变量,此时 data 变量已经不是函数了,而是最终的数据对象。

接下来是个if 判断:

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

);

}

上面的代码中使用 isPlainObject 函数判断变量 data 是不是一个纯对象,如果不是纯对象那么在非生产环境会打印警告信息。

接下来代码:

// proxy data on instancevar 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);

}

}

// observe dataobserve(data, true /* asRootData */ );

Object.keys 、 while 循环这些就略过了直接挑重点讲。

if (methods && hasOwn(methods, key)) {

warn(

("Method \"" + key + "\" has already been defined as a data property."),

vm

);

}

这是在做什么?这段代码的意思是在非生产环境下如果发现在 methods 对象上定义了同样的key,也就是说 data 数据的 key 与 methods 对象中定义的函数名称相同,那么会打印一个警告,提示开发者:你定义在 methods 对象中的函数名称已经被作为 data 对象中某个数据字段的key了,需要换个名字。

为什么要这么做?如下示例代码:

const vm= new Vue({

data: {

count: 1

},

methods: {

unique: function(){}

}

})

ins.count // 1ins.unique // function

可以看到不管是定义在 data 中的数据对象,还是定义在 methods 对象中的函数,都可以通过实例对象代理访问。为了避免产生覆盖掉的现象这么做是必然之举。

接下来代码:

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);

}

在处理 props 对象中的key字段时与上同理。当上面的代码中当if语句的条件不成立,则会判断 else if 语句中的条件:!isReserved(key),该条件的意思是判断定义在 data 中的 key 是否是保留键。isReserved 函数通过判断一个字符串的第一个字符是不是 $ 或_来决定其是否是保留的,Vue 是不会代理那些键名以 $ 或 _ 开头的字段的,因为Vue自身的属性和方法都是以 $ 或 _ 开头的,所以这么做是为了避免与 Vue 自身的属性和方法相冲突。 了解更多 isReserved 详情

如果 key 既不是以 $ 开头,又不是以 _ 开头,那么将执行 proxy 函数,实现实例对象的代理访问:

proxy(vm, "_data", key);

proxy 源码如下:

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 函数的原理是通过 Object.defineProperty 函数在实例对象 vm 上定义与 data 数据字段同名的访问器属性,并且这些属性代理的值是 vm._data 上对应属性的值。

const vm = new Vue ({

data: {

count: 1

}

})

如上示例代码当我们访问 vm.count 时实际访问的是 vm._data.count 。而 vm._data 才是真正的数据对象。

最后一句:

observe(data, true /* asRootData */)

正式进入响应式之路。

你可能感兴趣的:(vue判断数据是对象)