《编写可维护的 JavaScript》读书笔记

最近阅读了《编写可维护的 JavaScript》,在这里记录一下读书笔记。书中主要基于三个方向来说解怎么增长代码的可维护性:编程风格编程实践自动化javascript

笔记已加入到个人博客里,欢迎 Star。html

编程风格

  • 缩进方式java

    • 使用空格(推荐)。
    • 使用制表符(Tab 键)。git

      • 不一样的操做系统对于制表符的解释不一致,致使看起来不一致。
  • 命名方式github

    • 变量名用名词。编程

      • 常量使用大写单词加下划线 MAX_NUM。
    • 函数名用动词开头数组

      • 构造函数用大驼峰写法。
  • null 对象的用法浏览器

    • 初始化一个变量。
    • 和一个已经初始化的变量作比较。
    • 函数参数指望是对象时,用做参数传入。
    • 函数返回值是对象时,能够当作返回值。
// 好的写法
var person = null;

function getPerson(num){

  if (num >5) {
    return new Person('Lily');
  } else {

    // 好的写法
    return null;
  }
}

var person = new Person();

// 好的写法
console.log(person === null);
  • 不要和非对象,或者未赋值变量作比较。
  • 不要用来检测函数参数是否传入。
var person;

// 很差的写法
console.log(person === null);

function doSomeThing(arr1, arr2){

  // 很差的写法
  if(arr1 === null){
    console.log('arr1 参数没有传递');
  }
}

编写注释

使用注释的通常原则是:让代码变得更清晰。咱们通常经过这几点来注释代码:服务器

  • 难以理解的代码。
  • 可能被误解的代码。
  • 浏览器 hack。

注释的书写规范:dom

  • 注释的上方须要一个空行。
  • 单行注释,若是在尾部注释,须要加一个空格。
  • 多行注释中的每一行需对齐,最好也有一个星号。
  • 能够加一些注释声明。

    • TODO 代码未完成。
    • HACK 代码走了一个捷径,应注明缘由
    • FIXME 代码有问题,应尽快修复
    • REVIEW 代码的改动须要评审
var person = null;

if (condition) {

  // 作一些事情
  doSomeThing(); // 作一些事情
}

/*
  * 这是建立一个对象
  * 第二行
*/
var p = new Person();


/**
@method merge
@param {Object} 被合并的对象
@return {Object} 一个新的对象
**/
function doSomeThing(obj) {
  return Object.assign(obj,{asd:123});
}

// REVIEW: 有更好的写法吗?
if(document.all){

}

语句和表达式

在语句和表达式一章,书中主要是写关于大括号对齐方式等,如下两点让我影响特别深入:

  • 循环中尽量避免使用 continue,使用条件判断替代(可读性更好)。
  • for in 循环只是用来遍历对象的,不要用来遍历数组。若是不须要遍历原型链上的对象,可使用 hasOwnProperty

变量、函数和运算符

  • 定义变量

    • 合并 var 语句,让代码更短。
    • 函数内部的变量定义应做为函数内的第一条语句。
    • for 语句初始化部分不该该有变量声明,应提早到外面。
  • 当即执行的函数须要被一眼看出来,能够将函数用一堆圆括号包裹起来。
  • 严格模式不要加在全局,只应加在须要用到的函数做用域内,避免影响其余库。
  • 禁止使用 eval,new Function,setTimeOut,setInterval 的字符串形式。
  • 三元运算符仅仅用在赋值语句上,不要成为 if else 的替代品。
提示
String 类型能够调用 String 包装器的方法,是由于语句的背后,JS 引擎建立了 String 类型的新实例,紧跟着就销毁了,因此给 String 类型上添加属性和方法是没有用的。

编程实践

在 WEB 开发中,用户界面是由三个彼此隔离又相互做用的层定义的:HTML、CSS、JavaScript。因为这三个部分是相互做用的,应该尽可能分开这几个模块,以加强代码的可维护性。

  • 将 JS 从 CSS 中抽离出来。

    • 避免使用 CSS 表达式。
  • 将 CSS 从 JS 中抽离出来。

    • 避免使用 JS 直接操做 dom.style 属性。
    • 应该只须要操做 CSS 的 className 属性。
  • 将 JS 从 HTML 中抽离出来。

    • 避免使用 DOM0 级事件处理函数。 例如:<div onclick="xxx"></div>
    • 使用自定义事件处理函数。
  • 将 HTML 从 JS 中抽离出来。

    • 避免 JS 使用写死的 HTML 字符串,去 innerHTML 插入代码。
div.innerHTML = '<h1>你好</h1>';

JS 可使用一下几种方式来操做 HTML:

  • 请求服务器,返回一段 HTML 文本,而后 innerHTML。
  • 简单客户端模板,写一个页面不可见的模板,用变量的值去填充模板后,插入到页面中来。
  • 复杂客户端模板,使用 Handlebars,建立模板,渲染数据。

避免建立全局变量

咱们都知道,在全局做用域下定义的变量和方法,都会挂载到 window 对象上,随着对象越挂越多,项目也就愈来愈难以维护了。建立全局变量还会致使如下几个问题:

  • 命名冲突
  • 代码脆弱性

    • 若是大量函数都引用了全局变量,而一旦某个地方不当心改变了值,全部使用的地方就完了。
  • 难以测试

    • 开发环境,测试环境,线上环境,都须要引入一套不一样的全局变量,不方便维护。

解决全局变量方案

  • 单全局变量

    • 只使用一个对象定义在全局变量上,其余的对象都做为这个对象的属性和方法。
    • 引用命名空间概念:

      • 调用 namespace() 来声明开发者将要使用的命名空间,即:将对象挂载到哪一个对象下,若是不存在,就建立一个对象。
  • AMD 模块化

    • 定义一个 define 方法,将模块名,依赖,都传进去,在工厂方法里就能使用所依赖的模块了。
// AMD 模块定义
define('module-name',['dep1','dep2'],function(dep1,dep2){});
  • 使用 RequireJS 更好地引入模块。

RequireJS 增长了另外一个全局函数 require(),专门用来加载指定的依赖和执行回调函数。

require(['my-book'], function(books){
  console.log(books);
});
  • 零全局变量

    • 若是你的脚本很是短,而且不须要和其余代码产生交互,能够使用一个自执行函数来实现模块化

事件处理

在编写事件处理程序时,咱们应该遵照一下几点:

  • 隔离应用逻辑。将应用逻辑从全部的事件处理程序中抽离出来。
  • 不要分发事件对象。只传递须要的数据,给应用逻辑就好了。
  • 让事件处理程序成为接触到 Event 对象的惟一函数。其余须要用到的,就从事件处理程序中传递。

实际应用场景是这样的:

<button onclick="doSomeThing()">click me</button>

<script>
// 事件处理程序
function doSomeThing(e) {
  var clientX = e.clientX;

  // 不要将e 传入应用逻辑中,只传递须要使用的字段
  log(clientX);
}

// 应用逻辑
function log(text){
  console.log(text);
}
</script>

类型检测

检测原始类型

推荐使用 typeof 来检测原始类型的值。typeof 自己是一个表达式,因此推荐使用无括号的写法。

检测自定义类

JS 检测自定义类时,最好的作法是使用 instanceof,这也是惟一的方法。

function Person(){}

var p = new Person():

console.log(p instanceof Person);

instanceof 也能够检测引用对象的值。但可能会检测到原型对象上。

var now = new Date();

console.log(now instanceof Date); // true
console.log(now instanceof Object); // true

检测函数

typeof 是检测 JS 检测函数的最佳选择。但 IE8 及其之前的浏览器对 DOM 上的方法实现方式有问题。

// IE 8 会存在问题
typeof document.getElementById    // object

检测数组

使用 Object.prototype.toString.call(value) === '[object Array]' 检测数组效果很不错,在 ES6 中能够经过 Array.isArray 来实现。

检测属性

检测属性使用如下两个操做符:

  • in 运算符(会检测原型链上的属性)
  • hasOwnProperty

将配置数据从代码中分离出来

在实际的项目中能够遇到这种问题,原本只须要更改一些静态数据,但因为不当心改到了其余业务代码,致使部分业务代码报错。因此,把须要配置的静态数据抽离出来是很是利于维护的。通常按照如下几点将数据抽离出来:

  • URL
  • 展现给用户的字符串
  • 重复的值
  • 设置信息
  • 任何可能发生变动的值

咱们能够将抽离出来的配置数据放在如下几个地方:

  • 抽离数据到一个 JSON 文件中。(经过请求去获取)
  • 抽离封装到一个 JSONP 文件中。(经过 script 标签获取)
  • 使用纯 javascript。(直接读取)

抛出自定义错误

咱们在开发项目时,遇到错误其实并不可怕,可怕的将这些错误带到线上环境中去。为此,在程序的一些关键地方,使用自定义错误,可让咱们在上线前提早发现问题,避免出错的影响变大。编写自定义错误能够遵照一下几点:

  • 老是在错误信息中包含函数名称,以及函数失败的缘由。
  • 只在该抛出错误的地方抛出错误,没必要要过分的进行错误预判,抛出错误。
  • 若是使用 try catch ,catch 里不能为空,应该对错误作一些处理。
  • 对于自定义错误,最好是继承 Error 错误类型,浏览器会给 Error 对象附加一些额外的信息。
function MyError(message){
  this.message = message;
}
MyError.prototype = new Error();

不是你的对象不要动

在咱们编写代码的时候,会用到不少其余对象,一些是存在于上下文做用域中、一些是存在于其余库里等等,这些对象都不是咱们本身定义的。对于这些对象,咱们要作到彻底不去改动。其余对象主要包括:

  • JS 原生对象
  • DOM 对象
  • 浏览器对象模型,BOM 对象
  • 类库的对象

即便是目前项目中的对象,只要不是你写的,也不该该随便修改。

  • 不要覆盖方法
  • 不新增方法

    • 例如:Prototype 库,是在源生 JS 上扩展一些方法,但扩展的方法可能之后被新的规范所使用,Prototype 库不得不当即修改以支持新的规范。
  • 不删除方法

若是是必需要修改其余对象,能够经过继承的方式来克隆一个以前的对象,而后再进行扩展。

  • 基于对象的继承(常规的继承)
  • 基于类型的继承(如继承 Error 类型)

关于 Polyfill

Polyfills 的优势是:若是浏览器提供原生实现,能够很是轻松的移除他们。若是浏览器没有实现,就使用现有的方法,巧妙地实现,实现过程可能会不精确。

为了防止对象修改

  • 防止扩展 Object.preventExtensions() 不可扩展
  • 密封对象 Object.seal() 不能删除已存在属性,不可扩展
  • 冻结对象 Object.freeze() 不能修改已存在的属性,不能删除已存在属性,不可扩展

浏览器嗅探

一、user-agent 方式(能够被篡改)。

二、使用特性检测。为特定浏览器的特性进行测试,并仅当特性存在时便可应用特性检测。例如:

// 早期浏览器不支持 getElementById
if(document.getElementById){
  var dom = document.getElementById('xx')
} else if(document.all){
  var dom = document.all('xx')
}

特性检测的流程:

  • 探测标准的方法。
  • 探测不一样浏览器的特定方法。
  • 都不存在时,提供一个合乎逻辑的备用方法。

避免特性推断

推断是假设并不是事实。例如:这里根据 getElementsByTagName 去推断 getElementById,显然是不合理的。

if(document.getElementsByTagName){
  var dom = document.getElementById('xx')
}

避免浏览器推断

经过 document.all 判断就是 IE 浏览器了,这是“自做聪明”的,由于其余浏览器也可能存在 document.all。

if(document.all){
  console.log('This is IE');
}

自动化

如下列举了书中介绍的自动化配置过程,很实用,如今的项目均可以使用这些思想来配置持续集成。惋惜的是,如今看来书中的信息比较滞后(由于写的早),不少介绍的库已通过时了,这里就不详细介绍了。

  • ant 构建工具
  • 校验语法错误
  • 文件合并和加工
  • 文件精简和压缩
  • 文档化
  • 自动化测试

    • PhantomJS
  • 组装到一块儿

    • jekins

总结

这本书看着很快,三天时间就看完了,但书中的编码规范,编程实践部分仍是学到了很多东西。如今写代码会经常冒出几个问题:注释该怎么写?类型判断要怎么才最好?数据代码是否抽离?

相关文章
相关标签/搜索