在HTML里嵌入JavaScript
在HTML文档里嵌入客户端JavaScript代码有4种方法:
- 内联,放置在
<script>
和</script>
标签对之间。 - 放置在
<script>
标签的src属性指定的外部文件中。 - 放置在HTML事件处理程序中,由HTML属性onclick或onmousemove等指定。
- 放在一个URL里,这个URL使用特殊的"javascript:"协议。
有个编程哲学叫"unobtrusive JavaScript",主张内容(HTML)和行为(JavaScript代码)应该尽量地保持分离。根据这个编程哲学,JavaScript最好通过<script>
元素的src属性来嵌入HTML文档里。
<script>
标签对之间使用
在XHTML中,<script>
标签中的内容被当做其他内容一样对待。如果JavaScript代码包含"<"或"&"字符,那么这些字符就被解释为XML标记。因此如果要使用XHTML,最好把所有的JavaScript代码放入到一个CDATA部分里:
<script>
<![CDATA[
// 这里放置JavaScript代码
]]>
</script>
URL中使用JavaScript
在URL后面跟一个javascript:协议限定符,是另一种嵌入JavaScript代码到客户端的方式。javascript: URL可以用在可以使用常规URL的任意地方:比如<a>
标记的href属性,<form>
的action属性,甚至window.open()方法的参数。
<a href="javascript:new Date().toLocaleTimeString();">
What time is it ?
</a>
就像单击一个URL链接,浏览器会擦除当前文档并显示新文档。上例中将显示当地时间。
同步、异常和延迟脚本的执行
- 当HTML解析器遇到
<script>
元素时,默认先执行脚本,然后再恢复文档的解析和渲染。 - 如果在
<script>
标签中使用async属性,则脚本执行的同时,可以进行文档的解析。 - 如果在
<script>
标签中使用defer属性,则当文档解析完成后,才会执行脚本。
// 脚本执行的同时进行文档解析
<script async src="async.js"></script>
// 文档解析完成后才执行脚本
<script defer src="deferred.js"></script>
JavaScript的事件驱动
有些事件的目标是文档元素,它们会经常往上传递给文档树,这个过程叫做"冒泡"。
例如,如果用户在<button>
元素上单击鼠标,单击事件就会在按钮上触发。如果注册在按钮上的函数没有处理该事件,事件会冒泡到按钮嵌套的容器元素,这样,任何注册在容器元素上的单击事件都会调用。
JavaScript的线程模型
JavaScript语言并不包含任何线程机制,只是单线程工作,永远不需要担心锁、死锁和竞争条件。
HTML5定义了一种后台线程"WebWorker",但是JavaScript还是严格的单线程一样工作。
JavaScript时间线
步骤1:
- Web浏览器创建Document对象,并且开始解析web页面,解析HTML元素和它们的文本内容后添加Element对象和Text节点到文档中。
这个阶段document.readystate属性的值是"loading".
步骤2:
- 当HTML解析器遇到没有async和defer属性的
<script>
元素时,会把这些元素添加到文档中,然后执行脚本,解析器会暂时停止,此时脚本如果执行document.write()把文本插入到输入流中,解析器恢复时这些文本会成为文档的一部分。 - 当HTML解析器遇到sync属性的
<script>
元素时,它开始下载脚本文本,并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器没有停下来。异步脚本禁止使用document.write()方法。 - 当HTML解析器遇到defer属性的
<script>
元素时,脚本会按它们的出现顺序执行,异步脚本可能在这个时间还在执行。延迟脚本禁止使用document.write()方法。当文档完成解析,document.readystate属性的值是"interactive".
步骤3:
- 浏览器在Document对象上触发DOMContentLoaded事件。这标志着程序执行从同步脚本执行阶段转换到了异步事件驱动阶段。但要注意,此时异步脚本可能还在执行。
步骤4:
- 文档已经解析完成,但是浏览器可能还在等待其他内容载入,如图片。当所有的内容完成载入,并且所有的异常脚本完成载入和执行,浏览器触发Window对象上的load事件。
document.readystate属性的值是"complete".
步骤5:
- 此时会调用异步事件,以异步响应用户输入事件、网络事件、计时器过期等。
IE里的条件注释
IE支持条件注释(由IE5引入),尽管这种做法并不符合标准规范,但是在处理不兼容性时非常有用。
HTML中的条件注释
由于IE不支持canvas,而其他浏览器是支持的,那么为了兼容性,可以添加条件注释如下:
// 只有在IE浏览器中才加载js文件
<!--[if IE]><script src="excanvas.js"><![endif]-->
JavaScript中的条件注释
可以通过通过条件注释完成在不同的浏览器中执行不同的代码:
/*@cc_on
@if (@_jscript)
// 在IE中@_jscript是解释器的名字,值为true
alert("你在使用IE浏览器");
@else*/
// 这段代码并没在JavaScript注释中,但仍然在IE条件注释中
// 也就是说除了IE之外的所有浏览器都执行这里的代码
alert("你在使用非IE浏览器");
/*@end
@*/
JavaScript安全性
JavaScript不能做什么
- JavaScript没有权限来写入或删除客户计算机上的任意文件或列出任意目录。
- JavaScript可以打开一个新的浏览器窗口,但为了防止广告商滥用弹出窗口,很多浏览器限制了这一功能。
- JavaScript程序可以关闭自己打开的浏览器窗口,但是不允许它不经过用户确认就关闭其他的窗口。
- HTML的FileUpload元素的value属性是只读的。
- 脚本不能读取从不同服务器载入的文档的内容,除非这个就是包含脚本的文档。类似的,一个脚本不能在来自不同服务器的文档上注册事件监听器。
同源策略 same-origin policy
脚本只能读取和所属文档来源相同的窗口和文档的属性。脚本本身的来源和同源策略并不相关,相关的是脚本所嵌入的文档的来源。
例如,一个来自主机A的脚本被包含到宿主B的一个Web页面中,那么脚本能完整地访问包含它的文档内容(B的web页面),如果脚本打开一个新窗口并载入来自主机B的另一个文档,脚本对这个文档的内容也具有完全的访问权限。但是,如果脚本打开第三个窗口并载入一个来自主机C的文档,同源策略就会发挥作用,阻止脚本访问这个文档。
突破同源策略的限制
Document对象的domain属性
同源策略给那些使用多个子域的大站点带来了一些问题。例如,来自home.example.com的文档里的脚本想要合法地读取developer.example.com载入的文档的属性。为了支持这种类型的多域名站点,可以使用Document对象的domain属性。
如果两个窗口(或窗体)包含的脚本把domain设置成相同的值,那么这两个窗口就不再受同源策略的约束,它们可以相互读取对方的属性。跨域资源共享 Cross-Origin Resource Sharing
这个标准草案用新的"Origin:"请求头和新的Access-Control-Allow-Origin响应头来扩展HTTP。它允许服务器用头信息显式地列出源,或使用通配符来匹配所有的源并允许由任何地址请求文件。跨文档消息 cross-document messaging
这是一种新技术,允许来自一个文档的脚本可以传递文本消息(调用window对象的postMessage()方法)到另一个文档里的脚本,而不管脚本的来源是否相同。
跨站脚本 cross-site scripting
跨站脚本,或者叫XSS,表示攻击者向目标web站点注入HTML标签或者脚本。防止XSS攻击是服务器端web开发者的一项基本工作,然而客户端JavaScript程序员也必须预防跨站脚本。
如果web页面动态地产生文档内容,并且文档内容是基于用户提交的数据,而并没有从中移除任何嵌入的HTML标签的话,那么这个web页面很容易遭到跨站脚本攻击。
<script>
// 获取URL中以"?"开始的部分
var name = decodeURIComponent(window.location.search.substring(1)) || "";
document.write("Hello" + name);
</script>
- 如果URL是:
http://www.example.com/greet.html?David
,那么文档将显示"Hello David"。 - 但是如果URL是
http://www.example.com/greet.html?name=%3Cscript src=siteB/evil.js%3E%3C/script%3E
(%3C和%3E是尖括号的编码),那么就会遭到B站点的evil.js脚本注入,evil.js脚本此时将可以对站点A的内容进行任何想要的操作。
通常,防止XSS攻击的方式是,在使用任何不可信的数据来动态创建文档内容之前,从中移除HTML标签。
name = name.replace(/</g, "<").replace(/>/g, ">");