Vue 构造函数

如何寻找Vue构造函数

当我们使用 Vue 的时候,常常这样开始:

1
2
3
4
5
6
var app = new Vue({
  el: '#app',
  data: {
	message: 'Hello Vue!'
  }
})

那么当我们 new Vue() 的时候,到底发生了什么??

跟我心中念着 「洋葱」这首歌:一层一层地拨开我的心

首先我们从package.jsonnpm run dev 着手

1
"dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",

有写过webpack配置的话,应该理解rollup就没难度了。这里大概就不重点解释了。先不管细节,反正我们知道了要去 scripts 文件夹下找到 config.js

打开后,寻找有 web-full-dev那行, 这就是上面命令 --environment TARGET:web-full-dev" 的含义。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const aliases = require('./alias')

const builds = {
  ...
  // Runtime+compiler development build (Browser)
  'web-full-dev': {
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'),
    format: 'umd',
    env: 'development',
    alias: { he: './entity-decoder' },
    banner
  },
  ...
  }
  // 生成配置的方法
function genConfig(name){
    ...
}

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

从 entry可知,入口是 web/entry-runtime-with-compiler.js

那么 web又是在哪呢?

这时候结合第一行的const aliases = require('./alias'),打开同一目录下的alias.js文件,我们知道web这个别名实际所指的位置:

1
2
3
4
5
module.exports = {
  ...
  web: resolve('src/platforms/web'),
  ..
}

最终,得到入口文件是 src/platforms/web/entry-runtime-with-compiler.js

下面!! 我们即将进入正题!!

打开 entry-runtime-with-compiler.js,我们可以看到

1
import Vue from './runtime/index'

再往深处,继续打开runtime/index,你会发现第一行是

1
import Vue from 'core/index'

终于 按照思路,剥开洋葱般, 我们知道 Vue构造函数的位置应该是在 src/core/instance/index.js 文件中.

分析Vue原型实例

看一看 src/core/instance/index.js文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '../util/index'

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

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

export default Vue

引入依赖,定义 Vue 构造函数,然后以Vue构造函数为参数,调用了五个方法,最后导出 Vue。 我们找到这五个文件:'./init''./state' './render' './events' './lifecycle' 下这些mixin方法,可以知道这些方法或属性最后会挂载到 Vue 的prototype 上。 所以 其实Vue应该长这样:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// initMixin(Vue)    src/core/instance/init.js **************************************************
Vue.prototype._init = function (options?: Object) {}

// stateMixin(Vue)    src/core/instance/state.js **************************************************
// 利用Object.defineProperty 处理$data属性
Object.defineProperty(Vue.prototype, '$data', dataDef)
// 利用Object.defineProperty 处理$props属性
Object.defineProperty(Vue.prototype, '$props', propsDef)
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ){}

// renderMixin(Vue)    src/core/instance/render.js **************************************************
Vue.prototype.$nextTick = function (fn: Function) {}
Vue.prototype._render = function (): VNode {}

// eventsMixin(Vue)    src/core/instance/events.js **************************************************
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {}
Vue.prototype.$once = function (event: string, fn: Function): Component {}
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {}
Vue.prototype.$emit = function (event: string): Component {}

// lifecycleMixin(Vue)    src/core/instance/lifecycle.js **************************************************
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {}
Vue.prototype.$forceUpdate = function () {}
Vue.prototype.$destroy = function () {}

在这里有两个需要关注的地方:

1
2
Object.defineProperty(Vue.prototype, '$data', dataDef)
Object.defineProperty(Vue.prototype, '$props', propsDef)

我们可以看到,其他属性或方法都是通过prototype原型链的方式添加属性,但是$data$props 是利用Object.defineProperty()的方式添加到原型中。那么这里有什么奥妙呢?

看到同一个stateMixin()函数下的如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.
  const dataDef = {}
  dataDef.get = function () { return this._data }
  const propsDef = {}
  propsDef.get = function () { return this._props }
  if (process.env.NODE_ENV !== 'production') {
	dataDef.set = function (newData: Object) {
	  warn(
		'Avoid replacing instance root $data. ' +
		'Use nested data properties instead.',
		this
	  )
	}
	propsDef.set = function () {
	  warn($props is readonly., this)
	}
  }

这里利用defineProperty()方法的特性,修改了相应属性的特性。 $data$props定义了各自的 getter方法。对于属性$data$props的访问会自动调用一个隐藏函数,它的返回值会被当作属性访问的返回值,也就是返回 this._datathis._props 。由于我们只定义了 this._datathis._props 的getter, 所以对$data$props的值进行设置时set操作会忽略赋值操作,不会抛出错误。即便有合法的setter,set操作也是没有意义的,所以在下面的 if (process.env.NODE_ENV !== 'production') {..}中,对非生产环境下的setter操作,即试图修改$data$props的值的时候会抛出相应的警告。所以$data$props是只读的.

那么在src/core/instance/index.jsexport default Vue 后,Vue下一步又有怎样的变化呢?我们留到下回分解。

-EOF-