一月 22nd, 2010

jssc 5.0 beta5

9 Comments, jssc, 前端开发, by army8735.

下载页面:http://code.google.com/p/jssc/downloads/list

源码地址:http://jssc.googlecode.com/svn/trunk/

预览效果:http://army8735.org/wp-content/uploads/jssc/

全部是细节方面的调整。

性能有所略微的提升,体积稍微减少一点,可能整体没啥感觉。因为性能瓶颈主要在两方面:as的分析阶段和js的显示阶段。分析阶段中主要是词法分析阶段和字符串拼接阶段,调整的是词法分析阶段,而这个部分占整体所耗时间不是最多的,所以提升不明显。

接口稍微改了下,具体的看源代码。php由原本的纯php代码变为了内嵌显示方式,像html那样。

这个可能是最后一个beta版了,rc如果放出基本都是针对bug的修补,另外会全面更新wiki的使用方法。

说下5.x系列的计划吧:

  1. 首先是增加语言:诸如jsp、ruby、csharp等等。语系的增加不会增加子版本号,如5.1,而是后缀的小版本号——5.0.1。
  2. 新特性增加将以子版本号形式出现,譬如5.1版本首先考虑的是缓存输出优化(针对代码行上万的高亮显示)。
  3. 也是以前考虑实现而没有实现的特性:自动格式化,这个可能难一点,在以后的计划之内吧。

暂且这么多,有想到新的或者别人的想法再列入计划里。

一月 7th, 2010

智能JPEG优化技术

8 Comments, 其它, 前端开发, 翻译, by army8735.

同事发来了一篇《Clever JPEG Optimization Techniques》,文中提到的部分技术很细致,分享下。

大部分人在考虑图像压缩优化的时候,还只停留在设置图像处理软件的保存选项上。另外我们也有一些常用的优化工具,诸如OptiPNG和jpegtran。但是还有一些鲜为人知的方法,比如即将介绍的“8像素格优化方法”,它的原理是基于图像数据存储的格式上的。

8像素格

众所周知,jpeg图像存储是以8像素格为基本单位的,看下图示例:

8pixel-1 32×32 pixels, Quality: 10 (in Photoshop), 396 bytes.

两个白色块大小均为8×8像素,图像保存质量为低。可以看出,左上角的方块很清晰,右下角却出现了杂色,这是为什么呢?让我们放大图片并画出参考线格:

8pixel-2

可以看出,左上角的方块恰好在8×8格子里面(占据4个),而右下角的却横跨了9个格子,除了中间部分占满一个格子外,周围8个都只占据一部分。

由于jpeg存储算法中,每8×8个像素格是单独进行优化的,算法会寻找这个基本单位格中的均色(jpeg是以颜色正弦波编码)。因此,图像处理时应该尽可能考虑到这点,使得元素的位置靠近每个8×8的像素格。

这个方法使用起来很简单,比如下面这个例子:

8grid-bad13.51 KB

8grid-good12.65 KB

第一张图片中,微波炉的位置是随意放的,而第二张却经过了细微的调整。两者存储的质量相同,都是55。让我们放大点看,红线是参考线:

8grid-zoom

可以看到,在略微移动了几个像素之后,图像减少了大约1 KB,并且也更清晰了一点。

颜色优化

这部分主要介绍不常用的图片存储格式,它主要应用在电视上面,暂不介绍。

常见的JPEG优化方法

这里介绍一些常用的优化方法。

JPEG算法很严格,唯一的压缩准则是图像软件设置里的质量选项。你可能在Photoshop中存质量为55~60的图片,但是在其它软件中存80质量才拥有同样的尺寸和外观。

一定不要用100的质量来保存图片!这其实并不是最高的质量值,因为这只是个数学理论上限,如果你非要质量很高的图片的话,一般存到95就够了,5点的质量丢失几乎没有区别。

注意Photoshop中低于50质量的图片保存。因为在50之下的时候,jpeg优化会启动一个附加的算法——color down-sampling——它将均衡相邻的8×8像素格的颜色。

q50 48×48 pixels, Quality: 50 (in Photoshop), 530 bytes.

q51 48×48 pixels, Quality: 51 (in Photoshop), 484 bytes.

高一个质量反而更好更小。所以,如果图片拥有小尺寸、高差别的情况,请至少在Photoshop中保存质量为51。

NCZ在他的同名博客《Feature detection is not browser detection》中,讲述了一直以来前端开发中的一个热门技术——检测用户的浏览器平台,并详细地叙说历史发展以及各种办法的优缺点。我大致翻译了部分文章,可能有理解错误的地方,敬请指正。值得一提的是,评论部分的争论亦值得一看。

特性检测

起初前端工程师们就极力反对浏览器检测,他们认为类似user-agent嗅探的方法是很不好的,理由是它并不是一种面向未来的代码,无法适应新版的浏览器。更好的做法是使用特性检测,就像这样:

if (navigator.userAgent.indexOf("MSIE 7") > -1){
    //do something
}

而更好的做法是这样:

if(document.all){
    //do something
}

这两种方式并不相同。前者是检测浏览器的特殊名称和版本;后者却是检测浏览器的特性。UA嗅探能够精确得到浏览器的类型和版本(至少能得知浏览器类型),而特性检测却是去确定浏览器是否拥有某个对象或者支持某个方法。注意这两者是完全不同的。

因为特性检测依赖于哪些浏览器支持,当出现新版本浏览器的时候需要繁琐的确认工作。例如DOM标准刚出现的时候,并不是所有浏览器都支持getElementById()方法,所以一开始代码可能是这样:

if(document.getElementById){  //DOM
    element = document.getElementById(id);
} else if (document.all) {  //IE
    element = document.all[id];
} else if (document.layers){  //Netscape < 6
    element = document.layers[id];
}

这是特性检测很好的一个例子,亮点在于当其它浏览器开始支持getElementById()方法时不必修改代码。

混合方式

后来前端工程师们考虑改进的写法,代码变化成这样:

//AVOID!!!
if (document.all) {  //IE
    id = document.uniqueID;
} else {
    id = Math.random();
}

这个代码的问题是通过检测document.all属性来确定是否是IE。当确定是IE后,假定使用私有的document.uniqueID属性也是安全的。然而,目前所作的只是确定是否支持document.all,并非是去辨识浏览器是否为IE。仅仅支持document.all的话也不意味着document.uniqueID是可用的。

后来人们开始这样写,用下面那行代替上面的:

var isIE = navigator.userAgent.indexOf("MSIE") > -1;
//下面这行代替上面那行
var isIE = !!document.all;

这些变化说明大家对“不要使用UA嗅探”存在误解——不再对浏览器的详细信息进行检测,取而代之的是通过特性的支持来推断。这种基于浏览器特性检测的方式非常不好。

后来前端们发现document.all并不可靠,更好的检测IE变为:

var isIE = !!document.all && document.uniqueID;

这种实现方式陷入歧途。不仅需要费时费事地去识别浏览器所增加的特性支持,另外也不能确定其它浏览器开始支持相同的特性。

如果你认为这样的代码并未被广泛使用,那么看看来自于老版本的Mootools代码片段吧:

//from MooTools 1.1.2
if (window.ActiveXObject) window.ie = window[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
else if (document.childNodes && !document.all && !navigator.taintEnabled) window.webkit = window[window.xpath ? 'webkit420' : 'webkit419'] = true;
else if (document.getBoxObjectFor != null || window.mozInnerScreenX != null) window.gecko = true;

注意它是如何使用特性检测的。我可以指出它一系列的问题,比如通过检测window.ie会将ie8误认为ie7。

余波

随着浏览器的快速发展,使用特性检测变得越来越困难和不可靠。但是Mootools 1.2.4仍然使用这一方法,例如:getBoxObjectFor()

//from MooTools 1.2.4
var Browser = $merge({

	Engine: {name: 'unknown', version: 0},

	Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},

	Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},

	Plugins: {},

	Engines: {

		presto: function(){
			return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
		},

		trident: function(){
			return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? ((document.querySelectorAll) ? 6 : 5) : 4);
		},

		webkit: function(){
			return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
		},

		gecko: function(){
			return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? false : ((document.getElementsByClassName) ? 19 : 18);
		}

	}

}, Browser || {});

应该怎么做?

特性检测是个应该避免的方法,尽管直接进行特性检测是个很好的方法,并且大部分情况下能满足需求。一般只要在检测前知道这个特性是否被实现即可,而不会去考虑它们之间的关系。

我并非是说永远不使用浏览器特性检测而是基于UA嗅探,因为我相信它还是有很多用途的,然而我不相信它有很多合理的用途。如果你考虑UA嗅探的话,请先贯彻这一思想:唯一安全的方式是针对特定浏览器的特定版本,超出范围之外都是不可靠的——例如新出的浏览器版本。其实这样做也是个明智的办法,因为相较于向前兼容不确定的新版本而言,向后兼容老版本是最简单的做法。

十二月 27th, 2009

删除数组的重复项

9 Comments, 前端开发, by army8735.

怿飞在同名文章《删除数组中重复项(uniq)》中分享了他的方法,时间复杂度和空间复杂度均为O(n),但还无法解决弱类型的问题。凑巧我也思考过类似的方法,在此基础上修改一下,可以KO弱类型问题。

<script src="mootools-1.2.4-core-nc.js"></script>
<script>
Array.implement({
	distinct: function() {
		if(this.length < 2) {
			return this;
		}
		var hash = new Hash(), value;
		outer:
		for(var i = 0, len = this.length; i < len; i++) {
			if(hash.has(this[i])) {
				value = hash.get(this[i]);
				for(var j = 0, len2 = value.length; j < len2; j++) {
					if($type(this[i]) == value[j]) {
						this.splice(i, 1);
						i--;
						len--;
						continue outer;
					}
				}
				value.push($type(this[i]));
			}
			else {
				hash.set(this[i], [$type(this[i])]);
			}
		}
		return this;
	}
});
var arr = [0, 0, "0", '0', false, null, NaN, undefined, 1, "1", true];
alert(arr.distinct().join(", ")); //[0, "0", false, null, NaN, undefined, 1, "1", true]
</script>

基于mootools,我为Array类提供一个distinct方法,以供删除重复项。

基本思想是:遍历数组,建立一个hash用以统计出现过的每条项目。不过hash的key是数组每项的toString()值,value却是个类型数组。每遍历数组中的一个值时,首先检查hash中是否存在key,不存在设置value为一个新数组——并且里面有唯一的类型值(这个数组项的类型);如果hash中存在key,则遍历value,查找此数组项的类型是否在value中,在则说明重复,不在将类型push到value里面。

这样就解决了弱类型的问题,但如果数组中不仅仅有基本类型,还有数组、对象、arguments、hash等,则会更加复杂。

十二月 24th, 2009

960gs中浮动清除新变化

2 Comments, 前端开发, by army8735.

昨天下了新版本960gs,瞅瞅里面有无新的变化,正巧发现其中.clearfix的新变化:

/* `Clear Floated Elements
----------------------------------------------------------------------------------------------------*/

/* http://sonspring.com/journal/clearing-floats */

.clear {
	clear: both;
	display: block;
	overflow: hidden;
	visibility: hidden;
	width: 0;
	height: 0;
}

/* http://perishablepress.com/press/2009/12/06/new-clearfix-hack */

.clearfix:after {
	clear: both;
	content: ' ';
	display: block;
	font-size: 0;
	line-height: 0;
	visibility: hidden;
	width: 0;
	height: 0;
}

/*
	The following zoom:1 rule is specifically for IE6 + IE7.
	Move to separate stylesheet if invalid CSS is a problem.
*/
* html .clearfix,
*:first-child+html .clearfix {
	zoom: 1;
}

关键在于最后几行的更新,用单独的样式为ie6和ie7设置了hack(应该是zoom:1触发hasLayout),来解决极少数情况下浮动清除的bug。至于争议性的.clearfix到底用不用,则实在是个鱼与熊掌的问题。