yeren's profile界面精髓BlogLists Tools Help

琳 郭

Occupation
致力于界面及交互设计。
Face!!

界面精髓

About Face
10/13/2006

以人为中心的设计是有害的

作者 Don Norman

阅读本文英文原文 (翻译:张亮,校对:李鱼)

本文是为2005年ACM通信杂志的“交互设计”专栏所写的文章。这里刊登的是作者自己的版本。除了改正了一些排版错误外,此处和那篇正式发表的是一样的。ACM允许文章在这里刊登,但仅限于您个人使用。文章可以被传播,但是只能用于非商业目的,同时本段文字必须被包含。本文的正式版本发表在ACM通信杂志的“交互设计”专栏(2005年七-八月)的14至19页

在设计界,以人为中心的设计已经成为一个占统治地位的主题,以至于它经常被界面和应用设计人员不加思考地加以采用,更不要说是用一种带有批判的眼光加以采用。这是一种危险的状态――当某些事情被当作是被广泛认可的知识来对待时。这篇文章的目的就是要引起人们对于以人为中心设计方法的基本原理的重新思考和讨论。我认为,这些原理可能是有益的,有误导性的,或是是错误的。有时候,它们甚至可能是有害的。以活动为中心的设计是更好的一种方法。

了解你的用户

如果说在用户界面设计或人机交互领域有什么原理被大家极力推崇的话,那就是“了解你的用户”。毕竟,如果对你的用户没有深入而仔细的了解,你怎么能够为他们设计一些东西呢?这世界上的很多糟糕的设计都极好地印证了这一点:忽略设计所为之服务的用户将会是一件很危险的事情。以人为中心的设计是为了克服软件产品中的拙劣设计而发展起来的。通过强调那些将要使用软件的人们的需求和能力,软件产品的可用性和可理解性确实得到了提高。不过尽管有这些进步,软件对于我们来说依然是那么复杂。甚至是那些引以为豪地采用了以人为中心的设计原理的公司仍然还是有复杂的、令人费解的软件。

如果了解产品的特定用户是那么重要的话,那么当一个产品要设计成被世界上几乎每个人都使用时,会发生什么情况呢?确实有很多的设计对于每个人都能工作得很好。这是自相矛盾的,不过正是这个自相矛盾才使得我来重新思考这个普通的设计信念。

这个世界上的大多数东西都是在没有得益于用户研究和以人为中心的设计方法的情况下被设计出来的,不过这些东西仍然工作得很好。不仅如此,这些东西当中还包括了我们当今这个技术化的世界中的一些最成功的产品。考虑以下两有典型意义的例子:

汽车
通过操作差不多是一样的一组控制装置,全世界的人都能学会非常成功地驾驶。(在汽车的设计过程中)没有任何系统的用户研究。实际情况是,早期的汽车设计尝试过不同的控制装置:最开始是完全照搬马车上的座位和驾驶方式的安排,再到类似船舵的控制,然后是各式各样的手和脚的控制装置,直到演化成今天的模样。

日常用品
看看我们周围的物品:厨具,花园里的各种工具,伐木工具,打字机,照相机,还有体育用品。尽管在不同的文化中它们有所差别,但其相似之处还是超过了其不同之处。世界各地的人都能学会使用它们,并且用的很好。

以行为活动为中心的设计

为什么这些物品会工作得那么好呢? 最基本的原因就是,在它们被设计时,这些物品所被用来从事的活动是经过了深入理解的:这就是以活动为中心的设计。有些东西甚至不是按照这个词的通常意义所描述的方式来进行设计的,而是以一种随时间演进的方式。每一代的设计人员都根据他自己和用户的使用经验中得到的反馈,缓慢地对上一代的产品进行改进。这是一种缓慢的、以演进方式进行的民间设计方式。另外一些产品,它们是通过一个由冠以“设计师”工作头衔的人们构成的正式工作团队创造出来的,但即便在这种情况下,仍然是这些设计师们根据他们自己对于将要进行的活动的理解,来决定设备将会被如何操作。用户被假定能够理解任务,并且能够理解设计师的意图。

活动不等同于任务

请注意这里强调的是“活动”这个词,而不是“任务”,他们之间有些微妙的区别。我是在一种层次化的意义上来使用这些词的。最高一层的概念是活动,活动由任务构成,任务由动作构成,而动作则由一些具体的操作构成。这种层次结构来源于我自己的一套“活动理论”,该理论在很大程度上受到早期俄罗斯和斯堪迪那维亚的研究成果的启发。在我看来,一个行为活动是一组完整的、经过协调的任务。例如,手机,这种集成了约会安排、日记、日历、便笺、短信息和照相机的设备,能够很好地支持人们进行沟通和交流这种行为活动。这种单一的设备集成了很多任务:查号码、拨打电话、谈话、记笔记、查看日记或日历、交换照片、发短信息和电子邮件。在这里,一个活动包含了多个任务。

谁应该适应谁?人去适应技术,还是技术适应人

历史上有很多的例子表明,一个设计成功的物品也需要人去适应并学会如何使用。人们需要对要从事的活动有一个很好的理解,同时也需要对技术上的操作方法很好掌握。这些工具当中没有一个是“工具适应人”――这是在说瞎话,应当是人去适应工具。

考虑上面提到的最后一点,以人为中心的设计的一个基本推论就是技术应当适应人,而不是人去适应技术,真的是这样吗?考虑下面这些历史上成功的技术。

钟表(和手表)

将时间分成年、月、日、星期和时、分、秒的这种任意的分法完全是根据某种物理的原理,它和心理、生物原理是不同的,但这种分割方法却主宰了我们的生活。我们吃饭是当手表告诉我们现在是吃饭的时间了,而不是当我们感觉到饥饿时。我们早上醒来是根据闹钟那刺耳的叫声来决定的,而不是在我们完全休息好之后。在大学里,每个班每次上一小时的课,每周三次,每个学期10到15周,这样的安排不是因为这样做对教育有好处,而是因为这样安排起来容易一些。对于时间的极度依赖是工厂的兴起以及由此产生的技术化社会的一个意外的结果。

书写系统

考虑一下印刷、手写和打字,都非常不自然,需要人们花几个星期,几个月,甚至几年的时间来学习和变得熟练。一个成功的基于触笔的罗马字母输入设备是Palm的Grafiti――而它也是一种不自然的书写方式。

乐器

乐器很复杂,很难操作,并且能引起严重的疾病,乐谱是形式化的:同样一个标记,在高音部和低音部出现时有不同的解释。可用性专业认识到乐谱的模式问题已经很长时间了,但大量的乐谱已经存在了大约一千年的时间了。一个人需要经过大量的指导和练习后,才能熟练地读谱和演奏。音乐家们面临的疾病问题非常严重,以至于有专门的关于这方面的书籍、医生、网页和讨论组。例如,肌腱疾病(Repetitive Stress Injuries)在小提琴和钢琴家中很常见。乐器和乐谱都无法通过任何以人为中心的设计评审.

以人为中心和以活动为中心,区别在哪里?

那么到底发生了什么?为什么不以人为中心的设计会那么成功呢?我认为有两个原因,第一是以活动为中心的本质,第二是来自物品的构造者和设计者的对于其设计目的的交流。设计成功的物品能够完美地融入到所进行的活动的需求中,并以一种人们能够理解的方式来支持这种活动。如果你理解所要进行的活动,那么这种设备就是可以理解的。构造者和设计者对于他们所构建的系统的工作方式经常有自己很好的原因。如果这些原因能被解释出来的话,那么学习这个系统就将变得不仅是容易了,而且令人觉得是合乎情理的。的确,学习演奏小提琴需要花很多年的时间,但人们接受了这一点,这是因为这种乐器自身相当精妙地展示出琴弦和它产生的声音之间的关系。这项活动和设计都是可以理解的,尽管人们必须扭曲自己的身体来抓住,拨弄它,向它致意。

实际上,以活动为中心的设计(ACD)很像以人为中心的设计(HCD)。以人为中心的设计的最好的一些特点都被传承了下来,但这两者还是有一些不同。首先,也是最重要的,是关于态度的问题。态度?没错,是关于设计者的思想。

活动,说到底,是人类的活动,因此,它反应了一个可能的范围内的动作,一些人们进行活动所依赖的条件,以及人自身的一些限制。关于人本身的深入了解仍是以活动为中心的设计的一部分,但它还有更多的内容:它也需要深入了解技术、工具以及进行这种活动的原因。

工具决定活动:人们确实是在适应技术

以人为中心的设计坚持的一个基本信念是由技术来适应人。而在以活动为中心的设计中,我们承认很多人类的行为可以被认为是对技术能力和限制的一种适应。这包括所有的方面,从我们睡觉的时间,到我们穿衣、吃饭、和别人交往、旅行、学习、交流、游戏以及放松的方式。不仅仅是我们做这些事情的方式,还包括和谁来做,在什么时间做,以及做这些事情时的应采取的方式,所有这些都在不同程度上需要风俗,习惯和传统的约束。

人们确实是在适应技术。技术改变了社会和家庭结构。它改变了我们的生活。以活动为中心的设计不仅了解这一点,而且还可能很好地利用了这一点。

首先学习如何进行活动,然后工具就会自然而然地被理解了,这是以人为中心的设计团体所推崇的。但实际上,这是一个具有误导性的说法,因为对于很多活动来说,是工具决定了活动。所以实际情况可能恰好相反:先学习工具,然后你就会理解所要进行的活动了。

学习艺术的时候,很多时间都会花在学习用以表达艺术思想的介质的特点上。如果你想画油画,你需要了解油彩,画笔,画布――甚至包括什么时间以及如何清洗画笔。这里的工具是不是有些喧宾夺主。没错,但它从来就是这样,并且将来也会是这样。真正优秀的艺术家对他们的工具和技术都有深刻而透彻的理解,仅有艺术家的感觉是不够的。体育、烹饪、音乐以及所有其它使用工具的活动都是如此。

对于以人为中心的设计团体来说,工具应该是看不见的,它不应该成为一种防碍。而对于以活动为中心的设计来说,工具恰恰就是要达到目的的途径。

为什么以人为中心的设计可能是有害的?

为什么以人为中心的方法中可能是有害的呢?毕竟,它的出现和发展正是源于人们在设计中遇到的问题,这些问题导致了用户在使用产品时失败、伤心,浪费时间和精力;而在一些安全性至关重要的应用中,这些问题甚至导致了错误、事故和死亡。不仅如此,以人为中心的设计还表现出它能带来的明显的好处:更好的可用性,使用中更少的错误,更短的学习时间。那么我们对以人为中心的设计的担忧是什么呢?

一个担忧是,对于某个人(或一个群体)的关注也许能为这些人改进产品,但这是以牺牲其他人为代价的,一个东西越是为了某个特定的喜好、厌恶、技巧和某个特定的目标人群而进行特别设计,它就越不可能适合其他人。

个体是个总在移动中的目标。为了今天的个体而进行的设计可能在明天就是错误的。实际上,产品越成功,它就越不可能在将来也适用。这是因为随着个体在使用中变得越来越熟练,他们将会需要和他们还是初学者时不同的界面。除此之外,成功的产品还经常会带来一些未曾料到的新的使用方法,而这些方法往往不能被原来的设计很好地支持。

不过还有更严重 的担忧:首先,对于人本身的关注会影响对于活动自身的支持;第二,对于用户需求的过度关注会导致产品缺乏内聚力,从而增加设计的复杂性。考虑一下应用的动态本质,每个任务需要一系列的操作,而活动是由多个重叠的任务构成的。在这里,两种设计方法在关注点上的区别变得明显了,过分关注用户的的缺点也就显现出来了 。

静态界面和动态序列

“我们发现,厨房里的工作不是由一些独立的行动构成的,而是由一系列相互关联的过程构成的。(Christine Frederick, 省力的厨房。 1919)”

以人为中心的设计方法似乎是围绕着对于每组控制和每个电子显示屏上的画面的静态理解来进行的。但这样做的结果就是对于活动中的一系列操作不能很好地支持。从二十世纪早期的关于时间和运动之间关系(time-and-motion studies)的研究到现在,人们了解要支持活动序列这一点的重要性已经很长时间了。正如上面所引用的Frederick 的论述 表明的那样。如果把“在厨房里”这个词去掉,她的论述仍旧是一个有力的设计指导。Frederick是在1919年写下的那段话,而在过去的一百年里是什么使我们忘记了这些呢?需要指出的是,支持序列操作的重要性今天仍然为工业工程学、人类因素学和人体工程学的设计团体深刻理解。但不知为什么,它好像在人机交互设计团体中不是那么流行。

很多通过了以人为中心的设计阶段和可用性评审的系统能够在静态的、单独的显示中表现得很好,但都不能支持任务和活动的序列性需求。以人为中心的设计有忽略这种行为的倾向,而以活动为中心的方法则很关注这一点。

过多地倾听用户

以人为中心的设计的一个基本思想就是倾听用户,认真对待他们的投诉和批评。的确,倾听用户永远是明智的,但屈从于用户的要求会导致过于复杂的设计。一些引以为荣地采用了以人为中心的设计思想的大软件公司也遇到了这样的问题。随着每一次的更新,他们的软件变得越来越复杂,越来越难以理解。以活动为中心的设计有助于防止这种错误的发生,这是因为它关注的是活动,不是人本身。这样做的结果就是有一个连贯并且能被清晰表达的设计模型。如果一个用户的建议不能很好地适合这个设计模型,它将不会被考虑。只可惜有太多的公司,由于得意于倾听用户需求,还是会把这样的建议考虑进来。

这里需要的是一个强有力的权威设计者,他能够仔细地审查用户的建议,并且是从是否满足活动的需求这个角度来评估它们。如果必要,能够忽略这些要求是很重要的。这就是设计要达到的内聚性和可理解性的目标。在这里,令人觉得自相矛盾的是,最好的让用户满意的方法反而是在某些时候忽略他们。

需要说明的是,这个思想也适用于服务行业。通过这种方式,尽管忽略了两个反映最多的旅客投诉:要求提供保留座位和跨航空公司转运行李的服务,西南航空公司仍然很成功。该公司认定它的主要的战略优势是便宜并且可靠的运输,而这需要一个快速的在各个目的的转场时间。尽管乘客在抱怨,他们仍然倾向于选择该航空公司。

有些时候需要的是这样的一个设计决策者,他能够说“不要理睬用户说的,我知道什么对他们最好”。苹果电脑的例子很有说明性。长久以来,苹果公司的产品就由于容易使用备受推崇。但是,苹果公司却把它的著名的受人尊重的人机界面设计团队换成了一个权威性(有些独裁性质的)的领导。可用性遭到破坏了吗?事实恰恰相反,它的新产品被认为是伟大设计的典范。

“倾听你的用户”会带来不连贯的设计。但除非设计负责人对于产品有着清晰的洞察力,也就是我所谓的“概念模型”,“忽略用户”将会导致可怕的后果。产品负责人必须坚持该想法,同时不能害怕忽略其它发现。没错,倾听客户,但不要总是按他们说的做。

现在考虑一下以人为中心的设计团体采用的方法。该方法的重点通常是人而不是活动。看看那些详细的情节描述和人物角色:现在说老实话,这些方法真的能对你的设计有什么作用吗?仅仅知道虚构的人物是一个晚上读工商管理硕士的37岁的单身母亲能够帮助你排列控制面板或者屏幕的布局吗?更重要的是,它能帮助你设计合适的行动序列吗?正式或非正式的用户模型能够帮你决定应该采用什么技术吗?

请给我举一个主要技术的例子,它是根据以人为中心的设计原理研究出来的,或者是快速原型加测试,或者是根据用户模型,或者是通过让技术来适应用户的方法。请注意“主要”这个词。我确信有很多项目是通过使用这些技术得以改进的,甚至是很大的改进。但是请说出一个通过使用这些方法而得到的一个根本性的、主要的技术进步。

以人为中心的设计的确能保证好的产品。它能带来相对于不良设计的明确的改进。进一步讲,好的以人为中心的设计能避免失败。它能保证产品可以工作,人们能够使用。但好的设计就是我们的目标吗? 我们很多人希望的是伟大的设计。我要说的是,伟大的设计来自能够打破陈规,忽略那些被大众所接受的做法,来自根据一个清晰的最终结果目标并且奋勇向前,不顾其它。这种以自我为中心的、以对产品的洞察力为导向的设计方法有可能带来巨大的成功,也有可能带来巨大的失败。如果你想要的是伟大的设计而不仅仅是好的设计,这就是你必须要做的。

关于这个话题还有很多可以讨论的。我的这些言论就其本身而言可能是危险的。我们不敢让全世界的设计者都去听从他们本能的想法而忽略传统知识:这是因为他们当中的大部分缺乏对于活动的深刻理解以及一个清晰的概念模型。还有,这世界上肯定还有很多不良设计的例子可以来反驳我的立场。但请注意,很多那些不良设计都是很赚钱的产品,这又能表明什么呢?想想看,如果采用了以人为中心的设计原理,它们是不是会变得更赚钱呢?也许。但也许它们可能根本不存在。

的确,我们都明白,如果缺乏对人和系统的理解,任何将计算机系统引入一个企业的尝试都将带来灾难性的失败。但我们是否也可以说这是由于不理解人们的活动而引起的呢?也许所需要的是更加的以活动为中心的设计,也许失败是由于对于所要支持的活动只有一个肤浅的理解。还需要指出的是,在那些安全性至关重要的应用中,深刻理解用户的活动是很重要的。安全性通常是一个很复杂的系统问题,如果对于所有相关的情况没有一个深刻理解,最终的设计很容易有缺陷。

我仍然认为现在是重新思考一下我们所依赖的基本假设的时候了。关注人本身可能是有误导性的。关注活动而不是关注人可能会带来好处。更进一步,用以活动为中心的设计来代替以人为中心的设计并不意味着抛弃所有我们已经学到的东西。活动都是和人相关的,所以那些支持活动的系统必然也能很好地支持从事这些活动的人,我们仍旧可以利用我们先前得到的知识和经验,这既包括以人为中心的设计领域,也包括工业工程学和人机工程学领域。

所有的领域都有一些基本假设前提。有时候,我们有必要来重新考量它们,重新思考它们的长处和短处,看看它们是否应该被修正或者甚至被取代。这一点是不是适用于那些对于以人为中心的设计很感兴趣的人呢?如果我们不这样做,我们就永远不会知道答案。

Don Norman有很多头衔,包括Nielse Norman集团的合伙创始人,西北大学的教授以及作家,他的新书是《情感化设计》。他的主页是www.jnd.org

转载:On having layout

转载:On having layout

On having layout

  译者注:一篇很好的文章,很久以前在blog上就推荐过,这两天断断续续花了点时间翻译了一下,推荐读读。英文原文在此

  文中所有的 layout 这个单词都未作翻译,一来本身这个单词意思就比较多,翻成啥都觉得别扭,二来它也是专有的属性,所以就意会一下吧。水平有限,很多地方都是模模糊糊地意译,发现错误欢迎留言指出。

  引用一段来自Dean Edwards的评价:

  I recommend that every CSS designer and DOM scripter read this. Understanding “layout” gives a huge insight into lots of other IE bugs and idiosyncrasies.

(Dean Edwards)

介绍

  Internet Explorer 中有很多奇怪的渲染问题可以通过赋予其“layout”得到解决。John Gallant 和 Holly Bergevin 把这些问题归类为“尺寸bug(dimensional bugs)”,意思是这些 bug 可以通过赋予相应元素某个宽度或高度解决。这便引出关于“layout”的一个问题:为什么它会改变元素的渲染特性,为什么它会影响到元素之间的关系?这个问题问得很好,但却很难回答。在这篇文章中,我们专注于这个复杂问题会有那些方面的表现,某一方面的具体讨论和范例请参考文中给出的相关链接。

hasLayout — 定义

  “Layout”是一个 IE/Win 的私有概念,它决定了一个元素如何显示以及约束其包含的内容、如何与其他元素交互和建立联系、如何响应和传递应用程序事件/用户事件等,这有点类似于一个窗体的概念。

  微软的开发者们认为盒状元素(box-type elements)应该具有一个“属性(property)”(这是面向对象编程中的一个概念),于是他们便使用了 layout , 也就是 hasLayout

  hasLayout 其实既不是一个属性更不是一个行为,而是 IE 这个渲染引擎代代继承一贯拥有的一个渲染概念,在这个概念下渲染的元素将具有一种特性。

  实际上这种渲染特性在有些 HTML 元素中与身俱来,而在另外一些元素中也可以通过一些 CSS 属性将其触发为 true ,且一旦触发将不可逆转。

术语

  当我们说一个元素“拥有layout”或“得到layout”,或者说一个元素“has layout” 的时候,我们的意思是指它的微软专有属性 hasLayout 被设为了 true 。一个“layout元素”可以是一个默认就拥有 layout 的元素或者是一个通过设置某些 CSS 属性得到 layout 的元素。

  而“无layout元素”,是指 hasLayout 未被触发的元素,比如一个未设定宽高尺寸的干净 div 元素就可以做为一个 “无layout祖先”。

  给一个默认没有 layout 的元素赋予 layout 的方法包括设置可触发 hasLayout = true 的 CSS 属性。参考默认 layout 元素以及这些属性列表。没有办法设置 hasLayout = false , 除非把一开始那些触发 hasLayout = true 的 CSS 属性去除。

问题种种

  hasLayout 的问题不管新手还是老手,不管设计师或者程序员可能都遇到过。具有 layout 的元素通常有着不同寻常而且难以预料的的显示效果,而且有时甚至会牵连到他们的孩子元素。

  一个元素是否具有“layout”可能会引发如下的一些问题:

  • IE 很多常见的浮动 bug 。
  • 元素本身对一些基本属性的异常处理问题。
  • 容器和其子孙之间的边距重叠(margin collapsing)问题。
  • 使用列表时遇到的诸多问题。
  • 背景图像的定位偏差问题。
  • 使用脚本时遇到的浏览器之间处理不一致的问题。

  上面的列表只是列出一个大概,也不完善。下面的文章将尽可能详细彻底的描述有无“layout”所带来的各种问题。

Layout 从何而来

  不同于标准属性,也不像某些浏览器的私有 CSS 属性,layout 无法通过某一个 CSS 声明直接设定 。也就是说没有“layout属性”这么一个东西,元素要么本身自动拥有 layout,要么借助一些 CSS 声明悄悄地获得 layout。

默认layout元素

  下列元素应该是默认具有 layout 的:

  • <html>, <body>
  • <table>, <tr>, <th>, <td>
  • <img>
  • <hr>
  • <input>, <select>, <textarea>, <button>
  • <iframe>, <embed>, <object>, <applet>
  • <marquee>
属性

  下列 CSS 属性和取值将会让一个元素获得 layout:

position: absolute
绝对定位元素的包含区块(containing block)就会经常在这一方面出问题。
float: left|right
由于 layout 元素的特性,浮动模型会有很多怪异的表现。
display: inline-block
当一个内联级别的元素需要 layout 的时候往往就要用到它,这也可能也是这个 CSS 属性的唯一效果——让某个元素拥有 layout。“inline-block行为”在IE中是可以实现的,但是非常与众不同: IE/Win: inline-block and hasLayout
width: 任意值
很多人遇到 layout 相关问题发生时,一般都会先尝试用这个来修复。
height: 任意值
height: 1% 就在 Holly Hack 中用到。
zoom: 任意值 (MSDN)
MS专有属性,无法通过校验。 不过 zoom: 1 可以临时用做调试。
writing-mode: tb-rl (MSDN)
MS专有属性,无法通过校验。

  在 IE7 中,overflow 也变成了一个 layout 触发器:

overflow: hidden|scroll|auto
这个属性在之前版本 IE 中没有触发 layout 的功能。
overflow-x|-y: hidden|scroll|auto
overflow-x 和 overflow-y 是 CSS3 盒模型中的属性,尚未得到浏览器的广泛支持。他们在之前版本IE中没有触发 layout 的功能。

  另外 IE7 的荧幕上又新添了几个 haslayout 的演员,如果只从 hasLayout 这个方面考虑,min/max 和 width/height 的表现类似,position 的 fixed 和 absolute 也是一模一样。

position: fixed
./.
min-width: 任意值
就算设为0也可以让该元素获得 layout。
max-width: 除 none 之外的任意值
./.
min-height: 任意值
即使设为0也可以让该元素的 haslayout=true
max-height: 除 none 之外的任意值
./.

  以上结论借助 IE Developer Toobar 以及预先测试得出。

有关内联级别元素

  对于内联元素(可以是默认即为内联的比如 span 元素,也可以是 display: inline 的元素)

  • widthheight 只在 IE5.x 下和 IE6 的 quirks 模式下触发 hasLayout 。因为在 IE6 中,如果浏览器运行于标准兼容模式下,内联元素会忽略 width 或 height 属性,所以设置 width 或 height 不能在此种情况下令该元素具有 layout。
  • zoom 总是可以触发 hasLayout,但是在 IE5.0 中不支持。

  具有“layout” 的元素如果同时也 display: inline ,那么它的行为就和标准中所说的 inline-block 很类似了:在段落中和普通文字一样在水平方向和连续排列,受 vertical-align 影响,并且大小可以根据内容自适应调整。这也可以解释为什么单单在 IE/Win 中内联元素可以包含块级元素而少出问题,因为在别的浏览器中 display: inline 就是内联,不像 IE/Win 一旦内联元素拥有 layout 还会变成 inline-block。

脚本属性 hasLayout

  我们这里称 hasLayout 为“脚本属性”是为了和我们熟知的 CSS 属性相区别。

  注意一旦一个元素拥有了 layout,就没有办法再将其设成 hasLayout = False 了。

  hasLayout-property 可以用来检测一个元素是否拥有 layout:举个例子,如果它的 id 是“eid”,那么只要在 IE5.5+ 的地址栏里输入 javascript: alert(eid.currentStyle.hasLayout) 即可检测它的状态。

  IE的 Developer Toolbar 可以实时检查一个元素的当前样式;如果 hasLayout 是 true ,那么它的值显示为 “-1”。 我们可以通过实时修改一个元素的属性将“zoom(css)”设置为“1”来触发 hasLayout 以便调试。

  另外一个需要注意的是“layout”会影响脚本编程。如果一个元素没有“layout”,那么clientWidth/clientHeight 总是返回0。这会让一些脚本新手感到困惑,而且这和 Mozilla 浏览器的处理方式也不一样。不过我们可以利用这一点在 IE5.0 中检测“layout”:如果 clientWidth 是零那么这个元素就没有 layout。

CSS hacks

  下面用于触发 haslayout 的 hack 已经经过 IE6 及以下版本测试。今后版本的IE有可能会对此做不同处理。如果新版本浏览器发布我们会重新整理这部分内容。

  John Gallant 和 Holly Bergevin 在2003年发布的 Holly hack

/* \*/
* html .gainlayout { height: 1%; }
/* */	
  • 可以让 IE5+ 的任意元素获得 layout,除了标准兼容模式 IE6 中的内联元素。
  • 一般都很有效,除了在某些极少情况下,需要用 height:0 或者 1px 更好一些。
  • overflow: hidden 不相容,除非在 IE6 的标注兼容模式下(因为这时如果父元素没有定高,那么height: 1% 会被变回 height: auto)。

  或者我们可以用 underscore hack:

.gainlayout { _height: 0; }

  另外,更具有向后兼容性的方法是使用 条件注释(conditional comments):

<!--[if lte IE 6]>
<style>
.gainlayout { height: 1px; }
</style>
<![endif]-->

  在条件注释中链接一个专门对 IE/Win 做修正的外部样式表文件,也不失为一个安全有效的好方法:

<link rel="stylesheet" href="allbrowsers.css" type="text/css" />
	
<!--[if lte IE 6]>
<link rel="stylesheet" href="iefix.css" type="text/css" />
<![endif]-->

  我们更倾向于使用 height: 01px —— 并主张始终使用 height 除非它和别的什么东西冲突 (overflow: hidden)。对于取值,我们则倾向于避免 1% ,因为它可能会(虽然很少)引起一些问题

  一个需要注意的情况是如果我们希望一个元素保持内联,那么就不能使用 height 了,这时可以用 display: inline-block 。我们只在早期调试阶段用 zoom: 1 来避免一些渲染错误。

  我们曾看过一些把 Holly hack 真的当作 holy(神圣的) hack 盲目使用的情况,比如对浮动元素使用或者对已经具有特定宽度的元素也使用这个 hack。要记住这个 hack 的目的不是要给某个元素加一个高度,而只是要触发 hasLayout = True 而已。

  不要给所有元素设置 layout:* {_height: 1px;}。所谓过犹不及,获得 layout 不等于获得灵丹妙药,它只是用来改变渲染模式。

Hack整理

  但是浏览器总是会变的,我们需要面对很多问题,比如一些依赖 IE6 的 bug 所做的 hack 会在 IE7 或更高版本的新浏览器中因 bug 修复而失效(甚至有害)的问题;比如新版本浏览器中类似的布局 bug 依然存在但用于 hack 的过滤器比如 * html 却不能正常工作的问题。这种情况下,MS专有属性 zoom 就可以考虑使用了。

<!--[if lt IE 7]><style>
/* IE 6 + IE5.5 + IE5.0 所用样式*/
.gainlayout { height: 0; }
</style><![endif]-->
	
<!--[if IE 7]><style>
.gainlayout { zoom: 1;}
/* 或者其他任何以后可能需要的东西 */
</style><![endif]-->
  • zoom: 1; 可以让 IE5.5+ 的任何元素(包括内联元素)获得 layout,但是在 IE5.0 中无效。
  • 没有其他附带效果(内联元素会变成 inline-block,这个当然)。
  • 如果需要通过验证,应该用条件注释将 zoom 隐藏起来。

  其实当我们考虑到“向后兼容”时是很自相矛盾的,我们强烈建议页面设计者回过头看一下自己页面中用的到的明显的或是不明显的“hacks”,并用条件注释针对不同浏览器重新处理以保万无一失。

关于IE Mac 的小问题

  IE Mac 和 windows 下的 IE 是完全不同的两个东西,它们各自拥有自己的渲染引擎,IE Mac 就全然不知“hasLayout”(或contenteditable)所谓何物。相比之下 IE Mac 的渲染引擎要更标准兼容一点,比如 height 就是被当作 height 处理,没有别的效果。因此针对“hasLayout”的 hacks 和别的解决方法(特别是通过使用 heightwidth 属性的)往往对 IE Mac 来说是有害的,所以需要对其隐藏。更多的关于 IE Mac 相关的问题可以在 IE Mac, bugs and oddities pages 找到。

MSDN 文档

  MSDN 中涉及到 hasLayout 这个 MS 属性的地方寥寥无几,而具体解释 layout 和 IE 渲染模型之间关系的则少之又少。

  在IE4的时候,除了未经绝对定位也未指定宽高的内联元素,几乎所有元素都有某种 layout(MSDN)。在这种早期的layout概念中,像 border, margin, padding 这些属性被称作“layout属性”,它们是不能应用到一个简单的内联元素上的。换句话说,“拥有layout”就可以粗略理解成:“可以拥有这几个属性”。

  MSDN 上仍然使用 layout 属性这种说法, 只是含义变了,它们和拥有 layout 的元素已经没有什么关系了。在 IE5.5 中方才引入了 MS 的这个专有属性 hasLayout,也只是某种内部的标志位而已。

  在 IE5.5 中,MSHTML Editing Platform(即可以通过设置<body contenteditable=true>来允许用户实时编辑、拖动 layout 元素以及调整其尺寸等)的文档中描述了三个和 layout 相关的重要特性:

  如果一个 layout 元素中有内容,内容的排版布局将由它的边界矩形框决定。

  拥有 layout 的意思基本上就是表示某元素是一个矩形。

  从内部来说,拥有 layout 意思就是一个元素将自己负责绘制其内部内容。

(Editing Platform)

  和 layout 自身相关的内部工作机制直到2005年8月才有相应文档描述,当时由于 The Web Standards Project 和微软的特别工作小组的原因,Markus Mielke [MSFT] 打开了深入讨论的大门:

  一般来说,在 Internet Explorer 的 DHTML 引擎中,元素是不对自己的位置安排负责的。虽然一个 div 或者一个 p 元素都在源码中有一个位置,在文档流有一个位置,但是它们的内容却是由它们最近的一个 layout 祖先(经常是 body)控制安排的。这些元素依赖它们祖先的 layout 来为他们处理诸如决定大小尺寸和测量信息等诸多繁重的工作。

(HasLayout概述)

分析

  我们的分析试图解释在已知案例下发生了什么事情,这种分析也应该可以作为未知案例下的指导。但我们这种试图利用种种测试案例投石探路的黑箱测试方法,是注定无法消除黑箱的神秘感的——我们无法回答“为什么”的问题。我们只能去尝试了解整个“hasLayout”模式的工作框架,以及它会怎样影响网页文档的渲染。因此,最终我们只能提供一些指导方针(而且只能是指导方针,而不是绝对的解决方案)。

  我们认为他们所指的是一个小窗体。一个 layout 元素的内部内容是完全独立的,而且也无法影响其边界外的任何内容。

  而 MS 属性 layout 只是某种标志位:一旦它被设定,这个元素就会拥有其特殊的 layout“特性”,这包括体现在其自身以及其非 layout 孩子身上的浮动、清除浮动、层叠、计数等等诸多方面的特殊性能。

  这种独立性也许正可以解释为什么 layout 元素通常比较稳定,而且它们可以让某些 bug 消失。这种情况的代价有二,一是偏离了标准,二是它没有考虑到今后可能因此出现的 bug 和问题。

  MS 的“页面”模式,从符号学角度考虑,可以看做是由很多互不相关的小的区块构成,而 HTML 和 W3C 的模式则认为“页面”模式应该是叙述完备的,故事性的相关信息区块构成的。

各种情况的详细说明

清除浮动和自动扩展适应高度

  浮动元素会被 layou 元素自动包含。这是很多新手经常遇到的问题:在 IE 下完成的页面到了标准兼容浏览器下所有未清除的浮动元素都伸出了其包含容器之外。

  相反的情况:如果确实需要一个浮动元素伸出其包含容器,也就是自动包含不是想要的效果时,该怎么办?你很可能也会遇到这种头疼的问题,下面的深入讨论就是一个例子:

  在IE中,一个浮动元素总是“隶属于”它的 layout 包含容器。而后面的元素会受这个 layout 包含容器影响而不是这个浮动元素影响。

  这个特性和IE6的那个自动扩展以适应内部内容宽度的特性,都可以看成是受这个规则影响的:“由它的边界矩形框决定”。

  更糟的问题:clear 无法影响其 layout 包含容器之外的 float 元素。如果依赖这个 bug 在 IE 中布局的页面要转到标准兼容浏览器中,只有全部重做。

  更多相关信息查看本文 “和 CSS 规范类似的地方” 这一部分。

浮动元素旁边的元素

  当一个块级元素紧跟在一个左浮动元素之后时,其中的文字内容应该沿着浮动元素的右边顺序排列并会滑到浮动元素下方。但是如果这个块级元素有 layout,比如由于某种原因被设置了宽度,那么这个 layout 元素就会表现为一个矩形,其中文字不会滑向浮动元素下方。其宽度也被错误计算——从浮动元素的右边开始算起了,所以如果给它设置 width: 100% 将会导致显示时这个 block 的宽度加上了浮动元素的宽度而出现横向滚动条。这种表现就和规范中描述的相去甚远了。

  与此类似,和浮动元素相邻的相对定位元素,它的位置偏移量应该参照的是父元素的补白(padding)边缘(例如,left: 0; 应该将一个相对定位元素叠放于它前面的浮动元素之上)。在IE中,偏移量 left: value; 是从浮动元素的右边距(margin)边缘开始算起的,这会因浮动元素所占的宽度变化导致水平方向的错位(一个解决方法是用 margin-left 代替,但是也要注意如使用百分值时会有一些怪异问题)。

  根据规范所述,浮动元素应该与其后的盒子交织在一起。而对于没有交叉的二维空间中的矩形而言这是无法实现的。

  可以(再次)访问下面这个页面:

  我们可以看到跟在一个浮动元素后的 layout 元素不会显示这个3px间隙的 bug,因为浮动元素外围的3px硬边无法影响一个 layout 元素的内部内容,所以这个硬边将整个 layout 元素右推了3px。好比一个防护罩,layout 可以保护其内部内容不受影响,但是浮动元素的力量却将整个防护罩推了开来。

  更多相关信息查看本文 “和 CSS 规范类似的地方” 这一部分

列表

  无论是列表本身(ol, ul) 还是单个的列表元素(li),拥有 layout 后都会影响列表的表现。不同版本 IE 的表现又有不同。最明显的效果就体现在列表符号上(如果你的列表自定义了列表符号则不会受这个问题影响)。这些符号很可能是通过某种内部机制附到列表元素上的(通常是附着在它们外面)。不幸的是,由于是通过“内部机制”添加的,我们无法访问它们也无法修正它们的错误表现。

  最明显的效果有:

  • 列表获得 layout 后,列表符号会消失或者被放置在不同的或者错误的位置。

  有时它们又可以通过改变列表元素的边距而重新出现。这看起来似乎是以下事实导致的结果:layout 元素会试图裁掉超出其边界的内部内容。

  进一步又有一个问题就是(在有序列表中)任何具有 layout 的列表元素似乎都有自己独立的计数器。比如我们有一个含五个列表元素的有序列表,只有第三个列表元素有 layout。我们会看到这样:

  1… 2… 1… 4… 5…

  此外,如果一个有 layout 的列表元素跨行显示时,列表符号会底部对齐(而不是按照预料的顶部对齐)。

  以上某些问题还是无法解决的,所以如果需要列表符号的时候最好避免让列表和列表元素获得 layout。如果需要限定尺寸,最好给别的元素设定尺寸,比如给整个列表外面套一个元素并设定它的宽度,又或者比如给每个列表元素中的内容设定高度等等。

  另一个IE中列表的常见问题出现在当某个 li 中的内容是一个 display: block 的锚点(anchor)时。在这种情况下,列表元素之间的空格将不会被忽略而且通常会显示成额外的一行夹在每个 li 之间。一种避免这种竖直方向多余空白的解决方法是赋予这些锚点 layout。这样还有一个好处就是可以让整个锚点的矩形区域都可以响应鼠标点击。

表格

  table 总是有 layout 的,它总表现为一个已定义宽度的对象。在IE6中,table-layout: fixed 通常和一个宽度设为100%的表格相同,同时这也会带来很多问题(一些计算方面的错误)。另外在IE5.5和IE6的quirks模式下还有一些别的需要注意的情况

相对定位元素(r.p.)

  注意,由于 position: relative 并不触发 hasLayout,所以很多诸如内容消失或错位的渲染错误就会因此而起。这些现象可能会在刷新页面、调整窗口大小、滚动页面、选中内容等情况下出现。原因是 IE 在据这个属性对元素做偏移处理时,却似乎忘了发出信号让其 layout 孩子元素进行“重绘”(而如果是一个layout元素,那么在其重绘事件的信号链中,这个传给其孩子的信号是会正常发出的)。

  以上是一些相关问题的描述。作为经验之谈,相对定位一个元素时最好给予其 layout。再有,我们也需要检查拥有这种结构的父元素是否也需要 layout 或者position: relative亦或二者都需要,如果涉及到浮动元素这点就十分重要。

绝对定位元素(a.p.):
包含区块,什么是包含区块?

  理解 CSS 的包含区块概念很重要,它回答了绝对定位元素是相对哪里定位的问题:包含区块决定了偏移起点,包含区块定义了百分比长度的计算参考。

  对于绝对定位元素,包含区块是由其最近的定位祖先决定的。如果其祖先都没有被定位,那么就使用初始包含区块 html

  通常情况下我们会用 position: relative 来设定任意包含区块。这就是说,我们可以让一个绝对定位元素所参考的原点和长度等不依赖于元素的排列顺序,这可以满足诸如“内容优先”这种可访问性概念的需要,也可以给复杂的浮动布局带来方便。

  但是由于 layout 概念的存在,这种设计理念的效果在IE中就要打个问号了。因为在IE中绝对定位的元素是相对于其最近的 layout 定位祖先而做偏移的,而百分比的尺寸却是参考这个 layout 定位祖先的下一个 layout 祖先计算的。注意这里的小差别,还有刚才提到 position: relative 是不会触发 hasLayout 的。

  假设一个无 layout 的父元素被相对定位了——我们就得给它赋予 layout 才能使偏移量起作用:

  假设一个未定位的父元素需要特定尺寸,而且页面设计是基于百分比宽度的——我们就可以放弃这个想法了,因为浏览器支持不佳:

滤镜

  MS专有的滤镜属性 filter 是只适用于 layout 元素的。有些滤镜扩展了对象的边界。它们会显示出自身特有的缺陷

对已渲染元素的重排(re-flow)

  当所有元素都已渲染完成时,如果有一个因鼠标经过而引起的变化产生(比如某个链接的 background 有变化),IE会对其 layout 包含区块进行重排。有时一些元素就会因此被排到了新的位置,因为当这个鼠标经过发生时,IE已经知道了所有相关元素的宽度、偏移量等数据了。这在文档首次载入时则不会发生,那时由于自动扩张的特性,宽度还无法确定。这种情况会导致在鼠标经过时页面出现跳变。

  这些和重排问题相关的 bug 会给百分比边距和补白使用较多的流动布局带来不少麻烦。

背景原点

  MS专有的这个 hasLayout 还会影响背景的定位和扩展。比如,根据 CSS 规范background-position: 0 0 应该指元素的“补白边缘(padding edge)”。而在 IE/Win 下,如果 hasLayout = false 则指的是“边框边缘(border edge)”,当 hasLayout=true 时指的才是补白边缘:

边距重叠

  hasLayout 会影响一个盒子和其子孙的边距重叠。根据规范,一个盒子如果没有上补白和上边框,那么它的上边距应该和其文档流中的第一个孩子元素的上边距重叠:

  在 IE/Win 中如果这个盒子有 layout 那么这种现象就不会发生了:似乎拥有 layout 会阻止其孩子的边距伸出包含容器之外。此外当 hasLayout = true 时,不论包含容器还是孩子元素,都会有边距计算错误的问题出现。

块级别的链接

  hasLayout 会影响一个块级别链接的鼠标响应区域(可点击区域)。通常 hasLayout = false 时只有文字覆盖区域才能响应。而 hasLayout = true 则整个块状区域都可响应。添加了 onclick/onmouseover 等事件的任意块级元素也有同样的现象。

在页面内使用键盘浏览:探索中

  当使用 tab 在页面中浏览时,如果进入了一个页内链接(in-page link),那么接下来再按的 tab 键就不会正常继续了:

  tab 键会把用户带到(这通常是错误的)其最近的 layout 祖先中的第一个目标(如果这个祖先是由 tabledivspan 或某些别的标签构成)。

堆叠,分层和 layout

  IE/Win 中似乎有两种分层和堆叠顺序:

  • 一种是(伪)试图采用CSS的模式:Effect of z-index value to RP and AP blocks
  • 还有一种是由“hasLayout”及其孪生兄弟“contenteditable”的行为产生的堆叠顺序。正如在上面相对定位的例子中展现的那样,在 layout 影响下的堆叠现象就好像 Harry Houdini (译者注:魔术师,以纸牌魔术成名)的拿手戏法儿一样。

  两种堆叠模式虽互不相容,但却共存于IE的渲染引擎中。经验之谈:调试的时候,两种情况都要考虑到。我们可能会有规律地在下拉菜单或者类似的复杂菜单中看到相关问题,因为它们往往牵涉到堆叠,定位和浮动等诸多令人头疼的问题。给那些 z-index 定位的元素 layout 是一种可能的修正方法,不过也不限于此,这里只是提醒一下。

混乱的 contenteditable

  如果给一个 HTML 标签设定 contenteditable=true 属性,比如<body contenteditable=true>,将会允许对该元素以及其 layout 子元素进行实时的编辑、拖动改变尺寸等操作。你可以把这属性用在浮动元素或者一个有序列表中的 layout 列表元素上看看效果。

  为了对元素进行操作(编辑它们),“contenteditable”和“hasLayout”为那些 hasLayout 返回 true 的元素引入了一套单独的堆叠顺序。

  Editing Platform 继承了 layout 概念,对 layout 的误解多是因 contenteditable 而起即可作为证明(那些某种程度上集成了IE编辑引擎的应用软件多暗含着对layout概念的某种强制向后兼容性)。

和 CSS 规范类似的地方

  你的 MSIE 页面在别的浏览器中一团糟?我们可没必要让这种事情发生。如果使用恰当,任何好的浏览器都能摆平 MSIE 的页面——只要你使用一些正确的 CSS。

  利用 hasLayout 和“新的块级格式内容”之间的细微相似之处,我们可以有几种方法在标准兼容浏览器中重新实现 hasLayout 的“包含浮动元素”效果,和一些“浮动元素旁边的元素”所特有的效果。

Quirks 模式

  某些 doctype,或者 <xml> 声明,在 IE6 中会触发“quirks模式”或曰向后兼容模式。在这种模式下,IE6 就像 IE5.5,并且和它老弟拥有一样的bug,一样的问题和一样的行为。

  而对于IE7,<xml> 声明不会再改变渲染模式了;要触发 quirks 模式,我们不得不插入一个注释才行。(IE7 的 quirks 模式和 IE6 的 quirks 模式是否一样还有待验证)

<?xml version="1.0" encoding="utf-8"?>
<!-- ... 让 IE7 运行在 quirks 模式 -->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

Layout — 结论

  整个 layout 概念和一些基本 CSS 概念是不兼容的,即包含,排列,浮动,定位和层叠等。

  由于页面中元素或有或没有 layout,会导致 IE/Win 的行为和 CSS 规范相违背。

拥有 layout — 另外一个引擎?

  IE 的对象模型看起来是文档模型和他们传统的应用程序模型的糅合。我之所以提到这点是因为它对于理解IE如何渲染页面很重要。而从文档模型切换到应用程序模型的开关就是给一个元素“layout”。

(Dean Edwards)

  有时候要解释清楚某种行为是不可能的:就比如 hasLayout,会根据它的状态选择两种不同渲染引擎的一种使用,而且每一种都有其自己的 bug 和怪异之处。

不可消除的 bug

  软件 bug 是由于在制作过程中对完整性和逻辑问题考虑不周等人为错误而导致的。这是人类的固有缺陷,目前还没有什么好的解决方法。

  同样由于这种缺陷,任何试图不重写软件而修复 bug 的做法,都将会不可避免的导致软件中出现更复杂的bug。

  所有依赖别的软件的软件——当然包括依赖操作系统,也会同样依赖他们的 bug。于是我们会从所有关联的软件中得到一连串的 bug,这也更说明找到一个无 bug 软件是几乎不可能的。

(Molly, the cat?)

    本文创建于2005年6月30日,最后一次修改于2006年4月2日。

编者:
Holly Bergevin
Ingo Chao
Bruno Fassino
John Gallant
Georg S?rtun
Philippe Wittenbergh
特别致谢给予此项目支持的:
Dean Edwards, and Molly ?the cat?
各种语言版本:
Original(English)
Brazilian Portuguese by Mauricio Samy Silva
中文版本 by old9
相关讨论:
dean.edwards.name/weblog/
联系作者:
spam.layout@satzansatz.de
版权说明:
本文基于创作共用协议发布。

JavaScript中的私有成员

JavaScript中的私有成员

Private Members in JavaScript

JavaScript是世界上是被误解得最厉害的编程语言。有些人认为它不具备“信息隐藏”的能力,因为JavaScript的对象没有私有变量和方法。这是误解。JavaScript对象可以拥有私有成员,下面我们来看看怎么做。(SharkUI.com注:JavaScript并不是真正拥有私有、公有等等OOP的特性,这篇译文中提到的这些私有、公有、特权等特性,是利用JavaScript的其他特性(参看本文的“闭包”一节)“模拟”出来的。感兴趣的话可以搜索相关的文章来看,当然也可以不管这些,就当它是真正的OOP来用。Have fun!)

对象

JavaScript是建立在对象之上的。数组(Array)是对象,函数(Function)是对象,对象(Objects)当然也是对象。那什么是对象呢?对象是一组“名称:值”对(name-value pair)的集合。名称是字符串,值却可以是字符串、数值、布尔或对象(包括数组和函数)。对象通常是用哈希表来实现的,以便可以快速地取值。

如果值是一个函数,我们就可以把它当作一个“方法”。当对象的一个方法被执行,变量this就被设为对象本身。如此,方法就可以通过this变量来访问对象的实例。

对象可以通过“构造器(constructor)”来创建。构造器是一个拥有初始化对象的函数。构造器提供了类似其他语言中的“类(class)”所提供的特性和功能,包括静态变量和方法。

公有

对象的所有成员都是公有成员。任何函数都可以访问、修改或者删除这些成员,当然也可以添加新的成员。给对象添加成员的两种主要方法:

通过构造器

这种方法一般用来初始化对象实例的公有变量。构造器的this变量被用来给对象添加成员:

function Container(param) {
   this.member = param;
}

构造一个新的对象:

var myContainer = new Container('abc');

然后,公有变量 myContainer.member 就拥有了值 'abc'。

通过原型(prototype)

这种方法通常用来添加公有方法。在对象本身搜寻一个成员但没有找到时,就使用构造器的原型(prototype)成员。这种原型机制实现了面向对象所谓的 “继承(inheritance)”,同时也节省了内存。给创建自同一个构造器的所有的对象加上一个方法,只需要给构造器的prototype增加一个函数:

Container.prototype.stamp = function (string) {
   return this.member + string;
}

然后我们就可以调用这个方法:

myContainer.stamp('def')

返回'abcdef'。

私有

私有(Private)成员是由构造器创建的。通常构造器中用var声明的变量和函数参数成为私有成员。

function Container(param) {
   this.member = param;
   var secret = 3;
   var self = this;
}

这个构造器创建了三个私有的实例变量:param,secret和self。

function Container(param) {
   function dec() {
      if (secret > 0) {
         secret -= 1;
         return true;
      } else {
         return false;
      }
   }

   this.member = param;
   var secret = 3;
   var self = this;
}

私有方法dec会检查实例变量secret,如果它大于0,自减1并返回true;如果它小于0,返回false。这样就实现了由这个架造器所创建对象的dec函数只能用三次的功能。

按惯例,我们创建了一个私有变量self。私有方法可以通过它来访问到对象本身。但这只是一种权宜之计,因为《ECMAScript Language Specification》中有一个错误,使得内部函数的this变量被设置成一个错误值。

公有方法(SharkUI.com注:即上文说的通过prototype创建的方法)是无法调用私有方法的,所以为了能使用私有方法,我们需要引入特权方法(privileged method)。

特权

一个特权方法可以访问私有变量和方法,而它本身可以被公有方法和外界访问。你可以删除或替换一个特权方法,但不能修改它,也不能强制它放弃自己的密秘(SharkUI.com注:原文如此,可能是指它的特权,关于这点请高手指教)。

特权方法是在构造器内部通过this来创建的。

function Container(param) {
   function dec() {
      if (secret > 0) {
         secret -= 1;
         return true;
      } else {
         return false;
      }
   }

   this.member = param;
   var secret = 3;
   var self = this;

   this.service = function () {
      if (dec()) {
         return self.member;
      } else {
         return null;
      }
   };
}

service是一个特权方法。前三次调用myContainer.service()将返回'abc',之后将返回null。service通过调用私有方法dec来访问私有变量secret。对于其他对象和方法来说,可以访问到service,但不能直接访问到私有的成员。

闭包

这种公有、私有和特权成员模式的存在是由于JavaScript的内在机制:闭包。这意味着一个内部函数永远可以访问它外部函数的变量和参数,即使外部函数已经返回。这是JavaScript语言非常强大的一个特性。目前还没有关于JavaScript编程的书籍展示了如何来利用它,它们甚至都没有提到这一点。

私有和特权成员只能在对象初始化的时候创建,而公有成员可以被随时添加进来。

模式

公有
function Constructor(...) {
   this.membername = value;
}
Constructor.prototype.membername = value;
私有
function Constructor(...) {
   var self = this;
   var membername = value;
   function membername(...) {...}
}

注:这句代码:

function membername(...) {...}

事实上是以下代码的简略写法

var membername = function membername(...) {...};
特权
function Constructor(...) {
   this.membername = function (...) {...};
}

后记

Douglas Crockford的这篇文章为我们写出更优美的JavaSciprt程序奠定了基础,为我们创建出更合理的面向对象应用和框架带来了可能。在这篇译文快要完成的时候,惊诧的发现作者网站上出现了一个本文中文版的链接。好事!有越来越多的中国人开始关注这些“边边角角”的技术。虽然做了重复工作,但一样希望各位能从这篇文章中有所收益。也希望有更多的人能投入到原创和翻译前端技术文章中来,在多数人浮躁的时候,我们需要更多基础性的工作。一周一篇不多,一年一篇不少,只要开始了就行!

1/6/2006

HTML4置标的默认样式列表

html, address,
blockquote,
body, dd, div,
dl, dt, fieldset, form,
frame, frameset,
h1, h2, h3, h4,
h5, h6, noframes,
ol, p, ul, center,
dir, hr, menu, pre   { display: block }
li              { display: list-item }
head            { display: none }
table           { display: table }
tr              { display: table-row }
thead           { display: table-header-group }
tbody           { display: table-row-group }
tfoot           { display: table-footer-group }
col             { display: table-column }
colgroup        { display: table-column-group }
td, th          { display: table-cell }
caption         { display: table-caption }
th              { font-weight: bolder; text-align: center }
caption         { text-align: center }
body            { margin: 8px }
h1              { font-size: 2em; margin: .67em 0 }
h2              { font-size: 1.5em; margin: .75em 0 }
h3              { font-size: 1.17em; margin: .83em 0 }
h4, p,
blockquote, ul,
fieldset, form,
ol, dl, dir,
menu            { margin: 1.12em 0 }
h5              { font-size: .83em; margin: 1.5em 0 }
h6              { font-size: .75em; margin: 1.67em 0 }
h1, h2, h3, h4,
h5, h6, b,
strong          { font-weight: bolder }
blockquote      { margin-left: 40px; margin-right: 40px }
i, cite, em,
var, address    { font-style: italic }
pre, tt, code,
kbd, samp       { font-family: monospace }
pre             { white-space: pre }
button, textarea,
input, select   { display: inline-block }
big             { font-size: 1.17em }
small, sub, sup { font-size: .83em }
sub             { vertical-align: sub }
sup             { vertical-align: super }
table           { border-spacing: 2px; }
thead, tbody,
tfoot           { vertical-align: middle }
td, th          { vertical-align: inherit }
s, strike, del  { text-decoration: line-through }
hr              { border: 1px inset }
ol, ul, dir,
menu, dd        { margin-left: 40px }
ol              { list-style-type: decimal }
ol ul, ul ol,
ul ul, ol ol    { margin-top: 0; margin-bottom: 0 }
u, ins          { text-decoration: underline }
br:before       { content: "\A" }
:before, :after { white-space: pre-line }
center          { text-align: center }
:link, :visited { text-decoration: underline }
:focus          { outline: thin dotted invert }

/* Begin bidirectionality settings (do not change) */
BDO[DIR="ltr"]  { direction: ltr; unicode-bidi: bidi-override }
BDO[DIR="rtl"]  { direction: rtl; unicode-bidi: bidi-override }

*[DIR="ltr"]    { direction: ltr; unicode-bidi: embed }
*[DIR="rtl"]    { direction: rtl; unicode-bidi: embed }

@media print {
  h1            { page-break-before: always }
  h1, h2, h3,
  h4, h5, h6    { page-break-after: avoid }
  ul, ol, dl    { page-break-before: avoid }
}


11/29/2005

css中字体单位px与em

px是固定大小单位称为像素,em是非固定大小单位称为相对单位字。在ie中px单位的文本的字号不随浏览器字号设置的改变而改变。浏览器字号的设置将改变默认em的大小。

例如:当ie文字大小为中时默认的1em相当于16px,当文字大小为较小时默认的1em相当于12px。

当em单位处于具有固定字号的对象中时,em将以此对象中的文字大小为基准单位

ie下文字大小为中:

<div style="font-size:12px;">
12px字体<br /> <span style="font-size:1em; color:blue">12px字内的1em字</span><br /><span style="font-size:2em;color:red">12px字内的2em字</span><br /> </div>
<div style="font-size:13px;">13px字体</div>
<div style="font-size:14px;">14px字体</div>
<div style="font-size:16px;">16px字体</div>
<div style="font-size:1em">1em字体</div>
<div style="font-size:0.85em">0.85em字体<br />
<span style="font-size:1em; color:blue">0.85em字内的1em字</span><br />
<span style="font-size:2em; color:red">0.85em字内的2em字</span><br /> </div>
<div style="font-size:0.75em">0.75em字体<br />
<span style="font-size:14px; color:blue">0.75em字内的14px字</span><br /> <span style="font-size:12px;color:red">0.75em字内的12px字</span><br />
</div>
<div style="font-size:0.5em">0.5em字体</div>

关于新手、中间用户和专家

1.     新手:较少部分工作通过OA完成或者对windowsoffice不熟练的人群。界面要求具备:操作提示(Tips)、功能按钮摆放明显引导性强,可通过增加界面增强易用性。

2.     中间用户:大部分工作通过OA完成,对windowsoffice熟练的人群。界面要求具备:用户界面精简、操作过程简便(鼠标点击少)、有一定的效率,可通过压缩用户界面使操作相对集中,提高效率。

3.     专家:OA的管理员或者几乎所有工作通过OA完成,对web软件有相当的认识的人群。界面要求:有极高操作效率和响应速度,可通过减少用户界面的修饰进一步压缩界面并且提供快捷键的支持。

10/27/2005

OOP概念

一、对象

考虑现实世界中的一个对象 - 例如,一只猫。我们可以说一只猫具有属性(或状态),例如猫名、猫龄和颜色;猫还具有各种行为,例如睡觉、吃食和发出呜呜声。在面向对象编程的世界里,对象也具有属性和行为。使用面向对象的技术,您可以为现实世界中的对象(例如一只猫)或更为抽象的对象(例如一个化学过程)建立模型。

二、类

在面向对象的编程中,类定义一类对象的蓝图,用来描述对象的属性(数据)和方法(行为)。属于某个类的特性和行为总称为该类的成员。特性(在猫的例子中,特性包括猫名、猫龄和颜色)称作类的属性,用变量表示;行为(吃食、睡觉)称作类的方法,用函数表示。

三、实例、成员

猫的颜色、猫龄和猫名可能不同,它们吃食和发出叫声的方式也可能不同。但是不管它们具有怎样的个体差异,所有的猫都是同一个类别的成员;或者,就 OOP 术语而言,它们属于同一个类:猫类。在 OOP (面向对象的编程)术语中,每只猫都是 Cat 类中的一个实例。

  • 每一只具体的猫是猫类的一个实例。
  • 在类的每一个实例中都有该类的方法和属性的唯一副本,这些方法和属性称为实例成员
  • 有些成员(方法和属性)仅用于类中,只有一个副本,这些成员称为类成员。只有由类的所有个体所共享的属性和方法才能是类成员。
  • 静态属性和方法被称为类成员,或者说类成员就是静态方法和属性

举例:Person

 

class Person {
       public var age:Number;
       public var username:String;
       public function getInfo():String {
               // getInfo() 方法定义
       }
}

假定您想要每一个类都具有一个 species 变量,用来表示该类代表的种类的正确的拉丁文名称。对于每一个 Person 对象,其种类都是智人 (Homo sapiens)。为类的每一个实例存储"Homo sapiens" 字符串的唯一副本是很浪费的,因此这个成员应是类成员。类成员用 static 关键字声明。例如,您可以使用下面的代码声明 species 类成员:
class Person {
public static var species:String = "Homo sapiens";
// ...
}
也可以将类的方法声明为静态方法,如下面的代码所示:
public static function getSpecies():String {
return Person.species;
}
静态方法只能访问静态属性,而不能访问实例属性。

所以静态属性和方法被称为类成员,或者说类成员就是静态方法和属性。

 

三、继承

面向对象编程(OOP)的主要优点之一就是可以创建(或扩展)类的子类;子类可以继承类的所有属性和方法。子类通常会定义其它方法和属性或重写超类中定义的方法或属性。子类还可以重写(为其提供自己的定义)在超类中定义的方法。使用超类/ 子类结构的最大优点之一是,更便于在各种不同的类之间重复使用相似的代码。
例如,可以构建一个名为 Animal 的超类,其中包含所有动物的共有特性和行为。接下来可以构建几个继承自 Animal 超类的子类,并添加特定于某类动物的特性和行为。您可以创建一个继承自另一类的 Cat 类。例如,您可以创建一个 Mammal 类,定义所有哺乳动物共有的某些属性和行为。然后,您可以创建一个扩展 Mammal 类的 Cat 子类。另一个子类(比如 Siamese 类)可以再次扩展(子类) Cat 类,依此类推。通过编写子类可以重用代码。您不必重新创建两个类共有的所有代码,而只需对现有类加以扩展即可。

四、接口

在 OOP(面向对象的编程)中,接口 可以描述为类定义的模板,需要使用实现接口的类以实现该方法模板。在猫的示例中,接口类似于猫的蓝图:通过蓝图可了解需要的部分,但并不一定提供关于这些部分的组装方法或工作原理的信息。可以使用接口向应用程序添加结构和易维护性。由于 ActionScript 2.0 仅支持从单个超类进行扩展,因此您可以将接口以受限多次继承的形式使用。也可将接口看作是用于将两个若没有接口便没有任何关系的类关联起来的“编程约定”。例如,假设您和一个程序员小组一起工作,每个程序员开发同一个应用程序的不同部分(类)。设计应用程序时,约定不同的类使用一组方法进行通信。因此,您创建了一个接口,用以声明这些方法、方法的参数及其返回类型。任何实现此接口的类都必须提供这些方法的定义,否则将出现编译器错误。

五、多态

OOP 允许使用一种名为多态 的技术来表达单个类之间的差异,使用这种技术,类可以重写其超类的方法并定义这些方法的专用实现。在 Flash 中,子类可以定义方法(从其超类继承)的专用实现,但不能访问其超类的实现,这一点与其它编程语言相同。例如,您可以从具有 play() 和 sleep() 方法、名为 Mammal 的类开始。然后您可以创建Cat、Monkey 和 Dog 子类来扩展 Mammal 类。这些子类重写 Mammal 类的 play() 方法,来反映那些特定种类的动物的习性。Monkey 实现悬挂在树上的 play() 方法; Cat 实现对线球猛扑的 play() 方法; Dog 实现捡回球的 play() 方法。因为动物的 sleep() 功能相似,所以可以使用超类实现。

六、封装

在完美的面向对象的设计中,对象被看作包含(或封装)功能的“黑匣子”。程序员应当能够在仅知道对象的属性、方法和事件(对象的编程接口)的情况下与对象进行交互,而不需知道其实现的详细信息。此方法使程序员可以在更高的抽象层次上思考,并能提供可用于构建复杂系统的组织框架。封装是 ActionScript 2.0 之所以包含诸如成员访问控制等功能的原因,这样实现的详细信息对于对象外的代码是私有的和不可见的。对象外代码将被强制与对象的编程接口交互,而不是与实现详细信息(可隐藏在私有方法和属性中)交互。这种方法提供了一些重要优点;例如,只要编程接口不变,对象的创建者就可以在不对对象外代码做任何更改的情况下更改对
象的实现。

10/25/2005

常用CSS缩写方式

使用缩写可以帮助减少你CSS文件的大小,更加容易阅读。css缩写的主要规则如下:

颜色

16进制的色彩值,如果每两位的值相同,可以缩写一半,例如:
#000000可以缩写为#000;#336699可以缩写为#369;

盒尺寸

通常有下面四种书写方法:

  • property:value1; 表示所有边都是一个值value1;
  • property:value1 value2; 表示top和bottom的值是value1,right和left的值是value2
  • property:value1 value2 value3; 表示top的值是value1,right和left的值是value2,bottom的值是value3
  • property:value1 value2 value3 value4; 四个值依次表示top,right,bottom,left

方便的记忆方法是顺时针,上右下左。具体应用在margin和padding的例子如下:
margin:1em 0 2em 0.5em;


边框(border)

边框的属性如下:

  • border-width:1px;
  • border-style:solid;
  • border-color:#000;

可以缩写为一句:border:1px solid #000;

语法是border:width style color;


背景(Backgrounds)

背景的属性如下:

  • background-color:#f00;
  • background-image:url(background.gif);
  • background-repeat:no-repeat;
  • background-attachment:fixed;
  • background-position:0 0;

可以缩写为一句:background:#f00 url(background.gif) no-repeat fixed 0 0;

语法是background:color image repeat attachment position;

你可以省略其中一个或多个属性值,如果省略,该属性值将用浏览器默认值,默认值为:

  • color: transparent
  • image: none
  • repeat: repeat
  • attachment: scroll
  • position: 0% 0%


字体(fonts)

字体的属性如下:

  • font-style:italic;
  • font-variant:small-caps;
  • font-weight:bold;
  • font-size:1em;
  • line-height:140%;
  • font-family:"Lucida Grande",sans-serif;

可以缩写为一句:font:italic small-caps bold 1em/140% "Lucida Grande",sans-serif;

注意,如果你缩写字体定义,至少要定义font-size和font-family两个值。


列表(lists)

取消默认的圆点和序号可以这样写list-style:none;,

list的属性如下:

  • list-style-type:square;
  • list-style-position:inside;
  • list-style-image:url(image.gif);

可以缩写为一句:list-style:square inside url(image.gif);

!important

!important是CSS1就定义的语法,作用是提高指定样式规则的应用优先权(参见:W3.org的解释)。语法格式{ sRule!important },即写在定义的最后面,例如:

box{color:red !important;}

最重要的一点是:IE一直都不支持这个语法,而其他的浏览器都支持。因此我们就可以利用这一点来分别给IE和其他浏览器不同的样式定义。