在JavaScript异步和同步之间穿梭

异步和同步

如果你正在使用JavaScript,那么你对异步和同步的代码并不会陌生。经常接触到的异步的用法很多,比如一个DOM事件的调用,一个Ajax请求或是NodeJS中读文件方法的调用等等都是在使用异步调用。当你遍历取到的数据并加上处理逻辑的时候,你就会使用上同步的代码。整个编码的过程里,其实你就是在同步和异步的方式之间穿梭。

无需多说同步代码是怎么运行的,程序顺序执行的方式是学习编程的第一课,但它却先入为主地给我们的脑子里烙了个印,以至于很多开发者想学JS的时候,觉得JS是个调用方式特别的语言,需要时间去适应同步到异步的思维转换。所以我期望更多地介绍异步方式的调用,帮助大家打破那些受禁锢的异步编程思维

异步调用并不会阻止代码的顺序执行,而是在将来的某一个时刻触发设置好的逻辑,所以我们

  1. 并不知道逻辑什么时候会被调用
  2. 只能定义当触发的时候逻辑是什么
  3. 只能等待,同时可以去处理其他的逻辑

setTimeout就是这样的一个异步调用。

setTimeout(function () {
  console.log(1);
}, 0);
console.log(2);

// output
// 2
// 1

冒泡排序动画的例子

冒泡算法人人都会,但是如果需要加上动画效果,算法就会变得有趣得多。先看看冒泡逻辑是怎么写的。

function bubbleSort(arr) {
  for(var i = 0, len = arr.length ; i < len - 1 ; i++){
    for(var ii = 0; ii < len - i - 1; ii++){
      if(arr[ii] > arr[ii + 1]){
        swap(arr, ii, ii + 1);
      }
    }
  }
}

需要改变的点就是swap方法需要是异步的,当两两交换的动画结束后,继续下一步比较,请参考Comparison Sorting Algorithms里的动画。这个例子虽然简单,但其实在Web应用和页游中,需要在计算逻辑(同步)和可视化(异步)之间频繁切换的场景还很多。

为达目的,于是把代码写成了这样。

function asyncBubbleSort(arr, i){
  i = i || 0;
  if(i == arr.length - 1) return;

  (function bubble(arr, ii){
    if(ii != arr.length - i - 1){
      if(arr[ii] > arr[ii + 1]){
        /* 异步的swap */
        asyncSwap(arr, ii, ii + 1, function(){
          bubble(arr, ++ii);
        });  
      }else bubble(arr, ++ii);
    }else{
      bubble(arr, ++i);
    }
  })(arr, 0);
}

这个冒泡的算法逻辑已经面目全非,和脑海中的冒泡排序天差地别,试想如果是遇到更加复杂的场景,那这个逻辑将会多么的复杂。带着问题,我们先看看目前在JS异步调用方面,我们都有些什么尝试。

Read More...

Viff,一种新的页面样式测试策略

测试页面样式时遇到的问题

在我们测试Web页面的时候,往往需要面对如下几个问题

手工测试多个浏览器

多浏览器 跨浏览器测试,测试页面在多个浏览器中的表现,浏览器可能包括Chrome,Firefox,Safari,IE和Opera。除了测试基本功能是否健全以外,还需要测试外观样式,因为当前的CSS技术在各个浏览器里的表现不完全一致。比如会看看IE和其他现代浏览器的差距有多大,从而判定之间的差别是否能被接受。

除了测试多个浏览器之外,很可能需要进行多版本的测试,现在浏览器的更新速度是非常快的,这样就导致了用户群在浏览器版本之间的跨度变大,为了保证质量,多个版本的测试也是必要的。比如IE6到IE10的测试。

其次,随着Mobile Webapp的发展,响应式设计的广泛应用,页面的测试还需要照顾到移动设备和多尺寸屏幕。

可以看出,页面样式的测试情况是十分复杂的,更甚的是,我们现在都是用手工在测试,耽误很多时间。

可复用的样式代码容易影响现有样式

可复用的样式开发

我们推荐在开发样式的时候使用SASS或LESS的CSS预编译语言,这样可以更好的管理CSS代码,但是也会导致一个问题,一个可复用的样式的修改可能会影响很多个页面,比如一个颜色变量的改变。而我们往往是让页面引入所有样式,很难判断哪些页面会有影响。可能一个能力很强的QA能预见到一些并做了测试,可能有些影响到了回归测试的时候才发现,也可能到了生产环境上也没有发现。

现有CSS测试的问题

样式不是不可以“测试”的,项目中可能会使用

Read More...

Web应用E2E测试框架

Web应用E2E(End To End)测试是模拟用户进行页面操作,通过来判断页面状态的变化,从而检查功能是否运行正常的测试方法。为了模拟用户操作,开发者会选择浏览器驱动来完成,Selenium就是其中一个。开发者可以使用多种语言开发Selenium脚本,并且运行在多种浏览器上。我是两年前接触到selenium的,当时还不知道什么是E2E测试,就知道使用了一种能够模拟用户浏览器操作的神器,那时使用的语言是C#,其实想过它是怎么工作的,但由于当时学艺不精,未能完全破解。

这篇文章并不是来说Selenium的好,它的好处显而易见,而且目前几乎没有另一工具能取代它的地位。时间在推移,各种技术在飞速发展,我们却还是一如既往地在使用Selenium或者建立在它之上的测试框架。反思下,我之前从没想过开发者需要的E2E测试框架中到底应该有什么,如果你也没有想过,那看看我现在的理解是什么。

我期望的E2E框架

支持多浏览器

首先,为了测试浏览器兼容性,需要考虑桌面浏览器和移动平台浏览器。目前Selenium已经解决了iOS和Android的浏览器驱动问题,可以在真实移动设备上模拟用户行为,测试移动网络上的表现。

其次,每当想到所有浏览器都需要测试就觉得糟透了,即使有了自动化E2E测试,如果是每个浏览器顺序跑一遍,那么每一次提交都需要等待十多二十分钟的测试时间,当然是不能接受的。所以说,具备同时驱动所有浏览器的能力是非常必要的。

支持远程测试

如果我把测试看成一种检测的服务,待测设备看成一个目标的话,那么只要我有设备,不管是真机还是模拟器,只要有浏览器我就可以使用该服务看Web应用在此设备上的表现。同时,我也期望设备不需要通过USB连接到电脑上来测试。

Read More...

JavaScript严格模式

本文是我对《Strict mode》的翻译,文中的描述含我个人的理解,有可能和原文有所出入,望理解。

使用ECMAScript 5的严格模式(“strict mode”)会进入受限制的JavaScript模式。严格模式下的JavaScript并不是标准模式的一个子集,而是直接使用了不同语意的代码。JavaScript在支持和不支持严格模式的浏览器之间会有着不同的表现,所以功能开发时不要依赖于严格模式。严格模式的代码和非严格模式的代码是可以共存的,在严格模式下,非严格模式代码会递增地想严格模式代码转化。

严格模式下的JavaScript有很多处改变。首先要说说的是,严格模式下的JavaScript会表现得没那么多陷阱,该报错的时候就报错。另外,严格模式修复了JavaScript中的一些错误,使得JavaScript解释器更好地进行代码优化,也就是说严格模式下的代码运行地会快一些。同时,严格模式还阻止开发者使用一些可能会用于以后ECMAScript中的语法。

如何使用严格模式

严格模式可以使用于全局所有脚本代码,也可以单独使用到一个方法中。它可以使用在eval中的代码、Function中的代码、事件处理属性和在setTimeout方法中传入的字符串和全局脚本中。如果在一个以{}括号包裹住的代码块中使用严格模式是没有作用的。

全局脚本中使用严格模式

在全局脚本中使用严格模式其实很简单,只用在所有代码之前加入"use strict;"就可以了。

// Whole-script strict mode syntax
"use strict";
var v = "Hi! I'm a strict mode script!";

这种语法有个陷阱需要注意,它不能盲目地连接不冲突的脚本。用一段严格模式下的脚本去连接一段严格或非严格模式下的脚本,整段脚本看上去是严格的;用一段非严格模式下的脚本连接一段严格或非严格模式下脚本看上去是非严格的。但是问题就出在多段严格和非严格模式下的脚本混合组合在一起,最终无法判断整段脚本是严格的还是非严格的。所以我们推荐你把"use strict;"设置在方法级别(function_by_function)

你可以使用把整段脚本包含在一个function中,然后在方法外部使用严格模式。这可以帮助你避免混合连接脚本的问题,但是你必须把方法中的变量或方法暴露出来供外部使用。

在方法中使用严格模式

同样的,如果想要在function上使用严格模式,只要在方法中其他语句之前写上"use strict;"就可以了。

function strict () {
  // Function-level strict mode syntas
  'use strict';
  function nested() { return 'And so am I!'; }
  return 'Hi! I'm a strict mode function!  ' + nested();
}

function notStrict () {
  return 'I'm not strict.';
}

Read More...

跨了个域

如今是一个平台众多,软件开发平民化,软件服务可拼凑成产品的时代。优秀的互联网产品,像微博,微信,Github,Instagram都变成平台,提供一些优雅或不优雅的API让靠谱或不靠谱的开发者来玩,这些API里肯定会涉及到跨域访问的知识。靠谱的开发者即使遇到没有开发文档也能摸索出套路,反之就需要了解下跨域访问的知识了。

为什么需要“踩过界”

跨域访问指的是不同源的A和B网站,A请求访问B的资源。资源包括B网站的图片、视频、数据、文件等等。例如,新浪微博提供的微博API就是提供给不同源网站访问微博数据的接口;又如,A网站嵌入了B网站的图片或视频资源也属于跨域访问。

那么,为什么不能直接访问呢?等等,让我们先来聊聊浏览器同源策略(Same-Origin Policy),同源策略限制了本网站的脚本不能对其他源的网站资源进行操作。两个域必须满足协议(protocol)、端口(port)、主地址(host)都是一致的,才能相互请求资源。

假设有不同源的源A和源B,源A对源B的读操作指的是:

  1. 读取源B的JavaScript,CSS的源代码(受到限制)
  2. 读取源B的返回的文档,JSON等数据(受到限制)
  3. 读取源B的图片的二进制数据(受到限制)

源A对源B的写操作指的是源A发送数据到源B,主要包括:

  1. 源A对源B进行表单,Ajax(XMLHttpRequest或XDomainRequest)POST方式提交
  2. 源A嵌入指向源B的链接,点击后发生的跳转
  3. 源A的脚本操作嵌入到iframe中的源B的DOM对象(受到限制)
  4. 源A使用postMessage发送嵌入到iframe中的的源B

以上读和写操作,加上了(受到限制)的操作都属于浏览器同源策略限制。

其中最重要的危害最大的是写操作中的对源B的DOM对象进行操作。就以支付宝做一个例子,作为攻击者,我申请了一个网址叫做http://alipay.tb.com,整个网站没有内容就一个隐藏的iframe嵌入了http://alipay.com,因为支付宝会记录登陆用户的cookie为了让用户不用每次访问都需要登陆,在没有同源策略的保护下,我可以控制你支付宝,获取你的余额记录和消费记录(此时没有了读取文档限制)或直接利用脚本帮我进行支付,而用户不会洞察到什么,只是收到了消费的短信通知,钱财不翼而飞,损失很大。可见在没有同源策略下的互联网是多么的恐怖。不过放心,即使没有同源策略我们的支付宝也没有这么弱,支付时还需要支付密码。

不过光有浏览器同源策略是不能完全阻止脚本在你已经登陆的网站上肆虐操作,如果用户主动点击了页面上加载不安全脚本的链接,不安全脚本也会趁虚而入,我们把这种攻击叫做XSS(Cross Site Scripting)。目前可能遇见的方式有这么几种:1、点击了链接加载不安全脚本,2、保存成bookmark的脚本不安全,3、Chrome插件被注入恶意脚本。这里就有个血淋淋的新浪微博遭到XSS攻击的例子

在读取操作中有一项限制很有趣,就是不同源之间读取文档,JSON数据是受限制的。提供API就是为了让不同源之间可以相互访问,但是因为同源策略的存在,源B的JSON数据不能直接被读取到,导致现在出现了各种各样的跨域访问方式,例如JSONP,CORS(Cross Origin Resource Sharing),后台代理等。

跨域访问需要跨越的就是同源策略的多个限制。现在我们来看看几种使用广泛的跨域访问方式是如何工作的。

Read More...