leechael.orgHome

在 Web-App 中侦测浏览器是否启用了 JavaScript

虽然 noscript 很好用,但这不再是标准中 recommended 的标签。PPK 在其的 《PPK on JavaScript》 中曾提及使用 location.replace() 取代 noscript 的方法:

window.onload = function(){
location.replace('http://path.to/target');
}

可这般为每一个 ajax 页面创建一个侦测页面似乎有点浪费,虽然可以机器生成,但也不是一个很好的办法。由于在 Web-App 中,绝大部分的包含 JavaScript 的页面都是动态页面,我们可以使用一个专有的脚本对浏览器进行判别是否启用了 JavaScript,然后使用 location.replace() 重定向。假设我们目标页面为 /foo.php, 而在运行这个脚本以前,用于进行是否启用的 JavaScript 的脚本是 /jsdetector.php。

在 jsdetector.php 输出的页面中,我们尽可发挥创造力,做一个很漂亮、state-of-the-art 的页面,而只需要在 head 中使用 PHP 输出一句:

echo '<script type="text/javascript">location.replace("/' . $_GET['target'] . '.php");</script>';

然后,我们把所有 /foo.php 的链接替换为 /jsdetector.php?target=foo。

为了避免用户直接访问 /foo.php 这个页面、绕开浏览器的检测,在 foo.php 输出任何内容之前,进行一个小小的检测:

$referer = strtolower( basename( $_SERVER['HTTP_REFERER'], '.php' ));
if ( $referer !== 'foo' ) {
header( 'Location: /foo' );
}

这般我们就能得到一个较为简便的方法,来进行浏览器是否启用了 JavaScript 的检测。当然,我们也可以使用 Apache 的 Rewrite 功能,让这一切看起来都更为语义化、隐藏 jsdetector.php 的存在:

RewriteRule ^/fetch/foo$ /jsdetector.php?target=foo [L]
RewriteRule ^/fetch/bar$ /jsdetector.php?target=bar [L]
RewriteRule ^/fetch/baz$ /jsdetector.php?target=baz [L]
RewriteRule ^/foo$ /foo.php [L]
RewriteRule ^/bar$ /bar.php [L]
RewriteRule ^/baz$ /baz.php [L]

OO in PHP (3)

让我们首先欢呼,良好的学术气氛万岁。 :P

先看看 fcicq 同学在 上一篇 后面的留言。既然有同学提出不同的看法,我们就去看看对比的结果。

测试环境: Windows XP sp2, WAMP5 1.7.2 (Apache 2.2.4, PHP 5.2.3), CPU Inter T2050 @ 1.6GHz。两个脚本分别运行一万次,计算运行耗时及内存使用情况。

第一次测试

Tradition:
Time Usage: 1.05502605438
Memory Usage: 66368

Linked:
Time Usage: 1.10734796524
Memory Usage: 69344

第二次测试

Tradition:
Time Usage: 1.04215908051
Memory Usage: 66368

Linked:
Time Usage: 1.0259718895
Memory Usage: 69344

重启 apache,第三次测试

Tradition:
Time Usage: 0.997123003006
Memory Usage: 66616

Linked:
Time Usage: 1.10502290726
Memory Usage: 69344

第四次

Tradition:
Time Usage: 1.04014492035
Memory Usage: 66368

Linked:
Time Usage: 1.08955287933
Memory Usage: 69344

Conclusion

一万次的运算才带来 0.1 秒的差别,我想利用 OO 打造 Validator 带来的便捷以及更少的代码量,我想大家会从中平衡两者的关系?

再唠叨两句:OO 相应而至的内存使用量(php.ini 中的 memory_limit 记得调整至合适的值),以及实际情况中是否需要牺牲时间来获得如此这般的灵活性(不需要假设你的代码会面临的情况有多么严峻,每秒钟超过 10 requests?),exception 是昂贵的。

测试中的代码可以在这里下载,有兴趣的朋友可以自己试试看: http://leechael.org/files/validator.tar.bz2

Update

经 fcicq 同学指点,改进了脚本,重新测试后的结果:

Tradition
Time Usage: 0.43701004982
Memory Usage: 67512

Linked
Time Usage: 0.607961893082
Memory Usage: 71712

这次的数字可能带来的心理影响不同: 0.17s。最起码 6 看起来比 4 大。 :D

Update 2

作为我还不足够严谨的证明,所以加上这个 Update。如下面 Cofyc 所言,对于 PHP Programmer 来说,更重要的是寻找到一个平衡点,因为 OO 只会带来开发上的便捷,而不是脚本运行速度上的提升。

OO in PHP (2)

上一篇 的内容;不过也不去解说什么了,看例子:

class Validator {

	private $cur;

	public function set($name) {
		if (!isset($_POST[$name])) throw new Exception();
		$this->cur = $_POST[$name];
		return $this;
	}

	public function sanitize() {
		$this->cur = trim($this->cur);
		return $this;
	}

	public function isValidNick() {
		// Do something you like to....
		// If something goes wrong, just throw a exception.
		return $this;
	}

	public function fetch() {
		return $this->cur;
	}

}

$v = new Validator();
try {
	$nick = $v->set('usr_name')->sanitize()->isValidNick()->fetch();
	// Do something after sanitize.
} catch (Exception $e) {
	echo 'Error: ' . $e->getMessage();
}

OO in PHP

如果你也是一个 PHP Programmer:你知道怎么用 PHP 写程序,自信 PHP 已经入门了;你知道 Project Babel 并倾慕于加入对 OO 支持后的 PHP 的代码;你知道 Zend FrameworkCakePHP 这般的 Framework,以及 OO 带来的,开发效率的提升;那么,我推荐你阅读 PHP in Action

好了,对书的推荐到此为止;这里不是 OO 教程,我只在这里谈一谈 OO。OO —— Object-Oriented——是用来干复杂的事情的,它既为我们带来开发效率的提升,在使用恰当的情况下,也可以得到不错的运行效率。自然,我也曾经是 OO 的重度使用者,甚至想在 PHP 代码中尽可能地使用 try {} catch {} 来代替 if {} else {}。但 OO 不是一切。或许先来看看下面的这个描述。

你在一间不大也不小的房间里,房间里除了你以外,还有好几个人,你们是一个 team,协作开发软件,而你是 team leader。你知道 A 君长于界面设计,B 君只会写代码,C 君是一个很有经验的系统维护人员。这天你接到一个 case,你亲自完成策划案后,将结果分开为不同的部分交给你的伙伴们。当然,C 君负责 LAMP 的配置,而这个任务你也不会让对服务器配置一无所知的 A 君负责。

原谅我的词不达意;希望上面的这段东西能够帮助你理解 OO。在这里,ABC 三人就是对象,他们专长于他们懂得的事情,而不懂其他伙伴懂得的事情。当你只需要代码的时候,你不会让 C 君来参与 B 君的工作。

工作很顺利,你的这个小团队扩展到数十人,原来的伙伴也成为了小头目。A 君还是继续负责界面的设计,但随着项目规模的扩大,他现在只负责整体界面的一致性,细节部分由其手下的人员来负责。

不知道用这个来解释继承是否有些勉强呢?

这个团队已经不小了——原本的小房间已经容不下更多人了。由于你们团队的高效、代码的质量很不错,所以财源滚滚,所以你决定换一个更大的工作间。

到这里——一切都完了。假设这个房间是内存,这些工作者是对象——以此类比 OO,你是否有更深刻的了解了呢?

OO 是用来干复杂的事情。PHP 和 Python 都不是很极端 OO 的 Program Language,相对于 JAVA 来说。如果你对某个故事有印象:在屏幕上输出“Hello world!”——那么你一定记得,Hacker 的办法是,“echo 'Hello world!'”——bash script。JAVA 能用一句代码实现吗?不能。但是,JAVA 能比 bash shell 干更多的事情,例如,使用 bash shell 你无法写一个 Gtalk Robot。

实际上,我们需要考虑为什么 PHP 和 Python 并不是极端的 Object-Oriented Program Language:对于脚本语言来说,它们更需要的是 Get Things Done。下一次我将以一个例子来说说使用 OO 提高开发效率应有的思路。

使用 Imagick 生成 favicon

这需要在你编译 PHP 时开启 Imagick 这个模块。

$img = new Imagick('image.jpg');
$img->scaleImage(16,16);
$img->setImageFormat('ico');
file_put_contents('favicon.ico', $img->getImageBlob());

关于 favicon 的使用可以参考车东的这篇文章: 网站头像: favicon.ico。

对于一个网志托管服务来说,如果能为用户提供这般的个性化的小功能——上传图像,自动制作 favicon 应用于自己的网志站点;这般的小功能,目前还没有任何人提供吧?

echo(), postscript

在上一篇 echo() 发布后, fcicq 就来添乱子了。 :D

$s = microtime(true);
echo str_repeat('Must I write something here? I don\'t know. But I need some characters for testing.<br />', 100000);
$e=microtime(true);
echo '<br /><br /> . ($e-$s);

0.0314919948578

$foo = array_fill(0, 100000, 'Must I write something here? I don\'t know. But I need some characters for testing.<br />');
$s = microtime(true);
$bar = implode('', $foo);
echo $bar;
$e=microtime(true);
echo '<br /><br /> . ($e-$s);

0.371160984039

用 str_repeat() 来循环同一个句子, 以及 implode() 来合并数组。首先说的是,str_repeat() 的表现很好,但却不是想要的: 没有人会在页面中呈现同一个句子,除非是 spammer。而implode() 的成绩与 for loop 相近。

echo()

做了一个小测试, 测试 echo 的性能: 循环 100000 次输出同一个句子。第一个脚本使用每一次循环都调用一次 echo(); 而第二个脚本则是通过修改一个变量,循环结束后输出结果。测试中分别使用了 if, while, do-while 三种循环方式。

第一个脚本的结果在前,第二个脚本的结果在后,单位是秒。

if loop: 5.05437803268 - 0.375290155411
while loop: 5.26496505737 - 0.417444944382
do-while loop: 6.0743200779 - 0.379925966263

试验过程简略记录在 v2ex, 有需要的同学可以 翻墙查看

我的结论是, 尽量避免大量使用 echo()。例如,为了源代码单行代码更短,一般我们都是这般:

echo('Must I write something here?');
echo(' I don\'t know.');
echo('But I need some characters for testing.<br />');

而实际上,从代码的可读性出发,我们可以考虑这般的写法:

$str = '';
$str .= 'Must I write something here?';
$str .= ' I don\'t know.';
$str .= 'But I need some characters for testing.<br />';
echo($str);

此外, 也能看出 if loop 比 do-while loop 更常用,而 do-while loop 只能坐冷板凳的原因,在 do-while loop 的测试中也大致能看出来。

有一点需要提醒的是,注意 do-while 的不同: 先执行, 再判断是否达到跳出循环的条件。