Monthly Archives: 一月 2013

Hello World 点点滴滴

setTimeout的精度

前几天刚发了一篇利用setTimeout提高ui响应速度的文章,本来感觉自己对setTimeout的理解已经够深入了,但是昨天偶然听说setTimeout其实是有“bug”的:在setTimeout 0时,并不能精确定时,还说IE会有16ms的延时
虽然早就想到js这种东西定时肯定不会很精确的,而且这个时延对“提高ui响应速度”影响也不大,但是如果是做动画的话,影响可能就比较大了,还说抽空写了段小程序试了试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//定时长度
var OUT_TIME = 0;
//循环次数
var LOOP_COUNT = 100;
$(function(){
  $('a#start').click(function(){
    var t1 = mst();
    loopOut(LOOP_COUNT, t1);
  });
});
//递归循环
function loopOut(i, t1){
  if(--i > 0){
	setTimeout(function(){
	  loopOut(i, t1);
	}, OUT_TIME);
  }else{
    var t2 = mst();
    alert(t2 - t1);
  }
}
//返回当前时间点的毫秒数
function mst(){
  return new Date().getTime();
}

上面的程序,当点击“start”按钮后,首先会取当前时间t1,然后调用loopOut,当循环参数i大于0时,会通过setTimeout定时0ms递归调用loopOut,同时i会减1,形成循环终止条件,放循环满100次时,再次取当前时间,与t1相减,也就是100此setTimeout循环的用时
测试结果发现,“IE会有16ms的延时”,这话并不算错,IE7、8,最后的返回值大概是1530~1550,算起来,一次setTimeout大概要延迟DT=15.4ms
而且chrome/safari/firefox,返回值大概是420~430,即DT=4.2ms

当增大定时长度OUT_TIME时,只有增大到各浏览器的DT时,实际的定时长度才会有效,也就是DT可以看成“最小定时长度”
但是即便定时长度大于DT定时也是不精确的,非IE大概是0.2ms,IE就有点离谱了,比如OUT_TIME=50时,平均每次的定时长度大概是60ms,差了10ms(鄙视)

综上所述,虽然setTimeout传入的时间值是毫秒,但是是在并不精确,如果对精度要求较高,最好好好计算各浏览器的误差

下面讨论另外两种情况

1. 既然setTimeout有这种问题,那么与它类似的setInterval有问题吗?
将上面的“loopOut”方法稍加改进,即可setInterval:

1
2
3
4
5
6
7
8
9
function loopOut(i, t1){
  var si = setInterval(function(){
    if(--i == OUT_TIME){
      var t2 = mst();
      alert(t2 - t1);
      clearInterval(si);
    }
  }, OUT_TIME);
}

这是,IE再次表型了它的卓尔不群:当OUT_TIME=0的时候,setInterval的回调方法只会执行一次,只有OUT_TIME>0,这段脚本在IE里才能正常执行
除此之外,setInterval和前面setTimeout的表现基本相同

2. node.js的setTimeout精确吗?
将上面click部分的代码去掉,稍加修改,既可以让代码再node里执行,但是为了去除代码初始化的影响,这里用了另外一个setTimeout,让其在程序初始化100ms以后在执行

1
2
3
4
setTimeout(function(){
  var t1 = mst();
  loopOut(LOOP_COUNT, t1);
}, 100);

测试结果发现,服务器端脚本相对浏览器端的效率果然高出不少,在循环次数为100的时候,node.js的setTimeout 0ms的延迟大约在0.1ms左右,当把循环次数提高的100000后,平均的延迟降到了0.01ms

Hello World 点点滴滴

如何调试压缩后的js—— JavaScript Source Map的使用

随着webApp的流行,前端应用越来越复杂,js脚本越来越多,为了提高页面页面响应速度,我们常常需要对js进行压缩、整合
但是经过压缩后的js,调试起来会特别麻烦,基本是对于chrome/ff这些“现代”浏览器,如果压缩过的js出了错误,报的错误也是让你无所适从
之前的做法是,在开发环境不回js做压缩、整合,而且在上线前最后确认的verify以及线上环境对js做压缩,这么做虽然可以避免上面的问题,但又是js的加载顺序对功能会有影响,所以还是不能完全模拟线上的情况
更何况,线上可能还会出问题的
这里介绍一种可以调试压缩脚本的方法,就是所谓“Source Map”,大概的原理是这样的:
在使用compiler.jar压缩js的时候,同时生成一个“.map”文件,对压缩前的js诸如变量之类的内容做索引,页面加载min版的js,可以通过对应的.map文件找到压缩前的js文件,如果这是js再出错,可以直接将错误定位到压缩前的js文件内
怎么样,听起来是不是很棒
好,下面就详细讲一下用法
比如页面上有一个js文件:t.js,下面对t.js做压缩

1
2
3
4
5
java -jar /usr/local/compiler.jar \
	--js t.js \
	--create_source_map t.js.map \
	--source_map_format=V3 \
	--js_output_file t.min.js

其中,“create_source_map”就是生成.map文件的名字,“source_map_format”是source map的版本
完了页面,在页面调用t.min.js,故意制造个错误,这是你会发现,页面上的错误指示~~~还TM和以前一样

呵呵,因为还有两步我们还没做:
1. 手动往t.min.js结尾处加上下面这行,告诉浏览器.map文件所在的位置:
//@ sourceMappingURL=t.js.map

2. 开启浏览器的source map支持,悲剧的是,目前只有chrome支持source map,具体未知是在:
“开发者工具” -> “设置”(右下角齿轮) -> 勾选“Enable source maps”

ok了,这是再试试,一旦js报错,点击错误,就能定位到压缩前的js文件上
并且,在开发者工具里的“Sources”标签里,还能找到压缩前的js源码

————-
参考:http://www.csdn.net/article/2013-01-25/2813953-JavaScript-Source-Map
ps:关于.map文件内部各项内容的含义,我并没有仔细研究~~~反正只要设置正确,chrome就可以把错误定位到源js文件

Hello World

Xcode对编译node.js扩展的影响

mac系统下,使用node-gyp编译node.js扩展,总共两步:

1
2
	node-gyp configure
	node-gyp build

悲催的是,两步居然都出了问题
第一步碰上的问题是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
	gyp info spawn args   'build',
	gyp info spawn args   '-Goutput_dir=.' ]
	xcode-select: Error: No Xcode is selected. Use xcode-select -switch <path-to-xcode>, or see the xcode-select manpage (man xcode-select) for further information.
 
	Traceback (most recent call last):
  	  File "/usr/local/lib/node_modules/node-gyp/gyp/gyp", line 18, in <module>
    	sys.exit(gyp.main(sys.argv[1:]))
		//..................
	Exception: Error 2 running xcode-select
	gyp ERR! configure error 
	gyp ERR! stack Error: `gyp` failed with exit code: 1
	gyp ERR! stack     at ChildProcess.onCpExit (/usr/local/lib/node_modules/node-gyp/lib/configure.js:420:16)
	gyp ERR! stack     at ChildProcess.EventEmitter.emit (events.js:91:17)
	gyp ERR! stack     at Process._handle.onexit (child_process.js:678:10)
	gyp ERR! System Darwin 12.2.0
	gyp ERR! command "node" "/usr/local/bin/node-gyp" "configure"
	gyp ERR! cwd /JKL/MyWorld/node/c_m/helloWorld
	gyp ERR! node -v v0.8.9
	gyp ERR! node-gyp -v v0.8.3
	gyp ERR! not ok 
	xcode-select: Error: No Xcode is selected. Use xcode-select -switch <path-to-xcode>, or see the xcode-select manpage (man xcode-select) for further information.

第一反应是,node.js和Xcode有毛关系,google“path-to-xcode”,发现还真是有问题:我的系统曾经装过早期版本的xcode,是默认装在developer系统下,但是最新版本的xcode是在Applications目录里,虽然这对xcode没影响(至少我没发现),但是看起来是对里面的c++模块有影响,具体啥情况,我也不晓得~~只要知道解决方案就可以了:
重新设置xcode-select指到你的Xcode.app

1
	sudo xcode-select -switch /Applications/Xcode.app/

设置了xcode-select以后,“node-gyp configure”通过,但是“node-gyp build”再次出现问题:

1
2
3
4
5
6
7
8
	gyp info it worked if it ends with ok
	gyp info using node-gyp@0.8.3
	gyp info using node@0.8.9 | darwin | x64
	gyp ERR! build error 
	gyp ERR! stack Error: not found: make
	gyp ERR! stack     at F (/usr/local/lib/node_modules/node-gyp/node_modules/which/which.js:43:28)
	//..........
	gyp ERR! not ok

对configure/make这种玩意本来就不熟,于是有迷茫了一阵,继续google,发现据说是缺少GCC(GNU Compiler Collection,GNU编译器集合),还是需要通过Xcode解决:
打开Xcode -> Preferences -> Downloads -> Command Line Tools -> install -> Reboot
下载这个100多M的东西,用了些时间,然后再次“node-gyp build”
~~~ok

Hello World

玩死浏览器的n种方法 (1)—-动态dom集合导致的无限循环

来看看这段简单的dom操作,clone子节点操作代码:将div内的子节点挨个clone一次,然后再插入这个div内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<body>
<a href="javascript:void(0);" id="start">start</a>
<div id="test">
  <div>t1</div>
  <div>t2</div>
</div>
<script type="text/javascript">
window.onload = function(){
  document.getElementById('start').onclick = function(){
	var div = document.getElementById('test'),
	  childs = div.getElementsByTagName('div');
	for(var i = 0; i < childs.length; i++){
	  var d = childs[i].cloneNode(true);
	  div.appendChild(d);
	}
  };
};
</script>
</body>

试着执行一下,你会发现,浏览器卡死了 :(
为什么哪?
因为通过getElementsByTagName取得的节点集合childs是动态的,当继续往父节点div内添加子节点时,也同时将这些节点添加到了childs的里面~~所以循环变成了无限的
对于这个例子,你可以在循环前将将childs.length保存到一个变量里面,然后在循环时,将i与这个变量比较
当然,这样并非真的解决了问题,比如你如果是要通过insertBefore插入节点,即插入的节点不是在已有的节点后面,上面虽然不会死循环,但循环仍然会乱掉,原因很简单:childs是动态的
正切的做法是重新定义一个childs数组,让后将原来的DOM对象childs里的节点,放到js数组childs里面,这时的childs就不再是动态的了

Hello World

请求状态的浏览器关闭后,服务器的响应会继续吗?

相信许多人和我都有相同的习惯:浏览完网页的时候,如果页面响应非常慢,除非特别关心,否则会立即“咔嚓”关掉这个页面。
作为一个web开发者,一直以为浏览器关闭后,服务器脚本(php)自然也会立即停止,然而,今天做了一个测试:

1
2
3
4
5
6
7
8
<?php 
$fp = fopen('/usr/local/logs/test.log', 'a');
for($i = 0; $i &lt; 100; $i++){
	fwrite($fp, "----------$i\n");
	sleep(1);
}
fclose($fp);
?>

把这个脚本放到apache,tail -f test.log,同时tail apache的access日志,用浏览器访问,然后关闭或者停止浏览器,你会发现test.log日志仍然在跑,直到脚本内的循环走完,然后apache的access日志会跳出一条状态为200的日志~~~和正常访问一样