Недавно автор читал исходный код Vue 2.6.11.В процессе чтения я не только осознал красоту компонентного и адаптивного к данным дизайна Vue, но и вздохнул от спецификации и элегантности кода Youda. Итак, вот сводки, перечисленные один за другим, чтобы новички могли читать и писать более сложный код. Старик посмотрел и пошел дальше.
Дружеское напоминание: обратите внимание на китайские аннотации
- однострочный комментарий
/* 声明变量位于声明之后 */
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В процессе исходного кода автор осознал красоту разделения функций, единой ответственности и инкапсуляции в Yuda. просто возьми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: Суть исходников не только перечисляет автор, но автор будет постепенно добавлять и перечислять их позже.
В жизни всего несколько важных шагов, особенно в молодом возрасте. - Лу Яо
Использованная литература:
- vue2.6.11
- "Написание поддерживаемого Javascript"
- «Рефакторинг: улучшение дизайна существующего кода»
В этой статье используетсяmdniceнабор текста