Vue源码之旅(1)-vue实例

vue version 1.0.28。后续会有与2.x版本的比较。原创,欢迎转载(请注明链接)。未完待续。


入口文件

导入Vue类,添加全局api(例如:编译器,工具方法,解析器,指令,过滤器….),这些之后文章会单独介绍。
后面是对chrome扩展程序Vue devtool的处理,在开发环境中会唤醒Vue devtool,没安装的话在chrome环境下回提示用户进行安装已获得更好的开发体验。开发者工具主要用来组件数据可视化的。
关键代码:

installGlobalAPI(Vue)

Vue.version = '1.0.28'

export default Vue

// devtools global hook
/* istanbul ignore next */
setTimeout(() => {
  if (config.devtools) {
    if (devtools) {
      devtools.emit('init', Vue)
    } else if (
      process.env.NODE_ENV !== 'production' &&
      inBrowser && /Chrome\/\d+/.test(window.navigator.userAgent)
    ) {
      console.log(
        'Download the Vue Devtools for a better development experience:\n' +
        'https://github.com/vuejs/vue-devtools'
      )
    }
  }
}, 0)

Vue实例

主要分为2大部分:内部和外部api

  • 公共方法/属性以 $ 开头
  • 私有方法/属性以 _ 开头
  • 其他属性为用户自定义

入口文件:


function Vue (options) {
  this._init(options)
}

// install internals
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
miscMixin(Vue)

// install instance APIs
dataAPI(Vue)
domAPI(Vue)
eventsAPI(Vue)
lifecycleAPI(Vue)

export default Vue

Vue初始化

initMixin(Vue)做了什么?要从internal/init.js看起。
绑定_init方法到Vue原型上。初始化一堆变量。

  • 绑定上下文_content
  • 给每个实例都绑定上递增的uid
  • 打通构造器options和用户自定义options和实例,共享数据。
  • 触发钩子函数init&created。可以看到 init 是在除state和events初始化前进行触发的, created 是在其之后触发。
    `js
    let uid = 0

export default function (Vue) {
/**

  • The main init sequence. This is called for every
  • instance, including ones that are created from extended
  • constructors.
    *
  • @param {Object} options - this options object should be
  • the result of merging class
  • options and the options passed
  • in to the constructor.
    */

    Vue.prototype._init = function (options) {
    options = options || {}

    this.$el = null
    this.$parent = options.parent
    this.$root = this.$parent
    ? this.$parent.$root
    : this
    this.$children = []
    this.$refs = {} // child vm references
    this.$els = {} // element references
    this._watchers = [] // all watchers as an array
    this._directives = [] // all directives

    // a uid
    this._uid = uid++

    // a flag to avoid this being observed
    this._isVue = true

    // events bookkeeping
    this._events = {} // registered callbacks
    this._eventsCount = {} // for $broadcast optimization

    // fragment instance properties
    this._isFragment = false
    this._fragment = // @type {DocumentFragment}
    this._fragmentStart = // @type {Text|Comment}
    this._fragmentEnd = null // @type {Text|Comment}

    // lifecycle state
    this._isCompiled =
    this._isDestroyed =
    this._isReady =
    this._isAttached =
    this._isBeingDestroyed =
    this._vForRemoving = false
    this._unlinkFn = null

    // context:
    // if this is a transcluded component, context
    // will be the common parent vm of this instance
    // and its host.
    this._context = options._context || this.$parent

    // scope:
    // if this is inside an inline v-for, the scope
    // will be the intermediate scope created for this
    // repeat fragment. this is used for linking props
    // and container directives.
    this._scope = options._scope

    // fragment:
    // if this instance is compiled inside a Fragment, it
    // needs to register itself as a child of that fragment
    // for attach/detach to work properly.
    this._frag = options._frag
    if (this._frag) {
    this._frag.children.push(this)
    }

    // push self into parent / transclusion host
    if (this.$parent) {
    this.$parent.$children.push(this)
    }

    // merge options.
    options = this.$options = mergeOptions(
    this.constructor.options,
    options,
    this
    )

    //更新父组件$refs属性,绑定到当前组件
    this._updateRef()

    // initialize data as empty object.
    // it will be filled up in _initData().
    this._data = {}

    // call init hook
    this._callHook(‘init’)

    // 启动实例作用域包括对数据observe,计算属性computed,自定义methods
    // 主要是对数据流进行处理,监听数据变化,变化后对绑定的计算属性进行计算
    this._initState()

    // 绑定实例的events & watchers,用于组件间同行,和数据变动后的watch钩子函数触发
    this._initEvents()

    // call created hook
    this._callHook(‘created’)

    // if el option is passed, start compilation.
    if (options.el) {
    //el: {Element|DocumentFragment|string}
    //编译处理dom
    this.$mount(options.el)
    }
    }
    }

    ## Vue api
    这部分是暴露给框架使用者的api
    包括data的操作:
    1. $get, $set, $delete 解析表达式
    ```js
    Vue.prototype.$get = function (exp, asStatement) {
    var res = parseExpression(exp)
    if (res) {
    if (asStatement) {
    var self = this
    return function statementHandler () {
    self.$arguments = toArray(arguments)
    var result = res.get.call(self, self)
    self.$arguments = null
    return result
    }
    } else {
    try {
    return res.get.call(this, this)
    } catch (e) {}
    }
    }
    }
    function parseExpression (exp, needSet) {
    exp = exp.trim()
    // 内存已存放直接使用
    var hit = expressionCache.get(exp)
    if (hit) {
    if (needSet && !hit.set) {
    hit.set = compileSetter(hit.exp)
    }
    return hit
    }
    var res = { exp: exp }
    //简单路径,例如{{user.name}}{{path + '/lala'}}
    //复杂路径,例如{{user[nam + 'e']}}
    res.get = isSimplePath(exp) && exp.indexOf('[') < 0
    // 例如exp='user.name'
    // 返回function(scope){return scope.user.name}
    ? makeGetterFn('scope.' + exp)
    // dynamic getter
    : compileGetter(exp)
    if (needSet) {
    res.set = compileSetter(exp)
    }
    expressionCache.put(exp, res)
    return res
    }
    

    2, $watch, $eval, $interpolate就暂时不展开说了,感觉每个都可以当做一篇来写。

结语

大家有什么看法欢迎各种渠道沟通,谢谢~