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

源码地址

<p style="font-size: 2rem;color: red;margin-bottom: 1.5rem">前方高能!!!</p>

这只是一篇个人学习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下面的属性上,三个都是空对象,<font color=#FF0000>再有一个_base = Vue
是自身的赋值,这里暂时不知道为什么要这么做,可能是为了缓存吧</font>,然后执行
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) + '<br>' +
      'value: '      + s(binding.value) + '<br>' +
      'expression: ' + s(binding.expression) + '<br>' +
      'argument: '   + s(binding.arg) + '<br>' +
      'modifiers: '  + s(binding.modifiers) + '<br>' +
      '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: '<span>{{ message }}</span>',
  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: {}
}

<p style="font-weight: bold;margin-bottom: 10px;color: #FF0000">剩下的几个问题</p>


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__'

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容

  • 都说巴黎出美女,巴黎的确有美女出没。倘若夏季来到莫斯科,那里简直就是眼睛的天堂,美女的海洋,片片是绝色美女群啊! ...
    米素文阅读 1,511评论 17 24
  • 我们每个人生活在这个五彩缤纷而又眼花缭乱的世界上,就必须有个固定的衣食来源来养活自己,借用黑格尔的一句话来说,一...
    崇修阅读 239评论 0 0
  • 冷吗 这扇漏风的窗子 像你在说话 我离开后 及时拍掉了墙灰 鱼群围上来 以为那是可吃的东西 我向每一条鱼都道歉了 ...
    竹喧归浣女阅读 126评论 5 6