學了點技術,我要開始裝X了
今天不看技術文,而是帶大家裝x,就從 獨釣寒江雪 的《前端裝逼技巧 108 式》偷師幾招吧~ 絕對讓你過把癮!
作者:獨釣寒江雪
原文:https://segmentfault.com/a/1190000040437981
第一式:你見過頁面跳舞嗎?
還記得魔性的小蘋果和抖音嗎,作為前端的你,有想過讓頁面也 High 起來、舞動起來嗎?
先看效果:

下面這段代碼可直接在控制臺執(zhí)行,略長??芍苯犹奖拘」?jié)末,使用簡短 JS 引入的方式進行體驗(帶音樂效果喲)
setTimeout(letDance, 1000);
var bgmSrc =
'https://nd002723.github.io/carnival/audio/Martin%20Jensen%20-%20Fox%20(Loop%20Remix).mp3';
var cssHref = 'https://nd002723.github.io/carnival/css/high.css';
function letDance() {
function loadCss() {
//將css文件引入頁面
var myCss = document.createElement('link');
myCss.setAttribute('type', 'text/css');
myCss.setAttribute('rel', 'stylesheet');
myCss.setAttribute('href', cssHref); //css文件地址
myCss.setAttribute('class', l);
document.body.appendChild(myCss);
}
function h() {
var e = document.getElementsByClassName(l);
for (var t = 0; t < e.length; t++) {
document.body.removeChild(e[t]);
}
}
function p() {
var e = document.createElement('div');
e.setAttribute('class', a);
document.body.appendChild(e);
setTimeout(function () {
document.body.removeChild(e);
}, 100);
}
function getSize(e) {
//獲取目標的寬高
return {
height: e.offsetHeight,
width: e.offsetWidth,
};
}
function checkSize(i) {
//判斷目標大小是否符合要求
var s = getSize(i); //獲取目標的寬高
return (
s.height > minHeight &&
s.height < maxHeight &&
s.width > minWidth &&
s.width < maxWidth
); //判斷目標是否符合條件
}
function m(e) {
var t = e;
var n = 0;
while (!!t) {
n += t.offsetTop;
t = t.offsetParent;
}
return n;
}
function g() {
var e = document.documentElement;
if (!!window.innerWidth) {
return window.innerHeight;
} else if (e && !isNaN(e.clientHeight)) {
return e.clientHeight;
}
return 0;
}
function y() {
if (window.pageYOffset) {
return window.pageYOffset;
}
return Math.max(
document.documentElement.scrollTop,
document.body.scrollTop
);
}
function E(e) {
var t = m(e);
return t >= w && t <= b + w;
}
function setBgm() {
//設置音樂
var e = document.createElement('audio');
e.setAttribute('class', l);
e.src = bgmSrc; //bgm地址
e.loop = false;
e.addEventListener(
'canplay',
function () {
setTimeout(function () {
x(k);
}, 500);
setTimeout(function () {
N();
p();
for (var e = 0; e < O.length; e++) {
T(O[e]);
}
}, 15500);
},
true
);
e.addEventListener(
'ended',
function () {
N();
h();
},
true
);
e.innerHTML =
' <p>If you are reading this, it is because your browser does not support the audio element. We recommend that you get a new browser.</p> <p>';
document.body.appendChild(e);
e.play();
}
function x(e) {
e.className += ' ' + s + ' ' + o;
}
function T(e) {
e.className += ' ' + s + ' ' + u[Math.floor(Math.random() * u.length)];
}
function N() {
var e = document.getElementsByClassName(s);
var t = new RegExp('\\b' + s + '\\b');
for (var n = 0; n < e.length; ) {
e[n].className = e[n].className.replace(t, '');
}
}
var minHeight = 3; //最小高度
var minWidth = 3; //最小寬度
var maxHeight = 800; //最大高度
var maxWidth = 1400; //最大寬度
var s = 'mw-harlem_shake_me';
var o = 'im_first';
var u = ['im_drunk', 'im_baked', 'im_trippin', 'im_blown'];
var a = 'mw-strobe_light';
var l = 'mw_added_css'; //最終要移除的css
var b = g();
var w = y();
var C = document.getElementsByTagName('*');
var k = null;
for (var L = 0; L < C.length; L++) {
var targetDiv = C[L];
if (checkSize(targetDiv)) {
if (E(targetDiv)) {
k = targetDiv;
break;
}
}
}
if (targetDiv === null) {
//如果沒找到合適大小的
console.warn('沒能找到合適的大小. 換一個頁面試試?.');
return;
}
loadCss(); //將自定義css文件引入頁面
setBgm(); //添加背景音樂
var O = [];
for (var L = 0; L < C.length; L++) {
var targetDiv = C[L];
if (checkSize(targetDiv)) {
O.push(targetDiv);
}
}
//網頁整體傾斜效果(這塊兒本來是JQuery實現的,為了避免引入JQuery,做了改動。)
var style = document.createElement('style');
style.type = 'text/css';
try {
style.appendChild(
document.createTextNode(
'body{overflow-x:hidden;transform: rotate(1deg);-webkit-transform: rotate(1deg);-moz-transform: rotate(1deg);-o-transform: rotate(1deg);-ms-transform: rotate(1deg)}'
)
);
} catch (ex) {
style.styleSheet.cssText = 'body{background-color:red}'; //針對IE
}
var head = document.getElementsByTagName('head')[0];
head.appendChild(style);
}
或者更簡潔一點,在頁面 URL 欄或者控制臺鍵入以下代碼直接體驗:
在瀏覽器地址欄黏貼以下內容的話,有三點需要注意,一是必須是已有內容的頁面;二是如果是通過復制黏貼代碼到瀏覽器地址欄的話,IE 及 Chrome會自動去掉代碼開頭的
javascript:,所以需要手動添加起來才能正確執(zhí)行,而 Firefox 中雖然不會自動去掉,但它根本就不支持在地址欄運行 JS 代碼;三是引用的carnival.js會依賴JQuery(沒有的話也沒事,只是頁面少了一個傾斜的效果)。
javascript: void (function () {
var d = document,
a = 'setAttribute',
s = d.createElement('script');
s[a]('tyle', 'text/javascript' "a");
s[a]('src', 'https://nd002723.github.io/carnival/js/carnival.js' "a");
d.head.appendChild(s);
})();
一個能讓你的網站 high 起來的 js[1]
第二式:一個可以拿來裝逼的題庫
作為程序員,需求無處不在,bug 無處不在,自然,裝逼也可以無處不在?;叵肽愕拿恳淮蚊嬖?,是否曾被面試官鄙視或者秀一臉?回想你和同事的每一次交流,是否曾被同事展示的古怪面試題或者奇技淫巧搞的無語難受 ??,想秀回去卻苦于一時“腦中羞澀”,JavaScript Puzzlers![2]就是一個收錄的各種表態(tài) JavaScript 題目的網站,有了它,現在媽媽再也不怕我找不到可以裝逼的代碼/題目了。
我們不妨先看幾道題目:
var min = Math.min(),
max = Math.max();
min < max; // 答案: false
// 有趣的是, Math.min 不傳參數返回 Infinity, Math.max 不傳參數返回 -Infinity ??
// 這個還是相對容易的
var name = 'World!';
(function () {
if (typeof name === 'undefined') {
var name = 'Jack';
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
// 答案:Goodbye Jack
'1 2 3'.replace(/\d/g, parseInt); // 答案: 1, NaN, 3
不,我是一個前端工程師,工程師的事兒,能叫裝逼么?也許它是沒有太大用處的腦筋急轉彎,但是這叫小技巧后面藏著的大智慧,哈哈~
話說回來,這些變態(tài)題目涉及的知識點很廣,如果你能掌握這些題目背后的原理,也許就會對 JavaScript 的理解有一個不小的提升,下面將題目奉上,供諸君參閱:
目前由于未知原因,也許是去裝逼答題的太多了吧 ??,JavaScript Puzzlers![3]網站已不可訪問,所幸您還可以通過下面兩個 GitHub 鏈接查看具體的題目及解析。
44 個 Javascript 變態(tài)題解析 (上)[4] 44 個 Javascript 變態(tài)題解析 (下)[5] JavaScript Puzzlers![6]
第三式:瀏覽器地址欄,????克拉斯
作為一名前端開發(fā)者,我們甚至每天與瀏覽器相伴的時間比女朋友/男朋友(如果有的話 ??)陪伴你的都要久,想想那每一個令人“不是那么期待”的早晨,每一個爭分奪秒完成任務的黃昏,只有瀏覽器和編輯器一直是你忠實的伙伴,那么,你了解瀏覽器嗎?了解那個每天都要輸入http://localhost:3000/的地址欄嗎?本節(jié)帶你認識那些關于地址欄的好玩兒的細節(jié)。
瀏覽器地址欄運行 JavaScript 代碼
對,你沒看錯,在“瀏覽器地址欄運行 JavaScript 代碼”,做法是以javascript:開頭后跟要執(zhí)行的語句。需要注意的是如果是通過復制、黏貼代碼到瀏覽器地址欄的話,IE 及 Chrome 會自動去掉代碼開頭的 javascript:,所以需要手動添加起來才能正確執(zhí)行。并且,你需要在網站的地址欄運行javascript:,而不能在新打開的空標簽頁面上運行,那樣也不會生效。
// 點擊確定后,會接著彈出:孤舟蓑笠翁,獨釣寒江雪,點擊取消則不會彈出
javascript: if (confirm('千山鳥飛絕,萬徑人蹤滅'))
alert('孤舟蓑笠翁,獨釣寒江雪');

瀏覽器地址欄運行 HTML 代碼
如果說在“瀏覽器地址欄運行 JavaScript 代碼”知道的人還算多的話,在“瀏覽器地址欄運行 HTML 代碼”知道的人就要少一些了,在非 IE 內核的瀏覽器地址欄可以直接運行 HTML 代碼!在地址欄輸入以下代碼然后回車運行,會出現下圖所示的頁面:
data:text/html,
<h1>Nothing is given, Everything is earned!</h1>

瀏覽器可以是你的記事本
這個還是在瀏覽器地址欄上面做文章,將以下代碼復制粘貼到瀏覽器地址欄,運行后瀏覽器就變成了一個簡單原始的編輯器,話不多說,直接試試吧。
data:text/html,
<html contenteditable></html>

另外,補充一個萬能的瀏覽器撤銷關閉頁快捷鍵:MAC OS 下,command + shift + t;Windows:ctrl + shift + t。這個重新打開被關閉的頁面的快捷鍵,可以一直按,會依次恢復被關閉的頁面喲~,要不,看看小伙伴都干了些什么,看看會不會被打 ??
參考資料:這些鮮為人知的前端冷知識,你都 GET 了嗎?
第四式:讓汪峰輕松走上百度熱搜
“汪峰上頭條”一直是娛樂界里的一個梗,每次在微博熱搜榜看到汪峰,以為能登頂榜首,不想都會有其他的爆點壓制住,不管粉絲怎么努力,汪峰還是遲遲上不了頭條!
最后“幫汪峰上頭條”反而成了熱搜,甚至被收錄入百度百科[7]??。

蒽 ??,如果汪峰是個前端工程師,那熱搜的事兒不是分分鐘搞定嗎?就算不懂 HTML,只要知道 HTML5 的contenteditable屬性,控制臺輸入一個document.body.contentEditable='true';,熱搜還不是隨便改,想要多少有多少 ??。

蒽 ??,學了這招,甚至你可以輕松通過熱搜向對象告白了...不過作為專業(yè)的 web 應用和網站開發(fā)人員,我們怎能滿足于此呢?
同理,也是利用了 HTML5 中的contenteditable屬性,巧妙的在 body 增加一個可編輯的 style 標簽,那這個樣式,我們在頁面上都可以修改了,想想就...沒有太多卵用,哈哈哈 ??,不過話說回來,這個屬性在部分富文本編輯器上還是很有用處的。很多富文本編輯器就是基于contenteditable實現的,具體可以參考深入淺出 contenteditable 富文本編輯器[8]。
<style style="display:block; height:50px;" contenteditable>
body {
background: red;
}
</style>
效果圖(圖片來自下面的參考文章):

你以為contenteditable只有true和false?其實它的可選值包括:
contenteditable="" contenteditable="events" contenteditable="caret" contenteditable="plaintext-only" contenteditable="true" contenteditable="false"
除了 HTML5 的contenteditable屬性,其實還有一個不常用的 css 屬性 —— user-modify可以實現類似的效果,user-modify可取值為以下四個:
read-onlyread-writewrite-onlyread-write-plaintext-only
其中,write-only不用在意,當下這個年代,基本上沒有瀏覽器支持,以后估計也不會有。read-only表示只讀,就是普通元素的默認狀態(tài)。然后,read-write和read-write-plaintext-only會讓元素表現得像個文本域一樣,可以 focus 以及輸入內容,前者可以輸入富文本,而后者只能輸入純文本。
具體效果你可以通過審查元素,給元素添加 CSS 樣式查看,也可以直接看張鑫旭大佬的CSS user-modify 屬性行為表現測試實例頁面[9]demo。
這些鮮為人知的前端冷知識,你都 GET 了嗎? 小 tip: 如何讓 contenteditable 元素只能輸入純文本[10]
第五式:鼠標消失術
這個一個隱藏頁面上鼠標的技巧,其實不值一提,也沒有什么卵用。可以直接復制以下代碼到控制臺試試(此時如果在頁面上吊起右鍵菜單,還是可以看見鼠標的):
var style = document.createElement('style');
document.head.appendChild(style);
style.type = 'text/css';
style.styleSheet
? (style.styleSheet.cssText = '* { cursor: none;!important; }')
: style.appendChild(
document.createTextNode('* { cursor: none;!important; }')
);
原理非常簡單,設置cursor屬性為none即可:
* {
cursor: none !important;
}
或者在瀏覽器地址欄輸入以下內容:
IE 和 Chrome 會自動隱去前面的
javascript:然后把后面的部分當做查詢字段。你需要復制以下代碼黏貼后手動在前面加上javascript:, 然后回車效果就出來了。
javascript:function play(){var style = document.createElement('style');document.head.appendChild(style);style.type = 'text/css';style.styleSheet ? (style.styleSheet.cssText = '* { cursor: none !important; }') : style.appendChild(document.createTextNode('* { cursor: none !important; }'));}play();
參考資料:前端隨便玩兒[11]
第六式:讓你的網站模糊不清
也許你經常碰到這樣的頁面,當沒有登錄的時候,只能看到下面的效果:

當然,這里的模糊不清效果是通過背景圖占位來做的,其實如果不考慮安全性、被破解等因素,我們也完全可以使用 css 來實現類似的效果。
javascript:function play(){var style = document.createElement('style');document.head.appendChild(style);style.type = 'text/css';style.styleSheet ? (style.styleSheet.cssText = '* { color: transparent !important; text-shadow: #333 0 0 10px !important; }') : style.appendChild(document.createTextNode('* { color: transparent !important; text-shadow: #333 0 0 10px !important; }'));}play();
顯而易見,這里主要是使用了以下兩個 CSS 屬性:
color: transparent !important;
text-shadow: #333 0 0 10px !important;

參考資料:前端隨便玩兒[12]
第七式:讓網站屏蔽開發(fā)者工具
瀏覽器開發(fā)者工具是給我們這些專業(yè)的 web 應用和網站開發(fā)人員使用的工具(當然,到底專不專業(yè),自己心里都會有點 B 數 ??),它的作用在于,幫助開發(fā)人員審查元素、對網頁進行布局、幫助前端工程師更好的斷點調試等,還可以使用工具查看網頁加載過程,進行性能分析和優(yōu)化,獲取網頁請求等(這個過程也叫做抓包)。筆者可以豪不違心的說,離開了開發(fā)者工具,火熱而有趣的的前端開發(fā)將變得索然無味,因為我感受不到有比瀏覽器自帶開發(fā)者工具更趁手的利器。當然,據說真正的大神寫出的 JS 和 CSS 都是不需要進行調試的,那我們另當別論,顯然我和真正的大神不是一類人。
既然開發(fā)者工具這么可愛、這么好用,那我們?yōu)槭裁匆帘嗡兀俊?可能是因為我們用過了、完成了開發(fā)工作,不想讓別人有機會發(fā)現那些我們自己也看不懂的代碼以及其中蘊含的商業(yè)機密吧,哼哼,果然是渣男~
那么,到底該如何做一個有能力可以屏蔽開發(fā)者工具的渣男呢?
也許依據打開控制臺的幾種方式,你自然就想到了:
監(jiān)聽 F12; 監(jiān)聽和禁止右鍵菜單(因為右鍵菜單里有“檢查”選項可以打開控制臺);
但是這樣真的就行了嗎?不,我們依然可以通過瀏覽器右上角的三個點,找到更多工具中的開發(fā)者工具,然后點擊打開。

那我們該以什么思路解決這一問題呢?網上解法有很多,有些非主流,有些由于瀏覽器的升級已失效,相關思路及鏈接會在本小節(jié)末尾給出,這里只說兩個我覺得還不錯的方法:
const im = new Image();
Object.defineProperty(im, 'id', {
get: function () {
// 在這里放入你的代碼,比如我這里會讓他跳到百度
console.log('Console is opened');
window.location.href = 'http://www.baidu.com';
},
});
console.log(im); //谷歌最新版失效
let num = 0; //谷歌最新版有效
const devtools = new Date();
devtools.toString = function () {
num++;
if (num > 1) {
// 在這里放入你的代碼,比如我這里會讓他跳到百度
console.log('Console is opened');
// window.location.href = "http://www.baidu.com";
return Date.prototype.toString.call(devtools); // 返回devtools結果(這一步不是必須的)
}
};
console.log(devtools);
以上方法的核心原理在于一點:只有打開控制臺,才會執(zhí)行 console 方法,而使用console打印Date,會調用Date的toString方法,我們對toString方法做了改寫。如果直接注入代碼,如console = 1,以上代碼將失效。
其他幾種思路包括:
監(jiān)聽 F12 或者 shift+ctrl+i 調起開發(fā)者工具(無法防止先打開開發(fā)者工具,然后在地址欄輸入網址的訪問); 監(jiān)聽和禁止右鍵菜單(因為右鍵菜單里有“檢查”選項可以打開控制臺); 監(jiān)視窗口大?。ㄟm用于未將開發(fā)工具設置成獨立窗口的情況); 監(jiān)視 DOM 修改(適用于水印保護等場景); 利用 debugger 的特性,無限遞歸。
除了以上方法,也有諸如devtools-detector[13]一類的插件,用來對開發(fā)者工具打開的監(jiān)測問題,在此不過多贅述。
其他參考:
網站如何檢測到是否開啟開發(fā)者工具?[14] JS 檢測,禁用瀏覽器開發(fā)者工具之 6 大方法探討[15] 前端開發(fā)中如何在 JS 文件中檢測用戶瀏覽器是否打開了調試面板(F12 打開開發(fā)者工具)?[16] 網站這樣來屏蔽開發(fā)者工具,不比監(jiān)聽 MouseDown 舒服?[17] JS 禁止打開控制臺[18]
第八式:代碼性能大pk
性能、各種寫法的優(yōu)劣是我們在日常開發(fā)、技術討論中最常提及和關注的。在寫一段代碼的時候,很多同學可能都會想要知道它的性能到底如何,和其他寫法比起來哪個更快,但卻苦于沒有好用的工具,只能手動測試運行時間,這樣一個是不方便,二是因為樣本數太少誤差較大。那么,除了對原理解析這種理論性的東西之外,我們可以怎樣簡潔、清晰、高效的對比各種不同 JS 寫法的執(zhí)行速度和性能呢?這就涉及到 JS性能測試工具了。
JS 性能測試工具原理一般是將給定的測試用例循環(huán)在指定環(huán)境下運行許多次,然后輸出比對結果。JSBench.Me[19]就是這樣一款在線代碼性能測試利器。

同時也有一款 npm 插件 —— 強大的基準測試庫Benchmark.js[20]官方說:
Benchmark.js 是一個強大的**基準測試**[21]庫,支持高分辨率計時器并返回具有統(tǒng)計意義的結果。正如在 jsPerf 上看到的那樣。
上文提到的jsPerf[22]本來是我想要介紹的一個工具,奈何這款工具無情的拒絕了我 ??。

所以,我們還是看看Benchmark.js這個庫的使用吧:
var suite = new Benchmark.Suite();
// add tests
suite
.add('RegExp#test', function () {
/o/.test('Hello World!');
})
.add('String#indexOf', function () {
'Hello World!'.indexOf('o') > -1;
})
// add listeners
.on('cycle', function (event) {
console.log(String(event.target));
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').map('name'));
})
// run async
.run({ async: true });
// logs:
// => RegExp#test x 4,161,532 +-0.99% (59 cycles)
// => String#indexOf x 6,139,623 +-1.00% (131 cycles)
// => Fastest is String#indexOf
第九式:瀏覽器也會摸魚 ?? ?
瀏覽器一幀都會干些什么?
我們都知道,頁面的內容都是一幀一幀繪制出來的,瀏覽器刷新率代表瀏覽器一秒繪制多少幀。原則上說 1s 內繪制的幀數也多,畫面表現就也細膩。
電影放映的標準是每秒放映 24 幀,也就是說電影每秒放映 24 幅畫面,以達到動畫的效果,超過 24 幀/s 連續(xù)的變化,視覺暫留就會將靜態(tài)的畫“動”起來。研究表明,人眼承受的極限為每秒 55 幀,還有研究表明,每秒 60 幀以上可以明顯提升觀眾的觀影感受。每秒 120 幀是每秒 24 幀的 5 倍,采用這樣的拍攝技術可以讓畫面更加栩栩如生,讓觀眾仿佛置身其中,給人一種似真似幻的感覺。
目前瀏覽器大多是 60Hz(60 幀/s),每一幀耗時也就是在 16.6ms 左右。那么在這一幀的(16.6ms) 過程中瀏覽器又干了些什么呢?

通過上面這張圖可以清楚的知道,瀏覽器一幀會經過下面這幾個過程:
接受輸入事件,處理用戶的交互,如點擊、觸碰、滾動等事件 執(zhí)行事件回調 開始一幀 執(zhí)行 RAF ( RequestAnimationFrame)頁面布局,樣式計算 繪制渲染 執(zhí)行 RIC ( RequestIdelCallback)
第七步的 RIC 事件不是每一幀結束都會執(zhí)行,只有在一幀的 16.6ms 中做完了前面 6 件事兒且還有剩余時間,才會執(zhí)行。如果一幀執(zhí)行結束后還有時間執(zhí)行 RIC 事件,那么下一幀需要在事件執(zhí)行結束才能繼續(xù)渲染,所以 RIC 執(zhí)行不要超過 30ms,如果長時間不將控制權交還給瀏覽器,會影響下一幀的渲染,導致頁面出現卡頓和事件響應不及時。
requestIdleCallback 的啟示
我們以瀏覽器是否有剩余時間作為任務中斷的標準,那么我們需要一種機制,當瀏覽器有剩余時間時通知我們。
requestIdleCallback((deadline) => {
// deadline 有兩個參數
// timeRemaining(): 當前幀還剩下多少時間,最大值50ms
// didTimeout: 是否超時
// 另外 requestIdleCallback 后如果跟上第二個參數 {timeout: ...} 則會強制瀏覽器在當前幀執(zhí)行完后執(zhí)行。
if (deadline.timeRemaining() > 0) {
// TODO
} else {
requestIdleCallback(otherTasks);
}
});
// 用法示例
let tasksNum = 10000;
requestIdleCallback(unImportWork);
function unImportWork(deadline) {
while (deadline.timeRemaining() && tasksNum > 0) {
console.log(`執(zhí)行了 ${10000 - tasksNum + 1}個任務`);
tasksNum--;
}
if (tasksNum > 0) {
// 在未來的幀中繼續(xù)執(zhí)行
requestIdleCallback(unImportWork);
}
}
其實部分瀏覽器已經實現了這個 API,這就是 requestIdleCallback。但是由于以下因素,Facebook 在 React 的重構升級中, 拋棄了 requestIdleCallback 的原生 API,而實現了功能更完備的 requestIdleCallbackpolyfill,這就是Scheduler。除了在空閑時觸發(fā)回調的功能外,Scheduler 還提供了多種調度優(yōu)先級供任務設置:
瀏覽器兼容性; 觸發(fā)頻率不穩(wěn)定,受很多因素影響。比如當我們的瀏覽器切換 tab 后,之前 tab 注冊的 requestIdleCallback 觸發(fā)的頻率會變得很低。
requestIdleCallback 的 callback 會在瀏覽器的空閑時間運行,而在w3c 文檔[23]里,空閑時間分兩種:
在執(zhí)行一段連續(xù)的動畫時,將給定幀提交到屏幕與開始處理下一幀之間的時間,這段時間內屬于空閑時間。在連續(xù)動畫和屏幕更新期間,此類空閑時間會頻繁發(fā)生,但通常會非常短(即,如果我們的屏幕是 60hz(1s 內屏幕刷新 60 次)的設備,小于 16 毫秒),如下圖所示。

另外一種空閑時間,當用戶屬于空閑狀態(tài)(沒有與網頁進行任何交互),并且屏幕中也沒有動畫執(zhí)行。此時空閑時間理論上是無限長的。但為了避免在不可預測的任務(例如處理用戶輸入)引起用戶可察覺的延遲,這些空閑時間段的長度應限制為最大值 50ms,一旦空閑期結束,瀏覽器可以安排另一個空閑期。

也就是說,即使瀏覽器一直處于空閑狀態(tài)的話,deadline.timeRemaining可以得到的最長時間,也是 50ms,這是 w3c 標準[24] 規(guī)定的。一些低優(yōu)先級的任務可使用 requestIdleCallback 等瀏覽器不忙的時候來執(zhí)行,同時因為時間有限,它所執(zhí)行的任務應該盡量是能夠量化,細分的微小任務。
50 ms 的最大截止時間來自一個
RESPONSETIME研究,該研究表明,對 100 毫秒內用戶輸入的響應通常被人類感知為瞬時的。將空閑期限限制為 50 ms 意味著即使用戶輸入在空閑任務開始后立即發(fā)生,用戶代理仍有剩余的 50 ms 時間來響應用戶輸入,而不會產生用戶可察覺的延遲。
當設備的性能越來越好,瀏覽器支持的效果越來越炫,瀏覽器的開發(fā)者開始越來越多的考慮使用原生 API 來處理一些之前特別占用性能的功能,自從最初的 requestAnimationFrame,InsterSectionObserver,到requestIdleCallback,對于前端的將來,充滿希望,沒錯,我們都會有“光明的未來”,哈哈 ??,關于瀏覽器的更多細節(jié),可以參考我之前的兩篇文章:
瀏覽器是如何工作的:Chrome V8 讓你更懂 JavaScript[25] 47 張圖帶你走進瀏覽器的世界[26]
拓展閱讀與參考
requestIdleCallback-后臺任務調度[27] 瀏覽器幀原理剖析[28] Accurately measuring layout on the web[29] Cooperative Scheduling of Background Tasks[30]
第十式:自制hash生成器
項目中,也許我們會遇到需要使用 JS 生成特定長度隨機字符串的需求,比如用來做 Hash 值、uuid、隨機碼等,除了可以借助一些庫和插件之外,其實部分場景下,我們完全可以自定義函數實現指定長度隨機字符串的生成。
簡潔版函數只需要兩行代碼:
/**
* 生成長度為len的包含a-z、A-Z、0-9的隨機字符串
*/
function generateStr(len = 18) {
// 一行代碼生成0-9、A-Z、a-z、總長度為62的字符數組
var arr = [...new Array(62)].map((item, i) =>
String.fromCharCode(i + (i < 10 ? 0 : i < 36 ? 7 : 13) + 48)
);
return [...new Array(len)]
.map(() => arr[Math.floor(Math.random() * arr.length)])
.join('');
}
generateStr(18);
如果擔心重復,則可以添加一個Map來緩存已經生成的字符串,每次返回時判斷一下:
/**
* 生成長度為len的包含a-z、A-Z、0-9的隨機字符串
*/
const cacheMap = new Map(); // 緩存已經生成過了的字符串
// 一行代碼生成0-9、A-Z、a-z、總長度為62的字符數組
const arr = [...new Array(62)].map((item, i) =>
String.fromCharCode(i + (i < 10 ? 0 : i < 36 ? 7 : 13) + 48)
);
function generateStr(len = 18) {
const str = [...new Array(len)]
.map(() => arr[Math.floor(Math.random() * arr.length)])
.join('');
if (cacheMap.has(str)) {
// 這里會有死循環(huán)的問題,比如下面的for循環(huán),i設置的大于62
console.log(cacheMap, str);
// i 值越大,len越小,重復的概率越大
return generateStr(len);
} else {
cacheMap.set(str, true);
return str;
}
}
for (let i = 0; i < 20; i++) {
// 長度選小一點,測試20次
// i設置的大于62會出現死循環(huán),可先算出排列組合數進行預防
// i 值越大,len越小,重復的概率越大,執(zhí)行時間越長
generateStr(1);
}
console.log(cacheMap);
1 行代碼生成指定長度數字:這種方法有缺點,低概率會出現位數不足的問題(原因是 0.00566 * 100000 = 566,會丟失前面的 0),不推薦使用。
// len 最多16,可能出現
function generateNum(len = 16) {
return Math.floor(Math.random() * Math.pow(10, len));
}
2 行代碼生成包含大小寫字母和數字的隨機字符串[31]
第十一式:如何在離開頁面時發(fā)送請求?
用戶卸載網頁的時候(關閉瀏覽器、刷新瀏覽器或者跳轉其他頁面時),有時需要向服務器發(fā)送一些統(tǒng)計數據;同時,前端在做異常監(jiān)控、統(tǒng)計頁面訪問時長時,也會需要在頁面崩潰、關閉的時候發(fā)送請求。很自然的做法是在 unload 事件或 beforeunload 事件的監(jiān)聽函數里面,使用 XMLHttpRequest 對象發(fā)送數據。但是,這樣做不是很可靠,因為 XMLHttpRequest 對象是異步發(fā)送,很可能在它即將發(fā)送的時候,頁面和相關資源已經卸載,會引起 function not found 的錯誤,從而導致發(fā)送取消或者發(fā)送失敗。
解決方法就是 AJAX 通信改成同步發(fā)送,即只有發(fā)送完成,頁面才能卸載。但是,很多瀏覽器已經不支持同步的 XMLHttpRequest 對象了(即 open()方法的第三個參數為 false):
window.addEventListener('unload', logData, false);
function logData() {
var client = new XMLHttpRequest();
// 第三個參數表示同步發(fā)送
client.open('POST', '/log', false);
client.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
client.send(analyticsData);
}
同步通信有幾種變通的方法:
一種做法是新建一個 <img>元素,數據放在 src 屬性,作為 URL 的查詢字符串,這時瀏覽器會等待圖片加載完成(服務器回應),再進行卸載。另一種做法是創(chuàng)建一個循環(huán),規(guī)定執(zhí)行時間為幾秒鐘,在這幾秒鐘內把數據發(fā)出去,然后再卸載頁面。
通過在 unload 事件處理器中,創(chuàng)建一個圖片元素并設置它的 src 屬性的方法來延遲卸載以保證數據的發(fā)送。因為絕大多數瀏覽器會延遲卸載以保證圖片的載入,所以數據可以在卸載事件中發(fā)送。
const reportData = (url, data) => {
let img = document.createElement('img');
const params = [];
Object.keys(data).forEach((key) => {
params.push(`${key}=${encodeURIComponent(data[key])}`);
});
img.onload = () => (img = null);
img.src = `${url}?${params.join('&')}`;
};
這些做法的共同問題是,卸載的時間被硬生生拖長了,后面頁面的加載被推遲了,用戶體驗不好。
而Navigator.sendBeacon 就是天生用來解決“網頁離開時的請求發(fā)送”問題的,該方法可用于通過 HTTP 將少量數據異步傳輸到 Web 服務器??梢园l(fā)現,同樣是采用異步的方式,但是Navigator.sendBeacon發(fā)出的異步請求是作為瀏覽器任務執(zhí)行的,與當前頁面是脫鉤的。因此該方法不會阻塞頁面卸載流程和延遲后面頁面的加載。當用戶代理成功把數據加入瀏覽器傳輸隊列時,sendBeacon() 方法將會返回 true,如果受到隊列總數、數據大小的限制后,會返回 false。返回true后,只是表示進入了發(fā)送隊列,瀏覽器會盡力保證發(fā)送成功,但是否成功了,無法判斷。
目前 Google Analytics 使用 Navigator.sendBeacon 來上報數據。Navigator.sendBeacon方法接受兩個參數,第一個參數是目標服務器的 URL,第二個參數是所要發(fā)送的數據(可選),可以是任意類型(字符串、表單對象、二進制對象等等)。這個方法的返回值是一個布爾值,成功發(fā)送數據為 true,否則為 false。該方法發(fā)送數據的 HTTP 方法是 POST,可以跨域,類似于表單提交數據。它不能指定回調函數。
window.addEventListener('unload', analytics, false);
function analytics(state) {
if (!navigator.sendBeacon) return;
var URL = 'http://example.com/analytics';
var data = 'state=' + state + '&location=' + window.location;
navigator.sendBeacon(URL, data);
}
sendBeacon方法具有如下特點:
發(fā)出的是異步請求,并且是 POST 請求,后端解析參數時,需要注意處理方式; 發(fā)出的請求,是放到的瀏覽器任務隊列執(zhí)行的,脫離了當前頁面,所以不會阻塞當前頁面的卸載和后面頁面的加載過程,用戶體驗較好; 只能判斷出是否放入瀏覽器任務隊列,不能判斷是否發(fā)送成功; Beacon API不提供相應的回調,因此后端返回最好省略response body。
參考資料
Google Analytics added sendBeacon functionality to Universal Analytics JavaScript API[32] Navigator.sendBeacon 無阻塞發(fā)送統(tǒng)計數據[33] Navigator.sendBeacon() —— MDN[34]
第十二式:讓 VSCode、瀏覽器和你心有靈犀
在剛接手比較大型項目時,也許你會經常碰到這樣的問題:需要修改一個頁面,卻苦于對項目結構不熟悉、文件夾結構不規(guī)范等,不知道文件在哪個目錄下;要改一個 bug,卻難以迅速定位到問題所在文件,這時候你是否幻想過,如果可以點擊頁面上的組件,在 VSCode 中自動跳轉到對應文件,并定位到對應行號豈不美哉?
react-dev-inspector [35]就是滿足你這些幻想的夢中女神,這個插件允許用戶通過簡單的點擊直接從瀏覽器 React 組件跳轉到本地 IDE 代碼。TA 不僅能滿足你的幻想,使用起來也是非常簡單方便,看完這張動圖,懂得都懂 ??:

如果看完圖,你還不放心的話,不妨現在就先在在線預覽體驗[36]地址體驗一下(在線體驗地址里,激活點擊跳轉的快捷鍵是四個按鍵的組合,不過你完全不用擔心,因為這個組合是可以自定義的,你完全可以改成兩個按鍵的組合)。
前面說了,使用方式非常簡單,只需三步:
首先,保證你的命令行本身可以通過 code命令打開 VSCode 編輯器,比如code .,用 VSCode 打開當前文件夾下的文件;如果沒有配置這個,可以參考以下步驟:首先打開 VSCode。 使用 command + shift + p(注意 window 下使用ctrl + shift + p) 然后搜索 code,選擇install 'code' command in path。安裝 react-dev-inspector,修改babelrc.js和webpack.config.ts文件:
// babelrc.js
export default {
plugins: [
// plugin options docs see:
// https://github.com/zthxxx/react-dev-inspector#inspector-babel-plugin-options
'react-dev-inspector/plugins/babel',
],
};
// webpack.config.ts
import type { Configuration } from 'webpack';
import { launchEditorMiddleware } from 'react-dev-inspector/plugins/webpack';
const config: Configuration = {
/**
* [server side] webpack dev server side middleware for launch IDE app
*/
devServer: {
before: (app) => {
app.use(launchEditorMiddleware);
},
},
};
對項目入口文件進行以下修改:
import React from 'react';
import { Inspector, InspectParams } from 'react-dev-inspector';
const InspectorWrapper =
process.env.NODE_ENV === 'development' ? Inspector : React.Fragment;
export const Layout = () => {
// ...
return (
<InspectorWrapper
// props docs see:
// https://github.com/zthxxx/react-dev-inspector#inspector-component-props
// 這里可以隨便配置快捷鍵,你可以改成兩個按鍵的組合
keys={['control', 'shift', 'command', 'c']}
disableLaunchEditor={false}
onHoverElement={(params: InspectParams) => {}}
onClickElement={(params: InspectParams) => {}}
>
{/*這里是你原來的入口組件jsx*/}
<YourComponent>...</YourComponent>
</InspectorWrapper>
);
};
當然,這個插件目前也支持在Vite2、create-react-app、Umi中使用,接入也都很簡單,可以參考react-dev-inspector GitHub 倉庫及使用[37]文檔。
這個插件的原理,簡單說也分為三步:
構建時:添加一個 webpack loader去遍歷編譯前的 AST 節(jié)點,在 DOM 節(jié)點上加上文件路徑、名稱等相關的信息。使用DefinePlugin注入項目運行時的根路徑,以便后續(xù)用來拼接文件路徑,打開 VSCode 相應的文件。運行時:需要在項目的最外層包裹 Inspector組件,用于在瀏覽器端監(jiān)聽快捷鍵,彈出 debug 的遮罩層,在點擊遮罩層的時候,利用fetch向本機服務發(fā)送一個打開 VSCode 的請求。本地服務:需要啟動 react-dev-utils里的一個中間件,監(jiān)聽一個特定的路徑,在本機服務端執(zhí)行打開 VSCode 的指令,如code src/pages/index.ts。
如果你對其中的原理很感興趣,可以參考字節(jié)跳動 Web Infra 團隊的文章——開發(fā)提效——我點了頁面上的元素,VSCode 乖乖打開了對應的組件?原理揭秘[38]。
第十三式:讓瀏覽器告訴大家你正在瀏覽不正經網站
上班滑水摸魚,同事來了趕緊切換瀏覽器界面?何不直接讓同事以為你在瀏覽某些“正經”網站?
以下代碼,當切換瀏覽器 tab,使得頁面不可見時,會更改不可見頁面的 title 和 icon 顯示效果,可以直接復制以下代碼在控制臺嘗試。
let interval = null;
(function () {
// 獲取icon所在link,rel*="icon"是為了兼容rel="shortcut icon"的情況
const Link = document.querySelector('link[rel*="icon"]');
const sourceTitile = document.title;
const sourceLink = Link.href;
document.addEventListener('visibilitychange', function () {
if (document.hidden) {
// 讓title文字動起來,更加醒目
interval = setInterval(scroll, 1000);
// 修改title,這里也可以直接寫文字,之所以用編碼后的嘛,??,是因為不想讓你一眼看出代碼里下了毒...
document.title = decodeURI(
'%E6%82%A8%E6%AD%A3%E5%9C%A8%E6%B5%8F%E8%A7%88%E9%BB%84%E8%89%B2%E7%BD%91%E7%AB%99...'
);
Link.href =
'https://king-hcj.github.io/images/posts/zhuangbility100/nh.gif?raw=true';
Link.type = 'image/gif';
} else {
clearInterval(interval);
document.title = sourceTitile;
Link.href = sourceLink;
}
});
})();
function scroll() {
// 讓title文字動起來,更加醒目
const titleInfo = document.title;
const firstInfo = titleInfo.charAt(0);
const lastInfo = titleInfo.substring(1, titleInfo.length);
document.title = lastInfo + firstInfo;
}
效果圖:

第十四式:帶有 Id 屬性的元素,會創(chuàng)建全局變量
關于 HTML 中的 Id 屬性,想必您已經再熟悉不過了:
id 屬性規(guī)定 HTML 元素的唯一的 id; id 在 HTML 文檔中必須是唯一的; id 屬性可用作鏈接錨(link anchor),通過 JavaScript(HTML DOM)或通過 CSS 為帶有指定 id 的元素改變或添加樣式。
對于 DOM 樹中具有 ID 的給定 HTML 元素,可以使用其 ID 作為變量名來檢索 div。所以,下面的console.log(foo)會打印出 id 為 foo 的這個 div DOM 元素:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
</head>
<body>
<div id="foo"></div>
<script type="text/javascript">
console.log(foo); // 會輸出id為foo的div 這個 DOM元素
</script>
</body>
</html>
如果您不信,那就此時打開您的網站看看:

那么,這是否也意味著可以在這些瀏覽器中使用它來替代 getElementById 方法呢?實際項目中最好還是老老實實該怎么寫就怎么寫,畢竟如果這樣,大概率你會被后來的接手者或者同事問候的 ?? ~
Do DOM tree elements with ids become global variables?[39]
第十五式:利用 a 標簽解析 url
很多時候我們有從一個 URL 中提取域名,查詢關鍵字,變量參數值等的需要,而萬萬沒想到可以讓瀏覽器方便地幫我們完成這一任務而不用我們寫正則去抓取。方法是在 JS 代碼里先創(chuàng)建一個 a 標簽然后將需要解析的 URL 賦值給 a 的 href 屬性,然后就得到了一切我們想要的了。
var a = document.createElement('a');
a.href = 'https://juejin.cn/user/2796746682939054/posts';
console.log(a.host);
利用這一原理,稍微擴展一下,就得到了一個更加健壯的解析 URL 各部分的通用方法了,下面提供一個網上常見的封裝示例。
function urlParse(url, key) {
var a = document.createElement('a');
a.href = url;
var result = {
href: url,
protocol: a.protocol.replace(':', ''),
port: a.port,
query: a.search,
params: (function () {
var ret = {},
centArr,
seg = a.search.replace(/^\?/, '').replace(/^\?/, '').split('&');
for (i = 0, len = seg.length; i < len; i++) {
if (!seg[i]) {
continue;
}
centArr = seg[i].split('=');
ret[centArr[0]] = centArr[1];
}
return ret;
})(),
hash: a.hash,
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [, ''])[1],
path: a.pathname.replace(/^([^\/])/, '/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [, ''])[1],
segments: a.pathname.replace(/^\//, '').split('/'),
};
a = null;
return key ? result[key] : result;
}
H5 有新的 API URL 也可以快速的處理一個鏈接,相對更加簡潔。
var url = new URL('https://www.baidu.com/')
url.hash
...
這些鮮為人知的前端冷知識,你都 GET 了嗎?
第十六式:一個可以在瀏覽器運行 Node.js 的神器
在最新的 Google I/O 主題演講中 stackblitz 向大家介紹了他們與 Next.js 和 Google 團隊合作開發(fā)的在線 IDE WebContainers:
幾年前,我們意識到網絡正朝著一個關鍵的拐點發(fā)展。
WebAssembly的出現讓我們可以有能力編寫基于WebAssembly的操作系統(tǒng),這個操作系統(tǒng)的功能強大到可以完全在瀏覽器中運行Node.js。我們設計了一個比本地環(huán)境更快,更安全和一致的高級開發(fā)環(huán)境,可以實現無縫代碼協(xié)作,而不需要設置本地環(huán)境,兩年后的今天,他終于誕生了!

官網聲稱`StackBlitz.com`[40]是這個星球上最快、最安全的開發(fā)環(huán)境,它的 logo 也是一個閃電的標識:

WebContainers 允許你創(chuàng)建一個完整的 Node.js環(huán)境,它可以在毫秒內啟動,并且可以實現一鍵聯(lián)機和鏈接共享。這個環(huán)境具有 VS Code 強大的編輯功能,完整的終端,還有 npm 等功能。它也完全在你的瀏覽器中運行,這帶來了一些關鍵的好處:
比本地環(huán)境快。構建速度比 yarn/npm快 20%,包安裝速度可以快 5 倍。支持在瀏覽器中調試 Node.js。與Chrome DevTools的無縫集成可實現本機后端調試,無需安裝擴展。默認安全。所有代碼執(zhí)行都發(fā)生在瀏覽器的安全沙箱中,而不是在遠程 VM或本地二進制文件上。
同樣,這些環(huán)境不需要在遠程服務器上運行。而是每個環(huán)境都完全包含在你的 Web 瀏覽器中。沒錯:Node.js 運行時本身是第一次在瀏覽器內部本機運行。你可以在`StackBlitz.com`[41] 上自己嘗試一下,下面是我截取的頁面截圖:

推薦一個神器!可以在瀏覽器運行 Node.js 在線 IDE WebContainers 體驗地址[42]
第十七式:一起富強民主、文明、和諧、自由、平等?
別人寫文章都妙筆生花,我上個網,我上個網,鼠標點過的地方都“富強、民主、文明、和諧、自由、平等”,我驕傲了嗎?
復制以下代碼到控制臺執(zhí)行,然后,開始點擊你的頁面吧~ ??
(function () {
var playWords = [
'富強',
'民主',
'文明',
'和諧',
'自由',
'平等',
'公正',
'法制',
'愛國',
'敬業(yè)',
'誠信',
'友善',
], // 點擊展示的詞庫
colors = ['#ff4545', '#3eff00'], // 顏色庫
wordIdx = Math.floor(Math.random() * playWords.length); // 隨機取詞下標
document.body.addEventListener('click', function (e) {
// 監(jiān)聽點擊事件
if (e.target.tagName == 'A') {
// a標簽
return;
}
var x = e.pageX,
y = e.pageY, // 獲取點擊位置
span = document.createElement('span'); // 創(chuàng)建展示playWords的span
span.textContent = playWords[wordIdx];
wordIdx = (wordIdx + 1) % playWords.length;
color = colors[Math.floor(Math.random() * colors.length)]; // 隨機取色
span.style.cssText = [
'z-index: 9999; position: absolute; top: ',
y - 20,
'px; left: ',
x,
'px; font-weight: bold; color: ',
color,
].join('');
document.body.appendChild(span);
renderWords(span);
});
function renderWords(el) {
var i = 0,
top = parseInt(el.style.top);
var playTimer = setInterval(function () {
if (i > 180) {
clearInterval(playTimer);
el.parentNode.removeChild(el);
} else {
i += 3;
el.style.top = top - i + 'px';
el.style.opacity = (180 - i) / 180;
}
}, 16.7);
}
})();
參考資料:前端隨便玩兒[43]
本文首發(fā)于個人博客[44],歡迎指正和star[45]。
參考資料
一個能讓你的網站 high 起來的 js: https://link.segmentfault.com/?url=https%3A%2F%2Floli-rbq.top%2Fcarnival%2F
[2]JavaScript Puzzlers!: https://link.segmentfault.com/?url=http%3A%2F%2Fjavascript-puzzlers.herokuapp.com%2F
[3]JavaScript Puzzlers!: https://link.segmentfault.com/?url=http%3A%2F%2Fjavascript-puzzlers.herokuapp.com%2F
[4]44 個 Javascript 變態(tài)題解析 (上): https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fxiaoyu2er%2Fblog%2Fissues%2F1
[5]44 個 Javascript 變態(tài)題解析 (下): https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fxiaoyu2er%2Fblog%2Fissues%2F3
[6]JavaScript Puzzlers!: https://link.segmentfault.com/?url=http%3A%2F%2Fjavascript-puzzlers.herokuapp.com%2F
[7]百度百科: https://link.segmentfault.com/?url=https%3A%2F%2Fbaike.baidu.com%2Fitem%2F%E5%B8%AE%E6%B1%AA%E5%B3%B0%E4%B8%8A%E5%A4%B4%E6%9D%A1%2F15079279
[8]深入淺出 contenteditable 富文本編輯器: https://link.segmentfault.com/?url=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F37051858
[9]CSS user-modify 屬性行為表現測試實例頁面: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.zhangxinxu.com%2Fstudy%2F201601%2Fuser-modify.html
[10]小 tip: 如何讓 contenteditable 元素只能輸入純文本: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.zhangxinxu.com%2Fwordpress%2F2016%2F01%2Fcontenteditable-plaintext-only%2F
[11]前端隨便玩兒: https://link.segmentfault.com/?url=https%3A%2F%2Fxiaohuazheng.github.io%2F2018%2F06%2F02%2Ffed-play%2F
[12]前端隨便玩兒: https://link.segmentfault.com/?url=https%3A%2F%2Fxiaohuazheng.github.io%2F2018%2F06%2F02%2Ffed-play%2F
[13]devtools-detector: https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2FAEPKILL%2Fdevtools-detector
[14]網站如何檢測到是否開啟開發(fā)者工具?: https://segmentfault.com/q/1010000039917621
[15]JS 檢測,禁用瀏覽器開發(fā)者工具之 6 大方法探討: https://link.segmentfault.com/?url=https%3A%2F%2Fblog.csdn.net%2Fcplvfx%2Farticle%2Fdetails%2F108518077
[16]前端開發(fā)中如何在 JS 文件中檢測用戶瀏覽器是否打開了調試面板(F12 打開開發(fā)者工具)?: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.zhihu.com%2Fquestion%2F24188524
[17]網站這樣來屏蔽開發(fā)者工具,不比監(jiān)聽 MouseDown 舒服?: https://segmentfault.com/a/1190000040157555
[18]JS 禁止打開控制臺: https://segmentfault.com/a/1190000021459140
[19]JSBench.Me: https://link.segmentfault.com/?url=https%3A%2F%2Fjsbench.me%2F
[20]Benchmark.js: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Fbenchmark
[21]基準測試: https://link.segmentfault.com/?url=https%3A%2F%2Fblog.csdn.net%2Fwoniu317%2Farticle%2Fdetails%2F82560312
[22]jsPerf: https://link.segmentfault.com/?url=https%3A%2F%2Fjsperf.com%2F
[23]w3c 文檔: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.w3.org%2FTR%2Frequestidlecallback%2F%23idle-periods
[24]w3c 標準: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.w3.org%2FTR%2Frequestidlecallback%2F%23idle-periods
[25]瀏覽器是如何工作的:Chrome V8 讓你更懂 JavaScript: https://segmentfault.com/a/1190000037435824
[26]47 張圖帶你走進瀏覽器的世界: https://segmentfault.com/a/1190000040330317
[27]requestIdleCallback-后臺任務調度: https://link.segmentfault.com/?url=http%3A%2F%2Fwww.zhangyunling.com%2F702.html
[28]瀏覽器幀原理剖析: https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fxitu%2Fgold-miner%2Fblob%2Fmaster%2FTODO1%2Fthe-anatomy-of-a-frame.md
[29]Accurately measuring layout on the web: https://link.segmentfault.com/?url=https%3A%2F%2Fnolanlawson.com%2F2018%2F09%2F25%2Faccurately-measuring-layout-on-the-web%2F
[30]Cooperative Scheduling of Background Tasks: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.w3.org%2FTR%2Frequestidlecallback%2F
[31]2 行代碼生成包含大小寫字母和數字的隨機字符串: https://link.segmentfault.com/?url=http%3A%2F%2Fblog.haoji.me%2Fgenerate-random-string.html
[32]Google Analytics added sendBeacon functionality to Universal Analytics JavaScript API: https://link.segmentfault.com/?url=https%3A%2F%2Fwww.thyngster.com%2Fgoogle-analytics-added-sendbeacon-functionality-universal-analytics-javascript-api
[33]Navigator.sendBeacon 無阻塞發(fā)送統(tǒng)計數據: https://link.segmentfault.com/?url=https%3A%2F%2Fblog.csdn.net%2Fu012193330%2Farticle%2Fdetails%2F102778979
[34]Navigator.sendBeacon() —— MDN: https://link.segmentfault.com/?url=https%3A%2F%2Fdeveloper.mozilla.org%2Fzh-CN%2Fdocs%2FWeb%2FAPI%2FNavigator%2FsendBeacon
[35]react-dev-inspector : https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fzthxxx%2Freact-dev-inspector
[36]在線預覽體驗: https://link.segmentfault.com/?url=https%3A%2F%2Freact-dev-inspector.zthxxx.me%2F
[37]react-dev-inspector GitHub 倉庫及使用: https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fzthxxx%2Freact-dev-inspector
[38]開發(fā)提效——我點了頁面上的元素,VSCode 乖乖打開了對應的組件?原理揭秘: https://link.segmentfault.com/?url=https%3A%2F%2Fjuejin.cn%2Fpost%2F6901466406823575560
[39]Do DOM tree elements with ids become global variables?: https://link.segmentfault.com/?url=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F3434278%2Fdo-dom-tree-elements-with-ids-become-global-variables
[40]StackBlitz.com: https://link.segmentfault.com/?url=https%3A%2F%2Fstackblitz.com%2F
StackBlitz.com: https://link.segmentfault.com/?url=https%3A%2F%2Fstackblitz.com%2F
在線 IDE WebContainers 體驗地址: https://link.segmentfault.com/?url=https%3A%2F%2Fstackblitz.com%2F
[43]前端隨便玩兒: https://link.segmentfault.com/?url=https%3A%2F%2Fxiaohuazheng.github.io%2F2018%2F06%2F02%2Ffed-play%2F
[44]個人博客: https://link.segmentfault.com/?url=https%3A%2F%2Fking-hcj.github.io%2F2021%2F08%2F01%2FJavaScript-108-tips4%2F
[45]指正和star: https://link.segmentfault.com/?url=https%3A%2F%2Fgithub.com%2Fking-hcj%2Fking-hcj.github.io
