一.AI前端CSS规范 每个属性声明后面都要加分号。
不使用id选择器
适用有意义的名词命名
单词全部小写,名词超过1个时,使用-
分隔符
# 属性声明顺序原则:整体到局部,外部到内部,重要属性优先
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 .element { display : block; float : left; position : absolute; top : 0 ; right : 0 ; bottom : 0 ; left : 0 ; z-index : 100 ; margin : 0 100px ; padding : 50px ; // padding 习惯写到margin 后面 width : 100px ; height : 100px ; border : 1px solid #e5e5e5 ; border-radius : 3px ; font : normal 13px "Helvetica Neue" , sans-serif; color : #333 ; text-align : center; line-height : 1.5 ; background-color : #f5f5f5 ; opacity : 1 ; } Copied!
# 其他规范使用prettier格式化工具约束,推荐配置如下:
格式自动化
4个缩进
全部单引号
属性:
后有空格
颜色全部小写
小数点前面0自动添加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 module .exports = { printWidth : 100 , tabWidth : 4 , useTabs : false , semi : false , singleQuote : true , trailingComma : 'none' , bracketSpacing : true , arrowParens : 'avoid' , jsxBracketSameLine : true , rangeStart : 0 , rangeEnd : Infinity , filepath : 'none' , requirePragma : false , insertPragma : false , proseWrap : 'preserve' } Copied!
# 参考连接百度CSS规范指南(opens new window)
腾讯CSS规范指南(opens new window)
Google CSS规范指南
二、AI前端Git规范 # Git分支命名
master:主分支,负责记录上线版本的迭代,该分支代码与线上代码是完全一致的。
develop:开发分支,该分支记录相对稳定的版本,所有的feature分支和bugfix分支都从该分支创建。其它分支为短期分支,其完成功能开发之后需要删除
feature/*:特性(功能)分支,用于开发新的功能,不同的功能创建不同的功能分支,功能分支开发完成并自测通过之后,需要合并到 develop 分支,之后删除该分支。
bugfix/*:bug修复分支,用于修复不紧急的bug,普通bug均需要创建bugfix分支开发,开发完成自测没问题后合并到 develop 分支后,删除该分支。
release/*:发布分支,用于代码上线准备,该分支从develop分支创建,创建之后由测试同学发布到测试环境进行测试,测试过程中发现bug需要开发人员在该release分支上进行bug修复,所有bug修复完后,在上线之前,需要合并该release分支到master分支和develop分支。
hotfix/*:紧急bug修复分支,该分支只有在紧急情况下使用,从master分支创建,用于紧急修复线上bug,修复完成后,需要合并该分支到master分支以便上线,同时需要再合并到develop分支。
img
# Git Commit Message格式type : subject
# type 提交类型:
feature: 新特性
fix: 修改问题
style: 代码格式修改
test: 测试用例修改
docs: 文档修改
refactor: 代码重构
misc: 其他修改, 比如构建流程, 依赖管理
# subject 提交描述对应内容是commit 目的的简短描述,一般不超过50个字符
# 参考链接
三、AI前端JS规范 命名方式:小驼峰
命名规范:前缀名词
1 2 3 4 5 6 7 8 let setCount = 10 let maxCount = 10 Copied!
命名方式:全部大写
命名规范:多个单词时使用分隔符_
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const serverErrorCode = { success : 200 , repeat : 444 }const SERVER_ERROR_CODE = { SUCCESS : 200 , REPEAT : 444 } Copied!
命名方式:小驼峰
命名规范:前缀动词
1 2 3 4 5 6 7 8 function wordClass ( ) {}function saveWordClass ( ) {} Copied!
常用动词:can、has、is、load、get、set
命名方式:大驼峰
命名规范:前缀名词
1 2 3 4 5 6 7 8 class person {}class Person {} Copied!
1 2 3 4 5 let maxCount = 123 Copied!
# 减少嵌套确定条件不允许时,尽早返回。经典使用场景:校验数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (condition1) { if (condition2) { ... } }if (!condition1) return if (!condition2) return ... Copied!
# 减少特定标记值使用常量进行自解释
1 2 3 4 5 6 7 8 9 10 11 12 13 type : 1 const MODIFY_TYPE = { ADD : 1 , EDIT : 2 }type : MODIFY_TYPE.ADD Copied!
# 表达式尽可能简洁表达式
1 2 3 4 5 6 7 8 9 10 11 12 if (name === '' ){}if (collection.length > 0 ){}if (notTrue === false ){}if (!name) {}if (collection.length){}if (notTrue){} Copied!
# 分支较多处理对于相同变量或表达式的多值条件,用switch
代替if
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 let type = typeof variableif (type === 'object' ) { }else if (type === 'number' || type === 'boolean' || type === 'string' ) { }switch (typeof variable) { case 'object' : break case 'number' : case 'boolean' : case 'string' : break } Copied!
# 使用变量名自解释 V1.1
逻辑复杂 时,建议使用变量名自解释,而不是晦涩难懂的简写。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function (value ) { return !helpers.req(value) || this .entity.entVocabularyEntries.filter(item => item.vocabularyEntryName === value).length < 2 }function (value ) { let entVocabularyList = this .entity.entVocabularyEntries let repeatCount = entVocabularyList.filter(item => item.vocabularyEntryName === value).length return !helpers.req(value) || repeatCount < 2 } Copied!
# 使用函数名自解释 V1.1
遵循单一职责 的基础上,可以把逻辑隐藏在函数中,同时使用准确的函数名自解释。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 if (modifyType === MODIFY_TYPE.ADD) { batchVariableAPI(data).then(() => { this .closeModal() this .$toast.show('添加变量成功' ) }) } else { updateVariableAPI(data).then(() => { this .closeModal() this .$toast.show('修改变量成功' ) }) } modifyType === MODIFY_TYPE.ADD ? this ._insertVariable(data) : this ._updateVariable(data)_insertVariable ( ) { batchVariableAPI(data).then(() => this ._successOperation('添加变量成功' )) }_updateVariable ( ) { updateVariableAPI(data).then(() => this ._successOperation('修改变量成功' )) }_successOperation (toastMsg ) { this .closeModal() this .$toast.show(toastMsg) } Copied!
# 其他规范使用prettier格式化工具以及eslint校验
格式自动化
4个缩进
全部单引号
方法if / else / for / while / function / switch / do / try / catch / finally 关键字后有一个空格
自动省略分号
.prettierrc配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 module .exports = { printWidth : 100 , tabWidth : 4 , useTabs : false , semi : false , singleQuote : true , trailingComma : 'none' , bracketSpacing : true , arrowParens : 'avoid' , jsxBracketSameLine : true , rangeStart : 0 , rangeEnd : Infinity , filepath : 'none' , requirePragma : false , insertPragma : false , proseWrap : 'preserve' } Copied!
.eslintrc.js规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 module .exports = { root : true , env : { browser : true , node : true }, extends : ['plugin:vue/essential' ], parserOptions : { parser : 'babel-eslint' }, plugins : ['vue' ], rules : { 'arrow-parens' : 0 , 'generator-star-spacing' : 0 , 'no-unused-vars' : 'error' , 'no-debugger' : process.env.NODE_ENV === 'production' ? 'error' : 'off' , 'indent' : [2 , 4 , { SwitchCase : 1 }], 'space-before-function-paren' : ['error' , 'never' ], } } Copied!
如果是vue-cli3项目,以上配置的eslint插件默认已安装;如果不是vue-cli3项目,需要npm安装对应包:npm install –save-dev babel-eslint eslint-plugin-vue
# 参考链接百度JS规范
四、AI前端Vue规范 # 文件命名统一小写,多个单词作为文件名使用分隔符-
1 2 3 4 5 6 7 8 9 EntityList.vue entityList.vue entity-list.vue Copied!
# 紧密耦合的组件命名和父组件紧密耦合的子组件应该以父组件名作为前缀命名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 components/ |- todo-list.vue |- todo-item.vue └─ todo-button.vue components/ |- todo-list.vue |- todo-list-item.vue └─ todo-list-item-button.vue Copied!
# 自闭合组件在单文件组件中没有内容的组件应该是自闭合的
1 2 3 4 5 6 7 8 <u-input > </u-input > <u-input /> Copied!
# 指令缩写用:
表示 v-bind
: ,用@
表示v-on
1 2 3 4 5 6 7 8 <input v-bind:value ="value" v-on:input ="onInput" > <input :value ="value" @input ="onInput" > Copied!
# 组件数据组件的 data 必须是一个函数,并且建议在此不使用箭头函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 export default { data : () => ({ foo : 'bar' }) }export default { data () { return { foo : 'bar' } } } Copied!
# props命名小驼峰命名。内容尽量详细,至少有默认值
1 2 3 4 5 6 7 8 greeting-text: String greetingText : { type : String , default : '' } Copied!
# 组件属性顺序和分行规则顺序原则:重要属性放前面
顺序依据:依次指令
->props属性
-> 事件
->dom属性(class有标记作用,除外)
分行规则:放在一行,重要内容较多时,可放置2~3行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <u-select class ="select" size ="s" @select ="searchEntity($event, row)" @blur ="searchEntity($event, row)" v-model ="row.variableId" :list ="variableList" /> <u-select v-model ="row.variableId" :list ="variableList" size ="s" @select ="searchEntity($event, row)" @blur ="searchEntity($event, row)" class ="select" /> Copied!
# Vue API顺序1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 export default { name : '' , extends : '' , mixins : [], components : {}, props : {}, model : { prop : '' , event : '' }, data () { return {} }, computed : {}, watch :{}, filters : {}, directives : {}, created () {}, mounted () {}, destroy () {}, methods : {}, } Copied!
# Vue组件顶级标签顺序顺序保持一致,且标签之间留有空行。template第一层级下四个空格,script和style第一层级都不加空格
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template > <div > </div > </template > <script > export default {}</script > <style > .app {}</style > Copied!
# import引入顺序 V1.1
原则:同等类型的放一起,优先放mixins和components等UI资源。忌随意放置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { getAllEntityList, getVariableGroup } from '@/server/api' import { helpers } from 'vuelidate/lib/validators' import { getRepeatLine } from '@/utils/common' import { CloseModalMixin, InvalidCheckMixin } from '@/components/common/mixins' import VSearchSelect from '@/components/variable/v-search-select' import EModifyModal from '@/components/entity/e-modify-modal' import { MODIFY_MODAL_TYPE } from '@/utils/config' import { botIdLoc, custIdLoc } from '@/utils/locs' import { CloseModalMixin, InvalidCheckMixin } from '@/components/common/mixins' import VSearchSelect from '@/components/variable/v-search-select' import EModifyModal from '@/components/entity/e-modify-modal' import { getAllEntityList, getVariableGroup } from '@/server/api' import { helpers } from 'vuelidate/lib/validators' import { MODIFY_MODAL_TYPE } from '@/utils/config' import { getRepeatLine } from '@/utils/common' import { botIdLoc, custIdLoc } from '@/utils/locs' Copied!
# Vue 复杂data加注释/分组 V1.1
data数据是连接View和Modal的基础,当ViewModal复杂时,建议进行注释并分组。另外,当data过于复杂时应考虑优化重构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 data ( ) { return { isOpenModal : false , list : [], groupList : [], searchParams : { groupId : '' , searchParam : '' , searchType : '' }, pageParam : { currentPage : 1 , pageSize : 50 }, totalCount : 0 , groupId : '' , typeList : [], defaultType : 'paramName' } }data ( ) { return { variableList : [], groupList : [], typeList : [], searchParams : { groupId : '' , searchParam : '' , searchType : '' , currentPage : 1 , pageSize : 50 }, totalCount : 0 , defaultType : '' , isOpenModal : false } } Copied!
五、AI前端工程工具链 为了提高整体开发效率,需要定制一些vue/electron/官网等脚手架工具。首先要将一些代码规范考虑在内,需要保持git仓库的代码就像是一个人写出来的。根据团队习惯,考虑后使用组合工具:
规范提交代码(必需) :eslint
+ stylelint
+ prettier
+ husky
+ lint-staged
。
规范commit日志(必需) :commitlint
+ commitizen
+ cz-conventional-changelog
+ conventional-changelog-cli
规范提交流程(github项目) :release-it
+ gh-pages
。
辅助代码分析(可选) :jsinspect
+ jscpd
# 1. 规范提交代码针对写出符合团队代码规范的js、css代码
eslint : 对js做规则约束。强制校验
stylelint : 对css做规则约束
prettier : 代码格式化。强制格式化
**husky + lint-staged (opens new window) **:本地git钩子工具
# 1.1.1 安装1 2 3 4 npm install --save-dev eslint eslint-plugin-vue babel-eslint Copied!
# 1.1.2 .eslintrc.js
配置1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 module .exports = { root : true , env : { browser : true , node : true }, parserOptions : { parser : 'babel-eslint' }, extends : [ 'plugin:vue/essential' ], plugins : [ 'vue' ], rules : { 'generator-star-spacing' : 'off' , 'no-debugger' : process.env.NODE_ENV === 'production' ? 'error' : 'off' , 'indent' : [2 , 4 , { 'SwitchCase' : 1 }], ... } } Copied!
# 1.1.3 提交前强制校验将约束命令放置在提交代码前检查,这就要使用husky (opens new window) + lint-staged (opens new window) 工具,工具能在提交代码precommit时调用钩子命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 npm install husky lint-staged -D Copied! "scripts" : { "lint" : "eslint --ext .js,.vue --ignore-path .gitignore ." , },"gitHooks" : { "pre-commit" : "lint-staged" },"lint-staged" : { "*.{js,vue,ts}" : [ "npm run lint" ] }, Copied!
TIP
如果是vue-cli3项目创建的项目,以上配置都自动生成了,直接执行vue-cli-service lint即可。
# 1.2.1 安装1 2 3 4 npm install --save-dev prettier Copied!
# 1.2.2. .prettierrc.js
配置以下感谢团队伙伴@Birttany的配置说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 module .exports = { printWidth : 100 , tabWidth : 4 , useTabs : false , semi : false , singleQuote : true , trailingComma : 'none' , bracketSpacing : true , arrowParens : 'avoid' , parser : 'babylon' , jsxBracketSameLine : true , rangeStart : 0 , rangeEnd : Infinity , filepath : 'none' , requirePragma : false , insertPragma : false , proseWrap : 'preserve' } Copied!
# 1.2.3 提交前强制格式化在提交git时需要对整个项目执行format
格式化,使得代码强制统一。格式化之后再用eslint
检查语法错误,无误后把格式化后的代码用git add .
添加进入。如果有错误直接中断提交。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 "scripts" : { "lint" : "eslint --ext .js,.vue --ignore-path .gitignore ." , },"gitHooks" : { "pre-commit" : "lint-staged" },"lint-staged" : { "*.{js,vue,ts}" : [ "prettier --write" , "npm run lint" ] }, Copied!
# 1.3 stylelint参见另一篇博客(opens new window)
# 2. 规范commit日志(必需)在多人协作的项目中,如果Git的提交说明精准,在后期协作以及Bug处理时会变得有据可查,项目的开发可以根据规范的提交说明快速生成开发日志,从而方便开发者或用户追踪项目的开发信息和功能特性。
规范的Git提交说明:
提供更多的历史信息,方便快速浏览
可以过滤某些commit,便于筛选代码review
可以追踪commit生成更新日志
可以关联issues
Git提交说明结构:
Git提交说明可分为三个部分:Header、Body和Footer。
1 2 3 4 5 6 7 8 <Header > <Body > <Footer > Copied!
Header部分包括三个字段type(必需)、scope(可选)和subject(必需)。
1 2 3 4 <type > (<scope > ): <subject > Copied!
type用于说明 commit 的提交性质。(Angular规范)
feat 新增一个功能
fix 修复一个Bug
docs 文档变更
style 代码格式(不影响功能,例如空格、分号等格式修正)
refactor 代码重构
perf 改善性能
test 测试
build 变更项目构建或外部依赖(例如scopes: webpack、gulp、npm等)
ci 更改持续集成软件的配置文件和package中的scripts命令,例如scopes: Travis, Circle等
chore 变更构建流程或辅助工具
revert 代码回退
约定commit message提交的格式 。
# 2.1.1 安装1 2 3 4 npm install --save-dev @commitlint/config-conventional @commitlint/cli Copied!
# 2.1.2 配置在根目录执行以下命令作为commitlint的配置
1 2 3 4 echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js Copied!
# 2.1.2 提交前校验1 2 3 4 5 6 7 8 "husky" : { "hooks" : { "commit-msg" : "commitlint -E HUSKY_GIT_PARAMS" } } Copied!
# 2.2 commitizen cz-conventional-changelogcommitlint约定了提交的格式,但每次书写都需要记住那些约定,增加记忆负担。所以使用cz工具让提交commit过程更加可视化 。
commitizen/cz-cli是一个可以实现规范的提交说明的工具,提供选择的提交信息类别,快速生成提交说明。
# 2.2.1 安装1 2 3 4 npm i commitizen cz-conventional-changelog -D Copied!
# 2.2.2 配置使用cz-conventional-changelog适配器(介于AngularJS规范),约定提交代码时的选项。
1 2 3 4 5 6 7 8 "config" : { "commitizen" : { "path" : "./node_modules/cz-conventional-changelog" } } Copied!
# 2.2.3 命令执行以后git commit都使用npm run commit(即git-cz)代替
1 2 3 4 5 6 "scripts" : { "commit" : "git add -A && git-cz" }, Copied!
commitlint + commitizen cz-conventional-changelog + conventional-changelog-cli
自动根据commit生成changelog 。
良好的changelog可以让用户一眼知道组件库的版本迭代演进,也是长期维护项目的必备内容。
# 2.3.1 安装1 2 3 4 npm install conventional-changelog-cli -D Copied!
# 2.3.2 命令执行1 2 3 4 5 6 "scripts" : { "changelog" : "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" }, Copied!
TIP
每次发布组件包版本后,即可使用npm run changelog命令,自动根据git commit生成日志。
# 3. 规范提交流程(可选)针对github + npm项目,可以简化流程。内部非开源项目可跳过该内容。
# 3.1 安装1 2 3 4 npm i gh-pages release-it -D Copied!
# 3.2 命令执行1 2 3 4 5 6 7 "scripts" : { "release" : "release-it" , "release:docs" : "npm run build:docs && gh-pages -d docs/.vuepress/dist }, Copied!
# 4. 辅助代码分析(可选)敏捷开发过程中,代码复查是至关重要的一环,团队需要使用工具辅助代码分析。经比较和实践后,使用工具:jsinspect
+ jscpd
jsinspect : 对js或jsx代码做重复检测。
jscpd : 对代码重复率进行报告总结,辅助代码复查
# 4.1.1 安装1 2 3 4 npm install jsinspect --save-dev Copied!
# 4.1.2 提交前强制校验1 2 3 4 5 6 "scripts" : { "inspect" : "jsinspect -t 50 ./src" , } Copied!
# 4.2.1 安装1 2 3 4 npm install jscpd --save-dev Copied!
# 4.2.2 代码复查辅助命令1 2 3 4 5 6 "scripts" : { "codereview" : "jscpd ./src" } Copied!
eslint + husky + lint-staged + prettier作为基础工具。
commitlint约束提交规范,commitizen让工具通过select选项,进行git commit, conventional-changelog-cli自动根据git commit生成changelog文件。
release-it核心是可以把包自动发送到npm上,gh-pages把指定目录发布到github gh-pages分支上。
配置好以上工具,开发组件库时常用三个命令:
平时开发提交commit: npm run commit “commit”: “git add -A && git-cz”,
发布npm包: npm publish “prepublish”: “npm run build && release-it && npm run changelog”,
发布docs: npm run docs “release:docs”: “npm run build:docs && gh-pages -d docs/.vuepress/dist”,
以下示例个实际github项目配置: lq782655835/yi-ui(opens new window)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 "scripts" : { "commit" : "git add -A && git-cz" , "changelog" : "conventional-changelog -p angular -i CHANGELOG.md -s -r 0" , "release" : "release-it" , "lint" : "vue-cli-service lint" },"husky" : { "hooks" : { "pre-commit" : "lint-staged" , "commit-msg" : "commitlint -E HUSKY_GIT_PARAMS" } },"lint-staged" : { "packages/**/*.{vue,ts,tsx,js,jsx,json}" : [ "prettier --write" , "npm run lint" ] },"config" : { "commitizen" : { "path" : "./node_modules/cz-conventional-changelog" } } Copied!
六、AI JavaScript 风格指南 Original Repository: ryanmcdermott/clean-code-javascript(opens new window)
笔者注:本文原文地址 (opens new window) ,作者根据 Robert C. Martin 《代码整洁之道》总结了适用于 JavaScript 的软件工程原则。该风格指南全面而实用,非常适合作为前端团队代码质量参考标准,也为新融入团队的同学提供一份代码质量指南。
1.# 目录
介绍
变量
函数
对象和数据结构
类
测试
并发
错误处理
格式化
注释
2.# 介绍 作者 (opens new window) 根据 Robert C. Martin 《代码整洁之道》 (opens new window) 总结了适用于 JavaScript 的软件工程原则《Clean Code JavaScript》 (opens new window) 。
本文是对其的翻译。
不必严格遵守本文的所有原则,有时少遵守一些效果可能会更好,具体应根据实际情况决定。这是根据《代码整洁之道》作者多年经验整理的代码优化建议,但也仅仅只是一份建议。
软件工程已经发展了 50 多年,至今仍在不断前进。现在,把这些原则当作试金石,尝试将他们作为团队代码质量考核的标准之一吧。
最后你需要知道的是,这些东西不会让你立刻变成一个优秀的工程师,长期奉行他们也并不意味着你能够高枕无忧不再犯错。千里之行,始于足下。我们需要时常和同行们进行代码评审,不断优化自己的代码。不要惧怕改善代码质量所需付出的努力,加油。
3.# 变量 # 使用有意义,可读性好的变量名反例 :
1 2 3 4 var yyyymmdstr = moment().format('YYYY/MM/DD' ); Copied!
正例 :
1 2 3 4 var yearMonthDay = moment().format('YYYY/MM/DD' ); Copied!
回到目录
# 使用 ES6 的 const 定义常量反例中使用”var”定义的”常量”是可变的。
在声明一个常量时,该常量在整个程序中都应该是不可变的。
反例 :
1 2 3 4 var FIRST_US_PRESIDENT = "George Washington" ; Copied!
正例 :
1 2 3 4 const FIRST_US_PRESIDENT = "George Washington" ; Copied!
回到目录
# 对功能类似的变量名采用统一的命名风格反例 :
1 2 3 4 5 6 getUserInfo(); getClientData(); getCustomerRecord(); Copied!
正例 :
回到目录
# 使用易于检索名称我们需要阅读的代码远比自己写的要多,使代码拥有良好的可读性且易于检索非常重要。阅读变量名晦涩难懂的代码对读者来说是一种相当糟糕的体验。 让你的变量名易于检索。
反例 :
1 2 3 4 5 6 7 for (var i = 0 ; i < 525600 ; i++) { runCronJob(); } Copied!
正例 :
1 2 3 4 5 6 7 8 var MINUTES_IN_A_YEAR = 525600 ;for (var i = 0 ; i < MINUTES_IN_A_YEAR; i++) { runCronJob(); } Copied!
回到目录
# 使用说明变量(即有意义的变量名)反例 :
1 2 3 4 5 const cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/ ; saveCityState(cityStateRegex.match(cityStateRegex)[1 ], cityStateRegex.match(cityStateRegex)[2 ]); Copied!
正例 :
1 2 3 4 5 6 7 8 9 const ADDRESS = 'One Infinite Loop, Cupertino 95014' ;var cityStateRegex = /^(.+)[,\\s]+(.+?)\s*(\d{5})?$/ ;var match = ADDRESS.match(cityStateRegex)var city = match[1 ];var state = match[2 ]; saveCityState(city, state); Copied!
回到目录
# 不要绕太多的弯子显式优于隐式。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 var locations = ['Austin' , 'New York' , 'San Francisco' ]; locations.forEach((l ) => { doStuff(); doSomeOtherStuff(); ... ... ... dispatch(l); }); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 var locations = ['Austin' , 'New York' , 'San Francisco' ]; locations.forEach((location ) => { doStuff(); doSomeOtherStuff(); ... ... ... dispatch(location); }); Copied!
回到目录
# 避免重复的描述当类/对象名已经有意义时,对其变量进行命名不需要再次重复。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 var Car = { carMake : 'Honda' , carModel : 'Accord' , carColor : 'Blue' };function paintCar (car ) { car.carColor = 'Red' ; } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 var Car = { make : 'Honda' , model : 'Accord' , color : 'Blue' };function paintCar (car ) { car.color = 'Red' ; } Copied!
回到目录
# 避免无意义的条件判断反例 :
1 2 3 4 5 6 7 8 9 10 11 function createMicrobrewery (name ) { var breweryName; if (name) { breweryName = name; } else { breweryName = 'Hipster Brew Co.' ; } } Copied!
正例 :
1 2 3 4 5 6 function createMicrobrewery (name ) { var breweryName = name || 'Hipster Brew Co.' } Copied!
回到目录
4.# 函数 # 函数参数 (理想情况下应不超过 2 个)限制函数参数数量很有必要,这么做使得在测试函数时更加轻松。过多的参数将导致难以采用有效的测试用例对函数的各个参数进行测试。
应避免三个以上参数的函数。通常情况下,参数超过两个意味着函数功能过于复杂,这时需要重新优化你的函数。当确实需要多个参数时,大多情况下可以考虑这些参数封装成一个对象。
JS 定义对象非常方便,当需要多个参数时,可以使用一个对象进行替代。
反例 :
1 2 3 4 5 6 function createMenu (title, body, buttonText, cancellable ) { ... } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var menuConfig = { title : 'Foo' , body : 'Bar' , buttonText : 'Baz' , cancellable : true }function createMenu (menuConfig ) { ... } Copied!
回到目录
# 函数功能的单一性这是软件功能中最重要的原则之一。
功能不单一的函数将导致难以重构、测试和理解。功能单一的函数易于重构,并使代码更加干净。
反例 :
1 2 3 4 5 6 7 8 9 10 11 function emailClients (clients ) { clients.forEach(client => { let clientRecord = database.lookup(client); if (clientRecord.isActive()) { email(client); } }); } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 function emailClients (clients ) { clients.forEach(client => { emailClientIfNeeded(client); }); }function emailClientIfNeeded (client ) { if (isClientActive(client)) { email(client); } }function isClientActive (client ) { let clientRecord = database.lookup(client); return clientRecord.isActive(); } Copied!
回到目录
# 函数名应明确表明其功能反例 :
1 2 3 4 5 6 7 8 9 10 function dateAdd (date, month ) { }let date = new Date (); Copied!
正例 :
1 2 3 4 5 6 7 8 9 function dateAddMonth (date, month ) { }let date = new Date (); dateAddMonth(date, 1 ); Copied!
回到目录
# 函数应该只做一层抽象当函数的需要的抽象多于一层时通常意味着函数功能过于复杂,需将其进行分解以提高其可重用性和可测试性。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 function parseBetterJSAlternative (code ) { let REGEXES = [ ]; let statements = code.split(' ' ); let tokens; REGEXES.forEach((REGEX ) => { statements.forEach((statement ) => { }) }); let ast; tokens.forEach((token ) => { }); ast.forEach((node ) => { }) } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 function tokenize (code ) { let REGEXES = [ ]; let statements = code.split(' ' ); let tokens; REGEXES.forEach((REGEX ) => { statements.forEach((statement ) => { }) }); return tokens; }function lexer (tokens ) { let ast; tokens.forEach((token ) => { }); return ast; }function parseBetterJSAlternative (code ) { let tokens = tokenize(code); let ast = lexer(tokens); ast.forEach((node ) => { }) } Copied!
回到目录
# 移除重复的代码永远、永远、永远不要在任何循环下有重复的代码。
这种做法毫无意义且潜在危险极大。重复的代码意味着逻辑变化时需要对不止一处进行修改。JS 弱类型的特点使得函数拥有更强的普适性。好好利用这一优点吧。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 function showDeveloperList (developers ) { developers.forEach(developer => { var expectedSalary = developer.calculateExpectedSalary(); var experience = developer.getExperience(); var githubLink = developer.getGithubLink(); var data = { expectedSalary : expectedSalary, experience : experience, githubLink : githubLink }; render(data); }); }function showManagerList (managers ) { managers.forEach(manager => { var expectedSalary = manager.calculateExpectedSalary(); var experience = manager.getExperience(); var portfolio = manager.getMBAProjects(); var data = { expectedSalary : expectedSalary, experience : experience, portfolio : portfolio }; render(data); }); } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function showList (employees ) { employees.forEach(employee => { var expectedSalary = employee.calculateExpectedSalary(); var experience = employee.getExperience(); var portfolio; if (employee.type === 'manager' ) { portfolio = employee.getMBAProjects(); } else { portfolio = employee.getGithubLink(); } var data = { expectedSalary : expectedSalary, experience : experience, portfolio : portfolio }; render(data); }); } Copied!
回到目录
# 采用默认参数精简代码反例 :
1 2 3 4 5 6 7 8 function writeForumComment (subject, body ) { subject = subject || 'No Subject' ; body = body || 'No text' ; } Copied!
正例 :
1 2 3 4 5 6 7 function writeForumComment (subject = 'No subject' , body = 'No text' ) { ... } Copied!
回到目录
# 使用 Object.assign 设置默认对象反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 var menuConfig = { title : null , body : 'Bar' , buttonText : null , cancellable : true }function createMenu (config ) { config.title = config.title || 'Foo' config.body = config.body || 'Bar' config.buttonText = config.buttonText || 'Baz' config.cancellable = config.cancellable === undefined ? config.cancellable : true ; } createMenu(menuConfig); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var menuConfig = { title : 'Order' , buttonText : 'Send' , cancellable : true }function createMenu (config ) { config = Object .assign({ title : 'Foo' , body : 'Bar' , buttonText : 'Baz' , cancellable : true }, config); } createMenu(menuConfig); Copied!
回到目录
# 不要使用标记(Flag)作为函数参数这通常意味着函数的功能的单一性已经被破坏。此时应考虑对函数进行再次划分。
反例 :
1 2 3 4 5 6 7 8 9 10 function createFile (name, temp ) { if (temp) { fs.create('./temp/' + name); } else { fs.create(name); } } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 function createTempFile (name ) { fs.create('./temp/' + name); } ----------function createFile (name ) { fs.create(name); } Copied!
回到目录
# 避免副作用当函数产生了除了“接受一个值并返回一个结果”之外的行为时,称该函数产生了副作用。比如写文件、修改全局变量或将你的钱全转给了一个陌生人等。
程序在某些情况下确实需要副作用这一行为,如先前例子中的写文件。这时应该将这些功能集中在一起,不要用多个函数/类修改某个文件。用且只用一个 service 完成这一需求。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var name = 'Ryan McDermott' ;function splitIntoFirstAndLastName ( ) { name = name.split(' ' ); } splitIntoFirstAndLastName();console .log(name); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 function splitIntoFirstAndLastName (name ) { return name.split(' ' ); }var name = 'Ryan McDermott' var newName = splitIntoFirstAndLastName(name);console .log(name); console .log(newName); Copied!
回到目录
# 不要写全局函数在 JS 中污染全局是一个非常不好的实践,这么做可能和其他库起冲突,且调用你的 API 的用户在实际环境中得到一个 exception 前对这一情况是一无所知的。
想象以下例子:如果你想扩展 JS 中的 Array,为其添加一个 diff
函数显示两个数组间的差异,此时应如何去做?你可以将 diff 写入 Array.prototype
,但这么做会和其他有类似需求的库造成冲突。如果另一个库对 diff 的需求为比较一个数组中首尾元素间的差异呢?
使用 ES6 中的 class 对全局的 Array 做简单的扩展显然是一个更棒的选择。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Array .prototype.diff = function (comparisonArray ) { var values = []; var hash = {}; for (var i of comparisonArray) { hash[i] = true ; } for (var i of this ) { if (!hash[i]) { values.push(i); } } return values; } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class SuperArray extends Array { constructor (...args ) { super (...args); } diff (comparisonArray ) { var values = []; var hash = {}; for (var i of comparisonArray) { hash[i] = true ; } for (var i of this ) { if (!hash[i]) { values.push(i); } } return values; } } Copied!
回到目录
# 采用函数式编程函数式的编程具有更干净且便于测试的特点。尽可能的使用这种风格吧。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const programmerOutput = [ { name : 'Uncle Bobby' , linesOfCode : 500 }, { name : 'Suzie Q' , linesOfCode : 1500 }, { name : 'Jimmy Gosling' , linesOfCode : 150 }, { name : 'Gracie Hopper' , linesOfCode : 1000 } ];var totalOutput = 0 ;for (var i = 0 ; i < programmerOutput.length; i++) { totalOutput += programmerOutput[i].linesOfCode; } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const programmerOutput = [ { name : 'Uncle Bobby' , linesOfCode : 500 }, { name : 'Suzie Q' , linesOfCode : 1500 }, { name : 'Jimmy Gosling' , linesOfCode : 150 }, { name : 'Gracie Hopper' , linesOfCode : 1000 } ];var totalOutput = programmerOutput .map((programmer ) => programmer.linesOfCode) .reduce((acc, linesOfCode ) => acc + linesOfCode, 0 ); Copied!
回到目录
# 封装判断条件反例 :
1 2 3 4 5 6 if (fsm.state === 'fetching' && isEmpty(listNode)) { } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 function shouldShowSpinner (fsm, listNode ) { return fsm.state === 'fetching' && isEmpty(listNode); }if (shouldShowSpinner(fsmInstance, listNodeInstance)) { } Copied!
回到目录
# 避免“否定情况”的判断反例 :
1 2 3 4 5 6 7 8 9 10 function isDOMNodeNotPresent (node ) { }if (!isDOMNodeNotPresent(node)) { } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 function isDOMNodePresent (node ) { }if (isDOMNodePresent(node)) { } Copied!
回到目录
# 避免条件判断这看起来似乎不太可能。
大多人听到这的第一反应是:“怎么可能不用 if 完成其他功能呢?”许多情况下通过使用多态(polymorphism)可以达到同样的目的。
第二个问题在于采用这种方式的原因是什么。答案是我们之前提到过的:保持函数功能的单一性。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Airplane { getCruisingAltitude ( ) { switch (this .type) { case '777' : return getMaxAltitude() - getPassengerCount(); case 'Air Force One' : return getMaxAltitude(); case 'Cessna' : return getMaxAltitude() - getFuelExpenditure(); } } } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class Airplane { }class Boeing777 extends Airplane { getCruisingAltitude ( ) { return getMaxAltitude() - getPassengerCount(); } }class AirForceOne extends Airplane { getCruisingAltitude ( ) { return getMaxAltitude(); } }class Cessna extends Airplane { getCruisingAltitude ( ) { return getMaxAltitude() - getFuelExpenditure(); } } Copied!
回到目录
# 避免类型判断(part 1)JS 是弱类型语言,这意味着函数可接受任意类型的参数。
有时这会对你带来麻烦,你会对参数做一些类型判断。有许多方法可以避免这些情况。
反例 :
1 2 3 4 5 6 7 8 9 10 function travelToTexas (vehicle ) { if (vehicle instanceof Bicycle) { vehicle.peddle(this .currentLocation, new Location('texas' )); } else if (vehicle instanceof Car) { vehicle.drive(this .currentLocation, new Location('texas' )); } } Copied!
正例 :
1 2 3 4 5 6 function travelToTexas (vehicle ) { vehicle.move(this .currentLocation, new Location('texas' )); } Copied!
回到目录
# 避免类型判断(part 2)如果需处理的数据为字符串,整型,数组等类型,无法使用多态并仍有必要对其进行类型检测时,可以考虑使用 TypeScript。
反例 :
1 2 3 4 5 6 7 8 9 10 11 function combine (val1, val2 ) { if (typeof val1 == "number" && typeof val2 == "number" || typeof val1 == "string" && typeof val2 == "string" ) { return val1 + val2; } else { throw new Error ('Must be of type String or Number' ); } } Copied!
正例 :
1 2 3 4 5 6 function combine (val1, val2 ) { return val1 + val2; } Copied!
回到目录
# 避免过度优化现代的浏览器在运行时会对代码自动进行优化。有时人为对代码进行优化可能是在浪费时间。
这里可以找到许多真正需要优化的地方(opens new window)
反例 :
1 2 3 4 5 6 7 8 9 for (var i = 0 , len = list.length; i < len; i++) { } Copied!
正例 :
1 2 3 4 5 6 for (var i = 0 ; i < list.length; i++) { } Copied!
回到目录
# 删除无效的代码不再被调用的代码应及时删除。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 function oldRequestModule (url ) { }function newRequestModule (url ) { }var req = newRequestModule; inventoryTracker('apples' , req, 'www.inventory-awesome.io' ); Copied!
正例 :
1 2 3 4 5 6 7 8 9 function newRequestModule (url ) { }var req = newRequestModule; inventoryTracker('apples' , req, 'www.inventory-awesome.io' ); Copied!
回到目录
5.# 对象和数据结构 # 使用 getters 和 settersJS 没有接口或类型,因此实现这一模式是很困难的,因为我们并没有类似 public
和 private
的关键词。
然而,使用 getters 和 setters 获取对象的数据远比直接使用点操作符具有优势。为什么呢?
当需要对获取的对象属性执行额外操作时。
执行 set
时可以增加规则对要变量的合法性进行判断。
封装了内部逻辑。
在存取时可以方便的增加日志和错误处理。
继承该类时可以重载默认行为。
从服务器获取数据时可以进行懒加载。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 class BankAccount { constructor ( ) { this .balance = 1000 ; } }let bankAccount = new BankAccount(); bankAccount.balance = bankAccount.balance - 100 ; Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class BankAccount { constructor ( ) { this .balance = 1000 ; } withdraw (amount ) { if (verifyAmountCanBeDeducted(amount)) { this .balance -= amount; } } }let bankAccount = new BankAccount(); bankAccount.withdraw(100 ); Copied!
回到目录
# 让对象拥有私有成员可以通过闭包完成
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var Employee = function (name ) { this .name = name; } Employee.prototype.getName = function ( ) { return this .name; }var employee = new Employee('John Doe' );console .log('Employee name: ' + employee.getName()); delete employee.name;console .log('Employee name: ' + employee.getName()); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 var Employee = (function ( ) { function Employee (name ) { this .getName = function ( ) { return name; }; } return Employee; }());var employee = new Employee('John Doe' );console .log('Employee name: ' + employee.getName()); delete employee.name;console .log('Employee name: ' + employee.getName()); Copied!
回到目录
6.# 类 # 单一职责原则 (SRP)如《代码整洁之道》一书中所述,“修改一个类的理由不应该超过一个”。
将多个功能塞进一个类的想法很诱人,但这将导致你的类无法达到概念上的内聚,并经常不得不进行修改。
最小化对一个类需要修改的次数是非常有必要的。如果一个类具有太多太杂的功能,当你对其中一小部分进行修改时,将很难想象到这一修够对代码库中依赖该类的其他模块会带来什么样的影响。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class UserSettings { constructor (user ) { this .user = user; } changeSettings (settings ) { if (this .verifyCredentials(user)) { } } verifyCredentials (user ) { } } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class UserAuth { constructor (user ) { this .user = user; } verifyCredentials ( ) { } }class UserSettings { constructor (user ) { this .user = user; this .auth = new UserAuth(user) } changeSettings (settings ) { if (this .auth.verifyCredentials()) { } } } Copied!
回到目录
# 开/闭原则 (OCP)“代码实体(类,模块,函数等)应该易于扩展,难于修改。”
这一原则指的是我们应允许用户方便的扩展我们代码模块的功能,而不需要打开 js 文件源码手动对其进行修改。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class AjaxRequester { constructor ( ) { this .HTTP_METHODS = ['POST' , 'PUT' , 'GET' ]; } get (url ) { } } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class AjaxRequester { constructor ( ) { this .HTTP_METHODS = ['POST' , 'PUT' , 'GET' ]; } get (url ) { } addHTTPMethod (method ) { this .HTTP_METHODS.push(method); } } Copied!
回到目录
# 利斯科夫替代原则 (LSP)“子类对象应该能够替换其超类对象被使用”。
也就是说,如果有一个父类和一个子类,当采用子类替换父类时不应该产生错误的结果。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 class Rectangle { constructor ( ) { this .width = 0 ; this .height = 0 ; } setColor (color ) { } render (area ) { } setWidth (width ) { this .width = width; } setHeight (height ) { this .height = height; } getArea ( ) { return this .width * this .height; } }class Square extends Rectangle { constructor ( ) { super (); } setWidth (width ) { this .width = width; this .height = width; } setHeight (height ) { this .width = height; this .height = height; } }function renderLargeRectangles (rectangles ) { rectangles.forEach((rectangle ) => { rectangle.setWidth(4 ); rectangle.setHeight(5 ); let area = rectangle.getArea(); rectangle.render(area); }) }let rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles(rectangles); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 class Shape { constructor ( ) {} setColor (color ) { } render (area ) { } }class Rectangle extends Shape { constructor ( ) { super (); this .width = 0 ; this .height = 0 ; } setWidth (width ) { this .width = width; } setHeight (height ) { this .height = height; } getArea ( ) { return this .width * this .height; } }class Square extends Shape { constructor ( ) { super (); this .length = 0 ; } setLength (length ) { this .length = length; } getArea ( ) { return this .length * this .length; } }function renderLargeShapes (shapes ) { shapes.forEach((shape ) => { switch (shape.constructor.name) { case 'Square' : shape.setLength(5 ); case 'Rectangle' : shape.setWidth(4 ); shape.setHeight(5 ); } let area = shape.getArea(); shape.render(area); }) }let shapes = [new Rectangle(), new Rectangle(), new Square()]; renderLargeShapes(shapes); Copied!
回到目录
# 接口隔离原则 (ISP)“客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。”
在 JS 中,当一个类需要许多参数设置才能生成一个对象时,或许大多时候不需要设置这么多的参数。此时减少对配置参数数量的需求是有益的。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class DOMTraverser { constructor (settings ) { this .settings = settings; this .setup(); } setup ( ) { this .rootNode = this .settings.rootNode; this .animationModule.setup(); } traverse ( ) { } }let $ = new DOMTraverser({ rootNode : document .getElementsByTagName('body' ), animationModule : function ( ) {} }); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class DOMTraverser { constructor (settings ) { this .settings = settings; this .options = settings.options; this .setup(); } setup ( ) { this .rootNode = this .settings.rootNode; this .setupOptions(); } setupOptions ( ) { if (this .options.animationModule) { } } traverse ( ) { } }let $ = new DOMTraverser({ rootNode : document .getElementsByTagName('body' ), options : { animationModule : function ( ) {} } }); Copied!
回到目录
# 依赖反转原则 (DIP)该原则有两个核心点:
高层模块不应该依赖于低层模块。他们都应该依赖于抽象接口。
抽象接口应该脱离具体实现,具体实现应该依赖于抽象接口。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class InventoryTracker { constructor (items ) { this .items = items; this .requester = new InventoryRequester(); } requestItems ( ) { this .items.forEach((item ) => { this .requester.requestItem(item); }); } }class InventoryRequester { constructor ( ) { this .REQ_METHODS = ['HTTP' ]; } requestItem (item ) { } }let inventoryTracker = new InventoryTracker(['apples' , 'bananas' ]); inventoryTracker.requestItems(); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 class InventoryTracker { constructor (items, requester ) { this .items = items; this .requester = requester; } requestItems ( ) { this .items.forEach((item ) => { this .requester.requestItem(item); }); } }class InventoryRequesterV1 { constructor ( ) { this .REQ_METHODS = ['HTTP' ]; } requestItem (item ) { } }class InventoryRequesterV2 { constructor ( ) { this .REQ_METHODS = ['WS' ]; } requestItem (item ) { } }let inventoryTracker = new InventoryTracker(['apples' , 'bananas' ], new InventoryRequesterV2()); inventoryTracker.requestItems(); Copied!
回到目录
# 使用 ES6 的 classes 而不是 ES5 的 Function典型的 ES5 的类(function)在继承、构造和方法定义方面可读性较差。
当需要继承时,优先选用 classes。
但是,当在需要更大更复杂的对象时,最好优先选择更小的 function 而非 classes。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 var Animal = function (age ) { if (!(this instanceof Animal)) { throw new Error ("Instantiate Animal with `new`" ); } this .age = age; }; Animal.prototype.move = function ( ) {};var Mammal = function (age, furColor ) { if (!(this instanceof Mammal)) { throw new Error ("Instantiate Mammal with `new`" ); } Animal.call(this , age); this .furColor = furColor; }; Mammal.prototype = Object .create(Animal.prototype); Mammal.prototype.constructor = Mammal; Mammal.prototype.liveBirth = function ( ) {};var Human = function (age, furColor, languageSpoken ) { if (!(this instanceof Human)) { throw new Error ("Instantiate Human with `new`" ); } Mammal.call(this , age, furColor); this .languageSpoken = languageSpoken; }; Human.prototype = Object .create(Mammal.prototype); Human.prototype.constructor = Human; Human.prototype.speak = function ( ) {}; Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class Animal { constructor (age ) { this .age = age; } move ( ) {} }class Mammal extends Animal { constructor (age, furColor ) { super (age); this .furColor = furColor; } liveBirth ( ) {} }class Human extends Mammal { constructor (age, furColor, languageSpoken ) { super (age, furColor); this .languageSpoken = languageSpoken; } speak ( ) {} } Copied!
回到目录
# 使用方法链这里我们的理解与《代码整洁之道》的建议有些不同。
有争论说方法链不够干净且违反了德米特法则 (opens new window) ,也许这是对的,但这种方法在 JS 及许多库(如 JQuery)中显得非常实用。
因此,我认为在 JS 中使用方法链是非常合适的。在 class 的函数中返回 this,能够方便的将类需要执行的多个方法链接起来。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 class Car { constructor ( ) { this .make = 'Honda' ; this .model = 'Accord' ; this .color = 'white' ; } setMake (make ) { this .name = name; } setModel (model ) { this .model = model; } setColor (color ) { this .color = color; } save ( ) { console .log(this .make, this .model, this .color); } }let car = new Car(); car.setColor('pink' ); car.setMake('Ford' ); car.setModel('F-150' ) car.save(); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class Car { constructor ( ) { this .make = 'Honda' ; this .model = 'Accord' ; this .color = 'white' ; } setMake (make ) { this .name = name; return this ; } setModel (model ) { this .model = model; return this ; } setColor (color ) { this .color = color; return this ; } save ( ) { console .log(this .make, this .model, this .color); } }let car = new Car() .setColor('pink' ) .setMake('Ford' ) .setModel('F-150' ) .save(); Copied!
回到目录
# 优先使用组合模式而非继承在著名的设计模式 (opens new window) 一书中提到,应多使用组合模式而非继承。
这么做有许多优点,在想要使用继承前,多想想能否通过组合模式满足需求吧。
那么,在什么时候继承具有更大的优势呢?这取决于你的具体需求,但大多情况下,可以遵守以下三点:
继承关系表现为”是一个”而非”有一个”(如动物->人 和 用户->用户细节)
可以复用基类的代码(“Human”可以看成是”All animal”的一种)
希望当基类改变时所有派生类都受到影响(如修改”all animals”移动时的卡路里消耗量)
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class Employee { constructor (name, email ) { this .name = name; this .email = email; } }class EmployeeTaxData extends Employee { constructor (ssn, salary ) { super (); this .ssn = ssn; this .salary = salary; } } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Employee { constructor (name, email ) { this .name = name; this .email = email; } setTaxData (ssn, salary ) { this .taxData = new EmployeeTaxData(ssn, salary); } }class EmployeeTaxData { constructor (ssn, salary ) { this .ssn = ssn; this .salary = salary; } } Copied!
回到目录
7.# 测试 一些好的覆盖工具 (opens new window) 。
一些好的 JS 测试框架 (opens new window) 。
# 单一的测试每个概念反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const assert = require ('assert' ); describe('MakeMomentJSGreatAgain' , function ( ) { it('handles date boundaries' , function ( ) { let date; date = new MakeMomentJSGreatAgain('1/1/2015' ); date.addDays(30 ); date.shouldEqual('1/31/2015' ); date = new MakeMomentJSGreatAgain('2/1/2016' ); date.addDays(28 ); assert.equal('02/29/2016' , date); date = new MakeMomentJSGreatAgain('2/1/2015' ); date.addDays(28 ); assert.equal('03/01/2015' , date); }); }); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const assert = require ('assert' ); describe('MakeMomentJSGreatAgain' , function ( ) { it('handles 30-day months' , function ( ) { let date = new MakeMomentJSGreatAgain('1/1/2015' ); date.addDays(30 ); date.shouldEqual('1/31/2015' ); }); it('handles leap year' , function ( ) { let date = new MakeMomentJSGreatAgain('2/1/2016' ); date.addDays(28 ); assert.equal('02/29/2016' , date); }); it('handles non-leap year' , function ( ) { let date = new MakeMomentJSGreatAgain('2/1/2015' ); date.addDays(28 ); assert.equal('03/01/2015' , date); }); }); Copied!
回到目录
8.# 并发 # 用 Promises 替代回调回调不够整洁并会造成大量的嵌套。ES6 内嵌了 Promises,使用它吧。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 require ('request' ).get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin' , function (err, response ) { if (err) { console .error(err); } else { require ('fs' ).writeFile('article.html' , response.body, function (err ) { if (err) { console .error(err); } else { console .log('File written' ); } }) } }) Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 require ('request-promise' ).get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin' ) .then(function (response ) { return require ('fs-promise' ).writeFile('article.html' , response); }) .then(function ( ) { console .log('File written' ); }) .catch(function (err ) { console .error(err); }) Copied!
回到目录
# Async/Await 是较 Promises 更好的选择Promises 是较回调而言更好的一种选择,但 ES7 中的 async 和 await 更胜过 Promises。
在能使用 ES7 特性的情况下可以尽量使用他们替代 Promises。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 require ('request-promise' ).get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin' ) .then(function (response ) { return require ('fs-promise' ).writeFile('article.html' , response); }) .then(function ( ) { console .log('File written' ); }) .catch(function (err ) { console .error(err); }) Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async function getCleanCodeArticle ( ) { try { var request = await require ('request-promise' ) var response = await request.get('https://en.wikipedia.org/wiki/Robert_Cecil_Martin' ); var fileHandle = await require ('fs-promise' ); await fileHandle.writeFile('article.html' , response); console .log('File written' ); } catch (err) { console .log(err); } } Copied!
回到目录
9.# 错误处理 错误抛出是个好东西!这使得你能够成功定位运行状态中的程序产生错误的位置。
# 别忘了捕获错误对捕获的错误不做任何处理是没有意义的。
代码中 try/catch
的意味着你认为这里可能出现一些错误,你应该对这些可能的错误存在相应的处理方案。
反例 :
1 2 3 4 5 6 7 8 try { functionThatMightThrow(); } catch (error) { console .log(error); } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 try { functionThatMightThrow(); } catch (error) { console .error(error); notifyUserOfError(error); reportErrorToService(error); } Copied!
# 不要忽略被拒绝的 promises理由同 try/catch
。
反例 :
1 2 3 4 5 6 7 8 9 10 getdata() .then(data => { functionThatMightThrow(data); }) .catch(error => { console .log(error); }); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 getdata() .then(data => { functionThatMightThrow(data); }) .catch(error => { console .error(error); notifyUserOfError(error); reportErrorToService(error); }); Copied!
回到目录
10.# 格式化 格式化是一件主观的事。如同这里的许多规则一样,这里并没有一定/立刻需要遵守的规则。可以在这里 (opens new window) 完成格式的自动化。
# 大小写一致JS 是弱类型语言,合理的采用大小写可以告诉你关于变量/函数等的许多消息。
这些规则是主观定义的,团队可以根据喜欢进行选择。重点在于无论选择何种风格,都需要注意保持一致性。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var DAYS_IN_WEEK = 7 ;var daysInMonth = 30 ;var songs = ['Back In Black' , 'Stairway to Heaven' , 'Hey Jude' ];var Artists = ['ACDC' , 'Led Zeppelin' , 'The Beatles' ];function eraseDatabase ( ) {}function restore_database ( ) {}class animal {}class Alpaca {} Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 var DAYS_IN_WEEK = 7 ;var DAYS_IN_MONTH = 30 ;var songs = ['Back In Black' , 'Stairway to Heaven' , 'Hey Jude' ];var artists = ['ACDC' , 'Led Zeppelin' , 'The Beatles' ];function eraseDatabase ( ) {}function restoreDatabase ( ) {}class Animal {}class Alpaca {} Copied!
回到目录
# 调用函数的函数和被调函数应放在较近的位置当函数间存在相互调用的情况时,应将两者置于较近的位置。
理想情况下,应将调用其他函数的函数写在被调用函数的上方。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class PerformanceReview { constructor (employee ) { this .employee = employee; } lookupPeers ( ) { return db.lookup(this .employee, 'peers' ); } lookupMananger ( ) { return db.lookup(this .employee, 'manager' ); } getPeerReviews ( ) { let peers = this .lookupPeers(); } perfReview ( ) { getPeerReviews(); getManagerReview(); getSelfReview(); } getManagerReview ( ) { let manager = this .lookupManager(); } getSelfReview ( ) { } }let review = new PerformanceReview(user); review.perfReview(); Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class PerformanceReview { constructor (employee ) { this .employee = employee; } perfReview ( ) { getPeerReviews(); getManagerReview(); getSelfReview(); } getPeerReviews ( ) { let peers = this .lookupPeers(); } lookupPeers ( ) { return db.lookup(this .employee, 'peers' ); } getManagerReview ( ) { let manager = this .lookupManager(); } lookupMananger ( ) { return db.lookup(this .employee, 'manager' ); } getSelfReview ( ) { } }let review = new PerformanceReview(employee); review.perfReview(); Copied!
回到目录
11.# 注释 # 只对存在一定业务逻辑复杂性的代码进行注释注释并不是必须的,好的代码是能够让人一目了然,不用过多无谓的注释。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 function hashIt (data ) { var hash = 0 ; var length = data.length; for (var i = 0 ; i < length; i++) { var char = data.charCodeAt(i); hash = ((hash << 5 ) - hash) + char; hash = hash & hash; } } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 function hashIt (data ) { var hash = 0 ; var length = data.length; for (var i = 0 ; i < length; i++) { var char = data.charCodeAt(i); hash = ((hash << 5 ) - hash) + char; hash = hash & hash; } } Copied!
回到目录
# 不要在代码库中遗留被注释掉的代码版本控制的存在是有原因的。让旧代码存在于你的 history 里吧。
反例 :
1 2 3 4 5 6 7 doStuff(); Copied!
正例 :
回到目录
# 不需要版本更新类型注释记住,我们可以使用版本控制。废代码、被注释的代码及用注释记录代码中的版本更新说明都是没有必要的。
需要时可以使用 git log
获取历史版本。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 function combine (a, b ) { return a + b; } Copied!
正例 :
1 2 3 4 5 6 function combine (a, b ) { return a + b; } Copied!
回到目录
# 避免位置标记这些东西通常只能代码麻烦,采用适当的缩进就可以了。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let $scope.model = { menu : 'foo' , nav : 'bar' };let actions = function ( ) { } Copied!
正例 :
1 2 3 4 5 6 7 8 9 10 11 let $scope.model = { menu : 'foo' , nav : 'bar' };let actions = function ( ) { } Copied!
回到目录
# 避免在源文件中写入法律评论将你的 LICENSE
文件置于源码目录树的根目录。
反例 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 function calculateBill ( ) { } Copied!
正例 :
1 2 3 4 5 6 function calculateBill ( ) { } Copied!
回到目录
12.# 补充
大概率坏代码判断
好的命名
函数单一职责
通过引入解释性变量或函数,使得表达更清晰
更少的嵌套,尽早 return
以多态取代条件表达式
倾向于遍历对象而不是 Switch 语句
使用默认参数和解构
善于利用ES6 Array处理数组
Array.find()
Array.forEach(),Array.filter(),Array.map(),Array.reduce()
数组rest运算符
多重判断时使用 Array.includes
对 所有/部分 判断使用 Array.every & Array.some
善于利用ES6 Object处理对象
对象解构
rest(ES9开始支持)
Object.assign()
Object.keys(),Object.values()
13.# 参考文章
七、推荐-200错误统一处理 对于接口层来说,后端经常定义的结构如下:
1 2 3 4 5 6 7 8 { code : [Number ], desc : [String ], detail : [Array , Object ] } Copied!
# bad1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 batchAddVariable({ globalParamList : validList }) .then(res => { if (res === SERVER_ERROR_CODE.SUCCESS) { this .close(true ) this .$toast.show(res.detail.colletion) } else { this .$toast.show(res.desc) if (res === SERVER_ERROR_CODE.REPEAT) { ... } } }) Copied!
# good1 2 3 4 5 6 7 8 9 10 11 12 13 batchAddVariable({ globalParamList : validList }) .then(data => { this .close(true ) this .$toast.show(data.colletion) }) .catch(res => { if (res === SERVER_ERROR_CODE.REPEAT) { ... } }) Copied!
# 解决方案http层axios拿到数据后进行统一处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import Vue from 'vue' import axios from 'axios' const service = axios.create({ baseURL : rootURL, timeout : 15000 , }) service.interceptors.request.use( config => { if (config.method === 'post' || config.method === 'put' || config.method === 'delete' ) { config.headers['Content-Type' ] = 'application/json' if (config.type === 'form' ) { config.headers['Content-Type' ] = 'multipart/form-data' } else { config.data = JSON .stringify(config.data) } } return config }, error => { Promise .reject(error) } ) service.interceptors.response.use( response => { const res = response.data if (res.code === SERVER_ERROR_CODE.SUCCESS) { return res.detail } else { Vue.prototype.$toast.show(res.desc) return Promise .reject(res) } }, error => { return Promise .reject(error) } )export default service Copied!
到此基本就可以很优雅的写逻辑代码了。不过还有个点可以继续优化。通常情况,后台返回非200错误,只需要$toast提示结果就行,catch代码部分可以省略。类似如下:
1 2 3 4 5 6 batchAddVariable({ globalParamList : validList }) .then(data => this .close(true )) Copied!
多么简洁的代码,但Promise执行reject代码,浏览器会报Uncaught (in promise)
错误。这是因为中断了Promise操作但又没有对其进行处理,故由此错误。只需要使用unhandledrejection
全局处理即可。
1 2 3 window .addEventListener('unhandledrejection' , event => event.preventDefault())
八、推荐-优雅引用字体 # 编程式字体方法的好处
学习视觉同学对于具体字体的考量,也许还能发现视觉同学的bug
全局控制,避免样式散乱
书写字体样式成为一门艺术
# bad1 2 3 4 5 6 7 8 9 10 11 12 13 14 .form-title { font : 'PingFang-SC-medium' ; font-size : 18px ; font -color : #222 22; }.form-text { font : 'PingFang-SC-regular' ; font-size : 14px ; font -color : #333333 ; } Copied!
# goodvariables.scss文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 $font-normal-color = #222222 ; $font-light-color = #333333 ;@mixin font-class($fontFamily , $fontSize , $fontColor ) { font-family : $fontFamily ; font-size : $fontSize ; color : $fontColor ; }@mixin font-large($size : 14px , $color : $font-normal-color ) { @include font-class($font-family-medium , $size , $color ); }@mixin font-normal($size : 14px , $color : $font-light-color ) { @include font-class($font-family-regular , $size , $color ); } Copied!
应用:
1 2 3 4 5 6 7 8 9 10 .form-title { @include font-large(18px , #222 22); }.form-text { @include font-large(14px , #333333 ); } Copied!
font-large/font-normal等公用mixins建议放在统一的variables.scss
文件中,再通过sass-resource自动引入到所有组件中
# 最佳字体顺序参考# PC端1 2 3 4 5 $font-family-medium = 'PingFang-SC-medium', Helveti ca, Tahoma, Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; $font-family-regular = 'PingFang-SC-regular', Helvetica, Tahoma, Arial, 'Microsoft YaHei', 'Hiragino Sans GB', 'WenQuanYi Micro Hei', sans-serif; Copied!
# 移动端1 2 3 4 5 6 7 8 9 $font-family-ultralight = 'PingFangSC-Ultralight', 'Source Han Sans CN', "Helvetica Neue"; $font-family-regular = 'PingFangSC-Regular', 'Source Han Sans CN', "Helvetica Neue"; $font-family-medium = 'PingFangSC-Medium', 'Source Han Sans CN Medium', "Helvetica Neue"; $font-family-thin = 'PingFangSC-Thin', 'Source Han Sans CN Thin', "Helvetica Neue"; $font-family-light = 'PingFangSC-Light', 'Source Han Sans CN Light', "Helvetica Neue"; $font-family-semibold = 'PingFangSC-Semibold', 'Source Han Sans CN Light', "Helvetica Neue"; Copied!
九、推荐-Vue实例选项顺序
在Vue中,export default对象中有很多约定的API Key。每个人的顺序排放都可能不一致,但保持统一的代码风格有助于团队成员多人协作。
Vue官网文档中也有推荐顺序 (opens new window) ,文档中对选项顺序做了许多分类。但从工程项目角度思考,需要更加精简以及合理的排序。推荐如下规则进行排序:
Vue扩展: extends, mixins, components
Vue数据: props, model, data, computed, watch
Vue资源: filters, directives
Vue生命周期: created, mounted, destroy…
Vue方法: methods
以下推荐顺序,基于团队小伙伴@akimoto整理的顺序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 export default { name : '' , extends : '' , mixins : [], components : {}, props : {}, model : { prop : '' , event : '' }, data () { return {} }, computed : {}, watch :{}, filters : {}, directives : {}, created () {}, mounted () {}, destroy () {}, methods : {}, }
十、组件设计风格 # 要求 Element 元素统一使用El后缀1 2 3 4 5 6 7 8 9 10 11 12 const elem = this .$el;const element = e.target;const input = this .$refs.inputconst el = this .$el;const el = e.target;const inputEl = this .$refs.input; Copied!
# 要求 Vue 实例统一使用VM后缀1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const instance = this ;const form = this .$refs.form;this .$emit('select' , { item, });const vm = this ;const formVM = this .$refs.form;this .$emit('select' , { item, itemVM : selectedVM, }); Copied!
# 被动接收事件方法使用on前缀1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 { methods : { input ( ) { }, handleValidate ( ) { }, }, } { methods : { onInput ( ) { }, onValidate ( ) { }, }, } Copied!
# slot 只在对应名称的 class 内设置1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <slot name="head" > <div :class ="$style.head" > <slot name ="title" > <div :class ="$style.title" > {{ title }} </div > </slot > <div :class ="$style.close" > </div > </div > </slot><div :class ="$style.head" > <slot name ="head" > <div :class ="$style.title" > <slot name ="title" > {{ title }}</slot > </div > <div :class ="$style.close" > </div > </slot > </div > Copied!
# 变量命名
常见状态:default, primary, info, success, warning, error, disabled, muted, …
大小分级:mini, small, base, large, huge, …
颜色分级:darkest, darker, dark, base, light, lighter, lightest, …
十一、推荐-Vue项目目录结构 目录结构保持一致,使得多人合作容易理解与管理,提高工作效率。Vue标准项目(opens new window)
# 简要说明
main.js
主入口,router.js
路由划分
plugins
自己或第三方插件,包括但不限于components、directives、filters、third lib
pages
所有路由页面。原则:轻page,重component
components
所有组件。包括原子组件、业务公用组件、页面独有组件
server
api引入入口
assets
sass、图片资源入口,不常修改数据
utils
工具文件夹
store
标准vuex格式,非必须
# 详细说明1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 project └───src │ │ app.vue // 主页面 │ │ main.js // 主入口 | | router.js // 所有路由 │ │ │ |____assets // css、image、svg等资源 │ | |____css // 所有sass资源 | | | | reset.scss // 兼容各浏览器 | | | | global.scss // 全局css | | | | variable.scss // sass变量和function等 │ | |____img // image图标库 | | |____svg // svg图标库 | | | |____components // 组件 │ | |____common // common自注册组件 │ | |____base // 原子组件(如果是引入第三方,该文件夹可省略) │ | | ... // 业务公用组件 │ | |____entity // entity页面组件 │ | |____about // about页面组件 | | | |____pages // UI层(原则:轻page,重component) | | |____entity | | | | list.vue // 列表页 | | | | create.vue // 新增页 | | | | edit.vue // 修改页 | | | main.vue | | | |____plugins // 自己或第三方插件 | | | index.js // 插件入口文件 | | | directives.js // 所有Vue指令 | | | filters.js // 所有Vue过滤 | | | |____server // 接口层 | | | index.js // 所有接口 | | | http.js // axios二次封装 | | | |____store // vuex数据 | | | index.js | | | |____utils // 工具层 | | | config.js// 配置文件,包括常量配置 | └───public // 公用文件,不经过webpack处理 │ │ favicon.ico │ │ index.html │ vue.config.js // vue-cli3主配置 │ babel.config.js// babel配置 │ .eslintrc.js // eslint配置 │ .prettierrc.js // perttier配置 │ package.json // npm配置 │ README.md // 项目说明 Copied!
十二、推荐-Vue-Router写法 使用路由懒加载,实现方式是结合Vue异步组件和Webpack代码分割功能。
优点:
减小包体积,提高加载速度
当页面>20个时,组件定义需要拉到编辑器顶部才知道具体路径
# bad1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import IntentionList from '@/pages/intention/list' import Variable from '@/pages/variable' ... { path : '/intention/list' , name : 'ilist' , component : IntentionList }, { path : '/variable' , name : 'variable' , component : Variable } Copied!
# good1 2 3 4 5 6 7 8 9 10 11 12 13 { path : '/intention/list' , name : 'ilist' , component : () => import ('@/pages/intention/list' ) }, { path : '/variable' , name : 'variable' , component : () => import ('@/pages/variable' ) } Copied!
import语法需要Babel添加syntax-dynamic-import
插件。最新当vue-cli 3.0中默认添加该特性,不需要额外引用。另外,合理控制异步模块的数量。