vue2.0 源码-基础

409 阅读1分钟

1.准备

1.1安装

  1. 安装rollup

     npm i -g rollup
    
  2. 拉取源码

     git clone https://github.com/vuejs/vue.git
    
  3. 修改启动文件package.json,添加 --sourcemap,生成新的dist

     "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:webfull-dev",
    
  4. 引入,dist/vue.js调试

         <script src="../../dist/vue.js"></script>
    
  5. 通过chrome,debugger 时候,通过右键 revael in sidebar 在左侧调试框显示对应树型结构。

  6. 安装对应插件

  • Flow Language Support
  • vscode 设置settings.json 新增下面配置,关闭js类型校验
"javascript.validate.enable": false
  • Babel JavaScript插件 使得关键字支持高亮效果

1.2 包版本

  • 完整版:同时包含编译器和运行时的版本。

  • 编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。

  • 运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。

      注意:运行时 runtime.js 是不能做<template>解析渲染
      
    

image.png

  1. UMD:UMD 版本可以通过 <script> 标签直接用在浏览器中。vue.umd.js 等价于 vue.js,是完整版,包含所有的编译器+运行时。适合异步加载

  2. CommonJS:CommonJS 版本用来配合老的打包工具比如 Browserify 或 webpack 1。适合node.js 服务端渲染,可以使用require("")引用的NODEJS格式

  3. ES Module:(支持import from 最新标准的)从 2.6 开始 Vue 会提供两个 ES Modules (ESM) 构建文件:

     1. 为打包工具提供的 ESM:为诸如 webpack 2 或 Rollup 提供的现代打包工具。
     2. 为浏览器提供的 ESM (2.6+):用于在现代浏览器中通过 <script type="module"> 直接导入。
    
     
    
  4. runtime 运行时,不能做<template>解析渲染

2.分析

2.1源码结构

image.png

image.png

2.2 打包编译分析

2.2.1 package.json入口

image.png "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev", 通过 TARGET:web-full-dev,找到文件scripts/config.js,匹配对应

'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
  },
  
 //入口 
  entry: resolve('web/entry-runtime-with-compiler.js'),
//resolve方法解析  
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)
  }
}
  
 //对应scripts/alias.js
const path = require('path')
const resolve = p => path.resolve(__dirname, '../', p)
module.exports = {
  vue: resolve('src/platforms/web/entry-runtime-with-compiler'),
  compiler: resolve('src/compiler'),
  core: resolve('src/core'),
  shared: resolve('src/shared'),
  web: resolve('src/platforms/web'),
  weex: resolve('src/platforms/weex'),
  server: resolve('src/server'),
  sfc: resolve('src/sfc')
}
//找到 web 对应的代码入口
web: resolve('src/platforms/web'),

总入口地址为:src/platforms/web/ + entry-runtime-with-compiler

2.2.2 从entry-runtime-with-compiler开始

分析原则:忽略打印,性能优化,调试代码。

//处理是否有template ,没有则取$el
import Vue from './runtime/index' //这里引入vue的实例

const mount = Vue.prototype.$mount //注意 这里为了切入新的逻辑,把原来的方法先缓存起来,再执行覆盖
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
    el = el && query(el)
    ...
    
  const options = this.$options
  // resolve template/el and convert to render function
  if (!options.render) { //判断是否没渲染函数
       let template = options.template
        if (template) { //再判断是否没有模板
        
        }else if (el) { //如果没有模板,有$el 则直接取$el的信息
          template = getOuterHTML(el)
        }
        if (template) { 如果模板不为空,模版生成render函数

      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render //设置render
      
  } 
  
  return mount.call(this, el, hydrating)//注意:这里把原来缓存的方法这里再执行,上面的内容都是切入的内容,最后再执行原来的挂载逻辑。

2.2.3 /src/platforms/web/runtime/index.js

实例化Vue对象

import { mountComponent } from 'core/instance/lifecycle'

// 这里就是diff算法,安装一个__patch__方法,__patch__ 加下划线表示内部方法,不想给外部使用
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)//挂载组件
}

Vue.prototype.__patch__ = inBrowser ? patch : noop

2.3 核心源码库 src/core/index.js

image.png

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)

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

2.3.1 src/core/instance/index.js

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) //这里混入了上面调用的_init 
stateMixin(Vue) // 状态相关api $data,$props,$set,$delete,$watch
eventsMixin(Vue)// 事件相关api $on,$once,$off,$emit
lifecycleMixin(Vue) // ⽣命周期api _update,$forceUpdate,$destroy
renderMixin(Vue)// 渲染api _render,$nextTick

export default Vue 

2.3.2 core/instance/init.js

创建组件实例,初始化其数据、属性、事件等


initLifecycle(vm) // $parent,$root,$children,$refs
initEvents(vm) // 处理⽗组件传递的事件和回调
initRender(vm) // $slots,$scopedSlots,_c,$createElement
callHook(vm, 'beforeCreate')
initInjections(vm) // 获取注⼊数据
initState(vm) // 初始化props,methods,data,computed,watch
initProvide(vm) // 提供数据注⼊
callHook(vm, 'created')

2.3.3 /core/instance/state.js

初始化数据,包括props、methods、data、computed和watch

//数据响应化
function initData (vm: Component) {
// 执⾏数据响应化
observe(data, true /* asRootData */)

}

3 Vue2 数据刷新

3.1 Vue的watchter加载流程

image.png

3.2 watcher的微队列异步刷新流程

image.png

4.虚拟dom

4.1 vue2和vue1区别

  • vue 1.0中有细粒度的数据变化侦测,它是不需要虚拟DOM的,但是细粒度造成了⼤量开销,这对于⼤型项⽬来说是不可接受的。
  • 因此,vue 2.0选择了中等粒度的解决⽅案,每⼀个组件⼀个watcher实例,
  • 这样状态变化时只能通知到组件,再通过引⼊虚拟DOM去进⾏⽐对和渲染。

4.2 定义

虚拟DOM(Virtual DOM)是对DOM的JS抽象表示,它们是JS对象,能够描述DOM结构和关系。应⽤ 的各种状态变化会作⽤于虚拟DOM,最终映射到DOM上。

4.3 snabbdom实现

<!DOCTYPE html>
<html lang="en">
<head></head>
<body>
<!--安装并引⼊snabbdom-->
<script src="../../node_modules/snabbdom/dist/snabbdom.js"></script>
<script>
// 之前编写的响应式函数
function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            return val
        },
        set(newVal) {
            val = newVal
            // 通知更新
            update()
        }
    })
}

// 导⼊patch的⼯⼚init,h是产⽣vnode的⼯⼚
const { init, h } = snabbdom
// 获取patch函数
const patch = init([])
// 上次vnode,由patch()返回
let vnode;
// 更新函数,将数据操作转换为dom操作,返回新vnode
function update() {
    if (!vnode) {
        // 初始化,没有上次vnode,传⼊宿主元素和vnode
        vnode = patch(app, render())
    } 
    else {
    // 更新,传⼊新旧vnode对⽐并做更新
    vnode = patch(vnode, render())
    }
}
// 渲染函数,返回vnode描述dom结构

function render() {
    return h('div', obj.foo)
}
// 数据
const obj = {}
// 定义响应式
defineReactive(obj, 'foo', '')
// 赋⼀个⽇期作为初始值
obj.foo = new Date().toLocaleTimeString()
// 定时改变数据,更新函数会重新执⾏
setInterval(() => {
    obj.foo = new Date().toLocaleTimeString()
}, 1000);
</script>
</body>
</html>

global-api

Vue.use


/* @flow */
//toArray把类数组转真正的数组
import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {//如果缓存已经有 就直接返回
      return this
    }

    // additional parameters
    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {//install  有可能是对象的方法 也有可能直接是function
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}