├── .vscode └── settings.json ├── README.md ├── compiler ├── codegen │ ├── events.js │ └── index.js ├── create-compiler.js ├── directives │ ├── bind.js │ ├── index.js │ ├── model.js │ └── on.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 └── to-function.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-listeners.js │ │ ├── bind-object-props.js │ │ ├── check-keycodes.js │ │ ├── index.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 │ ├── traverse.js │ └── watcher.js ├── util │ ├── debug.js │ ├── env.js │ ├── error.js │ ├── index.js │ ├── lang.js │ ├── next-tick.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 │ ├── is-async-placeholder.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 ├── platforms ├── web │ ├── compiler │ │ ├── directives │ │ │ ├── html.js │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── text.js │ │ ├── index.js │ │ ├── modules │ │ │ ├── class.js │ │ │ ├── index.js │ │ │ ├── model.js │ │ │ └── style.js │ │ ├── options.js │ │ └── util.js │ ├── entry-compiler.js │ ├── entry-runtime-with-compiler.js │ ├── entry-runtime.js │ ├── entry-server-basic-renderer.js │ ├── entry-server-renderer.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 │ │ ├── compiler.js │ │ ├── directives │ │ │ ├── index.js │ │ │ ├── model.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 │ ├── directives │ │ ├── index.js │ │ └── model.js │ ├── index.js │ └── modules │ │ ├── append.js │ │ ├── class.js │ │ ├── index.js │ │ ├── props.js │ │ ├── recycle-list │ │ ├── component-root.js │ │ ├── component.js │ │ ├── index.js │ │ ├── recycle-list.js │ │ ├── text.js │ │ ├── v-bind.js │ │ ├── v-for.js │ │ ├── v-if.js │ │ ├── v-on.js │ │ └── v-once.js │ │ └── style.js │ ├── entry-compiler.js │ ├── entry-framework.js │ ├── entry-runtime-factory.js │ ├── runtime │ ├── components │ │ ├── index.js │ │ ├── richtext.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 │ ├── recycle-list │ │ ├── render-component-template.js │ │ └── virtual-component.js │ └── text-node.js │ └── util │ ├── element.js │ ├── index.js │ └── parser.js ├── server ├── bundle-renderer │ ├── create-bundle-renderer.js │ ├── create-bundle-runner.js │ └── source-map-support.js ├── create-basic-renderer.js ├── create-renderer.js ├── optimizing-compiler │ ├── codegen.js │ ├── index.js │ ├── modules.js │ ├── optimizer.js │ └── runtime-helpers.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 /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false 3 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Vue源码解析 2 | 3 | > 通过注释的方式解析vue的每一行代码,帮助你更容易理解这个神奇的框架 4 | 5 | ### 关于本项目 6 | 7 | - 本项目中代码截取自`vue@2.5.17`仓库中`src`目录 8 | - 所有的解析都将通过注释的方式写入这个仓库的每一个代码文件中, 其中, 中文的注释为解析内容, 其他注释为原代码注释 9 | - 阅读此项目需具备一定的JavaScript基础, 过于基础的代码并不会详细解析 10 | - 人力有时尽, vue完整仓库代码过于庞大, 欢迎其他人通过PR参与贡献和完善这个项目 11 | 12 | #### 更新进度 13 | 14 | ``` 15 | ./core 16 | ├── components ----------------------------------- 已完成 17 | │   ├── index.js --------------------------------- 已完成 18 | │   └── keep-alive.js ---------------------------- 已完成 19 | ├── config.js ------------------------------------ 已完成 20 | ├── global-api ----------------------------------- 已完成 21 | │   ├── assets.js -------------------------------- 已完成 22 | │   ├── extend.js -------------------------------- 已完成 23 | │   ├── index.js --------------------------------- 已完成 24 | │   ├── mixin.js --------------------------------- 已完成 25 | │   └── use.js ----------------------------------- 已完成 26 | ├── index.js ------------------------------------- 已完成 27 | ├── instance 28 | │   ├── events.js 29 | │   ├── index.js --------------------------------- 已完成 30 | │   ├── init.js 31 | │   ├── inject.js 32 | │   ├── lifecycle.js 33 | │   ├── proxy.js 34 | │   ├── render-helpers 35 | │   │   ├── bind-object-listeners.js 36 | │   │   ├── bind-object-props.js -------------------- 已完成 37 | │   │   ├── check-keycodes.js ----------------------- 已完成 38 | │   │   ├── index.js 39 | │   │   ├── render-list.js -------------------------- 已完成 40 | │   │   ├── render-slot.js 41 | │   │   ├── render-static.js 42 | │   │   ├── resolve-filter.js 43 | │   │   └── resolve-slots.js 44 | │   ├── render.js 45 | │   └── state.js ------------------------------------ 进行中 46 | ├── observer 47 | │   ├── array.js ------------------------------------ 已完成 48 | │   ├── dep.js -------------------------------------- 已完成 49 | │   ├── index.js ------------------------------------ 已完成 50 | │   ├── scheduler.js 51 | │   ├── traverse.js --------------------------------- 已完成 52 | │   └── watcher.js 53 | ├── util -------------------------------------------- 已完成 54 | │   ├── debug.js ------------------------------------ 已完成 55 | │   ├── env.js ------------------------------------ 已完成 56 | │   ├── error.js ------------------------------------ 已完成 57 | │   ├── index.js ------------------------------------ 已完成 58 | │   ├── lang.js ------------------------------------- 已完成 59 | │   ├── next-tick.js -------------------------------- 已完成 60 | │   ├── options.js ---------------------------------- 已完成 61 | │   ├── perf.js ------------------------------------- 已完成 62 | │   └── props.js ------------------------------------ 已完成 63 | └── vdom 64 | ├── create-component.js 65 | ├── create-element.js --------------------------- 已完成 66 | ├── create-functional-component.js 67 | ├── helpers 68 | │   ├── extract-props.js ------------------------ 已完成 69 | │   ├── get-first-component-child.js ------------ 已完成 70 | │   ├── index.js -------------------------------- 已完成 71 | │   ├── is-async-placeholder.js ----------------- 已完成 72 | │   ├── merge-hook.js --------------------------- 已完成 73 | │   ├── normalize-children.js ------------------- 已完成 74 | │   ├── resolve-async-component.js 75 | │   └── update-listeners.js --------------------- 已完成 76 | ├── modules 77 | │   ├── directives.js ---------------------------- 已完成 78 | │   ├── index.js --------------------------------- 已完成 79 | │   └── ref.js ----------------------------------- 已完成 80 | ├── patch.js ------------------------------------- 进行中 81 | └── vnode.js ------------------------------------- 已完成 82 | ``` -------------------------------------------------------------------------------- /compiler/create-compiler.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend } from 'shared/util' 4 | import { detectErrors } from './error-detector' 5 | import { createCompileToFunctionFn } from './to-function' 6 | 7 | export function createCompilerCreator (baseCompile: Function): Function { 8 | return function createCompiler (baseOptions: CompilerOptions) { 9 | function compile ( 10 | template: string, 11 | options?: CompilerOptions 12 | ): CompiledResult { 13 | /** 14 | * 继承baseOptions 并返回一个新对象 15 | * 个人理解主要是用于拷贝一个新对象 16 | */ 17 | const finalOptions = Object.create(baseOptions) 18 | const errors = [] 19 | const tips = [] 20 | /** 21 | * 注册警告方法 用于收集error或tip 22 | * 允许在不同环境中自定义警告 23 | */ 24 | finalOptions.warn = (msg, tip) => { 25 | (tip ? tips : errors).push(msg) 26 | } 27 | 28 | if (options) { 29 | // merge custom modules 30 | // 合并modules 31 | if (options.modules) { 32 | finalOptions.modules = 33 | (baseOptions.modules || []).concat(options.modules) 34 | } 35 | // merge custom directives 36 | // 合并directives, 同key会覆盖__proto__上的key 37 | if (options.directives) { 38 | finalOptions.directives = extend( 39 | Object.create(baseOptions.directives || null), 40 | options.directives 41 | ) 42 | } 43 | // copy other options 44 | // 浅拷贝options上其他的key 45 | for (const key in options) { 46 | if (key !== 'modules' && key !== 'directives') { 47 | finalOptions[key] = options[key] 48 | } 49 | } 50 | } 51 | 52 | const compiled = baseCompile(template, finalOptions) 53 | if (process.env.NODE_ENV !== 'production') { 54 | errors.push.apply(errors, detectErrors(compiled.ast)) 55 | } 56 | compiled.errors = errors 57 | compiled.tips = tips 58 | return compiled 59 | } 60 | 61 | return { 62 | compile, 63 | compileToFunctions: createCompileToFunctionFn(compile) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /compiler/directives/bind.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 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' : 'false' 7 | }${ 8 | dir.modifiers && dir.modifiers.sync ? ',true' : '' 9 | })` 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /compiler/directives/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import on from './on' 4 | import bind from './bind' 5 | import { noop } from 'shared/util' 6 | 7 | export default { 8 | on, 9 | bind, 10 | cloak: noop 11 | } 12 | -------------------------------------------------------------------------------- /compiler/directives/on.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn } from 'core/util/index' 4 | 5 | export default function on (el: ASTElement, dir: ASTDirective) { 6 | if (process.env.NODE_ENV !== 'production' && dir.modifiers) { 7 | warn(`v-on without argument does not support modifiers.`) 8 | } 9 | el.wrapListeners = (code: string) => `_g(${code},${dir.value})` 10 | } 11 | -------------------------------------------------------------------------------- /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 | // strip strings in expressions 19 | const stripStringRE = /'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"|`(?:[^`\\]|\\.)*\$\{|\}(?:[^`\\]|\\.)*`|`(?:[^`\\]|\\.)*`/g 20 | 21 | // detect problematic expressions in a template 22 | export function detectErrors (ast: ?ASTNode): Array { 23 | const errors: Array = [] 24 | if (ast) { 25 | checkNode(ast, errors) 26 | } 27 | return errors 28 | } 29 | 30 | function checkNode (node: ASTNode, errors: Array) { 31 | if (node.type === 1) { 32 | for (const name in node.attrsMap) { 33 | if (dirRE.test(name)) { 34 | const value = node.attrsMap[name] 35 | if (value) { 36 | if (name === 'v-for') { 37 | checkFor(node, `v-for="${value}"`, errors) 38 | } else if (onRE.test(name)) { 39 | checkEvent(value, `${name}="${value}"`, errors) 40 | } else { 41 | checkExpression(value, `${name}="${value}"`, errors) 42 | } 43 | } 44 | } 45 | } 46 | if (node.children) { 47 | for (let i = 0; i < node.children.length; i++) { 48 | checkNode(node.children[i], errors) 49 | } 50 | } 51 | } else if (node.type === 2) { 52 | checkExpression(node.expression, node.text, errors) 53 | } 54 | } 55 | 56 | function checkEvent (exp: string, text: string, errors: Array) { 57 | const stipped = exp.replace(stripStringRE, '') 58 | const keywordMatch: any = stipped.match(unaryOperatorsRE) 59 | if (keywordMatch && stipped.charAt(keywordMatch.index - 1) !== '$') { 60 | errors.push( 61 | `avoid using JavaScript unary operator as property name: ` + 62 | `"${keywordMatch[0]}" in expression ${text.trim()}` 63 | ) 64 | } 65 | checkExpression(exp, text, errors) 66 | } 67 | 68 | function checkFor (node: ASTElement, text: string, errors: Array) { 69 | checkExpression(node.for || '', text, errors) 70 | checkIdentifier(node.alias, 'v-for alias', text, errors) 71 | checkIdentifier(node.iterator1, 'v-for iterator', text, errors) 72 | checkIdentifier(node.iterator2, 'v-for iterator', text, errors) 73 | } 74 | 75 | function checkIdentifier ( 76 | ident: ?string, 77 | type: string, 78 | text: string, 79 | errors: Array 80 | ) { 81 | if (typeof ident === 'string') { 82 | try { 83 | new Function(`var ${ident}=_`) 84 | } catch (e) { 85 | errors.push(`invalid ${type} "${ident}" in expression: ${text.trim()}`) 86 | } 87 | } 88 | } 89 | 90 | function checkExpression (exp: string, text: string, errors: Array) { 91 | try { 92 | new Function(`return ${exp}`) 93 | } catch (e) { 94 | const keywordMatch = exp.replace(stripStringRE, '').match(prohibitedKeywordRE) 95 | if (keywordMatch) { 96 | errors.push( 97 | `avoid using JavaScript keyword as property name: ` + 98 | `"${keywordMatch[0]}"\n Raw expression: ${text.trim()}` 99 | ) 100 | } else { 101 | errors.push( 102 | `invalid expression: ${e.message} in\n\n` + 103 | ` ${exp}\n\n` + 104 | ` Raw expression: ${text.trim()}\n` 105 | ) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /compiler/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { parse } from './parser/index' 4 | import { optimize } from './optimizer' 5 | import { generate } from './codegen/index' 6 | import { createCompilerCreator } from './create-compiler' 7 | 8 | // `createCompilerCreator` allows creating compilers that use alternative 9 | // parser/optimizer/codegen, e.g the SSR optimizing compiler. 10 | // Here we just export a default compiler using the default parts. 11 | export const createCompiler = createCompilerCreator(function baseCompile ( 12 | template: string, 13 | options: CompilerOptions 14 | ): CompiledResult { 15 | const ast = parse(template.trim(), options) 16 | if (options.optimize !== false) { 17 | optimize(ast, options) 18 | } 19 | const code = generate(ast, options) 20 | return { 21 | ast, 22 | render: code.render, 23 | staticRenderFns: code.staticRenderFns 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /compiler/parser/entity-decoder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | let decoder 4 | 5 | export default { 6 | decode (html: string): string { 7 | decoder = decoder || document.createElement('div') 8 | decoder.innerHTML = html 9 | return decoder.textContent 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /compiler/parser/filter-parser.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | const validDivisionCharRE = /[\w).+\-_$\]]/ 4 | 5 | export function parseFilters (exp: string): string { 6 | let inSingle = false 7 | let inDouble = false 8 | let inTemplateString = false 9 | let inRegex = false 10 | let curly = 0 11 | let square = 0 12 | let paren = 0 13 | let lastFilterIndex = 0 14 | let c, prev, i, expression, filters 15 | 16 | for (i = 0; i < exp.length; i++) { 17 | prev = c 18 | c = exp.charCodeAt(i) 19 | if (inSingle) { 20 | if (c === 0x27 && prev !== 0x5C) inSingle = false 21 | } else if (inDouble) { 22 | if (c === 0x22 && prev !== 0x5C) inDouble = false 23 | } else if (inTemplateString) { 24 | if (c === 0x60 && prev !== 0x5C) inTemplateString = false 25 | } else if (inRegex) { 26 | if (c === 0x2f && prev !== 0x5C) inRegex = false 27 | } else if ( 28 | // ascii对照表查: https://www.litefeel.com/tools/ascii.php 29 | /** 30 | * 匹配到 | 并且只有一个时 防止或 || 31 | * 且不是在 () [] {} 里面 32 | */ 33 | c === 0x7C && // pipe 34 | exp.charCodeAt(i + 1) !== 0x7C && 35 | exp.charCodeAt(i - 1) !== 0x7C && 36 | !curly && !square && !paren 37 | ) { 38 | if (expression === undefined) { 39 | // first filter, end of expression 40 | lastFilterIndex = i + 1 41 | expression = exp.slice(0, i).trim() 42 | } else { 43 | // 处理有多个过滤器 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 | // 保存filter 数组 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 | // 包装过滤器解析结果 87 | expression = wrapFilter(expression, filters[i]) 88 | } 89 | } 90 | 91 | return expression 92 | } 93 | 94 | function wrapFilter (exp: string, filter: string): string { 95 | const i = filter.indexOf('(') 96 | if (i < 0) { 97 | // _f: resolveFilter 98 | return `_f("${filter}")(${exp})` 99 | } else { 100 | const name = filter.slice(0, i) 101 | const args = filter.slice(i + 1) 102 | return `_f("${name}")(${exp}${args !== ')' ? ',' + args : args}` 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /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 | // 匹配默认分隔符 8 | const regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g 9 | // 缓存自定义分隔符 10 | const buildRegex = cached(delimiters => { 11 | const open = delimiters[0].replace(regexEscapeRE, '\\$&') 12 | const close = delimiters[1].replace(regexEscapeRE, '\\$&') 13 | return new RegExp(open + '((?:.|\\n)+?)' + close, 'g') 14 | }) 15 | 16 | type TextParseResult = { 17 | expression: string, 18 | tokens: Array 19 | } 20 | /** 21 | * 对动态文本内容进行解析 22 | * 例子: {{ text }} 23 | * 返回表达式和处理数组并做了filter预处理 24 | */ 25 | export function parseText ( 26 | text: string, 27 | delimiters?: [string, string] 28 | ): TextParseResult | void { 29 | /** 30 | * 查看有没有自定义分隔符 31 | * 有则缓存 32 | * 没有则用默认的: {{}} 33 | */ 34 | const tagRE = delimiters ? buildRegex(delimiters) : defaultTagRE 35 | // 没有匹配文本则直接返回 36 | if (!tagRE.test(text)) { 37 | return 38 | } 39 | const tokens = [] 40 | const rawTokens = [] 41 | let lastIndex = tagRE.lastIndex = 0 42 | let match, index, tokenValue 43 | // 循环匹配本文中的表达式 44 | while ((match = tagRE.exec(text))) { 45 | /** 46 | * 记录开始位置 47 | * exec:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec 48 | */ 49 | index = match.index 50 | // push text token 51 | if (index > lastIndex) { 52 | // 简化写法可读性一般 53 | rawTokens.push(tokenValue = text.slice(lastIndex, index)) 54 | tokens.push(JSON.stringify(tokenValue)) 55 | } 56 | // tag token 57 | /** 58 | * 对文本进行filter预处理 59 | */ 60 | const exp = parseFilters(match[1].trim()) 61 | tokens.push(`_s(${exp})`) 62 | rawTokens.push({ '@binding': exp }) 63 | lastIndex = index + match[0].length 64 | } 65 | // 处理剩下的多余文本 66 | if (lastIndex < text.length) { 67 | rawTokens.push(tokenValue = text.slice(lastIndex)) 68 | tokens.push(JSON.stringify(tokenValue)) 69 | } 70 | return { 71 | expression: tokens.join('+'), 72 | tokens: rawTokens 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /compiler/to-function.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { noop, extend } from 'shared/util' 4 | import { warn as baseWarn, tip } from 'core/util/debug' 5 | 6 | type CompiledFunctionResult = { 7 | render: Function; 8 | staticRenderFns: Array; 9 | }; 10 | 11 | function createFunction (code, errors) { 12 | try { 13 | return new Function(code) 14 | } catch (err) { 15 | errors.push({ err, code }) 16 | return noop 17 | } 18 | } 19 | 20 | export function createCompileToFunctionFn (compile: Function): Function { 21 | /** 22 | * 用于缓存 23 | */ 24 | const cache = Object.create(null) 25 | 26 | return function compileToFunctions ( 27 | template: string, 28 | options?: CompilerOptions, 29 | vm?: Component 30 | ): CompiledFunctionResult { 31 | /** 32 | * 将拷贝options的value 到新对象 33 | * 理解成Object.assign() 34 | */ 35 | options = extend({}, options) 36 | const warn = options.warn || baseWarn 37 | delete options.warn 38 | 39 | /* istanbul ignore if */ 40 | if (process.env.NODE_ENV !== 'production') { 41 | // detect possible CSP restriction 42 | /** 43 | * 禁止eval()函数 44 | */ 45 | try { 46 | new Function('return 1') 47 | } catch (e) { 48 | if (e.toString().match(/unsafe-eval|CSP/)) { 49 | warn( 50 | 'It seems you are using the standalone build of Vue.js in an ' + 51 | 'environment with Content Security Policy that prohibits unsafe-eval. ' + 52 | 'The template compiler cannot work in this environment. Consider ' + 53 | 'relaxing the policy to allow unsafe-eval or pre-compiling your ' + 54 | 'templates into render functions.' 55 | ) 56 | } 57 | } 58 | } 59 | 60 | // check cache 61 | /** 62 | * 是否有模版分隔符 63 | * 关于Vue api中delimiters的理解可看 https://stackoverflow.com/questions/33628558/vue-js-change-tags 64 | */ 65 | const key = options.delimiters 66 | ? String(options.delimiters) + template 67 | : template 68 | // 从缓存中取值 69 | if (cache[key]) { 70 | return cache[key] 71 | } 72 | 73 | // compile 74 | const compiled = compile(template, options) 75 | 76 | // check compilation errors/tips 77 | if (process.env.NODE_ENV !== 'production') { 78 | if (compiled.errors && compiled.errors.length) { 79 | warn( 80 | `Error compiling template:\n\n${template}\n\n` + 81 | compiled.errors.map(e => `- ${e}`).join('\n') + '\n', 82 | vm 83 | ) 84 | } 85 | if (compiled.tips && compiled.tips.length) { 86 | compiled.tips.forEach(msg => tip(msg, vm)) 87 | } 88 | } 89 | 90 | // turn code into functions 91 | const res = {} 92 | const fnGenErrors = [] 93 | res.render = createFunction(compiled.render, fnGenErrors) 94 | res.staticRenderFns = compiled.staticRenderFns.map(code => { 95 | return createFunction(code, fnGenErrors) 96 | }) 97 | 98 | // check function generation errors. 99 | // this should only happen if there is a bug in the compiler itself. 100 | // mostly for codegen development use 101 | /* istanbul ignore if */ 102 | if (process.env.NODE_ENV !== 'production') { 103 | if ((!compiled.errors || !compiled.errors.length) && fnGenErrors.length) { 104 | warn( 105 | `Failed to generate render function:\n\n` + 106 | fnGenErrors.map(({ err, code }) => `${err.toString()} in\n\n${code}\n`).join('\n'), 107 | vm 108 | ) 109 | } 110 | } 111 | 112 | return (cache[key] = res) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /core/components/index.js: -------------------------------------------------------------------------------- 1 | import KeepAlive from './keep-alive' 2 | // vue内置组件keep-alive 3 | export default { 4 | KeepAlive 5 | } 6 | -------------------------------------------------------------------------------- /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 | // 合并策略 14 | optionMergeStrategies: { [key: string]: Function }; 15 | // 是否打印警告log 16 | silent: boolean; 17 | // 在启动时显示生产模式提示信息 18 | productionTip: boolean; 19 | // 是否开启性能记录功能 20 | performance: boolean; 21 | // 是否开启devtools 22 | devtools: boolean; 23 | // 是否捕获全局的错误 24 | errorHandler: ?(err: Error, vm: Component, info: string) => void; 25 | // 是否捕获全局的警告 26 | warnHandler: ?(msg: string, vm: Component, trace: string) => void; 27 | // 要特殊忽略的元素 28 | ignoredElements: Array; 29 | // 键盘事件别名 30 | keyCodes: { [key: string]: number | Array }; 31 | 32 | // platform 33 | // 是否检查保留字tag, 用于避免与原生tag冲突 34 | isReservedTag: (x?: string) => boolean; 35 | // 是否检查保留属性 36 | isReservedAttr: (x?: string) => boolean; 37 | // 解析特定平台的标签名 38 | parsePlatformTagName: (x: string) => string; 39 | // 检查tag是否是未知元素 40 | isUnknownElement: (x?: string) => boolean; 41 | // 获取tag的命名空间 42 | getTagNamespace: (x?: string) => string | void; 43 | // 检查属性是否必须用prop绑定 44 | mustUseProp: (tag: string, type: ?string, name: string) => boolean; 45 | 46 | // legacy 47 | // 生命周期钩子枚举 48 | _lifecycleHooks: Array; 49 | }; 50 | 51 | export default ({ 52 | /** 53 | * Option merge strategies (used in core/util/options) 54 | */ 55 | // $flow-disable-line 56 | optionMergeStrategies: Object.create(null), 57 | 58 | /** 59 | * Whether to suppress warnings. 60 | */ 61 | silent: false, 62 | 63 | /** 64 | * Show production mode tip message on boot? 65 | */ 66 | productionTip: process.env.NODE_ENV !== 'production', 67 | 68 | /** 69 | * Whether to enable devtools 70 | */ 71 | devtools: process.env.NODE_ENV !== 'production', 72 | 73 | /** 74 | * Whether to record perf 75 | */ 76 | performance: false, 77 | 78 | /** 79 | * Error handler for watcher errors 80 | */ 81 | errorHandler: null, 82 | 83 | /** 84 | * Warn handler for watcher warns 85 | */ 86 | warnHandler: null, 87 | 88 | /** 89 | * Ignore certain custom elements 90 | */ 91 | ignoredElements: [], 92 | 93 | /** 94 | * Custom user key aliases for v-on 95 | */ 96 | // $flow-disable-line 97 | keyCodes: Object.create(null), 98 | 99 | /** 100 | * Check if a tag is reserved so that it cannot be registered as a 101 | * component. This is platform-dependent and may be overwritten. 102 | */ 103 | isReservedTag: no, 104 | 105 | /** 106 | * Check if an attribute is reserved so that it cannot be used as a component 107 | * prop. This is platform-dependent and may be overwritten. 108 | */ 109 | isReservedAttr: no, 110 | 111 | /** 112 | * Check if a tag is an unknown element. 113 | * Platform-dependent. 114 | */ 115 | isUnknownElement: no, 116 | 117 | /** 118 | * Get the namespace of an element 119 | */ 120 | getTagNamespace: noop, 121 | 122 | /** 123 | * Parse the real tag name for the specific platform. 124 | */ 125 | parsePlatformTagName: identity, 126 | 127 | /** 128 | * Check if an attribute must be bound using property, e.g. value 129 | * Platform-dependent. 130 | */ 131 | mustUseProp: no, 132 | 133 | /** 134 | * Exposed for legacy reasons 135 | */ 136 | _lifecycleHooks: LIFECYCLE_HOOKS 137 | }: Config) 138 | -------------------------------------------------------------------------------- /core/global-api/assets.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ASSET_TYPES } from 'shared/constants' 4 | import { isPlainObject, validateComponentName } from '../util/index' 5 | 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' && type === 'component') { 21 | validateComponentName(id) 22 | } 23 | if (type === 'component' && isPlainObject(definition)) { 24 | definition.name = definition.name || id 25 | definition = this.options._base.extend(definition) 26 | } 27 | if (type === 'directive' && typeof definition === 'function') { 28 | definition = { bind: definition, update: definition } 29 | } 30 | this.options[type + 's'][id] = definition 31 | return definition 32 | } 33 | } 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /core/global-api/extend.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { ASSET_TYPES } from 'shared/constants' 4 | import { defineComputed, proxy } from '../instance/state' 5 | import { extend, mergeOptions, validateComponentName } from '../util/index' 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 | // 为每个构造函数分配一个唯一的cid, 以便用于缓存 14 | Vue.cid = 0 15 | let cid = 1 16 | 17 | /** 18 | * Class inheritance 19 | */ 20 | // 生成Vue构造器的子类 21 | Vue.extend = function (extendOptions: Object): Function { 22 | extendOptions = extendOptions || {} 23 | // 如果cid已存在, 则应用缓存, 直接返回缓存的构造器 24 | const Super = this 25 | const SuperId = Super.cid 26 | const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {}) 27 | if (cachedCtors[SuperId]) { 28 | return cachedCtors[SuperId] 29 | } 30 | 31 | const name = extendOptions.name || Super.options.name 32 | if (process.env.NODE_ENV !== 'production' && name) { 33 | validateComponentName(name) 34 | } 35 | // 创建一个包含_init方法的子类, 并赋予唯一的cid 36 | const Sub = function VueComponent (options) { 37 | this._init(options) 38 | } 39 | Sub.prototype = Object.create(Super.prototype) 40 | Sub.prototype.constructor = Sub 41 | Sub.cid = cid++ 42 | // 合并配置项 43 | Sub.options = mergeOptions( 44 | Super.options, 45 | extendOptions 46 | ) 47 | Sub['super'] = Super 48 | 49 | // For props and computed properties, we define the proxy getters on 50 | // the Vue instances at extension time, on the extended prototype. This 51 | // avoids Object.defineProperty calls for each instance created. 52 | // 将props和computed都代理到vm实例上 53 | if (Sub.options.props) { 54 | initProps(Sub) 55 | } 56 | if (Sub.options.computed) { 57 | initComputed(Sub) 58 | } 59 | 60 | // allow further extension/mixin/plugin usage 61 | // 添加extend,mixin,use这些API 62 | Sub.extend = Super.extend 63 | Sub.mixin = Super.mixin 64 | Sub.use = Super.use 65 | 66 | // create asset registers, so extended classes 67 | // can have their private assets too. 68 | ASSET_TYPES.forEach(function (type) { 69 | Sub[type] = Super[type] 70 | }) 71 | // 将组件实例自身也挂载为一个属性, 用于递归组件 72 | // enable recursive self-lookup 73 | if (name) { 74 | Sub.options.components[name] = Sub 75 | } 76 | 77 | // keep a reference to the super options at extension time. 78 | // later at instantiation we can check if Super's options have 79 | // been updated. 80 | Sub.superOptions = Super.options 81 | Sub.extendOptions = extendOptions 82 | Sub.sealedOptions = extend({}, Sub.options) 83 | 84 | // cache constructor 85 | cachedCtors[SuperId] = Sub 86 | return Sub 87 | } 88 | } 89 | 90 | // vm实例代理props 91 | function initProps (Comp) { 92 | const props = Comp.options.props 93 | for (const key in props) { 94 | proxy(Comp.prototype, `_props`, key) 95 | } 96 | } 97 | 98 | // vm实例代理computed 99 | function initComputed (Comp) { 100 | const computed = Comp.options.computed 101 | for (const key in computed) { 102 | defineComputed(Comp.prototype, key, computed[key]) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /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 | // 提供获取配置项的方法, 不允许在这里设置配置项 23 | const configDef = {} 24 | configDef.get = () => config 25 | if (process.env.NODE_ENV !== 'production') { 26 | configDef.set = () => { 27 | warn( 28 | 'Do not replace the Vue.config object, set individual fields instead.' 29 | ) 30 | } 31 | } 32 | Object.defineProperty(Vue, 'config', configDef) 33 | 34 | // exposed util methods. 35 | // NOTE: these are not considered part of the public API - avoid relying on 36 | // them unless you are aware of the risk. 37 | // 注册全局工具API, 这些API只在vue项目内使用, 并不是对外公开的API 38 | Vue.util = { 39 | warn, 40 | extend, 41 | mergeOptions, 42 | defineReactive 43 | } 44 | 45 | Vue.set = set 46 | Vue.delete = del 47 | Vue.nextTick = nextTick 48 | 49 | // 初始化options 50 | Vue.options = Object.create(null) 51 | ASSET_TYPES.forEach(type => { 52 | Vue.options[type + 's'] = Object.create(null) 53 | }) 54 | 55 | // this is used to identify the "base" constructor to extend all plain-object 56 | // components with in Weex's multi-instance scenarios. 57 | // 用_base属性来挂载Vue构造器 58 | Vue.options._base = Vue 59 | 60 | extend(Vue.options.components, builtInComponents) 61 | 62 | initUse(Vue) 63 | initMixin(Vue) 64 | initExtend(Vue) 65 | initAssetRegisters(Vue) 66 | } 67 | -------------------------------------------------------------------------------- /core/global-api/mixin.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { mergeOptions } from '../util/index' 4 | // 提供Vue.mixin API, 使用mergeOptions来合并mixin配置, mergeOptions详见util/options.js 5 | export function initMixin (Vue: GlobalAPI) { 6 | Vue.mixin = function (mixin: Object) { 7 | this.options = mergeOptions(this.options, mixin) 8 | return this 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/global-api/use.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toArray } from '../util/index' 4 | // 提供Vue.use API 用于安装Vue插件, 安装插件前会先检查插件是否已存在 5 | export function initUse (Vue: GlobalAPI) { 6 | Vue.use = function (plugin: Function | Object) { 7 | const installedPlugins = (this._installedPlugins || (this._installedPlugins = [])) 8 | if (installedPlugins.indexOf(plugin) > -1) { 9 | return this 10 | } 11 | 12 | // additional parameters 13 | const args = toArray(arguments, 1) 14 | args.unshift(this) 15 | if (typeof plugin.install === 'function') { 16 | plugin.install.apply(plugin, args) 17 | } else if (typeof plugin === 'function') { 18 | plugin.apply(null, args) 19 | } 20 | installedPlugins.push(plugin) 21 | return this 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /core/index.js: -------------------------------------------------------------------------------- 1 | import Vue from './instance/index' 2 | import { initGlobalAPI } from './global-api/index' 3 | import { isServerRendering } from 'core/util/env' 4 | import { FunctionalRenderContext } from 'core/vdom/create-functional-component' 5 | 6 | /** 7 | * 添加全局的API 8 | */ 9 | initGlobalAPI(Vue) 10 | 11 | /** 12 | * 服务端渲染需要 13 | */ 14 | Object.defineProperty(Vue.prototype, '$isServer', { 15 | get: isServerRendering 16 | }) 17 | 18 | /** 19 | * 服务端渲染需要 20 | */ 21 | Object.defineProperty(Vue.prototype, '$ssrContext', { 22 | get () { 23 | /* istanbul ignore next */ 24 | return this.$vnode && this.$vnode.ssrContext 25 | } 26 | }) 27 | 28 | /** 29 | * 服务端渲染需要 30 | */ 31 | // expose FunctionalRenderContext for ssr runtime helper installation 32 | Object.defineProperty(Vue, 'FunctionalRenderContext', { 33 | value: FunctionalRenderContext 34 | }) 35 | 36 | /** 37 | * vue版本号 这里的'__VERSION__'为占位符,发布版本时将被自动替换 38 | */ 39 | Vue.version = '__VERSION__' 40 | 41 | /** 42 | * 导出Vue构造函数 43 | */ 44 | export default Vue 45 | -------------------------------------------------------------------------------- /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 | 8 | // Vue构造函数必须使用new关键字实例化, 否则会抛出一个警告, 实例化Vue的时候会调用_init方法初始化 9 | // 这里options也是.vue文件中暴露出的对象 10 | function Vue (options) { 11 | if (process.env.NODE_ENV !== 'production' && 12 | !(this instanceof Vue) 13 | ) { 14 | warn('Vue is a constructor and should be called with the `new` keyword') 15 | } 16 | this._init(options) 17 | } 18 | 19 | initMixin(Vue) 20 | stateMixin(Vue) 21 | eventsMixin(Vue) 22 | lifecycleMixin(Vue) 23 | renderMixin(Vue) 24 | 25 | export default Vue 26 | -------------------------------------------------------------------------------- /core/instance/inject.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { hasOwn } from 'shared/util' 4 | import { warn, hasSymbol } from '../util/index' 5 | import { defineReactive, toggleObserving } from '../observer/index' 6 | 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 | toggleObserving(false) 20 | Object.keys(result).forEach(key => { 21 | /* istanbul ignore else */ 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 | toggleObserving(true) 36 | } 37 | } 38 | 39 | export function resolveInject (inject: any, vm: Component): ?Object { 40 | if (inject) { 41 | // inject is :any because flow is not smart enough to figure out cached 42 | const result = Object.create(null) 43 | const keys = hasSymbol 44 | ? Reflect.ownKeys(inject).filter(key => { 45 | /* istanbul ignore next */ 46 | return Object.getOwnPropertyDescriptor(inject, key).enumerable 47 | }) 48 | : Object.keys(inject) 49 | 50 | for (let i = 0; i < keys.length; i++) { 51 | const key = keys[i] 52 | const provideKey = inject[key].from 53 | let source = vm 54 | while (source) { 55 | if (source._provided && hasOwn(source._provided, provideKey)) { 56 | result[key] = source._provided[provideKey] 57 | break 58 | } 59 | source = source.$parent 60 | } 61 | if (!source) { 62 | if ('default' in inject[key]) { 63 | const provideDefault = inject[key].default 64 | result[key] = typeof provideDefault === 'function' 65 | ? provideDefault.call(vm) 66 | : provideDefault 67 | } else if (process.env.NODE_ENV !== 'production') { 68 | warn(`Injection "${key}" not found`, vm) 69 | } 70 | } 71 | } 72 | return result 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /core/instance/proxy.js: -------------------------------------------------------------------------------- 1 | /* not type checking this file because flow doesn't play well with Proxy */ 2 | 3 | import config from 'core/config' 4 | import { warn, makeMap, isNative } from '../util/index' 5 | 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 that this property is reactive, ' + 20 | 'either in the data option, or for class-based components, by ' + 21 | 'initializing the property. ' + 22 | 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', 23 | target 24 | ) 25 | } 26 | 27 | const hasProxy = 28 | typeof Proxy !== 'undefined' && isNative(Proxy) 29 | 30 | if (hasProxy) { 31 | const isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact') 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-listeners.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { warn, extend, isPlainObject } from 'core/util/index' 4 | 5 | export function bindObjectListeners (data: any, value: any): VNodeData { 6 | if (value) { 7 | if (!isPlainObject(value)) { 8 | process.env.NODE_ENV !== 'production' && warn( 9 | 'v-on without argument expects an Object value', 10 | this 11 | ) 12 | } else { 13 | const on = data.on = data.on ? extend({}, data.on) : {} 14 | for (const key in value) { 15 | const existing = on[key] 16 | const ours = value[key] 17 | on[key] = existing ? [].concat(existing, ours) : ours 18 | } 19 | } 20 | } 21 | return data 22 | } 23 | -------------------------------------------------------------------------------- /core/instance/render-helpers/bind-object-props.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | 5 | import { 6 | warn, 7 | isObject, 8 | toObject, 9 | isReservedAttribute 10 | } from 'core/util/index' 11 | 12 | /** 13 | * Runtime helper for merging v-bind="object" into a VNode's data. 14 | */ 15 | 16 | // 用于解析v-bind指令绑定对象类型值的场景, 将 17 | export function bindObjectProps ( 18 | data: any, 19 | tag: string, 20 | value: any, 21 | asProp: boolean, 22 | isSync?: boolean 23 | ): VNodeData { 24 | // 如果v-bind指令未传参数, 什么都不做, 直接返回原data 25 | if (value) { 26 | // 如果v-bind指令的参数不是object,array 直接报错 27 | if (!isObject(value)) { 28 | process.env.NODE_ENV !== 'production' && warn( 29 | 'v-bind without argument expects an Object or Array value', 30 | this 31 | ) 32 | } else { 33 | // 参数是数组时, 格式化成object 34 | if (Array.isArray(value)) { 35 | value = toObject(value) 36 | } 37 | let hash 38 | for (const key in value) { 39 | if ( 40 | key === 'class' || 41 | key === 'style' || 42 | isReservedAttribute(key) 43 | ) { 44 | hash = data 45 | } else { 46 | const type = data.attrs && data.attrs.type 47 | hash = asProp || config.mustUseProp(tag, type, key) 48 | ? data.domProps || (data.domProps = {}) 49 | : data.attrs || (data.attrs = {}) 50 | } 51 | // 把参数的key复制到data中, 对于使用sync修饰符的场景特殊处理 52 | if (!(key in hash)) { 53 | hash[key] = value[key] 54 | 55 | if (isSync) { 56 | const on = data.on || (data.on = {}) 57 | on[`update:${key}`] = function ($event) { 58 | value[key] = $event 59 | } 60 | } 61 | } 62 | } 63 | } 64 | } 65 | return data 66 | } 67 | -------------------------------------------------------------------------------- /core/instance/render-helpers/check-keycodes.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from 'core/config' 4 | import { hyphenate } from 'shared/util' 5 | 6 | // 检查数组是否不包含某值 7 | function isKeyNotMatch (expect: T | Array, actual: T): boolean { 8 | if (Array.isArray(expect)) { 9 | return expect.indexOf(actual) === -1 10 | } else { 11 | return expect !== actual 12 | } 13 | } 14 | 15 | /** 16 | * Runtime helper for checking keyCodes from config. 17 | * exposed as Vue.prototype._k 18 | * passing in eventKeyName as last argument separately for backwards compat 19 | */ 20 | // 用于检查keyCode是否存在 21 | export function checkKeyCodes ( 22 | eventKeyCode: number, 23 | key: string, 24 | builtInKeyCode?: number | Array, 25 | eventKeyName?: string, 26 | builtInKeyName?: string | Array 27 | ): ?boolean { 28 | const mappedKeyCode = config.keyCodes[key] || builtInKeyCode 29 | if (builtInKeyName && eventKeyName && !config.keyCodes[key]) { 30 | return isKeyNotMatch(builtInKeyName, eventKeyName) 31 | } else if (mappedKeyCode) { 32 | return isKeyNotMatch(mappedKeyCode, eventKeyCode) 33 | } else if (eventKeyName) { 34 | return hyphenate(eventKeyName) !== key 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /core/instance/render-helpers/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { toNumber, toString, looseEqual, looseIndexOf } from 'shared/util' 4 | import { createTextVNode, createEmptyVNode } from 'core/vdom/vnode' 5 | import { renderList } from './render-list' 6 | import { renderSlot } from './render-slot' 7 | import { resolveFilter } from './resolve-filter' 8 | import { checkKeyCodes } from './check-keycodes' 9 | import { bindObjectProps } from './bind-object-props' 10 | import { renderStatic, markOnce } from './render-static' 11 | import { bindObjectListeners } from './bind-object-listeners' 12 | import { resolveScopedSlots } from './resolve-slots' 13 | 14 | export function installRenderHelpers (target: any) { 15 | target._o = markOnce 16 | target._n = toNumber 17 | target._s = toString 18 | target._l = renderList 19 | target._t = renderSlot 20 | target._q = looseEqual 21 | target._i = looseIndexOf 22 | target._m = renderStatic 23 | target._f = resolveFilter 24 | target._k = checkKeyCodes 25 | target._b = bindObjectProps 26 | target._v = createTextVNode 27 | target._e = createEmptyVNode 28 | target._u = resolveScopedSlots 29 | target._g = bindObjectListeners 30 | } 31 | -------------------------------------------------------------------------------- /core/instance/render-helpers/render-list.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isObject, isDef } 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: ( 12 | val: any, 13 | keyOrIndex: string | number, 14 | index?: number 15 | ) => VNode 16 | ): ?Array { 17 | let ret: ?Array, i, l, keys, key 18 | // 当v-for循环的值是array或者string时, 使用length循环 19 | if (Array.isArray(val) || typeof val === 'string') { 20 | ret = new Array(val.length) 21 | for (i = 0, l = val.length; i < l; i++) { 22 | ret[i] = render(val[i], i) 23 | } 24 | // 当v-for循环的值是number时, 直接用这个数字做循环终止条件 25 | } else if (typeof val === 'number') { 26 | ret = new Array(val) 27 | for (i = 0; i < val; i++) { 28 | ret[i] = render(i + 1, i) 29 | } 30 | // 对于object的v-for, 用keys处理 31 | } else if (isObject(val)) { 32 | keys = Object.keys(val) 33 | ret = new Array(keys.length) 34 | for (i = 0, l = keys.length; i < l; i++) { 35 | key = keys[i] 36 | ret[i] = render(val[key], key, i) 37 | } 38 | } 39 | if (isDef(ret)) { 40 | (ret: any)._isVList = true 41 | } 42 | return ret 43 | } 44 | -------------------------------------------------------------------------------- /core/instance/render-helpers/render-slot.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { extend, warn, isObject } from 'core/util/index' 4 | 5 | /** 6 | * Runtime helper for rendering 7 | */ 8 | export function renderSlot ( 9 | name: string, 10 | fallback: ?Array, 11 | props: ?Object, 12 | bindObject: ?Object 13 | ): ?Array { 14 | const scopedSlotFn = this.$scopedSlots[name] 15 | let nodes 16 | if (scopedSlotFn) { // scoped slot 17 | props = props || {} 18 | if (bindObject) { 19 | if (process.env.NODE_ENV !== 'production' && !isObject(bindObject)) { 20 | warn( 21 | 'slot v-bind without argument expects an Object', 22 | this 23 | ) 24 | } 25 | props = extend(extend({}, bindObject), props) 26 | } 27 | nodes = scopedSlotFn(props) || fallback 28 | } else { 29 | const slotNodes = this.$slots[name] 30 | // warn duplicate slot usage 31 | if (slotNodes) { 32 | if (process.env.NODE_ENV !== 'production' && slotNodes._rendered) { 33 | warn( 34 | `Duplicate presence of slot "${name}" found in the same render tree ` + 35 | `- this will likely cause render errors.`, 36 | this 37 | ) 38 | } 39 | slotNodes._rendered = true 40 | } 41 | nodes = slotNodes || fallback 42 | } 43 | 44 | const target = props && props.slot 45 | if (target) { 46 | return this.$createElement('template', { slot: target }, nodes) 47 | } else { 48 | return nodes 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /core/instance/render-helpers/render-static.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Runtime helper for rendering static trees. 5 | */ 6 | export function renderStatic ( 7 | index: number, 8 | isInFor: boolean 9 | ): VNode | Array { 10 | const cached = this._staticTrees || (this._staticTrees = []) 11 | let tree = cached[index] 12 | // if has already-rendered static tree and not inside v-for, 13 | // we can reuse the same tree. 14 | if (tree && !isInFor) { 15 | return tree 16 | } 17 | // otherwise, render a fresh tree. 18 | tree = cached[index] = this.$options.staticRenderFns[index].call( 19 | this._renderProxy, 20 | null, 21 | this // for render fns generated for functional component templates 22 | ) 23 | markStatic(tree, `__static__${index}`, false) 24 | return tree 25 | } 26 | 27 | /** 28 | * Runtime helper for v-once. 29 | * Effectively it means marking the node as static with a unique key. 30 | */ 31 | export function markOnce ( 32 | tree: VNode | Array, 33 | index: number, 34 | key: string 35 | ) { 36 | markStatic(tree, `__once__${index}${key ? `_${key}` : ``}`, true) 37 | return tree 38 | } 39 | 40 | function markStatic ( 41 | tree: VNode | Array, 42 | key: string, 43 | isOnce: boolean 44 | ) { 45 | if (Array.isArray(tree)) { 46 | for (let i = 0; i < tree.length; i++) { 47 | if (tree[i] && typeof tree[i] !== 'string') { 48 | markStaticNode(tree[i], `${key}_${i}`, isOnce) 49 | } 50 | } 51 | } else { 52 | markStaticNode(tree, key, isOnce) 53 | } 54 | } 55 | 56 | function markStaticNode (node, key, isOnce) { 57 | node.isStatic = true 58 | node.key = key 59 | node.isOnce = isOnce 60 | } 61 | -------------------------------------------------------------------------------- /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 | export function resolveFilter (id: string): Function { 9 | return resolveAsset(this.$options, 'filters', id, true) || identity 10 | } 11 | -------------------------------------------------------------------------------- /core/instance/render-helpers/resolve-slots.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import type VNode from 'core/vdom/vnode' 4 | 5 | /** 6 | * Runtime helper for resolving raw children VNodes into a slot object. 7 | */ 8 | export function resolveSlots ( 9 | children: ?Array, 10 | context: ?Component 11 | ): { [key: string]: Array } { 12 | const slots = {} 13 | if (!children) { 14 | return slots 15 | } 16 | for (let i = 0, l = children.length; i < l; i++) { 17 | const child = children[i] 18 | const data = child.data 19 | // remove slot attribute if the node is resolved as a Vue slot node 20 | if (data && data.attrs && data.attrs.slot) { 21 | delete data.attrs.slot 22 | } 23 | // named slots should only be respected if the vnode was rendered in the 24 | // same context. 25 | if ((child.context === context || child.fnContext === context) && 26 | data && data.slot != null 27 | ) { 28 | const name = data.slot 29 | const slot = (slots[name] || (slots[name] = [])) 30 | if (child.tag === 'template') { 31 | slot.push.apply(slot, child.children || []) 32 | } else { 33 | slot.push(child) 34 | } 35 | } else { 36 | (slots.default || (slots.default = [])).push(child) 37 | } 38 | } 39 | // ignore slots that contains only whitespace 40 | for (const name in slots) { 41 | if (slots[name].every(isWhitespace)) { 42 | delete slots[name] 43 | } 44 | } 45 | return slots 46 | } 47 | 48 | function isWhitespace (node: VNode): boolean { 49 | return (node.isComment && !node.asyncFactory) || node.text === ' ' 50 | } 51 | 52 | export function resolveScopedSlots ( 53 | fns: ScopedSlotsData, // see flow/vnode 54 | res?: Object 55 | ): { [key: string]: Function } { 56 | res = res || {} 57 | for (let i = 0; i < fns.length; i++) { 58 | if (Array.isArray(fns[i])) { 59 | resolveScopedSlots(fns[i], res) 60 | } else { 61 | res[fns[i].key] = fns[i].fn 62 | } 63 | } 64 | return res 65 | } 66 | -------------------------------------------------------------------------------- /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 | 6 | import { def } from '../util/index' 7 | 8 | // 使用Array.prototype创建新的数组对象, 避免污染Array.prototype上的方法 9 | const arrayProto = Array.prototype 10 | export const arrayMethods = Object.create(arrayProto) 11 | 12 | // 对这些数组方法进行重写, 功能与原方法一致, 只是新增了一步通知watcher相应变化的操作 13 | const methodsToPatch = [ 14 | 'push', 15 | 'pop', 16 | 'shift', 17 | 'unshift', 18 | 'splice', 19 | 'sort', 20 | 'reverse' 21 | ] 22 | 23 | /** 24 | * Intercept mutating methods and emit events 25 | */ 26 | methodsToPatch.forEach(function (method) { 27 | // cache original method 28 | // 缓存数组原生方法, 以便后续调用, 用于重写方法时保持原功能不变 29 | const original = arrayProto[method] 30 | def(arrayMethods, method, function mutator (...args) { 31 | const result = original.apply(this, args) 32 | const ob = this.__ob__ 33 | let inserted 34 | // 这三个操作是插入操作, 需要额外的observeArray方法进行观察变更 35 | switch (method) { 36 | case 'push': 37 | case 'unshift': 38 | inserted = args 39 | break 40 | case 'splice': 41 | inserted = args.slice(2) 42 | break 43 | } 44 | if (inserted) ob.observeArray(inserted) 45 | // notify change 46 | // 将改动通知watcher 47 | ob.dep.notify() 48 | return result 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /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 | 8 | /** 9 | * A dep is an observable that can have multiple 10 | * directives subscribing to it. 11 | */ 12 | // Dep用于管理watcher, dep实例可以被多个指令观察 13 | export default class Dep { 14 | // target是一个全局唯一的Watcher 15 | static target: ?Watcher; 16 | id: number; 17 | subs: Array; 18 | // 生成每个实例唯一的uid, subs用于存储watcher 19 | constructor () { 20 | this.id = uid++ 21 | this.subs = [] 22 | } 23 | 24 | // 添加一个watcher 25 | addSub (sub: Watcher) { 26 | this.subs.push(sub) 27 | } 28 | 29 | // 删除一个watcher 30 | removeSub (sub: Watcher) { 31 | remove(this.subs, sub) 32 | } 33 | 34 | // 将自身加入到全局的watcher中 35 | depend () { 36 | if (Dep.target) { 37 | Dep.target.addDep(this) 38 | } 39 | } 40 | 41 | // 通知所有订阅者 42 | notify () { 43 | // stabilize the subscriber list first 44 | const subs = this.subs.slice() 45 | for (let i = 0, l = subs.length; i < l; i++) { 46 | subs[i].update() 47 | } 48 | } 49 | } 50 | 51 | // the current target watcher being evaluated. 52 | // this is globally unique because there could be only one 53 | // watcher being evaluated at any time. 54 | // 置空Dep.target以免后续重复添加依赖, 设计一个栈来存取watcher 55 | Dep.target = null 56 | const targetStack = [] 57 | 58 | // 将watcher实例赋值给Dep.target,用于依赖收集。同时将该实例存入target栈中 59 | export function pushTarget (_target: ?Watcher) { 60 | if (Dep.target) targetStack.push(Dep.target) 61 | Dep.target = _target 62 | } 63 | 64 | // 从target栈取出一个watcher实例 65 | export function popTarget () { 66 | Dep.target = targetStack.pop() 67 | } 68 | -------------------------------------------------------------------------------- /core/observer/traverse.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { _Set as Set, isObject } from '../util/index' 4 | import type { SimpleSet } from '../util/index' 5 | import VNode from '../vdom/vnode' 6 | 7 | const seenObjects = new Set() 8 | 9 | /** 10 | * Recursively traverse an object to evoke all converted 11 | * getters, so that every nested property inside the object 12 | * is collected as a "deep" dependency. 13 | */ 14 | // 对数组和对象进行递归遍历, 对每个属性都进行依赖收集 15 | export function traverse (val: any) { 16 | _traverse(val, seenObjects) 17 | seenObjects.clear() 18 | } 19 | 20 | function _traverse (val: any, seen: SimpleSet) { 21 | let i, keys 22 | const isA = Array.isArray(val) 23 | // 三种情况不进行递归处理, 1.不是数组或对象, 2.是一个冻结对象, 3. 是一个vnode 24 | if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { 25 | return 26 | } 27 | if (val.__ob__) { 28 | const depId = val.__ob__.dep.id 29 | if (seen.has(depId)) { 30 | return 31 | } 32 | seen.add(depId) 33 | } 34 | // 递归处理数组和对象 35 | if (isA) { 36 | i = val.length 37 | while (i--) _traverse(val[i], seen) 38 | } else { 39 | keys = Object.keys(val) 40 | i = keys.length 41 | while (i--) _traverse(val[keys[i]], seen) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /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 generateComponentTrace = (noop: any) // work around flow check 9 | export let formatComponentName = (noop: any) 10 | 11 | if (process.env.NODE_ENV !== 'production') { 12 | const hasConsole = typeof console !== 'undefined' 13 | const classifyRE = /(?:^|[-_])(\w)/g 14 | const classify = str => str 15 | .replace(classifyRE, c => c.toUpperCase()) 16 | .replace(/[-_]/g, '') 17 | 18 | warn = (msg, vm) => { 19 | const trace = vm ? generateComponentTrace(vm) : '' 20 | 21 | if (config.warnHandler) { 22 | config.warnHandler.call(null, msg, vm, trace) 23 | } else if (hasConsole && (!config.silent)) { 24 | console.error(`[Vue warn]: ${msg}${trace}`) 25 | } 26 | } 27 | 28 | // 打印警告 29 | tip = (msg, vm) => { 30 | if (hasConsole && (!config.silent)) { 31 | console.warn(`[Vue tip]: ${msg}` + ( 32 | vm ? generateComponentTrace(vm) : '' 33 | )) 34 | } 35 | } 36 | 37 | // 用于报错时打印组件名 38 | formatComponentName = (vm, includeFile) => { 39 | if (vm.$root === vm) { 40 | return '' 41 | } 42 | const options = typeof vm === 'function' && vm.cid != null 43 | ? vm.options 44 | : vm._isVue 45 | ? vm.$options || vm.constructor.options 46 | : vm || {} 47 | let name = options.name || options._componentTag 48 | const file = options.__file 49 | if (!name && file) { 50 | const match = file.match(/([^/\\]+)\.vue$/) 51 | name = match && match[1] 52 | } 53 | 54 | return ( 55 | (name ? `<${classify(name)}>` : ``) + 56 | (file && includeFile !== false ? ` at ${file}` : '') 57 | ) 58 | } 59 | 60 | // 很花哨的生成重复字符串方法, 其实只是用来给后面打印的log加空格.... 61 | const repeat = (str, n) => { 62 | let res = '' 63 | while (n) { 64 | if (n % 2 === 1) res += str 65 | if (n > 1) str += str 66 | n >>= 1 67 | } 68 | return res 69 | } 70 | 71 | // 生成一个组件栈字符串用于打印错误信息 72 | generateComponentTrace = vm => { 73 | if (vm._isVue && vm.$parent) { 74 | const tree = [] 75 | let currentRecursiveSequence = 0 76 | while (vm) { 77 | if (tree.length > 0) { 78 | const last = tree[tree.length - 1] 79 | if (last.constructor === vm.constructor) { 80 | currentRecursiveSequence++ 81 | vm = vm.$parent 82 | continue 83 | } else if (currentRecursiveSequence > 0) { 84 | tree[tree.length - 1] = [last, currentRecursiveSequence] 85 | currentRecursiveSequence = 0 86 | } 87 | } 88 | tree.push(vm) 89 | vm = vm.$parent 90 | } 91 | return '\n\nfound in\n\n' + tree 92 | .map((vm, i) => `${ 93 | i === 0 ? '---> ' : repeat(' ', 5 + i * 2) 94 | }${ 95 | Array.isArray(vm) 96 | ? `${formatComponentName(vm[0])}... (${vm[1]} recursive calls)` 97 | : formatComponentName(vm) 98 | }`) 99 | .join('\n') 100 | } else { 101 | return `\n\n(found in ${formatComponentName(vm)})` 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /core/util/env.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | // can we use __proto__? 4 | // 判断浏览器是否支持__proto__这个非标准属性 5 | export const hasProto = '__proto__' in {} 6 | 7 | // Browser environment sniffing 8 | // 通过UA判断各种浏览器 9 | export const inBrowser = typeof window !== 'undefined' // 这可用于检测代码是否在典型的浏览器环境 10 | export const inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform 11 | export const weexPlatform = inWeex && WXEnvironment.platform.toLowerCase() 12 | export const UA = inBrowser && window.navigator.userAgent.toLowerCase() 13 | export const isIE = UA && /msie|trident/.test(UA) 14 | export const isIE9 = UA && UA.indexOf('msie 9.0') > 0 15 | export const isEdge = UA && UA.indexOf('edge/') > 0 16 | export const isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android') 17 | export const isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios') 18 | export const isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge 19 | 20 | // Firefox has a "watch" function on Object.prototype... 21 | // Firefox自作聪明加了个watch属性 这个自带的watch其实引发了不少问题 22 | export const nativeWatch = ({}).watch 23 | 24 | export let supportsPassive = false 25 | if (inBrowser) { 26 | try { 27 | const opts = {} 28 | Object.defineProperty(opts, 'passive', ({ 29 | get () { 30 | /* istanbul ignore next */ 31 | supportsPassive = true 32 | } 33 | }: Object)) // https://github.com/facebook/flow/issues/285 34 | window.addEventListener('test-passive', null, opts) 35 | } catch (e) {} 36 | } 37 | 38 | // this needs to be lazy-evaled because vue may be required before 39 | // vue-server-renderer can set VUE_ENV 40 | // 判断是否在服务端 41 | let _isServer 42 | export const isServerRendering = () => { 43 | if (_isServer === undefined) { 44 | /* istanbul ignore if */ 45 | if (!inBrowser && !inWeex && typeof global !== 'undefined') { 46 | // detect presence of vue-server-renderer and avoid 47 | // Webpack shimming the process 48 | _isServer = global['process'].env.VUE_ENV === 'server' 49 | } else { 50 | _isServer = false 51 | } 52 | } 53 | return _isServer 54 | } 55 | 56 | // detect devtools 57 | export const devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__ 58 | 59 | /* istanbul ignore next */ 60 | export function isNative (Ctor: any): boolean { 61 | return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) 62 | } 63 | 64 | // 判断是否完全支持Symbol和Reflect 65 | export const hasSymbol = 66 | typeof Symbol !== 'undefined' && isNative(Symbol) && 67 | typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys) 68 | 69 | // 自己搞了一个非标准的Set, 和原生Set的区别是只支持number/string类型的key 70 | let _Set 71 | /* istanbul ignore if */ // $flow-disable-line 72 | if (typeof Set !== 'undefined' && isNative(Set)) { 73 | // use native Set when available. 74 | _Set = Set 75 | } else { 76 | // a non-standard Set polyfill that only works with primitive keys. 77 | _Set = class Set implements SimpleSet { 78 | set: Object; 79 | constructor () { 80 | this.set = Object.create(null) 81 | } 82 | has (key: string | number) { 83 | return this.set[key] === true 84 | } 85 | add (key: string | number) { 86 | this.set[key] = true 87 | } 88 | clear () { 89 | this.set = Object.create(null) 90 | } 91 | } 92 | } 93 | 94 | // 用于实现一个非标准Set 95 | interface SimpleSet { 96 | has(key: string | number): boolean; 97 | add(key: string | number): mixed; 98 | clear(): void; 99 | } 100 | 101 | export { _Set } 102 | export type { SimpleSet } 103 | -------------------------------------------------------------------------------- /core/util/error.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import config from '../config' 4 | import { warn } from './debug' 5 | import { inBrowser, inWeex } from './env' 6 | 7 | // 捕获错误 8 | export function handleError (err: Error, vm: any, info: string) { 9 | if (vm) { 10 | let cur = vm 11 | while ((cur = cur.$parent)) { 12 | const hooks = cur.$options.errorCaptured 13 | if (hooks) { 14 | for (let i = 0; i < hooks.length; i++) { 15 | try { 16 | const capture = hooks[i].call(cur, err, vm, info) === false 17 | if (capture) return 18 | } catch (e) { 19 | globalHandleError(e, cur, 'errorCaptured hook') 20 | } 21 | } 22 | } 23 | } 24 | } 25 | globalHandleError(err, vm, info) 26 | } 27 | 28 | // 如果开启了errorHandler 会统一在这里处理错误 29 | function globalHandleError (err, vm, info) { 30 | if (config.errorHandler) { 31 | try { 32 | return config.errorHandler.call(null, err, vm, info) 33 | } catch (e) { 34 | logError(e, null, 'config.errorHandler') 35 | } 36 | } 37 | logError(err, vm, info) 38 | } 39 | 40 | // 在非生产环境打印log 41 | function logError (err, vm, info) { 42 | if (process.env.NODE_ENV !== 'production') { 43 | warn(`Error in ${info}: "${err.toString()}"`, vm) 44 | } 45 | /* istanbul ignore else */ 46 | if ((inBrowser || inWeex) && typeof console !== 'undefined') { 47 | console.error(err) 48 | } else { 49 | throw err 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 * from './next-tick' 11 | export { defineReactive } from '../observer/index' 12 | -------------------------------------------------------------------------------- /core/util/lang.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | /** 4 | * Check if a string starts with $ or _ 5 | */ 6 | /** 7 | * 用于检查一个key是否是私有或内部属性 8 | */ 9 | export function isReserved (str: string): boolean { 10 | const c = (str + '').charCodeAt(0) 11 | return c === 0x24 || c === 0x5F 12 | } 13 | 14 | /** 15 | * Define a property. 16 | */ 17 | // 添加一个属性, 和直接对obj.key赋值没什么区别 18 | export function def (obj: Object, key: string, val: any, enumerable?: boolean) { 19 | Object.defineProperty(obj, key, { 20 | value: val, 21 | enumerable: !!enumerable, 22 | writable: true, 23 | configurable: true 24 | }) 25 | } 26 | 27 | /** 28 | * Parse simple path. 29 | */ 30 | /** 31 | * 创建一个可以解析对象属性路径的function 32 | * 比如 let a = { b: { c: { d: 1 } } } 33 | * parsePath('b.c.d')(a) 会返回 1 34 | */ 35 | const bailRE = /[^\w.$]/ 36 | export function parsePath (path: string): any { 37 | if (bailRE.test(path)) { 38 | return 39 | } 40 | const segments = path.split('.') 41 | return function (obj) { 42 | for (let i = 0; i < segments.length; i++) { 43 | if (!obj) return 44 | obj = obj[segments[i]] 45 | } 46 | return obj 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /core/util/perf.js: -------------------------------------------------------------------------------- 1 | import { inBrowser } from './env' 2 | 3 | export let mark 4 | export let measure 5 | 6 | // window.performance 用于监控页面性能的 7 | // 具体API见MDN: https://developer.mozilla.org/zh-CN/docs/Web/API/Window/performance 8 | if (process.env.NODE_ENV !== 'production') { 9 | const perf = inBrowser && window.performance 10 | /* istanbul ignore if */ 11 | if ( 12 | perf && 13 | perf.mark && 14 | perf.measure && 15 | perf.clearMarks && 16 | perf.clearMeasures 17 | ) { 18 | mark = tag => perf.mark(tag) 19 | measure = (name, startTag, endTag) => { 20 | perf.measure(name, startTag, endTag) 21 | perf.clearMarks(startTag) 22 | perf.clearMarks(endTag) 23 | perf.clearMeasures(name) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | // 用于提取组件的props的值 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 | // 如果props的key不是横线连接的命名或者驼峰命名, 会展示一个警告信息 30 | if (process.env.NODE_ENV !== 'production') { 31 | const keyInLowerCase = key.toLowerCase() 32 | if ( 33 | key !== keyInLowerCase && 34 | attrs && hasOwn(attrs, keyInLowerCase) 35 | ) { 36 | tip( 37 | `Prop "${keyInLowerCase}" is passed to component ` + 38 | `${formatComponentName(tag || Ctor)}, but the declared prop name is` + 39 | ` "${key}". ` + 40 | `Note that HTML attributes are case-insensitive and camelCased ` + 41 | `props need to use their kebab-case equivalents when using in-DOM ` + 42 | `templates. You should probably use "${altKey}" instead of "${key}".` 43 | ) 44 | } 45 | } 46 | // 检查props时不删除, 检查attrs时删除 47 | checkProp(res, props, key, altKey, true) || 48 | checkProp(res, attrs, key, altKey, false) 49 | } 50 | } 51 | return res 52 | } 53 | 54 | function checkProp ( 55 | res: Object, 56 | hash: ?Object, 57 | key: string, 58 | altKey: string, 59 | preserve: boolean 60 | ): boolean { 61 | if (isDef(hash)) { 62 | if (hasOwn(hash, key)) { 63 | res[key] = hash[key] 64 | if (!preserve) { 65 | delete hash[key] 66 | } 67 | return true 68 | } else if (hasOwn(hash, altKey)) { 69 | res[key] = hash[altKey] 70 | if (!preserve) { 71 | delete hash[altKey] 72 | } 73 | return true 74 | } 75 | } 76 | return false 77 | } 78 | -------------------------------------------------------------------------------- /core/vdom/helpers/get-first-component-child.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import { isDef } from 'shared/util' 4 | import { isAsyncPlaceholder } from './is-async-placeholder' 5 | 6 | // 获取第一个子节点, 该节点必须是一个组件节点或者是一个异步占位节点 7 | export function getFirstComponentChild (children: ?Array): ?VNode { 8 | if (Array.isArray(children)) { 9 | for (let i = 0; i < children.length; i++) { 10 | const c = children[i] 11 | if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) { 12 | return c 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /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 | export * from './is-async-placeholder' 10 | -------------------------------------------------------------------------------- /core/vdom/helpers/is-async-placeholder.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | // 用于判断是否是异步占位节点(或注释节点) 3 | export function isAsyncPlaceholder (node: VNode): boolean { 4 | return node.isComment && node.asyncFactory 5 | } 6 | -------------------------------------------------------------------------------- /core/vdom/helpers/merge-hook.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode from '../vnode' 4 | import { createFnInvoker } from './update-listeners' 5 | import { remove, isDef, isUndef, isTrue } from 'shared/util' 6 | 7 | export function mergeVNodeHook (def: Object, hookKey: string, hook: Function) { 8 | // vnode没有钩子时初始化为空对象 9 | if (def instanceof VNode) { 10 | def = def.data.hook || (def.data.hook = {}) 11 | } 12 | let invoker 13 | const oldHook = def[hookKey] 14 | 15 | // 用于删除已合并的多余hook, 防止二次调用的情况 16 | function wrappedHook () { 17 | hook.apply(this, arguments) 18 | // important: remove merged hook to ensure it's called only once 19 | // and prevent memory leak 20 | remove(invoker.fns, wrappedHook) 21 | } 22 | 23 | // 如果不存在旧的钩子则创建 24 | if (isUndef(oldHook)) { 25 | // no existing hook 26 | invoker = createFnInvoker([wrappedHook]) 27 | } else { 28 | /* istanbul ignore if */ 29 | if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { 30 | // already a merged invoker 31 | invoker = oldHook 32 | invoker.fns.push(wrappedHook) 33 | } else { 34 | // existing plain hook 35 | invoker = createFnInvoker([oldHook, wrappedHook]) 36 | } 37 | } 38 | 39 | invoker.merged = true 40 | def[hookKey] = invoker 41 | } 42 | -------------------------------------------------------------------------------- /core/vdom/helpers/normalize-children.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | import VNode, { createTextVNode } from 'core/vdom/vnode' 4 | import { isFalse, isTrue, 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 | /** 19 | * 当children包含组件时, 可以简单的将children数组扁平化 20 | */ 21 | export function simpleNormalizeChildren (children: any) { 22 | for (let i = 0; i < children.length; i++) { 23 | if (Array.isArray(children[i])) { 24 | return Array.prototype.concat.apply([], children) 25 | } 26 | } 27 | return children 28 | } 29 | 30 | // 2. When the children contains constructs that always generated nested Arrays, 31 | // e.g.