编程风格
基本格式化
- 4空格缩进
- 不省略分号(在原生及使用工具函数的情况不建议省略,在使用比较完善的框架如vue或者自己配置好 webpack 时可以省略)
- 1行代码长度不超过80个字符(个人比较推荐,毕竟编辑器的自动换行有时真的很难受)
- 运算符后换行,换行后增加2个缩进单位(变量赋值换行,变量保持和上一行等号右侧第一个变量对齐)
- 空行,流控制语句之前,方法之间,变量和第一条语句之间,注释之前,
- 变量和函数命名方式,小驼峰方式
-
变量命名名词开头,函数命名动词开头,命名长度尽可能短,尽量体现出值得数据类型
- can 函数返回布尔值
- has 函数返回布尔值
- is 函数返回布尔值
- get 函数返回非布尔值
- set 函数用来保存值
- 常量使用大写字母和下划线来命名,下划线分割单词 (URL MAX_NUMBER)
- 构造函数(class) 大驼峰命名方式
- 字符串,统一用单引号,多行字符或者需要携带参数使用反引号
- 数字,不要省略浮点数小数点前后的部分
- null 当做对象占位符
- 避免使用 undefined
- 创建对象时使用对象直接量: {}
- 创建数组时使用数组直接量: []
注释
-
单行注释
- 独占一行,注释前总是有一个空行,缩进与下一行代码保持一致
- 代码行尾部,代码结束到注释之间有一个空格,代码加上注释不应超过单行最大字符数限制,超过了就应放到当前代码行的上方
- 单行注释不应以连续多行注释的形式出现
-
多行注释
-
最少三行第一行 / , 第二行 与上一行 对齐,最后一行 / (单行多行注释区分开比较好,多行注释风格很多,这里只是一种,找到适合的就好)
/* *空格 + 注释 */
- 注释前总是有一个空行,缩进与下一行代码保持一致
- 代码结束到注释之间有一个缩进
-
-
添加注释的一般原则
- 需要让代码表达的意思更清晰
- 难以理解的代码
- 可能被认为错误的代码
- 浏览器特性 hack
-
文档注释
- 最流行的 javaDoc 文档格式
- 多行注释以单斜杠加双星号开头 /**
- 接下来是描述信息
- 使用 @ 来表示一个或多个属性
/** *注释 *@ 变量1 {String} 一个字符串变量 */
-
如何添加文档注释
- 所有的方法: 方法作用,期望的参数,返回值 进行描述
- 所有的构造函数(类): 自定义类型,期望的参数添加描述
- 所有包含文档化方法的对象: 如果对象包含一个或多个附带文档注释的方法,那么这个对象也应适当添加文档注释
- 文档生成工具 (jsdoc:目前还在维护更新,使用量,star数也比较多)
- 注:使用文档生成工具要使用工具要求的注释风格
语句和表达式
-
块语句都应使用花括号
- if...else...
- switch
- for
- while
- do..while..
- try...catch...
- ...
-
花括号对齐方式,左花括号挡在块语句的第一行末尾
if () { } else { }
-
块语句间隔
-
语句名,小括号,花括号都没有空格
if(true){ }
-
小括号左右加空格
if (true) { }
-
小括号左右加空格,小括号与小括号内容之间加空格
if ( true ) { }
-
-
switch 语句
-
缩进
-
每条 case 语句相对于 switch 关键字缩进一个层级,每条 case 语句后都有一个空行
switch (color) { case '#000': break case '#f00': break case '#00F': break default: break }
-
case 和 switch 左对齐,且没有空行
switch (color) { case '#000': break case '#00F': break default: break }
-
- 连续执行: 有意连续执行需要加注释,其他情况默认每条语句结束必须是 break,return,throw
- default:如果default什么也没做,加上注释,省略default
-
-
while 语句 尽量避免使用,因为在严格模式下不能运行,而且容易造成对于变量的认知错误
let message = '你好!' let book = { name: '少少', age: 18, message: '你好吗?' } with (book) { message += name console.log(message) }
-
for 循环 遍历数组
- break 立即退出循环
- commit 跳过本次循环 // 尽量避免这种方式
-
for-in 循环 遍历对象
- 不用定义任何控制条件,返回属性名而不是值
-
不仅遍历对象的实例属性同样遍历从原型继承来的属性,往往因意外的结果而终止
- 使用 hasOwnProperty() 过滤实例属性,除非想查找原型链
变量 函数 运算符
-
变量声明
- 集中在函数顶部
-
在有相关性时,合并变量声明
let value = 10, result = 10, i, len;
-
函数声明
- 先声明后使用
- 函数声明不应该出现在语句块之内
-
函数调用间隔
- 函数名和小括号之间没有空格
-
立即调用的函数
const myFunction = function () { // 函数 } let value = function () { return 'string' // 变量 }() // 好的写法 let value = (function () { return 'string' }())
-
严格模式 "use strict"
- 尽可能使用严格模式
-
不在全局使用严格模式
function () { "use strict" } (function () { "use strict" function () { } function () { } function () { } })()
-
相等 JavaScript 有强制类型转换机制
- 数字 与 字符串比较,尝试将字符串转换数字
- 数字 与 布尔值比较,尝试将布尔值转换为数字
- 对象 与 其他类型,先调用valueOf(),没有就调用toString()
- 使用 === 和 !==
编程实践
UI 层的松耦合
- 什么是松耦合:修改一个组件而不需要修改其他的组件;组件知道的越少,越有利于形成整个系统
- 将 js 代码和 css 代码分离开,需要操作样式时最佳方法是操作 className,除非需要 js 计算 css 的属性(比如常见的轮播图)
-
将 html 和 js 代码分离开
- 从服务器加载
- 客户端模板(Handlebars)
避免使用全局变量
-
全局变量带来的问题
- 命名冲突
- 代码脆弱性,依赖全局变量,任何地方都能改变全局变量的值
- 难以测试依赖全局变量的函数
-
意外的全局变量
- 当使用一个未被声明的变量时,JavaScript会自动创建为全局变量
-
单全局变量
-
创建的这个唯一全局对象名是独一无二的(不会和内置API产生冲突),并将所有的功能代码全都挂载到这个全局对象上,因此每个可能的全局变量都会成为你唯一的全局对象的属性,从而不会创建多个全局变量
// 再比如表示三本书 // 方法一 function Book (name) { return `书名:${name}` } const Book1 = Book("name1") const Book2 = Book("name2") const Book3 = Book("name3") console.log(Book1, Book2, Book3) // 单全局变量 class Books { Book1 = Book("name1") Book2 = Book("name2") Book3 = Book("name3") Book (name) { return `书名:${name}` } } console.log(Books.Book1, Books.Book2, Books.Book3) /* *其他例子: jQuery 定义了 $ 和 jQuery *以及vue 定义了 Vue */
-
命名空间(在类中定义分组,方便管理)
class My { dom = { addDom () { ... }, removeDom () { ... } ... } event = { onClick () { ... }, oninput () { ... } ... } }
-
事件处理
-
典型用法 (流水账式写法)
// 常见但是不推荐的写法 handleClick (event) { const popup = document.getElementById("popup") popup.style.left = event.clientX popup.style.top = event.clientY popup.className = "show" }
-
隔离应用逻辑 应用逻辑和用户行为应该区分开(一个函数只做一件事,便于函数复用)
clas Myapp { handleClick (event) { this.showPopup(event) }, showPopup (event) { const popup = document.getElementById("popup") popup.style.left = event.clientX popup.style.top = event.clientY popup.className = "show" } }
-
不要分发事件对象 应用逻辑不应该依赖 event 对象来完成功能(便于测试,便于函数复用)
clas Myapp { handleClick (event) { // 事件处理程序是接触 event 对象的唯一函数 (一个函数只做一件事) event.preventDefault() event.stopPropagation() // 传入应用逻辑 this.showPopup(event.clientX, event.clientY) }, showPopup (x, y) { const popup = document.getElementById("popup") popup.style.left = x popup.style.top = y popup.className = "show" } }
避免空比较
- 使用 "typeof 变量" 的方式检测 字符串,数字,布尔值,undefined,函数,
- 使用 "变量 instanceof 对象" 的方式检测某个值的引用类型(不能用于检测是否属于 Object,因为 js 所有的值原型链最终都是 Object )
-
检测数组 (es5已有自带检测方法)
// 关注对象能做什么,而不是他是什么 function isArr(value) { return typeof value.sort === 'function' } // 更为优雅有效的解决方案 (兼容 IE6 以下版本) function isArray (value) { if (typeof Array.isArray === 'function') { return Array.isArray(value) // 自带判断数组方法 } else { // 某个值内置的 toString 在所有的浏览器中都会返回标准的字符串结果 return Object.prototype.toString.call(value) === '[object Array]' } }
-
检测属性是否存在
-
不好的写法,通过给定的名字检查属性的值
let myObject = { count: 0 } if (myObject['count']) { // 存在但不执行 }
-
好的写法,通过 in 运算符或者 hasOwnPrperty()
let myObject = { count: 0 } if ('count' in myObject) { // in 只检查属性是否存在于对象实例或者对象原型 // 执行 } if (myObject.hasOwnPrperty('count')) { // 只检查是否存在于对象实例 // 执行 }
-
将配置数据从代码中分离出来
-
什么是配置数据 写死在代码里,且将来可能会被修改
- 接口地址
- 重复的值
- 设置(比如页面配置项)
- 任何可能发生变更的值
- ...
- 抽离配置数据,把配置数据拿到外部(类似 vue 中的 data)
- 保存配置文件,把配置单独保存到文件(类似 vue-cli 搭建的项目中 vue.config.js 的作用)
抛出自定义错误
- 什么是错误 帮助我们快速定位代码 bug ,以便于调试维护
-
JavaScript 抛出错误的方法
throw new Error('错误信息')
-
如何书写抛出的错误文本
throw new Error('函数名: 可能的原因')
-
什么时候抛出错误
- 不确定自己的函数在哪些地方调用
- 工具函数
- JavaScript 类库 (函数调用栈应该在进入库代码接口的时候就终止;意思是在调用类库时先要校验然后进行相应的提示,而不是等到)
- 修复了一个很难调试的错误,这时添加一两个自定义错误
- 不想要某种情况发生,为避免这种情况
-
try-catch 语句 catch 代码块不能省略或留空
能在错误抛出前解析它; 抛出错误会中断 js 进程, 使用 try-catch 则不会中断 js 进程 个人理解: try-catch 语句可以用在自己知道可能会出错但是不能明确的知道错误原因, 或者在报错后仍需继续报错代码块之后语句的情况下
不是自己的对象不要动
-
什么是自己的对象
- 自己创建了一个对象,那么自己拥有这个对象
- 自己不是创建对象的人,但是负责维护代码,那么也拥有这个对象
- 只是使用者,不要修改对象
-
对象的使用原则
- 不覆盖对象原型上的属性,方法
- 不在对象原型新增属性,方法
- 不删除对象原型上的属性,方法
-
如何使用?
- 继承
- 设计模式之外观模式(门面模式 Facade Pattern)
-
防止对象被修改
- Object.preventExtensions(对象名) 该方法不能向对象中新添加属性和方法了,但是可以修改对象中存在的属性
- Object.seal(对象名) 密封对象不能新添加属性、不能删除属性。
- Object.freeze(对象名) 冻结对象,即是不可扩展的,也是密封的,而且其属性值也不能修改
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。