ES6 in Depth: Rest parameters and defaults

今天文章是关于JS函数中更具有表达力的两个特性:剩余参数和默认参数。

剩余参数

创建一个可变的函数作为API是个常见的需求,而这函数可接受任意个参数。例如,String.prototype.concat方法可接受任意数量的字符串参数。利用剩余参数,ES6提供了一种编写可变函数的方式。

为了说明,让我们编写一个简单的可变函数containsAll,用来检查一个字符串是否包含有一定数量的子串。例如,containsAll("banana", "b", "nan") 会返回 true,而containsAll("banana", "c", "nan") 会返回false

这里是一个传统的实现方式:

1
2
3
4
5
6
7
8
9
function containsAll(haystack) {
  for (var i =1; i < arguments.lenght; i++){
    var needle = arguments[i];
    if (haystack.indexOf(needle) == -1 ){
      return false;
    }
  }
  return true;
}

这种实现方式使用了具有魔力的 arguments 对象,它是一个类似数组的对象,包含了传递给函数的所有参数。这代码确实可以达到我们所需求的目标,但是其可读性并不是最佳的。这函数的参数列表中只包括了一个参数haystack,所以如果只看一眼的话,是不可能知道这函数实际上是支持多个参数的。另外,我们必须小心迭代方式,arguments需要是以 1 开始,而不是 0 ,因为 arguments[0] 对应于 haystack。如果我们需要在 haystack 之前或者之后添加额外的参数时,我们不得不要记得更新循环方法。剩余参数 可解决这些问题。下面是一种利用剩余参数的自然的ES6实现方式:

1
2
3
4
5
6
7
8
function containsAll(haystack, ...needles){
  for (var needle of needles){
    if (haystack.indexOf(needle) === -1)I{
      return false;
    }
  }
  return true;
}

这个版本的函数拥有的行为和之前第一个的一样,但是它包含了特殊的...needles语法。让我们看一下containsAll("banana", "b", "nan")是怎么调用此函数的。参数 haystack 如平常一样接受的是传递的第一个参数,值为“banana”。needles之前的省略号表示为一个剩余参数,所有其它的参数会组成一个数组被安排到变量needles中。对于这个例子,needles的值为["b", "nan"]。函数则按正常执行。(注意,我们这里使用了ES6 for-of的循环结构。)

只有函数的最后一个参数可以作为一个剩余参数。在一次调用中,剩余参数之前的参数会和平常一样。所有额外的参数会被放到数组中,并被分配给剩余参数。如果没有额外的参数,剩余参数会简单地被置为空数组;剩余参数不可能会是 undefined

默认参数

通常来说,一个函数并不需要调用所有由调用者传递给的参数,当没有传递参数时,可以智能地使用默认值来作用于这些参数。JS 也有顽固的表单默认参数,当参数没有被传递时会默认设置为default。ES6 推出一种可以指定任意的默认参数的方法。

这里是例子。(其中音符号表示模板字符串,上周已经讨论过了。)

1
2
3
function animalSentence(animals2="tigers", anmals3="bears"){
  return `Lions and ${animals2} and ${animals3}! Oh my!`'
}

对于每个参数,当调用者没有传递值时,包含=的部分是个表达式,会默认将值赋予到参数中。所以,animalSentence() 返回 "Lions and tigers and bears! Oh my!animalSentence("elephants") 返回 "Lions and elephants and bears! Oh my!"animalSentence("elephants", "whales") 返回 "Lions and elephants and whales! Oh my!"

这些微妙的变化来源于 默认参数:

  • 并不像Python, ES6的默认值表达式会在每次函数调用时从左到右执行。这就意味着默认表达式可以使用参数之前的参数值。例如,我们可以让我们的动物句子更有想象力些:
1
2
3
4
5
function animalSentenceFancy(animals2="tigers",
     animals3=(animals2 == "bears") ? "sealions" : "bears")
{
  return `Lions and ${animals2} and ${animals3}! Oh my!`;
}

然后,animalSentenceFancy("bears")会返回"Lions and bears and sealins. Oh my!"

  • 传递 undefined会被认为等价于没有传递参数,因此, animalSentence(undefined, "unicorns") 会返回 "Lions and tigers and unicorns! Oh my!"

  • 当参数传递一个没有值的变量时,其传递的值会被设置为 undefined,所以,

1
function myFunc(a=42, b){ ... }

是允许的,而且其等价于

1
function myFunc(a=42, b=undefined){ ... }

关闭 arguments

我们现在看到,剩余和默认参数可能替代arguments对象的用处,并且,移除 arguments 通常会使得代码更加易读。除了影响可读性,arguments臭名昭著还因为它 影响JS的VM效率

为了使得剩余和默认参数完成取代 arguments,首先第一步需要函数使用剩余或者默认参数,禁止使用 argumentes 对象。尽管支持 arguments 的特性最近不会被移除,但是现在应该尽量避免使用 arguments,而尽可能地使用剩余和默认参数。

浏览器支持

Firefox 从 版本15之后就支持 剩余和默认参数了。

不幸的是,没有其它发布的浏览器版本支持剩余和默认参数。V8 最近支持剩余参数,并且 默认参数 已经提到日程中。JSC 也将 剩余参数默认参数 提到的日程中。

BabelTraceur 解释器都是支持默认参数的,所以可以开始使用它们。

结论

尽管技术上并不允许其它新的行为,但是剩余参数和默认参数可以让JS函数声明更加具有表现力和可读性。欢迎来电!


Note: 感谢 Benjamin Peterson 为 Firefox 实现这些特性,包括他对项目的贡献,同样包括这周的文章教程。

下周,我们将会介绍另外 简单、强大、实现、常用的 ES6 特性。它语法类似于你已经使用的数组和对象,并会生成出一种新的、精确的数组和对象的分离方式。这意味着什么呢?为什么你需要分离对象呢?在下周三加入我们并一同来寻找,来自 Mozilla 工程师 Nick Fitzgerald 展示的 【深入ES6 的去结构化(析构)】。

ES6 in Depth: Template strings

上周,我们已经提到要变步伐。在迭代器和生成器之后,我们来开始些简单的,这是我之前说过的。我也说过,这些是你未曾遇到过的。那,我们来看这承诺是否能够保持吧。

现在,让我们开始些简单的。

音符基础

ES6 推出了一种新的字符串语法,叫做模板字符串。他们看起来很像普通的字符串,除了其使用了音符式的字符,而不仅仅是'"这些引号字符。对于最简单的情况,他们就是字符串:

1
context.fillText(`Ceci n'est pas une chaîne.`, x, y);

但是,之所以称为模板字符串,而不是又普通又老的、仅包含音符的字符串,是因为模板字符串为JS提供了字符串的插入操作。这为JS提供了种好看、且方便的方法来将值插入到字符串中。

错误提示可以有上千百万的方法来实现,但是下面是我内心比较喜欢的:

1
2
3
4
5
6
function authorize(user, action) {
  if (!user.hasPrivilege(action)) {
     throw new Error(
       `User ${user.name} is not authorized to do ${action}.`);
  }
}

在这个例子中,${user.name}${action}叫做 模板替换。JS 会将user.nameaction的值插入到最终的字符串中。这可能会产生一个消息如:User jorendorff is not authorized to do hockey.

目前,这相对于+的拼接操作更显得优雅点,你可能还会希望其详细如下:

  • 代码中的模板替换可以是任意的JS表达式,如 函数调用、算法等等都是允许的。(或许,你甚至还希望能在一个模板字符串中肉插入另一个模板字符串,我称为模板插入。)

  • 如果值不是字符串时,它会使用常用的规则转为字符串。例如,如果action是个对象,那么.toString()方法将会被调用。

  • 如果你需要在模板字符串中写入音符号,你必须避免直接用,而是使用斜杠:\',其会为”’”。

  • 同样的,如果你在模板字符串中需要包含${这两个字符,我不知道你想怎么实现,但是你可以使用write \${$\{来规避。

不像普通的字符串,模板字符串可以进行多行:

1
2
3
$("#warning").html(`
  <h1>Watch out!</h1>
  <p>Unauthorized hockeying can result in penalties of up to ${maxPenalty} minutes.</p> `);

所有模板字符串中的空字符,包括换行符和缩进,都会逐个输出的。

好了。由于我之前在上周的承诺,我感到对你们的大脑需要负一定的责任。所以,先在这说明:我心里有些许的紧张。你现在可以停止往下读,或者应该拿着一杯咖啡、好好享受一下完整而未被融化的大脑。真的,返回去并不丢脸。当 Lopes Goncalves 得到一艘海里怪兽攻击、不被暗礁打翻的船后,他是不是探索完了整个南半球?不,他返回,回家了,然后还吃了顿可口的午餐。你喜欢吃午餐,对不对?

音符的未来

让我们讨论一下新的东西,模板字符串不做什么:

  • 它们不会自动帮你处理特殊字符。为了避免跨域网站的脚本攻击,你将不得不小心地处理非信任的数据,就如你拼接普通的字符串。

  • 它不直接作用于国际化库(对于不同的用户使用不同的语言库)。模板字符串不会处理特定语言的数字和日期的格式,更不用说复数了。

  • 它们并不是模板库的替代品,如 Mustanche 和 Nunjucks。模板字符串没有任何内置的循环语法–例如,利用一数组来构建HTML中table的多行,或者条件。(是的,你可以使用模板插入来做到这目的,但对我来说,你那样做看起来就是是排序,是个笑话。)

ES6 为模板字符串不仅仅提供了一种方法,其可让JS开发人员和库的设计人员更多的能量来解决这些限制或者更多。这一特性特为 标签模板

标签模板的语法很简单,它只是在模板字符串打开重音符之前加入额外的标签,这个标签是SaferHTML,是合法的html字符。然后,我们就可以使用这个标签来解决之前的第一条的限制:自动处理特殊字符。

注意,SaferHTML并不是ES6标准库中的某些东西,而是要我们自己来实现的,

1
2
var message =
  SaferHTML`<p>${bonk.sender} has sent you a bonk.</p>`;

这标签有个简单的身份:SaferHTML,但是一个标签也可以是一个属性,例如 SaferHTML.escape,或者甚至是一个方法调用,如SaterHTML.escape({unicodeControlCharacters:false})。(准确来说,任何的 MemberExpression or CallExpression 都可以作为一个标签。)

我们已经看到,非标签的模板字符串对于简单的字符串拼接是比较快捷的,标签模板对于某些更为便捷的是:函数调用。

之前的代码等价于:

1
2
var message =
  SaferHTML(templateData, bonk.sender);

templateData是一个不可变的数组,它包括了由JS引擎替我们生成的模板所有字符串的各部分。这里,这个数组有两个元素,因为这两部分是由替代的标签来分隔的,所以templateData会如Object.freeze(["<p>"," has seet you a bonk.</p>"])

(实际上,还有一个额外的属性出现在templateData中。我们不会在此文章中使用到,但是,为了完整性,我将进行些说明:templdateData.raw是标签模板中的另外一个包含所有字符串各部分的数组,它看起来像代码中在每部分的左边增加多类如\n的符号,而不直接另外换行,等等。标准的标签 String.raw 就是使用这种字符串。)

这里给了方便的SaferHTML方法来解释字上百万种的符串和替代物。

阅读了以上部分时,你也许会对SaferHTML做什么感到困扰,然后,你试着来实现它吧。毕竟,这只是一个函数而已。你可以在 Firefox 的开发者控制台进行调试。

这里,可能的一个答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function SaferHTML(templateData) {
  var s = templateData[0];
  for (var i = 1; i < arguments.length; i++) {
     var arg = String(arguments[i]);

     // Escape special characters in the substitution.
     s += arg.replace(/&/g, "&amp;")
         .replace(/</g, "&lt;")
         .replace(/>/g, "&gt;");

     // Don't escape special characters in the template.
     s += templateData[i];
  }
  return s;
}

通过这个定义,标签模板SaferHTML <p>${bonk.sender} has sent you a bonk.</p> 会输出为<p>ES6&lt;3er has sent you a bonk.</p>(这结果怎么看都不对啊,作者怎么玩的)。你用户在使用时它都是安全的,即使用户是恶意的用户,如Hacker Steve <script>alert('xss');</script>这样发送一个炸弹。无论代表什么意思。

(顺便提一下,如果函数使用 arguments 对象 来攻击你时确实是个问题,下周会解决掉。这是ES6的另一个新特性,我想你会喜欢的。)

一个简单的例子是不能够说明标签模板其灵活性的,让我们重温一下之前我的提到的模板字符串的短板吧,而现在又可以怎么做:

  • 模板字符串不能自动过滤特殊字符,但是,就如我们看到的,利用标签模板,你使用一个标签来解决这一问题。

实际上,你可以比上面做得更好。

从安全的角度上看,我的SaferHTML函数是十分脆弱的。在HTML中,不同的地方有不同的特殊字符,需求不同的过滤方式,SaferHTML并不能过滤掉所有。但是,通过努力,你可以编写更为智能的SafterHTML函数来对templdateData字符串中的部分HTML,这样子可以让其知道在HTML中哪个是替代物;哪些是内置的元素属性,需要过滤'";哪些是URL查询字符串,需要用URL过滤,而不是HTML过滤,等等。这样子,才能正确处理好每个替代物。

会不会由于HTML的解释速度慢而使目标难到达呢?幸运的是,当标签模板重新执行时,其字符串块并不会改变。SafteHTML 可以将解释结果进行缓存,以提升之后调用的速度。(这缓存可以是 WeakMap, 这是另外的ES6特性,我们将会在以后的文章进行讨论。)

  • 模板字符串没有内置的国际化特性。但是,利用标签,我们可以实现。文章 展示了怎么实现的第一步。下面是个例子,可当作小玩意:
1
2
i18n`Hello ${name}, you have ${amount}:c(CAD) in your bank account.`
// => Hallo Bob, Sie haben 1.234,56 $CA auf Ihrem Bankkonto.

注意,现在这个例子,nameamount是属于Javascript,但是这里又有些不同的不熟悉的代码,那就是:c(CAD),这就是模板中需要替换的字符串部分。Javascript部分由JS的引擎控制,那个字符串部分由 Jack 的 i18n 的标签来控制。用户可以从 i18n 的文档中知道 :c(CAD)表示amount数量的货币,叫做加元。

这就是标签模板的作用。

  • 模板字符串不是Mustache和Nunjucks的替代品,一定程度上是因为它没有内置的循环或条件的语法。但是现在,让我们看看你可以怎么实现它。如果JS没有提供这一特性,那么可以写一个标签来达到这目的。
1
2
3
4
5
6
7
8
9
// Purely hypothetical template language based on
// ES6 tagged templates.
var libraryHtml = hashTemplate`
  <ul>
     #for book in ${myBooks}
       <li><i>#{book.title}</i> by #{book.author}</li>
     #end
  </ul>
`;

其灵活性不止于此。注意,标签函数中的参数并不会自动转为字符串,它们可以是任何类型,返回值也是一样。标签模板甚至并不必要是字符串!你可以使用自定义的标签来实现一定的表达式,DOM 树、图片、代表全部异步进程的Promise、JS的数据结构、GL着色器(GL shaders)…

标签模板欢迎库设计者来创建强大的领域语言。这语言可能看起来并不像JS,但是它们可以无缝地嵌于JS中,并智能地与其它语言相互影响。不负责地说,我不能想象有其它语言可以做到这点。我说不出这特性会带给我们什么,这可能性却是令人兴奋的。

什么时候我可能开始使用?

在服务端,io.js现在已经支持ES6的模板字符串了。

在浏览器,Firefox 34+ 支持模板字符串,它由Guptha Rajagopal 在去年夏天的一个内部项目中实现的。模板字符串还得到 Chrome 41+ 的支持,但是不是包括 IE 和 Safari。现在,如果你需要在网站中使用模板字符串时,你将需要 Babel 或者 Traceur (来转化为ES5)。你现在还可以使用 TypeScript

什么是Markdown?

Hmm?

好问题。

(这部分并不真正与JS有关,如果你不使用Markdown,你可以跳过这部分。)

对于模板字符串,Markdown和JS现在都使用 ` 符号来代码特殊含义。实际上,在Markdown中,它是内容中的代码界定符号。

如果你写以下Markdown文档时,这会带来些问题:

1
To display a message, write `alert(`hello world!`)`.

这会显示为:

To display a message, write `alert(`hello world!`)`.

注意,这里并没有符号输出,Markdown 解释器会将四个空格认为是代码的界定符号,然后使用HTML的标签来替换它。

为了避免这问题,我们在开始时了解到Markdown些特性:你可以多个符号来作为代码的界定符号,如:

1
To display a message, write ``alert(`hello world!`)``.

Gist 有更为详细的内容,它是用Markdown来编写的,你可以看到其源码。

下一步

下周,我们将着手这数十年来程序大大在其它语言十分乐道的两个特性:一、大家喜欢尽可能不写没必要的参数;二、部分人喜欢很多的参数。好吧,我说的是就是函数的参数,这特性是我们所有人都需要知道的。

我们将通过 Firefox中这一特性的作者的角度来了解这些特性,所以,请在下周加入我们吧,猜测一下,其作者 Benjamin Peterson 是怎么深入 ES6的默认参数和剩余参数。

ES6 In Depth: Generators

我对今天的文章感到非常的兴奋,今天,我们将讨论ES6中最为魔法的特性。

从“魔法”这词我想表达什么?对于初学者来说,这个特性是不同于现有JS中的,它一开始就是神秘的。从某种意义上说,它还是语言内部的正常行为(In a sense, it turns the normal behavior of the language inside out)。如果这还不够魔法,我都不知道怎么说了。

另外,这个特性重点还在于,其以简明的代码来消除邪恶代码中的callback。(JS常见的多层callback, 写nodejs会很是恶心。)

是我有点夸大了?让我们开始进入,然后由你自己来作出判断吧。

ES6 生成器介绍

什么是生成器?

让我们开始用一个例子来看一下:

1
2
3
4
5
6
7
8
function* quips(name) {
  yield"hello " + name + "!";
  yield"i hope you are enjoying the blog posts";
  if (name.startsWith("X")) {
    yield"it's cool how your name starts with X, " + name;
  }
  yield"see you later!";
}

这代码来自a talking cat,它可能是现今网络最为重要的一种应用。(继续,点击,一起玩耍吧。当你感觉到完全懵时,回到这里看解释。)

这代码看起来有几分类似函数,是不是?这个称为generator-function(生成器函数),它拥有许多和函数相同的地方。但是,你现在必须清楚以下两个不同点:

  • 一般的函数以function开头,生成器函数是function*
  • 在生成器函数中,yield是关键字,其语法更像是return。区别在于函数(甚至是生成器函数)只能return一次,而生成器函数可能yield多次。生成器中的yield会延缓执行,所以它可以在以后重新唤起。

这就是一般函数和生成器函数的一个很大的不同点。一般函数不能内部暂停,但生成器函数可以。

生成器做什么

当你调用quips()生成器函数时,会发生什么呢?

1
2
3
4
5
6
7
8
9
10
> var iter = quips("jorendorff");
  [object Generator]
> iter.next()
  { value: "hello jorendorff!", done: false }
> iter.next()
  { value: "i hope you are enjoying the blog posts", done: false }
> iter.next()
  { value: "see you later!", done: false }
> iter.next()
  { value: undefined, done: true }

你可能会习惯地按照普通的函数去推断其是怎么运行的。当你调用时,它会马上运行,它一直运行直到碰到return或者throw,这是所有JS程序大大的老习惯。

调用一个生成器的方法类似于quips("jorendorff"),但是,当你调用一个生成器时,它并不立即运行,而返回的是一个暂停的生成器对象(在例子中是iter)。你可以认为这个生成器函数是一个函数调用,它这时是冻结的。具体来说,仅在运行第一行代码之前,它在生成器函数开始时是冻结的。

每次你调用生成器对象的.next()方法时,函数自已会开始解冻,并且直到下一个yield时才会停止。

这就是上面为什么我们每次调用iter.next(),却得到不同的字符串值的原因。这些值来源于quips()函数中的yield表达式。

在最后一次调用iter.next(),我们来到了生成器函数的末尾,所以.done域的结果是true。到达函数的末尾时返回值是undefined,这就是.value域的结果是undefined

现在可能合适回到 the talking cat demo page 把这代码玩起来了,试试将一个yield放到循环里面,会发生什么呢?

从技术的角度来说,每次生成器yield时,生成器的栈架构中,yield的本地变量、参数、临时变量、执行的位置都会从此栈架构中移除。但是,这个生成器依然会对此栈架构保持着引用(或者是复制体),所以之后的.next()调用时会再次激活生成器并继续执行。

这里需要指出的是生成器并不是多线程的在多线程的语言中,多个代码块可以在同一时间一起运行。这会引起不同条件的资源竞争、不确定性、非常好的性能。生成器一点都不像这样,当生成器运行时,它只能运行于调用者的那个线程中,这个执行过程是有序并确定的。并不像系统线程,当生成器碰到yield时,它只会在其内部的相应位置暂停起来。

好了,我们现在知道生成器是什么了。我们知道生成器运行、暂停、重新执行。现在有个大问题?怎么使得这强大的能力变得可用呢?

生成器是迭代器

上周,我们看到的ES6迭代器不仅仅是个内置的类,它们还是这新语言允许扩展的地方。你通过实现 [Symbol.iterator]().next()方法来创建自己的迭代器。

但是,实现这个接口多少都会需要些工作,让我们来看一下在实践中是怎么实现一个迭代器的。例如,让我们来实现一个简单的值域来作简单地递增一个数值,就像旧时的for(:;)循环:

1
2
3
4
// This should "ding" three times
for (var value of range(0, 3)) {
  alert("Ding! at floor #" + value);
}

这里有个解决方案,就是使用ES6的类。(如果对类的概念语法不清楚时,不用担心,我们会在以后的文章中进行说明的。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class RangeIterator {
  constructor(start, stop) {
     this.value = start;
     this.stop = stop;
  }
  [Symbol.iterator]() { return this; }
  next() {
     var value = this.value;
     if (value < this.stop) {
       this.value++;
       return {done: false, value: value};
     } else {
       return {done: true, value: undefined};
     }
  }
}


// Return a new iterator that counts up from 'start' to 'stop'.
function range(start, stop) {
  return new RangeIterator(start, stop);
}

点击这里看效果

这就是如 Java 或者 Swift 的实现方式。它不是很坏,但是它又不是那么微不足道,而不用注意。这代码没有bug?这并不好说。我们试图模拟原来的for(:;)循环,但是看起来不并像,相反,迭代器的协议强制我们去拆散这循环。

看到这里,你或许对迭代器有些许的冷淡了。迭代器好用,但不好实现。

为了让迭代器更为容易地构建,我们不可能向你介绍一种少见、奇怪的新的JS语言流结构。但是,自从我们已有了生成器,我们能不能用它们来实现?让我们试一下吧:

1
2
3
4
function* range(start, stop) {
  for (var i = start; i < stop; i++)
     yield i;
}

点击这里看效果

以上的四行代码的生成器,相比于之前的包含有RangeIterator类的23行Range()实现,复杂度得到了降低。这可能就是生成器是迭代器的原因。所有的生成器都有内置的.next()[Symbol.iterator]()的实现。你只需要写循环的行为就行了。

离开生成器来实现迭代器就像是被强制用被动语态来写一整封长长的邮件。如果你不能通过简单的语言来说明你想表达的意思,那么相反你最终说的会变得很费解。由于没有使用循环语法,必须要用函数式的代码来完成循环,使RangeIterator变得又长又显怪异。因此,生成器就是你应该要的答案。

生成器作为迭代器,我们可以用来做什么呢?

  • 使得任何对象都可迭代。只需要写一个生成器函数来串连this(对象集),yielding (反复地产出)它的每次值。然后,使用这个生成器函数来应用对象的[Symbol.iterator]()方法。
  • 简化构建数组的函数。它支持你构建函数,来返回每次循环都可处理的数组,就像这样:
1
2
3
4
5
6
7
8
9
// Divide the one-dimensional array 'icons'
// into arrays of length 'rowLength'.
function splitIntoRows(icons, rowLength) {
  var rows = [];
  for (var i = 0; i < icons.length; i += rowLength) {
     rows.push(icons.slice(i, i + rowLength));
  }
  return rows;
}

生成器可使这代码变为更短些:

1
2
3
4
5
function* splitIntoRows(icons, rowLength) {
  for (var i = 0; i < icons.length; i += rowLength) {
     yield icons.slice(i, i + rowLength);
  }
}

这两者在行为上的唯一不同的地方是,第一种一次性返回处理后的结果,而第二种是返回数组,是一个迭代器,结果可以按要求逐一进行处理。

  • 得到不常见的大小。你不能创建一个无限的数组,但是,你可以返回一个产生无终止序列的生成器。无论调用者想得到多少值,都可以从这生成器中得到。

  • 重构复杂的循环。你是否写过庞大而丑陋的函数?你会不会想要把它打散到两个更为简单的部分中?生成器是你重构工具中的新的一把刀子。当你面对复杂的循环时,你可以将生成数据的那部分代码抽离出来,放到一个分离出来的生成器函数中。然后,循环就可以变为for (var data of myNewGenerator(args))

  • 配合迭代器使用。ES6 并没有为过滤、重组或者直接处理可迭代的数组集 提供额外的库。但是,生成器是搞定这些的好工具,你只需要几行代码就行。

例如,为了让DOM的NodeList支持Array.prototype.filter的等同功能,而不只是数组。部分代码有:

1
2
3
4
5
6
function* filter(test, iterable) {
  for (var item of iterable) {
     if (test(item))
      yield item;
  }
}

所以,生成器很有用,是不是?这是确定的。它们是以一种难以想象的简单方法来实现自定义的迭代器,这个迭代器是ES6中新的数据循环标准。

但是,这些并不是所有生成器可做的作用,这还没有包括它们能做的最为重要的事情。

生成器和异步代码

这里有些我之前写的JS结束代码:

1
2
3
4
5
6
      };
    })
  });
});
  });
});

也许,你要你的代码中也看到类似的事情。异步API 通常需要一个返回函数(callback),这意味着你每次都需要写额外的匿名函数。所以,如果你使用超过三行的代码来做这三件事时,你则正在处理三层的内嵌代码结构。

下面是我写的些JS代码:

1
2
3
4
5
}).on('close', function(){
  done(undefined, undefined);
}).on('error', function(error) {
  done(error);
});

异步API 有单独的错误处理会话,而不是通过 Exception 来处理,不同的API拥有不同的会话。在多数用户的API中,错误通常会被安静地默认过滤掉。在某些,甚至连成功的完成操作也会被默认过滤掉。

到现在,这些问题可以通过异步编程来简单地解决。但是,我们不得不接受,异步代码并不像同步代码看起来简单、好理解。

生成器提供了新的希望而不一定要这方式(异步代码)。

Q.async()是个实验性的方法,利用它有助于生成器通过promise来完成异步代码,其类似于返回型的异步代码。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Synchronous code to make some noise.
function makeNoise() {
  shake();
  rattle();
  roll();
}

// Asynchronous code to make some noise.
// Returns a Promise object that becomes resolved
// when we're done making noise.
function makeNoise_async() {
  return Q.async(function* () {
     yield shake_async();
     yield rattle_async();
     yield roll_async();
  });
}

异步版本的代码主要的区别是必须在每个要调用的异步函数之前添加yield关键字。

如果在Q.async代码中,添加if表达式或者try/catch代码块时,就如普通的异步代码一般。相比于其它异步代码的方式,至少这看起来不会像是学习一种新语言。

如果你理解到现在所介绍的,你可能会喜欢 James Long 的 更为详细的文章A-Study-on-Solving-Callbacks-with-JavaScript-Generators

所以,生成器有助于进行异步编程,其更适合人类的大脑(思维方式),这工作还要继续进行中。在这些工作中,有帮助的可能是更好的语法。A proposal for async functions 同时利用promise和 生成器 来编程,这灵感来源于 C#,此已经在 ES7 的开发日程中。

什么时候我可以用上这些疯狂的特性

在服务端,你在 io.js 中现在就可以用上生成器了,或者 加上--harmony的Node命令行模式中。

对于浏览器来说,目前只有火狐27+和Chrome39+才支持ES6的生成器。现在为了使用生成器,你需要使用 BabelTraceur ,将ES6的代码转为浏览器更为友好的ES5。

值得说明的是:JS中的生成器首先是由 Brendan Eich 实现的,他的设计接近于由 Icon 灵感而发的 Python 生成器。生成器在2006年被火狐2.0有提到,这标准化的路程是比较波折的,在这期间其语法和行为有了不少的变化。在火狐和Chrome中的ES6生成器是由极客 Andy Wingo 实现的,这是由 Bloomberg 支持的。

yield

对于生成器还有更多的东西要说的。我们还没有提到.throw().return()方法,.next()的可选参数,和 yield*语法。但是,我认为这文章已经够长的了,现在足够引起疑惑了。就如生成器一般,我们应该暂停一下,在以后的时间再继续 。

但是下一周,我们要变一下方向。我们这里已经连续说到了两上高级的主题了,难道还没有说ES6会改变你的生活吗?某些是不是有简单又很有用?某些是不是也会让你微笑?ES6 还有些这样的特性。

即将到来的特性会作用于你每天的代码编程,请下周加入我们,一起深入ES6的模板吧。

Comments

ES6 In Depth: Iterators and the for-of loop

【深入ES6】是深入了解 ECMAScript 标准中第6版本的JS(Javascript)语言新特性的系列文章,ES6是缩写。

你是怎么循环处理数组中的元素的?20年前的JS,你可能会如下:

1
2
3
for (var index = 0; index < myArray.length; index++) {
  console.log(myArray[index]);
}

ES5中,你可以使用内置的方法:

1
2
3
myArray.forEach(function (value) {
  console.log(value);
});

这样子会简单些,但是这里有一个缺点:你不能使用break跳出循环,或者你也不能使用return跳出整个函数。

如果使用for-循环语法,显得更为好些。

那么,for-in循环怎么样呢?

1
2
3
for (var index in myArray) { // don't actually do this
  console.log(myArray[index]);
}

这是个坏主意,因为:

  • 代码中的index是字符串,”0”,”1”,”2”等,并不是真正的数值。因此,你可能会得到不想要的结果,如 “2”+1=”21”,这是十分不方便的。
  • 此循环中的主体部分,不仅仅数组的元素的执行,同时,其他人增加的属性也会被执行。例如,如果你的数组中包含可访问的属性myArray.name,那么这个循环将会执行额外的一次,此时的 index == "name"。甚至,数组原型链中的属性也会被访问。
  • 更加令人无语的是,要某些环境中,以上代码的执行顺序是不确定的。

简单来说,for-in是为以字符串为关键字的旧Objects 来设计的。对于,数组来说,这方法并不好。

强大的 for-of 循环

上周,我已经提到了ES6不会影响到你现有的JS代码。好了,现在成千上万的网站依赖了 for-in,甚至有可能已经使用在数组(Array)中。然而,当数组中使用for-in时,并没有什么好方法来解决它的问题。ES6的唯一方法是增加一些新的循环语法来解决这些问题。

在这里,

1
2
3
for (var value of myArray) {
  console.log(value);
}

相较于已有的其它结构,它是不是也很诱人?好了,我们将会看到for-of优雅的使用技巧。现在,先说明一下:

  • 更加精简。循环中直接使用数组元素的语法;
  • 它避免了for-in的陷阱;
  • 不像forEach,它可以使用breakcontinuereturn

for-in循环会对处理对象的属性。

for-of循环会处理数据 – 如数组中的值。

但这并不是其所有的特性。

for-of 对其它集合的支持

for-of 不仅仅作用于数组,它还可用于类数组的对象,如 DOM NodeList

它还可用于字符串,将字符串看成是 Unicode 字符序列:

1
2
3
for (var chr of "  ") {
  alert(chr);
}

它还可用于MapSet对象中。

噢,对不起。你还没有听说过MapSet对象?好吧,它们是ES6新添加的对象。我们将会在以后用一整篇文章来对它们的主要特点进行说明。如果你已经在其它编程语言中碰到过它们,应该就没什么觉得特别的了。

例如,一个Set对象有利于消除重复数据:

1
2
 // make a set from an array of words
var uniqueWords = new Set(words);

当你拥有一个Set时,你可以这样子很简单地循环其内容:

1
2
3
for (var word of uniqueWords) {
  console.log(word);
}

Map稍微有点不同:里面的数据是 key-value 对,所以,你很可能去结构化(destructuring)来打开 key 和 value,并赋予两个不同的变量:

1
2
3
for (var [key, value] of phoneBookMap) {
  console.log(key + "'s phone number is: " + value);
}

去结构化是ES6的特性,我们也将会在以后的文章中进行说明,此处就写到这里。

现在,你应该有个印象:JS 拥有些新的不一样的集合类,甚至是它们的使用方法。for-of被设计成一个吃苦耐劳的老马的循环表达式,来操作这些集合。

for-of 不能用于原有的对象中,但是,如果你想循环其对象属性时,你可能使用for-in(这就是其产生的原因),或者使用内置的 Object.keys()

1
2
3
4
// dump an object's own enumerable properties to the console
for (var key of Object.keys(someObject)) {
  console.log(key + ": " + someObject[key]);
}

掀开面纱(Under the hood)

“Good artists copy, great artists steal.” —- Pablo Picasso

ES6主要推行的是现在JS语言没有的一些新添加的特性,大多数已经在其它语言中使用并证明是可用的。

for-of循环类似于 C++、Java、C#、Python中的循环表达式,它可以处理JS语言及其标准库中的多种数据结构。但是,它还进行些扩展。

就像这些其它语言中的for/foreach表达式,只要相应方法可调用,for-of就能使用。我们之前讨论到的 Array``Map``Set及其它的对象,都拥有一个迭代方法。

其它类型的对象,都可拥有迭代方法,任何对象都可以。

你可以添加myObject.toString()方法给任何对象,然后JS就知道将对象转化成为字条串。类似这样的,你可以添加myObject[Symbol.iterator]()方法给任何对象,然后JS就知道如何循环这对象了。

例如,假定你正在使用 jQuery,而且非常喜欢使用.each()方法,而你你想jQuery对象能够使用for-of方法,那么可以这样子:

1
2
3
// Since jQuery objects are array-like,
// give them the same iterator method Arrays have
jQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

好吧,我知道你正在想些什么,这个[Symbol.iterator]看起来很怪异。为什么会出现这情况呢?肯定的是,其方法名称是必须的。标准委员会认为 可以只使用.iterator()方法,但是当你部分对象已经有了.iterator()方法时,这是会十分迷惑的。所以,标准委员会使用了Symbol, 而不是一个字符串,也不是仅仅的一个方法。

符号(Symbol)是ES6新添加的特性,我们将会讨论到它–你猜什么时候–在之后的一篇文章中。现在,你只需要知道新标准可以定义新形式(brand-new)的符号,如 Symbol.iterator,它保证肯定是不会与现有的代码有冲突。这一做法从语法上看显得很怪异,但是,这样可以以较小的代价来得到这个通用的新特性和很好地向下兼容。

一个对象拥有[Symbol.iterator]()方法时,称为iteratable。在以后的周里,我们将会看到JS语言的iteratable的用法,不仅仅在for-of,同时还在MapSet的构造函数中,去结构化时的分配(destructuring assignment),还有新的应用广泛的操作符。

迭代对象

现在,你不 一定要对你自己的对象实现为一个迭代器,它可能是有错误的,这也是为什么我们将在下周在说明的原因。但是,为了完整性,让我们看一下一个迭代器是怎么样的。(如果你跳过这一部分,你很可能会错过些易忽略的(crunchy)技术细节。)

一个for-of循环在集合中首先会调用[Symbol.iterator]()方法,它会返回一个新的迭代器对象。这个迭代器对象可以是做任意的拥有.next()方法的对象。当for-of循环每一次执行时,它会反复地调用这个方法。例如,这里有个我认为是最为简单的迭代器对象:

1
2
3
4
5
6
7
8
var zeroesForeverIterator = {
  [Symbol.iterator]: function () {
    return this;
  },
  next: function () {
    return {done: false, value: 0};
  }
};

每次.next()调用时,它会返回相同的结果,告诉for-of: a). 我们还没有停止这个迭代器,b). 下个值是0,这表示for (value of zeroesForeverIterator) {}将是个无限的循环。好吧,常用的迭代器是没必要这么繁琐的。

这个迭代器设计,包含有 .done.value属性,很明显地不同于其它语言的迭代器。在Java中,迭代器有两个不同的.hasNext().next()方法。在Python中,它只有一个.next()方法,当没有更多值时会抛出StopIteration。但是,从根本上说,这三种设计都返回相同的信息。

迭代器对象也可以实现可选的return().throw(exc)方法。当for-of出现错误或者调用breakreturn表达式时,会提前退出,这时调用.return()方法。如果迭代器需要做些清理工作或者清空正式使用的资源,可以对.return()方法运行实现,多数迭代器对象是不需要实现它的。.throw(exc)是一种特殊的情况,for-of是不能调用这方法的。但是,我们将在下周看到更多相关的信息。

现在,我们知道了迭代吕所有的详细信息,我们可以根据以下的方法来写简单的for-of循环:

首先for-of循环:

1
2
3
for (VAR of ITERABLE) {
  STATEMENTS
}

这里是个粗糙的等价物(示例),使用下面的方法和些临时的变量:

1
2
3
4
5
6
7
var $iterator = ITERABLE[Symbol.iterator]();
var $result = $iterator.next();
while (!$result.done) {
  VAR = $result.value;
  STATEMENTS
  $result = $iterator.next();
}

这代码并没有说明.return()是怎么处理的,我们可以添加上去,但是,我认为这样子会让人难以理解它是怎么工作的,还不如简明点好。for-of很易用,但其在后面仍然有很多知识。

什么时候我可以用上

for-of循环在现在所有的发行版本都是支持的,如果你在chrome中打开chrome://flags并启动”Experimental JavaScript”,chrome也会支持。微软Spartan浏览器也支持,但固守的IE版本(in the shiping versions of IE)是不支持的。如果你希望使用新的语法在网页开发中,但是又需要支持IE和Safari,你可以使用如Babel或Google的Traceur,来将ES6代码转化为现在对Web更为友好的ES5。

在服务器端,你不需要转化器,你可以在io.js直接使用for-of,或者在带参--harmony的Node。

好了,今天我们就到这,但是for-of还没有。

在ES6中,有更多的对象可以漂亮地使用for-of,我之所以没有提到,是因为那是下周文章的主题。我认为这是在ES6最为魔法的地方,如果你没有遇到过,比如在Python和C#中,你开始会有些芥蒂。但是,写一个迭代器是很简单的,对重构是很有用的。同时,无论是在浏览器还是服务器中,它可能还会改变我们写异步代码的方式。所以,在下周加入我们吧,我们会深入ES6的生成器(generators)。

Comments(回复)

Brett Zamir wrote on April 30th, 2015 at 17:31:

arr.some() 方法,可以中断循环和跳过循环。

跳过循环:

1
2
3
4
5
6
arr.some(function(e){
  if(e%2==0){return false;};
  console.log(e);
})

# 输出 1, 3, 返回值为false

中断循环:

1
2
3
4
5
6
arr.some(function(e){
  if(e%2==0){return true;};
  console.log(e);
})

# 输出 1, 返回值为true

Andrea Giammarchi wrote on May 1st, 2015 at 07:22:

Andrea 提出个暴力的方法,直接重写数组,来结束整个循环,如下:

1
2
3
4
5
arr = [1,2,3];
arr.forEach(function (v,i,a) {
  console.log(i, v);
  a.length = 0;
});

注意,直接会影响循环的数组,运行此代码后,arr = []

其实,JS原有的forEach通过return可能跳过本次循环的,但是不能直接结束循环。

Comments

ES6 In Depth: An Introduction

欢迎来到【深入ES6】(ES6 in Depth)。 从这周开始,我们将要进入ES6(ECMAScript 6)系列, ES6是一个即将要来临的Javascript语言的新版本。ES6 包含了许多新的特性,它让JS(Javascirpt)更加强大、更有表现力,我们也将在以后一周周地对其进行详细探讨。但是,在详细探讨之前,我们应该花点时间来说明 ES6 是什么,你能从中期待些什么。

ECMAScript 是做什么的

现在有 JS 语言的标准化是由 ECMA 组织维护的ECMAScript项目,这组织类似 W3C。ECMAScript 对 JS 进行了以下工作:

  • 语法 – 解释规则、关键字、表达式、声明、操作符 等等
  • 类型 – 布尔、数值、字符串、对象 等等
  • 原型和继承
  • 对象(object)和函数(function)中的标准库: JSON, Math, Array 方法,Object自省方法( Object introspection methods), 等等

他们不会对其它语言进行定义,如HTML 、CSS, 或者 Web APIs,如 DOM(Document Object Model),这些是语言等的标准化是不同的。ECMAScript 负责的不仅仅是现今在浏览器中运行的 JS,还有 非浏览器运行环境中的JS,如 node.js。

新标准

上周,ECMAScript 第6版本的最终草稿的说明书已经提交到 Ecma General Assembly ,这意味着什么呢?

这意味着,这个夏天,我们将有一个对JS语言核心的新标准了。

这是一个大新闻。JS语言的标准化从未停止过,之前的于2009年第5版本,ES 标准委员会就开始着手 ES6 了。

ES6 对于 JS 语言来说,是一个比较大的升级。同时,你之前的JS也可以继续运行。ES6 在设计时已经考虑到最大程度地兼容现有的JS代码。实际上,很多浏览器已经支持 ES6 的特性,并且不断地完善中,这意味着你的代码可能已经使用上了ES6的特性。如果你现在没有发现兼容性的问题,你很可能在以后也不会遇到。

版本回顾

ECMAScript 在之前发布有 1,2,3,5 版本。

版本4 在哪里 ?其实,版本4是有计划的,并且已经做了大量的工作在上面,但是最后因其设计过于庞大(野心太大)而不得不放弃了。例如,其还包括有静态类型语言中的泛型和类型推断(generics and type inference)。(强类型?)

ES4 一直都有争论,当标准化委员会最后停止这项工作之后 ,成员们同意发布一个相对合适的版本ES5,并继续开发些新特性。准确来说,这次可以说是“妥协的”,这就是为什么在ES5的说明中有这两句话:

ECMAScript 是个有活力的(JS)语言,其改进并不完整。此版本说明中的一些重要技术会在以后的发布版本在出现。

这话也可以理解为对某些事情的承诺。

兑现承诺

2009年发布的ES5,介绍了 Object.create() Object.defineProperty()getterssettersstrict mode ,和 JSON 对象。我已经使用了这些所有的特性,我喜欢ES5为JS语言增加的特性。我使用这些特性写出很多有魔法效果的JS代码,以致不能一一点出这些好的特性。对我来说,最重要的改进可能就是Array方法: .map() .filter(),等等。

不同的是, ES6是这些年和谐工作的产物。新的语言、库、原有JS语言大量的升级,使得ES6是个未曾挖掘的宝藏。新的特性主要从易用性出发,如箭头函数(arrow function)和简单的字符吕报插值方法,还有些脑洞大开的概念,如 proxiesgenerators

ES6 将会改变你写JS代码的方式。

这系列文章将通过代码检查的方式,给JS程度大大展示ES6中的新特性。

我们将从经典的“易忽略特性”开始,这是我一直渴望去深入的部分。所以,下周我们将对 for-of 循环进行探究。

声明

rust-postgres nickel.rs

示例代码段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//
let conn = Connection::connect("postgres://postgres:123456@localhost:5432", &SslMode::None).unwrap();
let stmt = conn.prepare("SELECT id, name, data FROM person").unwrap();
println!("{:?}", stmt.query(&[]).unwrap());
let rows = stmt.query(&[]).unwrap();
println!("{:?}", rows.get(0));
let row = rows.get(0);
println!("{:?}", row.len());
println!("id: {:?}", row.columns());

let row_id: i32 = row.get(0);
let row_name: String = row.get("name");
println!("{:?},,,{:?}", row_id, row_name);

//
let mut server = Nickel::new();
server.get("**", middleware!(row_name.clone()));
server.listen("127.0.0.1:6767");

conn::prepare 返回的是 Statement, statement需要真正执行,如 query(&[]) 才能真正查询数据库,&[] 返回的内置值;

本来返回的是 Result, 但是 代码中 直接 unwrap() , 所以直接得到 rows. 其中,有 Rows 和 Row 这两种不同的结构。rows.len() 得到记录数量, row.len() 得到是列数量。

注意: 在row.get() 时,需要指明返回的类型。其中,row.get()可以通过Index 或者 名称访问到数据。

直接 middleware(row_name) 会出现 cannot move out of captured outer variable in anFnclosure. 的错误,作用域的问题。这时可能通过 clone() 的方式处理。

http://rustbyexample.com/trait/clone.html

1
When dealing with resources, the default behavior is to transfer them during assignments or function calls.

没有 update_column 的方法,只能看源代码 https://github.com/mongoid/mongoid/blob/master/lib/mongoid/timestamps/updated.rb 发现其代码中有,

1
2
set_callback :create, :before, :set_updated_at
set_callback :update, :before, :set_updated_at

联想rails中方法,需要在方法中,特定的方法重新设置此内容, 如

1
2
3
4
5
6
7
8
9
10
11
def skip_auto_updated_at(&block)
  User.skip_callback(:update, :before, :set_updated_at)
  yield
  User.set_callback(:update, :before, :set_updated_at)
end

def update_some_column(attrs)
  skip_auto_updated_at do
    update_attributes(attrs)
  end
end

go
Comments

Go 语言前几年出来之时,火了一段时间,然后相对寂静了些时间,最近似乎又开始热了。 促使我了解这一语言的动力是 Ruby-China 上老大居然用 go 搭建了简单的 类似 web 程序,看来rubyist都是堆喜欢折腾的妖人。

为此,我也打算浅尝一下:

  • ubuntu 环境安装
  • Sublime plugin: GoSumlime

go 安装不麻烦,就是它那类似先编译再执行的传统变化的东西有点烦,安装完go 后,可以go env得到:

1
2
3
4
5
6
7
8
9
10
11
GOROOT="/usr/lib/go"
GOBIN=""
GOARCH="amd64"
GOCHAR="6"
GOOS="linux"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOTOOLDIR="/usr/lib/go/pkg/tool/linux_amd64"
GOGCCFLAGS="-g -O2 -fPIC -m64 -pthread"
CGO_ENABLED="1"

初略看了第一部分,确实有点意思,但亦些难受。

因为 Go 语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性

这个有意思,大家玩对象编程时,出个直接接口编程;但是,很多平时觉得必要的功能没有,也可能这书太落后的原因,它列出了个人觉得应该它却说没有的特性:

  • 为了简化设计,不支持函数重载和操作符重载
  • 不支持动态链接库
  • 不支持静态变量

或许也是go 的优势,以后仔细看看再深入吧。

Comments

好久不写东西了,上来看了自己的github,空了好长的时间。

之前电脑重装,之前的项目已经不在了。重新的时候只是将md文件备份,其它的样式都没有备份。所以不得不重新抽时间折腾了。

很简单,直接走octopress的流程就可以了。二货的我(突然想到今天电影《银河战队》的foreigner说二货有点搞,中国市场真心大啊)将github地址搞错了,以为将.git/config的remote改了就可以了,不然,其实要修改的是_deploy/.git/config。下次记住了。

之前正确使用这工具,没有那么傻的事情,突然傻一回,反而搞懂了其大致的原理。

我还对样式进行了修改,最近迷上了紫色,主题颜色打紫了。

充数了,睡觉。

对于登录,为了防止机器人,部分网站需要配合验证码进行登录。常用的Gem有 recaptcha 和 simple_captcha。

下面简要描写一下,我的使用过程。项目是在他人的基础上修改的,他人已经使用 simple_captcha 提供了验证码登录的功能。但是没有刷新的按键功能。

对于“无刷新的验证码”登录功能,可以参考 https://github.com/galetahub/simple-captcha。 以下是添加刷新功能的过程。

原代码中,登录页面的views代码为:

1
<%= show_simple_captcha(:distortion => 'high',:image_style => 'simply_green') %>

此代码会自动添加输入框和验证码图片,其中的参数可以参考之前 github 项目链接。

需要添加刷新功能,就添加按钮:

1
2
3
4
<%= show_simple_captcha(:distortion => 'high',:image_style => 'simply_green') %>
<div class="refresh_simple_captcha">
    <a href="javascript:;">点击刷新验证码</a>
</div>

通过观察,页面中的验证码图片的链接值:

1
<img src="http://127.0.0.1:3000/simple_captcha/simple_captcha?distortion=high&amp;image_style=simply_green&amp;simple_captcha_key=9e02709d78e964e4802b6b56937cdc63a45eac87&amp;time=1394527382" alt="simple_captcha.jpg">

可以看到有四个参数值,包括:distortion, image_style, simple_captcha_key, time.

从资源的链接值中,可以试图简单并直接去改变time的值。

1
2
var src = $('#simple_captcha img').attr('src').split('time=')+'time='+(new Date()).valueOf();
$('#simple_captcha img').attr('src', src);

这个方案或许能够改变,但是字符还是那几个;甚至,可能没有发生变化。可以想象一下其它的参数,毕竟是刷新吧,可以将distortion=low了,提升一下差别特性。


那么,如果想进一步,如何使得字符也改变呢?

需要添加额外的接口,

1
2
3
4
5
class SimpleCaptchaController < ActionController::Base
  def update_simple_captcha
    render :layout => false
  end
end

添加 views/simple_captcha/update_simple_captcha.erb 文件,

1
<%=raw show_simple_captcha(:distortion => 'high',:image_style => 'simply_green') %>

注意,参数是要加上去的。

1
2
3
4
5
$('div.refresh_simple_captcha a').bind('click',function(e){
  $.get('/simple_captcha/update_simple_captcha', function(data){
    $("div#simple_captcha").replaceWith(data);
  });
});

这样就字符就可以改变了。