Vue2.6.14源码分析

Vue2源码分析

资源

vue源码

当前版本:2.6.14

迁出项目:git clone https://github.com/vuejs/vue.git

文件架构

dist —> 发布目录,存放不同Vue版本,如完整版或运行时版

examples —> 范例,存放测试代码,直接用浏览器打开即可

flow —> Vue源码的类型检查是flow,存放flow的类型声明文件

types —> Vue新版支持typescript,此目录是ts的类型声明文件

packages —> 核心代码之外的独立库

scripts ----> 构建脚本

src ----> 源码

​ >> compiler ----> 编译器相关

​ >>> codegen —> 把AST转换为Render函数

​ >>> parser —> 解析模板成AST

​ >> platforms ----> 架构相关

​ >>> weex

​ >>> web

​ >>>> runtime ----> web端运行时相关代码

​ >>>> compiler ----> web编译器相关代码,将模板编译成render函数

​ >>>> server ----> 服务端渲染

​ >>>> util —> 工具类

​ >> core ----> 核心代码

​ >>> components —> 通用组件,如keep-alive

​ >>> global-api —> 全局api

​ >>> instance —> 构造函数等

​ >>> observer —> 响应式相关

​ >>> util —> 工具方法

​ >>> vdom —> 虚拟dom相关

调试环境搭建

  • 安装依赖:npm i —> 安装phantom.js时即可终止
  • 安装rollup: npm i -g rollup
  • 打开package.json,修改dev脚本,添加sourcemap --> 源码映射,可以在浏览器中使用源码调试

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

  • 运行开发命令: npm run dev ----> 打包文件放在dist目录下,文件名为vue.js

  • 在examples目录下的测试案例中,引入前面创建的vue.js

入口

执行npm run dev命令,其实际执行的是package.json中scripts下的dev脚本:

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

dev脚本中-c scripts/config.js指明配置文件的位置;TARGET:web-full-dev指明了输出文件的配置项

'web-full-dev': {
   
    // 入口: src/platforms/web/entry-runtime-with-compiler.js
    entry: resolve('web/entry-runtime-with-compiler.js'),
    dest: resolve('dist/vue.js'), // 输出文件的目录dist/vue.js
    format: 'umd',// umd版本
    env: 'development',
    alias: {
    he: './entity-decoder' },
    banner
},

在dist目录下有很多Vue.js的构建版本:

  • ​ 完整版:包含编译器和运行时的版本;
  • ​ 编译器:将template编译成javascript渲染函数的代码;
  • ​ 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切;
  • ​ UMD:UMD 版本可以通过 <script> 标签直接用在浏览器中,默认文件就是运行时 + 编译器的UMD版本 (vue.js);
  • ​ CommonJS:CJS 版本用来配合老的打包工具比如webpack 1,只包含运行时的版本 (vue.runtime.common.js);
  • ​ ES Module:ESM版本用于webpack2+,默认文件是只有运行时版本 (vue.runtime.esm.js),我们使用vue-cli创建的项目使用的就是这个版本。
// vue.runtime.esm.js:由于cli使用的vue版本不带编译器,因此无法使用template或el,只能用render
new Vue({
   
    // template: '<div>template</div>',
    render(h) {
   
    	return h(App)
    }
}).$mount('#app')
// examples/test/init.html:测试代码
<!DOCTYPE html>
<html>

<head>
    <title>Vue源码剖析</title>
    <!-- 这里使用的vue.js是带编译器、umd格式的版本 -->
    <!-- 平时cli创建项目,使用的vue.runtime.esm.js,不带编译器 -->
    <script src="../../dist/vue.js"></script>
</head>

<body>
    <div id="demo">
        <h1>初始化流程</h1>
        <p>{
   {
    foo }}</p>
        <p>{
   {
   obj.foo}}</p>
        <div v-for="a in arr" :key="a">
          {
   {
    a }}
        </div>
        <!--普通事件-->
        <p @click="onClick">this is p</p>
        <!--自定义事件-->
        <comp @comp-click="onCompClick"></comp>
    </div>
    <script>
        Vue.component('comp', {
   
            template: `
                  <div @click="onClick">this is comp</div>
              `,
            methods: {
   
                onClick() {
   
                    this.$emit('comp-click')
                }
            }
        })
        // render > template > el
        // 创建vue实例
        // 省略了$mount()
        const app = new Vue({
   
            el: '#demo',
            // template: '<div>template</div>',
            // template: '#app',
            // render(h){return h('div','render')},
            data:{
   
              foo:'foo',
              obj: {
    foo: 'obj_foo' },
              arr: ['arr_foo', 'arr_bar'] 
            },
            methods: {
   
                onClick() {
   
                    console.log('普通事件');
                },
                onCompClick() {
   
                    console.log('自定义事件');
                }
            },
            mounted() {
   
                setTimeout(() => {
   
                    this.foo = 'fooooo'
                }, 1000);
            },
            beforeCreate(){
   
              console.log('beforeCreate ');
            }
        })
        // 由于cli使用的vue版本不带编译器,因此无法使用template或el,只能用render
        // new Vue({
   
        //     render(h) {
   
        //         return h(App)
        //     }
        // }).$mount('#app')

        console.log(app.$options.render);
        
    </script>
</body>
</html>
// src/platforms/web/entry-runtime-with-compiler.js

import {
    warn, cached } from 'core/util/index'
import {
    mark, measure } from 'core/util/perf'

import Vue from './runtime/index'
import {
    query } from './util/index'
import {
    compileToFunctions } from './compiler/index'
import {
    shouldDecodeNewlines, shouldDecodeNewlinesForHref } from './util/compat'

const idToTemplate = cached(id => {
   
    const el = query(id)
    return el && el.innerHTML
})

// 扩展$mount:解析初始化选项
// new Vue().$mount(el)
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function (
el?: string | Element,
 hydrating?: boolean
): Component {
   
    // el:#demo ---> 获取el对应的dom对象
    el = el && query(el)

    // this指的是vue实例或VueComponent实例,$options挂在在Vue实例上的属性,会经过this._init(options)进行合并
    const options = this.$options

    // 若不存在render属性
    if (!options.render) {
   
        // 获取options中的template属性值
        let template = options.template
        // 若存在template
        if (template) {
   
            // 若template为string,如 template: '#app' 或 template: '<div>template</div>'
            if (typeof template === 'string') {
   
                // 若template第一个字符为#,如 #app
                // 若首字母不是#,则template = '<div>template</div>'
                if (template.charAt(0) === '#') {
   
                    // 拿到template中id对应的dom对象
                    template = idToTemplate(template)
                    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,将el对象的dom对象中的outerHTML拿出,作为template
            template = getOuterHTML(el)
        }

        // 若template或el生成的template存在
        if (template) {
   

            // 编译器函数compileToFunctions将拿到的template编译成render函数
            // render() ---> vdom
            const {
    render, staticRenderFns } = compileToFunctions(template, {
   
                outputSourceRange: process.env.NODE_ENV !== 'production',
                shouldDecodeNewlines,
                shouldDecodeNewlinesForHref,
                delimiters: options.delimiters,
                comments: options.comments
            }, this)
            // 将render重新设置到options上
            options.render = render
            options.staticRenderFns = staticRenderFns
        }
    }
    // 默认工作:挂载
    return mount.call(this, el, hydrating)
}

function getOuterHTML (el: Element): string {
   
    if (el.outerHTML) {
   
        return el.outerHTML
    } else {
   
        const container = document.createElement('div')
        container.appendChild(el.cloneNode(true))
        return container.innerHTML
    }
}

Vue.compile = compileToFunctions

export default Vue
// src/platforms/web/runtime/index.js
import Vue from 'core/index'
import {
    extend, noop } from 'shared/util'
import {
    mountComponent } from 'core/instance/lifecycle'
import {
    devtools, inBrowser } from 'core/util/index'

import {
    patch } from './patch'
import platformDirectives from './directives/index'
import platformComponents from './components/index'

// 安装平台运行时指令和组件
extend(Vue.options.directives, platformDirectives)
extend(Vue.options.components, platformComponents)

// 安装平台特有的patch函数:执行diff算法
Vue.prototype.__patch__ = inBrowser ? patch : noop

// public mount method
// Vue原型上定义$mount方法:实际上调用mountComponent, vdom => dom,并添加到页面上dom树上
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
   
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}
// src/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'

// 初始化全局api:定义Vue类属性,如Vue.set/delete/nextTick/observable/use/mixin/extend
initGlobalAPI(Vue)

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
})

Vue.version = '__VERSION__'

export default 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'

// Vue构造函数
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')
  }
  // 初始化: _init()在哪?
  this._init(options)
}

// mixin做什么的?声明vue实例属性和方法
initMixin(Vue) // _init()
stateMixin(Vue) // $attrs/$listeners/$set / $delete/$watch
eventsMixin(Vue) // $on()/$emit()/$once()/$off()
lifecycleMixin(Vue) // _update()/$forceUpdate()/$destroy()
renderMixin(Vue) // $nextTick()/_render()

export default Vue

文件加载顺序:

src/core/instance/index.js

  • initMixin:Vue原型上(Vue.prototype),定义_init()
  • stateMixin:Vue原型上,定义 d a t a ( 只 读 ) / data(只读)/ data()/props(只读)/ s e t ( ) / set()/ set()/delete()/$watch()
  • eventsMixin:Vue原型上,定义 o n ( ) / on()/ on()/emit()/ o n c e ( ) / once()/ once()/off()
  • lifecycleMixin:Vue原型上,定义_update()/ f o r c e U p d a t e ( ) / forceUpdate()/ forceUpdate()/destroy()
  • renderMixin:Vue原型上,定义$nextTick()/_render()

src/core/index.js

  • initGlobalAPI:定义Vue类属性和方法

    –> Vue.config(只读)

    –> Vue.util

    –> Vue.set

    –> Vue.delete

    –> Vue.nextTick

    –> Vue.observable

    —> Vue.options.components/directives/filters/_base

    –> initUse:Vue.use

    –> initMixin:Vue.mixin

    –> initExtend:Vue.extend

    –> initAssetRegisters:Vue.component / Vue.directive / Vue.filter

src/platforms/web/runtime/index.js

  • 安装web平台特有指令和组件
  • 在Vue.prototype上定义__patch__:补丁函数,执行patching算法进行更新
  • 在Vue.prototype上定义$mount:挂载vue实例到指定宿主元素(vdom => dom)

src/platforms/web/entry-runtime-with-compiler.js

  • 扩展默认$mount方法:处理template或el选项

初始化流程

​ 创建Vue实例时,只执行this._init(options)函数,因此从_init函数开始解析。

// 测试demo
<!DOCTYPE html>
<html>

<head>
    <title>Vue源码剖析</title>
    <!-- 这里使用的vue.js是带编译器、umd格式的版本 -->
    <!-- 平时cli创建项目,使用的vue.runtime.esm.js,不带编译器 -->
    <script src="../../dist/vue.js"></script>
</head>

<body>
    <div id="demo">
        <h1>初始化流程</h1>
        <p>{
   {
    foo }}</p>
        <p>{
   {
   obj.foo}}</p>
        <div v-for="a in arr" :key="a">
          {
   {
    a }}
        </div>
        <!--普通事件-->
        <p @click="onClick">this is p</p>
        <!--自定义事件-->
        <comp @comp-click="onCompClick"></comp>
    </div>
    <script>
        Vue.component('comp', {
   
            template: `
                  <div @click="onClick">this is comp</div>
              `,
            methods: {
   
                onClick() {
   
                    this.$emit('comp-click')
                }
            }
        })
        // render > template > el
        // 创建vue实例
        // 省略了$mount()
        const app = new Vue({
   
            el: '#demo',
            // template: '<div>template</div>',
            // template: '#app',
            // render(h){return h('div','render')},
            data:{
   
              foo:'foo',
              obj: {
    foo: 'obj_foo' },
              arr: ['arr_foo', 'arr_bar'] 
            },
            methods: {
   
                onClick() {
   
                    console.log('普通事件');
                },
                onCompClick() {
   
                    console.log('自定义事件');
                }
            },
            mounted() {
   
                setTimeout(() => {
   
                    this.foo = 'fooooo'
                }, 1000);
            },
            beforeCreate(){
   
              console.log('beforeCreate ');
            }
        })
        // 由于cli使用的vue版本不带编译器,因此无法使用template或el,只能用render
        // new Vue({
   
        //     render(h) {
   
        //         return h(App)
        //     }
        // }).$mount('#app')

        console.log(app.$options.render);
        
    </script>
</body>
</html>
// src/core/instance/init.js:执行Vue原型上的_init方法
Vue.prototype._init = function (options?: Object) {
   
    // 原型方法中的this表示实例,这里指的是vue实例;这里的作用是防止this指向改变,存为vm
    const vm: Component = this
    // a uid
    vm._uid = uid++

    let startTag, endTag
    /* istanbul ignore if */
    if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
   
        startTag = `vue-perf-start:${
     vm._uid}`
        endTag = `vue-perf-end:${
     vm._uid}`
        mark(startTag)
    }

    vm._isVue = true
    
    // 代码分析点一:合并选项
    if (options && options._isComponent) {
   
        // optimize internal component instantiation
        // since dynamic options merging is pretty slow, and none of the
        // internal component options needs special treatment.
        initInternalComponent(vm, options)
    } else {
   
        vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {
   },
            vm
        )
    }
    
    // 代码分析点二:代理
    if (process.env.NODE_ENV !== 'production') {
   
        initProxy(vm)
    } else {
   
        vm._renderProxy = vm
    }
    
    // 代码分析点三:挂载实例属性
    vm._self = vm
    initLifecycle(vm) // $parent/$root/$refs/..
    // <comp @click>
    initEvents(vm) // event 
    // <comp>xxxxxx</comp>
    initRender(vm) // $slots/$scopSlots/$createElement()
    // 代码分析点四:执行生命周期钩子
    callHook(vm, 'beforeCreate')
    // 代码分析点五:注入组件状态相关的属性
    initInjections(vm) // 注入祖辈传下来的数据
    initState(vm) // 组件数据初始化,包括了props/methods/data/computed/watch
    initProvide(vm) // 给后代传递数据
    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)
    }

    // 代码分析点六:执行$mount
    if (vm.$options.el) {
   
        vm.$mount(vm.$options.el)
    }
}

代码分析点一:合并选项

// 合并选项: 默认选项和用户选项
if (options && options._isComponent) {
   
    // 1.1 若是Vue组件,即VueComponent实例
    initInternalComponent(vm, options)
} else {
   
    // 1.2 Vue实例
    vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor), 
        options || {
   }, 
        vm
    )
}
// 参数分析start
// 参数1:options --> 执行new Vue时,传入构造函数的对象
options = {
   
    beforeCreate: ƒ beforeCreate(),
    data:{
   
        arr: ["arr_foo", "arr_bar"],
        obj: {
   foo: "obj_foo"},
		foo: "foo"
    },
	el: "#demo"
	methods: {
   
        onClick: ƒ onClick(),
		onCompClick: ƒ onCompClick()
    },
	mounted: ƒ mounted()
}

// 参数2:vm.constructor  ---> Vue的构造函数,可以访问Vue的类属性
// 如initGlobalAPI中定义的Vue.util/Vue.set/Vue.delete/Vue.nextTick/Vue.observable/Vue.options

// 函数3:resolveConstructorOptions(vm.constructor) --> 函数的作用是将Ctor中的options和其父类的options合并并返回
// 由于这里是根组件,没有父类,因此直接返回Vue.options
export function resolveConstructorOptions (Ctor: Class<Component>) {
   
  let options = Ctor.options
  // 若Ctor是Vue.extend构建的子类,则存在super属性
  if (Ctor.super) {
   
      //对Ctor的父类进行递归遍历,获取其父类-类属性options
    const superOptions = resolveConstructorOptions(Ctor.super)
    // 也可以通过Ctor来获取父类的options
    const cachedSuperOptions = Ctor.superOptions
    // 若不相等,则说明父类中的options发生了改变
    if (superOptions !== cachedSuperOptions) {
   
      // 重新挂载父类的options
      Ctor.superOptions = superOptions
      // 检查是否有后期修改或附加的option
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // 更新子类的options
      if (modifiedOptions) {
   
        extend(Ctor.extendOptions, modifiedOptions)
      }
      // 父类和子类的options进行合并
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
   
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}
// 函数3执行结果如下:
{
   
    components: {
   
        KeepAlive: {
   name: "keep-alive", abstract: true, props: {
   }, created: ƒ, destroyed: ƒ,},
        Transition: {
   name: "transition", props: {
   }, abstract: true, render: ƒ},
        TransitionGroup: {
   props: {
   }, methods: {
   }, beforeMount: ƒ, render: ƒ, updated: ƒ},
        comp: ƒ VueComponent(options)
    },
	directives: {
   
        model: {
   inserted: ƒ, componentUpdated: ƒ},
		show: {
   bind: ƒ, update: ƒ, unbind: ƒ}
    },
    filters: {
   },
    _base: ƒ Vue(options)
}
// 函数4:mergeOptions:参数1是Vue类属性中的options,命名为parent,参数2是执行Vue构造函数传入的options,命名为child,参数3是Vue实例
export function mergeOptions (parent: Object,child: Object,vm?: Component): Object {
   
    if (process.env.NODE_ENV !== 'production') {
   
        checkComponents(child)
    }

    if (typeof child === 'function') {
   
        child = child.options
    }
    
    // 由于child传入的props、inject、directives可以是数组或对象,因此对其格式化处理,统一转换为对象
    // 举例:props:['foo'] 或 props:{ foo: {type:String,required: true}}
    normalizeProps(child, vm)
    normalizeInject(child, vm)
    normalizeDirectives(child)

    // 将child中的extends和mixins合并到parent中去
    if (!child._base) {
   
        if (child.extends) {
   
            parent = mergeOptions(parent, child.extends, vm)
        }
        if (child.mixins) {
   
            for (let i = 0, l = child.mixins.length; i < l; i++) {
   
                parent = mergeOptions(parent, child.mixins[i], vm)
            }
        }
    }
	
    // 将parent和child进行合并,对于不同的key,其合并策略不同,有的是替换有的是组合
    const options = {
   }
    let key
    for (key in parent) {
   
        mergeField(key)
    }
    for (key in child) {
   
        if (!hasOwn(parent, key)) {
   
            mergeField(key)
        }
    }
    function mergeField (key) {
   
        const strat = strats[key] || defaultStrat
        options[key] = strat(parent[key], child[key], vm, key)
    }
    return options
}

// 举例:钩子函数合并策略:将parentVal和childVal合并成数组并返回
function mergeHook (parentVal: ?Array<Function>,childVal: ?Function | ?Array<Function>): ?Array<Function> {
   
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal)
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res ? dedupeHooks(res) : res
}

export const LIFECYCLE_HOOKS = [
  'beforeCreate',
  'created',
  'beforeMount',
  'mounted',
  'beforeUpdate',
  'updated',
  'beforeDestroy',
  'destroyed',
  'activated',
  'deactivated',
  'errorCaptured'
];

LIFECYCLE_HOOKS.forEach(function (hook) {
   
  strats[hook] = mergeHook;
})

// 举例:components/directives/filters合并策略:返回{...childVal,__proto__: parentVal}
function mergeAssets (
  parentVal: ?Object,
  childVal: ?Object,
  vm?:</
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值