Core/Util主体结构
- next-tick:
Vue.nextTick的实现
- props:用于合法化prop,并加入observe
- options:Component构造函数中输入的options所需的相关处理
- lang:一些通用的检测与类型转换的封装
- error:针对
ViewModel的错误信息的输出
- perm:Performance API的封装
- env:运行环境的检测
- debug:警告信息与错误栈的生成与输出,方便调试时排错
1. util/next-tick.js
Vue.nextTick()的实现,其优先级为:
Promise:micro
setImmediate:macro
MessageChannel:macro
setTimeout:macro
建模
对于nextTick()方法,它需要以下变量来建模:
- callbacks:回调队列,记录下一个tick要执行的所有回调
- pending:指示是否正在运行上一个tick的回调
- microTimerFunc:microtask的实现方式
- macroTimerFunc:macrotask的实现方式
- useMacroTask:指示是否使用macrotask
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| const callbacks = []
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 microTimerFunc let macroTimerFunc
let useMacroTask = false
|
macroTimerFunc
决定使用MacroTask来执行的方式,优先级为:setImmediate > MessageChannel > setTimeout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
if(typeof setImmediate !== 'undefined' && isNative(setImmediate)) { macroTimerFunc = () => { setImmediate(flushCallbacks) } }
else if(typeof MessageChannel !== 'undefined' && ( isNative(MessageChannel) || MessageChannel.toString() === '[object MessageChannelConstructor]' )) { const channel = new MessageChannel() const port = channel.port2 channel.port1.onmessage = flushCallbacks macroTimerFunc = () => { port.postMessage(1) } }
else { macroTimerFunc = () => { setTimeout(flushCallbacks, 0) } }
|
microTimerFunc
决定使用MicroTask执行的方式,首选Promise,不支持则转macroTimerFunc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
if(typeof Promise !== 'undefined' && isNative(Promise)) { const p = Promise.resolve() microTimerFunc = () => { p.then(flushCallbacks) if(isIOS) setTimeout(()=>{}, 0) } } else { microTimerFunc = macroTimerFunc }
|
核心:Vue.nextTick()实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
function nextTick(cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => { if(cb) { try { cb.call(ctx) } catech(e) { handlerError(e, ctx, 'nextTick') } } else if(_resolve) { _resolve(ctx) } }) if(!pending) { pending = true if(useMacroTask) { macroTimerFunc() } else { microTimerFunc() } } if(!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }
|
其他的helper函数
1 2 3 4 5 6 7 8 9
| function withMacroTask(fn: Function) { return fn._withTask || (fn._withTask = function() { useMacroTask = true const res = fn.apply(null, arguments) useMacroTask = false return res }) }
|
2. util/lang.js
提供一些通用方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| function isReserved(str: String): boolean { const c = (str + '').charCodeAt(0) return c === 0x24 || c ===0x5F }
function def(obj: Object, key: string, val: any, enumerable: boolean = false) { Object.defineProperty(obj, key, { value: val, enumerable, writable: true, configurable: true }) }
const bailRE = /[^\w.$]/ export function parsePath(path: string): any { if(bailRE.test(path)) return const segments = path.split('.') return function(obj) { for(let i = 0; i < segments.length; i++) { if(!obj) return obj = obj[segments[i]] } return obj } }
|
3. util/error.js
针对ViewModel的错误处理器,会输出错误栈以便进行追踪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| function logError(err, vm, info) { if(process.env.NODE_ENV !== 'production') { warn(`Error in ${info}: "${err.toString()}"`, vm) } if((inBrowser || inWeex) && typeof console !== 'undefined') { console.log(err) } else { throw err } }
function globalHandleError(err, vm, info) { if(config.errorHandler) { try { return config.errorHandler.call(null, err, vm, info) } catch(e) { logError(e, null, 'config.errorHandler') } } logError(err, vm, info) }
function handleError(err: Error, vm: any, info: string) { if(vm) { let cur = vm while(cur = cur.$parent) { const hooks = cur.$options.errorCaptured if(hooks) { for(let i = 0; i < hooks.length; i++) { try { const capture = hooks[i].call(cur, err, vm, info) === false if(capture) return } catch(e) { globalHandleError(e, cur, 'errorCaptured hook') } } } } } globalHandleError(err, vm, info) }
|
4. util/perm.js
Performance API相关的操作,用来记录操作发生的时间戳和计算某些操作所用的时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| export let mark export let measure
if (process.env.NODE_ENV !== 'production') { const perf = inBrowser && window.performance if ( perf && perf.mark && perf.measure && perf.clearMarks && perf.clearMeasures ) { mark = tag => perf.mark(tag) measure = (name, startTag, endTag) => { perf.measure(name, startTag, endTag) perf.clearMarks(startTag) perf.clearMarks(endTag) perf.clearMeasures(name) } } }
|
5. util/env.js
env用于检测相关的系统环境。对于环境和设备类型的检测非常重要,可以根据其差异,来优化用户体验。
它包括了对以下环境变量和运行特性的检测:
__proto__
- 运行环境:浏览器及其类型,Weex
- 被动的
eventListener,即不能preventDefault()
- 是否为SSR
Vue Devtools插件
- Symbol
- Set
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| export const hasProto = '__proto__' in {}
export const inBrowser = typeof window !== 'undefined' export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase() export const UA = inBrowser && window.navigator.userAgent.toLowerCase() export const isIE = UA && /msie|trident/.test(UA) export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 export const isEdge = UA && UA.indexOf('edge/') > 0 export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android') export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios') export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge
export const nativeWatch = ({}).watch
export let supportsPassive = false if (inBrowser) { try { const opts = {} Object.defineProperty(opts, 'passive', ({ get () { supportsPassive = true } }: Object)) window.addEventListener('test-passive', null, opts) } catch (e) {} }
let _isServer export const isServerRendering = () => { if (_isServer === undefined) { if (!inBrowser && !inWeex && typeof global !== 'undefined') { _isServer = global['process'].env.VUE_ENV === 'server' } else { _isServer = false } } return _isServer }
export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__
export function isNative (Ctor: any): boolean { return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) }
export const hasSymbol = typeof Symbol !== 'undefined' && isNative(Symbol) && typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys)
let _Set if (typeof Set !== 'undefined' && isNative(Set)) { _Set = Set } else { _Set = class Set implements SimpleSet { set: Object; constructor () { this.set = Object.create(null) } has (key: string | number) { return this.set[key] === true } add (key: string | number) { this.set[key] = true } clear () { this.set = Object.create(null) } } }
|
6. util/debug.js
debug模式下的一些信息输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| export let warn export let tip export let generateComponentTrace export let formatComponentName
if(process.env.NODE_ENV !== 'production') { const hasConsole = typeof console !== 'undefined' const classifyRE = /(?:^|[-_])(\w)/g const classify = str => str .replace(classifyRE, c => c.toUpperCase()) .replace(/[-_]/g, '') warn = (msg, vm) => { const trace = vm ? generateComponentTrace(vm) : '' if (config.warnHandler) { config.warnHandler.call(null, msg, vm, trace) } else if (hasConsole && (!config.silent)) { console.error(`[Vue warn]: ${msg}${trace}`) } } tip = (msg, vm) => { if (hasConsole && (!config.silent)) { console.warn(`[Vue tip]: ${msg}` + (vm ? generateComponentTrace(vm) : '')) } } formatComponentName = (vm, includeFile) => { if (vm.$root === vm) { return '<Root>' } const options = typeof vm === 'function' && vm.cid != null ? vm.options : vm._isVue ? vm.$options || vm.constructor.options : vm || {} let name = options.name || options._componentTag const file = options.__file if (!name && file) { const match = file.match(/([^/\\]+)\.vue$/) name = match && match[1] }
return ( (name ? `<${classify(name)}>` : `<Anonymous>`) + (file && includeFile !== false ? ` at ${file}` : '') ) } const repeat = (str, n) => { let res = '' while (n) { if (n % 2 === 1) res += str if (n > 1) str += str n >>= 1 } return res } generateComponentTrace = vm => { if (vm._isVue && vm.$parent) { const tree = [] let currentRecursiveSequence = 0 while (vm) { if (tree.length > 0) { const last = tree[tree.length - 1] if (last.constructor === vm.constructor) { currentRecursiveSequence++ vm = vm.$parent continue } else if (currentRecursiveSequence > 0) { tree[tree.length - 1] = [last, currentRecursiveSequence] currentRecursiveSequence = 0 } } tree.push(vm) vm = vm.$parent } return '\n\nfound in\n\n' + tree .map((vm, i) => `${i === 0 ? '---> ' : repeat(' ', 5 + i * 2)} ${Array.isArray(vm) ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)` : formatComponentName(vm) }`, ) .join('\n') } else { return `\n\n(found in ${formatComponentName(vm)})` } } }
|