可维护性
- 容易理解
- 符合常识
- 容易适配
- 容易扩展
- 容易调试
编码规范
- 可读性
-
- 缩进 一般来说4个空格
- 注释 一般覆盖下列地方
-
-
- 函数和方法 描述用途、使用了什么算法/工具、使用前提、参数含义以及返回值
- 大型代码块 描述完成的任务/功能
- 复杂的算法 独特的解决方法,方便他人和往后自己查阅
- 黑科技 写明解决某个问题而使用的
黑科技
-
- 变量和函数命名 小驼峰 变量名称 函数动词开始 常量大写下划线分割 布尔使用is开头 类名大驼峰
- 变量类型透明化
-
- 初始化表明 null - Object、'' - String、 false/true - Boolean、-1 - Number ...
- 匈牙利表示法 o s i b 作为变量前缀
- 注释表明
松散耦合,各司其职(HTML、CSS、JavaScript)
防止应用某个部分代码和其他部分依赖过于紧密,导致维护成本增加。
- 解耦 HTML/JavaScript (数据层/行为层)
-
- 理想情况下,HTML与JS应该完全分开,通过外部文件引入JS
- 使用JS插入数据时,尽可能不插入标签。改为先隐藏标签,动态显示和隐藏。
- 解耦 CSS/JavaScript (页面显示/行为层)
-
- 动态修改类名而不是直接修改样式
- 解耦 应用程序逻辑/事件处理程序(业务逻辑/EventListener) - 利于将来的测试和开发
-
- 不把event对象传给其他方法,只传递event对象中必要的数据
- 应用程序中每个可能的操作应该无需事件处理程序就可以执行
- 事件处理程序应该处理事件,后续处理交给应用程序逻辑
编码习惯
- 尊重对象所有权
-
- 不给实例或者原型添加属性或方法
- 不重新定义已有的方法
- 利用继承,创建新定义类型继承本来想修改的类型,可以给自定义类型添加功能
- 不声明全局变量
-
- 在确定一个所有人都同意的全家对象名称的基础上,使用命名空间创建对象
- 不比较null
-
- 如果是原始类型数据,使用typeof检查
- 引用类型,instanceof
- Object.prototype.toString.call()
- 提取使用常量 - 使用常量是企业级开发的重要技术,它让代码更易于维护,并且免受数据变化的影响。
-
- 重复使用的值
- 用户界面字符串 - 显示给用户的字符串,以方便实现国际化
- URL 统一集中管理
- 任何可能变化的值
性能
作用域意识
随着作用域链中作用域数量的增加,访问当前作用域外部变量所需的时间也会增加。访问全家变量始终比访问局部变量慢,因为必须遍历作用域链。
- 避免全局查找
-
- 通过在局部作用域中保存document对象的引用,能够明显提升这个函数的性能,因为将全局查找的数量限制为一个。- 只要函数中有引用超过两次的全局对象,就应该保存这个对象为局部变量。
- 不使用with语句
和函数类似,with会创建自己的作用域,因此会加长其中代码的作用域链。
function func() {
with(document.body) {
console.log(tagName);
innerHtml = 'with';
}
}
function func2() {
const body = document.body
console.log(body.tagName);
body.innerHtml = 'with';
}
案例中可以看出,不使用with,处理无需新增新作用域,代码也更容易理解,tagName 和 innerHtml 属于谁也很明确。
选择正确的方法
- 避免不必要的属性查找,减少代码时间复杂度
-
- 使用变量和数组(O(1))相比访问对象(O(n))属性效率更高
- 尽量通过在局部变量中保存值来替代属性查找。
- 优化循环
-
- 简化终止条件 - 每次循环都会计算终止条件,所以它应该尽可能快,要避免属性查找或其他O(n)操作
- 简化循环体 - 循环体最花费时间,确保其中不包含可以轻松转移到循环外部的密集计算
- 使用后测试循环 - for/whiele 先测试循环,do-while 后测试循环。避免了对终止条件的初始评估,应该会更快。
for (let i = 0; i < values.length; i++){
const maxValue = getMaxValue()
maxValue > values[i] && doSth(values[i])
}
// 转移密集计算
const maxValue = getMaxValue()
// 终止条件计算从访问values的length(属性查找) O(n), 变成了访问0 O(1)
for (let i = values.length - 1; i >= 0; i--){
maxValue > values[i] && doSth(values[i])
}
// 改为do-while
let i = values.length - 1
if (i > -1) {
do {
maxValue > values[i] && doSth(values[i])
} while (--i >= 0)
}
- 展开循环
-
- 如果循环次数有限,通常抛弃循环而直接多次调用会更快。展开循环操作可以节省创建循环、计算终止条件的消耗
- 对于大型数据集,使用 达夫设备 的技术能够加快处理速度。
- 避免重复解释
eval('console.log('Hello world!')') // NO
let newFunc = new Function('console.log('Hello world!')') // NO
setTimeout(console.log('Hello world!'), 500) // NO
console.log('Hello world!') // YES
let newFunc = function() {
console.log('Hello world!')
} // YES
setTimeout(function() {
console.log('Hello world!')
}, 500) // YES
- 其他性能优化注意事项
-
- 原生方法很快 比如Math对象的复杂数学运算,比JS函数快得多,比如求正弦、余弦等。
- switch语句很快 - 把最可能的放前面,不太可能的放后面
- 位操作很快 选择性的将某些数学操作改为位操作,可以极大提升复杂计算的效率。比如求模、逻辑AND、逻辑OR等等。
语句最少化
- 多个变量声明,逗号隔开即可
- 插入迭代性值 如let name = value[i++]
- 使用数组和对象字面量 - 即不使用构造函数生成。温馨提示: 不是绝对的法则。
优化DOM交互
- 实时更新最小化 - 以向列表中添加10个元素为例
-
- 方案一,从页面中移除列表,执行更新,然后把列表插回页面中相同位置
- 方案二,使用文档片段(createDocumentFragment)构建DOM结构,然后一次性添加进列表
- 使用innerHtml
-
- 在页面创建DOM节点方式有两种:JS - DOM方法:createElement()和appendChilde()、使用innerHtml
- 对于大量的DOM更新,innerHtml更快。原因是,在给innerHtml赋值时,后台会创建HTML解析器,然后会使用原生DOM调用而不是JavaScript的DOM方法来创建DOM结构。元素DOM方法更快,因为执行编译代码而不是解释代码。
- 使用innerHtml创建DOM结构,虽然拼接字符串也会有一些性能消耗,但是比执行多次DOM操作速度更快。
- 注意1,不要循环调用innerHtml,循环处理好字符串,一次性调用innerHtml即可。
- 注意2,innerHtml可以提升性能,但是会暴露巨大的XSS攻击面。可能被攻击者注入可执行代码,使用需要当心。
- 使用事件委托
-
- 一个页面中事件处理程序的数量与页面响应用户交互的速度有直接关系,尽可能使用事件委托两元减少对页面响应的影响。
- 事件委托利用事件的冒泡。
- 任何冒泡的事件都可以不在事件目标上,而在目标的任何祖先元素上处理。所以可以把事件处理程序添加到负责处理多个目标的高层元素上。有可能的话,应该在文档级添加事件处理程序,因为可以处理整个页面的事件。
- 注意HTMLCollection - 只要返回HTMLCollection对象,就尽量不要访问它。以下情况会返回:
-
- 调用getElementsByTagName()
- 读取元素的childeNode属性
- 读取元素的attributes属性
- 访问特殊集合,如document.form, document.images等
部署
构建流程
代码如果不作处理就直接交给浏览器会有以下问题
- 知识产权问题: 如果把满是注释的代码发布,别人很容易知道你在做什么,重用它,并可能发现安全漏洞
- 文件大小
- 代码组织:可维护性的代码不一定适合浏览器,所以需要前端工程化
所以需要为JavaScript文件建立构建流程
- 文件结构
-
- 把代码分散到多个文件是从可维护性而不是部署角度出发的。对于部署,应该把所有源文件合并为一个或多个汇总文件。
- Web应用程序使用的文件越少越好,因为HTTP请求对某些Web应用程序而言是主要的性能瓶颈
- 使用
- 任务运行器 - Jenkins
- tree-shaking
-
- 实现了摇树优化的构建工具(webpack、grunt、vite等等),可以分析出选择性导入的代码,减少冗余代码,实现文件瘦身。
- 模块打包器 - 识别应用程序中涉及的JS依赖关系,将它们组合成一个大文件,完成对模块的串行组织和拼接,然后最终生成提供给浏览器的输出文件。
-
- Webpack
- Vite
- Roolupt
- Browserify
- ....
验证
代码检查工具可以发现JS代码中的语法错误和常见的编码错误(ESLint、JSLint)
- 使用eval()
- 使用未声明变量
- 遗漏分号
- 不适当的换行
- 不正确地使用逗号
- 遗漏了包含语句的括号
- 遗漏switch的break
- 变量重复声明
- 使用with
- 错误使用等号
- 执行不到的代码
压缩
聚焦于两件事: 代码压缩和传输负载。
- 代码压缩 - 浏览器需要解析的字节数
- 传输负载 - 服务器实践发送给浏览器的字节数
- 代码压缩
-
- 删除空格
- 删除注释
- 缩短变量名、函数名和其他标识符
- JavaScript编译
-
- 删除未使用的代码
- 某些代码转换为更简洁的语法
- 全全局变量、常量和变量行内化
- JavaScript转译 - Babel - ES6、ES7
- HTTP压缩
-
- 请求头部 Accept-Encoding, Gzip/Defalte
如何避免烂代码的规则
- DRY 原则(Don't repeat yourself 不要重复你自己)
- ETC原则。即 Easier To Change,更容易变更
- 不要破坏正交性
“正交性”是几何学中的概念。若两条直线相交后构成直角,它们就是正交的。
“正交性”在计算科学中,表示独立性或解耦性。
对于两个或多个事物,其中一个的改变不影响其他任何一个,则这些事物是正交的。
举个例子,在项目中,如果你改动了 UI 相关的代码,而不影响其他业务逻辑(比如数据库操作,网络访问逻辑等等),那么这样的系统,就属于设计良好的系统。
相反,如果你发现自己改动其中一处的代码,会影响很多地方,那么你就得思考一下,是否需要重构代码了。
一般来说,保持代码的正交性有以下几个实用的方法:
● 使用最少知识原则(迪米特法则)来保持代码的解耦性。
● 避免全局数据。只要代码引用了全局数据,就会将自己绑定到共享该数据的其他组件上。