源码获取:https://github.com/vuejs/vue

一、了解Flow

  • 官网: https://flow.org/
  • JavaScript 的静态类型检查器
  • Flow 的静态类型检查错误是通过静态类型推断实现的
  • 文件开头通过 // @flow 或者 /* @flow */ 声明
/* @flow */ 
function square(n: number): number { 
  return n * n;
}
square("2"); // Error!

二、调试设置

  • 打包工具 Rollup

    。Vue.js 源码的打包工具使用的是 Rollup,比 Webpack 轻量
    。Webpack 把所有文件当做模块,Rollup 只处理 js 文件更适合在 Vue.js 这样的库中使用
    。Rollup 打包不会生成冗余的代码

  • 安装依赖

npm i
  • 设置 sourcemap

    。package.json 文件中的 dev 脚本中添加参数 --sourcemap

  • 执行 dev

    。npm run dev 执行打包,用的是 rollup,-w 参数是监听文件的变化,文件变化自动重新打包

三、调试

  • examples 的示例中引入的 vue.min.js 改为 vue.js
  • 打开 Chrome 的调试工具中的 source

四、Vue 的不同构建版本

五、Vue的不同构建版本

在这里插入图片描述
术语

  • 完整版:同时包含编译器运行时的版本。
  • 编译器: 用来将模板字符串编译成为 JavaScript 渲染函数的代码,体积大、效率低。
  • 运行时: 用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码,体积小、效率高。基本上就是除去编译器的代码。
  • UMD:UMD 版本通用的模块版本,支持多种模块方式。 vue.js 默认文件就是运行时 + 编译器的UMD 版本
  • CommonJS(cjs):CommonJS 版本用来配合老的打包工具比如 Browserify 或 webpack 1。
  • ES Module:从 2.6 开始 Vue 会提供两个 ES Modules (ESM) 构建文件,为现代打包工具提供的版本。
    。ESM 格式被设计为可以被静态分析,所以打包工具可以利用这一点来进行“tree-shaking”并将用不到的代码排除出最终的包。
    ES6 模块与 CommonJS 模块的差异

Runtime + Compiler vs. Runtime-only

//	 Compiler 
	// 需要编译器,把 template 转换成 render 函数 
	// const vm = new Vue({ 
	// el: '#app', 
	// template: '<h1>{{ msg }}</h1>', 
	// data: { 
	// msg: 'Hello Vue' 
	// } 
	// }) 
	// Runtime 
	// 不需要编译器 
	const vm = new Vue({
		el: '#app',
		render(h) {
			return h('h1', this.msg)
		},
		data: {
			msg: 'Hello Vue'
		}
	})
  • 推荐使用运行时版本,因为运行时版本相比完整版体积要小大约 30%
  • 基于 Vue-CLI 创建的项目默认使用的是vue.runtime.esm.js
    。通过查看 webpack 的配置文件
vue inspect > output.js // > 符号的作用是把前面的结果输出到output.js中来
  • 注意: *.vue 文件中的模板是在构建时预编译的,最终打包后的结果不需要编译器,只需要运行时版本即可

六、寻找入口文件

  • 查看 dist/vue.js 的构建过程

执行构建

npm run dev 
# "dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:web-full-dev" 
# --environment TARGET:web-full-dev 设置环境变量 TARGET
  • script/config.js 的执行过程
    。作用:生成 rollup 构建的配置文件
    。使用环境变量 TARGET = web-full-dev
// 判断环境变量是否有 TARGET 
	// 如果有的话 使用 genConfig() 生成 rollup 配置文件 
	if(process.env.TARGET) {
		module.exports = genConfig(process.env.TARGET)
	} else {
		// 否则获取全部配置 
		exports.getBuild = genConfig exports.getAllBuilds = () => Object.keys(builds).map(genConfig)
	}
  • genConfig(name)
    。根据环境变量 TARGET 获取配置信息
    。builds[name] 获取生成配置的信息
// Runtime+compiler development build (Browser) 
	'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()
    。获取入口和出口文件的绝对路径
const aliases = require('./alias') const resolve = p => {
		// 根据路径中的前半部分去alias中找别名 
		const base = p.split('/')[0]
		if(aliases[base]) {
			return path.resolve(aliases[base], p.slice(base.length + 1))
		} else {
			return path.resolve(__dirname, '../', p)
		}
	}

结果

  • 把 src/platforms/web/entry-runtime-with-compiler.js 构建成 dist/vue.js,如果设置 --sourcemap 会生成 vue.js.map
  • src/platform 文件夹下是 Vue 可以构建成不同平台下使用的库,目前有 weex 和 web,还有服务器端渲染的库

七、从入口开始

  • src/platform/web/entry-runtime-with-compiler.js

通过查看源码解决下面问题

  • 观察以下代码,通过阅读源码,回答在页面上输出的结果
const vm = new Vue({
		el: '#app',
		template: '<h3>Hello template</h3>',
		render(h) {
			return h('h4', 'Hello render')
		}
	})
  • 阅读源码记录
    。el 不能是 body 或者 html 标签
    。如果没有 render,把 template 转换成 render 函数
    。如果有 render 方法,直接调用 mount 挂载 DOM
// 1. el 不能是 body 或者 html 
	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
	if(!options.render) {
		// 2. 把 template/el 转换成 render 函数 
		……}
	// 3. 调用 mount 方法,挂载 DOM 
	return mount.call(this, el, hydrating)

八、四个导出 Vue 的模块

  • src/platforms/web/entry-runtime-with-compiler.js
    。web 平台相关的入口
    。重写了平台相关的 $mount() 方法
    。注册了 Vue.compile() 方法,传递一个 HTML 字符串返回 render 函数

  • src/platforms/web/runtime/index.js
    。web 平台相关
    。注册和平台相关的全局指令:v-model、v-show
    。注册和平台相关的全局组件: v-transition、v-transition-group
    。全局方法:
    (1)patch:把虚拟 DOM 转换成真实 DOM
    (2)$mount:挂载方法

  • src/core/index.js
    。与平台无关
    。设置了 Vue 的静态方法,initGlobalAPI(Vue)

  • src/core/instance/index.js
    。与平台无关
    。定义了构造函数,调用了 this._init(options) 方法
    。给 Vue 中混入了常用的实例成员

九、动态添加一个响应式属性

methods: {
	handler() {
		this.obj.count = 555 
		this.arr[0] = 1 
		this.arr.length = 0 
		this.arr.push(4)
	}
}

转换成响应式数据

methods: {
	handler() {
		this.$set(this.obj, 'count', 555) 
		this.$set(this.arr, 0, 1) 
		this.arr.splice(0)
	}
}

十、set-源码

export function set(target: Array < any > | Object, key: any, val: any): any {
	if(process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target))) {
		warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
	}
	// 判断 target 是否是对象,key 是否是合法的索引 
	if(Array.isArray(target) && isValidArrayIndex(key)) {
		target.length = Math.max(target.length, key)
		// 通过 splice 对key位置的元素进行替换 
		// splice 在 array.js进行了响应化的处理
		target.splice(key, 1, val) return val
	}
	// 如果 key 在对象中已经存在直接赋值 
	export function set(target: Array < any > | Object, key: any, val: any): any {
		if(process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target))) {
			warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
		}
		// 判断 target 是否是对象,key 是否是合法的索引 
		if(Array.isArray(target) && isValidArrayIndex(key)) {
			target.length = Math.max(target.length, key)
			// 通过 splice 对key位置的元素进行替换 
			// splice 在 array.js进行了响应化的处理 
			target.splice(key, 1, val) return val
		}
		// 如果 key 在对象中已经存在直接赋值 
		if(key in target && !(key in Object.prototype)) {
			target[key] = val
			return val
		}
		// 获取 target 中的 observer 对象 const ob = (target: any).__ob__ 
		// 如果 target 是 vue 实例或者$data 直接返回
		if(target._isVue || (ob && ob.vmCount)) {
			process.env.NODE_ENV !== 'production' && warn('Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.') return val
		} // 如果 ob 不存在,target 不是响应式对象直接赋值 
		if(!ob) {
			target[key] = val
			return val
		}
		// 把 key 设置为响应式属性 defineReactive(ob.value, key, val) 
		// 发送通知 
		ob.dep.notify() return val
	}
	if(key in target && !(key in Object.prototype)) {
		target[key] = val
		return val
	} // 获取 target 中的 observer 对象 const ob = (target: any).__ob__ // 如果 target 是 vue 实例或者$data 直接返回
	if(target._isVue || (ob && ob.vmCount)) {
		process.env.NODE_ENV !== 'production' && warn('Avoid adding reactive properties to a Vue instance or its root $data ' + 'at runtime - declare it upfront in the data option.')
		return val
	}
	// 如果 ob 不存在,target 不是响应式对象直接赋值 
	if(!ob) {
		target[key] = val
		return val
	}
	// 把 key 设置为响应式属性 
	defineReactive(ob.value, key, val)
	// 发送通知 
	ob.dep.notify() 
	return val
}

十一、delete

vm.$delete(vm.obj, 'msg')
export function del(target: Array < any > | Object, key: any) {
	if(process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target))) {
		warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
	}

	// 判断是否是数组,以及 key 是否合法 
	if(Array.isArray(target) && isValidArrayIndex(key)) {
		// 如果是数组通过 splice 删除 // splice 做过响应式处理 
		target.splice(key, 1) return
	}
	// 获取 target 的 ob 对象 
	const ob = (target: any).__ob__
	// target 如果是 Vue 实例或者 $data 对象,直接返回 
	if(target._isVue || (ob && ob.vmCount)) {
		process.env.NODE_ENV !== 'production' && warn('Avoid deleting properties on a Vue instance or its root $data ' + '- just set it to null.') return
	}
	// 如果 target 对象没有 key 属性直接返回
	if(!hasOwn(target, key)) {
		return
	}
	// 删除属性 
	delete target[key]
	if(!ob) {
		return
	}
	// 通过 ob 发送通知 
	ob.dep.notify()
}

十二、watch

const vm = new Vue({
	el: '#app',
	data: {
		a: '1',
		b: '2',
		msg: 'Hello Vue',
		user: {
			firstName: '诸葛',
			lastName: '亮'
		}
	}
})
// expOrFn 是表达式 
vm.$watch('msg', function(newVal, oldVal) {
	console.log(newVal, oldVal)
}) vm.$watch('user.firstName', function(newVal, oldVal) {
	console.log(newVal)
})
// expOrFn 是函数 
vm.$watch(function() {
	return this.a + this.b
}, function(newVal, oldVal) {
	console.log(newVal)
})
// deep 是 true,消耗性能
vm.$watch('user', function(newVal, oldVal) {
	// 此时的 newVal 是 user 对象 
	console.log(newVal === vm.user)
}, {
	deep: true
})
// immediate 是 true 
vm.$watch('msg', function(newVal, oldVal) {
	console.log(newVal)
}, {
	immediate: true
})

十三、nextTick

let timerFunc
// The nextTick behavior leverages the microtask queue, which can be accessed 
// via either native Promise.then or MutationObserver. 
// MutationObserver has wider support, however it is seriously bugged in 
// UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It 
// completely stops working after triggering a few times... so, if native 
// Promise is available, we will use it: /* istanbul ignore next, $flow-disable-line */ 
if(typeof Promise !== 'undefined' && isNative(Promise)) {
	const p = Promise.resolve() timerFunc = () => {
		p.then(flushCallbacks)
		// In problematic UIWebViews, Promise.then doesn't completely break, but 
		// it can get stuck in a weird state where callbacks are pushed into the 
		// microtask queue but the queue isn't being flushed, until the browser
		// needs to do some other work, e.g. handle a timer. Therefore we can 
		// "force" the microtask queue to be flushed by adding an empty timer. 
		if(isIOS) setTimeout(noop)
	}
	isUsingMicroTask = true
} else if(!isIE && typeof MutationObserver !== 'undefined' && (isNative(MutationObserver) ||

		// PhantomJS and iOS 7.x 
		MutationObserver.toString() === '[object MutationObserverConstructor]')) {
	// Use MutationObserver where native Promise is not available, 
	// e.g. PhantomJS, iOS7, Android 4.4 
	// (#6466 MutationObserver is unreliable in IE11)
	let counter = 1
	const observer = new MutationObserver(flushCallbacks) 
	const textNode = document.createTextNode(String(counter)) observer.observe(textNode, {
		characterData: true
	})
	 timerFunc = () => {
		counter = (counter + 1) % 2 textNode.data = String(counter)
	}
	isUsingMicroTask = true
} else if(typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
	// Fallback to setImmediate. 
	// Technically it leverages the (macro) task queue, 
	// but it is still a better choice than setTimeout. 
	timerFunc = () => {
		setImmediate(flushCallbacks)
	}
} else {
	// Fallback to setTimeout. 
	timerFunc = () => {
		setTimeout(flushCallbacks, 0)
	}
}
export function nextTick(cb ? : Function, ctx ? : Object) {
	let _resolve
	// 把 cb 加上异常处理存入 callbacks 数组中 
	callbacks.push(() => {
		if(cb) {
			try {
				// 调用 cb() 
				cb.call(ctx)
			} catch(e) {
				handleError(e, ctx, 'nextTick')
			}
		} else if(_resolve) {
			_resolve(ctx)
		}
	})
	 if(!pending) {
		pending = true timerFunc()
	}
	// $flow-disable-line 
	if(!cb && typeof Promise !== 'undefined') {
		// 返回 promise 对象 
		return new Promise(resolve => {
			_resolve = resolve
		})
	}
}
Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐