Google+中URL的渐进增强

在高级浏览器下点击Google+的一些链接,并不是直接从服务器返回完整的页面,而是通过AJAX刷新页面的局部。

如在首页点击以下链接:

  • https://plus.google.com/stream
  • https://plus.google.com/photos
  • https://plus.google.com/photos
  • https://plus.google.com/circles

浏览器通过AJAX请求服务器返回数据,动态刷新页面上的一部分区域,这样做可以减少网络传输,快速响应,以达到更好的用户体验。同时我们注意 到,点击链接的时候,浏览器上的地址栏也做出了相应的调整,并且不会刷新整个页面。如点击“圈子”的时候,地址会变为 https://plus.google.com/circles。

这在早期浏览器版本下是做不到的,因为给window.location赋值会导致整个页面的刷新,除非是用改变URL的hash达到同样的目 的。Google+是用HTML5中history.pushState来实现替换当前的URL同时不刷新整个页面。

关于history.pushState的官方文档:https://developer.mozilla.org/en/DOM/Manipulating_the_browser_history

Google+针对不同的操作场景和浏览器,做了一些方案处理点击链接后的行为。

  1. 如果直接在浏览器地址栏中输入地址回车,那么就会从服务器拿完整的页面呈现给用户。
  2. 如果浏览器不支持history.pushState,会降级用url hash的方式。
  3. 如果浏览器支持history.pushState,就用AJAX取区块的数据,然后生成HTML刷新到相应的区域上,并调用 history.pushState更新地址栏,生成访问历史。

值得注意的是,Google+对AJAX返回的数据做了缓存处理,再次请求的话会从缓存中获取数据。

nodejs中websocket传输大量数据

用nodejs当websocket server时,当客户端传输的数据量比较大时,数据会分片到达服务器,这时socket的data处理函数接收到的只是整个数据中的一部分,因此我们要将分片的数据拼合起来再做处理。当最后的数据到达时,数据的末尾是\ufffd,所以可以靠判断数据中是否包含\ufffd来指示整个数据是否已经全部接收。

var message = '';

socket.on('data', function(chunk) {   
    message += chunk;
    var index = message.indexOf('\ufffd');
    if(index !== -1) {
        console.log(message.slice(0, index)); 
        // console.log(message.slice(1, index)); 
        // Do your logic here
        message = message.slice(index + 1);
    }
});

Python中的装饰器

Python中的装饰器本质上是一个高阶函数,它接收一个函数,并且对这个函数进行包装,从而改变原函数的默认行为。

@deco1(deco_arg)
@deco2
def func(): pass

相当于

func = deco1(deco_arg)(deco2(func))

Demo如下:

# -*-coding:utf-8 -*-
def decorator(str):
	print str
	def retfn(fn):
		def retfn(*arg):
			print '已装饰'	# 装饰
			return fn(*arg)	# 调用原始函数
		return retfn
	return retfn

@decorator("实例化装饰器")
def test(str0, str1):
	print str0, str1

test('Hello', 'world');

快速清除多选框的已选中状态

工作中碰到一个变态的性能问题。CMS中有个页面,上面有个多选框,其中有14000个选项。页面中提供给用户一个按钮,点击这个按钮时要清除已选中状态。原有的代码是这样的:

function re() {
    for (var i = 0; i < document.form1.totopicid.options.length; i++) {
        document.form1.totopicid.options[i].selected = false;
    }
}

先抛开循环时多次计算length的问题不谈,光是执行14000次的document.form1.totopicid.options[i].selected = false;就要用户等很长时间。其实有一种更快捷的方式去做这件事情。代码如下:

function re() {
    var select = document.form1.totopicid;
    select.selectedIndex = 0;
    select.options[0].selected = false;
}

这样不仅代码量小,而且性能也能得到很大提升。

用JavaScript判断IE版本号

网上抄来的,找不到原文链接了。望告知。

// ----------------------------------------------------------
// A short snippet for detecting versions of IE in JavaScript
// without resorting to user-agent sniffing
// ----------------------------------------------------------
// If you're not in IE (or IE version is less than 5) then:
//     ie === undefined
// If you're in IE (>=5) then you can determine which version:
//     ie === 7; // IE7
// Thus, to detect IE:
//     if (ie) {}
// And to detect the version:
//     ie === 6 // IE6
//     ie > 7 // IE8, IE9 ...
//     ie < 9 // Anything less than IE9
// ----------------------------------------------------------
   
// UPDATE: Now using Live NodeList idea from @jdalton
   
var ie = (function(){ 
   
    var undef, 
        v = 3,
        div = document.createElement('div'),
        all = div.getElementsByTagName('i');
   
    while ( 
        div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->', 
        all[0]
    );
   
    return v > 4 ? v : undef; 
   
}());

对大量子节点DOM操作的最佳实践方式

有时要处理一个DOM节点下面的大量子节点,比如对一个ul一次性插入几百个li,或者清空一个ul下面的所有li,或者是替换掉ul下面的所有内容。本文记录一下最佳的实践方式。

插入子节点

用innerHTML的方式要优于appendChild,但切记不要在循环里用innerHTML += “str”这样的方式,可以先把字符串拼接好,最后再赋值给innerHTML。

替换全部子节点

用replaceChild的方式要优于直接给innerHTML赋值。在firefox下,如果一个元素有大量子节点,给这个元素的innerHTML赋值将会十分的慢,目前最好的办法是Clone这个元素得到新元素,然后给新元素的innerHTML赋值,最后用新元素替换掉旧元素。代码如下:

var str = '', 
	test = document.getElementById('test'),
	newUL = test.cloneNode(false);

for(var i = 0; i < iteration; i++) {
	str += '<li>Test</li>';
}
newUL.innerHTML = str;
test.parentNode.replaceChild(newUL, test);

清除全部子节点

用replaceChild的方式要优于innerHTML = ”,道理同“替换全部子节点”。不要用removeChild循环清空一个元素下面的所有子节点,这种方式在IE 8下非常慢。代码如下:

var test = document.getElementById('test'),
	newUL = test.cloneNode(false);

test.parentNode.replaceChild(newUL, test);

使用minify合并YUI请求

介绍

minify是一个基于php的开源项目,用它可以合并多个JS与CSS的请求。

下载与安装

下载链接:http://code.google.com/p/minify/

将下载下来的压缩包解压缩,将里面的min目录放到网站的根目录下。打开min目录修改里面的config.php文件,将$min_serveOptions[‘minApp’][‘maxFiles’] = 10;的值改大一些,这个参数表示一次请求最多可以合并多少个文件,因为YUI的细粒度很高,所以一次请求的模块文件很多,默认值10显然不够。同时将$min_cachePath = ‘/tmp’;这行的注释去掉,可以提升性能。

完成上面的步骤可以在浏览器测试一下,我机器的测试地址是http://localhost:8086/min/?f=/build/oop/oop.js,/build/event-custom/event-custom.js

注意服务器要开启mod_rewrite模块

改造YUI loader

因为YUI loader构造的请求地址是类似http://yui.yahooapis.com/combo?3.3.0/build/classnamemanager/classnamemanager-min.js&3.3.0/build/oop/oop-min.js&3.3.0/build/event-custom/event-custom-min.js&3.3.0/build/event/event-base-min.js&3.3.0/build/pluginhost/pluginhost-min.js&3.3.0/build/dom/dom-min.js&3.3.0/build/node/node-min.js&3.3.0/build/event/event-delegate-min.js&3.3.0/build/attribute/attribute-base-min.js&3.3.0/build/base/base-base-min.js&3.3.0/build/plugin/plugin-min.js&这样的,URL没有参数名,每个JS文件用”&”号分隔。而minify的地址格式是http://localhost:8086/min/?f=/build/oop/oop.js,/build/event-custom/event-custom.js,可以看出minify的请求地址用’f’当作参数名,每个JS地址用”,”号分隔。所以要对YUI loader进行一下改造,让它可以构造出符合minify格式的请求地址。

进入到YUI的build/loader文件夹,将loader.js备份一下,之后编辑loade.js,找到如下行:

url += frag;
if (i < (len - 1)) {
    url += '&';
}

替换成下面的代码

//fixed YUI 3.1.0 combo bug
//http://yuilibrary.com/projects/yui3/ticket/2528680
if(url.slice(-3) ==='.js' || url.slice(-4) === '.css'){
    //for K2
    //使用Minfy 取代 Combo Handler
    //url += '&';
    url += ',';
}

url += frag;

// there is a bug that combo?a.css&b.css&
/*
if (i < (len - 1)) {
    url += '&';
}
*/

在YUI使用minify

做完上面的工作之后,就可以在YUI中使用自己的combine服务了。在实例化YUI的时候加上如下的配置项:

YUI({
    combine: true,
    comboBase: '/min/?b=build&f=',
    root: ''
}).use('placeholder', function(Y) {
	Y.all('.test').plug(Y.Plugin.Placeholder);
});

阶乘

// 定义:计算n的阶乘,并把结果传到con中
function fac(n, con) {
    if(n <= 1) {
        // 直接将1传到con中,并执行
        con(1);
    } else {
        // 调用:求解n-1的阶乘
        arguments.callee(n - 1, function(r) {
            // 将n-1阶乘的结果与n相乘
            con(n * r);
        });
    }
}

// 调用:计算5的阶乘
fac(5, function(r) {
    console.debug(r);
});