Skip to content

Latest commit

 

History

History
106 lines (91 loc) · 5.89 KB

DRY开发者的Javascript模式.md

File metadata and controls

106 lines (91 loc) · 5.89 KB

DRY的Javascript模式

原文链接:Patterns for DRY-er JavaScript

最近遇到的一段代码提醒我是时候去整理一下有关Javascript模式的内容了。

问题中提到的代码作用是:当复选框(checkbox)被选中时,去设置表单字段的值;当选中取消后,相应字段的值也将被清空。大概像下面这样:

// config is defined outside of this snippet
// and may contain more than the properties
// we care about 
$('#myCheckbox').click(function() {
  if (this.checked) {
    $('#field_foo').val(config.foo);
    $('#field_bar').val(config.bar);
    $('#field_baz').val(config.baz);
  } else {
    $('#field_foo').val('');
    $('#field_bar').val('');
    $('#field_baz').val('');
  }
});

这是一段简单易读的代码——看上去毫无问题。在另一方面,也很容易看到大量的代码重复,它并没有遵守DRY(don't repeat yourself)原则。

上面的代码在if-else里重复的初始化了多个选择器并调用了相同的方法.val()。看到这里,我就有了重构的冲动。下面是第一步:

$('#myCheckbox').click(function() {
  // note whether the checkbox is checked
  var checked = this.checked;
  
  // iterate over the keys we care about
  $.each(['foo', 'bar', 'baz'], function(i, v) {
    // find the field for the given key
    // and set its value either to the string
    // stored for the key, or to an empty string
    // depending on whether the checkbox was checked
    $('#field_' + v).val(checked ? config[v] : '');
  });
});

看起来和之前的代码有了很大的不同,如果没有了注释,代码自身的可读性也不够以前的好。我心里一直这样告诉自己,写JS代码的人应该能很好的理解JS,所以可读性是可以接受的代价。此外,注释能比代码自说明更好的解释作用和意图。

在这次迭代中,我们将会介绍两种模式:

数组或对象字面量的遍历来实现重复,而且当逻辑相对简单的情况下可以考虑三元运算符来替代。

我们将需要改动到的字段存入数组里,当checkbox被选中时,我们就遍历数组来获取选择器,然后借助三元运算符来设置对应的值。这样,我们就能有效地将11行代码压缩至6行,另一个好处是如果我们需要checkbox来改变更多字段时,也不用新增更多的代码。(这算是过早优化吗?我不想去争辩。如果在编程之前能考虑到这些模式,你会明白编写它们实际上比直接的代码更简单容易。例如,假设复选框影响了20个其它字段,而不是1个?当采取"直接"的方式来解决问题时,你无疑会发现自己在不停的复制和粘贴代码,做着一些低效率的东西。)

使用模式能让你快速的理解自己想要做的事,并使重构的过程不那么痛苦,它还帮助我去梳理代码,发现更多重用的机会。

让我们为自己自豪吧!

现在,有另一个checkbox要用另一张config表对另外的字段做设置,我们应该怎么实现呢?没问题,代码已经有了,只要复制粘贴一下然后改些参数就可以了。呃。。。好像我们又抛弃DRY原则了。

另一个模式出场了:

创建新的函数,并返回已经配置好的功能函数(创建闭包)。

我们将执行创建者函数,然后将它的返回代替之前绑定到click事件时使用的匿名函数。

// handleClick accepts a config object and a makeSelector function
// it returns a function that can be bound to a click event
// using the config object and the makeSelector function to react
// appropriately to the click
var handleClick = function(fields, config, makeSelector) {
  return function() {
    var checked = this.checked;
    fields && $.each(fields, function(i, v) {
      $(makeSelector(v)).val(checked ? config[v] : '');
    });
  }
}

$('#myCheckbox').click(
  // use handleClick to create a function
  // that has these variable baked in
  // pass the created function as the click handling function
  handleClick(['foo', 'bar', 'baz'], myCheckboxConfig, function(field) {
    return '#field_' + field;
  })
);

$('#myOtherCheckbox').click(
  handleClick(['bim', 'bar', 'bop'], myOtherCheckboxConfig, function(field) {
    return 'input[name="' + field + '"]';
  })
);

通过创建函数并返回函数的方式,我们可以将两个函数的不同点有效隔离,去执行共同的部分。如果你的事件处理函数要比这个还小一点,或者你是绑定了5个checkbox,那代码合并带来的好处会更明显。

Javascript提供了很多模式来书写遵循DRY原则的代码,我们要学会去识别和使用它们。同样重要的是,当你在写un-DRY的代码时,复制和粘贴是一个非常明确的指标,其它的地方会相对微妙难以识别。就拿下面两个函数来说,每个函数都只接收列表数组中的一个元素,返回上一个或下一个元素直到首元素或尾元素。

var getNextItem = function($item) {
  return $item.next().length ? $item.next() : $items.first();
}

var getPrevItem = function($item) {
  return $item.prev().length ? $item.prev() : $item.last();
}

在我写这个函数的时候,感觉上它有重复,但我不能快速的想到更精简的函数来代替它。在短暂的思考过后,我决定传入第二个参数: 方向(direction)。这个参数用来决定是返回上一个元素还是下一个元素,还有决定返回首元素或尾元素。在合并以后,它也能有效减少.next().prev()的多次调用。

var getItem = function($item, direction) {
  var $returnItem = $item[direction]();
  return $returnItem.length ? $returnItem : $item[(direction == 'next') ? 'first' : 'last']();
}

学习这些模式,然后找到使用它们的机会是我在工作中饱含乐趣的一个方法。我也希望这能给你一些帮助。