今天看到一篇文章:为何 Vue 实例化后,经过 this.能获取到 data 内的数据javascript
一下是这篇文章的结论:vue
不卖关子啦!其实 Vue 实例化的时候,经过 getData 方法中 call 修改 this 指针来实现 Vue 实例后能获取到 data 体内的数据,小伙伴可能感受认为我将 vue 初始化部分流程拉出来是的你愈来愈糊涂,可是若是我不讲上面的代码拉出来,我直接来这句总结,你会更加懵的 🙂🙂🙂java
这篇文章根本没有说清楚,或者说根本就说错了。可能不知道误导了多少人,并且网上对于该问题的解释都一致的看向源码中的:git
return data.call(vm, vm);
复制代码
图片截至:Vue 源码解析:this.$data、this._data、this.xxx 为何都能获取数据?data 为何是个函数?github
然而实现该功能的其实并非这句话!!!api
然而实现该功能的其实并非这句话!!!浏览器
然而实现该功能的其实并非这句话!!!函数
(重要的事情说三遍!)oop
首先得从 Vue 的实例化开始看起!源码分析
function Vue(options) {
if (process.env.NODE_ENV !== "production" && !(this instanceof Vue)) {
warn("Vue is a constructor and should be called with the `new` keyword");
}
this._init(options);
}
复制代码
在这段代码中,对咱们实际有用的是:this._init(options)
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
...
if (options && options._isComponent) {
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
...
initState(vm)
...
}
复制代码
在 Vue.prototype._init() 中,要注意的是:
export function initState (vm: Component) {
...
const opts = vm.$options
...
if (opts.data) {
initData(vm)
} else {
observe(vm._data = {}, true /* asRootData */)
}
...
}
复制代码
在这段代码中,若是 vm.$options.data(也就是咱们的数据源)若是有数据,咱们就进行 initData(vm)操做,传入的 vm 即当前 Vue 对象!
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
...
// proxy data on instance
const keys = Object.keys(data)
...
let i = keys.length
while (i--) {
const key = keys[i]
...
proxy(vm, `_data`, key)
}
...
}
复制代码
值得注意的是:
咱们实例化 Vue 数据源的时候写法经常是:
data: function() {
return {
key: value,
...
}
}
复制代码
那么这时候咱们就会走向data = vm._data = getData(data, vm)
export function getData (data: Function, vm: Component): any {
...
return data.call(vm, vm)
...
}
复制代码
这里咱们就要理解一下,data.call(vm, vm)到底干了啥?
对于 call 的深刻理解建议阅读一下:对于 Function.call()的深刻理解
这里我将简单地进行解释:
data.call(vm, vm)
// 实际上与是以以下方式运行的
vm.data(vm)
//假如咱们有以下数据源
data: function() {
return {
content: "Hello World!"
}
}
//那么data.call(vm, vm)执行的实际上就是获得了return中的对象
{
content: "Hello World!"
}
复制代码
data.call(vm, vm) 并无实现咱们想要的功能。
data = vm._data = typeof data === "function" ? getData(data, vm) : data || {};
复制代码
这段代码的意思其实是:不管咱们定义的 data 是一个 function 仍是一个{},最终都将返回一个{}而且赋值给 data 这个变量!
**值得注意:**vm._data 就是返回的{},能够在控制的进行打印输出看一看,和 vm.$data 的值是同样的!
那么真正的实如今哪呢?
咱们看到 initData()函数的后半段代码:
const keys = Object.keys(data)
...
let i = keys.length
while (i--) {
const key = keys[i]
...
proxy(vm, `_data`, key)
}
复制代码
这里遍历了咱们以前所取到的 data,对其中每个属性都进行了proxy(vm,
_data, key)
调用。
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
export function proxy(target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter() {
return this[sourceKey][key];
};
sharedPropertyDefinition.set = function proxySetter(val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
复制代码
理解这段代码,这里得先普及一个知识点:
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
经过 defineProperty()实际上就是给 Vue 实例定义了新的属性!!!
这才是重点,这里经过遍历 data,将 data 中的属性/值绑定在了 vm(也就是 Vue 实例)上。
在 proxy()中的传入的第二个参数_data
,实际上为了后面this[sourceKey][key]
调用 vm._data(前面有提)中的的属性/值。
这里经过 vm 上建立新属性,而且采用 set,get 属性描述符使得每一次改变数据都会直接做用于 vm._data 之上。
其实今天的话题在 Vue.js 的文档就有解释:
data
类型:
Object | Function
限制:组件的定义只接受
function
。详细:
Vue 实例的数据对象。Vue 将会递归将 data 的属性转换为 getter/setter,从而让 data 的属性可以响应数据变化。对象必须是纯粹的对象 (含有零个或多个的 key/value 对):浏览器 API 建立的原生对象,原型上的属性会被忽略。大概来讲,data 应该只能是数据 - 不推荐观察拥有状态行为的对象。
一旦观察过,不须要再次在数据对象上添加响应式属性。所以推荐在建立实例以前,就声明全部的根级响应式属性。
实例建立以后,能够经过
vm.$data
访问原始数据对象。Vue 实例也代理了 data 对象上全部的属性,所以访问vm.a
等价于访问vm.$data.a
。以
_
或$
开头的属性 不会 被 Vue 实例代理,由于它们可能和 Vue 内置的属性、API 方法冲突。你可使用例如vm.$data._property
的方式访问这些属性。当一个组件被定义,
data
必须声明为返回一个初始数据对象的函数,由于组件可能被用来建立多个实例。若是data
仍然是一个纯粹的对象,则全部的实例将共享引用同一个数据对象!经过提供data
函数,每次建立一个新实例后,咱们可以调用data
函数,从而返回初始数据的一个全新副本数据对象。若是须要,能够经过将
vm.$data
传入JSON.parse(JSON.stringify(...))
获得深拷贝的原始数据对象。
因此说,遇到问题要多看文档,实在不行再看源码!
-EFO-
笔者专门在 github 上建立了一个仓库,用于记录平时学习全栈开发中的技巧、难点、易错点,欢迎你们点击下方连接浏览。若是以为还不错,就请给个小星星吧!👍
2019/05/27