new Vue大致干了啥

new Vue大致干了啥

接触vue也有段时间了,经手的项目也有几个了,一直没怎么研究下源码,趁着没有加班的日子,参考了几篇文章的分析后,自己也想看看new Vue的大致过程,加深对vue的了解,以便于日后更好的编写vue代码

我们无论是用官方的脚手架,还是自己搭建的项目模板,最终都会创建一个vue的实例对象并挂载到指定dom上,我们可以从new Vue()的实例化过程中,作为vue源码分析的入口,此篇文章在具体实现上面不做深入了解,意在浅析vue的大致结构,对new Vue有一个整体的了解

1. vue入口:new Vue()构造函数

// vue/src/platform/web/entry-runtime.js

/* @flow */

import Vue from './runtime/index'

export default Vue

从vue1到vue2的迭代上,vue采用了flow进行静态代码类型检查

// vue/src/platform/web/runtime/index.js

import Vue from 'core/index'

import config from 'core/config'

import { extend, noop } from 'shared/util'

import { mountComponent } from 'core/instance/lifecycle'

import { devtools, inBrowser, isChrome } from 'core/util/index'

继续找到Vue的引用所在地

// vue/src/core/instance/index.js

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

2. 构造函数干了什么

到这里,我们可以在上述代码中看到Vue的构造函数,在构造函数中执行了_init,随后执行了导入的五大Mixin,进行实例化的初始化过程

initMixin(Vue) // options初始化

stateMixin(Vue) // 状态(props、state、computed、watch)

eventsMixin(Vue) // 事件

lifecycleMixin(Vue) // 生命周期

renderMixin(Vue) // 页面渲染

找到_init执行函数

export function initMixin (Vue: Class) {

Vue.prototype._init = function (options?: Object) {

const vm: Component = this

// a uid

vm._uid = uid++

...

这个函数主要对我们在实例化中的配置与默认配置进行了合并,并且依次执行了以下几步

initLifecycle(vm)

initEvents(vm)

initRender(vm)

callHook(vm, 'beforeCreate')

initInjections(vm) // resolve injections before data/props

initState(vm)

initProvide(vm) // resolve provide after data/props

callHook(vm, 'created')

/* istanbul ignore if */

if (process.env.NODE_ENV !== 'production' && config.performance && mark) {

vm._name = formatComponentName(vm, false)

mark(endTag)

measure(`vue ${vm._name} init`, startTag, endTag)

}

if (vm.$options.el) {

vm.$mount(vm.$options.el)

}

initLifecycle: 初始化生命周期

initEvents: 初始化事件

initRender: 渲染页面

callHook(vm, 'beforeCreate'): beforeCreate钩子函数

initState:初始化状态 props data computed watch methods

callHook(vm, 'created'):created钩子函数

我们重点关注下 initState中的 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 */)

}

在上面的代码中找到两个关键字 proxy 和 observe

前者的作用:

我们在vue中调用数据: this.demo = 123

但是在源码初始化的过程中,是这样的 this._data.demo = 123

proxy就是将key值做了代理,简化了调用,方便了我们

后者的作用:

开始进行双向数据绑定 observe(data, true /* asRootData */)

简化后的observe

export function observe (value) {

if (!isObject(value)) {

return

}

let ob = new Observer(value)

return ob

}

export class Observer {

constructor (value) {

this.value = value

this.dep = new Dep()

this.vmCount = 0

def(value, '__ob__', this)

this.walk(value)

}

walk (obj) {

const keys = Object.keys(obj)

for (let i = 0; i < keys.length; i++) {

defineReactive(obj, keys[i], obj[keys[i]])

}

}

}

export function defineReactive (obj, key, val) {

const dep = new Dep()

let childOb = observe(val)

Object.defineProperty(obj, key, {

enumerable: true,

configurable: true,

// 取值时给数据添加依赖

get: function reactiveGetter () {

const value = val

if (Dep.target) {

dep.depend()

if (childOb) {

childOb.dep.depend()

}

}

return value

},

// 赋值时通知数据依赖更新

set: function reactiveSetter (newVal) {

const value = val

if (newVal === value) {

return

}

val = newVal

childOb = observe(newVal)

dep.notify()

}

})

}

这里在简单阐述下vue双向数据绑定的原理:

发布者-订阅者 + 数据劫持

在上述的代码中,重点关注 defineReactive函数,对vue对象中的每个属性进行了递归遍历的监听,利用 Object.defineProperty对每个属性进行监听,在取值的时候添加依赖进行依赖收集,在复制的时候进行通知订阅者进行依赖更新。

具体的细节请参考:https://segmentfault.com/a/1190000006599500