vue源码(一) this.data是怎么来的?
0.获取源码
从github地址,直接download下来就行了。在新建项目的时候也可以node_modelus里的vue搭配着看。
1.数据的挂载
首先先引入vue,然后新建他的实例。
import vue from 'vue' var app = new vue({ el:'#app', data:{ return { message:"hello world!" } } })
首先我们得知道我们引入 的是个什么东西。所以我们找到源码./src/core/instance/index.js
里,找到了vue的庐山真面目了,其实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) }
首先process.env.node_env
是判断你启动时候的参数的,如果不符合的话,就发出警告,否则执行_init
方法。值得一提的是一般属性名前面加_
默认代表是私有属性,不对外展示。当然如果你打印vue实例的话还是能看见,因为只是_
是私有属性人们约定俗成的,没有js语言层面的私有。
那么这个_init
是哪来的呢?往下看:
initmixin(vue) statemixin(vue) eventsmixin(vue) lifecyclemixin(vue) rendermixin(vue)
可以看到下面有一大串mixin,我们挑第一个initmixin,然后去查看他的定义。vscode可以直接右键,然后选择转到定义 或者直接command加鼠标左键点击函数名称就可以跳过去看到定义这个方法的地方。
export function initmixin(vue: class<component>) { vue.prototype._init = function (options?: object) { const vm: component = this // a uid vm._uid = uid++ //.. // a flag to avoid this being observed vm._isvue = true // merge options if (options && options._iscomponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initinternalcomponent(vm, options) } else { vm.$options = mergeoptions( resolveconstructoroptions(vm.constructor), options || {}, vm ) } //.. /* istanbul ignore else */ if (process.env.node_env !== 'production') { initproxy(vm) } else { vm._renderproxy = vm } // expose real self vm._self = vm initlifecycle(vm) initevents(vm) initrender(vm) callhook(vm, 'beforecreate') initinjections(vm) // resolve injections before data/props initstate(vm) // .. if (vm.$options.el) { vm.$mount(vm.$options.el) } } }
init就在最开头
vue.prototype._init = function (options?: object) { const vm: component = this // a uid vm._uid = uid++ //.. // a flag to avoid this being observed vm._isvue = true // merge options if (options && options._iscomponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initinternalcomponent(vm, options) } else { vm.$options = mergeoptions( resolveconstructoroptions(vm.constructor), options || {}, vm ) }
init具体包括啥呢,首先将this上下文传给vm这个对象,然后设置_uid
然后再机型一系列的初始化的工作。然后再合并options,最后挂载到vm上。
可能有人会好奇,在形参部分,vue: class<component>
是什么意思,因为javascript是一个动态类型语言,也就是说,声明变量的时候不会指派他是任何一种类型的语言,像java就是典型的静态类型语言。例如:boolean result = true
就是声明result是一个布尔类型,而相对的,javascript中可以声明var result =true
。这样虽然方便很多,但是因为静态类型在编译过程中就会查出错误并提示开发者改正错误,但是像js这样的动态语言在编译的时候既是存在错误也不会提出,只有在真正运行时才会出错。所以就会有不必要的麻烦,那么如何对js进行静态类型检查呢?就是插件呗。vue用了flow的插件,让js有了静态类型检查,:
后面代表了限定vue这个形参的属性。具体就不展开了,可以去看flow的文档。
flow:
接下来接着说正文,const vm: component = this
可以看到把当前的执行前后文给了vm。然后之后就是一些陆陆续续的挂载,值得注意的就是vm.$options
就是填写在vue实例里的参数,例如el
,mounted
,data
都被保存在$options
里。
但是平常使用的时候我们没有用到this.$options.data1
里,反而是直接用this.data1
来调用,这其实vue也在其中进行了操作。
我们会发现在上面的代码段里有一行initstate(vm)
,我们找到initstate的定义。
export function initstate (vm: component) { // .. const opts = vm.$options if (opts.data) { initdata(vm) } // .. }
然后我们可以接着转到initdata这个方法的定义
function initdata (vm: component) { let data = vm.$options.data data = vm._data = typeof data === 'function' ? getdata(data, vm) : data || {} if (!isplainobject(data)) { data = {} process.env.node_env !== 'production' && 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 const keys = object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.node_env !== 'production') { if (methods && hasown(methods, key)) { warn( `method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasown(props, key)) { process.env.node_env !== 'production' && 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 data observe(data, true /* asrootdata */) }
把上面的代码拆分来看
let data = vm.$options.data data = vm._data = typeof data === 'function' ? getdata(data, vm) : data || {}
上面代码先通过$options获取到data,然后判断data是不是通过返回对象的方式建立的,如果是,那么则执行getdata方法。getdata的方法主要操作就是 data.call(vm, vm)
这步通过给data调用了vm这个上下文环境,然后直接返回这个包括data的vm对象。
那么现在vm上已经有data了是吗?确实,但是这个data是vm._data
也就是说如果你想访问message
这个属性你现在只能通过vue._data.message
这样来访问。所以我们接着往下看。
// proxy data on instance const keys = object.keys(data) const props = vm.$options.props const methods = vm.$options.methods let i = keys.length while (i--) { const key = keys[i] if (process.env.node_env !== 'production') { if (methods && hasown(methods, key)) { warn( `method "${key}" has already been defined as a data property.`, vm ) } } if (props && hasown(props, key)) { process.env.node_env !== 'production' && 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) } }
这一大段上面聚焦的是prop data methods 们如果相同之后就会提出相应的警示。为什么要他们不一样呢,因为他们都是通过this.xx来调用的,如果重名,vue分不清他们是谁。如果都没问题了,我们就把_datas上的值直接赋给vm,然后转到最后一步proxy(vm, _data, key)
,然后我们转移到proxy这个方法中:
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) }
就是通过sharedpropertydefinition.get
和sharedpropertydefinition.set
的设置的get和set方法,然后在通过object.defineproperty
来定义访问target.key
的时候调用sharedpropertydefinition
的set和get。
也就是相当于,我要求vm.message
,就会触发sharedpropertydefinition
的get,然后返回vm._data.message
至此数据就可以通过vm.message
的方式访问了。