筆者最近在讀Vue2.6.11的源碼,在閱讀過程當中,不只體會到Vue組件化及數據響應式的設計之美,也感嘆於尤大擼碼的規範、優雅。因此這裏一一將其總結羅列出來,保證新手看了,寫代碼更老練。老人看了,更進一步。html
友情提示:注意漢語註釋前端
/* 聲明變量位於聲明以後 */
vm._vnode = null // the root of the child tree vm._staticTrees = null // v-once cached trees /* 函數中使用註釋 注意縮進 */ export function initRender (vm){ // bind the createElement fn to this instance // so that we get proper render context inside it. // args order: tag, data, children, normalizationType, alwaysNormalize // internal version is used by render functions compiled from templates vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) } /* if...else... if 以前說明if做用,if裏面表示經過檢測 */ // if the returned array contains only a single node, allow it if (Array.isArray(vnode) && vnode.length === 1) { vnode = vnode[0] } if (res.iterator2) { // (if以後) // (item, key, index) for object iteration // is this even supported? addRawAttr(el, 'index', res.iterator2) } else if (res.iterator1) { addRawAttr(el, 'index', res.iterator1) } /* 變量或者參數後註釋 說明其做用 */ isCloned: boolean; // is a cloned node? isOnce: boolean; // is a v-once node? asyncFactory: Function | void; // async component factory function const _target = target // save current target element in closure 複製代碼
/* 導出模塊以前 */
/** * Map the following syntax to corresponding attrs: * * <recycle-list for="(item, i) in longList" switch="cellType"> * <cell-slot case="A"> ... </cell-slot> * <cell-slot case="B"> ... </cell-slot> * </recycle-list> */ export function preTransformRecycleList ( el: ASTElement, options: WeexCompilerOptions ) { // 省略的代碼 } /* 函數聲明以前 */ /** * Convert an input value to a number for persistence. * If the conversion fails, return original string. */ function toNumber (val) { // 省略的代碼 } 複製代碼
/* 每行最多在75個字節左右 */
/* 單行註釋換行 */ // There's no need to maintain a stack because all render fns are called // separately from one another. Nested component's render fns are called // when parent component is patched. currentRenderingInstance = vm vnode = render.call(vm._renderProxy, vm.$createElement) /* 多行註釋換行 */ /** * Collect dependencies on array elements when the array is touched, since * we cannot intercept array element access like property getters. */ function dependArray (value) { // 省略的代碼 } 複製代碼
+
位置
warn(
`Property or method "${key}" is not defined on the instance but ` + 'referenced during render. Make sure that this property is reactive, ' + 'either in the data option, or for class-based components, by ' + 'initializing the property. ' + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', target ) 複製代碼
/* better */
function mergeHook (...) { return childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal } /* bad */ function mergeHook (...) { return childVal ? parentVal ? parentVal.concat(childVal) : Array.isArray(childVal) ? childVal : [childVal] : parentVal } 複製代碼
|| 、&&
代替
...? ... : ...
function getComponentName (opts: ?VNodeComponentOptions): ?string { return opts && (opts.Ctor.options.name || opts.tag) } /* 至關於 */ function getComponentName (opts: ?VNodeComponentOptions): ?string { return opts ? (opts.Ctor.options.name ? opts.Ctor.options.name : opts.tag) : opts } // 哪一個更簡潔 複製代碼
!!
將值轉爲
boolean
this.deep = !!options.deep
this.user = !!options.user this.lazy = !!options.lazy this.sync = !!options.sync 複製代碼
/* 命名時,文件內的代碼應與其功能或者業務模塊相關 */
├── scripts ------------------------------- 構建相關的文件 │ ├── git-hooks ------------------------- 存放git鉤子的目錄 │ ├── alias.js -------------------------- 別名配置 │ ├── config.js ------------------------- 生成rollup配置的文件 │ ├── build.js -------------------------- 對 config.js 中全部的rollup配置進行構建 │ ├── ci.sh ----------------------------- 持續集成運行的腳本 │ ├── release.sh ------------------------ 用於自動發佈新版本的腳本 ├── src ----------------------------------- 重要部分 │ ├── compiler -------------------------- 編譯模板 │ ├── core ------------------------------ 存放通用的,與平臺無關的代碼 │ │ ├── observer ---------------------- 觀測數據 │ │ ├── vdom -------------------------- 虛擬DOM │ │ ├── instance ---------------------- 構造函數 │ │ ├── global-api -------------------- Vue全局API │ │ ├── components -------------------- 通用組件 │ ├── server ---------------------------- 服務端渲染(server-side rendering) │ ├── platforms ------------------------- 包含平臺特有的相關代碼, │ │ ├── web --------------------------- web平臺 │ │ │ ├── entry-runtime.js ---------- 運行時構建的入口 │ │ │ ├── entry-runtime-with-compiler.js -- 獨立構建版本的入口,帶編譯 │ │ │ ├── entry-compiler.js --------- vue-template-compiler 包的入口文件 │ │ │ ├── entry-server-renderer.js -- vue-server-renderer 包的入口文件 │ │ │ ├── entry-server-basic-renderer.js -- 輸出 packages/vue-server-renderer/basic.js 文件 │ │ ├── weex -------------------------- 混合應用 │ ├── sfc ------------------------------- 包含單文件組件(.vue文件)的解析邏輯,用於vue-template-compiler包 │ ├── shared ---------------------------- 項目通用代碼 複製代碼
index
文件,將當前業務文件夾中模塊導入導出
/* util/index.js */
export * from './attrs' export * from './class' export * from './element' /* src/core/util/index.js */ export * from 'shared/util' export * from './lang' export * from './env' export * from './options' export * from './debug' export * from './props' export * from './error' export * from './next-tick' export { defineReactive } from '../observer/index' 複製代碼
判斷詞前綴 | 含義 | 栗子 |
---|---|---|
is |
是否 | isDef 、isTrue 、isFalse |
has |
有沒有 | hasOwnProperty 、hasProxy |
static |
是否靜態 | staticRoot 、staticInFor 、staticProcessed |
should |
應不該該 | shouldDecodeTags 、shouldDecodeNewlines 、shouldDecodeNewlinesForHref |
dynamic |
是否動態 | dynamicAttrs |
動詞前綴 | 含義 | 栗子 |
---|---|---|
init |
初始化 | initMixin |
merge |
合併 | mergeOptions |
compile |
編譯 | compileToFunctions |
validate |
校驗 | validateProp |
handle |
處理 | handleError |
update |
更新 | updateListeners |
create |
建立 | createOnceHandler |
/* const 代替 let */
/* better */ const opts const parentVnode const vnodeComponentOptions const superOptions const cachedSuperOptions /* bad*/ let opts let parentVnode let vnodeComponentOptions let superOptions let cachedSuperOptions 複製代碼
_
和
$
// Vue中通常以:
// _ 開頭表私有屬性或方法 // 以 $ 開頭表全局屬性或方法 declare interface Component { // public properties (表全局屬性) $el: any; // so that we can attach __vue__ to it $data: Object; $props: Object; $options: ComponentOptions; $parent: Component | void; $root: Component; // ...省略一部分 // public methods (表全局方法) $mount: (el?: Element | string, hydrating?: boolean) => Component; $forceUpdate: () => void; $destroy: () => void; $set: <T>(target: Object | Array<T>, key: string | number, val: T) => T; $delete: <T>(target: Object | Array<T>, key: string | number) => void; // ...省略一部分 // private properties (表私有屬性) _uid: number | string; _name: string; // this only exists in dev mode _isVue: true; _self: Component; _renderProxy: Component; // ...省略一部分 }; 複製代碼
if...else...
/* 閉合{} */
if (!valid) { warn( getInvalidTypeMessage(name, value, expectedTypes), vm ) return } 複製代碼
for
循環
/* 優先聲明key */
/* 緣由就一點:高效 */ var key; for (key in parent) { // 省略 } for (key in child) { // 省略 } /* 同時聲明 i,length */ function processAttrs (el) { var i, l, for (i = 0, l = list.length; i < l; i++) { // 省略 } } export function getAndRemoveAttr (): { const list = el.attrsList for (let i = 0, l = list.length; i < l; i++) { // 省略 } } 複製代碼
for...in...
循環中使用
hasOwnProperty
/* for...in...會遍歷對象原型鏈中的屬性或者方法 */
oldClassList.forEach(name => { const style = stylesheet[name] for (const key in style) { if (!result.hasOwnProperty(key)) { result[key] = '' } } }) 複製代碼
undefined
或者
null
export function isUndef (v: any): boolean %checks {
return v === undefined || v === null } 複製代碼
export function isPrimitive (value: any): boolean %checks {
return ( typeof value === 'string' || typeof value === 'number' || // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' ) } 複製代碼
export function isObject (obj: mixed): boolean %checks {
return obj !== null && typeof obj === 'object' } 複製代碼
export function isPlainObject (obj: any): boolean {
return _toString.call(obj) === '[object Object]' } 複製代碼
===
代替
==
/* 注意換行處邏輯運算符的位置 */
while ( (lastNode = el.children[el.children.length - 1]) && lastNode.type === 3 && lastNode.text === ' ' ) { // 省略 } function isPrimitive (value) { return ( typeof value === 'string' || typeof value === 'number' || // $flow-disable-line typeof value === 'symbol' || typeof value === 'boolean' ) } 複製代碼
/* shared/contants */
export const SSR_ATTR = 'data-server-rendered' export const ASSET_TYPES = [ 'component', 'directive', 'filter' ] export const LIFECYCLE_HOOKS = [ 'beforeCreate', 'created', 'beforeMount', 'mounted', 'beforeUpdate', 'updated', 'beforeDestroy', 'destroyed', 'activated', 'deactivated', 'errorCaptured', 'serverPrefetch' ] 複製代碼
/* 開關called保證代碼只會執行一次 */
export function once (fn: Function): Function { let called = false return function () { if (!called) { called = true fn.apply(this, arguments) } } } 複製代碼
/** * Check whether an object has the property. */ const hasOwnProperty = Object.prototype.hasOwnProperty export function hasOwn (obj: Object | Array<*>, key: string): boolean { return hasOwnProperty.call(obj, key) } 複製代碼
userAgent
進行瀏覽器嗅探,能夠判斷當前設備環境,而進行合理的優化,兼容
/* src/core/util/env.js */
UA = inBrowser && window.navigator.userAgent.toLowerCase() 複製代碼
Javascript
對象原有屬性、方法(不是你的對象不要動)
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original method const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { // 注意這裏並無改變數組原有方法,只不過改變的是this指向 const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { // ...省略的代碼 } return result }) }) 複製代碼
/* 下面的filter會過濾掉轉爲false*/
export function pluckModuleFunction ( modules key ){ return modules ? modules.map(m => m[key]).filter(_ => _) : [] } 複製代碼
Javascript
閉包及柯里化
/** * Vue的compiler利用了函數柯里化及閉包的原理,實現將公共配置緩存, * 根據不一樣平臺須要而進行compiler的功能, * 感興趣的能夠閱讀源碼,體驗Vue設計之美 */ /* src/compile/index.js */ /* 源碼註釋說明能夠根據不一樣環境進行編譯 */ // `createCompilerCreator` allows creating compilers that use alternative // parser/optimizer/codegen, e.g the SSR optimizing compiler. // Here we just export a default compiler using the default parts. export const createCompiler = createCompilerCreator(function baseCompile ( template: string, options: CompilerOptions ) { // 省略的代碼 }) /* src/compile/create-compiler.js */ import { createCompileToFunctionFn } from './to-function' export function createCompilerCreator (baseCompile: Function): Function { return function createCompiler (baseOptions: CompilerOptions) { function compile ( template: string, options?: CompilerOptions ): CompiledResult { //... 省略的代碼 const compiled = baseCompile(template.trim(), finalOptions) return { compile, compileToFunctions: createCompileToFunctionFn(compile) } } } /* src/compile/to-function.js */ export function createCompileToFunctionFn (compile: Function): Function { const cache = Object.create(null) return function compileToFunctions ( template: string, options?: CompilerOptions, vm?: Component ): CompiledFunctionResult { // ... 省略的代碼 } } 複製代碼
在
Javascript
中函數爲一等公民,能夠說一箇中初級前端程序員在僅使用函數的狀況下就可完成平常開發任務。可見函數在Javascript
中有多麼強大。可是函數使用也須要規範。在閱讀Vue
源碼的過程當中,筆者就體會到尤大對函數解耦,單一職責,封裝使用的美妙之處。就拿Vue
初始化舉例:vue
// 一目瞭然, 保證你本身都看的明白
/* src/core/instance/init.js */ initLifecycle(vm) initEvents(vm) initRender(vm) callHook(vm, 'beforeCreate') initInjections(vm) // resolve injections before data/props initState(vm) initProvide(vm) // resolve provide after data/props callHook(vm, 'created') 複製代碼
PS: 源碼中的精髓之處不止筆者羅列的這些,筆者後期還會慢慢補充,羅列。node
人的一輩子中關鍵的就那麼幾步,特別是在年輕的時候。——路遙react
參考資料:git
本文使用 mdnice 排版程序員