十二月 26th, 2010

好笑的API风格

No Comments, 前端开发, by army.

http://hax.javaeye.com/blog/850778

回帖时突然想到了:

所有dom原生API都是和此文表述一致。比如addEventLister、removeEventLister;没见过eventLister()根据参数个数确定是add还是remove。

相反,jq当中语法风格都是这样。

问题就变成了,为什么会有这两种相反情况的出现?

不知为何我很想笑……

请去原帖参与讨论。

http://jssc.googlecode.com/svn/trunk/jssc5/bin/index.html

已经可以看到添加了不少语言了,找翻译可真够受罪的。通过flashvars中传递lang=param来设置语系,比如说:lang=chinese。

还有检测时添加了对textarea节点的检查,最初也是有的,后来为了标准只检测pre节点,现在发现textarea还是有优势的,比如html代码在pre节点中得转义<号,textarea则不需要。控制权还是交给用户为妙。

另外删除了关于按钮,右上角只保留复制功能,协议改成了MIT。github和google code并存:https://github.com/army8735/jssc

最近始终在考虑5.0的结构设计问题,lexer之间父子继承的关系真可谓一坨坨的:

想要添加新语种十分麻烦,因为要实现这个语言的lexer才行。虽说当初设计的两大目的是准确性和速度性能,但是忽略了易配置性,罪过罪过。UNIX上的Lex做得很不错,每种语言用描述自己的规则文件来定义,值得借鉴,这样即使不懂如何实现lexer的人只要通过配置自己所需的语言规则,也能添加语法种类。

下一代的jssc要提炼出一个lexer核心,这样其它地方也能用这个核心,不必重复实现。现在的做法耦合太严重,解析过程和生成html的逻辑死死绑在一起,当然唯一的好处是性能最快。解耦后将略微损失一些性能,但总体时间复杂度还是O(n)的,再加上加入缓冲区的概念,性能不是问题,最终结果得要远远大于失的。

至于版本号么,5.1?5.5?6.0?算了,还是别学chrome了……

最后,昨天看见syntaxhighlighter更新到3.x了,开源竞争激烈啊,不过准确性和速度性还是不能比的。习惯性地祭出js校验代码(前一部分是我编的,后一部分来源于hax),还是没有根本性的改变:

//javascript
var reg = /**//[\/][/][*]([\S\s]*?)(?:[*][/;\[\]]|$)|[\/][/g;//](.*)|"((?:\\\\|\\"|[^"///])*)"|'((?:\\\\|\\'|[^'\//;])*)'/gm; ''; /\d/.test(1);
reg = 12;
reg = 2 / 1 /**///
/**///
/reg/ 1;
var num1 = 0.541;
function f(test) {
    return (test/*
    /* //
    ' "
    { ;
    \*/ && // /* // " ' { ; \
    test &&
    " /* // \
    \" ' \
    { ;" &&
    ' /* // \
    " \' \
    { ;' &&
    test);
}

http://code.google.com/p/swfobject/

swfobject是目前比较通用的嵌入flash的js库,很多库自己实现的插件也无一例外借鉴了它。不过我的日常工作中很少用到静态方式嵌入flash,都是动态方式调用,因此许多代码是冗余的;而且swfobject虽然针对不同浏览器进行了分支判断,但没有进行“惰性化”,性能没有最优。

举个例子,判断flash的版本,swfobject会在初始化的时候进行,即使没有使用它。很多人会进行改写,使之成为“首次使用时初始化”。我之前也是如此:

(function() {

var SHOCKWAVE_FLASH = 'Shockwave Flash',
	FLASH_MIME_TYPE = 'application/x-shockwave-flash',
	SHOCKWAVE_FLASH_AX = 'ShockwaveFlash.ShockwaveFlash',
	version;

function getVersion() {
	var d,
		ver = [0, 0, 0];
	if(!$.isUndefined(navigator.plugins) && $.isObject(navigator.plugins[SHOCKWAVE_FLASH])) {
		d = navigator.plugins[SHOCKWAVE_FLASH].description;
		if (d && !(!$.isUndefined(navigator.mimeTypes) && navigator.mimeTypes[FLASH_MIME_TYPE] && !navigator.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
			d = d.replace(/^.*\s+(\S+\s+\S+$)/, '$1');
			ver[0] = parseInt(d.replace(/^(.*)\..*$/, '$1'), 10);
			ver[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, '$1'), 10);
			ver[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, '$1'), 10) : 0;
		}
	}
	else if(!$.isUndefined(window.ActiveXObject)) {
		try {
			var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
			if(a) { // a will return null when ActiveX is disabled
				d = a.GetVariable('$version');
				if (d) {
					d = d.split(' ')[1].split(',');
					ver = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
				}
			}
		}
		catch(e) {}
	}
	return ver;
}

TUI.getVersion = function() {
	if(!version) {
		version = getVersion();
	}
	return version;
}

})();

这样做的好处是在第一次获取版本时才去初始化,因为ActiveX的消耗很厉害,而以后再次获取的时候取单例version,避免再次初始化。

但是这不是最优的方案,因为即使这样,每次获取版本的时候还会进行一次if判断,检查是否有初始化过程,访问父层作用域的变量。理想情况应该是直接返回版本,改写方案如下:

(function() {

var SHOCKWAVE_FLASH = 'Shockwave Flash',
	FLASH_MIME_TYPE = 'application/x-shockwave-flash',
	SHOCKWAVE_FLASH_AX = 'ShockwaveFlash.ShockwaveFlash';

function getVersion() {
	var d,
		ver = [0, 0, 0];
	if(!$.isUndefined(navigator.plugins) && $.isObject(navigator.plugins[SHOCKWAVE_FLASH])) {
		d = navigator.plugins[SHOCKWAVE_FLASH].description;
		if (d && !(!$.isUndefined(navigator.mimeTypes) && navigator.mimeTypes[FLASH_MIME_TYPE] && !navigator.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
			d = d.replace(/^.*\s+(\S+\s+\S+$)/, '$1');
			ver[0] = parseInt(d.replace(/^(.*)\..*$/, '$1'), 10);
			ver[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, '$1'), 10);
			ver[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, '$1'), 10) : 0;
		}
	}
	else if(!$.isUndefined(window.ActiveXObject)) {
		try {
			var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
			if(a) { // a will return null when ActiveX is disabled
				d = a.GetVariable('$version');
				if (d) {
					d = d.split(' ')[1].split(',');
					ver = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
				}
			}
		}
		catch(e) {}
	}
	return ver;
}

TUI.getVersion = function() {
	var version = getVersion();
	this.getVersion = function() {
		return version;
	};
	return version;
}

})();

第一次调用时初始化,并且覆盖本身的方法,使之成为直接返回的函数,省略了外部的version变量以及if判断。甚至可以将getVersion方法移到TUI.getVersion中,这便是惰性函数

这个操作比较简单,分支较少,还体现不出来优势,不如嵌入swf等操作明显,以下是全貌:

(function() {

	var SHOCKWAVE_FLASH = 'Shockwave Flash',
		FLASH_MIME_TYPE = 'application/x-shockwave-flash',
		SHOCKWAVE_FLASH_AX = 'ShockwaveFlash.ShockwaveFlash';

	function getVersion() {
		var d,
			ver = [0, 0, 0];
		if(!$.isUndefined(navigator.plugins) && $.isObject(navigator.plugins[SHOCKWAVE_FLASH])) {
			d = navigator.plugins[SHOCKWAVE_FLASH].description;
			if (d && !(!$.isUndefined(navigator.mimeTypes) && navigator.mimeTypes[FLASH_MIME_TYPE] && !navigator.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
				d = d.replace(/^.*\s+(\S+\s+\S+$)/, '$1');
				ver[0] = parseInt(d.replace(/^(.*)\..*$/, '$1'), 10);
				ver[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, '$1'), 10);
				ver[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, '$1'), 10) : 0;
			}
		}
		else if(!$.isUndefined(window.ActiveXObject)) {
			try {
				var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
				if(a) { // a will return null when ActiveX is disabled
					d = a.GetVariable('$version');
					if (d) {
						d = d.split(' ')[1].split(',');
						ver = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
					}
				}
			}
			catch(e) {}
		}
		return ver;
	}
	function createSwf(attrs, params, node) {
		//ie下需考虑原有节点就是个object(flash)的情况,强行覆盖会致使flash无法正常播放。移除原object再插入新div节点,再动态插入flash到新div节点中。也可防止内存泄漏
		if(node.tagName.toLowerCase() == 'object') {
			var newDiv = document.createElement('div');
			node.parentNode.insertBefore(newDiv, node);
			removeObject(node);
			createHtml(attrs, params, newDiv);
		}
		else {
			createHtml(attrs, params, node);
		}
	}
	function createHtml(attrs, params, node) {
		if($.browser.msie) {
			createHtml = createInIe;
		}
		else {
			createHtml = createInNormal;
		}
		createHtml(attrs, params, node);
	}
	function createInIe(attrs, params, node) {
		var att = '',
			par = '';
		for(var i in attrs) {
			if(attrs[i] != Object.prototype[i]) { //需过滤掉一些潜在因素为prototype添加属性
				if(i.toLowerCase() == 'data') {
					params.movie = attrs[i];
				}
				else if(i.toLowerCase() == 'class') {
					att += ' class="' + attrs[i] + '"';
				}
				else if(i.toLowerCase() != 'classid') {
					att += ' ' + i + '="' + attrs[i] + '"';
				}
			}
		}
		for(var j in params) {
			if(params[j] != Object.prototype[j]) { //同上
				par += '<param name="' + j + '" value="' + params[j] + '"/>';
			}
		}
		node.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
	}
	function createInNormal(attrs, params, node) {
		var o = document.createElement('object');
		o.setAttribute('type', FLASH_MIME_TYPE);
		for(var m in attrs) {
			if(attrs[m] != Object.prototype[m]) { //同上
				if(m.toLowerCase() == 'class') {
					o.setAttribute('class', attrs[m]);
				}
				else if(m.toLowerCase() != "classid") { //过滤掉IE特有属性
					o.setAttribute(m, attrs[m]);
				}
			}
		}
		for(var n in params) {
			if(params[n] != Object.prototype[n] && n.toLowerCase() != 'movie') { //同上
				createObjParam(o, n, params[n]);
			}
		}
		node.parentNode.replaceChild(o, node);
	}
	function createObjParam(el, pName, pValue) {
		var p = document.createElement('param');
		p.setAttribute('name', pName);
		p.setAttribute('value', pValue);
		el.appendChild(p);
	}

	function removeObject(obj, cb) {
		//ie下需移除object上的function,以免内存泄漏。移除需要在readyState为4后进行,无法侦听,只能用定时器
		if($.browser.msie) {
			removeObject = removeObjectInIe;
		}
		else {
			removeObject = removeObjectInNormal;
		}
		removeObject(obj, cb);
	}
	function removeObjectInIe(obj, cb) {
		obj.style.display = 'none';
		(function(){
			if(obj.readyState == 4) {
				for(var i in obj) {
					if($.isFunction(obj[i])) {
						obj[i] = null;
					}
				}
				if($.isFunction(cb)) {
					cb();
				}
				return;
			}
			setTimeout(arguments.callee, 100);
		})();
	}
	function removeObjectInNormal(obj, cb) {
		obj.parentNode.removeChild(obj);
		if($.isFunction(cb)) {
			cb();
		}
	}

	TUI.mix({
		/**
		 * @public 获取flash对象
		 * @param {string} 对象id
		 */
		getItem: function(id){
			return $.browser.msie ? window[id] : document[id];
		},

		/**
		 * @public 改写swfobject设置flash对象
		 * @param {string} swf对象的url
		 * @param {string} 动态写入的node的id
		 * @param {int} width
		 * @param {int} height
		 * @param {object} flashvars
		 * @param {object} param标签参数
		 * @param {object} object标签参数
		 */
		setItem: function(url, id, width, height, flashvars, params, attrs) {
			var node = document.getElementById(id),
				p = {},
				a = {};
			if(node) {
				//将url、width、height混入attrs中,防止原配置参数被修改:这是个很隐蔽的引用修改bug
				$.extend(true, a, attrs, {
					data: url,
					width: width + '',
					height: height + ''
				});
				//没有设置id则继承原有dom元素的id
				if(!a.id) {
					a.id = id;
				}
				$.extend(true, p, params);
				p.flashvars = $.param(flashvars);
				createSwf(a, p, node);
				return this.getItem(a.id);
			}
		},

		/**
		 * @public 移除flash对象
		 * @param {string/object} 对象id或对象本身
		 * @param {func} 移除后的回调函数
		 */
		removeItem: function(id, cb) {
			var obj = $.isString(id) ? document.getElementById(id) : id;
			if (obj && obj.nodeName.toUpperCase() == 'OBJECT') {
				removeObject(obj, cb);
			}
		},

		/**
		 * @public 获取flash版本,单例闭包
		 * @return {array} 3位长度的版本号
		 */
		version: function() {
			var version = getVersion();
			this.version = function() {
				return version;
			}
			return version;
		}
	}, 'swf');

})();

十二月 17th, 2010

jssc 5.0.4

2 Comments, jssc, 前端开发, by army.

http://code.google.com/p/jssc/

昨天偶然同学使用发现ie下无法显示,maxthon下却行,很是郁闷。最后发现是object标签上的visibility:hidden导致,真没想到。于是乎连忙修正之,并用compiler压缩js部分的代码,这样整体又稍稍减少了0.2k。

成员中增加了lubin1119,来尝试编写sas的解析,我本人倒是希望加入python的解析,随后便是拖延很久的缓冲式解析方法,用以支持超多行数代码高亮。看来又要变动不少了。

上篇中外链的一篇分享:http://limu.javaeye.com/blog/840159提到了依赖循环的问题,即模块互相依赖形成了死循环。

举例说明:a->b、b->c、c->a。这种依赖是没法执行的,如果运行的话会引发无限死循环,必须预防。可以考虑的方案无非定义时检测(链接中limu中的方案),或者使用时检测,两者大同小异。

伪代码:

//检测是否有循环依赖
function checkRecursion(list) {
	var history = {},
		stack;
	//遍历需要异步加载的模块列表,每个模块深度遍历检测自己的依赖,出现过的说明此回路已检测过无需重复,记录在history中
	list.forEach(function(mod) {
		stack = [];
		if(!history[mod.name]) {
			history[mod.name] = 1;
			scanRecursion(mod, {});
		}
	});
	//每个模块深度遍历过程中,边记录边遍历,发现重复的说明出现循环回路,报异常
	function scanRecursion(mod, has) {
		if(mod && mod.deps) {
			stack.push(mod.name);
			mod.deps.forEach(function(dep) {
				if(has[dep]) {
					throw new Error('InfiniteLoop of dependence: ' + stack.join('->'));
				}
				has[dep] = 1;
				history[dep] = 1;
				scanRecursion(module[dep], has);
			});
			//自身完成后需清除
			has[mod.name] = 0;
			stack.pop();
		}
	}
}

我用的是使用时检测,防止出现死循环回路,代码都在上面了,没啥好说的。传入的list即是下面3个模块组成的列表,准备一次性并发请求的。做了个测试,成功地抛出了一个详细定位的异常:

$$.def('a', 'a.js', 'b');
$$.def('b', 'b.js', 'c');
$$.def('c', 'c.js', 'a');

$$.use('a');

错误: InfiniteLoop of dependence: a->b->c->a