vue.js源码分析-响应式
源码获取:https://github.com/vuejs/vue
源码获取: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的不同构建版本
- npm run build 重新打包所有文件
- 官方文档 - 对不同构建版本的解释
- dist\README.md
术语
- 完整版:同时包含编译器和运行时的版本。
- 编译器: 用来将模板字符串编译成为 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
})
}
}
更多推荐
所有评论(0)