Vue 构造函数
如何寻找Vue构造函数
当我们使用 Vue 的时候,常常这样开始:
1
2
3
4
5
6
|
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
|
那么当我们 new Vue()
的时候,到底发生了什么??
跟我心中念着 「洋葱」这首歌:一层一层地拨开我的心
首先我们从package.json
的 npm 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._data
和this._props
。由于我们只定义了 this._data
和this._props
的getter, 所以对$data
和$props
的值进行设置时set操作会忽略赋值操作,不会抛出错误。即便有合法的setter,set操作也是没有意义的,所以在下面的 if (process.env.NODE_ENV !== 'production') {..}
中,对非生产环境下的setter操作,即试图修改$data
或$props
的值的时候会抛出相应的警告。所以$data
和$props
是只读的.
那么在src/core/instance/index.js
中export default Vue
后,Vue下一步又有怎样的变化呢?我们留到下回分解。
-EOF-