2012五一扬州

2012五一小长假,三日游扬州

烟雨下扬州,柳岸风行三五舟,二十四桥人与曲,散尽风流

2012五月初

每次体检,都被告知有鼻息肉鼻窦炎,需要手术治疗,拖得久了,终于本周,想好好治疗,周五挂号预约,周一安排到床位,然后住院检查,胸片,心电图,ct,抽血(九罐),定在周三手术,空腹等待,直到下午三点半,拖入手术室再等待,四点半上手术台,白光洒下,绿衣大褂围过来,让人不得不心跳加快,但就在此时出现变数,我告知医生最近咳嗽厉害,有点小感冒,而这可能影响手术,于是一阵紧张之后,手术取消,待感冒好转再安排。

这就是最近几日的瞎忙活,自五一扬州之游,状态一直低迷,考个驾照还挂两次,UI嵌套让人晕头,血压也高,鼻子不通还咳嗽,真真两个字:不爽,当然驾照还可以再考,咳嗽迟早会好,架构可以静心构建,都是暂时的,而立之年前的经历,是为人处世的积累,来关心一下生活,意识到找对象一直偏执和误区,就像鉴赏古董,追求漂亮花瓶,算是浪漫但缺少实用价值,烟花三月的时光已经过去,迟播的种子得好好挑,这时节可以种点棉花了

最后写首打油诗:
肥沃乡间土,
田根赏桃花,
花开美落雁,
发呆忘年华,
埋头泥泞里,
后背映彩霞

HTML5 WebSocket应用示例

继续上一篇《HTML5 WebSocket 技术介绍》的内容,本篇将以示例说明WebSocket的使用,这个示例同时结合了TWaver HTML5的使用,场景如下:后台提供拓扑数据,并以JSON格式通过WebSocket推送到各个客户端,客户端获取到拓扑信息后,通过TWaver HTML5的Network组件呈现于界面,客户端可以操作网元,操作结果通过WebSocket提交到后台,后台服务器更新并通知所有的客户端刷新界面,此外后台服务器端还会不断产生告警,并推送到各个客户端更新界面。

大体结构

准备

需要用到jetty和twaver html5,可自行下载:

jetty :http://www.eclipse.org/jetty/
twaver html5 : 上官网申请

jetty目录结构

jetty下载解压后是下面的结构,运行start.jar(java -jar start.jar)启动jetty服务器,web项目可以发布在/webapps目录中,比如本例目录/webapps/alarm/

后台部分

后台使用jetty,其使用风格延续servlet的api,可以按Serlvet的使用和部署方式来使用,本例中主要用到三个类

  • WebSocketServlet – WebSocket服务类
  • WebSocket – 对应一个WebSocket客户端
  • WebSocket.Conllection – 代表一个WebSocket连接

WebSocketServlet

全名为org.eclipse.jetty.websocket.WebSocketServlet,用于提供websocket服务,继承于HttpServlet,增加了方法public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol),在客户端第一次请求websocket连接时会调用该方法,如果允许建立连接,则返回一个WebSocket实例对象,否则返回null。

本例中将定义一个AlarmServlet类,继承于WebSocketServlet,并实现doWebSocketConnect方法,返回一个AlarmWebSocket实例,代表一个客户端。

AlarmServlet

AlarmWebSocket中有个clients属性,用于维持一个客户端(AlarmWebSocket)列表,当与客户端建立连接时,会将客户端对应的AlarmWebSocket实例添加到这个列表,当客户端关闭时,则从这个列表中删除。

public class AlarmServlet extends org.eclipse.jetty.websocket.WebSocketServlet {
	private final Set clients;//保存客户端列表

	public AlarmServlet() {
		initDatas();//初始化数据
	}

	@Override
	public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
		return new AlarmWebSocket();
	}
	//...
}

AlarmWebSocket

来看看AlarmWebSocket的实现,这里定义的是一个内部类,实现了接口org.eclipse.jetty.websocket.WebSocket.OnTextMessage的三个方法:onOpen/onMessage/onClose,这三个方法分别在连接建立,收到客户端消息,关闭连接时回调,如果需要向客户端发送消息,可以通过Connection#sendMessage(…)方法,消息统一使用JSON格式,下面是具体实现:

    class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage
    {
    	WebSocket.Connection connection;
		@Override
		public void onOpen(Connection connect) {
			this.connection = connect;
			clients.add(this);
			sendMessage(this, "reload", loadDatas());
		}
		@Override
		public void onClose(int code, String message) {
			clients.remove(this);
		}
		@Override
		public void onMessage(String message) {
			Object json = JSON.parse(message);
			if(!(json instanceof Map)){
				return;
			}
			//解析消息,jetty中json数据将被解析成map对象
			Map map = (Map)json;
			//通过消息中的信息,更新后台数据模型
			...
			//处理消息,通知到其他各个客户端
			for(AlarmWebSocket client : clients){
				if(this.equals(client)){
					continue;
				}
				sendMessage(client, null, message);
			}
		}
	}
	private void sendMessage(AlarmWebSocket client, String action, String message){
		try {
			if(message == null || message.isEmpty()){
				message = "\"\"";
			}
			if(action != null){
				message = "{\"action\":\"" + action + "\", \"data\":" + message + "}";
			}
			client.connection.sendMessage(message);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

后台配置

后台配置如serlvet相同,这里设置的url名称为/alarmServer

<!--?xml version="1.0" encoding="UTF-8"?-->

        alarmServlet
        web.AlarmServlet
        1

        alarmServlet
        /alarmServer

前台部分

看看前台的大体结构,创建websocket连接,监听相关事件,比如onmessage事件,可以收到后台发送的信息(JSON格式),解析后更新到界面,详细的处理函数将稍后介绍

        function init(){
            window.WebSocket = window.WebSocket || window.MozWebSocket;
            if (!window.WebSocket){
                alert("WebSocket not supported by this browser");
                return;
            }
            var websocket = new WebSocket("ws://127.0.0.1:8080/alarm/alarmServer");
            websocket.onopen = onopen;
            websocket.onclose = onclose;
            websocket.onmessage = onmessage;
            ...
        }
        function onmessage(evt){
            var data = evt.data;
            if(!data){
                return;
            }
            data = stringToJson(data);
            if(!data){
                return;
            }
            ...
        }
        function jsonToString(json){
            return JSON.stringify(json);
        }
        function stringToJson(str){
            try{
                str = str.replace(/\'/g, "\"");
                return JSON.parse(str);
            }catch(error){
                console.log(error);
            }
        }

WebSocket前后台流程


业务实现

数据模型

本例需要用到三种业务类型,节点,连线和告警,后台分别提供了实现类,并定义了名称,位置,线宽等属性,此外还提供了导出json数据的功能。

    interface IJSON{
    	String toJSON();
    }
    class Data{
    	String name;
    	public Data(String name){
    		this.name = name;
    	}
    }
    class Node extends Data implements IJSON{
    	public Node(String name, double x, double y){
    		super(name);
    		this.x = x;
    		this.y = y;
    	}
    	double x, y;
    	public String toJSON(){
    		return "{\"name\":\"" + name + "\", \"x\":\"" + x + "\",\"y\":\"" + y + "\"}";
    	}
    }
    class Link extends Data implements IJSON{
    	public Link(String name, String from, String to, int width){
    		super(name);
    		this.from =from;
    		this.to = to;
    		this.width = width;
    	}
    	String from;
    	String to;
    	int width = 2;
    	public String toJSON(){
    		return "{\"name\":\"" + name + "\", \"from\":\"" + from + "\", \"to\":\"" + to + "\", \"width\":\"" + width + "\"}";
    	}
    }
    class Alarm implements IJSON{
    	public Alarm(String elementName, String alarmSeverity){
    		this.alarmSeverity = alarmSeverity;
    		this.elementName = elementName;
    	}
    	String alarmSeverity;
    	String elementName;
		@Override
		public String toJSON() {
			return "{\"elementName\": \"" + elementName + "\", \"alarmSeverity\": \"" + alarmSeverity + "\"}";
		}
    }

后台维持三个数据集合,分别存放节点,连线和告警信息,此外elementMap以节点名称为键,便于节点的快速查找

	Map elementMap = new HashMap();
	List nodes = new ArrayList();
	List links = new ArrayList();
	List alarms = new ArrayList();

初始化数据

在servlet构造中,我们添加了些模拟数据,在客户端建立连接时(AlarmWebSocket#onOpen(Connection connection)),后台将节点连线和告警信息以JSON格式发送到前台(sendMessage(this, “reload”, loadDatas());)


	public AlarmServlet() {
		initDatas();
		...
	}

	public void initDatas() {
		int i = 0;
		double cx = 350, cy = 230, a = 250, b = 180;
		nodes.add(new Node("center", cx, cy));
		double angle = 0, perAngle = 2 * Math.PI/10;
		while(i++ < 10){
			Node node = new Node("node_" + i, cx + a * Math.cos(angle), cy + b * Math.sin(angle));
			elementMap.put(node.name, node);
			nodes.add(node);
			angle += perAngle;
		}
		i = 0;
		while(i++ < 10){
			Link link = new Link("link_" + i, "center", "node_" + i, 1 + random.nextInt(10));
			elementMap.put(link.name, link);
			links.add(link);
		}
	}

	private String loadDatas(){
		StringBuffer result = new StringBuffer();
		result.append("{\"nodes\":");
		listToJSON(nodes, result);
		result.append(", \"links\":");
		listToJSON(links, result);
		result.append(", \"alarms\":");
		listToJSON(alarms, result);
		result.append("}");
		return result.toString();
	}

    class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage
    {
    		...
		@Override
		public void onOpen(Connection connect) {
			this.connection = connect;
			clients.add(this);
			sendMessage(this, "reload", loadDatas());
		}
    		...
    }

初始数据前台展示

初始数据通过后台的sendMessage(…)方法推送到客户端,客户端可以在onmessage回调函数中收到,本例我们使用twaver html5组件来展示这些信息。TWaver组件的使用流程一如既往,先作数据转换,将JSON数据转换成TWaver的网元类型,然后填充到ElementBox数据容器,最后关联上Network拓扑图组件,代码如下:

        var box, network, nameFinder;
        function init(){
            network = new twaver.network.Network();
            box = network.getElementBox();
            nameFinder = new twaver.QuickFinder(box, "name");

            var networkDom = network.getView();
            networkDom.style.width = "100%";
            networkDom.style.height = "100%";
            document.body.appendChild(networkDom);

            window.WebSocket = window.WebSocket || window.MozWebSocket;
            if (!window.WebSocket){
                alert("WebSocket not supported by this browser");
                return;
            }
            var websocket = new WebSocket("ws://127.0.0.1:8080/alarm/alarmServer");
            ...
            websocket.onmessage = onmessage;

        }
        ...
        function onmessage(evt){
            var data = evt.data;
            if(!data){
                return;
            }
            data = stringToJson(data);
            if(!data){
                return;
            }
            var action = data.action;
            if(!action){
                return;
            }
            if(action == "alarm.clear"){
                box.getAlarmBox().clear();
                return;
            }
            data = data.data;
            if(!data){
                return;
            }
            if(action == "reload"){
                reloadDatas(data);
                return;
            }
            if(action == "alarm.add"){
                newAlarm(data)
                return;
            }
            if(action == "node.move"){
                modeMove(data);
                return;
            }
        }

        function reloadDatas(datas){
            box.clear();
            var nodes = datas.nodes;
            var links = datas.links;
            var alarms = datas.alarms;

            for(var i=0,l=nodes.length; i < l; i++){
                var data = nodes[i];
                var node = new twaver.Node();
                node.setName(data.name);
                node.setCenterLocation(parseFloat(data.x), parseFloat(data.y));
                box.add(node);
            }

            for(var i=0,l=links.length; i < l; i++){
                var data = links[i];
                var from = findFirst(data.from);
                var to = findFirst(data.to);
                var link = new twaver.Link(from, to);
                link.setName(data.name);
                link.setStyle("link.width", parseInt(data.width));
                box.add(link);
            }

            var alarmBox = box.getAlarmBox();
            for(var i=0,l=alarms.length; i < l; i++){
                newAlarm(alarms[i]);
            }
        }
        function findFirst(name){
            return nameFinder.findFirst(name);
        }
        function newAlarm(data){
            var element = findFirst(data.elementName);
            var alarmSeverity = twaver.AlarmSeverity.getByName(data.alarmSeverity);
            if(!element || !alarmSeverity){
                return;
            }
            addAlarm(element.getId(), alarmSeverity, box.getAlarmBox());
        }
        function addAlarm(elementID,alarmSeverity,alarmBox){
            var alarm = new twaver.Alarm(null, elementID,alarmSeverity);
            alarmBox.add(alarm);
        }
        function modeMove(datas){
            for(var i=0,l=datas.length; i<l; i++){
                var data = datas[i];
                var node = findFirst(data.name);
                if(node){
                    var x = parseFloat(data.x);
                    var y = parseFloat(data.y);
                    node.setCenterLocation(x, y);
                }
            }
        }

界面效果


后台推送告警,前台实时更新

增加后台推送告警的代码,这里我们在后台起了一个定时器,每隔两秒产生一条随机告警,或者清除所有告警,并将信息推送给所有的客户端

后台代码如下:

	public AlarmServlet() {
		...
		Timer timer = new Timer();
		timer.schedule(new TimerTask() {
			@Override
			public void run() {
				if(random.nextInt(10) == 9){
					alarms.clear();
					sendMessage ("alarm.clear", "");
					return;
				}
				sendMessage("alarm.add", randomAlarm());
			}
		}, 0, 2000);
	}
	public void sendMessage(String action, String message) {
		for(AlarmWebSocket client : clients){
			sendMessage(client, action, message);
		}
	}
	private Random random = new Random();
	private Data getRandomElement(){
		if(random.nextBoolean()){
			return nodes.get(random.nextInt(nodes.size()));
		}
		return links.get(random.nextInt(links.size()));
	}
	String[] alarmSeverities = new String[]{"Critical", "Major", "Minor", "Warning", "Indeterminate"};
	private String randomAlarm(){
		Alarm alarm = new Alarm(getRandomElement().name, alarmSeverities);
		alarms.add(alarm);
		return alarm.toJSON();
	}

前台代码:

客户端接收到消息后,需要对应的处理,增加对”alarm.clear”和”alarm.add”的处理,这样告警就能实时更新了

        function onmessage(evt){
            ...
            if(action == "alarm.clear"){
                box.getAlarmBox().clear();
                return;
            }
            data = data.data;
            if(!data){
                return;
            }
            ...
            if(action == "alarm.add"){
                newAlarm(data)
                return;
            }
            ...
        }

客户端拖拽节点,同步到其他客户端

最后增加拖拽同步,监听network网元拖拽监听,在网元拖拽放手后,将节点位置信息发送给后台

前台代码:

            network.addInteractionListener(function(evt){
                var moveEnd = "MoveEnd";
                if(evt.kind.substr(-moveEnd.length) == moveEnd){
                    var nodes = [];
                    var selection = box.getSelectionModel().getSelection();
                    selection.forEach(function(element){
                        if(element instanceof twaver.Node){
                            var xy = element.getCenterLocation();
                            nodes.push({name: element.getName(), x: xy.x, y: xy.y});
                        }
                    });
                    websocket.send(jsonToString({action: "node.move", data: nodes}));
                }
            });

后台接收到节点位置信息后,首先更新后台数据(节点位置),然后将消息转发给其他客户端,这样各个客户端就实现了同步操作
后台代码

    class AlarmWebSocket implements org.eclipse.jetty.websocket.WebSocket.OnTextMessage
    {
    		...
		@Override
		public void onMessage(String message) {
			Object json = JSON.parse(message);
			if(!(json instanceof Map)){
				return;
			}
			Map map = (Map)json;
			Object action = map.get("action");
			Object data = map.get("data");
			if("node.move".equals(action)){
				if(!(data instanceof Object[])){
					return;
				}
				Object[] nodes = (Object[])data;
				for(Object nodeData : nodes){
					if(!(nodeData instanceof Map) || !((Map)nodeData).containsKey("name") || !((Map)nodeData).containsKey("x") || !((Map)nodeData).containsKey("y")){
						continue;
					}
					String name = ((Map)nodeData).get("name").toString();
					Data element = elementMap.get(name);
					if(!(element instanceof Node)){
						continue;
					}
					double x = Double.parseDouble(((Map)nodeData).get("x").toString());
					double y = Double.parseDouble(((Map)nodeData).get("y").toString());
					((Node)element).x = x;
					((Node)element).y = y;
				}

			}else{
				return;
			}
			for(AlarmWebSocket client : clients){
				if(this.equals(client)){
					continue;
				}
				sendMessage(client, null, message);
			}
		}
	}

完整代码

代码:webSocketDemo 

结构:

HTML5 WebSocket 技术介绍

WebSocket是html5规范新引入的功能,用于解决浏览器与后台服务器双向通讯的问题,使用WebSocket技术,后台可以随时向前端推送消息,以保证前后台状态统一,在传统的无状态HTTP协议中,这是“无法做到”的。

传统服务端推(server push)技术

WebSocket提出之前,为了解决后台推送消息到前台的需求,提出了一些解决方案,这些方案使用已有的技术(如ajax,iframe,flashplayer,java applet …),通过一些变通的处理来实现。

简单轮询

最简单的是前台轮询,每隔一段时间去请求后台,以获取最新状态,这种方式最容易实现,但效果也最差,频繁盲目的调用后台,带来不必要的开销,且实时性无法保障,后台出现更新,前端需要在下一次轮询时才知道。

长轮询

为了解决这些弊端,进化出长轮询技术,轮询请求会在后台阻塞,相当于保持一个长连接,直到后台出现更新或者超时才返回,这样就可以保证更新的及时性,前端得到请求后,重新建立轮询连接,等待后台的更新通知。

其他方案

其他解决方案无外乎保持一个长连接(如Iframe及htmlfile流),实时的从后台获取信息,或者借助第三方插件(flashPlayer, jre),使用的是flash xmlsocket或者java applet socket技术,这些方式都不够纯html,所以这里就不过多介绍了,更多传统server push 技术可参考IBM的文章:http://www.ibm.com/developerworks/cn/web/wa-lo-comet/

WebSocket介绍

webSocket是html5新引入的技术,允许后台随时向前端发送文本或者二进制消息,WebSocket是一种全新的协议,不属于http无状态协议,协议名为”ws”,这意味着一个websocket连接地址会是这样的写法:ws://twaver.com:8080/webSocketServer。ws不是http,所以传统的web服务器不一定支持,需要服务器与浏览器同时支持, WebSocket才能正常运行,目前的支持还不普遍,需要特别的web服务器和现代的浏览器。

浏览器对WebSocket的支持

Google Chrome浏览器最先支持WebSocket,随后是Safari,Firefox,此外最新版本的Opera和IE(Opera11,IE10)也支持WebSocket。
下面是主要浏览器的支持情况,Opera11中默认关闭了WebSocket支持,所以这里没有列出,更多信息可参考:http://en.wikipedia.org/wiki/WebSocket 与 http://caniuse.com/#search=websockets

客户端WebSocket的主要方法

浏览器支持程度各有区别,前面wiki中关于WebSocket的“Browser support”章节有介绍,遵循w3c关于WebSocket API的相关规范,浏览器提供了WebSocket类型,在Firefox中为MozWebSocket,检测浏览器是否支持WebSocket可以使用下面的脚本代码:
检测浏览器是否支持WebSocket

            window.WebSocket = window.WebSocket || window.MozWebSocket;
            if (!window.WebSocket){
                alert("WebSocket not supported by this browser");
                return;
            }

构造函数 – WebSocket#constructor(url, optional protocols)
第一个参数是请求地址,第二个参数选填,表示协议名
使用示例:

var websocket = new WebSocket("ws://127.0.0.1:8080/alarm/alarmServer");

事件 – open/message/close/error
WebSocket#onopen, onmessage, onclose, onerror
连接打开时,回调onopen方法,接收到后台消息时会触发到onmessage事件,后台关闭时调用onclose,出现连接异常时可在onerror中捕获
使用示例:

websocket.onmessage = function(evt){
    var data = evt.data;
}

方法 – send/close
发送消息 – WebSocket#send(data)
关闭连接 – WebSocket#close(optional code, optional reason)
使用示例:

websocket.send(JSON.stringify({action: "node.remove", id: "001"}));

服务器对WebSocket的支持

WebSocket不同于http协议,传统的web服务器通常不支持WebSocket,比如tomcat目前就不支持,反倒是一些小众的或者更活跃的web server率先支持WebSocket,如jetty,以及基于node.js的WebSocket-Node扩展,基本上各种编程语言都有相应的服务器可以选择,下图是我列举的几种,详细情况参阅:http://en.wikipedia.org/wiki/Comparison_of_WebSocket_implementations

 
服务器端编程语言各不相同,具体实现自然也不相同,即使是同一种语言,实现类和接口函数也有很大的差别,比如jetty和netty都是基于java语言,但他们的实现类几乎没有相同命名的,下面我以jetty(http://www.eclipse.org/jetty)为例,简单介绍WebSocket相关的类和方法:

jetty对WebSocket的实现

WebSocketServlet基于servlet标准,增加了doWebSocketConnect(…)方法,为客户端请求创建一个后台对应的WebSocket实例

org.eclipse.jetty.websocket.WebSocketServlet
{
WebSocket doWebSocketConnect(HttpServletRequest request, String protocol)
}

后台WebSocket类,与客户端WebSocket类对应,能监听open, message, close等状态变化事件,并包含一个Connection属性,用于向客户端发送消息

org.eclipse.jetty.websocket.WebSocket
org.eclipse.jetty.websocket.WebSocket.OnTextMessage
{
void onOpen(Connection connect);
void onClose(int code, String message);
void onMessage(String message);
}

WebSocket.Connection 后台连接类,包含于WebSocket对象中,用于向客户端推送消息

org.eclipse.jetty.websocket.WebSocket.Connection
{
void sendMessage(String message);
}

本篇先做介绍,后续将介绍一个WebSocket与TWaver组件结合的实例…

2012二月底

二月的雨下个没完,还要遗传到三月,此刻,听着窗外雨滴滴答的声音,写下这流水的记忆

二月是春天,只是春风稍显无力,扫着阴霾、寒气几个流氓,如大小姐般花拳绣腿,纠缠绵绵,昏天暗地,倒也一片和谐。

二月梅

先说生活方面,前段时间接触了一位梅小姐,老乡兼同行,算是难得,但几天下来,聊不投机,草草收场,留下的只有思考,都说女人是男人成熟的老师,她可算一位。二月是梅花时节(正月梅花),联想到她的姓氏,写过一首诗,现节选一段于此:

“或撑起一片天,共淋一片雨,相依相伴,你一言我一语,聊生活意义,未来契机,还有内心烈火的讯息,纵有千言,思百转,念万语,亦难断”

此情只待成追忆

这个春天不算温暖,但你我等待,总会有温暖的到来,但若是生死两界,就大不一样了,我的一位朋友就没有等到。

她是我高中暗恋的女生,隔壁班,出黑板报时会遇到,课间操能看到她的身影,后来毕业,各自在遥远的两地大学,于是我弄到她的地址:湖南科技大学北校区XXX,写下高中时对她的爱慕,简单的通信,我给她邮寄兰大《视野》杂志合订版,她给我看校园樱花美景,兰州到衡阳有千里,就像回忆贴着邮票漫游两地。假期同学聚会我也常想起她,但始终没能约见,算来我们最后的见面,是高中毕业,帮她搬行李的面孔,我穿着足球服,她——哎,记不清楚,只记得是美丽的笑容,然后成了永远,生死两茫茫,此情只待成追忆…

其他

例行应该说说工作上的事,产品设计思索中,没有大的突破,暂且不提了吧,最后分享些好东西,某位四川某位吉他牛人的作品,几首好听的指弹曲子,也是与二月有关的两首曲子,一首是最近去世的歌后惠特尼休斯顿的经典曲目《I will always love you》,记得大学看来电影《保镖》被她深深吸引,还特意购买了她的CD,其中就有这首歌;另一首是去年情人节上映的电影插曲,在影院与位美女看过,电影一般,曲子很动听,让人难忘

惠特尼休斯顿的《I will always love you》

《将爱》主题曲《因为爱情》

JSProfiler v0.2 更新

JSProfiler v0.2 更新,改善了分析界面,增加了使用实例,打包了新的Chrome插件:
下载地址:JSProfiler v0.2

可二次开发

除了通过Chrome浏览器插件方式使用JSProfiler外,新增加了另一种使用方式:直接引入Profiler.js和Profiler.css到自己的网页中,类似下面的代码:

    <!-- include profiler.js and profiler.css -->
<script type="text/javascript" src="../jsprofiler/Profiler.js"></script><script type="text/javascript">// <![CDATA[
    window.addEventListener("load", function(){
        //init js profiler ------ 1
        var profiler = Profiler.init(window);

        //start js profiler ----- 2
        profiler.start();

        //do some thing --------- 3
        doTest();

        //stop js profiler ------ 4
        setTimeout(function(){
            profiler.stop();
        });
    }, false);

// ]]></script>

JSProfiler API

这里简单介绍一下JSProfiler的API使用
Profiler.init: function(window) – 初始化分析器,并创建一个分析面板,并得到一个Profiler类的实例对象,用于后面使用,这里的传入参数是需要调试的window对象,默认是当前window,如果被测试window与当前window不同(比如在网页a.html中分析页面b.html中函数调用情况),则需要传入此参数。
Profiler#start: function() – 开始分析
Profiler#stop: function() – 停止分析,如果有分析面板,则会自动显示分析报表。

分析面板 – Profiler.Panel
调用Profiler.init()会自动创建一个分析面板实例,界面呈现如下:

完整实例

下面的例子演示了如何使用JSProfiler来分析自己的web项目,比如分析某次处理过程,通常的步骤是:
1、初始化Profiler对象
2、开始分析
3、测试用例,用户处理过程
4、定制分析,生成报表

sample.html
<!DOCTYPE html>
<html>
<head>
    <title>JSProfiler Sample</title>
    <style>
    body{
        margin:0;
        color: #333;
        font-family: verdana, helvetica, arial, sans-serif;
        font-size: 8pt;
    }
    h2{
        text-align: center;
    }
    .Panel{
        color: #DDD;
        -khtml-user-select : none;
        -webkit-user-select : none;
        -moz-user-select : none;
        width: 500px;
        height: 260px;
        background-color: black;
        margin: 20px auto;
        border: solid 16px #EEEEEE;
        box-shadow: 0 0 16px #000000;
        border-radius: 8px;
        position : relative;
        overflow: auto;
    }
    .Node{
        cursor: default;
        padding: 3px;
        border-radius: 2px;
        background-color: #EEEEEE;
        position: absolute;
    }
    </style>
    <script type="text/javascript" src="sample.js"></script>
    <script type="text/javascript">
    function doTest(){
        var sample = new yCoder.ProfilerSample(document.getElementById("canvas"));
        sample.loadNodes(100);
    }
    </script>

    <!-- include profiler.js and profiler.css -->
    <link rel="stylesheet"  href="../jsprofiler/Profiler.css" />
    <script type="text/javascript" src="../jsprofiler/Profiler.js"></script>
    <script type="text/javascript">
    window.addEventListener("load", function(){
        //init js profiler ------ 1
        var profiler = Profiler.init(window);

        //start js profiler ----- 2
        profiler.start();

        //do some thing --------- 3
        doTest();

        //stop js profiler ------ 4
        setTimeout(function(){
            profiler.stop();
        });
    }, false);
    </script>
</head>
<body>
<h2>JSProfiler Sample</h2>
<div id='canvas' class="Panel" ></div>
</body>
</html>

sample.js

var yCoder = {
    __index: 0,
    createNodes : function(parent, w, h, count, backgroundColor){
        var i = 0;
        while(i++ < count){
            var x = Math.floor(Math.random()*w);
            var y = Math.floor(Math.random()*h);
	        var div = document.createElement("div");
	        div.id = yCoder.__index++;
	        div.setAttribute("class", "Node");
            div.style.backgroundColor = backgroundColor;
	        div.innerHTML = "" + x + " - " + y;
	        var tx = div.clientWidth / 2;
            var ty = div.clientWidth / 2;
	        div.style.left = (x - tx) + "px";
	        div.style.top = (y - ty) + "px";
	        parent.appendChild(div);
        }
    }
};
yCoder.ProfilerSample = function(canvas){
    this.init(canvas);
}
yCoder.ProfilerSample.prototype = {
    init : function(canvas){
        this.canvas = canvas;
        var _this = this;
        this.canvas.addEventListener("mousemove", function(evt){
            _this.onmousemove.call(_this, evt);
        }, false);
    },
    onmousemove : function(evt){
        var target = evt.target;
        if(!target || target.getAttribute("class") != "Node"){
            return;
        }
        this.unhighlight();
        this.highlight(target);
    },
    highlight: function(target){
        this.currentElement = target;
        this.currentElement.style.zIndex = "10";
        this.currentElement.style.border = "2px solid";
    },
    unhighlight: function(){
        if(this.currentElement){
            this.currentElement.style.border = "";
            this.currentElement.style.zIndex = "0";
        }
    },
    loadNodes: function(){
        var w = this.canvas.clientWidth;
        var h = this.canvas.clientHeight;
        yCoder.createNodes(this.canvas, w, h, 100, "green");
        yCoder.createNodes(this.canvas, w, h, 100, "red");
        yCoder.createNodes(this.canvas, w, h, 100, "blue");
        this.deleteNodes("green");
    },
    deleteNodes: function(backgroundColor){
        var childNodes = this.canvas.childNodes;
        for(var i = 0, length = childNodes.length; i < length; i++){
            var div = childNodes.item(i);
            if(div && div.nodeType == 1 && div.style.backgroundColor == backgroundColor){
                this.canvas.removeChild(div);
            }
        }
    }
}

运行界面:

2011底2012初

今天情人节,看完电影回家,想到前几天的日志还是草稿,于是赶在睡觉之前修正发布 – 2012.2.14

有人建议我多写点博客,找对象时对方可以更多了解我,想来不无道理,于是翻起尘封的思绪,回顾最近的那些人和事。

先说当前,2012的春天不太温暖,今天上海还雪花纷飞(二月十一日),新闻说欧亚大陆极寒天气,冻死人不少,是否2012的魔力,让忧郁的人们增添生活的憧憬。不久前温习了电影《2012》,感受世界末日,思索灾难降临时的心境,或许人类就像沙丘上的蚂蚁,永远无法知道世界有多大,你我的一脚踩下的可能就是一个家园。说到回顾,最近3D重映《泰坦尼克号》要上映,1997年的片子,我第一次看是在初中,姐姐特意为我们推荐,那时不懂,第二次看是在高中毕业,高考完,一身轻松,和一个朋友彻夜在县城游荡,喊着“you jump, I jump”,而今十多年过去,或许应该再看一遍

说完这些电影世界的畅想,回到现实,今年家里下了死命令:带个对象回家过年。恋爱方面经验有限,也不知如何着手,回上海开始联系,佳缘上买了些邮票,认真的寻觅,真诚的发送消息,期间还得到了某位网友的建议,完善了个人资料,增加了诚信,更新了头像,果然精诚所至,金石为开,收到了几封回信,qq上接触中,真诚交流,希望能聊得来的。

谈对象,谈理想,聊人生,也不得不展望生活与未来,一些实际的东西需要面对,隐约感受到恋爱与婚姻的区别,体会到支撑一个家的生活的艰辛,态度的乐观让人看到未来,细算当下却总是问题的实际,如果跨度一生考虑问题,或许生活会变的简单,但一步一步推进验算然每一步都变得严峻,或许这就是浪漫主义与现实主义吧,我应该属于前者。

生活相对的是工作,2011圣诞前夜,校友聚会时我写下了新年愿望“学好车,做个好的产品”,准确的说这不是愿望,只能算计划,我心中的愿望是更高的追求,而写下的都只能算是要走的步伐。“做一个好的产品”,什么算一个好的产品,每个人有不同的理解,从自我的角度,首先要自己满意,希望未来能招纳更多优秀的人,一起打造一件完美的作品,让我能欣然微笑。
– 2012.2.11

JSProfiler – JS性能分析工具

最近我做了一个javascript分析的工具,类似Java中的JProfiler,用于监测javascript类和函数调用时间,调用次数以及其调用路径等,JSProfiler使用树形结果直观的显示这些信息,让开发者能快速定位性能瓶颈所在之处,便于js类库和web应用程序的调优和分析。

为了更方便的使用,特意做了一个chrome插件的版本,赶在回家过年之前发布出来供大家使用,本来要放到chrome app store上去,但china不在服务地区,没法上传,于是只能先提供zip下载
chrome插件下载地址:JSProfiler Chrome Extension 0.1

详见:http://www.ycoder.com/jsprofiler/