设计模式(二):基础技巧

基础技巧

此章节讨论可以帮助我们写出高质量代码最核心的方法、模式和习惯

编写可维护的代码

  • 阅读性好
  • 具有一致性
  • 预见性
  • 看起来想一个人写的
  • 有文档(一些苦涩难懂的代码段,通过文档 or 注释描述结果,而隐藏其细节)

期望是使用如eslint这类代码规范工具来改善除文档外的情况。

尽量避免使用全局变量

全局变量容易导致变量的重名覆盖,影响代码的正常运行。

var b = 0
// 反模式
function f() {
  var a = b = '1' // 等价于 var a = (b = 1)
}
f()
console.log(b) // '1'

解决方案

通过命名空间或则闭包

访问全局对象

访问不带硬编码处理的标示Window

var global = (function(g) {
  return g
})(this)

这种模式可以在ES3, ES5 and ES5-strict下正常运行

增强内置的原型

尽量避免给内置的原型添加属性,下面情况除外:

  • 在未来的ECMAScript版本或JavaScript的具体实现可能将该功能作为统一的内置方法。
  • 检测自定义属性或方法并未存在。也许其它地方实现了该方法,或是浏览器的JavaScript引擎的一部分。
  • 你明确的用文档记录下来,并与团队交流清楚了。
if(!('myMethod' in Object.prototype)) {
  Object.prototype.myMethod = function() { }
}

var模式

使用一个var在函数顶部进行声明,好处如下:

  • 提供单一的地址查找函数所有局部变量
  • 防止变量定义前被使用的逻辑错误
  • 避免无意识下使用全局变量
  • 更少的代码量

声明提升问题

代码处理分为2个阶段

  1. 创建变量、函数声明及形式参数.这是解析和进入上下文的阶段.(声明提升)
  2. 代码运行时执行过程
    var a = 1
    

for循环

const arr = [1,2,3,4]
for(let i = 0; i < arr.length; i++ ) {}
  1. 通过缓存循环体的长度

    const arr = [1,2,3,4]
    for(let i = 0, len = arr.length; i < len; i++ ) {}
    
  2. 尽可能的减少变量的使用

    const arr = [1,2,3,4]
    for(let i = arr.length;i--;) {}
    
  3. 除非你知道你在自增i,否则尽可能的避免使用i++;

    let i = 0
    let a = i++ // 等价于 let a = i = i + 1
    console.log(a, i) // 1, 1
    

for-in循环

又称为枚举,主要处理对象类型,避免处理数组。

  • ECMAScript5.1规范中提到,for…in循环可能以任意顺序来遍历对象的属性。绝大部分我们不关心对象属性的顺序,但如果是数组,可以能存在一定的风险。
  • 通过对象点的形式添加的属性会挂载在原型上,避免for-in遍历该类属性,请使用hasOwnProperty()方法过滤之。
  • 使用hasOwnProperty()方法前,我们可以通过Object.keys(obj)来判断是否真需要处理它。避免不必要的资源浪费来处理错误的对象数据。
  • 尽量使用Object.prototype.hasOwnProperty.call(obj, property)的形式来处理,避免obj下的hasOwnProperty被改写引发问题。

switch模式

switch模式可以提供更好的可读性和健壮性,需要注意如下几点:

  • 每个caseswitch对齐
  • 每个case语句中使用前代码缩进
  • 每个case语句结尾有明确的break
  • 避免使用fall-throughs(即有意不使用break,使程序按照顺序执行)。如果希望使用该模式,请在标注注明,避免给后来者造成疑惑。
  • default语句作为结束,当所有case都不匹配时能有一个默认。

避免使用eval

eval虽然强大,但请牢记一句俗语:eval()是一个魔鬼

  • 不易调试,无法断点
  • 性能问题,eval的不可预测性导致编码速度低效。
  • 使用压缩(混淆)代码时,使用eval会报错
  • 安全性上,使用不当会引起XSS攻击

setTimeout, setInterval, Function()构造器来传递字符串函数,都会导致类似eval的隐患.

new Function() 与eval()比较相似,唯一不同的是eval会影响到作用域链,而Function更像是沙盒。eval可以访问和修改外部作用域链的变量,而Function不行。

// Function
(function() {
  var local = 1
  Function('local = 3')
  console.log(local) // 1
})()

// eval
;(function() {
  var local = 1
  eval('local = 3')
  console.log(local) // 3
})()

若无法避免需要使用eval,请使用Function代替,减少对局部变量的影响。

数值转化

字符串转化为数字,更多的是使用Number(‘09’), +’09’这类操作。

var str = '09'
+str // 9
Number(str) // 9

针对’09字符串’这类需要转化为数值可采用parseInt

var str = '09字符串'
+str // NaN
Number(str) // NaN
parseInt(str, 10) // 9

总是指定 parseInt 的第二个参数,即转化的进制

命名约定

  • 分隔单词使用驼峰式命名
  • 构造函数使用大写开头
  • 常量使用全大写
  • 私有变量以_开头

有时一个下划线表示受保护的属性,二个下划线表示私有属性

编写注释

好的命名规则可以有效减少注释,让人见词知意。但无法以命名阐述清时,请果断添加注释。

  • 内部复杂的函数体内,使用注释简要说明它都做了些什么,可以让后来者不必在意它的实现细节,更关注于业务代码。
  • 说明一些函数的入参及出参,也是屏蔽实现细节的方法。
  • 用注释说明一些有意为之的代码,及为何怎么做,减少他人的疑惑
  • 说明当前程序的一些事项(比如存在的一些隐藏的缺陷或未完成的功能),让他人能了解该程序的现状。

好的注释能让团队成员快速接手项目,但是过多的无用注释会适得其反。特别注意注释的时效性,一个过时的注释往往比没注释更为可怕。

其它事项

  • 避免使用隐式转化,坚持使用 ===. (减少不必要的脑力开销,猜测 == 是否有意为之)
  • 大括号的位置,合理插入空格让代码拥有更好的可读性
  • if-else优化策略(conditionals
  • 通过reduce处理异步数据(map-and-filter-by-reduce)

参考资料