從創建http鏈接開始,到頁面展現到瀏覽器裏,經歷了加載、執行、渲染,重構的幾個階段。將分享下我本身的心得和其餘人的優秀經驗。javascript
瀏覽器是友善的客戶端,對同域名併發請求是有數量限制,過去瀏覽器通常是2個,支持H5的通常是6個;而且服務器端是能夠關閉請求。 有朋友不理解,爲何不是併發越多越好?舉個例子:百萬級的PV,併發數量過大會形成什麼樣的後果? 由此,全部的優化都是基於這個點和單線程而延伸出來的。 因此,前端的資源加載優化有兩個方向css
js文件加載後是否要當即執行?當即執行是否會影響頁面渲染?過去瀏覽器在加載和執行js文件時是阻塞狀態,就是按照棧原理一個個來;因此,原來要求把js文件放到html代碼底部前,現代瀏覽器某種程度上解決了並行加載的問題,也能夠進行預加載,可是執行以後會否對頁面形成重排?因此要靈活應用dns-prefetch、preload和defer|async,固然defer和async不是全部瀏覽器都生效,webkit核心的就沒生效。html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Demo</title>
<link rel="dns-prefetch" href="//cdn.com/">
<link rel="preload" href="//js.cdn.com/currentPage-part1.js" as="script">
<link rel="preload" href="//js.cdn.com/currentPage-part2.js" as="script">
<link rel="preload" href="//js.cdn.com/currentPage-part3.js" as="script">
<link rel="prefetch" href="//js.cdn.com/prefetch.js">
</head>
<body>
<!-- html code -->
<script type="text/javascript" src="//js.cdn.com/currentPage-part1.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part2.js" defer></script>
<script type="text/javascript" src="//js.cdn.com/currentPage-part3.js" defer></script>
</body>
</html>
複製代碼
(function(w,d){})(window,document)
// 目的就是如此,再好比說的緩存某個變量或對象
function check(){
var d = document, t = document.getElementById('t'), l = t.children;
for(let i=0;i<l;i++){
//code
}
}
複製代碼
減值迭代——大多數循環使用一個從0開始,增長到某個特定值的迭代器。在不少狀況下,從最大值開始,在循環中不斷減值的迭代器更加有效。 簡化終止條件——因爲每次循環過程都會計算終止條件,故必須保證它儘量快,即避免屬性查找或其它O(n)的操做。 簡化循環體——循環體是執行最多的,故要確保其被最大限度地優化。確保沒有某些能夠被很容易移出循環的密集計算。 使用後測試循環——最經常使用的for和while循環都是前測試循環,而如do-while循環能夠避免最初終止條件的計算,因些計算更快。前端
for(var i = 0; i < values.length; i++) {
process(values[i]);
}
複製代碼
優化1:簡化終止條件vue
for(var i = 0, len = values.length; i < len; i++) {
process(values[i]);
}
複製代碼
優化2:使用後測試循環(注意:使用後測試循環須要確保要處理的值至少有一個)java
var i values.length - 1;
if(i > -1) {
do {
process(values[i]);
}while(--i >= 0);
}
複製代碼
當循環的次數肯定時,消除循環並使用屢次函數調用每每更快 當循環的次數不肯定時,可使用Duff Service來優化,基本概念是經過計算迭代的次數是否爲8的倍數將一個循環展開爲一系列語句。以下:react
// Jeff Greenberg for JS implementation of Duff's Device // 假設:values.length > 0 function process(v) { alert(v); } var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]; var iterations = Math.ceil(values.length / 8); var startAt = values.length % 8; var i = 0; do { switch(startAt) { case 0 : process(values[i++]); case 7 : process(values[i++]); case 6 : process(values[i++]); case 5 : process(values[i++]); case 4 : process(values[i++]); case 3 : process(values[i++]); case 2 : process(values[i++]); case 1 : process(values[i++]); } startAt = 0; }while(--iterations > 0); 複製代碼
如上展開循環能夠提高大數據集的處理速度。接下來給出更快的Duff裝置技術,將do-while循環分紅2個單獨的循環。(注:這種方法幾乎比原始的Duff裝置實現快上40%。)webpack
// Speed Up Your Site(New Riders, 2003)
function process(v) {
alert(v);
}
var values = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17];
var iterations = Math.floor(values.length / 8);
var leftover = values.length % 8;
var i = 0;
if(leftover > 0) {
do {
process(values[i++]);
}while(--leftover > 0);
}
do {
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
process(values[i++]);
}while(--iterations > 0);
複製代碼
針對大數據集使用展開循環能夠節省不少時間,但對於小數據集,額外的開銷則可能得不償失。web
當JS代碼想解析JS代碼時就會存在雙重解釋懲罰,當使用eval()函數或是Function構造函數以及使用setTimeout()傳一個字符串時都會發生這種狀況。以下正則表達式
eval("alert('hello world');"); // 避免
var sayHi = new Function("alert('hello world');"); // 避免
setTimeout("alert('hello world');", 100);// 避免
複製代碼
以上代碼是包含在字符串中的,即在JS代碼運行的同時必須新啓運一個解析器來解析新的代碼。實例化一個新的解析器有不容忽視的開銷,故這種代碼要比直接解析要慢。如下這幾個例子,除了極少狀況下eval是必須的,應儘可能避免使用上述。對於Function構造函數,直接寫成通常的函數便可。對於setTimeout能夠傳入函數做爲第一個參數。以下:
alert('hello world');
var sayHi = function() {
alert('hello world');
};
setTimeout(function() {
alert('hello world');
}, 100);
複製代碼
總之,若要提升代碼性能,儘量避免出現須要按照JS解釋的代碼。
性能的其它注意事項 原生方法更快——只要有可能,使用原生方法而不是自已用JS重寫。原生方法是用諸如C/C++之類的編譯型語言寫出來的,要比JS的快多了。不少人認爲自定義的排序會比sortby更快,事實比對效果仍是原生方法更加優秀。 switch語句較快——如有一系列複雜的if-else語句,能夠轉換成單個switch語句則能夠獲得更快的代碼,還能夠經過將case語句按照最可能的到最不可能的順序進行組織,來進一步優化。 位運算較快——當進行數學運算時,位運算操做要比任何布爾運算或算數運算快。選擇性地用位運算替換算數運算能夠極大提高複雜計算的性能,諸如取模,邏輯與和邏輯或也能夠考慮用位運算來替換。
最小化語句數 JS代碼中的語句數量也會影響所執行的操做的速度,完成多個操做的單個語句要比完成單個操做的多個語句塊快。故要找出能夠組合在一塊兒的語句,以減來總體的執行時間。這裏列舉幾種模式
1.多個變量聲明
// 避免
var i = 1;
var j = "hello";
var arr = [1,2,3];
var now = new Date();
// 提倡
var i = 1,
j = "hello",
arr = [1,2,3],
now = new Date();
複製代碼
2.插入迭代值
// 避免
var name = values[i];
i++;
// 提倡
var name = values[i++];
複製代碼
3.使用數組和對象字面量,避免使用構造函數Array(),Object()
// 避免
var a = new Array();
a[0] = 1;
a[1] = "hello";
a[2] = 45;
var o = new Obejct();
o.name = "bill";
o.age = 13;
// 提倡
var a = [1, "hello", 45];
var o = {
name : "bill",
age : 13
};
複製代碼
4.優化DOM交互 在JS中,DOM無疑是最慢的一部分,DOM操做和交互要消耗大量時間,由於它們每每須要從新渲染整個頁面或者某一個部分,故理解如何優化與DOM的交互能夠極大提升腳本完成的速度。後面會針對性說明
計算機科學中有個經典問題:經過改變數據存儲的位置來得到最佳的讀寫性能,數據存儲的位置關係到代碼執行過程當中數據的檢索速度。在JS中這個問題相對簡單,由於只有4種方案。
理解做用域概念是JS和核心關鍵,不只從性能還得從功能的角度。簡單說:生效的範圍(域),哪些變量能夠被函數訪問,this的賦值,上下文(context)的轉換。說到做用域就不能繞開做用域鏈。理解了做用域鏈和標識符就理解了做用域。
每一個函數都是Function對象的實例,Function對象和其它對象同樣,擁有能夠編程訪問的屬性,和一系列不能經過代碼訪問而僅供JS引擎存取的內部屬性。其中一個內部屬性是[[Scope]],有ECMA-262標準第三版定義
內部屬性[[Scope]]包含了一個函數被建立的做用域中對象的集合。這個集合被稱爲函數的做用域鏈,它決定那些數據能被函數訪問,函數做用域中的每一個對象被稱爲一個可變對象,每一個可變對象都以「鍵值對」的形式存在。當一個函數建立後,他的做用域鏈會被建立此函數的做用域中可訪問的數據對象所填充。例如:
function fn(a,b){
return res = a*b;
}
複製代碼
當fn建立時,它的做用域鏈中插入了一個對象變量,這個全局對象表明着全部在全局範圍內定義的變量。該全局對象包含window、navigator、document等。fn執行的時候就會用到做用域,並建立執行環境也叫執行上下文。它定義了一個函數執行時的環境,即使是同一個函數,每次執行都建立新的環境,函數執行完畢,環境就銷燬。 每一個環境都要根據做用域和做用域鏈解析參數、變量。能夠理解爲做用域鏈比如一個堆棧,棧頂就是當前的活動對象(環境建立時函數[[Scope]]屬性中的對象集合)大多狀況也能夠理解爲函數內部定義的局部變量。
而閉包的是根據JS容許函數訪問局部做用域以外的數據,雖然會帶來性能問題,由於執行環境雖然銷燬,但激活的對象依然存在,因此能夠緩存變量,從而不用全局對象。適用
屬性和方法,二者都是對象的成員,引用了函數就是方法,非函數就是屬性。爲何對象訪問慢呢?由於原型鏈問題。
直接看代碼
function fun(name,age){
this.name = name+'';
this.age = age
}
fun.prototype.getName = function(){
return this.name;
}
var fn = new fun();
true = (fn instanceof fun) //true
true = (fn instanceof Object)
fn.__proto__ = fun.prototype
/*
* fun的原型方法
__proto__ = null
hasOwnProperty = (function)
isPrototypeOf = (function)
propertyIsEnumerable = (function)
toLocaleString = (function)
toString = (function)
valueOf = (function)
*/
複製代碼
平時普通變量也是這樣一級級向上直到根(window)下,沒有此變量或屬性或方法,才返回undefined;
DOM操做代價高昂,這是web application最多見的性能瓶頸,Document Oject Module(DOM)是獨立於語言的,用於操做xml和html文檔的的程序接口,並且在瀏覽器中是經過js實現的。 各個公司的瀏覽器渲染和js解釋引擎都不一樣,著名的V8相信你們都知道,是一個js引擎;但Chrome的渲染是WebCore。每一個瀏覽器都有兩套解釋器,並相對獨立。這就意味着每次操做都須要(V8<=>WebCore)==>Browser 兩個解釋器都是須要鏈接和通信成本。減小兩解釋器通信並減小頁面改變的頻率就是優化的方向。
DOM樹裏的每一個須要顯示的節點在渲染樹中至少存在一個對應的節點,隱藏的(display:none)的DOM元素則沒有;渲染樹的節點被稱爲幀(frames) 盒(boxes),DOM和渲染樹構建完畢,瀏覽器就開始繪製頁面元素(paint)
什麼時候發生重重繪?當頁面的幾何屬性發生變化,影響到現有的文檔流須要從新調整頁面排版的時候。舉幾個例子:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle() (currentStyle in IE)
function scroller(){
var H = document.body.offsetHeight || scrollHeight
return function(){
var args = arguments,ct = this;
// your code
}
}
複製代碼
爲了最小和最少的影響到重繪和重排,應該儘量少的修改DOM,訪問影響重排的屬性。若是非要修改,儘可能尊從三個步驟: 1.元素脫離文檔流 2.一次性應用多重改變 3.恢復到文檔流中 第一和第三步都會發生重排,因此核心的仍是第二步。如今虛擬dom大🔥,咱們稍微瞭解下基礎作法便可。 一次性更新的幾種方式:字符串或數組.join('') innerHTML方式,createElement最後appendChild,document.createDocumentFragment,cloneNode須要改變的節點到緩存節點中,改完替換。 再者,動畫時也須要儘量少重繪和重排,例如:沿對角線,從左上移動到右下角
function move2RB(){
var dom = document.getElementById('id'),curent = dom.style.top;
while(curent<500){
curent++
dom.style.cssText = 'left:'+curent+'px; top:'+curent+'px';
}
}
// 不要寫成每次都去獲取,left=dom.style.left再加1,甚至是dom.style.left = (pareSint(dom.style.left,10)+1)+'px'這種寫法,直接改變className也是能夠的。
複製代碼
總結起來就幾句話:少訪問DOM,在js裏處理計算完了再一次性修改,善用緩存和原生API;用如今的三大框架(angular、react、vue)便可不用操心這些 :)
代碼的總體結構是影響運行速度的主要因素之一,數量與運行速度不必定成正比。組織結構、思路和執行效率纔是核心!! JS屬於ECMA的範疇,是一種腳本類語言,不少流程上的控制,工程化的思路是從java、c等語言上借鑑過來的,因此,知道後端語言的編碼和工程化有助於咱們加深理解。
for(var i=0;i<10;i++){
// code
}
複製代碼
倒序能夠在大數據量時提升少量效率,i<obj.length;i-- 2. while 前置循環
var i=0;
while(i<10){
// code
i++;
}
複製代碼
後置循環
var i=0;
do{
// code
}while(i++<10)
複製代碼
for(var prop in object){
// code
}
複製代碼
除了for-in循環其它效率所差很少,那麼可以提升效率的點也就兩個
if-else vs switch 條件數量越多,switch的迭代效率會更高;當只有二選一或簡單判斷if-else的易讀性更好。在實際coding中,若是隻有二選一,有些狀況甚至能夠不用if-else,採用三目運算:result = (true||false)?condition0:condition1;還有將最可能發生的條件寫到「if()」裏面,減小判斷次數,延伸開來就是if-elseif的判斷可能性要從大到小。甚至能夠採用二分法:
//---假設某參數的值非正即負,或查詢二叉樹,或查詢不一樣SP的手機號
if(parse>0){
if(parse>10){
//code
}else if(parse<5&&parse>1){
//code
}else{
}
}else{
//code 負數處理
}
複製代碼
固然,這是個簡單的栗子,還有不少其它的方式能夠在代碼中引入算法,提升效率,好比星期幾的輸出
function getweek(){
var w = ['日','一','二','三','四','五','六'],
now = new Date(),
d = now.getDay();
return '星期'+w[d];
}
複製代碼
能夠將字符串、變量、方法,存到數組或對象中。由於是引用,效率也很是快
1.遞歸
//---階乘
function facttail(n){
if(n==0){
return 1;
}else{
return n*facttail(n-1);
}
}
//---冪次方
function fn(s,n){
if(n==0){
return 1;
}else{
return s*fn(s,n-1);
}
}
複製代碼
可是遞歸若是結束條件不明確就會致使一直運行,頁面長時間不響應。處於假死狀態!!並且,每一個瀏覽器的「調用棧」都是有上限的。有興趣的能夠本身實驗。爲避免此問題,除了明確結束條件,還能夠採用「尾遞歸」 2.尾遞歸
//---階乘
function facttail(n,res){
if(n<0){
return 0;
}else if(n==0){
return 1;
}else if(n==1){
return res;
}else{
return facttail(n-1, n*res);
}
}
//---冪次方
function fn(s,n){
if(n==0){
return 1;
}else{
return s*fn(s,n-1);
}
}
複製代碼
利用閉包特性,某個方法內部能夠存儲計算過的數據或變量,好比階乘函數重寫
function memfacttail(n){
if(!memfacttail.cache){
memfacttail.cache = {
"0":1,
"1":1
};
}
if(!memfacttail.cache.hasOwnProperty(n)){
memfacttail.cache.n = n * memfacttail(n-1);
}
return memfacttail.cache.n;
}
複製代碼
*?+ 這個部份也須要蠻長的篇幅,佔坑先。。。
老生常談的內容,若是讓頁面秒開;可優化的點有哪些?服務器直渲、首頁優化、組件懶加載、bigpipe、性能監控和針對性優化等等。
先挖坑,我會再專門的文章裏共享一點本身的心得
阮一峯老師說的更好,請移步連接 This link
從Google的Gears插件提出了Worker Pool API,它就是Web Workers的「原型」,最初但願可以加強瀏覽器的功能,好比支持離線瀏覽(離線訪問緩存頁面,從新上線後提交離線操做),但(2015/11)已經被棄用了。HTML5開始Web Workers API被分離出去,成立單獨的規範。自此,咱們能夠將計算、編解碼、真正的異步請求等放到Web Workers裏.
// html中直接寫
var worker = new Worker('worker.js')
// 或經過主頁面的js文件調用,例如:main.js
//---主頁面
if (window.Worker) {
var worker = new Worker('worker.js');
var data = {a: 1, b: [1, 2, 3], c: 'string'};
worker.postMessage(data);
worker.onmessage = function(e) {
console.log('main thread received data');
console.log(e.data);
// 接到消息當即中止worker,onerror將不會觸發
// worker.terminate();
// terminate以後收不到後續消息,但post不報錯
// worker.postMessage(1);
}
worker.onerror = function(err) {
console.log('main thread received err');
console.log(err.message);
// 阻止報錯
err.preventDefault();
}
}
複製代碼
worker.js
//---處理js,能夠引入其它依賴
// importScripts('lib.js');
// importScripts('a.js', 'b.js');
onmessage = function(e) {
console.log(self); // 看看global變量身上有些什麼
var data = e.data;
console.log('worker received data');
console.log(data);
var res = data;
res.resolved = true;
postMessage(res);
setTimeout(function() {
throw new Error('error occurs');
// close,當即中止,至關於主線程中的worker.terminate()
// close();
}, 100);
};
複製代碼
//---main
var worker = new Worker('worker.js')
worker.onmessage = (e)=>{
var jsonData = e.data // 回傳回來的數據
evaluateData(jsonData)
}
worker.postmessage(jsonText)
///---worker
self.onmessage = (e){
var jsonText = e.data // main傳過來的數據
var jsonData = JSON.parse(jsonText) // 解析轉換
self.postMessage(jsonData)
}
複製代碼
//---main
var sWorker = new SharedWorker('worker.js')
sWorker.port.start()
//---first
first.onchange = function() {
sWorker.port.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
//---first
second.onchange = function() {
sWorker.port.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
sWorker.port.onmessage = function(e) {
result1.textContent = e.data;
console.log('Message received from worker');
}
複製代碼
經過這種方式創建前端的「相似」多線程
debug工具list
優化工具,具體請搜索,就不當搬運工了。