如何培养良好的编程实践

花了三个星期的晚上,已经看完了《编写可维护的JavaScript》这本书。总结以下:第一部分编程风格和第三部分的自动化测试在书籍中的结尾做者有将它总结整理出来,须要的能够自行去阅读,也能够看看我以前整理的(《如何培养良好的编程风格》)。这本书的重点在于第二部分的编程实践,也是最有养分的地方,惋惜做者没有在书中没有去特地总结,我在这里总结一下,以帮助你们一块儿提升代码质量。建议边看边敲边感觉,比单纯的看文章要收获的多。内容有点多,须要耐心耐心。php

1. UI层的松耦合

不少设计模式是为了解决紧耦合的问题。如何作到松耦合,当修改一个组件而不须要更改其它地方的组件的时候,咱们能够说这就是作到了松耦合,也是提升代码可维护性的关键所在。css

1-1. 将JS从Css中抽离出来

示例代码html

// 很差的写法
  .box {
    // Css表达式包裹在一个特殊的expression()函数中
    width: expression(document.body.offsetWidth + 'px')
  }
复制代码

推荐作法:避免使用CSS表达式(IE9以及IE9以上的浏览器再也不支持CSS表达式)前端

1-2. 将CSS从JS中抽离出来

示例代码:git

// 很差的写法
element.style.color = 'red'
element.style.cssText = 'color: red; left: 10px; top: 100px;'
复制代码

当须要经过js来修改元素样式的时候,经过操做CSS的className,最后在js中添加对应的类名便可。github

示例代码:express

/*定义CSS样式*/
.reveal {
  color: red;
  left: 10px;
  top: 100px;
}
复制代码
// 好的写法 - 原生写法
element.className += 'reveal'
// 好的写法 - HTML5
element.classList.add('reveal')
复制代码

推荐作法:js不该当直接操做样式,以便保持和CSS的松耦合。除了修改定位属性的默认值,好比style.top,style.left经过js中修改默认值。编程

1-3. 将JS从HTML中抽离

// 很差的写法
<button onclick="doSomething()">Click Me </button>
复制代码

第一个问题:在于严谨上来看,当按钮上发生点击事件时,doSomething()函数必须存在。可能出现用户点击按钮时该函数还不存在,这时就会报JS错误; 第二个问题:在于可维护性来看,若是你修改了doSomething()函数名,在这个例子中,你须要同时修改HTML和JS两部分的函数代码,这是典型的紧耦合的代码。 改进方法: 示例代码设计模式

function doSomething () {
 // 一些代码
}
var btn = document.getElementById('action-btn')
btn.addEventListener('click', doSomething, false)
复制代码

兼容性处理 IE8以及更早的版本不支持addEventListener()函数, 所以你须要一个标准的函数将这些差别性作封装。数组

示例代码

function addEventListernner(target, type, handler) {
 if (target.addEventListener) {
   target.addEventListener(type, handler, false)
 } else if (target.addEventListener) {
   target.addEventListener('on' + type, handler)
 } else {
   target['on' + type] = handler
 }
}
复制代码

这个函数能够在全部情形下都正常工做,咱们经常像下面这样来使用这个方法

function doSomething () {
 // 一些代码
}
var btn = document.getElementByid('action-btn')
addEventListener(btn, 'click', doSomething)
复制代码

推荐作法:对于"节点驱动"的的库来讲,好比JQ,推荐用事件监听在js文件中绑定节点同时给予对应的函数事件,不推荐直接在html文件上绑定函数事件。

1-4. 将HTML从JS中抽离

// 很差的写法
var div = document.getElementById('mu-div')
div.innerHTML = '<h3>Hello World</h3>'
复制代码

改进方法:

  • a.对于大量的标签,能够采用 - 从服务器加载

  • b.对于少许的标签,能够采用 - 简单的客户端模板

  • c.复杂的客户端模板,能够考虑如Handlebars(http://handlebarsjs.com/)所提供的解决方案,Handlebars是专为浏览器端JS设计的完整的客户端模板系统。

1-5.一句话总结

HTML,CSS,JS,三者的关系应当是相互独立分离的。若是产生交集,出现紧耦合代码,则违反了代码可维护性的原则。

2. 事件处理

情景引入

// 很差的用法
function handleClick (event) {
  var popup = document.getElementById('popup')
  popup.style.left = event.clientX + 'px'
  popup.style.top = event.clientY + 'px'
  popup.className = 'reveal'
}
// 上文中的addEventListener()
addEventListener(element, 'click', handleClick)
复制代码

2-1. 隔离应用逻辑

上述实例代码的问题是事件处理程序(和用户行为相关的)包含了应用逻辑(应用逻辑是和应用相关的功能性代码, 而不是和用户行为相关的). 上述实例代码中,应用逻辑是在特定位置显示一个弹出框,可是有时你须要在用户鼠标移至某个元素上时判断是否显示弹出框,或者当按下键盘上的某个按键时也弹出显示框。 这样多个事件的处理程序执行了一样的应用逻辑,而你的代码却被不当心复制了多份。

将应用逻辑从全部事件处理程序中抽离出来的作法是一种最佳实践,咱们将上述代码重写一下以下:

// 好的写法 -事件处理程序抽离应用逻辑
var MyApplication = {
  handleClick: function (event) {
    this.showPopup(event)
  },
  // 应用逻辑:显示弹出框
  showPopup: function (event) {
    var popup = document.getElementById('popup')
    popup.style.left = event.clientX + 'px'
    popup.style.top = event.clientY + 'px'
    popup.className = 'reveal'
  }
}
addEventListener(element, 'click', function(event) {
  MyApplication.handleClick(event)
})
复制代码

推荐作法: 事件处理程序抽离应用逻辑

2-2. 不要分发事件对象

在剥离出应用逻辑以后,上述代码还存在一个问题,即event对象被无节制分发。它从匿名函的事件处理函数传入了MyApplication.handleClick(), 而后又传入了MyApplication。showPopup(), event对象上包含了不少和事件相关的额外信息,而这段代码只用到了其中的两个。 应用逻辑不该当依赖于event对象来正确完成功能。 最佳的作法是让事件处理程序使用event对象来处理事件,而后拿到所须要的数据传给应用逻辑。 例如:应用逻辑MyApplication。showPopup()方法只须要这两个数据,x坐标和y坐标,咱们将方法重写一下以下:

// 好的写法
var MyApplication = {
  handleClick: function (event) {
    this.showPopup(event.clientX, event.clientY)
  },
  // 应用逻辑:显示弹出框
  showPopup: function (x, y) {
    var popup = document.getElementById('popup')
    popup.style.left = x + 'px'
    popup.style.top = y + 'px'
    popup.className = 'reveal'
  }
}
addListener(element, 'click', function(event) {
  MyApplication.handleClick(event) // 能够这样用
})
复制代码

在这段重写的代码中MyApplication.handleClick()将x坐标和y坐标传入了MyApplication。showPopup(),代替以前传入的事件对象。这样能够很清晰地看到MyApplication。showPopup()所指望 传入的参数,而且在测试或代码的任意位置均可以很轻易地直接调用这段应用逻辑。好比:

// 这样调用很是棒
MyApplication.showPopup(1010)
复制代码

推荐作法: 事件处理程序使用event对象来处理事件, 应用逻辑不该当依赖于event对象来正确完成功能,

2-3. 让事件处理程序成为接触到event对象的惟一的函数

事件处理函数应当在进入应用逻辑以前针对event对象执行任何须要的操做,包括阻止事件或阻止事件冒泡,都应当直接包含在事件处理程序当中。咱们再次将上述代码重写一下以下:

// 好的写法
var MyApplication = {
  handleClick: function (event) {
    // 假设事件支持DOM Level2
    event.preventDefault()
    event.stopPropagation()
    // 传入应用逻辑
    this.showPopup(event.clientX, event.clientY)
  },
  // 应用逻辑:显示弹出框
  showPopup: function (x, y) {
    var popup = document.getElementById('popup')
    popup.style.left = x + 'px'
    popup.style.top = y + 'px'
    popup.className = 'reveal'
  }
}
addEventListener(element, 'click', function(event) {
  MyApplication.handleClick(event) // 能够这样用
})
复制代码

在这段代码中,MyApplication.handleClick是事件处理程序,所以它在将数据传入应用逻辑以前调用了event.preventDefault()和event.stopPropagation(), 这清楚的展现了事件处理程序和应用逻辑之间的分工,由于应用逻辑不须要对event产生依赖,进而在不少地方均可以轻松地使用相同的业务逻辑,包括写测试代码。

推荐作法:让事件处理程序成为接触到event对象的惟一的函数

2-4. 一句话总结

事件处理中的事件处理程序和应用逻辑的关系是独立而分离的。事件处理程序负责处理event对象(不限于阻止事件或阻止事件冒泡),应用逻辑负责接收所须要的数据,不须要对event产生依赖。

3.将配置数据从代码中抽离出来

定义: 配置数据是在应用中写死的值,且未来可能会被修改。

常见的配置数据有:URL,须要展示给用户的字符串,重复的值,设置(好比每页的配置项),任何可能发生变动的值

示例代码

// 将配置数据抽离出来
var config = {
  MSG_INVALID_VALUE: 'Invalid value',
  URL_INVALID: '/errors/invalid.php',
  CSS_SELECTED: 'selected'
}
function validate (value) {
  if (!value) {
    alert (config.MSG_INVALID_VALUE)
    location.href = config.URL_INVALID
  }
}
function toggleSelected (element) {
  if (hasClass(element, config, CSS_SELECTED} {
    removeClass(element, config.CSS_SELECTED)
  } else {
    addClass(element, config.CSS_SELECTED)
  }
复制代码

在这段代码中,咱们将配置数据保存在了config对象中。config对象的每一个属性都保存了一个数据片断,每一个属性名都有前缀,用以代表数据的类型(MSG表示展示给用户的信息,URL表示网络地址,CSS表示这是一个calssName)。固然,命名约定是我的偏好。对于这段代码来讲最重要的一点是,全部的配置数据都从函数中移除,并替换为config对象中的属性占位符。

4.不是你的对象不要动

请牢记,若是你的代码没有建立这些对象,不要修改他们,包括原生对象(Object, Array等等),Dom对象(例如document),BOM对象(例如window),类库的对象

4-1.原则

在面对不是咱们本身拥有的对象面前,应当遵循如下三个原则

不覆盖方法

// 很差的写法
document.getElementById = function () {
  return null    // 引发混论
}
复制代码

不新增方法

Array.prototype.reverseSort = functino () {
  return this.sort().reverse()
}
复制代码

推荐作法: 大多数JavaScript库有一个插件机制,容许为代码库新增一些功能。若是想修改,最佳最可维护的方式是建立一个插件

不删除方法

// 很差的写法 -删除了Dom方法
document.getElementById = null
复制代码

4-2. 更好的途径 --经过继承来扩充对象

在JavaScript中,继承仍然有一些很大的限制。首先,还不能从DOM或BOM对象继承。其次,因为数组索引和length属性之间错综复杂的关系,继承自Array是不能正常工做的。

4-2-1. 基于对象的继承,也常常叫作原型继承,经过ES5的Object.create()方法实现

示例代码

var person = {
  name: 'Nicholas',
  sayName: function () {
    alert(this.name)
  }
}
var myPerson = Object.create(person)
myPerson.sayName = function () {
  alert('Anonymous')
}
myPerson.sayName() // 弹出 'Anonymous' 从新定义myPerson.sayName会自动切断对person.sayName的访问
person.sayName() // 弹出'Nicholas'
复制代码

Object.create()方法的第二个参数的属性和方法将添加到新的对象中

var person = {
  name: 'Nicholas',
  sayName: function () {
    alert(this.name)
  }
}
var myPerson = Object.create(person, {
  name: {value:'Greg'}
  })
myPerson.sayName() // 弹出 'Greg'
person.sayName() // 弹出'Nicholas'
复制代码

一旦以这种方式建立了一个新对象,该新对象彻底能够随意修改。毕竟,你是该对象的拥有者,在本身的项目中能够任意新增方法, 覆盖已存在的方法,甚至是删除方法。

知识点传送门: 关于对象更多的深浅拷贝知识点,请点击这里自行扩展

4-2-2. 基于类型的继承

继承是依赖于原型的,经过构造函数实现

示例代码

function MyError (message) {
  this.message = message
}
MyError.prototype = new Error ()
复制代码

在上例中,MyError类继承自Error(所谓的超类)。MyError.prototype赋值为一个Error的实例。而后,每一个MyError实例从Error那里继承了它的属性和方法,instanceof也能正常工做

function MyError (message) {
  this.message = message
}
MyError.prototype = new Error ()
var error = new MyError('Something bad happened.')
console.log(error instanceof Error) // true
console.log(error instanceof MyError) // true
复制代码

4-2-3.门面模式

门面模式是一种流行的设计模式,它为一个已存在的对象建立一个新的接口。你没法从DOM对象上继承,因此惟一的可以安全地为其新增功能的选择就是建立一个门面。下面是一个DOM对象包装器代码示例

function (element) {
  this.element = element
}
DOMWrapper.prototype.addClass = function (className) {
  element.className += '' + className
}
DOMWrapper.prototype.remove = function () {
  this.element.parentNode.removeChild(this.element)
}
// 用法
var wrapper = new DOMWrapper(document.getElementById('my-div'))
// 添加一个className
wrapper = addClass('selected')
// 删除元素
wrapper.remove()
复制代码

DOMWrapper类型指望传递给其构造器的是一个DOM元素。该元素会保存起来以便之后引用,它还定义了一些操做该元素的方法。addClass()方法是为那些还未 实现HTML5的classList属性的元素增长ClassName的一个简单的方法。remove()方法封装了从DOM中删除一个元素的操做,屏蔽了开发者要访问该元素父节点的需求。

4-2-4.三种类型的对比

从JavaScript的可维护性而言,门面是很是合适的方式,本身能够彻底控制这些接口。你能够容许访问任何底层对象的属性或方法,反之亦然,也就是有效地过滤对该对象的访问。 你也能够对已有的方法进行改造,使其更加简单易用(上段示例代码就是一个案例)。底层的对象不管如何改变,只要修改门面,应用程序就能继续正常工做。 门面实现一个特定接口,让一个对象看上去像另外一个对象,就称做一个适配器。门面和适配器惟一的不一样是前者建立新街口,后者实现已存在的接口。

4-3 阻止修改

ES5引入了几个方法来防止对对象的修改。锁定这些对象,保证任何人不能有意或无心地修改他们不想要的功能。

4-3-1. 三种锁定修改的级别

防止扩展 禁止为对象'添加'属性和方法,但已存在的属性和方法是能够被修改或删除

密封 相似'防止扩展',并且禁止为对象'删除'已存在的属性和方法。

冻结 相似'密封',并且禁止为对象'修改'已存在的属性和方法(全部字段均只读)

每种锁定的类型都有两个方法:一个是用来实施操做,另外一个用来检测是否应用了相应的操做。

4-3-2. 应用示例代码

防止扩展

var person = {
  name: 'Nicholas'
}
// 锁定对象
Object.preventExtensions(person) // 实施可扩展
console.log(Object.isExtensible(person))  // false 检测一个对象是不是可扩展的
person.age = 25 // 正常状况下悄悄地失败,除非在strict模式下则会特地抛出错误提示
复制代码

密封

var person = {
  name: 'Nicholas'
}
// 锁定对象
Object.seal(person)
console.log(Object.isExtensible(person))  // false 检测一个对象是不是可扩展的
console.log(Object.isSealed(person)) // true 检测一个对象是不是密封的
delete person.name // 正常状况下悄悄地失败,除非在strict模式下抛出错误
person.age = 25 // 同上
console.log(person)
复制代码

冻结

var person = {
  name: 'Nicholas'
}
// 锁定对象
Object.freeze(person)
console.log(Object.isExtensible(person))  // false 检测一个对象是不是可扩展的
console.log(Object.isSealed(person)) // true 检测一个对象是不是密封的
console.log(Object.isFrozen(person)) // true 检测一个对象是不是冻结
person.name = 'Greg' // 正常状况下悄悄地失败,除非在strict模式下抛出错误
person.age = 25 // 同上
delete person.name // 同上
console.log(person)
复制代码

4-3-3. 一句话总结

使用ES5中的这些方法是保证你项目不通过你赞成锁定修改的极佳的作法。若是你是一个代码库的做者,极可能想锁定核心库某些部分来保证它们不被意外修改,或者想强迫 容许拓展的地方继续存活着。若是你是一个应用程序的开发者,锁定应用程序的任何不想被修改的部分。这两种状况中,在所有定义好这些对象的功能以后,才能使用上述的方法。 一旦一个对象被锁定了,它将没法解锁。

5.总结

《编写可维护的JavaScript》,第一部分的编程风格,给个人启示是:咱们在用Vue也好,React也好,在用框架前要多注意官方文档列出的编程风格,有助于咱们规范代码结构,这是个小细节也是咱们经常容易忽略的地方。第二部分编程实践,HTML,JS,CSS的相互分离独立,保持松耦合度;事件处理中的事件处理程序和应用逻辑的关系是独立而分离的;将配置数据从代码中抽离出来;不是你的对象不要动;这些细节的改善,对于代码维护度的提升都是颇有帮助的。至于第三部分自动化测试,讲的更多的是像Ant,Ci系统工具的使用与安装。整本书到这里就已经结束了,之后更多的是在工做中的应用。以为对你开发有帮助的能够点赞收藏一波,若是我哪里写错了,但愿能指点出来。若是你有更好的想法或者建议,能够提出来在下方评论区与我交流。你们一块儿进步,共同成长。感谢[鞠躬]。

6.一块儿交流

  • 我的的github仓库,欢迎你们来star一下

  • 我的的微信公众号,付出的前端路,订阅微信公众号yhzg_gz(点击复制,在微信中添加公众号粘贴便可)

ps: 提升本身,与异性交朋友

相关文章
相关标签/搜索