Archive for the 'Web开发' Category

PHP模拟BigPipe

前不久新浪微博改版,前端引入了BigPipe(最早是Facebook提出的),页面载入速度嗖嗖嗖地上去了。。那末为神马BigPipe能这么快呢?
与AJAX相似,BigPipe使页面可以按区块渲染。但与AJAX不同的是,BigPipe不需要在页面载入后再通过XMLHttpRequest向服务器发起异步请求,而是(通过使用缓冲区)在页面载入过程中先输出页面布局,并在HTML中预留各个页面区块(Facebook称为Pagelet),在页面底部才输出Javascript代码,DOM操作,从而对区块进行内容填充,也就是把内容赋给innerHTML。
这么做的好处是,在页面加载的过程中,把一些响应时间相对较长的输出放到了页面底部,而页面整体轮廓先显示出来,减少用户的等待时间;此外,与Ajax相比,BigPipe不需要进行多次请求就能完全显示内容,降低了服务器的负担。
下面用PHP模拟一下BigPipe,代码里用sleep(1)模拟一些运算的时间损耗(要先关闭Apache的GZip):

<!doctype>
<html>
<head>
	<meta charset="utf-8" />
	<title>PHP模拟BigPipe</title>
</head>
<style>
.section{height:200px;}
#con1{background:#ef8090;}
#con2{background:#fef000;}
#con3{background:#19c000;}
</style>
<body>
	<div class="wrapper">
		<div class="section" id="con1">页头内容,正在加载……</div>
		<div class="section" id="con2">正文内容,正在加载……</div>
		<div class="section" id="con3">页尾内容,正在加载……</div>
	</div>
	<?php
	/*
	 * 输出缓存区
	 */
	function flush_now(){
		ob_flush();
		flush();
	}
	flush_now();
	?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con1").innerHTML = "<?php echo str_repeat(\'内容1\', 200) ?>";
	</script>
	<?php flush_now()?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con2").innerHTML = "<?php echo str_repeat(\'内容2\', 200) ?>";
	</script>
	<?php flush_now()?>

	<?php sleep(1);?>
	<script>
		document.getElementById("con3").innerHTML = "<?php echo str_repeat(\'内容3\', 200) ?>";
	</script>
	<?php flush_now()?>
</body>
</html>

这样,打开页面后,页头、正文和页尾的内容一开始显示的是“正在加载……”,但随后内容会逐一显示出来,肿么样,很神奇吧。
但话说回来,这只是模拟的BigPipe,因为每个区块的内容输出之间是阻塞的,需要等待前一个区块处理完了,才能接着处理后一个。而要真正实现BigPipe就得使用多线程,可以通过CURL的异步模式或者pctnl。

关于一些编程语言的运算符“优先级”

Tips:这个问题是铁军师兄发现并提出的,我写篇博客梳理一下。
当我们在学习一门编程语言的时候,我们经常会看到类似这样的一句话:运算符优先级是一套规则。该规则在计算表达式时控制运算符执行的顺序。具有较高优先级的运算符先于较低优先级的运算符执行。
那么事实是不是果真如此呢,下面让我们用Javascript等几门语言为例来验证一下。
首先,在官方的ECMA-262/Core JavaScript手册以及权威的MDN Docs中,分别是这样介绍Javascript运算符优先级的:

All operators have a pre-defined execution priority or precedence. Within a given complex expression, operations will be performed in order of priority.

Operator precedence determines the order in which operators are evaluated. Operators with higher precedence are evaluated first.

两段介绍的意思大致是一样的,都是说运算符优先级决定了运算符执行的先后顺序。运算符优先级高的运算符会被先执行(计算)。我们以往的编程经验似乎也在告诉我们这是真理,然而请先看下面一段代码:

var a=1;
function fn(){
    console.log(a);
}
var b = a++ + fn();

按照Javascript官方手册上对运算符优先级的定义以及的运算符优先级表(如图所示或参考http://rx4ajax-jscore.com/ecmacore/operator/predence.html),我们可以推算运行结果:在最后一行代码中,由于函数调用的括号运算符“()”优先级比自增运算符“++”和算术运算符“+”都要高,所以会先被执行。所以在控制台中被log的a的值应该是1。而事实上,结果是2。这说明了什么?——虽然“()”运算符的优先级比“++”要来得高,但a++却先于fn()被执行。
javascript优先级
图节选自Javascript运算符优先级表(http://rx4ajax-jscore.com/ecmacore/operator/predence.html)
那么在Javascript中是这种状况,在其他语言中又会是什么情况呢?随后,我们分别测试了PHP、C/C++、Java以及C#版本的代码(Python和Ruby中没有自增运算符)。猜猜结果各是什么?
PHP版:

$a=1;
function fn($a1){
    echo $a1;
}
$b = $a++ + fn($a);

C++版:

#include "stdafx.h"
#include <iostream>
using namespace std;
int fn(int a1)
{
	cout<<a1;
	return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
	int a = 1;
	int b = a++ + fn(a);
	int i;
	cin>>i;
	return 0;
}

揭晓答案:
只有C/C++输出了1,其他语言都输出了2。
这样看来似乎最传统的编译型语言C/C++能得出与其运算符优先级定义相符合的结果。而事实上关于运算符优先级,PHP手册是这样定义的:

The precedence of an operator specifies how “tightly” it binds two expressions together.

意思是运算符优先级决定了两个表达式之间“紧密程度”——也就是说代码的组合方式。这么一来PHP为什么输出2就能解释了。而C#和Java的代码,如果这么解释也能解释得清楚。不过在Java的运算符中,并没有函数调用的“()”运算符。C#当中却是有的。更有趣的是,在C/C++中,前自增/减运算符“++/–”的优先级居然排在了函数调用运算符“()”的前面,仅次于“::”运算符。
结论:混乱啊。。

一道Javascript面试题

var n = 2;
var showNumber = function(){
    n = 1;
    alert(n);
}
(function(){
    n=3;
    alert(n);
})()

你能说出alert出的内容并解释其原因吗?

事件委托在jQuery和KISSY内核中的应用

一直觉得jQuery中的 .live() 方法很神奇,居然可以对动态加入的元素进行绑定。今天终于弄明白了,原来这是通过使用事件委托实现的。

   $('a').live('click', function(){
      //code block1
   })
   

上面的这段代码可以约等于(但不完全等于,总结中说明了为什么):

   $(document).bind('click', function(e){
      if(e.srcElement.tagName == 'a'){
         //code block1
      }
   })
   

此外,jQuery还有一个 .delegate() 方法,可以实现接近 .live() 的效果:

   $('#container').delegate('a','click',function(){
      //code block2
   })
   

jQuery在文档中查找#container,并使用click事件和’a'这一CSS选择器作为参数,把函数绑定到$(‘#container’)上。任何时候只要有事件冒泡到$(‘#container’)上,它就查看该事件是否是click事件,以及该事件的目标元素是否与CSS选择器相匹配。如果两种检查的结果都为真的话,它就执行绑定的函数。
在KISSY 1.2版本中也引入了 .delegate() 方法。
这两种方法中,个人觉得还是 .delegate() 方法效率高。因为 .live() 方法需要把选择器匹配到的元素都包装成jQuery对象;而 .delegate() 方法只在委托的节点上监听冒泡事件。感觉用 .live() 方法进行事件委托比较鸡肋啊,可能这也是为什么其他js框架没有类似方法的原因吧。

javascript元编程创建DOM节点

在使用javascript创建DOM节点的过程中,难免要使用document.createElement、setAttribute、document.createTextNode等冗长、还有浏览器兼容性问题的节点操作方法,虽然有人提议使用字符串拼接后,再使用.innerHTML=可以降低创建节点的成本,而且还有更好的性能,但在我印象中innerHTML并不能适应所有需求(具体神马需求我也忘了),所以下面给大家介绍一种使用javascript元编程技巧来动态创建节点的方法。
那么什么是元编程呢?了解Ruby的同学知道,Ruby的风靡离不开Ruby on Rails(RoR)框架的推动。RoR框架通过魔幻实现质朴的方式,把很多需要用其他语言实现的功能用Ruby来实现,最经典当属RoR框架的ActiveRecord。让我们看一下下面这段代码:

person = Person.find_by_email('xxx@x1989.com')
person.age = 23
person.save

上述代码可以用SQL语句替换,如下所示:

SELECT * FROM person WHERE email = 'xxx@x1989.com'
UPDATE person SET age = 23 WHERE email = 'xxx@x1989.com'

即使不会Ruby和SQL的人也可以看出,采用Ruby语法方式更自然。而正是Rails通过元编程技巧,让我们无须在一个程序中混用两种不同的语言。维基百科上元编程的定义:

编写一些程序来提前生成一些数据或代码供运行时使用,用来生成这些数据信息或代码的程序称为元程序(MetaProgram),编写这种程序就称为元编程(MetaProgramming)。

另一种说法:

元编程是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。

那么,我希望用javascript实现”create_”+任意HTML5标签名的动态节点生成函数,比如用create_form、create_div、create_input来生成对应名称的节点,使用下面的api:

//①创建只含有文本的节点
var span = create_span('hello world');
或不含文本的节点
var br = create_br();

//②可以在参数1中以对象字面量来定义属性
var input = create_input({
    'type': 'text',
    'size': 20,
    'value': '123'
})

//③用参数2定义文本节点
var a = create_a({
    'href': 'http://www.google.com.hk',
    'title': '谷歌搜索',
    'target': '_blank',
    'rel': 'xxxxx',
    'costom_attr': '自定义属性',
}, '搜索');

//④参数2可以是节点 参数3(可选)是文本节点
var div = create_div({}, create_img({'src': 'http://www.google.com.hk/intl/zh-CN/images/logo_cn.png'}), '这是logo')

//⑤参数2也可以是节点构成的数组
var ul = create_ul({
	'class': 'list1'
}, [create_li('项目1'), create_li('项目2'), create_li('项目3')]);

实现方式:

void function() {
    //支持以"create_"+任意HTML5标签名的动态节点生成函数
    var doc = document, win = window,
    //HTML5标签:
    tags = ["a", "abbr", "acronym", "address", "applet", "area", "article", "aside", "audio", "b", "base", "basefont", "bdo", "big", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "command", "datagrid", "datalist", "datatemplate", "dd", "del", "details", "dialog", "dir", "div", "dfn", "dl", "dt", "em", "embed", "event-source", "fieldset", "figure", "font", "footer", "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hr", "html", "i", "iframe", "img", "input", "ins", "isindex", "kbd", "label", "legend", "li", "link", "m", "map", "menu", "meta", "meter", "nav", "nest", "noframes", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rule", "s", "samp", "script", "section", "select", "small", "source", "span", "strike", "strong", "style", "sub", "sup", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "tt", "u", "ul", "var", "video", "xmp"];
    for (var i = 0; tags[i]; i++) {
        //闭包是个好东东
        win['create_' + tags[i]] = function(tag) {
            return function(attrs, childNode, textNode) {
                return createNode(tag, attrs, childNode, textNode)
            }
        } (tags[i]);
    }
    var createNode = function (tagName, attrs, childNode, textNode) {
        //创建以tagName命名的节点
        var node = doc.createElement(tagName);

        //处理attrs参数 设置节点属性
        typeof attrs === 'string' ? createTextNode(node, attrs) : createAttributeNode(node, attrs); //创建并设置属性节点
        //处理childNode参数 添加子节点
        if (typeof childNode === 'string') {
            createTextNode(node, childNode);
        } else if (childNode && childNode.nodeName) {
            node.appendChild(childNode)
        } else if (childNode instanceof Array) {
            for (var i = 0; childNode[i]; i++) {
                node.appendChild(childNode[i])
            }
        }

        //处理文本节点
        if (textNode && typeof textNode === 'string') {
            createTextNode(node, textNode);
        }
        return node;
    }
    var createAttributeNode = function(node, attrs) {
        for (var i in attrs) {
            //下面这种方式适用于原生的节点属性
            //node[i] = attrs[i];
            //在IE下setAttribute设置某些原生属性会有兼容性问题
            //node.setAttribute(i, attrs[i]);
            //document.createAttribute在IE下设置原生属性会不带引号
            var a = doc.createAttribute(i);
            a.value = attrs[i];
            node.setAttributeNode(a);
        }
    }
    var createTextNode = function (node, text) {
        node.appendChild(doc.createTextNode(text))
    }
} ();

Demo
文档?足够的注释不需要文档!

Javascript惰性函数函数的应用

function addEvent(element, type, func) {
	if (document.addEventListener)
		element.addEventListener(type, func);
	else
		element.attachEvent(type, func);
}

上面的函数在每次调用的时候都会检测document.addEventListener是否存在,并用它绑定一个事件,如果不存在,就使用ie的方式绑定事件。
这个函数工作得很好,尽管它在浏览器的兼容性检测上还不够完善。但从性能的角度出发上,我们可以做得更好:只执行一次检测,就是在函数第一次被调用的时候。
为了达到这个目的,我们使用一个javascript的惰性加载特性(http://peter.michaux.ca/articles/lazy-function-definition-pattern)

function addEvent(element, type, func) {
	if (document.addEventListener) {
		addEvent = function(element, type, func) {
			element.addEventListener(type, func)
		}
	}else{
		addEvent = function(element, type, func) {
			element.attachEvent(type, func)
		}
	}
	return addEvent(element, type, func)
}

Kissy中jsonp api的bug

在相邻的代码里多次调用Kissy的jsonp方法,调用十次大概会出现六次的:Uncaught ReferenceError: jsonp1302852733609 is not defined(anonymous function).同事仙羽查看源码发现,是kissy的jsonp api是调用太快了,产生了同一个时间戳。。两次请求返回的都是同个函数调用,但是kissy里面的jsonp有个垃圾收集的处理,结果把这个函数给删了,第二次再调用的时候就调用不到了。算是kissy的bug吧,应该用guid的。出错代码如下

for(var i=0;i<10;i++){
    KISSY.jsonp('http://110.75.14.39/home/recommendTmallSpu.htm?appID=87&num=4&key=110202',function(data){
        console.log(data);
    })
}

问题就出现在ajax模块下的这行代码:

jsonp = c['jsonpCallback'] || JSONP + S.now();

贴一段jQuery源码中的实现:

// 默认的发起jsonp请求的设置 即url中callback=?的情况 回调函数也是"jsonp_"+时间错
jQuery.ajaxSetup({
	jsonp: "callback",
	jsonpCallback: function() {
		return jQuery.expando + "_" + ( jsc++ );
	}
});

// 装载jsonp的callback函数
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var dataIsString = ( typeof s.data === "string" );

	if ( s.dataTypes[ 0 ] === "jsonp" ||
		originalSettings.jsonpCallback ||
		originalSettings.jsonp != null ||
		s.jsonp !== false && ( jsre.test( s.url ) ||
				dataIsString && jsre.test( s.data ) ) ) {
                //响应容器
		var responseContainer,
			jsonpCallback = s.jsonpCallback =
				jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
			previous = window[ jsonpCallback ],
			url = s.url,
			data = s.data,
			replace = "$1" + jsonpCallback + "$2",
			cleanUp = function() {
				window[ jsonpCallback ] = previous;
				// 当有请求时调用callback
				if ( responseContainer && jQuery.isFunction( previous ) ) {
					window[ jsonpCallback ]( responseContainer[ 0 ] );
				}
			};

		if ( s.jsonp !== false ) {
			url = url.replace( jsre, replace );
			if ( s.url === url ) {
				if ( dataIsString ) {
					data = data.replace( jsre, replace );
				}
				if ( s.data === data ) {
					// Add callback manually
					url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
				}
			}
		}

		s.url = url;
		s.data = data;

		// 装载callback
		window[ jsonpCallback ] = function( response ) {
			responseContainer = [ response ];
		};

		// Install cleanUp function
		jqXHR.then( cleanUp, cleanUp );

通过设置p3p头来实现跨域设置cookie[转]

转自:http://huaidan.org/archives/1526.html
今天在w3网站上看到了一篇介绍p3p的文章(http://www.w3.org/TR/p3p/),利用这个可以实现跨域访问cookie,我也试验一下。

其实很简单:试验用了2个域名readlog.cn和diaor.com

首先在readlog.cn下放置一个文件setcookie.php 内容:
PHP代码

header(’p3p: CP=”CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IND

PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV”’);//p3p
setcookie(“Testcookie”,”test”,time()+3600,”/”,”.readlog.cn”);//设置cookie

再放置一个readcookie.php 内容:

然后在diaor.com下放置setcookie.php ,内容:
JavaScript代码

1. <script src=”http://www.readlog.cn/setcookie.php”></script>

然后访问http://www.diaor.com/setcookie.php 抓取数据包可以发现,在readlog.cn域下生成了一个cookie ,名称是Testcookie,值是test

下面访问http://www.readlog.cn/readcookie.php 来验证一下,可以发现,cookie确实设置成功了。

有兴趣可以去掉header(’p3p: CP=”CAO DSP COR CUR ADM DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi IN

D PHY ONL UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV”’);这句,试一下就会发现cookie能设置,但是在readlog.cn读不到这个cookie。

PHP Fatal Error重定向跳转

在前端生产环境下,我们需要error_reporting(0)来禁止对用户输出任何错误信息;与此同时,如果能在不可预见地发生Fatal errors和Parse errors的时候跳转到其他页面,而不出现空白,那就能在一定程度上保证用户体验。
我们发现,PHP的error_handlers不能用来处理Fatal errors和Parse errors,于是就有了下面这个方法。
在php.ini文件中可以找到:

; String to output before an error message.
;error_prepend_string = "html code"
; String to output after an error message.
;error_append_string = "html code"

那么我们可以把error_prepend_string或者error_append_string前面的分号去掉,把它的值设置为”<script>window.location.href=’another_page.php’;</script>”,在出错的时候使用javascript的redirect到另一个页面。
但是这种做法似乎不太优雅…正在寻找通过服务器302临时重定向的方法。

PHP SPL File Browser

俗话说不想偷懒的程序员不是好程序员。
所以为了向“好程序员”靠拢,我们要自觉地在工作中偷懒…当然不是要你偷工减料,而是要用程序员应该有的方式来“偷懒”,提高工作效率。
我所在的公司使用的PHP demo环境,出于安全方面的考虑,设置了禁止访问网站目录(列出目录文件列表),造成了我没有办法通过浏览器直接访问目录,查看到目录下的文件列表。上传了个php探针到demo服务器上,发现PHP的系列文件操作函数都被禁用了。这样我也没有办法写一段php脚本通过opendir、chdir、scandir等函数来遍历目录下的文件,输出文件列表到浏览器。
好在经过测试,发现并没有禁用PHP的SPL(SPL是Standard PHP Library的缩写)。这样我们就可以通过SPL中的DirectoryIterator来遍历目录了。
于是就有了下面这个小工具,我想可以把它叫做PHP SPL File Browser或者PHP SPL Directory Browser。它强大得放在任意子目录下都可以浏览整站目录。代码写得很乱,就不献丑了,有需要的童鞋可以发Email给我。