├── README.md ├── compiler ├── codegen │ ├── events.js │ └── index.js ├── directives │ ├── bind.js │ ├── index.js │ └── model.js ├── error-detector.js ├── helpers.js ├── index.js ├── optimizer.js └── parser │ ├── entity-decoder.js │ ├── filter-parser.js │ ├── html-parser.js │ ├── index.js │ └── text-parser.js ├── core ├── components │ ├── index.js │ └── keep-alive.js ├── config.js ├── global-api │ ├── assets.js │ ├── extend.js │ ├── index.js │ ├── mixin.js │ └── use.js ├── index.js ├── instance │ ├── events.js │ ├── index.js │ ├── init.js │ ├── inject.js │ ├── lifecycle.js │ ├── proxy.js │ ├── render-helpers │ │ ├── bind-object-props.js │ │ ├── check-keycodes.js │ │ ├── render-list.js │ │ ├── render-slot.js │ │ ├── render-static.js │ │ ├── resolve-filter.js │ │ └── resolve-slots.js │ ├── render.js │ └── state.js ├── observer │ ├── array.js │ ├── dep.js │ ├── index.js │ ├── scheduler.js │ └── watcher.js ├── util │ ├── debug.js │ ├── env.js │ ├── error.js │ ├── index.js │ ├── lang.js │ ├── options.js │ ├── perf.js │ └── props.js └── vdom │ ├── create-component.js │ ├── create-element.js │ ├── create-functional-component.js │ ├── helpers │ ├── extract-props.js │ ├── get-first-component-child.js │ ├── index.js │ ├── merge-hook.js │ ├── normalize-children.js │ ├── resolve-async-component.js │ └── update-listeners.js │ ├── modules │ ├── directives.js │ ├── index.js │ └── ref.js │ ├── patch.js │ └── vnode.js ├── doc ├── Vue响应式原理初探.md ├── img │ └── vue-reactive.jpg └── 前端性能优化总结.md ├── platforms ├── web │ ├── compiler.js │ ├── compiler │ │ ├── directives │ │ │ ├── html.js │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── text.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── class.js │ │ │ ├── index.js │ │ │ └── style.js │ │ └── util.js │ ├── runtime-with-compiler.js │ ├── runtime.js │ ├── runtime │ │ ├── class-util.js │ │ ├── components │ │ │ ├── index.js │ │ │ ├── transition-group.js │ │ │ └── transition.js │ │ ├── directives │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── show.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── attrs.js │ │ │ ├── class.js │ │ │ ├── dom-props.js │ │ │ ├── events.js │ │ │ ├── index.js │ │ │ ├── style.js │ │ │ └── transition.js │ │ ├── node-ops.js │ │ ├── patch.js │ │ └── transition-util.js │ ├── server-renderer.js │ ├── server │ │ ├── directives │ │ │ ├── index.js │ │ │ └── show.js │ │ ├── modules │ │ │ ├── attrs.js │ │ │ ├── class.js │ │ │ ├── dom-props.js │ │ │ ├── index.js │ │ │ └── style.js │ │ └── util.js │ └── util │ │ ├── attrs.js │ │ ├── class.js │ │ ├── compat.js │ │ ├── element.js │ │ ├── index.js │ │ └── style.js └── weex │ ├── compiler.js │ ├── compiler │ ├── directives │ │ ├── index.js │ │ └── model.js │ ├── index.js │ └── modules │ │ ├── append.js │ │ ├── class.js │ │ ├── index.js │ │ ├── props.js │ │ └── style.js │ ├── framework.js │ ├── runtime-factory.js │ ├── runtime │ ├── components │ │ ├── index.js │ │ ├── transition-group.js │ │ └── transition.js │ ├── directives │ │ └── index.js │ ├── index.js │ ├── modules │ │ ├── attrs.js │ │ ├── class.js │ │ ├── events.js │ │ ├── index.js │ │ ├── style.js │ │ └── transition.js │ ├── node-ops.js │ ├── patch.js │ └── text-node.js │ └── util │ └── index.js ├── server ├── bundle-renderer │ ├── create-bundle-renderer.js │ ├── create-bundle-runner.js │ └── source-map-support.js ├── create-renderer.js ├── render-context.js ├── render-stream.js ├── render.js ├── template-renderer │ ├── create-async-file-mapper.js │ ├── index.js │ ├── parse-template.js │ └── template-stream.js ├── util.js ├── webpack-plugin │ ├── client.js │ ├── server.js │ └── util.js └── write.js ├── sfc └── parser.js └── shared ├── constants.js └── util.js /README.md: -------------------------------------------------------------------------------- 1 | # Vue 源码分析 2 | ## 目录结构(vue2.3) 3 | ``` 4 | ├── compiler //模板解析的相关文件 5 | │   ├── codegen //根据ast生成render函数 6 | │   ├── directives //通用生成render函数之前需要处理的指令 7 | │   └── parser //模板解析 8 | | 9 | ├── core //核心代码 10 | │   ├── components //全局组件 11 | │   ├── global-api //全局方法,也就是添加在Vue对象上的方法,比如Vue.use,Vue.extend,,Vue.mixin等 12 | │   ├── instance // Vue实例相关内容,包括实例方法,生命周期,事件等 13 | │   ├── observer //双向数据绑定 14 | │   ├── util //工具方法 15 | │   └── vdom //虚拟dom相关,如diff算法 16 | | 17 | ├── platforms //平台相关的内容 18 | │   ├── web //web端独有文件 19 | │   │   ├── compiler //编译阶段需要处理的指令和模块 20 | │   │   ├── runtime //运行阶段需要处理的组件、指令和模块 21 | │   │   ├── server //服务端渲染相关 22 | │   │   └── util //工具库 23 | │   └── weex //weex端独有文件 24 | │   ├── compiler 25 | │   ├── runtime 26 | │   └── util 27 | ├── server //服务端渲染相关 28 | │   ├── bundle-renderer 29 | │   ├── template-renderer 30 | │   └── webpack-plugin 31 | ├── sfc 32 | └── shared //公共的工具方法 33 | ``` -------------------------------------------------------------------------------- /compiler/codegen/events.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/ 4 | const simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/ 5 | 6 | // keyCode aliases 7 | const keyCodes: { [key: string]: number | Array } = { 8 | esc: 27, 9 | tab: 9, 10 | enter: 13, 11 | space: 32, 12 | up: 38, 13 | left: 37, 14 | right: 39, 15 | down: 40, 16 | 'delete': [8, 46] 17 | } 18 | /*Github:https://github.com/answershuto*/ 19 | // #4868: modifiers that prevent the execution of the listener 20 | // need to explicitly return null so that we can determine whether to remove 21 | // the listener for .once 22 | const genGuard = condition => `if(${condition})return null;` 23 | 24 | const modifierCode: { [key: string]: string } = { 25 | stop: '$event.stopPropagation();', 26 | prevent: '$event.preventDefault();', 27 | self: genGuard(`$event.target !== $event.currentTarget`), 28 | ctrl: genGuard(`!$event.ctrlKey`), 29 | shift: genGuard(`!$event.shiftKey`), 30 | alt: genGuard(`!$event.altKey`), 31 | meta: genGuard(`!$event.metaKey`), 32 | left: genGuard(`'button' in $event && $event.button !== 0`), 33 | middle: genGuard(`'button' in $event && $event.button !== 1`), 34 | right: genGuard(`'button' in $event && $event.button !== 2`) 35 | } 36 | 37 | export function genHandlers ( 38 | events: ASTElementHandlers, 39 | native: boolean, 40 | warn: Function 41 | ): string { 42 | let res = native ? 'nativeOn:{' : 'on:{' 43 | for (const name in events) { 44 | const handler = events[name] 45 | // #5330: warn click.right, since right clicks do not actually fire click events. 46 | if (process.env.NODE_ENV !== 'production' && 47 | name === 'click' && 48 | handler && handler.modifiers && handler.modifiers.right 49 | ) { 50 | warn( 51 | `Use "contextmenu" instead of "click.right" since right clicks ` + 52 | `do not actually fire "click" events.` 53 | ) 54 | } 55 | res += `"${name}":${genHandler(name, handler)},` 56 | } 57 | return res.slice(0, -1) + '}' 58 | } 59 | 60 | function genHandler ( 61 | name: string, 62 | handler: ASTElementHandler | Array 63 | ): string { 64 | if (!handler) { 65 | return 'function(){}' 66 | } 67 | 68 | if (Array.isArray(handler)) { 69 | return `[${handler.map(handler => genHandler(name, handler)).join(',')}]` 70 | } 71 | 72 | const isMethodPath = simplePathRE.test(handler.value) 73 | const isFunctionExpression = fnExpRE.test(handler.value) 74 | 75 | if (!handler.modifiers) { 76 | return isMethodPath || isFunctionExpression 77 | ? handler.value 78 | : `function($event){${handler.value}}` // inline statement 79 | } else { 80 | let code = '' 81 | let genModifierCode = '' 82 | const keys = [] 83 | for (const key in handler.modifiers) { 84 | if (modifierCode[key]) { 85 | genModifierCode += modifierCode[key] 86 | // left/right 87 | if (keyCodes[key]) { 88 | keys.push(key) 89 | } 90 | } else { 91 | keys.push(key) 92 | } 93 | } 94 | if (keys.length) { 95 | code += genKeyFilter(keys) 96 | } 97 | // Make sure modifiers like prevent and stop get executed after key filtering 98 | if (genModifierCode) { 99 | code += genModifierCode 100 | } 101 | const handlerCode = isMethodPath 102 | ? handler.value + '($event)' 103 | : isFunctionExpression 104 | ? `(${handler.value})($event)` 105 | : handler.value 106 | return `function($event){${code}${handlerCode}}` 107 | } 108 | } 109 | 110 | function genKeyFilter (keys: Array): string { 111 | return `if(!('button' in $event)&&${keys.map(genFilterCode).join('&&')})return null;` 112 | } 113 | 114 | function genFilterCode (key: string): string { 115 | const keyVal = parseInt(key, 10) 116 | if (keyVal) { 117 | return `$event.keyCode!==${keyVal}` 118 | } 119 | const alias = keyCodes[key] 120 | return `_k($event.keyCode,${JSON.stringify(key)}${alias ? ',' + JSON.stringify(alias) : ''})` 121 | } 122 | -------------------------------------------------------------------------------- /compiler/directives/bind.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /*Github:https://github.com/answershuto*/ 3 | export default function bind (el: ASTElement, dir: ASTDirective) { 4 | el.wrapData = (code: string) => { 5 | return `_b(${code},'${el.tag}',${dir.value}${ 6 | dir.modifiers && dir.modifiers.prop ? ',true' : '' 7 | })` 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import bind from './bind' 4 | import { noop } from 'shared/util' 5 | /*Github:https://github.com/answershuto*/ 6 | export default { 7 | bind, 8 | cloak: noop 9 | } 10 | -------------------------------------------------------------------------------- /compiler/directives/model.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Cross-platform code generation for component v-model 5 | */ 6 | export function genComponentModel ( 7 | el: ASTElement, 8 | value: string, 9 | modifiers: ?ASTModifiers 10 | ): ?boolean { 11 | const { number, trim } = modifiers || {} 12 | 13 | const baseValueExpression = '$$v' 14 | let valueExpression = baseValueExpression 15 | if (trim) { 16 | valueExpression = 17 | `(typeof ${baseValueExpression} === 'string'` + 18 | `? ${baseValueExpression}.trim()` + 19 | `: ${baseValueExpression})` 20 | } 21 | if (number) { 22 | valueExpression = `_n(${valueExpression})` 23 | } 24 | const assignment = genAssignmentCode(value, valueExpression) 25 | 26 | el.model = { 27 | value: `(${value})`, 28 | expression: `"${value}"`, 29 | callback: `function (${baseValueExpression}) {${assignment}}` 30 | } 31 | } 32 | /*Github:https://github.com/answershuto*/ 33 | /** 34 | * Cross-platform codegen helper for generating v-model value assignment code. 35 | */ 36 | export function genAssignmentCode ( 37 | value: string, 38 | assignment: string 39 | ): string { 40 | const modelRs = parseModel(value) 41 | if (modelRs.idx === null) { 42 | return `${value}=${assignment}` 43 | } else { 44 | return `var $$exp = ${modelRs.exp}, $$idx = ${modelRs.idx};` + 45 | `if (!Array.isArray($$exp)){` + 46 | `${value}=${assignment}}` + 47 | `else{$$exp.splice($$idx, 1, ${assignment})}` 48 | } 49 | } 50 | 51 | /** 52 | * parse directive model to do the array update transform. a[idx] = val => $$a.splice($$idx, 1, val) 53 | * 54 | * for loop possible cases: 55 | * 56 | * - test 57 | * - test[idx] 58 | * - test[test1[idx]] 59 | * - test["a"][idx] 60 | * - xxx.test[a[a].test1[idx]] 61 | * - test.xxx.a["asa"][test1[idx]] 62 | * 63 | */ 64 | 65 | let len, str, chr, index, expressionPos, expressionEndPos 66 | 67 | export function parseModel (val: string): Object { 68 | str = val 69 | len = str.length 70 | index = expressionPos = expressionEndPos = 0 71 | 72 | if (val.indexOf('[') < 0 || val.lastIndexOf(']') < len - 1) { 73 | return { 74 | exp: val, 75 | idx: null 76 | } 77 | } 78 | 79 | while (!eof()) { 80 | chr = next() 81 | /* istanbul ignore if */ 82 | if (isStringStart(chr)) { 83 | parseString(chr) 84 | } else if (chr === 0x5B) { 85 | parseBracket(chr) 86 | } 87 | } 88 | 89 | return { 90 | exp: val.substring(0, expressionPos), 91 | idx: val.substring(expressionPos + 1, expressionEndPos) 92 | } 93 | } 94 | 95 | function next (): number { 96 | return str.charCodeAt(++index) 97 | } 98 | 99 | function eof (): boolean { 100 | return index >= len 101 | } 102 | 103 | function isStringStart (chr: number): boolean { 104 | return chr === 0x22 || chr === 0x27 105 | } 106 | 107 | function parseBracket (chr: number): void { 108 | let inBracket = 1 109 | expressionPos = index 110 | while (!eof()) { 111 | chr = next() 112 | if (isStringStart(chr)) { 113 | parseString(chr) 114 | continue 115 | } 116 | if (chr === 0x5B) inBracket++ 117 | if (chr === 0x5D) inBracket-- 118 | if (inBracket === 0) { 119 | expressionEndPos = index 120 | break 121 | } 122 | } 123 | } 124 | 125 | function parseString (chr: number): void { 126 | const stringQuote = chr 127 | while (!eof()) { 128 | chr = next() 129 | if (chr === stringQuote) { 130 | break 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /compiler/error-detector.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { dirRE, onRE } from './parser/index' 4 | 5 | // these keywords should not appear inside expressions, but operators like 6 | // typeof, instanceof and in are allowed 7 | const prohibitedKeywordRE = new RegExp('\\b' + ( 8 | 'do,if,for,let,new,try,var,case,else,with,await,break,catch,class,const,' + 9 | 'super,throw,while,yield,delete,export,import,return,switch,default,' + 10 | 'extends,finally,continue,debugger,function,arguments' 11 | ).split(',').join('\\b|\\b') + '\\b') 12 | 13 | // these unary operators should not be used as property/method names 14 | const unaryOperatorsRE = new RegExp('\\b' + ( 15 | 'delete,typeof,void' 16 | ).split(',').join('\\s*\\([^\\)]*\\)|\\b') + '\\s*\\([^\\)]*\\)') 17 | 18 | // check valid identifier for v-for 19 | const identRE = /[A-Za-z_$][\w$]*/ 20 | 21 | // strip strings in expressions 22 | const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g 23 | 24 | // detect problematic expressions in a template 25 | export function detectErrors (ast: ?ASTNode): Array { 26 | const errors: Array = [] 27 | if (ast) { 28 | checkNode(ast, errors) 29 | } 30 | return errors 31 | } 32 | 33 | function checkNode (node: ASTNode, errors: Array) { 34 | if (node.type === 1) { 35 | for (const name in node.attrsMap) { 36 | if (dirRE.test(name)) { 37 | const value = node.attrsMap[name] 38 | if (value) { 39 | if (name === 'v-for') { 40 | checkFor(node, `v-for="${value}"`, errors) 41 | } else if (onRE.test(name)) { 42 | checkEvent(value, `${name}="${value}"`, errors) 43 | } else { 44 | checkExpression(value, `${name}="${value}"`, errors) 45 | } 46 | } 47 | } 48 | } 49 | if (node.children) { 50 | for (let i = 0; i < node.children.length; i++) { 51 | checkNode(node.children[i], errors) 52 | } 53 | } 54 | } else if (node.type === 2) { 55 | checkExpression(node.expression, node.text, errors) 56 | } 57 | } 58 | 59 | function checkEvent (exp: string, text: string, errors: Array) { 60 | const stipped = exp.replace(stripStringRE, '') 61 | const keywordMatch: any = stipped.match(unaryOperatorsRE) 62 | if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { 63 | errors.push( 64 | `avoid using JavaScript unary operator as property name: ` + 65 | `"${keywordMatch[0]}" in expression ${text.trim()}` 66 | ) 67 | } 68 | checkExpression(exp, text, errors) 69 | } 70 | 71 | function checkFor (node: ASTElement, text: string, errors: Array) { 72 | checkExpression(node.for || '', text, errors) 73 | checkIdentifier(node.alias, 'v-for alias', text, errors) 74 | checkIdentifier(node.iterator1, 'v-for iterator', text, errors) 75 | checkIdentifier(node.iterator2, 'v-for iterator', text, errors) 76 | } 77 | 78 | function checkIdentifier (ident: ?string, type: string, text: string, errors: Array) { 79 | if (typeof ident === 'string' && !identRE.test(ident)) { 80 | errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`) 81 | } 82 | } 83 | 84 | function checkExpression (exp: string, text: string, errors: Array) { 85 | try { 86 | new Function(`return ${exp}`) 87 | } catch (e) { 88 | const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE) 89 | if (keywordMatch) { 90 | errors.push( 91 | `avoid using JavaScript keyword as property name: ` + 92 | `"${keywordMatch[0]}" in expression ${text.trim()}` 93 | ) 94 | } else { 95 | errors.push(`invalid expression: ${text.trim()}`) 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /compiler/helpers.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { parseFilters } from './parser/filter-parser' 4 | 5 | /*Vue 编译器警告*/ 6 | export function baseWarn (msg: string) { 7 | console.error(`[Vue compiler]: ${msg}`) 8 | } 9 | 10 | export function pluckModuleFunction ( 11 | modules: ?Array, 12 | key: string 13 | ): Array { 14 | return modules 15 | ? modules.map(m => m[key]).filter(_ => _) 16 | : [] 17 | } 18 | 19 | /*将属性放入ele的props属性中*/ 20 | export function addProp (el: ASTElement, name: string, value: string) { 21 | (el.props || (el.props = [])).push({ name, value }) 22 | } 23 | 24 | /*将属性放入ele的attr属性中*/ 25 | export function addAttr (el: ASTElement, name: string, value: string) { 26 | (el.attrs || (el.attrs = [])).push({ name, value }) 27 | } 28 | 29 | /*将参数加入到ele的directives中去*/ 30 | export function addDirective ( 31 | el: ASTElement, 32 | name: string, 33 | rawName: string, 34 | value: string, 35 | arg: ?string, 36 | modifiers: ?ASTModifiers 37 | ) { 38 | (el.directives || (el.directives = [])).push({ name, rawName, value, arg, modifiers }) 39 | } 40 | 41 | export function addHandler ( 42 | el: ASTElement, 43 | name: string, 44 | value: string, 45 | modifiers: ?ASTModifiers, 46 | important?: boolean, 47 | warn?: Function 48 | ) { 49 | // warn prevent and passive modifier 50 | /* istanbul ignore if */ 51 | if ( 52 | process.env.NODE_ENV !== 'production' && warn && 53 | modifiers && modifiers.prevent && modifiers.passive 54 | ) { 55 | warn( 56 | 'passive and prevent can\'t be used together. ' + 57 | 'Passive handler can\'t prevent default event.' 58 | ) 59 | } 60 | // check capture modifier 61 | if (modifiers && modifiers.capture) { 62 | delete modifiers.capture 63 | name = '!' + name // mark the event as captured 64 | } 65 | if (modifiers && modifiers.once) { 66 | delete modifiers.once 67 | name = '~' + name // mark the event as once 68 | } 69 | /* istanbul ignore if */ 70 | if (modifiers && modifiers.passive) { 71 | delete modifiers.passive 72 | name = '&' + name // mark the event as passive 73 | } 74 | let events 75 | if (modifiers && modifiers.native) { 76 | delete modifiers.native 77 | events = el.nativeEvents || (el.nativeEvents = {}) 78 | } else { 79 | events = el.events || (el.events = {}) 80 | } 81 | const newHandler = { value, modifiers } 82 | const handlers = events[name] 83 | /* istanbul ignore if */ 84 | if (Array.isArray(handlers)) { 85 | important ? handlers.unshift(newHandler) : handlers.push(newHandler) 86 | } else if (handlers) { 87 | events[name] = important ? [newHandler, handlers] : [handlers, newHandler] 88 | } else { 89 | events[name] = newHandler 90 | } 91 | } 92 | 93 | export function getBindingAttr ( 94 | el: ASTElement, 95 | name: string, 96 | getStatic?: boolean 97 | ): ?string { 98 | /*得到用:或者v-bind:修饰的特殊属性*/ 99 | const dynamicValue = 100 | getAndRemoveAttr(el, ':' + name) || 101 | getAndRemoveAttr(el, 'v-bind:' + name) 102 | if (dynamicValue != null) { 103 | /*存在特殊属性*/ 104 | return parseFilters(dynamicValue) 105 | } else if (getStatic !== false) { 106 | /*getStatic非false的时候返回静态属性,即一般的属性*/ 107 | const staticValue = getAndRemoveAttr(el, name) 108 | if (staticValue != null) { 109 | return JSON.stringify(staticValue) 110 | } 111 | } 112 | } 113 | 114 | /*从ele的属性中获取name对应的值并将它从中删除*/ 115 | export function getAndRemoveAttr (el: ASTElement, name: string): ?string { 116 | let val 117 | if ((val = el.attrsMap[name]) != null) { 118 | const list = el.attrsList 119 | for (let i = 0, l = list.length; i < l; i++) { 120 | if (list[i].name === name) { 121 | list.splice(i, 1) 122 | break 123 | } 124 | } 125 | } 126 | return val 127 | } 128 | -------------------------------------------------------------------------------- /compiler/parser/entity-decoder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | let decoder 4 | 5 | export function decode (html: string): string { 6 | decoder = decoder || document.createElement('div') 7 | decoder.innerHTML = html 8 | return decoder.textContent 9 | } 10 | -------------------------------------------------------------------------------- /compiler/parser/filter-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const validDivisionCharRE = /[\w).+\-_$\]]/ 4 | 5 | /*解析过滤器*/ 6 | export function parseFilters (exp: string): string { 7 | let inSingle = false 8 | let inDouble = false 9 | let inTemplateString = false 10 | let inRegex = false 11 | let curly = 0 12 | let square = 0 13 | let paren = 0 14 | let lastFilterIndex = 0 15 | let c, prev, i, expression, filters 16 | 17 | for (i = 0; i < exp.length; i++) { 18 | prev = c 19 | c = exp.charCodeAt(i) 20 | if (inSingle) { 21 | // ' 单引号 22 | if (c === 0x27 && prev !== 0x5C) inSingle = false 23 | } else if (inDouble) { 24 | // " 双引号 25 | if (c === 0x22 && prev !== 0x5C) inDouble = false 26 | } else if (inTemplateString) { 27 | // ` 模板字符串 28 | if (c === 0x60 && prev !== 0x5C) inTemplateString = false 29 | } else if (inRegex) { 30 | // / 正则 31 | if (c === 0x2f && prev !== 0x5C) inRegex = false 32 | } else if ( 33 | // | 管道 34 | c === 0x7C && // pipe 35 | exp.charCodeAt(i + 1) !== 0x7C && 36 | exp.charCodeAt(i - 1) !== 0x7C && 37 | !curly && !square && !paren 38 | ) { 39 | if (expression === undefined) { 40 | // first filter, end of expression 41 | lastFilterIndex = i + 1 42 | expression = exp.slice(0, i).trim() 43 | } else { 44 | pushFilter() 45 | } 46 | } else { 47 | switch (c) { 48 | case 0x22: inDouble = true; break // " 49 | case 0x27: inSingle = true; break // ' 50 | case 0x60: inTemplateString = true; break // ` 51 | case 0x28: paren++; break // ( 52 | case 0x29: paren--; break // ) 53 | case 0x5B: square++; break // [ 54 | case 0x5D: square--; break // ] 55 | case 0x7B: curly++; break // { 56 | case 0x7D: curly--; break // } 57 | } 58 | if (c === 0x2f) { // / 59 | let j = i - 1 60 | let p 61 | // find first non-whitespace prev char 62 | for (; j >= 0; j--) { 63 | p = exp.charAt(j) 64 | if (p !== ' ') break 65 | } 66 | if (!p || !validDivisionCharRE.test(p)) { 67 | inRegex = true 68 | } 69 | } 70 | } 71 | } 72 | 73 | if (expression === undefined) { 74 | expression = exp.slice(0, i).trim() 75 | } else if (lastFilterIndex !== 0) { 76 | pushFilter() 77 | } 78 | 79 | function pushFilter () { 80 | (filters || (filters = [])).push(exp.slice(lastFilterIndex, i).trim()) 81 | lastFilterIndex = i + 1 82 | } 83 | 84 | if (filters) { 85 | for (i = 0; i < filters.length; i++) { 86 | expression = wrapFilter(expression, filters[i]) 87 | } 88 | } 89 | 90 | return expression 91 | } 92 | 93 | function wrapFilter (exp: string, filter: string): string { 94 | const i = filter.indexOf('(') 95 | if (i < 0) { 96 | // _f: resolveFilter 97 | return `_f("${filter}")(${exp})` 98 | } else { 99 | const name = filter.slice(0, i) 100 | const args = filter.slice(i + 1) 101 | return `_f("${name}")(${exp},${args}` 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /compiler/parser/text-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { cached } from 'shared/util' 4 | import { parseFilters } from './filter-parser' 5 | 6 | const defaultTagRE = /\{\{((?:.|\n)+?)\}\}/g 7 | const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g 8 | 9 | const buildRegex = cached(delimiters => { 10 | const open = delimiters[0].replace(regexEscapeRE, '\\$&') 11 | const close = delimiters[1].replace(regexEscapeRE, '\\$&') 12 | return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') 13 | }) 14 | /*Github:https://github.com/answershuto*/ 15 | export function parseText ( 16 | text: string, 17 | delimiters?: [string, string] 18 | ): string | void { 19 | const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE 20 | if (!tagRE.test(text)) { 21 | return 22 | } 23 | const tokens = [] 24 | let lastIndex = tagRE.lastIndex = 0 25 | let match, index 26 | while ((match = tagRE.exec(text))) { 27 | index = match.index 28 | // push text token 29 | if (index > lastIndex) { 30 | tokens.push(JSON.stringify(text.slice(lastIndex, index))) 31 | } 32 | // tag token 33 | const exp = parseFilters(match[1].trim()) 34 | tokens.push(`_s(${exp})`) 35 | lastIndex = index + match[0].length 36 | } 37 | if (lastIndex < text.length) { 38 | tokens.push(JSON.stringify(text.slice(lastIndex))) 39 | } 40 | return tokens.join('+') 41 | } 42 | -------------------------------------------------------------------------------- /core/components/index.js: -------------------------------------------------------------------------------- 1 | import KeepAlive from './keep-alive' 2 | 3 | export default { 4 | KeepAlive 5 | } 6 | -------------------------------------------------------------------------------- /core/components/keep-alive.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isRegExp } from 'shared/util' 4 | import { getFirstComponentChild } from 'core/vdom/helpers/index' 5 | 6 | type VNodeCache = { [key: string]: ?VNode }; 7 | 8 | const patternTypes: Array = [String, RegExp] 9 | 10 | /* 获取组件名称 */ 11 | function getComponentName (opts: ?VNodeComponentOptions): ?string { 12 | return opts && (opts.Ctor.options.name || opts.tag) 13 | } 14 | 15 | /* 检测name是否匹配 */ 16 | function matches (pattern: string | RegExp, name: string): boolean { 17 | if (typeof pattern === 'string') { 18 | /* 字符串情况,如a,b,c */ 19 | return pattern.split(',').indexOf(name) > -1 20 | } else if (isRegExp(pattern)) { 21 | /* 正则 */ 22 | return pattern.test(name) 23 | } 24 | /* istanbul ignore next */ 25 | return false 26 | } 27 | 28 | /* 修正cache */ 29 | function pruneCache (cache: VNodeCache, current: VNode, filter: Function) { 30 | for (const key in cache) { 31 | /* 取出cache中的vnode */ 32 | const cachedNode: ?VNode = cache[key] 33 | if (cachedNode) { 34 | const name: ?string = getComponentName(cachedNode.componentOptions) 35 | /* name不符合filter条件的,同时不是目前渲染的vnode时,销毁vnode对应的组件实例(Vue实例),并从cache中移除 */ 36 | if (name && !filter(name)) { 37 | if (cachedNode !== current) { 38 | pruneCacheEntry(cachedNode) 39 | } 40 | cache[key] = null 41 | } 42 | } 43 | } 44 | } 45 | 46 | /* 销毁vnode对应的组件实例(Vue实例) */ 47 | function pruneCacheEntry (vnode: ?VNode) { 48 | if (vnode) { 49 | vnode.componentInstance.$destroy() 50 | } 51 | } 52 | 53 | /* keep-alive组件 */ 54 | export default { 55 | name: 'keep-alive', 56 | /* 抽象组件 */ 57 | abstract: true, 58 | 59 | props: { 60 | include: patternTypes, 61 | exclude: patternTypes 62 | }, 63 | 64 | created () { 65 | /* 缓存对象 */ 66 | this.cache = Object.create(null) 67 | }, 68 | 69 | /* destroyed钩子中销毁所有cache中的组件实例 */ 70 | destroyed () { 71 | for (const key in this.cache) { 72 | pruneCacheEntry(this.cache[key]) 73 | } 74 | }, 75 | 76 | watch: { 77 | /* 监视include以及exclude,在被修改的时候对cache进行修正 */ 78 | include (val: string | RegExp) { 79 | pruneCache(this.cache, this._vnode, name => matches(val, name)) 80 | }, 81 | exclude (val: string | RegExp) { 82 | pruneCache(this.cache, this._vnode, name => !matches(val, name)) 83 | } 84 | }, 85 | 86 | render () { 87 | /* 得到slot插槽中的第一个组件 */ 88 | const vnode: VNode = getFirstComponentChild(this.$slots.default) 89 | 90 | const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions 91 | if (componentOptions) { 92 | // check pattern 93 | /* 获取组件名称,优先获取组件的name字段,否则是组件的tag */ 94 | const name: ?string = getComponentName(componentOptions) 95 | /* name不在inlcude中或者在exlude中则直接返回vnode(没有取缓存) */ 96 | if (name && ( 97 | (this.include && !matches(this.include, name)) || 98 | (this.exclude && matches(this.exclude, name)) 99 | )) { 100 | return vnode 101 | } 102 | const key: ?string = vnode.key == null 103 | // same constructor may get registered as different local components 104 | // so cid alone is not enough (#3269) 105 | ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') 106 | : vnode.key 107 | /* 如果已经做过缓存了则直接从缓存中获取组件实例给vnode,还未缓存过则进行缓存 */ 108 | if (this.cache[key]) { 109 | vnode.componentInstance = this.cache[key].componentInstance 110 | } else { 111 | this.cache[key] = vnode 112 | } 113 | /* keepAlive标记位 */ 114 | vnode.data.keepAlive = true 115 | } 116 | return vnode 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /core/config.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | no, 5 | noop, 6 | identity 7 | } from 'shared/util' 8 | 9 | import { LIFECYCLE_HOOKS } from 'shared/constants' 10 | 11 | export type Config = { 12 | // user 13 | optionMergeStrategies: { [key: string]: Function }; 14 | silent: boolean; 15 | productionTip: boolean; 16 | performance: boolean; 17 | devtools: boolean; 18 | errorHandler: ?(err: Error, vm: Component, info: string) => void; 19 | ignoredElements: Array; 20 | keyCodes: { [key: string]: number | Array }; 21 | 22 | // platform 23 | isReservedTag: (x?: string) => boolean; 24 | isReservedAttr: (x?: string) => boolean; 25 | parsePlatformTagName: (x: string) => string; 26 | isUnknownElement: (x?: string) => boolean; 27 | getTagNamespace: (x?: string) => string | void; 28 | mustUseProp: (tag: string, type: ?string, name: string) => boolean; 29 | 30 | // legacy 31 | _lifecycleHooks: Array; 32 | }; 33 | 34 | export default ({ 35 | /** 36 | * Option merge strategies (used in core/util/options) 37 | */ 38 | optionMergeStrategies: Object.create(null), 39 | 40 | /** 41 | * Whether to suppress warnings. 42 | */ 43 | silent: false, 44 | 45 | /** 46 | * Show production mode tip message on boot? 47 | */ 48 | productionTip: process.env.NODE_ENV !== 'production', 49 | 50 | /** 51 | * Whether to enable devtools 52 | */ 53 | devtools: process.env.NODE_ENV !== 'production', 54 | 55 | /** 56 | * Whether to record perf 57 | */ 58 | performance: false, 59 | 60 | /** 61 | * Error handler for watcher errors 62 | */ 63 | errorHandler: null, 64 | 65 | /** 66 | * Ignore certain custom elements 67 | */ 68 | ignoredElements: [], 69 | 70 | /** 71 | * Custom user key aliases for v-on 72 | */ 73 | keyCodes: Object.create(null), 74 | 75 | /** 76 | * Check if a tag is reserved so that it cannot be registered as a 77 | * component. This is platform-dependent and may be overwritten. 78 | */ 79 | isReservedTag: no, 80 | 81 | /** 82 | * Check if an attribute is reserved so that it cannot be used as a component 83 | * prop. This is platform-dependent and may be overwritten. 84 | */ 85 | isReservedAttr: no, 86 | 87 | /** 88 | * Check if a tag is an unknown element. 89 | * Platform-dependent. 90 | */ 91 | isUnknownElement: no, 92 | 93 | /** 94 | * Get the namespace of an element 95 | */ 96 | getTagNamespace: noop, 97 | 98 | /** 99 | * Parse the real tag name for the specific platform. 100 | */ 101 | parsePlatformTagName: identity, 102 | 103 | /** 104 | * Check if an attribute must be bound using property, e.g. value 105 | * Platform-dependent. 106 | */ 107 | mustUseProp: no, 108 | 109 | /** 110 | * Exposed for legacy reasons 111 | */ 112 | _lifecycleHooks: LIFECYCLE_HOOKS 113 | }: Config) 114 | -------------------------------------------------------------------------------- /core/global-api/assets.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { ASSET_TYPES } from 'shared/constants' 5 | import { warn, isPlainObject } from '../util/index' 6 | 7 | export function initAssetRegisters (Vue: GlobalAPI) { 8 | /** 9 | * Create asset registration methods. 10 | */ 11 | ASSET_TYPES.forEach(type => { 12 | Vue[type] = function ( 13 | id: string, 14 | definition: Function | Object 15 | ): Function | Object | void { 16 | if (!definition) { 17 | return this.options[type + 's'][id] 18 | } else { 19 | /* istanbul ignore if */ 20 | if (process.env.NODE_ENV !== 'production') { 21 | if (type === 'component' && config.isReservedTag(id)) { 22 | warn( 23 | 'Do not use built-in or reserved HTML elements as component ' + 24 | 'id: ' + id 25 | ) 26 | } 27 | } 28 | if (type === 'component' && isPlainObject(definition)) { 29 | definition.name = definition.name || id 30 | definition = this.options._base.extend(definition) 31 | } 32 | if (type === 'directive' && typeof definition === 'function') { 33 | definition = { bind: definition, update: definition } 34 | } 35 | this.options[type + 's'][id] = definition 36 | return definition 37 | } 38 | } 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /core/global-api/extend.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ASSET_TYPES } from 'shared/constants' 4 | import { warn, extend, mergeOptions } from '../util/index' 5 | import { defineComputed, proxy } from '../instance/state' 6 | 7 | export function initExtend (Vue: GlobalAPI) { 8 | /** 9 | * Each instance constructor, including Vue, has a unique 10 | * cid. This enables us to create wrapped "child 11 | * constructors" for prototypal inheritance and cache them. 12 | */ 13 | /* 14 | 每个构造函数实例(包括Vue本身)都会有一个唯一的cid 15 | 它为我们能够创造继承创建自构造函数并进行缓存创造了可能 16 | */ 17 | Vue.cid = 0 18 | let cid = 1 19 | 20 | /** 21 | * Class inheritance 22 | */ 23 | /* 24 | 使用基础 Vue 构造器,创建一个“子类”。 25 | 其实就是扩展了基础构造器,形成了一个可复用的有指定选项功能的子构造器。 26 | 参数是一个包含组件option的对象。 https://cn.vuejs.org/v2/api/#Vue-extend-options 27 | */ 28 | Vue.extend = function (extendOptions: Object): Function { 29 | extendOptions = extendOptions || {} 30 | /*父类的构造*/ 31 | const Super = this 32 | /*父类的cid*/ 33 | const SuperId = Super.cid 34 | const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) 35 | /*如果构造函数中已经存在了该cid,则代表已经extend过了,直接返回*/ 36 | if (cachedCtors[SuperId]) { 37 | return cachedCtors[SuperId] 38 | } 39 | 40 | const name = extendOptions.name || Super.options.name 41 | if (process.env.NODE_ENV !== 'production') { 42 | /*name只能包含字母与连字符*/ 43 | if (!/^[a-zA-Z][\w-]*$/.test(name)) { 44 | warn( 45 | 'Invalid component name: "' + name + '". Component names ' + 46 | 'can only contain alphanumeric characters and the hyphen, ' + 47 | 'and must start with a letter.' 48 | ) 49 | } 50 | } 51 | 52 | /* 53 | Sub构造函数其实就一个_init方法,这跟Vue的构造方法是一致的,在_init中处理各种数据初始化、生命周期等。 54 | 因为Sub作为一个Vue的扩展构造器,所以基础的功能还是需要保持一致,跟Vue构造器一样在构造函数中初始化_init。 55 | */ 56 | const Sub = function VueComponent (options) { 57 | this._init(options) 58 | } 59 | /*继承父类*/ 60 | Sub.prototype = Object.create(Super.prototype) 61 | /*构造函数*/ 62 | Sub.prototype.constructor = Sub 63 | /*创建一个新的cid*/ 64 | Sub.cid = cid++ 65 | /*将父组件的option与子组件的合并到一起(Vue有一个cid为0的基类,即Vue本身,会将一些默认初始化的option何入)*/ 66 | Sub.options = mergeOptions( 67 | Super.options, 68 | extendOptions 69 | ) 70 | /*es6语法,super为父类构造*/ 71 | Sub['super'] = Super 72 | 73 | // For props and computed properties, we define the proxy getters on 74 | // the Vue instances at extension time, on the extended prototype. This 75 | // avoids Object.defineProperty calls for each instance created. 76 | /*在扩展时,我们将计算属性以及props通过代理绑定在Vue实例上(也就是vm),这也避免了Object.defineProperty被每一个实例调用*/ 77 | if (Sub.options.props) { 78 | /*初始化props,将option中的_props代理到vm上*/ 79 | initProps(Sub) 80 | } 81 | if (Sub.options.computed) { 82 | /*处理计算属性,给计算属性设置defineProperty并绑定在vm上*/ 83 | initComputed(Sub) 84 | } 85 | 86 | // allow further extension/mixin/plugin usage 87 | /*加入extend、mixin以及use方法,允许将来继续为该组件提供扩展、混合或者插件*/ 88 | Sub.extend = Super.extend 89 | Sub.mixin = Super.mixin 90 | Sub.use = Super.use 91 | 92 | // create asset registers, so extended classes 93 | // can have their private assets too. 94 | /*使得Sub也会拥有父类的私有选项(directives、filters、components)*/ 95 | ASSET_TYPES.forEach(function (type) { 96 | Sub[type] = Super[type] 97 | }) 98 | // enable recursive self-lookup 99 | /*把组件自身也加入components中,为递归自身提供可能(递归组件也会查找components是否存在当前组件,也就是自身)*/ 100 | if (name) { 101 | Sub.options.components[name] = Sub 102 | } 103 | 104 | // keep a reference to the super options at extension time. 105 | // later at instantiation we can check if Super's options have 106 | // been updated. 107 | /*保存一个父类的options,此后我们可以用来检测父类的options是否已经被更新*/ 108 | Sub.superOptions = Super.options, 109 | /*extendOptions存储起来*/ 110 | Sub.extendOptions = extendOptions 111 | /*保存一份option,extend的作用是将Sub.options中的所有属性放入{}中*/ 112 | Sub.sealedOptions = extend({}, Sub.options) 113 | 114 | // cache constructor 115 | /*缓存构造函数(用cid),防止重复extend*/ 116 | cachedCtors[SuperId] = Sub 117 | return Sub 118 | } 119 | } 120 | 121 | /*初始化props,将option中的_props代理到vm上*/ 122 | function initProps (Comp) { 123 | const props = Comp.options.props 124 | for (const key in props) { 125 | proxy(Comp.prototype, `_props`, key) 126 | } 127 | } 128 | 129 | /*处理计算属性,给计算属性设置defineProperty并绑定在vm上*/ 130 | function initComputed (Comp) { 131 | const computed = Comp.options.computed 132 | for (const key in computed) { 133 | defineComputed(Comp.prototype, key, computed[key]) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /core/global-api/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { initUse } from './use' 5 | import { initMixin } from './mixin' 6 | import { initExtend } from './extend' 7 | import { initAssetRegisters } from './assets' 8 | import { set, del } from '../observer/index' 9 | import { ASSET_TYPES } from 'shared/constants' 10 | import builtInComponents from '../components/index' 11 | 12 | import { 13 | warn, 14 | extend, 15 | nextTick, 16 | mergeOptions, 17 | defineReactive 18 | } from '../util/index' 19 | 20 | export function initGlobalAPI (Vue: GlobalAPI) { 21 | // config 22 | const configDef = {} 23 | configDef.get = () => config 24 | if (process.env.NODE_ENV !== 'production') { 25 | configDef.set = () => { 26 | warn( 27 | 'Do not replace the Vue.config object, set individual fields instead.' 28 | ) 29 | } 30 | } 31 | Object.defineProperty(Vue, 'config', configDef) 32 | 33 | // exposed util methods. 34 | // NOTE: these are not considered part of the public API - avoid relying on 35 | // them unless you are aware of the risk. 36 | Vue.util = { 37 | warn, 38 | extend, 39 | mergeOptions, 40 | defineReactive 41 | } 42 | 43 | Vue.set = set 44 | Vue.delete = del 45 | Vue.nextTick = nextTick 46 | 47 | Vue.options = Object.create(null) 48 | ASSET_TYPES.forEach(type => { 49 | Vue.options[type + 's'] = Object.create(null) 50 | }) 51 | 52 | // this is used to identify the "base" constructor to extend all plain-object 53 | // components with in Weex's multi-instance scenarios. 54 | /*_base被用来标识基本构造函数(也就是Vue),以便在多场景下添加组件扩展*/ 55 | Vue.options._base = Vue 56 | 57 | extend(Vue.options.components, builtInComponents) 58 | 59 | initUse(Vue) 60 | initMixin(Vue) 61 | initExtend(Vue) 62 | initAssetRegisters(Vue) 63 | } 64 | -------------------------------------------------------------------------------- /core/global-api/mixin.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { mergeOptions } from '../util/index' 4 | 5 | /*初始化mixin*/ 6 | export function initMixin (Vue: GlobalAPI) { 7 | /*https://cn.vuejs.org/v2/api/#Vue-mixin*/ 8 | Vue.mixin = function (mixin: Object) { 9 | /*mergeOptions合并optiuons*/ 10 | this.options = mergeOptions(this.options, mixin) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /core/global-api/use.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toArray } from '../util/index' 4 | 5 | /*初始化use*/ 6 | export function initUse (Vue: GlobalAPI) { 7 | /*https://cn.vuejs.org/v2/api/#Vue-use*/ 8 | Vue.use = function (plugin: Function | Object) { 9 | /* istanbul ignore if */ 10 | /*标识位检测该插件是否已经被安装*/ 11 | if (plugin.installed) { 12 | return 13 | } 14 | // additional parameters 15 | const args = toArray(arguments, 1) 16 | /*a*/ 17 | args.unshift(this) 18 | if (typeof plugin.install === 'function') { 19 | /*install执行插件安装*/ 20 | plugin.install.apply(plugin, args) 21 | } else if (typeof plugin === 'function') { 22 | plugin.apply(null, args) 23 | } 24 | plugin.installed = true 25 | return this 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | import { isServerRendering } from 'core/util/env' 4 | 5 | initGlobalAPI(Vue) 6 | 7 | Object.defineProperty(Vue.prototype, '$isServer', { 8 | get: isServerRendering 9 | }) 10 | 11 | Vue.version = '__VERSION__' 12 | 13 | export default Vue 14 | -------------------------------------------------------------------------------- /core/instance/events.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { updateListeners } from '../vdom/helpers/index' 4 | import { toArray, tip, hyphenate, formatComponentName } from '../util/index' 5 | 6 | /*初始化事件*/ 7 | export function initEvents (vm: Component) { 8 | /*在vm上创建一个_events对象,用来存放事件。*/ 9 | vm._events = Object.create(null) 10 | /*这个bool标志位来表明是否存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/ 11 | vm._hasHookEvent = false 12 | // init parent attached events 13 | /*初始化父组件attach的事件*/ 14 | const listeners = vm.$options._parentListeners 15 | if (listeners) { 16 | updateComponentListeners(vm, listeners) 17 | } 18 | } 19 | 20 | let target: Component 21 | 22 | /*有once的时候注册一个只会触发一次的方法,没有once的时候注册一个事件方法*/ 23 | function add (event, fn, once) { 24 | if (once) { 25 | target.$once(event, fn) 26 | } else { 27 | target.$on(event, fn) 28 | } 29 | } 30 | 31 | /*销毁一个事件方法*/ 32 | function remove (event, fn) { 33 | target.$off(event, fn) 34 | } 35 | 36 | /*更新组件的监听事件*/ 37 | export function updateComponentListeners ( 38 | vm: Component, 39 | listeners: Object, 40 | oldListeners: ?Object 41 | ) { 42 | target = vm 43 | updateListeners(listeners, oldListeners || {}, add, remove, vm) 44 | } 45 | /*Github:https://github.com/answershuto*/ 46 | /*为Vue原型加入操作事件的方法*/ 47 | export function eventsMixin (Vue: Class) { 48 | const hookRE = /^hook:/ 49 | 50 | /*在vm实例上绑定事件方法*/ 51 | Vue.prototype.$on = function (event: string | Array, fn: Function): Component { 52 | const vm: Component = this 53 | 54 | /*如果是数组的时候,则递归$on,为每一个成员都绑定上方法*/ 55 | if (Array.isArray(event)) { 56 | for (let i = 0, l = event.length; i < l; i++) { 57 | this.$on(event[i], fn) 58 | } 59 | } else { 60 | (vm._events[event] || (vm._events[event] = [])).push(fn) 61 | // optimize hook:event cost by using a boolean flag marked at registration 62 | // instead of a hash lookup 63 | /*这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。*/ 64 | if (hookRE.test(event)) { 65 | vm._hasHookEvent = true 66 | } 67 | } 68 | return vm 69 | } 70 | 71 | /*注册一个只执行一次的事件方法*/ 72 | Vue.prototype.$once = function (event: string, fn: Function): Component { 73 | const vm: Component = this 74 | function on () { 75 | /*在第一次执行的时候将该事件销毁*/ 76 | vm.$off(event, on) 77 | /*执行注册的方法*/ 78 | fn.apply(vm, arguments) 79 | } 80 | on.fn = fn 81 | vm.$on(event, on) 82 | return vm 83 | } 84 | 85 | /*注销一个事件,如果不传参则注销所有事件,如果只传event名则注销该event下的所有方法*/ 86 | Vue.prototype.$off = function (event?: string | Array, fn?: Function): Component { 87 | const vm: Component = this 88 | // all 89 | /*如果不传参数则注销所有事件*/ 90 | if (!arguments.length) { 91 | vm._events = Object.create(null) 92 | return vm 93 | } 94 | // array of events 95 | /*如果event是数组则递归注销事件*/ 96 | if (Array.isArray(event)) { 97 | for (let i = 0, l = event.length; i < l; i++) { 98 | this.$off(event[i], fn) 99 | } 100 | return vm 101 | } 102 | // specific event 103 | const cbs = vm._events[event] 104 | /*本身不存在该事件则直接返回*/ 105 | if (!cbs) { 106 | return vm 107 | } 108 | /*如果只传了event参数则注销该event方法下的所有方法*/ 109 | if (arguments.length === 1) { 110 | vm._events[event] = null 111 | return vm 112 | } 113 | // specific handler 114 | /*遍历寻找对应方法并删除*/ 115 | let cb 116 | let i = cbs.length 117 | while (i--) { 118 | cb = cbs[i] 119 | if (cb === fn || cb.fn === fn) { 120 | cbs.splice(i, 1) 121 | break 122 | } 123 | } 124 | return vm 125 | } 126 | 127 | /*触发一个事件方法*/ 128 | Vue.prototype.$emit = function (event: string): Component { 129 | const vm: Component = this 130 | if (process.env.NODE_ENV !== 'production') { 131 | const lowerCaseEvent = event.toLowerCase() 132 | if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) { 133 | tip( 134 | `Event "${lowerCaseEvent}" is emitted in component ` + 135 | `${formatComponentName(vm)} but the handler is registered for "${event}". ` + 136 | `Note that HTML attributes are case-insensitive and you cannot use ` + 137 | `v-on to listen to camelCase events when using in-DOM templates. ` + 138 | `You should probably use "${hyphenate(event)}" instead of "${event}".` 139 | ) 140 | } 141 | } 142 | let cbs = vm._events[event] 143 | if (cbs) { 144 | /*将类数组的对象转换成数组*/ 145 | cbs = cbs.length > 1 ? toArray(cbs) : cbs 146 | const args = toArray(arguments, 1) 147 | /*遍历执行*/ 148 | for (let i = 0, l = cbs.length; i < l; i++) { 149 | cbs[i].apply(vm, args) 150 | } 151 | } 152 | return vm 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /core/instance/index.js: -------------------------------------------------------------------------------- 1 | import { initMixin } from './init' 2 | import { stateMixin } from './state' 3 | import { renderMixin } from './render' 4 | import { eventsMixin } from './events' 5 | import { lifecycleMixin } from './lifecycle' 6 | import { warn } from '../util/index' 7 | /*Github:https://github.com/answershuto*/ 8 | function Vue (options) { 9 | if (process.env.NODE_ENV !== 'production' && 10 | !(this instanceof Vue)) { 11 | warn('Vue is a constructor and should be called with the `new` keyword') 12 | } 13 | /*初始化*/ 14 | this._init(options) 15 | } 16 | 17 | initMixin(Vue) 18 | stateMixin(Vue) 19 | eventsMixin(Vue) 20 | lifecycleMixin(Vue) 21 | renderMixin(Vue) 22 | 23 | export default Vue 24 | -------------------------------------------------------------------------------- /core/instance/inject.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | /*Github:https://github.com/answershuto*/ 3 | import { hasSymbol } from 'core/util/env' 4 | import { warn } from '../util/index' 5 | import { defineReactive } from '../observer/index' 6 | /*Github:https://github.com/answershuto*/ 7 | export function initProvide (vm: Component) { 8 | const provide = vm.$options.provide 9 | if (provide) { 10 | vm._provided = typeof provide === 'function' 11 | ? provide.call(vm) 12 | : provide 13 | } 14 | } 15 | 16 | export function initInjections (vm: Component) { 17 | const result = resolveInject(vm.$options.inject, vm) 18 | if (result) { 19 | Object.keys(result).forEach(key => { 20 | /* istanbul ignore else */ 21 | /*为对象defineProperty上在变化时通知的属性*/ 22 | if (process.env.NODE_ENV !== 'production') { 23 | defineReactive(vm, key, result[key], () => { 24 | warn( 25 | `Avoid mutating an injected value directly since the changes will be ` + 26 | `overwritten whenever the provided component re-renders. ` + 27 | `injection being mutated: "${key}"`, 28 | vm 29 | ) 30 | }) 31 | } else { 32 | defineReactive(vm, key, result[key]) 33 | } 34 | }) 35 | } 36 | } 37 | 38 | export function resolveInject (inject: any, vm: Component): ?Object { 39 | if (inject) { 40 | // inject is :any because flow is not smart enough to figure out cached 41 | // isArray here 42 | const isArray = Array.isArray(inject) 43 | const result = Object.create(null) 44 | const keys = isArray 45 | ? inject 46 | : hasSymbol 47 | ? Reflect.ownKeys(inject) 48 | : Object.keys(inject) 49 | 50 | for (let i = 0; i < keys.length; i++) { 51 | const key = keys[i] 52 | const provideKey = isArray ? key : inject[key] 53 | let source = vm 54 | while (source) { 55 | if (source._provided && provideKey in source._provided) { 56 | result[key] = source._provided[provideKey] 57 | break 58 | } 59 | source = source.$parent 60 | } 61 | } 62 | return result 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/instance/proxy.js: -------------------------------------------------------------------------------- 1 | /* not type checking this file because flow doesn't play well with Proxy */ 2 | /*Github:https://github.com/answershuto*/ 3 | import config from 'core/config' 4 | import { warn, makeMap } from '../util/index' 5 | /*Github:https://github.com/answershuto*/ 6 | let initProxy 7 | 8 | if (process.env.NODE_ENV !== 'production') { 9 | const allowedGlobals = makeMap( 10 | 'Infinity,undefined,NaN,isFinite,isNaN,' + 11 | 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + 12 | 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + 13 | 'require' // for Webpack/Browserify 14 | ) 15 | 16 | const warnNonPresent = (target, key) => { 17 | warn( 18 | `Property or method "${key}" is not defined on the instance but ` + 19 | `referenced during render. Make sure to declare reactive data ` + 20 | `properties in the data option.`, 21 | target 22 | ) 23 | } 24 | 25 | const hasProxy = 26 | typeof Proxy !== 'undefined' && 27 | Proxy.toString().match(/native code/) 28 | 29 | if (hasProxy) { 30 | const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta') 31 | /*为config.keyCodes设置一个代理,在set赋值的时候先从isBuiltInModifier里检查,不存在再赋值*/ 32 | config.keyCodes = new Proxy(config.keyCodes, { 33 | set (target, key, value) { 34 | if (isBuiltInModifier(key)) { 35 | warn(`Avoid overwriting built-in modifier in config.keyCodes: .${key}`) 36 | return false 37 | } else { 38 | target[key] = value 39 | return true 40 | } 41 | } 42 | }) 43 | } 44 | 45 | const hasHandler = { 46 | has (target, key) { 47 | const has = key in target 48 | const isAllowed = allowedGlobals(key) || key.charAt(0) === '_' 49 | if (!has && !isAllowed) { 50 | warnNonPresent(target, key) 51 | } 52 | return has || !isAllowed 53 | } 54 | } 55 | 56 | const getHandler = { 57 | get (target, key) { 58 | if (typeof key === 'string' && !(key in target)) { 59 | warnNonPresent(target, key) 60 | } 61 | return target[key] 62 | } 63 | } 64 | 65 | initProxy = function initProxy (vm) { 66 | if (hasProxy) { 67 | // determine which proxy handler to use 68 | const options = vm.$options 69 | const handlers = options.render && options.render._withStripped 70 | ? getHandler 71 | : hasHandler 72 | vm._renderProxy = new Proxy(vm, handlers) 73 | } else { 74 | vm._renderProxy = vm 75 | } 76 | } 77 | } 78 | 79 | export { initProxy } 80 | -------------------------------------------------------------------------------- /core/instance/render-helpers/bind-object-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | import { isObject, warn, toObject } from 'core/util/index' 5 | 6 | /** 7 | * Runtime helper for merging v-bind="object" into a VNode's data. 8 | */ 9 | /*合并v-bind指令到VNode中*/ 10 | export function bindObjectProps ( 11 | data: any, 12 | tag: string, 13 | value: any, 14 | asProp?: boolean 15 | ): VNodeData { 16 | if (value) { 17 | if (!isObject(value)) { 18 | /*v-bind必须提供一个Object或者Array作为参数*/ 19 | process.env.NODE_ENV !== 'production' && warn( 20 | 'v-bind without argument expects an Object or Array value', 21 | this 22 | ) 23 | } else { 24 | if (Array.isArray(value)) { 25 | /*合并Array数组中的每一个对象到一个新的Object中*/ 26 | value = toObject(value) 27 | } 28 | let hash 29 | for (const key in value) { 30 | if (key === 'class' || key === 'style') { 31 | hash = data 32 | } else { 33 | const type = data.attrs && data.attrs.type 34 | hash = asProp || config.mustUseProp(tag, type, key) 35 | ? data.domProps || (data.domProps = {}) 36 | : data.attrs || (data.attrs = {}) 37 | } 38 | if (!(key in hash)) { 39 | hash[key] = value[key] 40 | } 41 | } 42 | } 43 | } 44 | return data 45 | } 46 | -------------------------------------------------------------------------------- /core/instance/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | 5 | /** 6 | * Runtime helper for checking keyCodes from config. 7 | */ 8 | /*从config配置中检查eventKeyCode是否存在*/ 9 | export function checkKeyCodes ( 10 | eventKeyCode: number, 11 | key: string, 12 | builtInAlias: number | Array | void 13 | ): boolean { 14 | const keyCodes = config.keyCodes[key] || builtInAlias 15 | if (Array.isArray(keyCodes)) { 16 | return keyCodes.indexOf(eventKeyCode) === -1 17 | } else { 18 | return keyCodes !== eventKeyCode 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /core/instance/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering v-for lists. 7 | */ 8 | /*处理v-for列表渲染*/ 9 | export function renderList ( 10 | val: any, 11 | render: () => VNode 12 | ): ?Array { 13 | /*根据类型循环render*/ 14 | let ret: ?Array, i, l, keys, key 15 | if (Array.isArray(val) || typeof val === 'string') { 16 | ret = new Array(val.length) 17 | for (i = 0, l = val.length; i < l; i++) { 18 | ret[i] = render(val[i], i) 19 | } 20 | } else if (typeof val === 'number') { 21 | ret = new Array(val) 22 | for (i = 0; i < val; i++) { 23 | ret[i] = render(i + 1, i) 24 | } 25 | } else if (isObject(val)) { 26 | keys = Object.keys(val) 27 | ret = new Array(keys.length) 28 | for (i = 0, l = keys.length; i < l; i++) { 29 | key = keys[i] 30 | ret[i] = render(val[key], key, i) 31 | } 32 | } 33 | return ret 34 | } 35 | -------------------------------------------------------------------------------- /core/instance/render-helpers/render-slot.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend, warn } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering 7 | */ 8 | /*处理slot的渲染*/ 9 | export function renderSlot ( 10 | name: string, 11 | fallback: ?Array, 12 | props: ?Object, 13 | bindObject: ?Object 14 | ): ?Array { 15 | const scopedSlotFn = this.$scopedSlots[name] 16 | if (scopedSlotFn) { // scoped slot 17 | props = props || {} 18 | if (bindObject) { 19 | extend(props, bindObject) 20 | } 21 | return scopedSlotFn(props) || fallback 22 | } else { 23 | const slotNodes = this.$slots[name] 24 | // warn duplicate slot usage 25 | if (slotNodes && process.env.NODE_ENV !== 'production') { 26 | slotNodes._rendered && warn( 27 | `Duplicate presence of slot "${name}" found in the same render tree ` + 28 | `- this will likely cause render errors.`, 29 | this 30 | ) 31 | slotNodes._rendered = true 32 | } 33 | return slotNodes || fallback 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /core/instance/render-helpers/render-static.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { cloneVNode, cloneVNodes } from 'core/vdom/vnode' 4 | 5 | /** 6 | * Runtime helper for rendering static trees. 7 | */ 8 | /*处理static树的渲染*/ 9 | export function renderStatic ( 10 | index: number, 11 | isInFor?: boolean 12 | ): VNode | Array { 13 | /*从_staticTrees中取出tree,如果已经被渲染则会存在*/ 14 | let tree = this._staticTrees[index] 15 | // if has already-rendered static tree and not inside v-for, 16 | // we can reuse the same tree by doing a shallow clone. 17 | /*如果已经被渲染的static tree并且它不是在v-for中,我们能够通过浅拷贝来重用同一棵树*/ 18 | if (tree && !isInFor) { 19 | return Array.isArray(tree) 20 | ? cloneVNodes(tree) 21 | : cloneVNode(tree) 22 | } 23 | // otherwise, render a fresh tree. 24 | /*否则渲染一刻新的树,同时存储在_staticTrees中,供上面检测是否已经渲染过*/ 25 | tree = this._staticTrees[index] = 26 | this.$options.staticRenderFns[index].call(this._renderProxy) 27 | markStatic(tree, `__static__${index}`, false) 28 | return tree 29 | } 30 | 31 | /** 32 | * Runtime helper for v-once. 33 | * Effectively it means marking the node as static with a unique key. 34 | */ 35 | /*处理v-once的渲染函数*/ 36 | export function markOnce ( 37 | tree: VNode | Array, 38 | index: number, 39 | key: string 40 | ) { 41 | markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) 42 | return tree 43 | } 44 | 45 | function markStatic ( 46 | tree: VNode | Array, 47 | key: string, 48 | isOnce: boolean 49 | ) { 50 | /*处理static节点*/ 51 | if (Array.isArray(tree)) { 52 | for (let i = 0; i < tree.length; i++) { 53 | if (tree[i] && typeof tree[i] !== 'string') { 54 | markStaticNode(tree[i], `${key}_${i}`, isOnce) 55 | } 56 | } 57 | } else { 58 | markStaticNode(tree, key, isOnce) 59 | } 60 | } 61 | 62 | /*处理static节点*/ 63 | function markStaticNode (node, key, isOnce) { 64 | node.isStatic = true 65 | node.key = key 66 | node.isOnce = isOnce 67 | } 68 | -------------------------------------------------------------------------------- /core/instance/render-helpers/resolve-filter.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { identity, resolveAsset } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for resolving filters 7 | */ 8 | /*处理filters*/ 9 | export function resolveFilter (id: string): Function { 10 | return resolveAsset(this.$options, 'filters', id, true) || identity 11 | } 12 | -------------------------------------------------------------------------------- /core/instance/render-helpers/resolve-slots.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Runtime helper for resolving raw children VNodes into a slot object. 5 | */ 6 | export function resolveSlots ( 7 | children: ?Array, 8 | context: ?Component 9 | ): { [key: string]: Array } { 10 | const slots = {} 11 | if (!children) { 12 | return slots 13 | } 14 | const defaultSlot = [] 15 | for (let i = 0, l = children.length; i < l; i++) { 16 | const child = children[i] 17 | // named slots should only be respected if the vnode was rendered in the 18 | // same context. 19 | if ((child.context === context || child.functionalContext === context) && 20 | child.data && child.data.slot != null) { 21 | const name = child.data.slot 22 | const slot = (slots[name] || (slots[name] = [])) 23 | if (child.tag === 'template') { 24 | slot.push.apply(slot, child.children) 25 | } else { 26 | slot.push(child) 27 | } 28 | } else { 29 | defaultSlot.push(child) 30 | } 31 | } 32 | // ignore whitespace 33 | if (!defaultSlot.every(isWhitespace)) { 34 | slots.default = defaultSlot 35 | } 36 | return slots 37 | } 38 | 39 | function isWhitespace (node: VNode): boolean { 40 | return node.isComment || node.text === ' ' 41 | } 42 | 43 | /*处理ScopedSlots*/ 44 | export function resolveScopedSlots ( 45 | fns: Array<[string, Function]> 46 | ): { [key: string]: Function } { 47 | const res = {} 48 | for (let i = 0; i < fns.length; i++) { 49 | res[fns[i][0]] = fns[i][1] 50 | } 51 | return res 52 | } 53 | -------------------------------------------------------------------------------- /core/observer/array.js: -------------------------------------------------------------------------------- 1 | /* 2 | * not type checking this file because flow doesn't play well with 3 | * dynamically accessing methods on Array prototype 4 | */ 5 | /*Github:https://github.com/answershuto*/ 6 | import { def } from '../util/index' 7 | 8 | /*取得原生数组的原型*/ 9 | const arrayProto = Array.prototype 10 | /*创建一个新的数组对象,修改该对象上的数组的七个方法,防止污染原生数组方法*/ 11 | export const arrayMethods = Object.create(arrayProto) 12 | 13 | /** 14 | * Intercept mutating methods and emit events 15 | */ 16 | /*这里重写了数组的这些方法,在保证不污染原生数组原型的情况下重写数组的这些方法,截获数组的成员发生的变化,执行原生数组操作的同时dep通知关联的所有观察者进行响应式处理*/ 17 | ;[ 18 | 'push', 19 | 'pop', 20 | 'shift', 21 | 'unshift', 22 | 'splice', 23 | 'sort', 24 | 'reverse' 25 | ] 26 | .forEach(function (method) { 27 | // cache original method 28 | /*将数组的原生方法缓存起来,后面要调用*/ 29 | const original = arrayProto[method] 30 | def(arrayMethods, method, function mutator () { 31 | // avoid leaking arguments: 32 | // http://jsperf.com/closure-with-arguments 33 | let i = arguments.length 34 | const args = new Array(i) 35 | while (i--) { 36 | args[i] = arguments[i] 37 | } 38 | /*调用原生的数组方法*/ 39 | const result = original.apply(this, args) 40 | 41 | /*数组新插入的元素需要重新进行observe才能响应式*/ 42 | const ob = this.__ob__ 43 | let inserted 44 | switch (method) { 45 | case 'push': 46 | inserted = args 47 | break 48 | case 'unshift': 49 | inserted = args 50 | break 51 | case 'splice': 52 | inserted = args.slice(2) 53 | break 54 | } 55 | if (inserted) ob.observeArray(inserted) 56 | 57 | // notify change 58 | /*dep通知所有注册的观察者进行响应式处理*/ 59 | ob.dep.notify() 60 | return result 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /core/observer/dep.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import type Watcher from './watcher' 4 | import { remove } from '../util/index' 5 | 6 | let uid = 0 7 | /*Github:https://github.com/answershuto*/ 8 | /** 9 | * A dep is an observable that can have multiple 10 | * directives subscribing to it. 11 | */ 12 | export default class Dep { 13 | static target: ?Watcher; 14 | id: number; 15 | subs: Array; 16 | 17 | constructor () { 18 | this.id = uid++ 19 | this.subs = [] 20 | } 21 | 22 | /*添加一个观察者对象*/ 23 | addSub (sub: Watcher) { 24 | this.subs.push(sub) 25 | } 26 | 27 | /*移除一个观察者对象*/ 28 | removeSub (sub: Watcher) { 29 | remove(this.subs, sub) 30 | } 31 | 32 | /*依赖收集,当存在Dep.target的时候添加观察者对象*/ 33 | depend () { 34 | if (Dep.target) { 35 | Dep.target.addDep(this) 36 | } 37 | } 38 | 39 | /*通知所有订阅者*/ 40 | notify () { 41 | // stabilize the subscriber list first 42 | const subs = this.subs.slice() 43 | for (let i = 0, l = subs.length; i < l; i++) { 44 | subs[i].update() 45 | } 46 | } 47 | } 48 | 49 | // the current target watcher being evaluated. 50 | // this is globally unique because there could be only one 51 | // watcher being evaluated at any time. 52 | /*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/ 53 | Dep.target = null 54 | const targetStack = [] 55 | 56 | /*将watcher观察者实例设置给Dep.target,用以依赖收集。同时将该实例存入target栈中*/ 57 | export function pushTarget (_target: Watcher) { 58 | if (Dep.target) targetStack.push(Dep.target) 59 | Dep.target = _target 60 | } 61 | 62 | /*将观察者实例从target栈中取出并设置给Dep.target*/ 63 | export function popTarget () { 64 | Dep.target = targetStack.pop() 65 | } 66 | -------------------------------------------------------------------------------- /core/util/debug.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { noop } from 'shared/util' 5 | 6 | export let warn = noop 7 | export let tip = noop 8 | export let formatComponentName: Function = (null: any) // work around flow check 9 | 10 | if (process.env.NODE_ENV !== 'production') { 11 | const hasConsole = typeof console !== 'undefined' 12 | const classifyRE = /(?:^|[-_])(\w)/g 13 | const classify = str => str 14 | .replace(classifyRE, c => c.toUpperCase()) 15 | .replace(/[-_]/g, '') 16 | 17 | warn = (msg, vm) => { 18 | if (hasConsole && (!config.silent)) { 19 | console.error(`[Vue warn]: ${msg}` + ( 20 | vm ? generateComponentTrace(vm) : '' 21 | )) 22 | } 23 | } 24 | 25 | tip = (msg, vm) => { 26 | if (hasConsole && (!config.silent)) { 27 | console.warn(`[Vue tip]: ${msg}` + ( 28 | vm ? generateComponentTrace(vm) : '' 29 | )) 30 | } 31 | } 32 | 33 | /*格式化组件名*/ 34 | formatComponentName = (vm, includeFile) => { 35 | if (vm.$root === vm) { 36 | return '' 37 | } 38 | let name = typeof vm === 'string' 39 | ? vm 40 | : typeof vm === 'function' && vm.options 41 | ? vm.options.name 42 | : vm._isVue 43 | ? vm.$options.name || vm.$options._componentTag 44 | : vm.name 45 | 46 | const file = vm._isVue && vm.$options.__file 47 | if (!name && file) { 48 | const match = file.match(/([^/\\]+)\.vue$/) 49 | name = match && match[1] 50 | } 51 | 52 | return ( 53 | (name ? `<${classify(name)}>` : ``) + 54 | (file && includeFile !== false ? ` at ${file}` : '') 55 | ) 56 | } 57 | 58 | const repeat = (str, n) => { 59 | let res = '' 60 | while (n) { 61 | if (n % 2 === 1) res += str 62 | if (n > 1) str += str 63 | n >>= 1 64 | } 65 | return res 66 | } 67 | 68 | const generateComponentTrace = vm => { 69 | if (vm._isVue && vm.$parent) { 70 | const tree = [] 71 | let currentRecursiveSequence = 0 72 | while (vm) { 73 | if (tree.length > 0) { 74 | const last = tree[tree.length - 1] 75 | if (last.constructor === vm.constructor) { 76 | currentRecursiveSequence++ 77 | vm = vm.$parent 78 | continue 79 | } else if (currentRecursiveSequence > 0) { 80 | tree[tree.length - 1] = [last, currentRecursiveSequence] 81 | currentRecursiveSequence = 0 82 | } 83 | } 84 | tree.push(vm) 85 | vm = vm.$parent 86 | } 87 | return '\n\nfound in\n\n' + tree 88 | .map((vm, i) => `${ 89 | i === 0 ? '---> ' : repeat(' ', 5 + i * 2) 90 | }${ 91 | Array.isArray(vm) 92 | ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)` 93 | : formatComponentName(vm) 94 | }`) 95 | .join('\n') 96 | } else { 97 | return `\n\n(found in ${formatComponentName(vm)})` 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /core/util/error.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { warn } from './debug' 5 | import { inBrowser } from './env' 6 | 7 | export function handleError (err: Error, vm: any, info: string) { 8 | if (config.errorHandler) { 9 | config.errorHandler.call(null, err, vm, info) 10 | } else { 11 | if (process.env.NODE_ENV !== 'production') { 12 | warn(`Error in ${info}: "${err.toString()}"`, vm) 13 | } 14 | /* istanbul ignore else */ 15 | if (inBrowser && typeof console !== 'undefined') { 16 | console.error(err) 17 | } else { 18 | throw err 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /core/util/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export * from 'shared/util' 4 | export * from './lang' 5 | export * from './env' 6 | export * from './options' 7 | export * from './debug' 8 | export * from './props' 9 | export * from './error' 10 | export { defineReactive } from '../observer/index' 11 | -------------------------------------------------------------------------------- /core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export const emptyObject = Object.freeze({}) 4 | 5 | /** 6 | * Check if a string starts with $ or _ 7 | */ 8 | export function isReserved (str: string): boolean { 9 | const c = (str + '').charCodeAt(0) 10 | return c === 0x24 || c === 0x5F 11 | } 12 | 13 | /** 14 | * Define a property. 15 | */ 16 | export function def (obj: Object, key: string, val: any, enumerable?: boolean) { 17 | Object.defineProperty(obj, key, { 18 | value: val, 19 | enumerable: !!enumerable, 20 | writable: true, 21 | configurable: true 22 | }) 23 | } 24 | 25 | /** 26 | * Parse simple path. 27 | */ 28 | const bailRE = /[^\w.$]/ 29 | export function parsePath (path: string): any { 30 | if (bailRE.test(path)) { 31 | return 32 | } 33 | const segments = path.split('.') 34 | return function (obj) { 35 | for (let i = 0; i < segments.length; i++) { 36 | if (!obj) return 37 | obj = obj[segments[i]] 38 | } 39 | return obj 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /core/util/perf.js: -------------------------------------------------------------------------------- 1 | import { inBrowser } from './env' 2 | 3 | export let mark 4 | export let measure 5 | 6 | if (process.env.NODE_ENV !== 'production') { 7 | const perf = inBrowser && window.performance 8 | /* istanbul ignore if */ 9 | if ( 10 | perf && 11 | perf.mark && 12 | perf.measure && 13 | perf.clearMarks && 14 | perf.clearMeasures 15 | ) { 16 | mark = tag => perf.mark(tag) 17 | measure = (name, startTag, endTag) => { 18 | perf.measure(name, startTag, endTag) 19 | perf.clearMarks(startTag) 20 | perf.clearMarks(endTag) 21 | perf.clearMeasures(name) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /core/vdom/create-element.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import VNode, { createEmptyVNode } from './vnode' 5 | import { createComponent } from './create-component' 6 | 7 | import { 8 | warn, 9 | isDef, 10 | isUndef, 11 | isTrue, 12 | isPrimitive, 13 | resolveAsset 14 | } from '../util/index' 15 | 16 | import { 17 | normalizeChildren, 18 | simpleNormalizeChildren 19 | } from './helpers/index' 20 | 21 | const SIMPLE_NORMALIZE = 1 22 | const ALWAYS_NORMALIZE = 2 23 | 24 | // wrapper function for providing a more flexible interface 25 | // without getting yelled at by flow 26 | export function createElement ( 27 | context: Component, 28 | tag: any, 29 | data: any, 30 | children: any, 31 | normalizationType: any, 32 | alwaysNormalize: boolean 33 | ): VNode { 34 | /*兼容不传data的情况*/ 35 | if (Array.isArray(data) || isPrimitive(data)) { 36 | normalizationType = children 37 | children = data 38 | data = undefined 39 | } 40 | /*如果alwaysNormalize为true,则normalizationType标记为ALWAYS_NORMALIZE*/ 41 | if (isTrue(alwaysNormalize)) { 42 | normalizationType = ALWAYS_NORMALIZE 43 | } 44 | /*创建虚拟节点*/ 45 | return _createElement(context, tag, data, children, normalizationType) 46 | } 47 | 48 | /*创建VNode节点*/ 49 | export function _createElement ( 50 | context: Component, 51 | tag?: string | Class | Function | Object, 52 | data?: VNodeData, 53 | children?: any, 54 | normalizationType?: number 55 | ): VNode { 56 | /* 57 | 如果data未定义(undefined或者null)或者是data的__ob__已经定义(代表已经被observed,上面绑定了Oberver对象), 58 | https://cn.vuejs.org/v2/guide/render-function.html#约束 59 | 那么创建一个空节点 60 | */ 61 | if (isDef(data) && isDef((data: any).__ob__)) { 62 | process.env.NODE_ENV !== 'production' && warn( 63 | `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` + 64 | 'Always create fresh vnode data objects in each render!', 65 | context 66 | ) 67 | return createEmptyVNode() 68 | } 69 | /*如果tag不存在也是创建一个空节点*/ 70 | if (!tag) { 71 | // in case of component :is set to falsy value 72 | return createEmptyVNode() 73 | } 74 | // support single function children as default scoped slot 75 | /*默认默认作用域插槽*/ 76 | if (Array.isArray(children) && 77 | typeof children[0] === 'function') { 78 | data = data || {} 79 | data.scopedSlots = { default: children[0] } 80 | children.length = 0 81 | } 82 | if (normalizationType === ALWAYS_NORMALIZE) { 83 | children = normalizeChildren(children) 84 | } else if (normalizationType === SIMPLE_NORMALIZE) { 85 | children = simpleNormalizeChildren(children) 86 | } 87 | let vnode, ns 88 | if (typeof tag === 'string') { 89 | let Ctor 90 | /*获取tag的名字空间*/ 91 | ns = config.getTagNamespace(tag) 92 | /*判断是否是保留的标签*/ 93 | if (config.isReservedTag(tag)) { 94 | // platform built-in elements 95 | /*如果是保留的标签则创建一个相应节点*/ 96 | vnode = new VNode( 97 | config.parsePlatformTagName(tag), data, children, 98 | undefined, undefined, context 99 | ) 100 | } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) { 101 | // component 102 | /*从vm实例的option的components中寻找该tag,存在则就是一个组件,创建相应节点,Ctor为组件的构造类*/ 103 | vnode = createComponent(Ctor, data, context, children, tag) 104 | } else { 105 | // unknown or unlisted namespaced elements 106 | // check at runtime because it may get assigned a namespace when its 107 | // parent normalizes children 108 | /*未知的元素,在运行时检查,因为父组件可能在序列化子组件的时候分配一个名字空间*/ 109 | vnode = new VNode( 110 | tag, data, children, 111 | undefined, undefined, context 112 | ) 113 | } 114 | } else { 115 | // direct component options / constructor 116 | /*tag不是字符串的时候则是组件的构造类*/ 117 | vnode = createComponent(tag, data, context, children) 118 | } 119 | if (isDef(vnode)) { 120 | /*如果有名字空间,则递归所有子节点应用该名字空间*/ 121 | if (ns) applyNS(vnode, ns) 122 | return vnode 123 | } else { 124 | /*如果vnode没有成功创建则创建空节点*/ 125 | return createEmptyVNode() 126 | } 127 | } 128 | /*Github:https://github.com/answershuto*/ 129 | function applyNS (vnode, ns) { 130 | vnode.ns = ns 131 | if (vnode.tag === 'foreignObject') { 132 | // use default namespace inside foreignObject 133 | return 134 | } 135 | if (isDef(vnode.children)) { 136 | for (let i = 0, l = vnode.children.length; i < l; i++) { 137 | const child = vnode.children[i] 138 | if (isDef(child.tag) && isUndef(child.ns)) { 139 | applyNS(child, ns) 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /core/vdom/create-functional-component.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode from './vnode' 4 | import { createElement } from './create-element' 5 | import { resolveInject } from '../instance/inject' 6 | import { resolveSlots } from '../instance/render-helpers/resolve-slots' 7 | 8 | import { 9 | isDef, 10 | camelize, 11 | validateProp 12 | } from '../util/index' 13 | 14 | export function createFunctionalComponent ( 15 | Ctor: Class, 16 | propsData: ?Object, 17 | data: VNodeData, 18 | context: Component, 19 | children: ?Array 20 | ): VNode | void { 21 | const props = {} 22 | const propOptions = Ctor.options.props 23 | if (isDef(propOptions)) { 24 | for (const key in propOptions) { 25 | props[key] = validateProp(key, propOptions, propsData || {}) 26 | } 27 | } else { 28 | if (isDef(data.attrs)) mergeProps(props, data.attrs) 29 | if (isDef(data.props)) mergeProps(props, data.props) 30 | } 31 | // ensure the createElement function in functional components 32 | // gets a unique context - this is necessary for correct named slot check 33 | const _context = Object.create(context) 34 | const h = (a, b, c, d) => createElement(_context, a, b, c, d, true) 35 | const vnode = Ctor.options.render.call(null, h, { 36 | data, 37 | props, 38 | children, 39 | parent: context, 40 | listeners: data.on || {}, 41 | injections: resolveInject(Ctor.options.inject, context), 42 | slots: () => resolveSlots(children, context) 43 | }) 44 | if (vnode instanceof VNode) { 45 | vnode.functionalContext = context 46 | if (data.slot) { 47 | (vnode.data || (vnode.data = {})).slot = data.slot 48 | } 49 | } 50 | return vnode 51 | } 52 | 53 | function mergeProps (to, from) { 54 | for (const key in from) { 55 | to[camelize(key)] = from[key] 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /core/vdom/helpers/extract-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { 4 | tip, 5 | hasOwn, 6 | isDef, 7 | isUndef, 8 | hyphenate, 9 | formatComponentName 10 | } from 'core/util/index' 11 | 12 | export function extractPropsFromVNodeData ( 13 | data: VNodeData, 14 | Ctor: Class, 15 | tag?: string 16 | ): ?Object { 17 | // we are only extracting raw values here. 18 | // validation and default values are handled in the child 19 | // component itself. 20 | const propOptions = Ctor.options.props 21 | if (isUndef(propOptions)) { 22 | return 23 | } 24 | const res = {} 25 | const { attrs, props } = data 26 | if (isDef(attrs) || isDef(props)) { 27 | for (const key in propOptions) { 28 | const altKey = hyphenate(key) 29 | if (process.env.NODE_ENV !== 'production') { 30 | const keyInLowerCase = key.toLowerCase() 31 | if ( 32 | key !== keyInLowerCase && 33 | attrs && hasOwn(attrs, keyInLowerCase) 34 | ) { 35 | tip( 36 | `Prop "${keyInLowerCase}" is passed to component ` + 37 | `${formatComponentName(tag || Ctor)}, but the declared prop name is` + 38 | ` "${key}". ` + 39 | `Note that HTML attributes are case-insensitive and camelCased ` + 40 | `props need to use their kebab-case equivalents when using in-DOM ` + 41 | `templates. You should probably use "${altKey}" instead of "${key}".` 42 | ) 43 | } 44 | } 45 | checkProp(res, props, key, altKey, true) || 46 | checkProp(res, attrs, key, altKey, false) 47 | } 48 | } 49 | return res 50 | } 51 | 52 | function checkProp ( 53 | res: Object, 54 | hash: ?Object, 55 | key: string, 56 | altKey: string, 57 | preserve: boolean 58 | ): boolean { 59 | if (isDef(hash)) { 60 | if (hasOwn(hash, key)) { 61 | res[key] = hash[key] 62 | if (!preserve) { 63 | delete hash[key] 64 | } 65 | return true 66 | } else if (hasOwn(hash, altKey)) { 67 | res[key] = hash[altKey] 68 | if (!preserve) { 69 | delete hash[altKey] 70 | } 71 | return true 72 | } 73 | } 74 | return false 75 | } 76 | -------------------------------------------------------------------------------- /core/vdom/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isDef } from 'shared/util' 4 | 5 | /* 获取第一个子组件 */ 6 | export function getFirstComponentChild (children: ?Array): ?VNode { 7 | if (Array.isArray(children)) { 8 | for (let i = 0; i < children.length; i++) { 9 | const c = children[i] 10 | if (isDef(c) && isDef(c.componentOptions)) { 11 | return c 12 | } 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/vdom/helpers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | export * from './merge-hook' 4 | export * from './extract-props' 5 | export * from './update-listeners' 6 | export * from './normalize-children' 7 | export * from './resolve-async-component' 8 | export * from './get-first-component-child' 9 | -------------------------------------------------------------------------------- /core/vdom/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { createFnInvoker } from './update-listeners' 4 | import { remove, isDef, isUndef, isTrue } from 'shared/util' 5 | 6 | export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { 7 | let invoker 8 | const oldHook = def[hookKey] 9 | 10 | function wrappedHook () { 11 | hook.apply(this, arguments) 12 | // important: remove merged hook to ensure it's called only once 13 | // and prevent memory leak 14 | remove(invoker.fns, wrappedHook) 15 | } 16 | 17 | if (isUndef(oldHook)) { 18 | // no existing hook 19 | invoker = createFnInvoker([wrappedHook]) 20 | } else { 21 | /* istanbul ignore if */ 22 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 23 | // already a merged invoker 24 | invoker = oldHook 25 | invoker.fns.push(wrappedHook) 26 | } else { 27 | // existing plain hook 28 | invoker = createFnInvoker([oldHook, wrappedHook]) 29 | } 30 | } 31 | 32 | invoker.merged = true 33 | def[hookKey] = invoker 34 | } 35 | -------------------------------------------------------------------------------- /core/vdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode, { createTextVNode } from 'core/vdom/vnode' 4 | import { isDef, isUndef, isPrimitive } from 'shared/util' 5 | 6 | // The template compiler attempts to minimize the need for normalization by 7 | // statically analyzing the template at compile time. 8 | // 9 | // For plain HTML markup, normalization can be completely skipped because the 10 | // generated render function is guaranteed to return Array. There are 11 | // two cases where extra normalization is needed: 12 | 13 | // 1. When the children contains components - because a functional component 14 | // may return an Array instead of a single root. In this case, just a simple 15 | // normalization is needed - if any child is an Array, we flatten the whole 16 | // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep 17 | // because functional components already normalize their own children. 18 | export function simpleNormalizeChildren (children: any) { 19 | for (let i = 0; i < children.length; i++) { 20 | if (Array.isArray(children[i])) { 21 | return Array.prototype.concat.apply([], children) 22 | } 23 | } 24 | return children 25 | } 26 | 27 | // 2. When the children contains constructs that always generated nested Arrays, 28 | // e.g.