nodejs目录遍历
近期解决一个问题的时候,需要一个简单的目录遍历。目录遍历挺常见,操作一个文件夹里的所有文件,替换或者添加删除某些东西是非常普遍的操作。由于 nodejs 本身并没有提供类似的API,所以这部分就得由自己实现。
虽然没有直接的遍历API,但是 nodejs 的文件操作也已经非常便利,用 fs.readdir 和 fs.stat 这两个API的组合就能达到目的。
出于参半程序员的懒惰的劣根性,其实在这之前我也搜索过看看是否有现成的可以拿来用,也确实有这种完善的 module,比如 node-walk,我试用过后觉得还是非常不错的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var walk = require('walk'), files = []; function getFileList(){ var walker = walk.walk('/dirName', { followLinks: false }); walker.on('file', function(root, stat, next) { files.push(root + '/' + stat.name); next(); }); walker.on('end', function() { console.log(files); }); console.log(files) } |
但是我使用其同步方法的时候并未达到同步的效果,不知是我哪里用法有问题。后来因为寻找原因的时间太长也就只好先放弃。其实我只是需要一个简单的同步遍历,所以用module就显得复杂化了。当然搜索过程中也找到了几个简单实现,但代码搞的复杂了,明明递归能轻易做到的情况下却用了数组循环…
所以最后吧,还是得自己写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | var fs = require('fs'), fileList = []; function walk(path){ var dirList = fs.readdirSync(path); dirList.forEach(function(item){ if(fs.statSync(path + '/' + item).isDirectory()){ walk(path + '/' + item); }else{ fileList.push(path + '/' + item); } }); } walk('/dirName'); console.log(fileList); |
递归调用 walk 遍历一个路径里所有的目录并将文件添加到文件列表里。这么做的结果是典型的深度优先,但往往程序往往对结果列表的序列有层次上的要求。所以如果按照广度优先的话,代码上可能需要略微调整一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function walk(path){ var dirList = fs.readdirSync(path); dirList.forEach(function(item){ if(fs.statSync(path + '/' + item).isFile()){ fileList.push(path + '/' + item); } }); dirList.forEach(function(item){ if(fs.statSync(path + '/' + item).isDirectory()){ walk(path + '/' + item); } }); } |
调整的也不多,只是每次需要先push所有的文件,然后再依次遍历子目录。
偷鸡不成蚀把米,想着偷懒,结果绕个圈回来还是自己写来的快——这么点懒都要偷,真是无药可救了。
昆腾2G硬盘拆解
拿出这么一块硬盘并非说明了自己使用计算机的时间,而恰恰只能说明自己是多么的穷苦。我在上了大学之后才有了第一台电脑,但是很快因为在学校呆的时间比较长所以就把机器借给别人使用。后来,我用废旧的零件组装了第二台机器,于是这块硬盘被我从二手市场里论斤买来,然后装进了一台奔三的机器里。
那时的主流硬盘尺寸为160G,CPU为奔四2.8G。所以这块2G的昆腾即使配上奔三依然短板,因为奔三时代的主流硬盘容量就已经是30G了。所以这块昆腾2G硬盘,从现在的角度来看,确实有一些年头了。电路板上的灰尘向我们展示了它这十几年来的沧桑——而就是这么一块略感古董的5.25寸硬盘,最后还是被我拆了。其实这块硬盘除了有几处坏道被我屏蔽以外,时至今日都能正常工作。里面是一个98精简版系统,和一个叫心跳回忆的有约会失败BUG的游戏,然后,就再无其他了。
我拆过很多硬盘,当然这块仍然是最古老的。现代的3.5寸的硬盘在外观看起来要更紧密。并且相对于现在硬盘上非常普遍的“六星”螺丝,这块昆腾用的还是十字螺丝。
98年1月21日,到现在为止,它已经有了14.4年的历史了。比起现在的贴纸,这货的参数完全印刷在盘体上。
拧下了所以周围的十字螺丝后,终于在中间看到了这颗唯一的六星螺丝。拧开的同时可以听到硬盘边发泡密封慢慢裂开,同时空气漏进去盘体而发出的丝丝声。
打开盘体,光洁的盘片映入眼帘。硕大的盘片比起3.5寸硬盘的大出很多,所以视觉冲击力自然也大出许多。
磁头的特写,比起现在的硬盘,可能这部分显得就不怎么精致了。
和其他硬盘一样,光滑如镜子。镜中反射出来的是我种的矮向日葵。
开盘后的全貌。
线圈在镜头里很整齐,旁边的塑料小件顿时让我明白这货“敲盘”声音为什么这么响了。随处可见的大型十字螺丝,总是显露出粗狂的风格感,
5.25寸和3.5寸盘片的对比,当真是差了很多啊。不过容量差的也很多,2G VS … 30G … 有一种时代进步创生出来的悲凉。
和现在硬盘里只有一块磁铁不同的是,这货有两块。顺便说一下,很多人想着要把这块磁铁从金属片上拆下来。其实这个磁铁是用一点点胶水粘在上面的。所以单单位移是去不下来的。最好的办法,是用老虎钳或者台钳先将金属片弄弯,然后让磁铁的边缘产生一些空隙,最后塞进薄一点的钢尺就能慢慢分离开了,并不需要很费力。
拆完了的硬盘,光秃秃的马达和盘壳,还是那么的沧桑。
每次拆硬盘,都感觉像是拆解一件精密的工艺品。各种铝质的小件做工看的我赏心悦目,很多零件都不舍得扔掉,但即使就这么放着也着实无用武之地。掏空的硬盘算是一个藏私房钱的好地方哟~谁能想到,计算机里装着的是一块肚子空空的硬盘,里面没有光亮的盘片,但也许有毛爷爷!
修复A590IS残缺的电池仓扣
我的相机——Canon PowerShot A590IS 我写过多次,可惜它命运多舛,这次又成了我的博客内容。在 修复A590IS电池报警缺陷问题 里,我解决了电池容量误报的问题。今天,我修理的是折断的电池仓扣
A590IS 的一大缺点就是机身太差,强度实在有点弱不禁风。特别是电池仓的扣子,一次跌落就会轻而易举地折断。我的相机的这个在前面板的挂钩一样的钩子很早就折断了,这个扣子的材料和固定用的另一个主机身的扣子的材料不同,很脆,所以才异常脆弱。以前,因为一直有完好的机身上的扣子挂住电池仓盖,所以勉强使用至今。在修复了电池误报问题后,理应不再受电池困扰的我却仍偶尔遇到电量问题,仔细查看在发现是因为缺少一个扣子,电池盖盖不严而总是接触不良。
于是,虽然这是一个坏了很久的地方,但既然影响使用就应该想想办法。
扣子坏的很彻底,整个折断了。这个位置非常难办,很难找到支点。由于这个损坏的钩子受力,所以用胶水是不靠谱的。我理所当然地第一想到的办法是更换整个前面板,于是淘宝和google了一下,发觉这个位置损坏的用户还真不少,很多人求购整个前面板,仅仅为了修复这个小小的钩子。可惜这款机型实在老了点,淘宝上只有一家店出手这种面板,但是要价110,坑爹啊!一部新机的当前价格都只有500,一个塑料壳子就要110?果断放弃。
只好自己另想办法。在电池仓的外面加装固定杆?还是整个做一个新的套子?一开始的想法太复杂,后来观察了一下电池盖,发现卡口有近四分之一指甲的大小,于是灵机一动:这足够放的下一个螺丝。于是我翻出以前拆机的各种螺丝零件,里面还有儿时玩过的四驱车配件,在凌乱的一堆螺丝里找合适的。不能太长也不能太短,螺帽不能太大也不能太小,找了一会终于找到一个合适的,于是立即开工。
先把折断的扣子留下的部分用锯子锯平,用小刀修一下。然后从柜子里挖出电磨机,装上最小号的钻头,对准卡口钻一个小洞。然后在放上螺丝,慢慢捻入,直到捻穿。整个过程不到10分钟,装好相机,效果非常好。只是在钻孔时有点倾斜,所以螺帽没有完全对准电池盖上的卡口,有点碰擦。给电磨换上砂轮直接打掉多余的螺帽部分,伴着嘈杂的一段声响,我的A590IS又重获新生了。
修好后,只要不去看,使用起来完全没有感觉。用螺帽代替了折断的卡口,这次应该不会再折断了吧~
我只有一部相机,虽然从大学时代开始我就嚷嚷着要买单反,不过数年已过,我仍然只有我的A590IS。其实仔细想象,虽然我喜爱摄影,但是购买单反的投入和产出实在太悬殊,随着时间的推移,越来越理性的消费习惯让我和单反越走越远了。未来可能仍然还是A590IS陪着我,记录着我的生活点滴。它其实还很年轻,才拍了7000张都不到的照片,不完全修好它真心觉得有点可惜。
最后这篇文章里的图片还是A590IS拍摄的,当然拆掉了面板,用裸机拍照的时候按键小到影响了一些稳定性。不过至少不像上次拆电路板那样,无法好好的记录这样一次重生。
jsdom——node.js的DOM
最近一周一直在写node,感觉很爽。不过 node 虽好却没有 DOM 还是有很多不方便的地方。好在有 jsdom —— 一个 W3C DOM 的 JS 实现。用这玩意相当犀利,它不仅可以将文档解析成 DOM,而且,你还可以用 YUI 或着 jQuery 去操作生成的 DOM。这在从页面中提取数据时格外有用。
虽然在类Unix系统上安装jsdom非常简单,但在window上就要麻烦许多,下面这些依赖还得独立安装。
node-gyp(nodejs 0.6.13 以上,node-gyp 已经被包含在 npm 中)- Python
- Microsoft Visual C++ Express 2010
装完后我赶紧使用了一下:
1 2 3 4 | var jsdom = require('jsdom'); jsdom.env('http://www.swordair.com',['http://localhost/aptana/code/js-lib/jquery-1.7.1.js'],function(errors, window){ console.log("contents:", window.$("div.blog-update-title").next().html()); }); |
效果妥妥的,无法想象的便利和快捷!
下面是我走的很多弯路,但作为错误信息源保留下来,以便有需要的人能够搜索到并解决他们的问题。
一开始,我就按照 jsdom 的 README 直接安装:
1 | npm install jsdom |
当然,那肯定是不行的。我的系统是xp并且几乎没有安装过什么常用开发工具。结果报错,信息如下:
出现如下错误信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | D:\xampp\htdocs\aptana\code\js\nodejs\playground\node_modules\jsdom\node_m
odules\contextify>node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bi
n\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild
info it worked if it ends with ok
ERR! Error: Python does not seem to be installed
at failNoPython (C:\Program Files\nodejs\node_modules\npm\node_modules\node-
gyp\lib\configure.js:78:14)
at Object.oncomplete (C:\Program Files\nodejs\node_modules\npm\node_modules\
node-gyp\lib\configure.js:66:11)
ERR! not ok
npm WARN optional dependency failed, continuing contextify@0.1.2
jsdom@0.2.14 ./node_modules/jsdom
├── cssom@0.2.3
├── request@2.9.202
└── htmlparser@1.7.6 |
根据 ERR! Error: Python does not seem to be installed,应该是没有安装Python的缘故。赶紧到官方网站下了Python,2.x.x和3.x.x都装了(后来知道其实用的是Python27)。
在搜索过程中,了解到可能还需要安装 node-gyp,所以顺便把 node-gyp 也一并装上:
1 | npm install -g node-gyp |
继续 npm install jsdom 安装 jsdom,不过仍然报错:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | D:\xampp\htdocs\aptana\code\js\nodejs\playground\node_modules\jsdom\node_m
odules\contextify>node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bi
n\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild
info it worked if it ends with ok
info downloading: http://nodejs.org/dist/v0.6.15/node-v0.6.15.tar.gz
info downloading: http://nodejs.org/dist/v0.6.15/node.lib
spawn C:\Python27\python.exe [ 'd:\\Documents and Settings\\jianwang2\\.node-gyp
\\0.6.15\\tools\\gyp_addon',
'binding.gyp',
'-ID:\\xampp\\htdocs\\aptana\\code\\js\\nodejs\\playground\\node_modules
\\jsdom\\node_modules\\contextify\\build\\config.gypi',
'-f',
'msvs',
'-G',
'msvs_version=2010' ]
ERR! Error: Can't find "msbuild.exe". Do you have Microsoft Visual Studio C++ 20
10 installed?
at Object.oncomplete (C:\Program Files\nodejs\node_modules\npm\node_modules\
node-gyp\lib\build.js:105:20)
ERR! not ok
npm WARN optional dependency failed, continuing contextify@0.1.2
jsdom@0.2.14 ./node_modules/jsdom
├── cssom@0.2.3
├── request@2.9.202
└── htmlparser@1.7.6 |
错误信息 ERR! Error: Can’t find “msbuild.exe”. Do you have Microsoft Visual Studio C++ 2010 installed? 显示无法找到编译器 msbuild.exe,并询问我是否安装有 Visual Studio C++ 2010。我擦,当然没有!
这里我偷懒了一下(后来证明是个错误),我知道 .Net 4.0 里包含有 msbuild.exe,所以直接到微软官方下载了 .Net 4.0。安装,运行 npm install jsdom,还是不行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | D:\xampp\htdocs\aptana\code\js\nodejs\playground\node_modules\jsdom\node_m
odules\contextify>node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bi
n\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild
info it worked if it ends with ok
spawn C:\Python27\python.exe [ 'd:\\Documents and Settings\\jianwang2\\.node-gyp
\\0.6.15\\tools\\gyp_addon',
'binding.gyp',
'-ID:\\xampp\\htdocs\\aptana\\code\\js\\nodejs\\playground\\node_modules
\\jsdom\\node_modules\\contextify\\build\\config.gypi',
'-f',
'msvs',
'-G',
'msvs_version=2010' ]
spawn C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\msbuild.exe [ 'build/binding
.sln',
'/clp:Verbosity=minimal',
'/nologo',
'/p:Configuration=Release;Platform=Win32' ]
D:\xampp\htdocs\aptana\code\js\nodejs\playground\node_modules\jsdom\node_
modules\contextify\build\contextify.vcxproj(1,685): error MSB4019: The imported
project "D:\Microsoft.Cpp.Default.props" was not found. Confirm that the path
in the <Import> declaration is correct, and that the file exists on disk.
ERR! Error: `C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\msbuild.exe` failed w
ith exit code: 1
at Array.0 (C:\Program Files\nodejs\node_modules\npm\node_modules\node-gyp\l
ib\build.js:176:25)
at EventEmitter._tickCallback (node.js:192:40)
ERR! not ok
npm WARN optional dependency failed, continuing contextify@0.1.2
jsdom@0.2.14 ./node_modules/jsdom
├── cssom@0.2.3
├── request@2.9.202
└── htmlparser@1.7.6 |
信息显示 msbuild.exe 被找到,不过编译失败。这次是报 error MSB4019: The imported project “D:\Microsoft.Cpp.Default.props” was not found. Confirm that the path in the
好吧,看来还是得安上 Microsoft Visual Studio C++ 2010。到官网下了 Microsoft Visual C++ Express 2010,一阵漫长的等待后,再次运行安装jsdom,终于成功:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | D:\xampp\htdocs\aptana\code\js\nodejs\playground\node_modules\jsdom\node_m
odules\contextify>node "C:\Program Files\nodejs\node_modules\npm\bin\node-gyp-bi
n\\..\..\node_modules\node-gyp\bin\node-gyp.js" rebuild
info it worked if it ends with ok
spawn C:\Python27\python.exe [ 'd:\\Documents and Settings\\jianwang2\\.node-gyp
\\0.6.15\\tools\\gyp_addon',
'binding.gyp',
'-ID:\\xampp\\htdocs\\aptana\\code\\js\\nodejs\\playground\\node_modules
\\jsdom\\node_modules\\contextify\\build\\config.gypi',
'-f',
'msvs',
'-G',
'msvs_version=2010' ]
spawn C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\msbuild.exe [ 'build/binding
.sln',
'/clp:Verbosity=minimal',
'/nologo',
'/p:Configuration=Release;Platform=Win32' ]
contextify.cc
d:\Documents and Settings\jianwang2\.node-gyp\0.6.15\src\node_object_wrap.h(57)
: warning C4251: 'node::ObjectWrap::handle_' : class 'v8::Persistent<T>' needs
to have dll-interface to be used by clients of class 'node::ObjectWrap' [D:\xam
pp\htdocs\aptana\code\js\nodejs\playground\node_modules\jsdom\node_module
s\contextify\build\contextify.vcxproj]
with
[
T=v8::Object
]
d:\Program Files\Microsoft Visual Studio 10.0\VC\include\xlocale(323): warning
C4530: C++ exception handler used, but unwind semantics are not enabled. Specif
y /EHsc [D:\xampp\htdocs\aptana\code\js\nodejs\playground\node_modules\js
dom\node_modules\contextify\build\contextify.vcxproj]
C:\Program Files\MSBuild\Microsoft.Cpp\v4.0\Microsoft.CppBuild.targets(990,5):
warning MSB8012: TargetPath(D:\xampp\htdocs\aptana\code\js\nodejs\playgro
und\node_modules\jsdom\node_modules\contextify\build\Release\contextify.dll) do
es not match the Linker's OutputFile property value (D:\xampp\htdocs\aptana\swo
rd-code\js\nodejs\playground\node_modules\jsdom\node_modules\contextify\build\R
elease\contextify.node). This may cause your project to build incorrectly. To c
orrect this, please make sure that $(OutDir), $(TargetName) and $(TargetExt) pr
operty values match the value specified in %(Link.OutputFile). [D:\xampp\htdocs
\aptana\code\js\nodejs\playground\node_modules\jsdom\node_modules\context
ify\build\contextify.vcxproj]
C:\Program Files\MSBuild\Microsoft.Cpp\v4.0\Microsoft.CppBuild.targets(991,5):
warning MSB8012: TargetExt(.dll) does not match the Linker's OutputFile propert
y value (.node). This may cause your project to build incorrectly. To correct t
his, please make sure that $(OutDir), $(TargetName) and $(TargetExt) property v
alues match the value specified in %(Link.OutputFile). [D:\xampp\htdocs\aptana\
code\js\nodejs\playground\node_modules\jsdom\node_modules\contextify\buil
d\contextify.vcxproj]
Creating library D:\xampp\htdocs\aptana\code\js\nodejs\playground\no
de_modules\jsdom\node_modules\contextify\build\Release\contextify.lib and obj
ect D:\xampp\htdocs\aptana\code\js\nodejs\playground\node_modules\jsdom
\node_modules\contextify\build\Release\contextify.exp
Generating code
Finished generating code
contextify.vcxproj -> D:\xampp\htdocs\aptana\code\js\nodejs\playground\
node_modules\jsdom\node_modules\contextify\build\Release\contextify.dll
info done ok
jsdom@0.2.14 ./node_modules/jsdom
├── cssom@0.2.3
├── request@2.9.202
├── htmlparser@1.7.6
└── contextify@0.1.2 (bindings@0.3.0) |
虽然有几个警告,不过看到 info done ok,说明安装完成了。至此绕了一个大圈终于装完 jsdom。其实也是后来才发现的,自己没仔细看 node-gyp 的说明,人家写的清清楚楚的:
- Python (v2.7.2 recommended, v3.x.x not yet supported)
- Microsoft Visual C++ (Express version works well)
所以以后万不可盲目,说明先看看清楚,走别人的弯路,让别人无路可走~
特修斯之车
标题杜撰自特修斯之船(Ship of Theseus),一个著名的古老的哲学问题。wiki上这么描述:
Plutarch thus questions whether the ship would remain the same if it were entirely replaced, piece by piece. Centuries later, the philosopher Thomas Hobbes introduced a further puzzle, wondering: what would happen if the original planks were gathered up after they were replaced, and used to build a second ship. Which ship, if either, is the original Ship of Theseus?
在国内,也随着诸如十个著名的悖论之类的文章流传甚广:
一艘可以在海上航行几百年的船,归功于不间断的维修和替换部件。只要一块木板腐烂了,它就会被替换掉,以此类推,直到所有的功能部件都不是最开始的那些了。问题是,最终产生的这艘船是否还是原来的那艘特修斯之船,还是一艘完全不同的船?如果不是原来的船,那么在什么时候它不再是原来的船了?哲学家Thomas Hobbes后来对此进来了延伸,如果用特修斯之船上取下来的老部件来重新建造一艘新的船,那么两艘船中哪艘才是真正的特修斯之船?
还在大学时代其实就对这类哲学问题非常着迷,不过后来现实生活的冲击里渐渐觉得哲学,其实还是适合做老年人的玩物。这个特修斯之船的问题从当年听到后就一直没有想通,直到自己遇到这样的事——十多年的老永久自行车,一辆伴着我从初中直到大学毕业的自行车,在经历岁月洗礼后,被大修了数次——现在它是否还是我的爱车?如果不是,从何时开始的?
这辆老车十几年来已经换过数次零件,但因为使用频繁而粗暴,各个部件又几乎都快损耗殆尽——脚踏板碎了,钢圈弯了,座椅破了,刹车失灵了…也许是到该换车的时候了,我在晚上搜索了数晚,在数个等级的自行车里徘徊——吉安特?不,还是美利达勇士吧…不,公爵更好?不,太贵了,还是继续国产永久吧…最后在自己折磨自己数日后,修车的年头居然又占了上风。
于是我又想起了特修斯之船,并急于得出一个结论,这辆老车还是不是它自己?我的天,我干嘛这么蛋疼!
但很快,我就找到了答案。
当我摆弄着这辆老车的数个苍老的元件的时候,各种记忆就像泉水一样从心头涌出来。十几年风里来雨里去,忍受我暴力的骑行,每次我都把它重新修好,也许不知不觉间,它的诸多零件早已经不再是原配,但陪伴在我身边的这十几年,无疑是它存在的证明。即使所有的零件都面目全非,但总有一些东西被保留,被继承,至少其名存于认识它的人的记忆里。
十几年后,甚至是几十年后,朋友相聚,也许大家的变化都很大。然而,船依旧是船,车依旧是车,人也依旧是人。那个你心里的人,也依旧还是她。
淘宝买回新的座椅和踏脚,准备了机油和工具,修车!在我心里,它理所当然要比那些光鲜亮丽的新车好100倍!
Chrome的alert与重复focus
前段时间,在写一个placeholder插件的时候,遇到了一个Chrome下的诡异问题,一段简单的代码,就可以使得浏览器弹出无限循环的 alert 提示框。
我用的代码片段精简到最后是这样的:
1 2 3 4 5 6 | <input id="test" placeholder="placeholder" type="text" />
<script type="text/javascript">
document.getElementById('test').onfocus = function(){
alert('focus!');
}
</script> |
可能是一时图方便,没log而是用lalert。结果当点掉alert提示框,Chrome会重复 focus 事件,而其他浏览器则不会。
我用的是 Chrome 18,看起来在 alert被点掉的瞬间,输入框再次获得了一次焦点,而这次的焦点获取同样触发了 focus 事件,再次弹出alert,然后如此循环往复…
好在这种现象只存在于Chrome里,其他浏览器都完全正常,虽然它们在点掉 alert 弹出框后的行为也不尽相同,但是无一会重复触发 focus。
随后我测试了主流浏览器的行为,整理一张简单的表以备参考:
| 浏览器 | 行为 |
|---|---|
| IE8 | 点掉alert后,输入框保留焦点,不重复触发focus |
| Firefox 11 | 点掉alert后,输入框焦点丢失,不重复触发focus |
| Chrome 18 | 点掉alert后,输入框保留焦点,重复触发focus陷入循环 |
| Opera 10.60 | 点掉alert后,输入框保留焦点,不重复触发focus |
| Safari 10.60 | 点掉alert后,输入框保留焦点,不重复触发focus |
可见 IE8,Opera 和 Safari 的表现是一样的,唯独 Firefox 会丢失焦点,还有就是唯独 Chrome 会重复 focus。
葵花,播种生活的希望
认识我的人都知道我有多么喜爱向日葵,以至于网名也与葵花有关。当然也不算是无缘无故喜欢上的,儿时的某些景象会像烙印一样镌刻,葵花在我心里就是这种感觉。当下我又种起了向日葵,只是品种是盆栽,而不是株高数米的庞然大物。
儿时我曾经种过两次向日葵。那时还有自家菜田,能放下很多蔬菜和花草,若非有这样的环境,恐怕一辈子都难接触到真正向日葵的风采。
第一次看到向日葵的时候我根本不认识它,那只是一株路边石缝里我偶然瞥见的寸长小苗。儿时的自己当然什么都不懂,就拿一个小药品装着这株小苗回家赶紧去做作业了。我从来没想过它会长成什么,不过时间流转,换了一个又一个大药瓶,直到药瓶放不下,又装进花瓶,然后又一个又一个的加大尺寸,终于在家里最大花盆都嫌小的时候,我把这株已经半人高的植物下到菜地里去种。
虽然对于一个小孩而言,这仅仅是一种会向着太阳歪脑袋的奇怪植物,不过童年里的残碎记忆经过时间的发酵,早就变得美轮美奂。只是结果并不理想,这株不明植物我只种了人一般高,虽然最后开花结子,知道了它叫向日葵,但可能因为独种或者是营养不良的缘故,种子都是空的。
第二年春天,我跑遍家附近的各个角落,寻找向日葵,最后在别家篱笆边找到两株。这次我直接就下地栽种,请教了爷爷施肥的技巧,每天精心呵护,两株向日葵很快就长的粗壮挺拔。
到了后来,我更是把自养蚕蛾交配后死去的尸体也埋到了这两株葵花的脚下。那以后葵花的长势就更加势不可挡了,顶端伸出围墙,株高接近3米。父辈和邻里都没见过这么高的向日葵,但恐怕当时的我,还无法用语言组织出“你们不知道这下面有多少蚕蛾的冤魂”这样的冷笑话。
每天放学第一件事就是看看自家的向日葵长的怎么样,越看越喜欢,什么时候喜欢上的呢?不清楚。向日葵对我来说已经不再是神奇的小植物——对于一个身高1米4都不到的小屁孩而言,向日葵俨然已经是顶天立地,它守望着太阳的光辉,在夏日的微风里微微摇曳。日出而东,日落向西,迎朝露,披霞光,虽无浓郁花香,却引得蜜蜂飞舞,那光景里溢满安定和希望。
但烙刻在心里的却是几乎相反的景象。
一天早上当我推开房门,看到的景象是这样:明媚的阳光洒向院中,两株葵花被昨夜的狂风暴雨折断成4节,上半截都歪歪斜斜地倚靠在篱笆上。但是花枝却都整个地弯成了U形,两朵花盘依旧执着地向着东日艳阳。
后来两株花都谢了枯了,但在我的心里那种执着却发了芽。那之后我没有再种过向日葵,后来的城市化也让我永远失去了再种葵花的条件。不过数十年后,今天我仍旧向往葵花的坚毅,沉默地微笑地向着阳光。即使在最黑暗的时候,也祈求自己的阳光终有一日会洒满全身的每个角落。
偶然?心血来潮?都有可能,但我又种起了我心爱的向日葵,虽然只是矮株品种,但是看着它们发芽,每天慢慢地在阳光中成长起来,仍然是充满了希望之光——不知不觉地,也渐渐觉得生活本身也当是应该如此的。
种些植物吧,然后看着它们成长,看着它们开花结果,那种喜悦和慰藉,无疑给平静的生活抹上了淡雅的一丝笑意。
调试样式:debug.css
之前写过一篇 开发过程中的调试样式,讲述了著名的37signals团队如何用样式视觉化代码的缺陷。但那只是浅尝辄止地提及了一下,现在我整理了一份相对完整的样式表。
由于是一个工具,主要是以用为主。所以如果你不想看下面的详细解释和说明,你可以直接将下面的链接拖动到书签栏:
这是一个 js 的 bookmarklet,它的作用只是简单地在页面注入一段样式。在把它放到书签栏之后,打开你喜欢的网站,然后点击这个书签,看看发生了什么?:)
RSS可能看不到,你也可以复制下面的代码并保存为一个书签,或者直接复制到浏览器地址栏后回车,效果是一样的:
1 | javascript:(function(){var elm = document.createElement('link');elm.setAttribute('rel','stylesheet');elm.setAttribute('type','text/css');elm.setAttribute('href','http://www.swordair.com/css/debug.css');if(typeof elm != undefined){document.getElementsByTagName('head')[0].appendChild(elm);}})(); |
然后我再开始详细说明这个样式都干了些什么——你自己也可以根据自己需要编写符合自身使用的调试样式,但通常包含我在下面讨论的几个要素。
什么是调试样式?
调试样式就是利用CSS选择器和特殊的样式,视觉化代码缺陷的手段。最常用的样式可能如下所示:
1 2 3 | img:not([width]):not([height]) { border: 2px solid red !important; } |
这段代码将页面里的没有设置高宽的图片全部添加2px的红色边框,使得我们能从页面上一下子看到识别它。这类样式通常包含这几个特点:
- 因为能检查的大部分是属性,所以大量的使用属性选择器
- 有 !important 用于提升特殊性
- 大多通过鲜艳的背影和边框凸显问题元素
前两条没什么可说的,最后一条,在当前的浏览器背景下,边框已经可以有很多种替代方式,并且这些方式更好。调试样式应该尽可能避免与页面样式的冲突,所以用 outline 替代 boder 可以避免调试样式对布局的影响,因为 outline 并不像 border 那样占据空间。而另一个替代的边框标注的方案是 box-shadow 模拟的边框,因为其同样不占据空间。并且,我们调试使用的浏览器都支持CSS3。
也许你已经可以想到,还有哪些问题需要被调试样式标出。根据检验的严格程度,标出的内容也不尽相同,但一个相对严格的样式应该包含下面这些项:
- 行内样式:通常这可以容忍
- 缺失必要属性:典型例子就是
img标签的alt - 属性为空值或常用占位值:典型例子就是
a标签的href - 元素内容为空:很常见,但能避免
- 废弃的属性:应尽量避免
- 废弃的元素:应尽量避免
这个列表自上而下是有顺序的,这样做是基于样式复写的考虑。当然也是个人习惯,我并不喜欢在调试样式里添加太多颜色和样式区分,线型和线粗就已经能很好的反应问题的严重程度。实际使用的时候配合3种颜色足以表示所有的情况。
本来打算把上面列出来的项逐个说明,不过现在觉得那样的话这篇东西就太长了。所以就请直接点击查看最终的代码吧:
我提供了两个版本,前者在实际使用时效果太强,所以轻微修改了一下,两者相比有如下不同:
img没有title非常常见,所以从debug-strict.css里移除了- 不设高宽的
img也很常见,所以单独拿出来用蓝色线标注
完成这个样式表的时候也同时参考了 Eric Myers toolkit,不过那个 toolkit 已经严重过期。比如 u,s,menu 等元素在HTML5中,被赋予了新的使命,已经不再是废弃元素。相反,table 的 summary 等属性反而被新标准废弃。这样的情况还有很多,但毕竟标准还是不断在更新完善中。
这个样式表过期属性和元素参照的是最新版的 HTML5 differences from HTML4,当然还包括了一些这份草案没有列出的项。其实,这份样式表本来就是我翻译这篇文档的附带产物:)~
实际使用分析
前面讲了这么多,无非是纸上谈兵,下面就来点实际截图。
AliExpress.com
正巧老东家AliExpress新首页上线,正好可以拿来做实验品~ 新首页很给力,风格清淡大气,个人感觉混合着亚马逊新版和ebay的味道,这页面一出就给人一种走C一路到底的感觉。
图中,黄色部分是空标签,红色点框是缺少 alt 的图片,蓝色点框则是没有设置高宽的图片——实际上,大部分网站都多少会有一些这种“算不上问题的问题”。但通过注入一段简单的样式,就能很轻易的看出页面的特色。
比如,截图里的旧版,主banner下面只有第二块小banner没有 alt,应该不至于是文案不到位吧,难道亚历克苏斯同学你又调皮了……另外,旧版其它两个缺少 alt 的图片也显而易见了。新版缺少的 alt 则更多,不知是否刻意为之。但撇开微量的行内样式,无论新版还是旧版,整个页面在 debug.css 面前都极为出色。
Taobao.com
惯例看一下淘宝的首页,不过页面截图太长,所以只能放个链接:淘宝截图
看起来相对是比较清爽的。从红色的1px点线可以看出,虽然类目里的样式相同,但有些却是行内样式,多少可以看出增改类目时留下的痕迹。
amazon.com & ebay.com
amazon 和 ebay的结果可能出乎意料,大量的黄色块说明有很多的空标签;隐约的绿色边框表示其使用了废弃的属性——这不难理解,最常见的废弃属性就是 table 的一些老属性,哪里有 table,哪里废弃属性就越可能多。
Baidu & Google
百度和 Google 的对比也是很有趣,特别是 Google,用了如 center 这样的元素(实际上百度在早几年前也一样是用 center 的)。Google是我所知最喜欢用空标签的了,圆角/箭头/阴影等等,我都曾看到其用过空标签来实现过。所以黄成这样其实也在意料之内。
360buy & 51buy & dangdang & amazon
除了亚马逊,其实国内的各B2C都相对比较清爽。当当你的广告还能在多一点吗?我擦,截个图要关你丫多少广告啊!!
总结
从上面的对比来说,没有什么绝对的结果,用什么代码,遗漏了多少东西似乎构不成足够的理由。有趣的是,在debug.css面前,明显是国内的网站比较干净——那并不是说国内的代码水平有多高,但似乎重视程度有差距。
对于动态加载的图片和链接,alt 之类的可能就不重要了,所以单单看看样式的结果也并不尽准确。闲来无事的时候,用这个样式测了下 w3c 的主页,真的几乎可以说无懈可击…
虽然我尽可能保证代码的准确,但毕竟一人为之,有错漏之处,欢迎指出。
最后我想说,好久不写这么长了,好累…果然是老了么…
HTML5相对于HTML4的差异更新浅析
去年我翻译了 HTML5 differences from HTML4,时隔约快一年了,这份工作草案再次更新。而我也开始着手更新自己的翻译稿。
这次的更新量相当大,所以翻译完整更新以及修订需要一些时间,但我会尽力最快地拿出更新稿。今天乘空的时候稍稍对比了一下两者,如果你读过去年5月25日版本,无论是英文原文,或者是我的翻译稿,那么你就可以从下面了解一些当前的最显而易见的改动。
新元素
WHATWG增加了一个 data 元素,用来把内容标注成机器可以读取的值。举个最简单易懂的例子:
I have <data value="8">eight</data> apples.
一目了然,data只是为机读服务的。这里 data 元素的 value 属性是必须出现的,其值代表了 data 元素中的内容的机读格式。
新属性
object 元素多了一个新属性 typemustmatch,使得嵌入外部资源更加安全。
img 元素多了一个新属性 crossorigin,用以在获取过程中使用CORS(Cross-Origin Resource Sharing),并在成功后允许图像数据被 canvas API 读取。
增加了全局属性 translate,用来给翻译器给出翻译提示(哪些需要被翻译,哪些则必须保持原样)
元素变动
当用户代理不支持由 script 元素调用的脚本语言时,noscript 元素不再被渲染。
script 元素现在可以用于脚本或自定义数据块。
大量的其他更新
- 大量的属性变动
- 增加了一整节内容:Content Model Changes
- API章节的大量修订
这部分非常庞大,基本上需要重译。我暂时还没有足够的空闲时间完整看完。但一旦我完成了这部分内容,也会第一时间放出更新稿。谢谢大家的支持:)
再谈box-shadow
早先,我详细地写过2篇关于 box-shadow 的文章: CSS3 box-shadow 详解(1) 以及 CSS3 box-shadow 详解(2)。但用现在的眼光来看,内容已经有些旧了。趁着这片闲言碎语,对 box-shadow 再做一些补充。
box-shadow 是我最喜欢的CSS3特性之一,它能创建凸显层次的阴影,但却还可以充当很多角色:边框/层叠/渐变/勾线,等等。当前浏览器的支持程度已经非常理想,除了IE8及以下,所有浏览器都已经更新到无前缀的支持——这跟一年前的情况很不同,那时,一些浏览器如firefox实现的 box-shadow 还存在占用空间的bug,Webkit类的模糊算法非常僵硬,随着这一年的发展这一些问题都已被克服。如今存在的问题只有两个,部分移动版浏览器的不支持,以及Opera在多阴影高模糊值时的效率低下。但相信这些问题也都能尽早解决。
之前虽然详细讲了 box-shadow 的方方面面,但唯独对IE模拟用其他引用链接一笔带过,现在做一个小补充。众所周知,IE低版本实现 box-shadow 是用滤镜的,这并不是推荐的做法:
1 2 3 4 5 6 7 8 | .box-shadow{ -moz-box-shadow: 3px 3px 4px #000; -webkit-box-shadow: 3px 3px 4px #000; box-shadow: 3px 3px 4px #000; background:#fff; /* for IE filter, required */ -ms-filter: "progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#000000')"; /* For IE 8 */ filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#000000'); /* For IE 5.5 - 7 */ } |
现在这种代码已经显得太碍眼了,对于IE低版本我更倾向于放任不管:
1 | .box-shadow{box-shadow: 3px 3px 4px #000;} |
当然,总有需求需要IE8-有阴影,如果身在需求主导的环境下,借滤镜一用也算是无奈之举。使用滤镜的话,给对应box一个背景颜色是必需的,否则你会在box内部以及字体上同时看到丑陋的阴影效果。