1.最俗学习之-Vue源码学习-引入篇(上)

源码地址

前方高能!!!

这只是一篇个人学习Vue.js源码的笔记,并非教程,鉴于个人水平有限,可能存在错误,还望各路大神指点
文章内容极度粗俗,各种无脑分析,各种疯狂输出,各位看官斟酌而行,切勿走火入魔!!!

Vue.js版本 –2.1.7

之所以选择这个是因为看了这位大神的分析,决定采用同一个版本,目前Vue已经发布了2.5.x了
这里极力推荐大家去看看,据说这位大神的两篇源码分析都是经过尤大佬推荐的哦,本文作为第一篇
也是参考大神的文章作为开头,参考了极大部分再加上自己的理解而写的

Vue2.1.7源码学习

JavaScript实现MVVM之我就是想监测一个普通对象的变化

src/core/instance/index.js

这是整个vue的入口文件,首先引入vue后我们只是引入了一个构造函数,所以
一般我们初始化的时候都是用new Vue的方式启动,即下面的Vue函数,里面执行了一句
this._init(options),这个options即是我们传入的各种参数,即


new Vue({
  el: '#app',
  data: {
    name: 'zhang san',
    age: 18
  }
})

下面是src/core/instance/index.js的源码,其中引入vue的时候马上就初始化了5个mixin


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.js state.js render.js events.js 以及 lifecycle.js。
打开这五个文件,找到相应的方法,你会发现,这些方法的作用,就是在 Vue 的原型 prototype 上挂载方法或属性,经历了这五个方法后的Vue会变成这样:


// initMixin(Vue)    src/core/instance/init.js **************************************************
Vue.prototype._init = function (options?: Object) {}

// stateMixin(Vue)    src/core/instance/state.js **************************************************
Vue.prototype.$data
Vue.prototype.$set = set
Vue.prototype.$delete = del
Vue.prototype.$watch = function(){}

// renderMixin(Vue)    src/core/instance/render.js **************************************************
Vue.prototype.$nextTick = function (fn: Function) {}
Vue.prototype._render = function (): VNode {}
Vue.prototype._s = _toString
Vue.prototype._v = createTextVNode
Vue.prototype._n = toNumber
Vue.prototype._e = createEmptyVNode
Vue.prototype._q = looseEqual
Vue.prototype._i = looseIndexOf
Vue.prototype._m = function(){}
Vue.prototype._o = function(){}
Vue.prototype._f = function resolveFilter (id) {}
Vue.prototype._l = function(){}
Vue.prototype._t = function(){}
Vue.prototype._b = function(){}
Vue.prototype._k = function(){}

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

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

这样就结束了吗?并没有,根据我们之前寻找 Vue 的路线,这只是刚刚开始,我们追溯路线往
回走,那么下一个处理 Vue 构造函数的应该是 src/core/index.js 文件,我们打开它:


import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'

initGlobalAPI(Vue)

Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

Vue.version = '__VERSION__'

export default Vue

我们则从这里作为第一出发点开始

首先这里说下这个isServerRendering,找到这个env文件,里面有个方法


export const inBrowser = typeof window !== 'undefined'

let _isServer
export const isServerRendering = () => {
  if (_isServer === undefined) {
    /* istanbul ignore if */
    if (!inBrowser && typeof global !== 'undefined') {
      // detect presence of vue-server-renderer and avoid
      // Webpack shimming the process
      _isServer = global['process'].env.VUE_ENV === 'server'
    } else {
      _isServer = false
    }
  }
  return _isServer
}

这段代码判断是否为服务端,const inBrowser = typeof window !== ‘undefined’这段代码
大概就明白了

然后绑定在构造函数的原型的一个属性$isServer上面


Object.defineProperty(Vue.prototype, '$isServer', {
  get: isServerRendering
})

然后再说initGlobalAPI方法的执行

src\core\global-api\index.js

这个文件导出一个函数,函数接受Vue构造函数作为参数


export function initGlobalAPI (Vue: GlobalAPI) {
  // config
  const configDef = {}
  configDef.get = () => config
  if (process.env.NODE_ENV !== 'production') {
    configDef.set = () => {
      util.warn(
        'Do not replace the Vue.config object, set individual fields instead.'
      )
    }
  }
  Object.defineProperty(Vue, 'config', configDef)
  Vue.util = util
  Vue.set = set
  Vue.delete = del
  Vue.nextTick = util.nextTick

  Vue.options = Object.create(null)
  config._assetTypes.forEach(type => {
    Vue.options[type + 's'] = Object.create(null)
  })

  // this is used to identify the "base" constructor to extend all plain-object
  // components with in Weex's multi-instance scenarios.
  Vue.options._base = Vue

  util.extend(Vue.options.components, builtInComponents)

  initUse(Vue)
  initMixin(Vue)
  initExtend(Vue)
  initAssetRegisters(Vue)
}

首先定义一个对象configDef,configDef.get关联到了config,打开src\core\config.js

里面其实就是导出一个对象,对象里面有很多的属性,其中有


_assetTypes: [
    'component',
    'directive',
    'filter'
  ],

  _lifecycleHooks: [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated'
  ],

这两个便是常见的属性和生命周期,然后判断在开发环境下则有个set方法用来发出提示的
最后Object.defineProperty(Vue, ‘config’, configDef),定义构造函数的一个静态
属性config,分别有get和set的方法的,然后绑定util,set,delete,nextTick在Vue
构造函数上面,然后创建一个options空对象,然后遍历config的_assetTypes,也就是
上面说的三个属性,把它们的名字加上s复数,即components,directives,filters绑定
再options下面的属性上,三个都是空对象,再有一个_base = Vue
是自身的赋值,这里暂时不知道为什么要这么做,可能是为了缓存吧
,然后执行
util.extend(Vue.options.components, builtInComponents),这里是把自身的默认
组件KeepAlive绑定到了options里面,即

Vue.config
Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick

Vue.options = {
    components: {
        KeepAlive
    },
    directives: {},
    filters: {},
    _base: Vue
}

最后执行4个方法


initUse(Vue)
initMixin(Vue)
initExtend(Vue)
initAssetRegisters(Vue)

这4个方法对应当前目录的4个js文件,首先是use.js,这里给Vue挂载了静态方法,并非原型上的
这个我们用的比较多,用来安装插件的,比如Vue.use(VueRouter)路由插件就是这样子,这里首先
判断installed是否安装过了,然后执行toArray方法,返回一个数组,toArray方法解释在
methods realizes目录下找到对应名字文件夹,然后args.unshift(this)在这个返回的数组头部
添加Vue构造函数,然后执行这个install方法,一般插件的install方法形式如官方文档所写


MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }

  })

  // 3. 注入组件
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

Vue.use(MyPlugin)

这里就是执行这个install方法了,即调用Vue的全局方法,注册指令,在prototype上挂载方法
或者使用全局mixin等等,这里还有个else语句,也就是install不是函数的情况,那么这种情况
应该是直接传入一个function,形如下面这样,但最后都是启动这个方法,在Vue构造函数上面做
一些操作


Vue.use(function (Vue, options) {
  // 1. 添加全局方法或属性
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }

  })

  // 3. 注入组件
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
})

最后则plugin.installed = true,设置为已经安装了,这样就不会重复安装插件了,这样
initUse(Vue)方法大致明白了思路


到了mixin.js文件,也就是第二个方法initMixin(Vue)
到了extend.js文件,也就是第二个方法initExtend(Vue)

这两个文件涉及到比较多的问题,加之个人水平有限,暂且留着,这里简单说下
mixin文件主要绑定了Vue.mixin方法,就是常用的全局混合,里面调用了mergeOptions合并策略
这个非常重要,后面细说,extend文件绑定了Vue.extend方法,这个就是常见的vue构造器,可以
理解为创建一个子组件吧,还添加了一个属性Vue.cid = 0。


这里直接看第4个方法initAssetRegisters(Vue),找到assets.js文件

这里又是遍历这个数组_assetTypes,这里和上面说的有点类似,当初看的时候有点懵逼了
其实是这样的


// 上面绑定是这这样的,在Vue.options上面绑定的

Vue.options = {
    components: {
        KeepAlive
    },
    directives: {},
    filters: {}
}

// 这里绑定的是这样的

Vue.component = function(id, definition){

}

Vue.directive = function(id, definition){

}

Vue.filter = function(id, definition){

}

// 一个在Vue的options属性上,一个直接挂载在Vue上,一个后面带有复数s,一个没有

这个三个方法都是同一个函数操作,接受两个参数,第一个是str即指令名称,第二个是fun或
者obj,如果没有第二个参数则返回对应的指令,比如


var directiveData = Vue.directive('show')

console.log(directiveData)

// 这样可以看到v-show指令的情况,这个情况一般较少

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

var filterData = Vue.filter('capitalize')

console.log(filterData)

// 这样可以看到自定义过滤器capitalize的方法,如果之前没有注册这个过滤器,那么则返回undefined

// 这里我们还是按照平常使用的方法例子来说,首先是指令

// 文档提供了两种方式注册指令

1.

Vue.directive('demo', {
  bind: function (el, binding, vnode) {
    var s = JSON.stringify
    el.innerHTML =
      'name: '       + s(binding.name) + '
'
+ 'value: ' + s(binding.value) + '
'
+ 'expression: ' + s(binding.expression) + '
'
+ 'argument: ' + s(binding.arg) + '
'
+ 'modifiers: ' + s(binding.modifiers) + '
'
+ 'vnode keys: ' + Object.keys(vnode).join(', ') } }) 2. Vue.directive('color-swatch', function (el, binding) { el.style.backgroundColor = binding.value }) // 但是会发现,第2种最后会经过 if (type === 'directive' && typeof definition === 'function') { definition = { bind: definition, update: definition } } // 这个if,最后还是变成了第1种的方式变成了 { bind: function (el, binding) { el.style.backgroundColor = binding.value }, update: function (el, binding) { el.style.backgroundColor = binding.value } } // 最后挂载在Vue构造函数上面this.options[type + 's'][id] = definition // return这个方法,这个倒是比较少用到 // 还记得Vue.options这个值是这样的 Vue.options = { components: { KeepAlive }, directives: { // 这两个是Vue自带的,在另一个地方初始化的,这里暂时这样理解就好,存在这两个方法的 model: { }, show: { }, // 如果经过了第一种方法注册指令,那么就会添加下面一个了 demo: { bind: function () { // some methods } } }, filters: {} }

当然,注册自定义指令还有其他的生命周期钩子和参数,可以参考文档自定义指令


// 然后是注册过滤器,方法和指令一样,只不过三个if都不走
// 直接是this.options[type + 's'][id] = definition
// 例如文档例子

Vue.filter('capitalize', function (value) {
  if (!value) return ''
  value = value.toString()
  return value.charAt(0).toUpperCase() + value.slice(1)
})

var filterData = Vue.filter('capitalize')

console.log(filterData)

// 这样就可以注册一个过滤器了

// 最后就剩下component方法了,这个是注册组件用的,按照文档例子说起

Vue.component('my-component', {
  name: 'my-name',
  template: '{{ message }}',
  data () {
    return {
      message: 'hello'
    }
  }
})

// 首先走第1个if,这里有个config.isReservedTag(id)方法,方法解释看methods realizes目录
// 这里构建前的源码的config.isReservedTag方法找到的是一个no方法,但其实这个方法在
// platforms\web\util\element.js文件里面,这里直接看构建后的源码即可
// 这个方法就是不允许用户使用html原有的标签作为组件的标签名字,然后到了第2个if语句,这里有个
// 方法isPlainObject,方法解释看methods realizes目录,这里这个是一个obj,如果没有传入name
// 则会使用id也就是注册的组件名字作为name,上面name是my-name,如果没有则name就是my-component
// 然后调用definition = this.options._base.extend(definition),这个其实就是上面跳过的方法
// initExtend(Vue)同样的,其实这里的this.options._base就是Vue构造函数,因为
// console.log(this.options._base === Vue)  // => true
// 最后挂载在this.options上面,就变成了这样

// 还记得Vue.options这个值是这样的

Vue.options = {
    components: {
      KeepAlive: {

      },
      // 这两个是Vue自带的,在另一个地方初始化的,这里暂时这样理解就好,存在这两个方法的
      Transition: {

      },
      TransitionGroup: {

      },
      my-component: {

      }
    },
    directives: {
      // 这两个是Vue自带的,在另一个地方初始化的,这里暂时这样理解就好,存在这两个方法的
      model: {

      },
      show: {

      },
      // 如果经过了第一种方法注册指令,那么就会添加下面一个了
      demo: {
        bind: function () {
          // some methods
        }
      }
    },
    filters: {}
}

剩下的几个问题


Vue.set = set                       // 涉及到Vue的数据响应式系统,先保留
Vue.delete = del                    // 涉及到Vue的数据响应式系统,先保留
Vue.nextTick = util.nextTick        // 水平有限,看不懂 - -#
initMixin(Vue)                      // 这个后面再讲
initExtend(Vue)                     // 水平有限,看不懂 - -#

综上所述:

initGlobalAPI 的作用是在 Vue 构造函数上挂载静态属性和方法,Vue 在经过 initGlobalAPI
之后,会变成这样:


Vue.config
Vue.util = util
Vue.set = set
Vue.delete = del
Vue.nextTick = util.nextTick

Vue.options = {
    components: {
        KeepAlive
    },
    directives: {},
    filters: {},
    _base: Vue
}

Vue.use
Vue.mixin
Vue.cid = 0
Vue.extend
Vue.component = function(){}
Vue.directive = function(){}
Vue.filter = function(){}

Vue.prototype.$isServer

Vue.version = '__VERSION__'

你可能感兴趣的:(vue)