1.准备
1.1安装
-
安装rollup
npm i -g rollup
-
拉取源码
git clone https://github.com/vuejs/vue.git
-
修改启动文件package.json,添加 --sourcemap,生成新的dist
"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:webfull-dev",
-
引入,dist/vue.js调试
<script src="../../dist/vue.js"></script>
-
通过chrome,debugger 时候,通过右键 revael in sidebar 在左侧调试框显示对应树型结构。
-
安装对应插件
- Flow Language Support
- vscode 设置settings.json 新增下面配置,关闭js类型校验
"javascript.validate.enable": false
Babel JavaScript
插件 使得关键字支持高亮效果
1.2 包版本
-
完整版:同时包含编译器和运行时的版本。
-
编译器:用来将模板字符串编译成为 JavaScript 渲染函数的代码。
-
运行时:用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切。
注意:运行时 runtime.js 是不能做<template>解析渲染
-
UMD:UMD 版本可以通过 <script> 标签直接用在浏览器中。vue.umd.js 等价于 vue.js,是完整版,包含所有的编译器+运行时。适合异步加载
-
CommonJS:CommonJS 版本用来配合老的打包工具比如 Browserify 或 webpack 1。适合node.js 服务端渲染,可以使用require("")引用的NODEJS格式
-
ES Module:(支持import from 最新标准的)从 2.6 开始 Vue 会提供两个 ES Modules (ESM) 构建文件:
1. 为打包工具提供的 ESM:为诸如 webpack 2 或 Rollup 提供的现代打包工具。 2. 为浏览器提供的 ESM (2.6+):用于在现代浏览器中通过 <script type="module"> 直接导入。
-
runtime 运行时,不能做<template>解析渲染
2.分析
2.1源码结构
2.2 打包编译分析
2.2.1 package.json入口
"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
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加载流程
3.2 watcher的微队列异步刷新流程
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
}
}