共计 2404 个字符,预计需要花费 7 分钟才能阅读完成。
对于统计页面数据这样的情景(俗称埋点),我们常用的方式就是动态创建 <img> 或 <script>,至于原因,一般有以下几点:
1. 埋点一般不用关心请求的结果
2. 可以实现跨域请求
3. 无需使用 ajax 就能达到发请求的目的
4. 都是原生实现,兼容性好
现就两种方式做一下对比和总结:
一、用法
1. 动态创建 <img>
方式 1:通过 img 标签
function sendByImg(src) {var img = document.createElement("img");
img.src = src;
}
方式 2:通过 Image 对象
function sendByImage(src) {var img = new Image();
img.src = src;
}
2. 动态创建 <script>
只有一种方式:通过 <script> 标签
function sendByScript(src){var script = document.createElement("script");
script.src = src;
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(script);
}
二、区别
区别 1: 如果要触发请求,动态创建的 <script> 必须要插入到 DOM 中,而动态创建的 <img> 则不需要
演示代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Img VS Script</title>
</head>
<body>
<h3>
请观察浏览器中的 Network 和 Elements!</h3>
<script>
function sendByScript(src){var script = document.createElement("script");
script.src = src;
}
function sendByScriptInsertDOM(src){var script = document.createElement("script");
script.src = src;
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(script);
}
function sendByImage(src) {var img = new Image();
img.src = src;
}
function sendByImg(src) {var img = document.createElement("img");
img.src = src;
}
function sendRequest(src) {
// 一、通过 script:// 不插入 DOM,不会触发请求
sendByScript(src + '/byScript');
// 插到 DOM 中,会触发请求
sendByScriptInsertDOM(src + '/byScriptDOM');
// 二、通过 img:// 不插入 DOM,不会触发请求
sendByImage(src+ '/byImage');
// 不插入 DOM,不会触发请求
sendByImg(src+ '/byImg');
}
sendRequest('https://wwww.baidu.com')
</script>
</body>
</html>
用 chrome 浏览器打开此 HTML 文件,查看 network:
可以看到,插入 DOM 中的 <script> 触发来请求,而未插入 DOM 中则没有发起请求;动态创建的 <img> 的两种方式没有插入 DOM,都触发请求。
那么问题来了, 为什么动态创建的 <script> 必须要插入 DOM 中才会触发请求,而动态创建的 <img> 则不用?
关于这个问题,在网上查了很久也没有找到特别强有力的解释。有人说是因为这两种标签本身的特性决定的:<img> 作为展示性标签加载的是图片资源,其对应的地址可以直接访问就能得到资源;<script> 往往加载的是 JavaScript 代码,需要网页这样的执行环境。个人觉得此解释有些牵强,但也没有找到更权威的解释。如果有更好的观点,欢迎留言。
区别 2: 动态创建的 <script> 可以对请求结果进行处理,而动态创建的 <img> 做不到
JSONP 的实现原理就是借助动态创建 <script> 标签,并对返回结果进行处理。
至此,我当时在想,那么借助 jQuery 发送 JSONP 请求,岂不是每发一次就要在页面上创建一个 <script> 标签?观察结果显示并没有。于是去看了下 jQuery 的源码:
如图所示,在 <script> 加载完成或出错后将标签移除。所以,我们在用动态创建 <script> 方式发送请求也可以参考这种方式。
三、选择哪种方式?
单纯从发送请求的角度看,理论上两者没有特别大的差异,但有一点一定要注意: 动态创建 <img> 的方式在浏览器禁用图片模式下不会触发请求
。
例如,在 chrome 浏览器中设置禁图模式(设置 > 高级设置 > 隐私设置和安全性 > 网站设置 > 图片 > 显示全部 去掉),结果:
只有动态创建 <script> 的方式有请求,动态创建 <img> 的方式没有任何请求记录。
综上,从扩展性和兼容性上看, 动态创建 <script> 的方式是首选
。
四、总结
对于发送埋点请求这种应用场景,我们有两种简单的处理方式:动态创建 <script> 和 <img> 两种方式,两者最大的差异是:动态创建的 <script> 必须插入到 DOM 中才能触发请求,而动态创建 <img> 的方式则不需要。但动态创建 <img> 的方式有个致命缺陷:浏览器设置了禁止图片显示时,无法触发请求。所以,对于封装埋点库的时候,动态创建 <script> 的方式是首选,而且可以参考 jQuery,在请求记载完成后对 <script> 进行清除。