1、内容及模式
- 1.介绍目录构造、找到外围入口
- 2.介绍全局api
- 3.以发问互动形式浏览外围源码
2、目录构造
- .circleci 继续集成
- benchmarks 性能评测
- dist 输入目录
- examples 案例
- flow flow申明文件
- packages vue中的包
- scripts 工程化
- src 源码目录
- test 测试相干
- types ts申明文件
3、外围源码目录
├─compiler # 编译的相干逻辑│ ├─codegen│ ├─directives│ └─parser├─core # vue外围代码│ ├─components # vue中的内置组件 keep-alive│ ├─global-api # vue中的全局api│ ├─instance # vue中的外围逻辑│ ├─observer # vue中的响应式原理│ ├─util │ └─vdom # vue中的虚构dom模块├─platforms # 平台代码│ ├─web # web逻辑 - vue│ │ ├─compiler│ │ ├─runtime│ │ ├─server│ │ └─util│ └─weex # weex逻辑 - app│ ├─compiler│ ├─runtime│ └─util├─server # 服务端渲染模块├─sfc # 用于编译.vue文件└─shared # 共享的办法和常量
4、打包流程及入口剖析
1.package.json
"build": "node scripts/build.js","build:ssr": "npm run build -- web-runtime-cjs,web-server-renderer","build:weex": "npm run build -- weex",
2.scripts/build.js
// 1.获取不同的打包的配置 let builds = require('./config').getAllBuilds()// 2.进行打包build(builds)
3.scripts/config.js
exports.getAllBuilds = () => Object.keys(builds).map(genConfig)//找到打包入口src/platforms/web/entry-runtime.jssrc/platforms/web/entry-runtime-with-compiler.js
4.外围代码、全局API initGlobalAPI()
- 1.import Vue from './runtime/index'
- 2.import Vue from 'core/index'
- 3.import Vue from './instance/index'
//global-api/index.js Vue.set Vue.delete Vue.nextTick initUse(Vue) Vue.use initMixin(Vue) Vue.mixin initExtend(Vue) Vue.extend
5.Vue.set / Vue.delete
export function set (target: Array<any> | Object, key: any, val: any): any { //1.数组 应用splice触发视图更新 vue.set(arr,0,99) if (Array.isArray(target) && isValidArrayIndex(key)) { target.length = M .max(target.length, key) target.splice(key, 1, val) return val } //2.对象 是对象自身的属性,间接增加即可 if (key in target && !(key in Object.prototype)) { target[key] = val return val } const ob = (target: any).__ob__ //3.不是响应式的 不须要将其定义成响应式属性 if (!ob) { target[key] = val return val } // 4.将属性定义成响应式的 defineReactive(ob.value, key, val) ob.dep.notify() return val}export function del (target: Array<any> | Object, key: any) { // 1.数组 仍旧调用splice办法 if (Array.isArray(target) && isValidArrayIndex(key)) { target.splice(key, 1) return } const ob = (target: any).__ob__ // 2.自身没有这个属性 if (!hasOwn(target, key)) { return } // 3.删除这个属性 delete target[key] if (!ob) { return } // 4.告诉更新 ob.dep.notify()}
对象只会拦挡曾经存在的属性 更改数组索引也不会引发视图更新
6.Vue.nextTick
const callbacks = []; // 寄存nextTick回调let pending = false; function flushCallbacks () { // 清空队列 pending = false const copies = callbacks.slice(0) callbacks.length = 0 for (let i = 0; i < copies.length; i++) { copies[i]() }}let timerFuncexport function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if (cb) { try { cb.call(ctx) // 1.将回调函数存入到callbacks中 } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) { pending = true timerFunc(); // 2.异步刷新队列 } // 3.反对promise写法 if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) }}
将回调函数存入到一个队列中,最初异步的清空这个队列
timerFunc
// 1.默认先应用Promise 因为mutationObserver有bug可能不工作if (typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() timerFunc = () => { p.then(flushCallbacks) // 解决队列不刷新问题 if (isIOS) setTimeout(noop) } isUsingMicroTask = true// 2.应用MutationObserver} else if (!isIE && typeof MutationObserver !== 'undefined' && ( isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]')) { 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// 3.应用 setImmediate} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { timerFunc = () => { setImmediate(flushCallbacks) }// 4.应用setTimeout} else { timerFunc = () => { setTimeout(flushCallbacks, 0) }}
EventLoop 微工作和宏工作,先微工作优先级 降级解决 promise => MutationObserver => setImmediate => setTimeout
5.初始化过程
initMixin(Vue) //_initstateMixin(Vue) //$set $delete $watcheventsMixin(Vue) // $on $emit $once $off lifecycleMixin(Vue) //_updaterenderMixin(Vue) //_render//初始化 init.jsinitLifecycle(vm) // 初始化组件间的父子关系 initEvents(vm) // 更新组件的事件initRender(vm) //初始化_c办法创立虚构节点initInjections(vm) // resolve injections before data/props // 初始化injectinitState(vm) // 初始化状态initProvide(vm) // resolve provide after data/props / 初始化provide// 1.如果有el就开始挂载 if (vm.$options.el) { vm.$mount(vm.$options.el)}// 2.组件的挂载Vue.prototype.$mount = function (el,hydrating){ el = el && inBrowser ? query(el) : undefined return mountComponent(this, el, hydrating);}// 3.创立渲染watcher进行渲染export function mountComponent (vm,el,hydrating) { vm.$el = el let updateComponent updateComponent = () => { vm._update(vm._render(), hydrating) } new Watcher(vm, updateComponent, noop, { // 创立渲染Watcher before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate') } } }, true /* isRenderWatcher */) return vm}
6、数据劫持 + 发问?
1.this如何拜访data和methods?
// 初始化data数据function initData(vm) { let data = vm.$options.data; // 实例的_data属性就是传入的data // vue组件data举荐应用函数 避免数据在组件之间共享 data = vm._data = typeof data === "function" ? data.call(vm) : data || {}; // 把data数据代理到vm 也就是Vue实例下面 咱们能够应用this.a来拜访this._data.a for (let key in data) { proxy(vm, `_data`, key); }}// 数据代理function proxy(object, sourceKey, key) { Object.defineProperty(object, key, { get() { return object[sourceKey][key]; }, set(newValue) { object[sourceKey][key] = newValue; }, });}
通过 this 间接拜访到 data 外面的数据的起因是:data里的属性最终会存储到new Vue的实例(vm)上的 _data对象中,拜访 this.xxx,是拜访Object.defineProperty代理后的 this._data.xxx
// initMethodsfunction initMethods (vm, methods) { var props = vm.$options.props; for (var key in methods) { vm[key] = typeof methods[key] !== 'function' ? noop : bind(methods[key], vm); }}function nativeBind (fn, ctx) { return fn.bind(ctx)}var bind = Function.prototype.bind ? nativeBind : polyfillBind
通过this间接拜访到methods外面的函数的起因是:因为methods里的办法通过 bind 指定了this为 new Vue的实例(vm)
2.数组的响应式?
1.因为对数组下标的拦挡太节约性能 对 Observer 构造函数传入的数据参数减少了数组的判断
// src/obserber/index.jsimport { arrayMethods } from "./array";class Observer {constructor(value) { if (Array.isArray(value)) { // 这里对数组做了额定判断 // 通过重写数组原型办法来对数组的七种办法进行拦挡 value.__proto__ = arrayMethods; // 如果数组外面还蕴含数组 须要递归判断 this.observeArray(value); } else { this.walk(value); }}observeArray(items) { for (let i = 0; i < items.length; i++) { observe(items[i]); }}}
2.每个响应式数据减少了一个不可枚举的__ob__属性 并且指向了 Observer 实例
- 1.能够依据这个属性来避免曾经被响应式察看的数据重复被观测
- 2.响应式数据能够应用__ob__来获取 Observer 实例的相干办法
// src/obserber/index.jsclass Observer { // 观测值 给每个value增加__ob__属性 constructor(value) { Object.defineProperty(value, "__ob__", { // 值指代的就是Observer的实例 value: this, // 不可枚举 enumerable: false, writable: true, configurable: true, }); }}
3.对数组原型重写
// src/obserber/array.js// 先保留数组原型const arrayProto = Array.prototype;// 而后将arrayMethods继承自数组原型// 这里是面向切片编程思维(AOP)--不毁坏封装的前提下,动静的扩大性能export const arrayMethods = Object.create(arrayProto);// 为什么重写七个办法? 会扭转原数组// 其它办法如何?join concat map filter forEach reduce every somesome flat slice// value.__proto__ => arrayMethods => arrayMethods.__proto__ => Array.prototypelet methodsToPatch = ["push","pop","shift","unshift","splice","reverse","sort",];methodsToPatch.forEach((method) => {arrayMethods[method] = function (...args) { // 这里保留原型办法的执行后果 const result = arrayProto[method].apply(this, args); // 这句话是要害 // this就是被检测的数据,比方数据是{msg:[1,2,3]},msg.push(4) this就是msg ob就是msg.__ob__ (上段代码减少,该数据曾经被响应式察看)所以上面就能够应用ob.observeArray const ob = this.__ob__; // 这里的标记就是代表数组有新增操作 let inserted; switch (method) { case "push": case "unshift": inserted = args; break; case "splice": inserted = args.slice(2); default: break; } // 如果有新增的元素 inserted是一个数组 调用Observer实例的observeArray对数组每一项进行观测 if (inserted) ob.observeArray(inserted); // 之后咱们还能够在这里检测到数组扭转了之后从而触发视图更新的操作--后续源码会揭晓 return result;};});
7、简易版Vue
https://github.com/minxiang51...
8、总结
源码并非遥不可及,从源码中学习思维,千里之行始于足下!