2009年2月26日星期四

网页栅格系统研究(4):技术实现

前三篇文章中,明确了栅格系统的设计细节和适用范围。这一篇将集中讨论960栅格系统的技术实现。

Blueprint的实现

Blueprint是一个完整的CSS框架,栅格系统是它的一部分功能。我们来看demo页面


以上三栏布局的代码为:
<style type="text/css">
.container { margin: 0 auto; width: 950px }
.span-8 { float: left; margin-right: 10px }
div.last { margin-right: 0 }
hr { clear: both; height: 0; border: none }
</style>
<div class="container">
<div class="span-8"></div>
<div class="span-8"></div>
<div class="span-8 last"></div>
<hr />
</div>

上面是基本功能,Blueprint还支持append-n, prepend-m, border等“高级”功能,这些就不细说了。Blueprint的特点简单总结如下

  1. 采用浮动来实现布局,简单明了
  2. 950两侧没有margin, 最后一列的class需要加上last
  3. 采用额外标签来清除浮动

960.gs的实现

谈到960栅格系统,不得不提960.gs. Nathan Smith在这篇文章中,详细阐述了他的想法和设计思路。这里有个demo页面,核心代码很简单:

<style type="text/css">
.container_12 { margin: 0 auto; width: 960px }
.grid_4 { float: left; margin: 0 10px }
</style>
<div class="container_12">
<div class="grid_4"></div>
<div class="grid_4"></div>
<div class="grid_4"></div>
<div class="clear"></div>
</div>

上面就构建了三栏布局:
有意思的几点:
  1. margin是均匀放在950两侧的
  2. 所有grid除了宽度不同,左右边距都一致margin: 0 10px;
  3. 代码简单清晰,起始和结束列不需要添加额外class

很明显,Blueprint和960.gs都是采用浮动来实现布局的,主容器需要添加额外标签来清除浮动(可以参考这里)。当然,这也不是什么大问题,请看这篇文章的总结,不添加额外标签也可以清除浮动。

YUI的实现

接着来看大名鼎鼎的YUI Grids CSS. YUI的CSS框架由三个文件组成:

reset.css - 样式重置
fonts.css - 版式字体控制
grids.css - 栅格系统

我们从demo开始:

注意,demo链接中的宽度是750的,但我们只要将<div id="doc"></div>中的id改为doc2, 页面宽度就自动变为950宽了(YUI非常强大^o^)。来看下dom结构:

采用的也是浮动布局,简化后的CSS代码为:
<style type="text/css">
.doc2 { margin: auto; width: 73.076em }
.yui-u { float: left; margin-left: 1.99%; width: 32% }
div.first { margin-left: 0 }
#ft { clear: both }
</style>

YUI的特点是:

  1. 依旧是采用浮动布局,槽(Gutter)宽通过margin-left来控制(Blueprint采用右边距,960.gs采用均分,这三个框架对槽的处理实在有意思)
  2. 总宽度采用em, 这样可以用在弹性布局上
  3. 栏的布局用的是百分比,采用了流体布局
YUI的好处是能用来做自适应布局,在这前面两个框架里是没有的。但普通的定宽布局,YUI则显得有点麻烦,比如我们要实现四栏布局,dom得这样写:
先来两个两栏布局,再细分为四栏布局,清晰度上欠佳。

更多栅格实现

栅格化更多是一种布局思想,实现技术可以千差万别。比如今年冒出来的伪绝对定位,立刻就可以用来实现栅格系统。明城兄弟就尝试了一把

肯定还有非常多的栅格化实现方案,这里就不一一挖掘了。

双飞翼栅格系统

挺奇怪这个名字?请先阅读这篇文章:渐进增强式布局探讨. 简单说,双飞翼布局是一种布局实现技术,可以利用它来实现一整套栅格系统。

先看test页面:Grids Layout Test.

具体技术细节在渐进增强式布局探讨一文中已经阐述,这里不再重复。有几点需要说明:

  1. 这套栅格系统并不能实现所有布局。这和YUI Grids类似,只能实现预定的一些布局。比如三栏布局,目前只加入了5 : 13 : 6, 5 : 12 : 7, 9 : 9 : 6, 8 : 8 : 8四种情况,这是从淘宝的现有页面中分析总结出来的。对于同一个站点来说,太多不同的三栏比例不是好事(淘宝目前都有点多,以后可能还会进一步统一)。因 此如果采用这套栅格系统的话,需要先分析站点,定义出一套合适的比例。这里有个所有比例的自动生成工具:grids_css_generator.html.
  2. 关于命名:.grid-c2-s6表示两栏(c2: column 2)布局,sub栏的宽度是4列(s4: sub width is 4 * 40 -10). 而.grid-c2-s6f, 最后的f表示两栏布局的第二种情况,即submain互换。类似地,.grid-c3-s5e6d表示三栏布局,其中sub栏的宽度是5, extra栏的宽度是6, 最后的d表示是s5e6三栏布局中的第四种情况。
  3. 为了方便使用,将最常用的两栏布局.grid-c2-s5.grid-c2直接表示。同样的,.grid-c3表示.grid-c3-s5e6. 这是淘宝的默认值,其他站点可以根据实际情况修改。
  4. 这套布局符合渐进增强式工作流程。细心的你可能已经发现,所有两栏布局和三栏布局,HTML中的DOM结构是完全一样的,只有最外层divclass不同。如果要交换左右栏,只要非常简单的修改下class就可以。
  5. 实际使用时,两栏布局和三栏布局已经够用。其实有了两栏,其它布局就都可以组合出来。这里有一个尝试性页面:grids_test4_v0.1.html. 组合布局看起来很强大,但实际使用时会把问题搞复杂,不推荐使用,干脆忘掉吧。

最后来看下两个测试页面:两栏布局grid-c2_test.html三栏布局grid-c3_test.html.

目前除了发现在ie6下有个bug(超大图片等会撑乱布局,其实可以用overflow: hidden来解决,但考虑overflow负面影响,最后还是由布局内部的模块来自主控制的好),尚未发现其他问题。

小结

栅格系统更多的是一种布局思想,在实际使用时,根据具体需求选用合适的技术来实现即可。需要注意的是,对于栅格的技术实现来说,太灵活未必是件好事,适度灵活最难得。怎么才能适度呢?这需要疯狂实践 + 不断的反思 + 持续的重构 + 悟…

栅格搭好了页面框架,接下来很重要的一件事情就是往里面添加内容模块。让内容模块规范化,让页面生成工业化,对大型站点来说,这是栅格系统最有商业价值的地方。下一篇也是本系列最后一篇将展示栅格系统中的模块化应用。


指定您的URL范式

发表者: Joachim Kupke, 资深软件工程师; Maile Ohye, 开发者项目技术带头人
原文: Specify your canonical
发表于: 2009年2月12日星期四,12:30 PM

您可能会对URL形式不同造成的重复内容有所担心,谷歌现在推出一种新的功能,使您可以指定您喜欢的URL格式。如果您的网站通过多种不同形式的URL向访问者提供完全相同或非常类似的内容,那么通过这种功能您可以自主控制出现在搜索结果中的您网站的URL格式。同时这也有助于将那些影响您网页声望值的因素更固定地指向您所青睐的URL格式上。

让我们以一个出售瑞典鱼的网页为例,假设我们所青睐的URL格式和所对应的内容是下面这样的:

http://www.example.com/product.php?item=swedish-fish
然而,访问者和谷歌机器人实际上可以通过另外的URL形式访问到这一内容。尽管URL的核心部分与您青睐的URL格式很相近,但是他们依据排序的参数或分类浏览种类的不同而向用户提供略有差别的网页。

http://www.example.com/product.php?item=swedish-fish&category=gummy-candy
或者,也有可能他们有着完全相同的内容,但是URL看起来并不相同,比如下面的URL还带有跟踪参数或者会话ID:

http://www.example.com/product.php?item=swedish-fish&trackingid=1234&sessionid=5678
现在,您可以将如下语句<link rel="canonical" href="http://www.example.com/product.php?item=swedish-fish">
加入到其他您不倾向于在搜索引擎出现的URL的代码部分,就能指定您喜欢的URL格式。

比如您不希望以下两种格式在搜索结果中出现:

http://www.example.com/product.php?item=swedish-fish&category=gummy-candy
http://www.example.com/product.php?item=swedish-fish&trackingid=1234&sessionid=5678

只要您将上文中的
语句加入到上述两个网页的代码部分,那么谷歌就会知道以上两个网址实际上是被建议指向您指定的标准URL: http://www.example.com/product.php?item=swedish-fish上。 其他的URL属性,比如PageRank和相关的其他因素,也都会自动指向该标准URL。

这个标准同时也被其他搜索引擎在抓取和索引您网站时所接受和使用。

以下我们将以FAQ的形式,解答一些您可能存在的疑问:

从强制性与否来说,请问rel="canonical"是一个建议,还是一个指令?
是一个建议。这是一个我们非常自豪的功能,您可以以此提示搜索引擎考虑您对URL格式的喜好。

我能用相对路径来指定我的URL规范么,比如 <link rel="canonical" href="product.php?item=swedish-fish">?
可以,在这里使用相对路径是可以被正确识别的,如果您在代码中指定了link,那么相对路径都会以此base URL为基础。

我可以将URL范式使用在不是完全相同内容的其他网页上吗?
我们允许这些网页之间有些细微差别,比如归在不同类目下的同一产品网页。

如果被指定为规范格式的URL返回404,怎么办呢?
我们会继续访问和抓取您的内容,并应用一些联想功能去寻找一个URL范式,但是我们强烈建议您将一个可访问的URL设置成URL范式。

如果我指定的URL范式并没有被索引会怎样?
就像网络上所有的公共内容一样,我们会努力发现和寻找您指定的URL范式,一旦我们索引到它,我们就会立即将您的rel="canonical"付诸考虑。

我的URL范式可以是一个重定向URL么?
可以,您可以指定一个发生重定向的URL作为URL范式,谷歌会继续跟踪这个重定向并尝试去抓取它。

如果我不小心指定了互相矛盾的URL范式怎么办?
不用担心,我们的算法是很聪明并宽容的,我们会跟踪抓取这个URL范式链,但是我们还是强烈建议您尽快将URL范式指定为特定单一URL形式,从而确保您的搜索结果早日得到优化。

这个link tag可以被用来建议一个在其他域名上的URL么?
不可以。如果您需要转移到一个不同的域名上,那么301永久重定向对您来说更合适。谷歌现在只能认可在不同子域名下的URL范式的指定。所以,站长们可以将www.example.comexample.com, 及help.example.com互相指定为范式,但是不能将example.comexample-widgets.com互相指定为范式。

听起来不错,能给我举一个现实中的例子么?
我们有一个真实的例子wikia.com。比如,您在http://starwars.wikia.com/wiki/Nelvana_Limited 的源代码中可以发现,该网页已经把http://starwars.wikia.com/wiki/Nelvana指定为了URL范式。通过使用rel="canonical",两个网页的PageRank被整合计算,避免了分散计算的流失,同时搜索结果中也只会包含网站管理员所指定的URL形式。

如果您未能应用URL范式指定您心仪的URL形式,您也不要担心,我们会尽我们最大努力,选择一个更优化的URL形式,并将声望等属性值进行相应转移处理,就像我们以前做的那样(英文)。

补充:这个link tag现在也被Ask.com,微软Live Search和Yahoo!搜索等搜索引擎所支持。

Parsing URLs with the DOM

This short function returns an object containing all possible information you would want to retrieve from a URL:

parseURL

// This function creates a new anchor element and uses location
// properties (inherent) to get the desired URL data. Some String
// operations are used (to normalize results across browsers).

function parseURL(url) {
var a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
}

Usage

var myURL = parseURL('http://abc.com:8080/dir/index.html?id=255&m=hello#top');

myURL.file; // = 'index.html'
myURL.hash; // = 'top'
myURL.host; // = 'abc.com'
myURL.query; // = '?id=255&m=hello'
myURL.params; // = Object = { id: 255, m: hello }
myURL.path; // = '/dir/index.html'
myURL.segments; // = Array = ['dir', 'index.html']
myURL.port; // = '8080'
myURL.protocol; // = 'http'
myURL.source; // = 'http://abc.com:8080/dir/index.html?id=255&m=hello#top'

2009年2月22日星期日

怎样设置密码保护问题

密码保护是注册过程中的关键环节,尤其是对帐户安全级别比较高的网站,尤其在账号被盗或者涉及安全登录等问题的情况下,密码保护问题作为用户身份识 别信息,来通过帐号异常登录(如密码被盗,被封帐户被封等)等权限认证。最近做项目才发现这里有很多细节问题,严重影响了用户的体验,总结了以下几点关键 因素,跟大家探讨。

一、安全性

设置密码保护问题的最关键的因素就是安全性,因此需要只有注册的用户本人才能提供正确的答案,不易被别人猜中或被机器破解。设计时可以参照如下因素:

  1. 答案不要问用户经常用到的信息,这样防止别人通过打听或调查而轻易获得。(比如:你的小名,生日,身份证号,电话号码,地址,宠物的名字等等)
  2. 问题最好和用户的隐私相关并且记忆深刻。

我们看看以下例子的比较,可以看出问题1和2要优于问题3和4:

  • 你小时候梦想的职业是?
  • 你的第一个男朋友(女朋友)的生日?
  • 你妈妈的名字?
  • 你手机号码的后四位数?

二、确定性

  1. 提示问题要唯一,不要同时给出多个问题。

以QQ的注册页面为例,有个问题是“你的学号(或工号)是?”就很迷惑,对于那些只有学号或工号的用户还好,对于两个答案都拥有的用户就很迷惑,很 可能他们在注册是随便输入一个,在找回的时候可要回想当初究竟填了那一种号码?我猜想当时的设计师会认为拥有学号的是学生,拥有工号的是上班族,这两个条 件是互斥的,所以只能二选一。其实情况并不这么简单,就以我来举例,注册的qq时 候我是学生,现在已经上班了。找回密码的时候可能早就忘了当时的学号了;或者有的人是在职的学生,同时拥有学号和工号;再或者他转了好几次学,换了n个工 作,可能就存在不止一个学号或工号。。。总之,这两个问题在很多情况下不是互斥的,甚至是重叠的。

图1 qq的注册页面
2.提示问题不要含混不清。
以“我最好朋友的生日”来说。“最好的朋友”有很多限定条件,一个人也许不止有一个最好的朋友,也许前两年的朋友过了几年就成了关系很淡的朋友。这还不算,还要在此基础上价加上“生日” 这个条件涉及到很多格式(下面会提到这个问题),无疑是给用户雪上加霜。
三、不变性
不要让问题的答案是可变的,不然会导致多种可能的答案:
  • 不要让用户自己去定义格式
比如上面提到的“生日”的格式就有很多种:1982/09/08;1982年9月8号(日);1982-09-08等等。我的建议是,如果一定要存在该问题,在答案填写的表单应该对应出现系统定义好的格式,而不单单是一个输入框。
  • 问题包含的内容要长时间保持不变
比如“你的手机号码的后6位”等问题,有的人会经常更换手机号码,对那些不经常更换手机号的人来说,手机被盗啊,换了居住地点等外在条件也会有变号的影响。所以不建议作为选项;“你的身份证的后6位”就会比前者要好很多。
  • 答案不要有缩写或简写的可能
比如“你大学毕业的学校”此类问题,答案会五花八门,以“西北工业大学”为例,就会有“西工大”“西北工大”“NWPU”等叫法,将问题改成”你毕业的大学的全称?“就好很多。
四、便于记忆
一个好的提示答案需要很容易被用户记住,理想情况下,用户在看到密码提示后能立马想到答案,而不需要去查身边的一些资料或努力回想。比如“我的驾驶执照号码是?”(反正我是不知道,你们呢?)
另外,不要问到童年时的信息,毕竟离现在这么长时间了。比如:“你最喜欢的小学老师的名字是?”等。

五、其它的细节

  1. 其实有的密码提示问题并不适用于所有人(如”我的汽车牌号?“),况且还有不少缺陷,因此我建议设计师应该多提供一些问题让用户选择,比如图1提供2-3个表单,每个包含15个问题,选择了第一个问题后,后两个表单可以自动清除已选择的选项。
  2. 不要让用户自定义保护问题。虽然这个功能看上去很人性化,但是用户是很怕麻烦的,如果有合适的问题,干嘛还要自己输入呢?( Don’t make me think).况且看看上面的例子就知道设置好的问题是很不容易的,这样的事情还是留给设计师去做吧。
  3. 问题避免提到颜色,因为每个人对颜色的感知都不同,尤其是色盲用户。
    最后我想说的是,密码保护主要是帮助用户方便找回自己的ID,如果这个过程太过繁琐反而会影响操作,在什么时候使用,以及怎样掌控找回的流程,是需要前期评估的,只有在确定使用的时候再去考虑如何设计,千万不要过于纠结问题的细节。

2009年2月21日星期六

如何判断脚本加载完成

在“按需加载”的需求中,我们经常会判断当脚本加载完成时,返回一个回调函数,那如何去判断脚本的加载完成呢?

我们可以对加载的 JS 对象使用 onload 来判断(js.onload),此方法 Firefox2、Firefox3、Safari3.1+、Opera9.6+ 浏览器都能很好的支持,但 IE6、IE7 却不支持。曲线救国 —— IE6、IE7 我们可以使用 js.onreadystatechange 来跟踪每个状态变化的情况(一般为 loading 、loaded、interactive、complete),当返回状态为 loaded 或 complete 时,则表示加载完成,返回回调函数。

对于 readyState 状态需要一个补充说明:

  1. 在 interactive 状态下,用户可以参与互动。
  2. Opera 其实也支持 js.onreadystatechange,但他的状态和 IE 的有很大差别。

具体实现代码如下:

function include_js(file) {
var _doc = document.getElementsByTagName('head')[0];
var js = document.createElement('script');
js
.setAttribute('type', 'text/javascript');
js
.setAttribute('src', file);
_doc
.appendChild(js);

if (!/*@cc_on!@*/0) { //if not IE
//Firefox2、Firefox3、Safari3.1+、Opera9.6+ support js.onload
js
.onload = function () {
alert
('Firefox2、Firefox3、Safari3.1+、Opera9.6+ support js.onload');
}
} else {
//IE6、IE7 support js.onreadystatechange
js
.onreadystatechange = function () {
if (js.readyState == 'loaded' || js.readyState == 'complete') {
alert
('IE6、IE7 support js.onreadystatechange');
}
}
}

return false;
}

//execution function
include_js
('http://www.planabc.net/wp-includes/js/jquery/jquery.js');

2009年2月19日星期四

请勿使用保留字来命名网页元素

转载自 Opera中国

最近,我处理了一个网友报上来的问题,问题提到“Opera 不支持通过 JavaScript 来提交表单”。这个 Bug 很诡意,在接下来的时间里,我动手编写了一段简单的 HTML,代码如下:

<form id="form1" action="" method="get">
<input name="parameter1" type="text">
</form>

<input onclick="document.getElementById('form1').submit()"
value="My Submit" type="button">

很显然,点击 My Submit 按钮是可以提交的。但是,通过参考了网上某个论坛系统的 HTML 代码,我发现了一个问题。如下的代码,只是在原有基础上添加了一个表单的提交按钮。

<form id="form1" action="" method="get">
<input type="text" name="parameter1"/>
<input type="submit" name="submit"/>
</form>

<input onclick="document.getElementById('form1').submit()"
value="My Submit" type="button">

但是,所添加的这行 HTML 代码,将造成已有 My Submit 按钮功能的失效。细心的朋友,也许已经发现了,这个新添加的提交按钮,其 name 也为 submit,这就是造成问题的原因所在。

对于表单中的某个元素,我们可以通过 DOM 方式逐级引用。假设,我想引用上面的 submit 提交按钮,我应该书写成:

document.getElementById('form1').submit
那假如,我想通过 JavaScript 使用 form1 完成提交表单的动作呢,我应该书写成:
document.getElementById('form1').submit()

这就怪了,提交表单的代码竟然与引用 submit 按钮的代码如此相似,以至于浏览器认为 submit 为 form1 的某个元素,而不是可以完成提交动作的方法。其实,是网站代码的编写者不小心造成了这种未期待的后果,且这一现象在其他浏览器中同样存在。

建议

由此,我们可以看到简单地对表单元素使用保留字命名,是多么不严谨的一件事情。Web 标准化不光是简单地使用 CSS 和 HTML,更多的非标准化问题是由于不正确使用几种简单技术产生的。因此,为了让技术更容易地为我们所有,就让我们更严谨地对待技术吧。


How to Use the Sticky Footer Code

The HTML Code

Below is the basic structure of the HTML code. You'll notice how the footer <div> sits outside of the wrap <div>.

<div id="wrap">
<div id="main" class="clearfix">
</div>
</div>

<div id="footer">
</div>
You would place your content elements inside the main <div>. For example, if you were using a 2 column floating layout you might have this;

<div id="wrap">
<div id="main" class="clearfix">
<div id="content">
</div>
<div id="side">
</div>
</div>
</div>

<div id="footer">
</div>
A header could be placed inside the wrap but above the main like this;

<div id="wrap">
<div id="header">
</div>
<div id="main" class="clearfix">
</div>
</div>

<div id="footer">
</div>

If you wanted to place any elements outside of either the wrap or the footer then you would need to use absolute positioning else it messes up the 100% height calculations.

The CSS Code

Below is the CSS code makes your sticky footers to stick to the bottom.
html, body, #wrap {height: 100%;}

body > #wrap {height: auto; min-height: 100%;}

#main {padding-bottom: 150px;} /* must be same height as the footer */

#footer {position: relative;
margin-top: -150px; /* negative value of footer height */
height: 150px;
clear:both;}
You'll notice that the footer height is used three times here. This is important and should be the same value for all three instances. The height properties are stretching the wrap <div> to the full height of the window. The negative margin of the footer brings it up into the padding created for the main <div>. Since the main rests inside the wrap the padding height is already part of the 100%. Thus the footer rests at the bottom of the page.

But we are not done just yet. We need to add the clearfix properties to the main <div>.

Clearfix Hack to the Rescue

Many CSS designers will be familiar with the Clearfix Hack. It solves a lot of problems with floating elements. We use it here to get the footer to stick in Google Chrome. It also solves issues that come up when using a 2-column layout where you float your content to one side and your sidebar to the other. The floating content elements inside the main <div> can cause the footer to become un-stuck in some browsers.

So we add this to our stylesheet as well;
.clearfix:after {content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;}
.clearfix {display: inline-block;}
/* Hides from IE-mac \*/
* html .clearfix { height: 1%;}
.clearfix {display: block;}
/* End hide from IE-mac */

If you prefer to use the Ryan Fait method with the extra push <div> you'll still need to apply a clearfix if you are using a floating multi-column layout.

Known Issues

Heights and Margins
Using top and bottom margins inside some elements may push your footer down by that margin height, perhaps in a header or the wrap or main <div>'s themselves. Instead use padding to create spacing inside the element. You'll notice this is happening if your page has little content so that the footer should be on the bottom but you see that your window scroll bar on the side indicates that it is sitting a bit below the window bottom. Go find the offending top or bottom margin and switch it to padding.

Be carefull with declaring padding on the main <div> in another part of your style sheet. If you were to add something like this; padding:0 10px 0 10px; you would end up overwriting the important bottom padding that is supposed to be the same as your footer height. This would cause your footer to start overlaping your content on your longer pages (in Google Chrome).
Font Sizes
When setting font sizes in your style sheet, if you use relative sizing be aware that some viewers may have their monitor settings create bigger font sizes. In some elements, like in the footer itself, it could break the height settings and create a gap below the footer if there is not enough room left for the text to expand lower. So try using absolute sizing by using px instead of pt or em.

2009年2月16日星期一

大型网站架构系列之四,多对多关系以及并发缓存的设计

多对多关系以及多表查询优化处理

上篇以用户数据表为例介绍了基本的数据分割方案以及基本的配置方案。但是在2.0时代,这种简单的列表索引已经远远实现起来是问题的,多对多关系将是最常见的关系。现在我们针对web2.0数据中广泛存在的多对多关系进行阐述和具体行为判断,比如一个很简单的例子,在2.0时代,好友功能是最常被用到的,每个用户会有很多的好友,同时也会是很多人的好友,那么这个数据量将会是用户数的平方的级别。同样,对于文章标签,每个文章可以有多个标签,而每个标签又可以有多个文章,这又是一个几何乘积,数据量又会是个天文数字。这里不再介绍基于硬件,IO,集群方面的问题,我们以项目开发的角度来实现他

这里先介绍一个基本的施行方案,而后我们进一步的对它进行扩充以满足我们的以后的具体需求

对于多对多关系,传统的处理方案有三种,一种是通过SEARCH的方法来实现,第二一种是通过另建一个索引表,存贮对应的ID以进行存贮,第三种是通过二次归档缓冲来实现(本人不知道用什么语言来描述这种处理方法,姑且如此吧)

对于第一种方案,因为要涉及大量的LIKE查询,性能不敢恭维,基于全文索引的方式可能解决这个问题,但是利用第三方的数据可能未必能适合我们的胃口,我们也可能没有足够的时间和精力来独立开发实现。第二种的情况下,数据库的行的数量也是惊人海量级别的,维护索引表的散列处理,并且要跨表跨区查询,还要维护数据的唯一性,数据处理过程相 当的复杂性能也就不言而喻了。

文入正题,下面以一个简单的例子解释下第三种方案,对数据多对多关系举出来具体的解决方案,我们这里以标签和文章之间的多对多关系为例来讲解,大家可以举一反三的思考群组和用户之间,相册和被圈用户之间等等复杂的多对多关系,如下方案可能不是最好的方案,但是实践证明还是综合时间和开发成本是最合理的。

首先滤清一下流程,在传统的数据库设计中我们是如下走的:当一篇博文发布的时候并插入标签的时候一般是三步走(也可以理解为四步,以为还要判断标签是否存在的问题),第一步插入文章数据库并获取文章的ID,第二步插入标签数据库同时查询标签是否存在,如果存在就取出标签的ID,否则的话插入新标签并取出ID,第三部,将文章的ID和标签的ID插入索引表来建立关联。如果这个时候在索引表上建立了索引的话就是灾难性的,特别是在数据量大的情况下,尽管它可以有效的提高查询速度,但是发布的速度可能就会让人无法忍受了。

我们处理的方法也是四部曲,对多对多关系进行进一步的处理。
用标签的时候,我们用的最多的就是查询标签下的文章和显示文章的标签,所以我们实现这例就成了。
第一步,数据冗余
老生常谈的话题,对文章做冗余,加一个TAG列,我们可以讲TAG的标签如下写[TagID,TagName]| [TagID,TagName]| [TagID,TagName] 同样对于TAG表,我们做如下冗余加个Article字段,如下内容[ArticleID,Title]| [ArticleID, Title]| [ArticleID, Title],在需要增加的时候我们只要APPEND一下就可以了,至于ARTICLE的结构和TAG的结构可以参考我上一篇文章的介绍。其实根据需要还可以存贮更多。
有人会问,为什么要存贮TagName和 ArticleTitle呢,其实是为了避免跨表查询和INNERJOIN查询来做的,In查询和跨表查询会造成全表遍历,所以我们在执行的时候In查询是必须要找到一个有效的替代方法的。关于数据冗余的问题,我们可能还会做的更变态一些,这个后面慢慢说。

第二步:异步存贮。
在设计模式下我们常思考的是单件模式,我们采用另类的单件模式思维来处理,也就是把文章和标签之间的索引作为专门的进程来做,异步的实现。
为了避免文章在发布的时候以为要检查TAG表而造成的线程拥堵,我们需要采取延迟加载的方案来做。服务器应该维护一个进程专业的对标签和文章地段的查询和索引,我们在发布文章的时候应该把标签同步这一块托管给另外的一个进程或者服务器进行处理,并进行索引。

第三步:二次索引:
对于频繁的判断标签去或者热门的标签我们还可以在内存里组织一套有效的索引,比如对于标签“疯狂代码”,我们用树来把它表示出来。对于疯狂代码我们索引一个疯,其实用程序表达就是疯狂代码[0]。而在数组”疯”中存贮以疯开头的标签组,以”傲”的数组中存贮以”傲”开头的标签。如果量更大的话还可以再做N级索引,将这些常用的标签对应设计内存索引,我们可以把它想象的理解为内存中的 Suggest(比如google搜索时的Suggest),使用中我们可以直接拿来使用
第四步:针对跨表查询的处理
很多情况下,我们可能避免不了多表查询,或者 IN,or查询,除去业务层封装的分区视图集群之外,我们还可以处理的更好,在很多情况下,我们的查询会是非常频繁非常统一的(这里的统一指热门查询),比如在SNS中常见的性别,嗜好等多条件搜索,而这些数据可能存贮在多个数据表结构中,而这样会吧不可避免的会产生全表遍历查询。
处理方法也很简单,把原来散列的垂直分割的表再合并起来,合并到另外的只读的订阅服务器上,然后做适当的结构优化和索引,剩下的大家应该明白我的意思了,虽然简单,但是这种处理方法非常适合以后服务器的横向扩充。

以上是对多对多关系和多表查询的一个简单的架构说明,肯定有人会问,如果这样做的话工作量不是太大了吗,分词处理什么的,对每个多对多关系进行处理。
OK,咱们可以进一步的把它来抽象化,我们用TableA 表示Article表,用TagbleT表示Tag表,我们可以讲字段抽象化出来,也就是一个ID,一个Tag的String 同理对于标签表也是如此。朋友们应该可以理解我的意思了。

对,就是做个代码生成器把对应的多对多关系给生成出来,这个很好写的,几个Append就可以搞定。如果想更方便的处理,那么把这个东西做成单件的模式抽象化出来,然后再违反一下原则,做成基类,其他关系继承这个基类。。。。。剩下的应该很简单了,具体实现大家思考吧。

让并发来的更猛烈些吧,高并发环境下的数据处理方案

对于高并发性质的网站,在sns特别是webgame方面应该是最容易也是最难处理的地方了,容易处理的是如果是纯粹基于数据库驱动也就是 select和update的问题,而难的地方也是不是select而是update,在高并发的驱动下,update经常会超时,虽然我们可以在 finally把它处理掉,让人郁闷的是,数据库连接池仍然会饱和,数据仍然会丢失….

上面的情况是非常常见的web项目失败的原因之一,在数据飞速膨胀和并发呈几何级增长的情况下,制约我们的可能是io,database本身的问题了,让我们头痛的是不管是哪种数据库,Oracle也好,mysql也好,sqlserver也好都会timeout,而且是频繁的timeout频繁的Exception。这个时候就需要我们的应用程序在处理的前期就应该考虑到的,一个好的数据缓存策略常常决定了我们的成败,而缓存策略也是web项目最难以测试和最容易出错的地方。

在大型网站架构中,最关键最核心的也是缓存策略了,介于其复杂性,这里只简单的介绍一下基于高并发数据库缓存方案,后面的将详细介绍常用的缓存策略。这个方法与其叫缓存不如叫数据缓冲,其实也是异步更新数据,根据负载情况不同,我们哪怕仅仅将数据缓冲1秒,带来的负载提升就已经非常好了。

实现原理很简单,将并发的更新首先缓存到一个应用程序池中,然后定时查询(注意这里的方案应和缓存方案具体结合,这里只介绍概要情况)。

传统的update请求处理流程是:请求—》应用程序—》更新数据库,如下图:
数据缓冲和更新部分可以在数据层里独立实现,也就是update的传递的时候首先传递缓冲池,然后定时更新,这里需要注意的数据缓冲池的还要做的另外一份 工作就是全局的数据缓存,缓存数据更新到数据这段的时间间隔,我们可以理解为临时表,再提取上下文请求的即时信息的时候首先从缓冲池里读取(这里有很多技 巧,比如巧妙的利用cookie,session做;临界条件判断),流程如下图所示
上面简单的介绍了一下基于数据更新缓存的处理,下篇具体详细介绍基于并发更新机制的详细缓存处理机制

2009年2月4日星期三

与谷歌机器人的第二次约会:HTTP 状态代码和If-Modified-Since

原文: Date with Googlebot, Part II: HTTP status codes and If-Modified-Since
发表于: 2008年11月27日星期四,中午12:12

我们与谷歌机器人的上一次约会棒极了,但网站们还对响应代码感到比较困惑,不知道自己返回的响应代码是否正确。我们的服务器返回了301永久重定向代码,但在什么情况下我们应该返回302临时重定向? 如果我们返回一些新的"404文件无法找到"代码,Googlebot是否不会再访问我们的网站?我们应该支持标头(header)"If-Modified-Since"吗?这些问题让人困惑不解,就好像懵懂的爱情一样。为了少一点诸如此类的烦恼,我们来问问专家——谷歌机器人,看看他怎样评价我们的响应代码。

亲爱的谷歌机器人,

最近我给我的网页做了一次大扫除,删除了一些陈旧的、无用的网页。现在这些网页都返回404"页面无法找到"代码,这么做合适吗?还是我让你感到困惑了? Frankie O'Fore

亲爱的Frankie,

404代码是告知网页已不存在的标准方式,对此我不会感到困惑,因为旧网页从网站上删除或更新是很正常的事情。大多数网站都会在网站管理员工具的抓取诊断中显示一些404错误。这绝对不是什么大问题。只要您有良好的网站架构并能链接到所有您可抓取的网页,我就会很高兴,因为我能籍此找到我需要的任何信息。

但是别忘了,不仅是我来访问你的网页,也会有很多访问者看到你的网页。如果你只是显示简单的"404页面无法找到"的信息的话,不了解的访问者可能会很迷惑。其实有许多办法可以让你的无法显示的页面变得更加友好,最便捷的一个方式就是使用谷歌网站管理员工具上的404小工具,它能够帮助访问者找到那些真正存在的内容。大多数的主机托管商,无论大小,都允许你自定义你的404页面(同样适用于其他返回代码)。

爱你的,

谷歌机器人


嘿,谷歌机器人,

我读了上面你给Frankie的回复,我有一些问题。如果有人链接到我网站上不存在的页面怎么办?我该怎样才能确保那些访问者能够找到他们想要的东西呢? 此外,如果我想移动一些页面该怎么办?我想更好地组织我的网站,但是我很担心这会让你感到困惑,我该怎样让你更明白我的网站呢?Little Jimmy

Jimmy你好,

让我们先不考虑你问题的先后顺序,从最核心的问题来回答吧。首先,我们来看一下来自其他网站的链接,很显然,这些链接可能是你网站的一个重要的流量来源,而且你不希望访问者看到的是一个不友好的"页面无法访问"的信息。因此,你可以利用强大的重定向来解决这些问题。

最常用的两种重定向是301302。事实上还有更多的重定向,但这两种是目前与我们联系最紧密的,正如404301302是可以发送给用户和搜索引擎机器人的不同种类的响应代码。301302都是重定向,但301为永久重定向、302为临时重定向。301重定向可以告诉我这个页面以前是什么样、目前转移到了什么地方等等。它可以完美地用于重新建构你的网站,并对重新计算被指向的新网页的声誉有很大帮助。每当我看到301永久重定向,我就会把所有指向旧网页的外部链接自动作为重定向后新网页的声誉计算因素。这不是很方便么?

如果你不知道怎样实施这些重定向的话,我可以帮你入门。这主要取决于你的web服务器,此外这里有一些搜索结果会比较有帮助:
Apache: http://www.google.cn/search?q=301+redirect+apache

IIS: http://www.google.cn/search?q=301+redirect+iis

你也可以参考服务器所附带的手册或自述文档。

作为重定向的另一个替代办法,你可以向链向您网站的网站管理员发送电子邮件,请他们更新链接指向。不能确定哪些网站指向你是吗?不用担心,我的谷歌同事们已经让这变得轻而易举了。在网站管理员工具的“链接”部分中,你可以输入你网站的某个具体URL来查阅哪些外部链接指向了该网页。

我的谷歌同事们最近还发布了一个新的工具,可以显示所有链向你网站中不存在网页的URL,你可以在此了解更多。

永远乐意为你效劳的,

谷歌机器人


亲爱的谷歌机器人,

我 有一个问题。我生活在互联网中一个充满活力的地方,我不断改变对事物的看法。当你问我一个问题,我绝不会说出同一个答案两次,我的网站上的头条内容每个小 时都在变化,我总会想到新的内容。你看起来像一个很直爽的人,希望得到直接的答案。当我的网站内容频繁更新的时候,我该怎么让你明白又不使你感到困惑呢? Temp O'Rary

亲爱的Temp

我刚刚告诉Jimmy通过301永久重定向来告知谷歌机器人你的新网址,但是你描述的情况则不同,应当适用于302临时重定向。对某一特定已经被索引的URL,如果你想告知你的用户该URL的地址是正确的,但是想访问的内容可以临时在另一个地址找到,那么使用302临时重定向(或更为罕见的"307临时重定向")是一个既礼貌又妥当的方式。例如,Orkut将用户从http://orkut.com 重定向http://google.com/accounts/login?service=orkut,但当检索Orkut*时,这个URL并不是一个具有实际检索价值的网页,而且是在另一个域名下。因此,使用302临时重定向可以告诉我,不要把属于http://orkut.com的内容和反向链接计算到重定向后的目标网址上——因为它只是一个临时页面。

这就是为什么当您搜索orkut时,您看到的是orkut.com,而不是那个更长的URL

请记住:直接沟通是保持良好关系的关键。

你的朋友,

谷歌机器人

*请注意,在这里我把URL做了简化,使它更易读。实际的URL要远复杂于此。


谷歌机器人上尉,

我是一个经常重新设计和组织的网站。我注意到很多网站链接的URL都是我在很久以前就删掉的URL。我已经为这些已删除的URL设置了指向新URL301永久重定向,但在这之后我又对网站做了重新设计,很多上述新的URL也不存在了,于是我又用301永久重定向来让它们指向更新的URL。现在我很担心,在抓取的时候,沿着这些指示,你很可能会不断地抓取到一连串的301重定向,而最终导致你可能放弃未来对我网站的抓取。 Ethel Binky

亲爱的Ethel

听起来好像你已经设置了很多嵌套重定向的URL。好吧,天哪!如果次数比较有限的话,这些"重复重定向"是可以被理解的,但我们或许应当首先思考一下你为什么要这样做。如果你把中间环节的301重定向统统移除,并将我直接引导到该URL的最终目标网址,你将为我们彼此节省大量的时间和HTTP请求。但是不要仅仅想到我们两个,想想其他访问者可能早已经厌倦了在状态栏中不断看到反复的连接加载连接的冗长过程。

设身处地地想一想,如果你的重定向开始看起来都相当长,用户们很可能担心你已经把他们推入到一个无限死循环中。机器人和人类都会害怕那种无穷无尽的重复。相反地,试着消除那些重复重定向,或至少保持他们尽可能短一些,我们就可以体谅一下广大访问者!

设想周到的谷歌网络机器人


亲爱的谷歌机器人,

我知道你一定是很喜欢我,要不然你就不会隔段时间就发出抓取我某一网页的请求,即使他们的内容从来没有改变过,就好像我的十年内都没有变化的大学论文一样。但是这些现在开始变成我的一个麻烦了,有什么办法可以帮助我不让你来占用我珍贵的带宽吗? Janet Crinklenose

Janet, Janet, Janet,

看起来你应该学会一个新的名词——304未修改”。如果我之前曾经访问过一个URL,那么我会在我的请求中插入一个"If-Modified-Since"。此行还包含一个HTTP格式的日期字符串。如果你不想再向我重复发送一遍该文档,那么你只需要向我发送一个正常的并带有"304未修改"状态的HTTP 标头。我很喜欢这样的信息。当你这么操作的时候,你没有必要再向我重复发送该文档,这就意味着你不用浪费你的带宽,而我也不会觉得你又在用老掉牙的重复内容糊弄我。

你很可能会注意到很多浏览器和代理服务器也会在标头上显示" If-Modified-Since ",你也可以这么做来抵制滥用带宽的行为。

现在就开始行动,节省更多的带宽吧!

谷歌机器人

——————

谷歌机器人对我们真是太有帮助啦!现在我们知道应该怎样更好地响应用户和搜索引擎了。下次我们再相聚的时候,就该和这个老朋友坐下来促膝谈心了与谷歌机器人的第三次约会即将发表,敬请关注!)。


与谷歌机器人的第一次约会:标头和压缩

发表者:Maile Ohye (饰网站),Jeremy Lilley (饰谷歌机器人)
原文: First date with the Googlebot: Headers and compression
发表于: 2008年3月5日星期三,晚上6:13
谷歌机器人 -- 多么神奇的梦幻之舟!他了解我们的灵魂和各个组成部分。或许他并不寻求什么独一无二的东西;他阅览过其它数十亿个网站(虽然我们也与其他搜索引擎机器人分享自己的数据:)),但是就在今晚,作为网站和谷歌机器人,我们将真正地了解对方。

我知道第一次约会的时候,过分地分析从来就不是什么好主意。我们将通过一系列的文章,一点点地了解谷歌机器人:
  1. 我们的第一次约会(就在今晚):谷歌机器人发出的数据标头和他所留意到的文件格式是否适于被进行压缩处理;
  2. 判断他的反应:响应代码(301s、302s),他如何处理重定向和If-Modified-Since;
  3. 下一步:随着链接,让他爬行得更快或者更慢(这样他就不会兴奋地过了头)。
今晚只是我们的第一次约会……

***************
谷歌机器人: 命令正确应答
网站: 谷歌机器人,你来了!
谷歌机器人:是的,我来了!

GET / HTTP/1.1
Host: example.com
Connection: Keep-alive
Accept: */*
From: googlebot(at)googlebot.com
User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
Accept-Encoding: gzip,deflate


网站: 这些标头太炫了!无论我的网站在美国、亚洲还是欧洲,你都用同样的标头爬行吗?你曾经用过其他标头吗?

谷歌机器人: 一般而言,我在全球各地所用的标头都保持一致。我试图从一个网站默认的语言和设定出发,搞清楚一个网页究竟长得什么样。有时候人们的用户代理各不相同,例如Adsense读取使用的是“Mediapartners-Google”:
User-Agent: Mediapartners-Google

或者对于图像搜索:
User-Agent: Googlebot-Image/1.0

无线读取的用户代理因运营商而异,而谷歌阅读器RSS读取则包含了订阅者数量等额外信息。

我 通常会避免Cookies(因此不存在所谓“Cookie:”标头),因为我并不希望与具体对话有关的信息对内容产生太大的影响。此外,如果某个服务器在 动态URL而不是Cookies上使用对话ID,通常我都能识别出来,这样就不用因为每次对话ID的不同而成千上万遍地重复爬行同一个网页。

网站:我的结构非常复杂。我是用许多类型的文件。你的标头说:“Accept:*/*”。你会对所有的URL进行收录,还是自动过滤某些文件扩展名?

谷歌机器人:这要取决于我想找什么。

如 果我只是对常规的Web搜索进行检索,当我看到指向MP3和视频内容的链接,我可能不会下载这些东西。类似地,如果我看到了一个JPG文件,处理方法自然 就与HTML或者PDF链接有所区别。例如JPG 的变动频率往往比HTML低很多,所以我不太经常检查JPG的变动,以节约带宽。同时,如果我为谷歌学术搜索寻找链接,那么我对PDF文章的兴趣就会远远 高于对JPG文件的兴趣。对于学者而言,下载涂鸦绘画(例如JPG),或者是关于小狗玩滑板的视频,是容易让他们分散注意力的,你说对吗?

网站:没错,他们可能会觉得被打扰到了。你的敬业精神令我佩服得五体投地。我自己就喜欢涂鸦绘画(JPG),很难抗拒它们的诱惑力。

谷歌机器人:我也一样。实际上我并不是一直都在做学问。如果我为搜索图像而爬行,就会对JPG非常感兴趣,碰到新闻,我会花大力气考察HTML和它们附近的图像。

还有很多扩展名,例如exe、dll、zip、dmg等,它们对于搜索引擎而言,既数量庞大,又没有多大用处。

网站:如果你看到我的URL“http://www.example.com/page1.LOL111”,(呜噎着说)你会不会只是因为里面包含着未知的文件扩展名就把它拒之门外呢?

谷 歌机器人: 网站老兄,让我给你讲点背景知识吧。一个文件真正下载完成后,我会使用“内容—类别”(Content-Type)标头来检查它属于HTML、图像、文本 还是别的什么东西。如果它是PDF、Word文档或Excel工作表等特殊的数据类型,我会确认它的格式是否合法有效,并从中抽取文本内容。但是你永远也 不能确定里面是否含有病毒。但是如果文档或数据类型混乱不清,我除了把它们扔掉之外,也没有什么更好的办法。

所以,如果我爬行你的 “http://www.example.com/page1.LOL111”URL并发现未知文件扩展名时,我可能会首先把它下载。 如果我从标头中无法弄清内容类型,或者它属于我们拒绝检索的文件格式(例如MP3),那么只能把它放在一边了。除此之外,我们会接着对文件进行爬行。


网站:谷歌机器人,我很抱歉对你的工作风格“鸡蛋里挑骨头”,但我注意到你的“Accept-Encoding”标头这样说:
Accept-Encoding: gzip,deflate

你能跟我说说这些标头是怎么回事吗?

谷歌机器人:当然。所有的主流搜索引擎和WEB浏览器都支持对内容进行gzip压缩,以节约带宽。你或许还会碰到其它的一些类型,例如“x-gzip”(与“gzip”相同),“deflate”(我们也支持它)和“identity”(不支持)。


网站:你能更详细地说说文件压缩和“Accept-Encoding: gzip,deflate”吗?我的许多URL都包含尺寸很大的Flash文件和美妙的图像,不仅仅是HTML。如果我把一个比较大的文件加以压缩,会不会有助于你更迅速地爬行呢?

谷歌机器人:对于这个问题,并没有一个简单的答案。首先,swf(Flash)、jpg、png、gif和pdf等文件格式本身已经是压缩过的了(而且还有专门的Flash 优化器)。

网站:或许我已经把自己的Flash文件进行了压缩,自己还不知道。很显然,我的效率很高喽。

谷 歌机器人:Apache和IIS都提供了选项,允许进行gzip和deflate压缩,当然,节省带宽的代价是对CPU资源的更多消耗。一般情况下,这项 功能只适用于比较容易压缩的文件,例如文本HTML/CSS/PHP内容等。而且,只有在用户的浏览器或者我(搜索引擎机器人)允许的情况下才可以使用。 就我个人而言,更倾向于“gzip”而不是“deflate”。Gzip的编码过程相对可靠一些,因为它不断地进行加和检查,并且保持完整的标头,不像 “deflate”那样需要我在工作中不断推测。除此之外,这两种程序的压缩算法语言都很相似。

如果你的服务器上有闲置的CPU资源,可以尝试进行压缩(链接:Apache, IIS)。但是,如果你提供的是动态内容,而且服务器的CPU已经处于满负荷状态,我建议你还是不要这样做。

网站:很长见识。我很高兴今晚你能来看我。感谢老天爷,我的robots.txt文件允许你能来。这个文件有时候就像对自己的子女过分保护的父母。

谷 歌机器人:说到这里,该见见父母大人了——它就是robots.txt。我曾经见过不少发疯的“父母”。其中有些实际上只是HTML错误信息网页,而不是 有效的robots.txt。有些文件里充满了无穷无尽的重定向,而且可能指向完全不相关的站点。另外一些体积庞大,含有成千上万条单独成行、各不相同的 URL。下面就是其中的一种有副作用的文件模式,在通常情况下,这个站点是希望我去爬行它的内容的:
User-Agent: *
Allow: /


然而,在某个用户流量的高峰时段,这个站点转而将它的robots.txt切换到限制性极强的机制上:
# Can you go away for a while? I'll let you back
# again in the future. Really, I promise!
User-Agent: *
Disallow: /


上述robots.txt文件切换的问题在于,一旦我看到这种限制性很强的robots.txt,有可能使我不得不把索引中已经爬行的该网站内容舍弃掉。当我再次被批准进入这个站点的时候,我不得不将原先的许多内容重新爬行一遍,至少会暂时出现503错误相应代码。

一 般来说,我每天只能重新检查一次robots.txt(否则,在许多虚拟主机站点上,我会将一大部分时间花在读取robots.txt文件上,要知道没有 多少约会对象喜欢如此频繁地拜见对方父母的)。站长们通过robots.txt 切换的方式来控制爬行频率是有副作用的,更好的办法是用网站管理员工具将爬行频率调至“较低”即可。

谷歌机器人: 网站老兄,谢谢你提出的这些问题,你一直做得很不错,但我现在不得不说“再见,我的爱人”了。

网站:哦,谷歌机器人…(结束应答):)

2009年2月3日星期二

JavaScript “Associative Arrays” Considered Harmful

The Problem

Try the following code on an empty page, one without any JavaScript libraries added:

var associative_array = new Array();
associative_array["one"] = "Lorem";
associative_array["two"] = "Ipsum";
associative_array["three"] = "dolor";
for (i in associative_array) { alert(i) };

You’ll get three sequential alert boxes: “one”; “two”; “three.” This code has a predictable output and looks logically sound: you’re declaring a new array, giving it three string keys, then iterating over them.

Now do this: replace “Array” with “RegExp” and run the code again. As if by magic, this also works! It’s not the only one. Try Boolean, or Date, or String, and you’ll find they all work as well. It works because all you’re doing is setting properties on an object (in JS, foo["bar"] is the same as foo.bar), and a for..in loop simply iterates over an object’s properties. All data types in JS are objects (or have object representations), so all of them can have arbitrary properties set.

In JavaScript, one really ought to use Object for a set of key/value pairs. But because Array works as demonstrated above, JavaScript arrays (which are meant to be numeric) are often used to hold key/value pairs. This is bad practice. Object should be used instead.

I’m not trying to ridicule or scold. This misconception is too common to attribute it to stupidity, and there are many legitimate reasons for the confusion. But this is something that needs to be cleared up if JavaScript is ever to be used on a grand scale.

If you need further evidence that Array is not meant to be used this way, consider:

  • There is no way to specify string keys in an array constructor.
  • There is no way to specify string keys in an array literal.
  • Array.length does not count them as items. In the above example, associative_array.length will return 0.
  • The page on Arrays in the Mozilla JavaScript reference makes no mention of this usage. (Nor does the ECMAScript specification, by the way, but you’ll have to do your own legwork to verify that, because I’m not linking to page 88 of a kajillion-page PDF.)