Inside and outside the Great Wall

The author recently read Vue2.6.11 source code, in the process of reading, not only realize the Vue component and data response type of the design of the United States, but also sigh at the specification, elegant especially large lu code. So here is a summary of them listed, to ensure that novice read, write more sophisticated code. The old man looked and went one step further.

Friendly note: pay attention to Chinese notes

  • Single-line comments
/* Declare variables after the declaration */ 
vm._vnode = null // the root of the child tree
vm._staticTrees = null // v-once cached trees

/* Use comments in the function to note the indentation */ 
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 the returned array contains only a single node, allow it  if (Array.isArray(vnode) && vnode.length === 1) {  vnode = vnode[0] }  if (res.iterator2) {  // (after 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) }  /* The function of a variable or parameter is commented */ 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 Copy the code
  • Block comments
/* Before exporting the module */ 
/ * * * 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 ) {  // The omitted code }  /* Before the function declaration */ / * * * Convert an input value to a number for persistence.  * If the conversion fails, return original string. * / function toNumber (val) {  // The omitted code } Copy the code
  • Note that a newline
/* Each line is about 75 bytes at most */

/* Single-line comment newline */
// 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)   /* Multiline comment newline */ / * * * Collect dependencies on array elements when the array is touched, since  * we cannot intercept array element access like property getters. * / function dependArray (value) {  // The omitted code } Copy the code
  • String concatenation newline, notice+location
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 ) Copy the code
  • Multiple ternary operators are used
/* 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 } Copy the code
  • | |, &&Instead of. ? . :...

function getComponentName (opts: ? VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}

/* is the same as */ function getComponentName (opts: ? VNodeComponentOptions): ?string {  return opts ? (opts.Ctor.options.name ? opts.Ctor.options.name : opts.tag) : opts } // Which is more concise Copy the code
  • use!!!!!A value toboolean
this.deep = !! options.deepthis.user = !! options.userthis.lazy = !! options.lazythis.sync = !! options.syncCopy the code
  • The file name
/* When naming, the code in the file should be related to its function or business module */

├ ─ ─ scripts -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- build related files│ ├ ─ ─ git - hooks -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- storage directory git hook│ ├─ Alias. Js -------------------------- Alias Configuration│ ├ ─ ─ config. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- to generate a rollup configuration file│ ├ ─ ─ build. Js -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - to config. All the rollup in the js configuration to build│ ├ ─ ─ ci. Sh -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- a continuous integration run script│ ├ ─ ─ the sh -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- used to automatically release the new version of the script├─ SRC ----------------------------------- Important part│ ├── Compiler --------------------------│ ├ ─ ─ the core -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- for universal, platform-independent code│ ├─ Observer ----------------------│ ├─ VDOM -------------------------- virtual DOM│ ├─ instance ---------------------- constructor│ ├─ Global API -------------------- Vue│ ├─ Components --------------------│ ├ ─ ─ server -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the service side rendering (server side rendering)│ ├ ─ ─ platforms -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains platform-specific code,│ ├─ Web ---------------------------│ │ ├─ Entry-Runtime.js ---------- Runtime Build entry│ │ ├─ Entry-Runtime -with- Compiler.js - entry to a independently built version, with compilation│ │ ├─ Entry-Compiler. js --------- Vue-template-Compiler package entry file│ │ ├─ Entry-server-renderer. Js - Vue-server-renderer│ │ ├─ Entry-server-basic.js - Output packages/ Jue-server-renderer /basic.js│ ├─ Weex -------------------------- Mixed application│ ├ ─ ─ SFC -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - contains a single file components (. Vue file) parsing logic, used in vue - the template - the compiler package│ ├─ Shared ---------------------------- common codeCopy the code
  • The rational use ofindexFile to import and export modules from the current service folder
/* 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' Copy the code
  • Variable and function naming
Judgment prefix meaning chestnuts
is Whether or not isDef,isTrue,isFalse
has Is there any hasOwnProperty,hasProxy
static Whether the static staticRoot,staticInFor,staticProcessed
should should shouldDecodeTags,shouldDecodeNewlines,shouldDecodeNewlinesForHref
dynamic Whether the dynamic dynamicAttrs
Verb prefix meaning chestnuts
init Initialize the initMixin
merge merge mergeOptions
compile compile compileToFunctions
validate check validateProp
handle To deal with handleError
update update updateListeners
create create createOnceHandler
  • Variable declarations
/* const instead of let */
/* better */
const opts 
const parentVnode 
const vnodeComponentOptions 
const superOptions const cachedSuperOptions  /* bad*/ let opts let parentVnode let vnodeComponentOptions let superOptions let cachedSuperOptions Copy the code
  • Use variable prefixes wisely_and$
// Vue:
// _ Start table private attributes or methods
// List global attributes or methods starting with $
declare interface Component {
    
 Public properties (table global properties)  $el: any; // so that we can attach __vue__ to it  $data: Object;  $props: Object;  $options: ComponentOptions;  $parent: Component | void;  $root: Component;  / /... Omit part   // public methods (table global)  $mount: (el? : Element | string, hydrating? : boolean) = > Component;  $forceUpdate: (a)= > void;  $destroy: (a)= > void;  $set: <T>(target: Object | Array<T>, key: string | number, val: T) => T;  $delete: <T>(target: Object | Array<T>, key: string | number) => void; / /... Omit part // private properties (table private) _uid: number | string;  _name: string; // this only exists in dev mode  _isVue: true;  _self: Component;  _renderProxy: Component; / /... Omit part};  Copy the code
  • if... else...
/* Close {} */
if(! valid) {    warn(
      getInvalidTypeMessage(name, value, expectedTypes),
      vm
 )  return } Copy the code
  • forcycle
/* Priority declaration key */
/* One reason: efficiency */
var key;
for (key in parent) {
   / / to omit
} for (key in child) {  / / to omit }   /* declare I,length */ function processAttrs (el) {  var i, l,  for (i = 0, l = list.length; i < l; i++) {  / / to omit  } } export function getAndRemoveAttr () :{  const list = el.attrsList  for (let i = 0, l = list.length; i < l; i++) {  / / to omit  } } Copy the code
  • for... in...Use in circulationhasOwnProperty
/* for... in... Iterates through properties or methods */ in the object prototype chain
oldClassList.forEach(name= > {
    const style = stylesheet[name]
    for (const key in style) {
      if(! result.hasOwnProperty(key)) { result[key] = ' '  }  } }) Copy the code
  • detectionundefinedornull
export function isUndef (v: any) :boolean %checks {
  return v === undefined || v === null
}
Copy the code
  • Detecting primitive types
export function isPrimitive (value: any) :boolean %checks {
  return (
    typeof value === 'string' ||
    typeof value === 'number' ||
    // $flow-disable-line
 typeof value === 'symbol' ||  typeof value === 'boolean'  ) } Copy the code
  • Detection object
export function isObject (obj: mixed) :boolean %checks {
  returnobj ! = =null && typeof obj === 'object'
}
Copy the code
  • Detection pure object
export function isPlainObject (obj: any) :boolean {
  return _toString.call(obj) === '[object Object]'
}
Copy the code
  • = = =Instead of= =
/* Note the position of the logical operator at the newline */
while (
 (lastNode = el.children[el.children.length - 1&&]) lastNode.type === 3 &&
 lastNode.text === ' '
 ) {  / / to omit }  function isPrimitive (value) {  return (  typeof value === 'string' ||  typeof value === 'number' ||  // $flow-disable-line  typeof value === 'symbol' ||  typeof value === 'boolean'  ) } Copy the code
  • Public constants are extracted
/* 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' ]  Copy the code
  • Use the switch
/* Switch called to ensure that the code is executed only once */
export function once (fn: Function) :Function {
  let called = false
  return function () {
    if(! called) { called = true  fn.apply(this.arguments)  }  } } Copy the code
  • Checks whether the object is an original property
/ * * * 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) } Copy the code
  • useuserAgentBrowser sniffing, you can judge the current device environment, and reasonable optimization, compatible
/* src/core/util/env.js */
UA = inBrowser && window.navigator.userAgent.toLowerCase()
Copy the code
  • keepJavascriptObject properties, methods (do not touch objects that are not your own)
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) {  // Note that there is no change to the array's original method, just to change the this pointer  const result = original.apply(this, args)  const ob = this.__ob__  let inserted  switch (method) {  / /... Omitted code  }  return result  }) }) Copy the code
  • Using type conversions
/* The following filter will be changed to false*/
export function pluckModuleFunction (
  modules
 key ){
 return modules  ? modules.map(m= > m[key]).filter(_= > _)  : [] } Copy the code
  • usingJavascriptClosures and Currification
/ * ** Vue compiler leverages the principles of function currization and closures to implement common configuration caches,* Function of Compiler according to different platform needs;* Interested can read the source code, experience the beauty of Vue design* /
 /* src/compile/index.js */ /* You can compile for different environments */ // `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 ) {  // The omitted code })  /* 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 {  / /... Omitted code  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 {  / /... Omitted code  } }  Copy the code
  • Functions keep a single responsibility, decoupled

Functions are first-class citizens in Javascript, and it can be said that a mid-level front-end programmer can accomplish daily development tasks using only functions. You can see how powerful functions are in Javascript. But function use also needs specification. In the process of reading Vue source code, THE author realized the beauty of the function decoupling, single responsibility, encapsulation. Take Vue initialization as an example:

// Make sure you understand it at a glance
/* 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') Copy the code

PS: the essence of the source code more than the author listed these, the author will slowly add later, listed.

There are only a few key steps in life, especially when you are young. – lu yao

References:

  • vue2.6.11
  • “Writing Maintainable Javascript”
  • Refactoring: Improving the Design of Existing Code

This article is formatted using MDNICE