如何使用
请求JavaScript脚本
<script src="path-to-javascript"></script>
内嵌JavaScript脚本
<script>
const name = 'lisi'
console.log(name)
</script>
有什么特性
加载
script标签加载脚本默认是同步的。当HTML页面在加载的过程中,遇到一个<script>标签,就会中断HTML页面的加载,然后请求src对应服务器上的脚本资源,当脚本加载完成后,HTML仍然不能继续加载,而是要等加载的JavaScript脚本执行后,HTML页面才能继续往下加载。
渲染
当遇到script标签的时候,会立即渲染一次。HTML页面加载的过程中,遇到一个script标签,如果之前已经加载了部分HTML内容,那么之前加载的HTML内容会被渲染出来,然后再去加载对应的JavaScript脚本。
针对以上两个特性,我们可以通过以下代码进行验证。
服务器代码(nodejs)
说明:以下服务端代码是模拟响应浏览器请求JavaScript脚本请求,延迟5秒才返回
const express = require('express')
const server = express()
server.get('/script', (req, res) => {
console.log(req.url)
setTimeout(() => {
res.send('console.log(123)')
}, 5000);
})
server.listen(3000, () => {
console.log('Server listen at port 3000');
})
JavaScript阻塞HTML页面渲染
说明:页面只有h1标题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="http://localhost:3000/script"></script>
<title>Document</title>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
当页面加载时,开始的5秒是白屏,直到JavaScript脚本响应后,页面才会渲染h1标题 效果如下图
script标签在HTML内容中间 把script标签放到两个h1标签之间
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h1>Hello World!</h1>
<script src="http://localhost:3000/script"></script>
<h1>Hello Guys!</h1>
</body>
</html>
开始第一个h1标签被渲染出来,第二个h1标签没渲染;因为遇到script标签,要等到脚本下载并执行完才会继续渲染第二个h1标签。
加载相关属性
默认的,script标签不加任何属性,加载顺序是这样的
其中不同颜色横条说明如下
async
这个属性能让JavaScript脚本异步下载,不过,一旦下载完成后,就会立马执行;JavaScript的执行会阻塞HTML的解析
defer
这个属性能让JavaScript脚本异步下载,而且会等到HTML页面解析渲染完毕后才执行
type="module"
添加该属性后,请求的脚本就是 module script,可以直接使用 import 导入模块,export 导出模块。
添加了type="mudule"的script标签有以下特性
- 不受 charset和defer属性的影响
- 浏览器行为
支持module script的浏览器,不会执行有 nomodule 属性的script 不支持module script的浏览器,会忽略 type="module"的script module script以及其依赖的所有文件(元文件中通过import导入的文件)都会被下载,一旦真个依赖的模块树都被导入,页面文档也完成解析,脚本才会执行。(行为和添加defer属性类似) module script添加async属性,脚本以及其依赖都会异步下载,下载完后立即执行,不会等页面是否渲染完成。<script nomodule scr="xxxx"></script> <script type="module" src="xxxx"></script>
受同源策略影响,请求跨域的脚本,如果没有设置CORS,则会产生跨越错误。 下面验证一下
- type="module"的script标签,是否会把其所依赖的所有文件都下载
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./module/index.js" type="module"></script>
<title>Document</title>
</head>
<body>
</body>
</html>
新建两个文件,一个是index.js,一个是math.js index.js
import {math} from './math.js'
console.log(math(1,2));
math.js
export function add (a, b) {
return a + b;
}
浏览器请求js文件如下
请求跨域脚本
请求本地3000端口服务器的脚本,页面启动是在本地5500端口
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="http://localhost:3000/script" type="module"></script>
<title>Document</title>
</head>
<body>
<h1>Hello World!</h1>
<h1>Hello Guys!</h1>
</body>
</html>
浏览器会产生如下错误
script标签添加不同步属性被下载执行如下图
安全相关属性
crossorigin
- 页面引入跨域脚本,如果这个脚本有错误,因为浏览器的限制,拿不到错误信息。当本地尝试使用window.onerror记录脚本的错误时,跨域脚本的错误只会返回"script error"。 HTML5新的规定是可以允许本地获取到跨域脚本的错误信息但有两个条件
- 跨域脚本的服务器必须通过"Access-control-Allow-origin"头信息允许当前域名可以获取错误信息
- 当前域名的script标签必须指明src属性指定的地址是支持跨域,也就是crossorigin属性
- crossorigin属性设计到网络安全问题:加入允许获取到跨域脚本的错误信息,那么我们通过报错信息的不一致,可以推断出当前访问的用户使用痕迹。
crossorigin属性值
- anonymous:如果使用这个值,就会在请求中的header中带上origin属性,但请求不会带上cookie和其他的一些认证信息
- use-credentials:同时会在跨域请求中带上cookie和其他一些认证信息。
在html的标签中,有些标签是自带跨域功能,如audio,img,link,script,video,它们的src属性可以是任意源的链接,并且均可加载。 但如果在标签中添加了 crossorigin属性,那么浏览器案在去解析这些跨域资源的时候,就不会以它自带跨域功能区加载,而是使用CORS方式加载,像Ajax一样,需要服务器设置跨域头,才能完成加载,否则会报跨域问题,导致加载失败。
integrity
<script crossorigin="anonymous" integrity="sha256-PJJrxrJLzT6CCz1jDfQXTRWOO9zmemDQbmLtSlFQluc=" src="https://assets-cdn.github.com/assets/frameworks-3c926bc6b24bcd3e820b3d630df4174d158e3bdce67a60d06e62ed4a515096e7.js"></script>
integrity属性是资源完整性规范的一部分,它允许你为script提供一个hash,用来进行验签,检验加载的JavaScript文件是否完整。intergrity的作用有:
- 减少由【托管在CDN的资源被篡改】而引入的XSS 风险
- 减少通信过程资源被篡改而引入的XSS风险(同时使用https会更保险)
- 可以通过一些技术手段,不执行有脏数据的CDN资源,同时去源站下载对应资源
事件处理
onload
script脚本被加载并执行完之后调用。 Chrome浏览器下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function handleOnload() {
console.log('Script onload');
}
</script>
<script src="./onloadTest.js" onload="handleOnload()"></script>
</html>
onLoadTest.js
console.log('onload script');
浏览器输出
onerror
- 请求的脚本同域
三个参数是onerror 事件的参数。三个参数分别代表:msg(错误消息)、url(发生错误的页面的 url)、line(发生错误的代码行) - 请求的脚本不同域
只会打印出script error,而不会有任何详细的错误信息,除非添加属性 crossorigin,且符合相应条件。
JSONP
通过动态生成script标签,获取跨越get请求数据。 实现原理
- 前端定义一个解析函数(如: jsonpCallback = function (res) {})
- 通过params的形式包装script标签的请求参数,并且声明执行函数(如cb=jsonpCallback)
- 后端获取到前端声明的执行函数(jsonpCallback),并以带上参数且调用执行函数的方式传递给前端
- 前端在script标签返回资源的时候就会去执行jsonpCallback并通过回调函数的方式拿到数据了。 实现代码 前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
window.jsonpCallback = function (res) {
console.log(res)
}
</script>
<script src="http://localhost:8080/script"></script>
</html>
服务端
const server = require('express')()
const items = [{ id: 1, title: 'title1' }, { id: 2, title: 'title2' }]
server.get('/script', (req, res) => {
const title = items.find(item => item.id == 1)['title']
res.send(`jsonpCallback(${JSON.stringify({title})})`)
})
console.log('listen 8080...')
server.listen(8080);
浏览器输出如下
JQuery对其进行的封装,因为调用形式很像ajax,所以JQuery将JSONP封装进ajax中,但请注意!JSONP与ajax不一样!
$.ajax({
url: 'http://example.com:8080/script
dataType: 'JSONP'
success: function(response) {}
})