JavaScript
是浏览器的内置脚本语言。也就是说,浏览器内置了JavaScript
引擎,并且提供各种接口,让JavaScript
脚本可以控制浏览器的各种功能。一旦网页内嵌了JavaScript
脚本,浏览器加载网页,就会去执行脚本,从而达到操作浏览器的目的,实现网页的各种动态效果。
网页中嵌入
JavaScript
代码,主要有四种方法。
<script>
元素直接嵌入代码。<script> let x = 1 + 5; console.log(x); </script>
<script>
标签加载外部脚本<script>标签也可以指定加载外部的脚本文件。 <script src="https://www.example.com/script.js"></script> 如果脚本文件使用了非英语字符,还应该注明字符的编码。 <script charset="utf-8" src="https://www.example.com/script.js"></script>
所加载的脚本必须是纯的 JavaScript
代码,不能有HTML
代码和<script>
标签。
加载外部脚本和直接添加代码块,这两种方法不能混用。下面代码的console.log
语句直接被忽略。
<script charset="utf-8" src="example.js"> console.log('Hello World!'); </script>
为了防止攻击者篡改外部脚本,script
标签允许设置一个integrity
属性,写入该外部脚本的 Hash
签名,用来验证脚本的一致性。
<script src="/assets/application.js" integrity="sha256-TvVUHzSfftWg1rcfL6TIJ0XKEGrgLyEq6lEpcmrG9qs="> </script>
上面代码中,script标签有一个integrity属性,指定了外部脚本/assets/application.js的 SHA256 签名。一旦有人改了这个脚本,导致 SHA256 签名不匹配,浏览器就会拒绝加载。
onclick
和onmouseover
),可以写入 JavaScript
代码。当指定事件发生时,就会调用这些代码。<button id="myBtn" onclick="console.log(this.id)">点击</button>
上面的事件属性代码只有一个语句。如果有多个语句,使用分号分隔即可。
URL
协议URL
支持javascript:
协议,即在 URL
的位置写入代码,使用这个 URL
的时候就会执行 JavaScript
代码。<a href="javascript:console.log('Hello')">点击</a>
浏览器的地址栏也可以执行javascript:
协议。将javascript:console.log('Hello')
放入地址栏,按回车键也会执行这段代码。
如果 JavaScript
代码返回一个字符串,浏览器就会新建一个文档,展示这个字符串的内容,原有文档的内容都会消失。
<a href="javascript: new Date().toLocaleTimeString();">点击</a>
上面代码中,用户点击链接以后,会打开一个新文档,里面有当前时间。
如果返回的不是字符串,那么浏览器不会新建文档,也不会跳转。
<a href="javascript: console.log(new Date().toLocaleTimeString())">点击</a>
上面代码中,用户点击链接后,网页不会跳转,只会在控制台显示当前时间。
浏览器加载 JavaScript
脚本,主要通过<script>
元素完成。正常的网页加载流程是这样的。
HTML
网页,一边开始解析。也就是说,不等到下载完,就开始解析。<script>
元素,就暂停解析,把网页渲染的控制权转交给 JavaScript
引擎。<script>
元素引用了外部脚本,就下载该脚本再执行,否则就直接执行代码。JavaScript
引擎执行完毕,控制权交还渲染引擎,恢复往下解析 HTML
网页。如果有多个script
标签,比如下面这样。
<script src="a.js"></script> <script src="b.js"></script>
浏览器会同时并行下载a.js
和b.js
,但是,执行时会保证先执行a.js
,然后再执行b.js
,即使后者先下载完成,也是如此。
也就是说,脚本的执行顺序由它们在页面中的出现顺序决定,这是为了保证脚本之间的依赖关系不受到破坏。当然,加载这两个脚本都会产生“阻塞效应”,必须等到它们都加载完成,浏览器才会继续页面渲染。
为了解决脚本文件下载阻塞网页渲染的问题,一个方法是对
<script>
元素加入defe
属性。它的作用是延迟脚本的执行,等到DOM
加载生成后,再执行脚本。
<script src="a.js" defer></script> <script src="b.js" defer></script>
上面代码中,只有等到 DOM
加载完成后,才会执行a.js
和b.js
。
解决“阻塞效应”的另一个方法是对<script>
元素加入async
属性。
<script src="a.js" async></script> <script src="b.js" async></script>
async
属性的作用是,使用另一个进程下载脚本,下载时不会阻塞渲染。
async
属性可以保证脚本下载的同时,浏览器继续渲染。需要注意的是,一旦采用这个属性,就无法保证脚本的执行顺序。哪个脚本先下载结束,就先执行那个脚本。
另外,使用async
属性的脚本文件里面的代码,不应该使用document.write
方法。
<script>
元素还可以动态生成,生成后再插入页面,从而实现脚本的动态加载。
['a.js', 'b.js'].forEach(function(src) { let script = document.createElement('script'); script.src = src; document.head.appendChild(script); });
好处
script
标签不会阻塞页面渲染缺点:
如果想避免这个问题,可以设置async
属性为false
。
['a.js', 'b.js'].forEach(function(src) { var script = document.createElement('script'); script.src = src; script.async = false; document.head.appendChild(script); });
上面的代码不会阻塞页面渲染,而且可以保证b.js
在a.js
后面执行。
不过需要注意的是,在这段代码后面加载的脚本文件,会因此都等待b.js
执行完成后再执行。
如果想为动态加载的脚本指定回调函数,可以使用下面的写法。
function loadScript(src, done) { let js = document.createElement('script'); js.src = src; js.onload = function() { done(); }; js.onerror = function() { done(new Error('Failed to load script ' + src)); }; document.head.appendChild(js); }
end