realazy


ppk on JavaScript第二章:背景(一)

JavaScript为网页而存在,它会被嵌入到一个同时使用HTML和CSS的环境中,而此环境中不可缺少可用性和无障碍。总而言之,脚本必须给站点增加用处,而站点在JavaScript失灵或者根本不存在的情况下依然能继续工作。兼容标准的CSS革命改变了Web开发,JavaScript编程也受到这场运动的巨大影响。

CSS革命

1998年,在Netscape和IE4无法达成任何协议时,一些先天下之忧而忧的Web开发者组成了 Web Standards Project(Web标准工程,简称WaSP),为解决JavaScript某些荒唐的私有元素,并推动使用CSS来定义网站的外观。她们的重要使命是“追随标准”,不仅针对浏览器厂商,而且号召Web开发者。

最初,WaSP及其支持者专注于CSS. 究其原因,CSS是一门比较新的技术,尚未被乱七八糟的东西污染,更容易成为历史的一个转折点,JavaScript就没有这么幸运了,那时候的JavaScript,无论是编程,还是人们对它的想法,都是完全非无障碍的。这也是导致某些标准支持者产生“JavaScript就是障碍”观念的原因,无论是过去还是现在。其实,JavaScript和无障碍可以和谐共存,只要您稍微谨慎。

Unobtrusive脚本编程

2002年,Stuart Langridge创造出Unobtrusive脚本编程(unobtrusive scripting, Stuart Langridge的原文),这是首次重要的尝试——在基于CSS,标准兼容的新理论中嵌入JavaScript.

Unobtrusive脚本应该具备一下所有的特征:

  • 可用性,比如,赋予网站明确的可用性好处;
  • 无障碍,比如,假使JavaScript失效,页面应该保持可读和可理解,尽管不可避免地丧失某些可用性;
  • 容易实现,一个经典案例是,Web开发者只需把脚本引入和增加一个JavaScript调用钩子(hook),脚本就起作用;
  • 分离,属于本身的.js文件而不是散落在HTML的各个角落。

理论上说,第一条自JavaScript诞生之日起就有的,但是经常会被程序员在炫耀JavaScript能力的热忱中忽略。如果没有可用性多酷都无关紧要。

其他三条都是新的。通常都认为无障碍和JavaScript是互斥的。容易的实现需要JavaScript钩子,W3C DOM的降临使之成为可能。分离,是偷师于CSS革命的。如果需要分离HTML和CSS, 逻辑上,也应该把JavaScript从它们中分离。

三个层面

网页包含三个层次(没错,它们都需要各自分离):

3layers
  1. HTML结构
  2. CSS表现
  3. JavaScript行为

HTML结构层是网页最重要的基础。HTML标签给予内容含义。CSS表现层则是定义您的HTML该如何显示。JavaScript行为层为页面增加交互。

不管如何,一个网页必需HTML结构层。没有HTML,没有网页。CSS和JavaScript都是可选项,旧的,无名的,罕见的浏览器可能不支持CSS和/或JavaScript,在这种情形下,这两层或其中一层都不起作用。后果是显而易见的,任何网站应能在行为层(或者表现层,但这种情形相比较少)的缺席下还能“存活”。也就是说,网站不能完全依赖于JavaScript,但要保证无障碍即使JavaScript不起作用。

分离的关系

一般来说,最好单独管理好每一层。最基础的,确保:

  • HTML是结构性的,不要太复杂,没有CSS和JavaScript下保持语义。
  • CSS表现层和JavaScript表现层分别归属于独自的.css.js文件。

分离更容易维护。您可以轻而易举地把分离的文件连接到整站的网页上,简单举个例子,您需要把字体从12px改成0.8em,您只需打开CSS文件编辑它,这样网页变化即刻生效。除此之外,分离让您可以不需修改HTML结构层或者JavaScript行为层,只需修改整个CSS表现层就可让网站换上新衣。

分离表现和结构

表现和结构分离的基本思想是确保HTML定义结构,只有结构,所有的表现都定义到分离的CSS文件中去。不再允许font标签或者表现性的表格!在一本JavaScript的书中似乎没有什么余地来探讨CSS和HTML的分离。那么我们就来说说这个分离对我们编写JavaScript代码方式的影响吧。

CSS更改

JavaScript可以让您修改CSS,比如,您可以在CSS定义一个连接为红色,然后用JavaScript控制CSS再定义为绿色。有时候这是很有用的,样式的变化会使用户能注意变化的HTML的元素,比如出错信息。如果没有正确地分离CSS表现层,CSS更改将会变得十分困难。改变元素的className通常是最佳的CSS更改方式。如下例子,假如表单验证程序发现用户输入错误,则改变该表单字段的class:

// obj is the form field
obj.className += ' errorMessage';

// in CSS
input.errorMessage {
    border: 1px solid #cc0000;
}

只有您正确分离了表现和结构,这样的方式才会起作用。class errorMessage必须定义在CSS中为了实现样式的更改,反过来,也只有您一开始就从正确的CSS表现层开始才有可能(或者说,可行)。

修改结构还是表现

JavaScript实际上允许您改变网站的表现,也允许您改变HTML文档。用户并不关心我们改了什么。但还是有所不同的。改变一个应答用户行为的表单应该是修改结构而不是表现。相关表单元素不应该只是从视觉上隐藏而已,而应该从文档结构中移除。当一个表单提交时,浏览器为所有表单元素创建名称/值配对,并发送给服务器。如果仅仅是在CSS中隐藏,这些字段依然是表单的组成部分,尽管可能不是服务器所需要的。这只是理论上的东西,您可以不同意我。

分离行为和结构

分离行为与结构很容易理解:不要把任何JavaScript代码写入HTML页面中。采取这两步:

  • 把所有的JavaScript函数定义在一个分离的.js文件中,让所需的HTML页面连接到它。
  • 删除所有的事件处理句柄(注:即行内的那些诸如onmouseover)并归入同一.js文件中去。

分离文件中的函数

JavaScript代码属于.js文件,而非HTML文件。

所以这是错误的:

<script type="text/javascript">
 function doAllKindsOfNiftyThings()
 {
     // JavaScript code
 }
 </script>
 </head>
 <body>
 <h1>My HTML page</h1>
 [etc.]

这才是正确的:

</head>
<body>
<h1>My HTML page</h1>
[etc.]

// 定义在分离的nifty.js中
function doAllKindsOfNiftyThings()
{
    // JavaScript code
}

删除事件处理句柄

第二步是把所有HTML内的JavaScript函数调用移到分离的.js中去。事实上,99%的HTML内的JavaScript代码是行内事件句柄。

以下,句柄在HTML内,但不应该属于HTML的:

<a href="home.html" onMouseOver="mOv('home')" onMouseOut="mOut('home')">Home</a>

应该定义在分离的.js文件中去:

<a href="home.html">Home</a>
// in separate .js file
var nav = document.getElementById('navigation');
var navLinks = nav.getElementsByTagName('a');
for (var i=0;i<navLinks.length;i++)
{
    navLinks[i].onmouseover = [code];
    navLinks[i].onmouseout = [code];
}

该脚本处理id="navigation"的元素并处理其内的所有连接,然后再赋予连接事件处理句柄。

javascript:伪协议

有些情况下你会看到像以下的javascript:伪协议:

<a href="javascript:doAllKindsOfNiftyThings()">Do Nifty!</a>

这个复杂肮脏代码隐藏的含义是一个onclick事件句柄:当用户点击该连接,我们需要的是呼叫doAllKindsOfNiftyThings()函数。所以您应该从该连接中删除javascript:呼叫而用一个独立.js文件中的onclick事件句柄来取代之:

<a href="somepage.html" id="nifty">Do Nifty!</a>
// in separate .js file
document.getElementById('nifty').onclick = doAllKindsOfNiftyThings;

因此,对于href,应该包含一个完整的url以备没script的用户能够访问,否则整条连接都由JavaScript产生,不具备无障碍性。

注意:以下内容非正文!

p.s.有人点名要爆隐,没办法,只好完成指标:

  1. 新千年,学会上网,为了能向心爱的女孩子炫耀,学做网页,想不到现在就跟网页打交道。
  2. 新世纪,开日在江西某所大学无所事事,所念专业跟以后的工作一点关系都没有,世界真奇妙。
  3. 继续无所事事……
  4. 继续无所事事……
  5. 继续无所事事……
  6. 开始在网上写blog, 让人知道我多多少少还是知道web标准的,在这行业资源匮乏的时候幸运地到了北京工作,并不断进步。
  7. 跳了两次槽,希望有个好开始。
  8. 不好意思,还有几天才到这个年龄。

22 Responses to “ppk on JavaScript第二章:背景(一)”

  1. sike Says:

    安安同学辛苦了

  2. wkz0712 Says:

    在线中文版?看来过不了多久就该有中文版出来了。

  3. aoao Says:

    举手~报告老师~我有个问题可不可以提问~挖哈哈

    就是以前一个老问题,关于要背景还是要图片,
    同样这个问题也一样,我们用js添加className时,这个是最佳的做法,
    可是有个服务器,比较烂或者刚好压力比较大的那种,你请求三个CSS时他只给你两个,
    这时页面并非裸体的,用户仍然可正常操作,
    刚好我们添加的className就在那个掉了的那个里面,
    这时,用户可能无法看到提示,怎办。
    当然,我知道你文章要表达的意思,这个是额外的问题。

  4. realazy Says:

    aoao, 那么如果是图片,刚好图片服务器的压力比较大,那你说,这是什么问题呢?跟我们的要讨论问题没啥关系嘛……只能说,这是服务器配置的不合理了……

  5. aoao Says:

    ..发现我打了错别字。。。。就是->就像。。
    其实想说。。包含有要添加的className 的CSS没加载到。

  6. htcmi Says:

    真是感謝你的分享!

  7. leiws Says:

    如果这是笔记的话,我相信,确实是一篇很到位的笔记;如果把它当成教程来看的话,就不见得了。

  8. Aether Says:

    我觉得所谓彻底分离这样的说法,还是过于偏执,把零碎的,临时的,仅对该页面有效的js放在页面中,反而会给维护带来好处,也减轻了整体架构的复杂程度,好处多多。

  9. dulao5 Says:

    彻底把html中的事件句柄放到js中,有两个问题:
    1。如果html中有为数居多的不同的事件句柄,那么需要在测试和修改的后续过程中,维护js中的赋值语句和html中属性的映射关系。
    当然,似乎有办法:
    html在设计时仍然使用内嵌的事件句柄;发布时使用智能程序提取html里面的事件属性,自动生成js。
    2。比较头疼的问题:如果html内直接嵌入js事件属性,那么浏览器可以在html下载未完成的过程中,一边下载一边解析html,让这些js事件句柄生效。而分离到js后,浏览器需要一个时机才能执行那些js语句,给html内的事件句柄赋值。

    安安说的是标准化的理想,现实似乎还没有到达这种程度。

    我觉得事件句柄其实并不属于javascript的范畴,它们只是html元素的一种属性而已;
    事件句柄和元素的className相比,似乎区别不大,都是一种超出html本身的扩展,只不过一个是行为,一个是表现。

  10. realazy Says:

    我的看法是,就像我前篇blog描述的,要看js用处了:面向任务还是面向信息呢?毫无疑问,像gmail这种运行在浏览器中的“软件”分离其实没有多大意义,也没有多大必要,因为它里面的HTML只是UI组件而已,没有或者很少需要传达信息。

    你的看法很有道理,但是我也可以举个反例,元素不也是可以直接用style属性来定义样式吗?那为什么还要给class或者id呢而不是直接内嵌样式就行了?从逻辑上说,分离了样式,为何不分离行为呢?呵呵。

    至于你说的浏览器需要一个时机才能执行js,确实,传统的window.onload弊病多多,但是进入web标准时代,世界级开发者不会等闲视之的,我相信你也知道了Dean Edward的解决方案了吧?现在的大部分js框架都提供了DOM载入完毕即可立即执行js的功能(比较有名的就是jQuery的$(document).ready()了,所以这个不是问题。

    一句话,我想你的真正想法(原谅我瞎猜)应该是,写内联js方便多了,不用再定义一个调用的hook。其实,JavaScript可以和CSS共享hook啊(这点对jQuery来说太轻松了)。

  11. dulao5 Says:

    的确要分情况来讨论这个问题(面向任务还是面向信息呢?);

    如果是一个专门的richclient应用:

    html会很复杂,事件句柄也很复杂,那么维护他们的映射关系、修改和测试、发布都会很复杂。
    而且html可能是一些琐碎的模版文件,他们也可能动态的被载入,所以也可能需要动态的为相应的模版做“事件句柄”赋值。
    对于复杂的系统,团队协作是很重要的,rich client应用更是这样,UI、HTML/CSS、js各方面都要配合,所以寻找一种实用而有效的合作方式是非常必要的。

    我的意思并不是说“写内联js方便多了”,所以js人员怎么方便怎么来吧。当然不是这样了,我的意思是我们要寻找更有效的合作方式,或许你的观点,可以在一些框架、工具、策略的支持下,得到实现。当然,项目的配置管理成本也相应的提高了。

    讨论一些可行方法:
    原始的赋值语句肯定是不行的;
    也许你说的jQuery能帮上忙;
    id和事件句柄做一些名字上的特殊关联,通过运行时脚本进行赋值处理;
    象上面我说的,发布时使用工具处理这个问题也是好办法(减少了js的运行时间)。

    其实这是软件开发中的一个经典问题,即如何对系统解耦?
    开发的过程中需要对模块进行解耦,降低耦合性,提高内聚性;
    但是开发完毕运行系统时,又需要把模块耦合到一起才能工作。
    这里有一个“度”的问题,要看开发过程中的实际情况来定。

    至于一些普通站点,我想实现你的观点没什么问题,少量的js脚本代价不大。html美观,js怡人,皆大欢喜:D。

  12. realazy Says:

    老五,你看问题太专业了,哈哈,我没啥开发经验,你已经把问题引申到软件工业的开发、发布等流程上了,大大超出了我们的范畴。不过你的观点对我们这篇文章有很好的补充作用,谢谢你花时间打这么多的字,哈哈,代表读者们感谢你。

  13. dulao5 Says:

    你作了那么多css,和那么多团队合作过,怎么能说没有经验呢,谦虚了吧:)

    实际上我们做这些研究,目的只有一个,就是怎么把工作做的漂亮。实际上本文和软件开发都是一个范围的,可以放到一起讨论的:)

  14. PorkFat Says:

    对dulao5谈到的问题,第一个我理解是开发成本的问题,通过敏捷的团队模型和规范化的流程还是可以减少这个问题的严重性的;第二个是说有没有必要这么做吧,因为把表现与结构分离,在浏览器端就常有可能看到xhtml下载完而css还没有加载的裸体页面,而把行为与结构分离,我不理解为什么又有这样的顾忌,其实这并没有什么大不了呀,三者分离本来就是我们扯了好几年的webstandards的思想,这样好像又回到原点了……

  15. mike Says:

    没有绝对的。
    分离有分离的好,内嵌有内嵌的好。一切还是给通过项目来决定。在这个瞬息万便的社会,没有绝对的定理。以上只代表偶的个人观点。。大家看之一笑 ^_^

  16. SONIC Says:

    又是行为与结构分离的文章,收藏。
    不过对于复杂结构的HTML文档。就如dulao5所说,确实存在着提高开发成本的问题!

  17. SOOW Says:

    原来代码是这样写的:-)
    不错,偶然发现这里的,希望自己有一天也可以和你一样,可以做到传道/受业/解惑
    更希望有机会和你切磋一下

  18. cvpc Says:

    感谢分享,你的Blog不错

  19. {|ihower.idv.tw| blog } » ppk on JavaScript Says:

    [...] 前兩章 Purpose 跟 Context 非常非常精采,作者就像說故事一樣娓娓道來,全貌性把 JavaScript 的來龍去脈講了一遍,realazy有做了書摘( JavaScript的目的、背景一、背景二、背景三 ),我就不多詳述了。 [...]

  20. ihibin Says:

    译的不错,顶了!!

  21. wkx Says:

    支持web标准

  22. GalaxySong Says:

    在较大的应用中,JS 文件应该分为3种:
    1) 类包。有些像 Java ,但也不能生搬硬套。发布时,可以把所有类包合并并压缩为一个 js 文件。
    2) 环境设置。开发时和发布后的环境(如图片路径)可能是不同的。
    3) 跟随页面的代码。把所有原本可能分散在页面中的 Script 统统何在一处。它们往往调用了类包来满足当前页面的具体需求。
    零散的 script 是很不好的。html 应该专心扮演结构的角色,不要硬绑定行为。
    设计良好的 JS 应该充分发挥闭包的功效,并最小化全局变量的使用。所以,html 元素只能被动地由 JS 对象来绑定,而不是相反。

Leave a Reply


realazy (懒到死) is proudly powered by WordPress | Entries (RSS) and Comments (RSS)