Vue.js源码解读--(1)

Vue框架对于前端来说有多重要就不多提了,三天前决定看看源码,奈何自己是个菜鸡,只能慢慢的一点一点啃,进行扫荡式学习,初有收获,特将笔记所记内容记下,逻辑略乱,各位客官觉得乱或者有问题的话请评论说下,我会重新组织语言并回答您。

本文为小白从头扫荡式教程,我都能懂你肯定也能的~

好的,下面开始。

首先你要去github把源码下载下来啦-- https://github.com/vuejs/vue 

首先我们进入package.json文件 在我们 npm run dev后执行这个语句

"scripts": {
    "dev": "rollup -w -c scripts/config.js --environment TARGET:web-full-dev",

-w为watch,监听,-c修改执行文件路径,进入scripts/config.js文件内,最底部。

if (process.env.TARGET) {
  module.exports = genConfig(process.env.TARGET)
} else {
  exports.getBuild = genConfig
  exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
}

process.env.TARGET存在,执行

function genConfig (name) {
  const opts = builds[name]
  const config = {
    input: opts.entry,
    external: opts.external,
    sourceMap:true,
    plugins: [
      replace({
        __WEEX__: !!opts.weex,
        __WEEX_VERSION__: weexVersion,
        __VERSION__: version
      }),
      flow(),
      buble(),
      alias(Object.assign({}, aliases, opts.alias))
    ].concat(opts.plugins || []),
    output: {
      file: opts.dest,
      format: opts.format,
      banner: opts.banner,
      name: opts.moduleName || 'Vue'
    }
  }

  if (opts.env) {
    config.plugins.push(replace({
      'process.env.NODE_ENV': JSON.stringify(opts.env)
    }))
  }

  Object.defineProperty(config, '_name', {
    enumerable: false,
    value: name
  })

  return config
}

上面方法为把builds相同属性的内容赋给opts,所以我们的opts如下

 '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
  },

接下来看到入口文件为resolve('web/entry-runtime-with-compiler.js') 

const aliases = require('./alias')
const resolve = p => {
  const base = p.split('/')[0]
  if (aliases[base]) {
    return path.resolve(aliases[base], p.slice(base.length + 1))
  } else {
    return path.resolve(__dirname, '../', p)
  }
}

resolve方法将传入参数经过aliases方法改变,所以最后我们得到的入口路径为src/platforms/web/entry-runtime-with-compiler.js。

在该文件内,Vue由./runtime/index导入,进入后,Vue由core/index导入,进入后,Vue由./instance/index导入(instance有实例的意思),进入后,即可发现Vue的构造函数。

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构造函数中,前五行判断开发者必须用NEW实例化vue,

new vue时,其中的el等值传到option内,然后调用_init方法。
构造函数下方,调用initMixin(),进入该文件内

 1 let uid = 0
 2 
 3 export function initMixin (Vue: Class<Component>) {
 4   Vue.prototype._init = function (options?: Object) {
 5     const vm: Component = this
 6     // a uid
 7     vm._uid = uid++
 8 
 9     let startTag, endTag
10     /* istanbul ignore if */
11     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
12       startTag = `vue-perf-start:${vm._uid}`
13       endTag = `vue-perf-end:${vm._uid}`
14       mark(startTag)
15     }
16 
17     // a flag to avoid this being observed
18     vm._isVue = true
19     // merge options
20     if (options && options._isComponent) {
21       // optimize internal component instantiation
22       // since dynamic options merging is pretty slow, and none of the
23       // internal component options needs special treatment.
24       initInternalComponent(vm, options)
25     } else {
26         console.log(Vue.options)
27       vm.$options = mergeOptions(
28         resolveConstructorOptions(vm.constructor),
29         options || {},
30         vm
31       )
32     }
33     /* istanbul ignore else */
34     if (process.env.NODE_ENV !== 'production') {
35       initProxy(vm)
36     } else {
37       vm._renderProxy = vm
38     }
39     // expose real self
40     vm._self = vm
41     initLifecycle(vm)
42     initEvents(vm)
43     initRender(vm)
44     callHook(vm, 'beforeCreate')
45     initInjections(vm) // resolve injections before data/props
46     initState(vm)
47     initProvide(vm) // resolve provide after data/props
48     callHook(vm, 'created')
49 
50     /* istanbul ignore if */
51     if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
52       vm._name = formatComponentName(vm, false)
53       mark(endTag)
54       measure(`vue ${vm._name} init`, startTag, endTag)
55     }
56 
57     if (vm.$options.el) {
58       vm.$mount(vm.$options.el)
59     }
60   }
61 }
View Code

第一行为被调用方法,第二行在vue构造函数中挂载了_init方法,所以在构造函数或实例内都可以直接调用。
参数写法为flow,第一行参数意思为传入参数类型为component,第二行为传入参数类型为Object,加问号意思为有无皆可,但类型必须为Object,问号在后同理。继续

函数内第一行的this指向被调用函数,即为Vue实例化的对象,然后将函数挂载两个属性:_uid,_isVue,然后判断options是否存在且其中是否有_isComponent,在寻找构造函数Vue时,core/index中将构造函数挂载了options,下图为core/index.js,

import Vue from './instance/index'
import { initGlobalAPI } from './global-api/index'
import { isServerRendering } from 'core/util/env'
import { FunctionalRenderContext } from 'core/vdom/create-functional-component'

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

Object.defineProperty(Vue.prototype, '$ssrContext', {
  get () {
    /* istanbul ignore next */
    return this.$vnode && this.$vnode.ssrContext
  }
})

// expose FunctionalRenderContext for ssr runtime helper installation
Object.defineProperty(Vue, 'FunctionalRenderContext', {
  value: FunctionalRenderContext
})
        console.log(Vue.options)

Vue.version = '__VERSION__'

export default Vue

在initGlobalAPI(下图)方法中,前十行将config值赋给configDef,并命名为config到Vue上,且不允许修改configDef的值。然后又加了util,set,delete,nexttick。

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

  // exposed util methods.
  // NOTE: these are not considered part of the public API - avoid relying on
  // them unless you are aware of the risk.
  Vue.util = {
    warn,
    extend,
    mergeOptions,
    defineReactive
  }

  Vue.set = set
  Vue.delete = del
  Vue.nextTick = nextTick

  Vue.options = Object.create(null)
  ASSET_TYPES.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

  extend(Vue.options.components, builtInComponents)

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

下图为被加的config值

export type Config = {
  // user
  optionMergeStrategies: { [key: string]: Function };
  silent: boolean;
  productionTip: boolean;
  performance: boolean;
  devtools: boolean;
  errorHandler: ?(err: Error, vm: Component, info: string) => void;
  warnHandler: ?(msg: string, vm: Component, trace: string) => void;
  ignoredElements: Array<string | RegExp>;
  keyCodes: { [key: string]: number | Array<number> };

  // platform
  isReservedTag: (x?: string) => boolean;
  isReservedAttr: (x?: string) => boolean;
  parsePlatformTagName: (x: string) => string;
  isUnknownElement: (x?: string) => boolean;
  getTagNamespace: (x?: string) => string | void;
  mustUseProp: (tag: string, type: ?string, name: string) => boolean;

  // legacy
  _lifecycleHooks: Array<string>;
};

继续initGlobalAPI方法,Vue加了options方法,并循环ASSET_TYPES的值末尾加s作为键,值为null,后加了options._base,再用extend方法将components对象中添加KeepAlive,extend方法目前理解为将第二个值复制给第一个值,(keepalive是一个内容很多的重要文件,在src/core/components中,标记以后看),最下面四个方法分别加了use,mixin,extend(和上边的不是一个),并在extend中使Vue.cid=0,最后一个方法被直接调用,如下图二。

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
import { ASSET_TYPES } from 'shared/constants'
import { isPlainObject, validateComponentName } from '../util/index'

export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        return this.options[type + 's'][id]
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          definition = this.options._base.extend(definition)
        }
        if (type === 'directive' && typeof definition === 'function') {
          definition = { bind: definition, update: definition }
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

该方法中,将Vue[component]加上了方法,由于没有definition值,options内也无id,所以返回的也是空(该过程仅仅是将Vue添加了三个空方法)。在initglobalapi方法经历一番后,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(){}
let _isServer
export const isServerRendering = () => {
  if (_isServer === undefined) {
    /* istanbul ignore if */
    if (!inBrowser && !inWeex && 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
}

最后回到initglobalapi方法调用处,第二,三个皆为在Vue.prototype调用引号内属性时,使用get内方法,isServerRendering方法(上图)为当调用时使当前环境变为server,第三个同理(暂时不知道$vnode是什么),第四个在Vue加上了FunctionalRenderContext属性名及调用该方法的值(无参数,)最后Vue加上了版本。

再往前回到initMixin方法中,options存在但没有options._isComponent,走else,

export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        extend(Ctor.extendOptions, modifiedOptions)
      }
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}

resolveConstructorOptions方法中,参数为vm.constructor,即为Vue构造函数,暂无super值,直接返回options,调用mergeOptions方法,传入三个参数,分别为Vue.options,实例化时所传参数,和Vue实例化对象。在mergeOptions方法中,

export function mergeOptions (
  parent: Object, //Vue.options
  child: Object, //实例化传入参数
  vm?: Component //Vue实例化对象
): Object {
  if (process.env.NODE_ENV !== 'production') {  
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)
  const extendsFrom = child.extends
  if (extendsFrom) {
    parent = mergeOptions(parent, extendsFrom, vm)
  }
  if (child.mixins) {
    for (let i = 0, l = child.mixins.length; i < l; i++) {
      parent = mergeOptions(parent, child.mixins[i], vm)
    }
  }
  const options = {}
  let key
  for (key in parent) { //循环Vue.options的键
    mergeField(key)
  }
  for (key in child) { //循环实例化参数的键
    if (!hasOwn(parent, key)) { //如果当前的键与Vue.options所有的键不重复
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat;     //strats[key]寻找方法,strat定义方法 //defaultStrat为哪个有值返回哪个
    
    options[key] = strat(parent[key], child[key], vm, key);     //左边在options定义相同的键名,右边调用上边的方法 //相当于调starts.key方法并传参
  }
//console.log(parent)
//console.log(child)
//console.log(options)
  return options
}
function checkComponents (options: Object) {
  for (const key in options.components) {
    validateComponentName(key)
  }
}

export function validateComponentName (name: string) {
  if (!/^[a-zA-Z][\w-]*$/.test(name)) {
    warn(
      'Invalid component name: "' + name + '". Component names ' +
      'can only contain alphanumeric characters and the hyphen, ' +
      'and must start with a letter.'
    )
  }
  if (isBuiltInTag(name) || config.isReservedTag(name)) {
    warn(
      'Do not use built-in or reserved HTML elements as component ' +
      'id: ' + name
    )
  }
  
}

checkComponents方法为检查实例化所传参数components的值是否合法(两个方法分别为检查是否与内置或保留元素重名)。然后检查child是否为function,传入参数的prop.inject,directives,以及两个判断。

然后定义options,循环options内的名及传入参数的名,进入方法。

function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?: Component,
  key: string
): Object {
  const res = Object.create(parentVal || null)
  if (childVal) {
    process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
    console.log(extend(res, childVal))
    return extend(res, childVal)
  } else {
    return res   
  }  
}

ASSET_TYPES.forEach(function (type) {
  strats[type + 's'] = mergeAssets
})

strats[属性名]对应相应方法,ASSET_TYPES中有三个,可为这三个添加方法,其余在该js上有独自方法,options[key]直接调用mergeAssets或defaultStrat或本页js上定义好的方法,最后合成options,重新梳理:循环Vue.options的键(为父,实例化参数为子)并定义同名键至新options,循环调用方法,ASSET_TYPES中有三个调用mergeAssets,该方法为如果同键名的子属性有值,则返回子属性的值,没有则返回空,添加至新的options,当没有strats.key方法时,则调用defaultStrat方法,该方法为如果子属性有值则返回子属性的值,没有则返回副属性的值,并添加至新options,调用strats.data时,有单独的方法,该方法返回了一个方法,最后返回options。总结:mergeOptions方法新建options对象,将ASSET_TYPES中三个参数设为新options的键并将值设为空,如果实例化传入的参数有相同的键则传该值,然后将Vue.options其余值添加键名至新options并单独处理其值,实例化传参的对象也同样,最后合并成一个拥有所有的键但值被处理过的对象。

 

继续回溯,我们在寻找Vue构造函数时在src/platforms/web/runtime/index.js停留过一会,现在看它,

Vue.config.mustUseProp = mustUseProp
Vue.config.isReservedTag = isReservedTag
Vue.config.isReservedAttr = isReservedAttr
Vue.config.getTagNamespace = getTagNamespace
Vue.config.isUnknownElement = isUnknownElement

// install platform runtime directives & components
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// install platform patch function
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

 

前五行为我们之前Vue内的config内的键赋新值,然后将Vue.options内的两个对象加新值,挂载patch,和$mount方法。query方法为获取该元素(获取不到则报错)。再回溯看
entry-runtime-with-compiler.js,

const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && query(el)
  /* istanbul ignore if */
  if (el === document.body || el === document.documentElement) {
    process.env.NODE_ENV !== 'production' && warn(
      `Do not mount Vue to <html> or <body> - mount to normal elements instead.`
    )
    return this
  }

  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) {
    let template = options.template
    if (template) {
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !template) {
            warn(
              `Template element not found or is empty: ${options.template}`,
              this
            )
          }
        }
      } else if (template.nodeType) {
        template = template.innerHTML
      } else {
        if (process.env.NODE_ENV !== 'production') {
          warn('invalid template option:' + template, this)
        }
        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }
    if (template) {
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }

      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns

      /* istanbul ignore if */
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating)
}
View Code

同样挂载了$mount方法,但我们先执行的是这个然后才是上一个,然后在最下方加上了Vue.compile。compileToFunctions 函数的作用,就是将模板 template 编译为render函数。
下面回到_init()方法内...本周就到这里

{

下面为小贴士~
在script/config.js内genConfig方法中,加上框内的可在浏览器调试(出现src目录),接下来就可以尽情的debugger了

}

 

posted on 2018-04-20 18:22  要一份黄焖鸡  阅读(3738)  评论(0编辑  收藏  举报

导航