前言
对于前端程序员来说,live-server插件估计都不陌生,想起刚开始学前端的时候,每次改完代码,浏览器直接就能自动更新页面,真的很方便。一次偶然的机会,我在浏览器中检查代码的时候发现,诶?这段代码是哪里来的?
逐层拆解
看着这段似懂非懂的代码,我决定花上几个晚上认真研究一下,不说废话,一起来看代码。
js
if ("WebSocket" in window) {
} else {
console.error(
"Upgrade your browser. This Browser is NOT supported WebSocket for Live-Reloading."
);}
先来看最外层,这个好理解,判断浏览器的全局对象window是否包含WebSocket;简单来说,WebSocket是浏览器和服务器进行双向即时通讯的一项技术,如果浏览器没有这个对象,就要报错了。
接下来作者用了一个立即执行的匿名函数,作者在代码中使用了var关键字;我们都知道,在函数中用var定义的变量是不会影响到函数以外的变量的,这样就很好地避免污染全局变量。
分析refreshCSS
随后作者定义了一个refreshCSS方法,见名知意,这是用来刷新css样式的。这里要注意一下[].slice.call(),call能够让slice方法的this指向传入的伪数组,对于slice方法来说,不传递参数的时候它可以返回原数组,那么通过这个方法我们得到了一个数组sheets,于此同时,我们还获取了一个parentElement。
js
function refreshCSS() {
//使用[].slice.call()来将获取到所有的link标签塞到一个数组里面
var sheets = [].slice.call(document.getElementsByTagName("link"));
//获取第一个head标签
var head = document.getElementsByTagName("head")[0];
for (var i = 0; i < sheets.length; ++i) {
var elem = sheets[i];
//这是考虑到了有些浏览器可能不支持parentElement的情况
var parent = elem.parentElement || head;
//获取一个移除了link本身的父元素
parent.removeChild(elem);
var rel = elem.rel;
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
}
parent.appendChild(elem);
}
}
这里的代码可能比较晦涩难懂,总的来说,它的含义是先判断其是否是个样式表,然后通过修改文件名来获取最新样式表的。
js
var rel = elem.rel;
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
//移除掉原来的缓存参数
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
//使用时间戳替换成最新的缓存参数
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
}
//将更新了href属性的link标签重新放入该元素
parent.appendChild(elem);
我详细讲一下为什么live-server要给css文件加上一个缓存参数,在正常情况下,浏览器为了节省请求频率,可能会使用本地的文件缓存,如果文件名没有改变,浏览器就会继续使用缓存下来的文件,举个例子,我引入一个link标签,刚开始浏览器页面是这样的。
但是当我改变了test.css文件内容之后,live-server就给它自动加上缓存参数了,我们可以明白这个__cacheOverride就是为了防止浏览器复用之前的样式表的,这样,浏览器看到文件名改变后,就会重新请求最新的样式表,因此,每一次样式表的改变,都对应着独一无二的时间戳。
除此之外,样式表的名字还可能长这样test.css?t=1&_cacheOverride=1704557378563
因此,作者需要通过正则(&|\?)
判断其是否是第一个参数,而下面的url.indexOf('?') >= 0 ? '&' : '?'
也是同理。
通过执行refreshCSS方法,我们可以获取到一个最新的样式表。
建立WebSocket链接
接下来,创建一个新的websocket链接,作者做了一个简单的判断,如果协议名是http:,那么就使用 ws://
协议,否则就使用更安全的wss://
协议。他的最终请求地址是这样的ws://127.0.0.1:5500/test2.html/ws
。
在这里,当服务器端文件发生了变化后,服务器会判断样式表是否发生了变化,如果是样式表发生了变化,则返回消息msg.data == 'refreshcss'
,浏览器因为link标签href的变化再次请求了test.css,如果是页面发生了变化,则返回reload
,浏览器直接刷新页面重新请求就好了。
js
var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
var address = protocol + window.location.host + window.location.pathname + '/ws';
var socket = new WebSocket(address);
socket.onmessage = function (msg) {
if (msg.data == 'reload') window.location.reload();
else if (msg.data == 'refreshcss') refreshCSS();
};
这是我修改了样式表之后的截图。
最后,这段代码就比较简单了,浏览器执行到这里,表明已经启用热重载了, 但是要告知用户一下,于是就在sessionStorage插了一个flag,并打印console.log('Live reload enabled.');
。
js
if (sessionStorage && !sessionStorage.getItem('IsThisFirstTime_Log_From_LiveServer')) {
console.log('Live reload enabled.');
sessionStorage.setItem('IsThisFirstTime_Log_From_LiveServer', true);
}
总结
讲了这么多,估计脑袋也晕晕的(@_@;),那咱们再总结一下:
- 我们在使用vscode的live-server插件打开文件的时候,它会自动帮我们创建一个端口号为5500的服务器,同时对我们的html文件注入一段代码。
- 我们的浏览器接收到文件后就会与服务器建立websocket连接,同时会在在控制台打印一下Live reload enabled。
- 当我们修改代码的时候,服务器会判断我们修改的是否是样式表,如果修改了样式表,就会通过websocket告诉我们refreshCSS,如果改变的是其他的文件(包括js文件),服务器就会返回reload。
- 浏览器在收到websocket请求以后,判断msg.data如果reload,则刷新浏览器重新请求数据,如果是refreshCSS,则更改css文件的缓存参数来重新请求样式表。
最后,全部代码在这里~
js
// <![CDATA[ <-- For SVG support
if ('WebSocket' in window) {
(function () {
function refreshCSS() {
var sheets = [].slice.call(document.getElementsByTagName("link"));
var head = document.getElementsByTagName("head")[0];
for (var i = 0; i < sheets.length; ++i) {
var elem = sheets[i];
var parent = elem.parentElement || head;
parent.removeChild(elem);
var rel = elem.rel;
if (elem.href && typeof rel != "string" || rel.length == 0 || rel.toLowerCase() == "stylesheet") {
var url = elem.href.replace(/(&|\?)_cacheOverride=\d+/, '');
elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf());
}
parent.appendChild(elem);
}
}
var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://';
var address = protocol + window.location.host + window.location.pathname + '/ws';
var socket = new WebSocket(address);
socket.onmessage = function (msg) {
if (msg.data == 'reload') window.location.reload();
else if (msg.data == 'refreshcss') refreshCSS();
};
if (sessionStorage && !sessionStorage.getItem('IsThisFirstTime_Log_From_LiveServer')) {
console.log('Live reload enabled.');
sessionStorage.setItem('IsThisFirstTime_Log_From_LiveServer', true);
}
})();
}
else {
console.error('Upgrade your browser. This Browser is NOT supported WebSocket for Live-Reloading.');
}
// ]]>