前置知識
絕大多數的程序語言,他們的內存生命周期基本一致:
對于所有的編程語言,第二部分都是明確的。而第一和第三部分在底層語言中是明確的。
但在像JavaScript這些高級語言中,大部分都是隱含的,因為JavaScript具有自動垃圾回收機制(Garbage collected)。
因此在做JavaScript開發時,不需要關心內存的使用問題,所需內存分配和無用內存回收,都完全實現自動管理。
1.概述
像C語言這樣的高級語言一般都有底層的內存管理接口,比如 malloc()和free()。另一方面,JavaScript創建變量(對象,字符串等)時分配內存,并且在不再使用它們時“自動”釋放。 后一個過程稱為垃圾回收。這個“自動”是混亂的根源,并讓JavaScript(和其他高級語言)開發者感覺他們可以不關心內存管理。 這是錯誤的。 ——《MDN JavaScript 內存管理》
MDN中的介紹告訴我們,作為JavaScript開發者,還是需要去了解內存管理,雖然JavaScript已經給我們做好自動管理。
2.JavaScript內存生命周期
2.1 分配內存
在做JavaScript開發時,我們定義變量的時候,JavaScript便為我們完成了內存分配:
var num=100; // 為數值變量分配內存 var str='pingan'; // 為字符串變量分配內存 var obj={ name : 'pingan' }; // 為對象變量及其包含的值分配內存 var arr=[1, null, 'hi']; // 為數組變量及其包含的值分配內存 function fun(num){ return num + 2; }; // 為函數(可調用的對象)分配內存 // 函數表達式也能分配一個對象 someElement.addEventListener('click', function(){ someElement.style.backgroundColor='blue'; }, false);
另外,通過調用函數,也會分配內存:
// 類型1. 分配對象內存 var date=new Date(); // 分配一個Date對象 var elem=document.createElement('div'); // 分配一個DOM元素 // 類型2. 分配新變量或者新對象 var str1="pingan"; var str2=str1.substr(0, 3); // str2 是一個新的字符串 var arr1=["hi", "pingan"]; var arr2=["hi", "leo"]; var arr3=arr1.concat(arr2); // arr3 是一個新的數組(arr1和arr2連接的結果)
2.2 使用內存
使用內存的過程實際上是對分配的內存進行讀取與寫入的操作。
通常表現就是使用定義的值。
讀取與寫入可能是寫入一個變量或者一個對象的屬性值,甚至傳遞函數的參數。
var num=1; num ++; // 使用已經定義的變量,做遞增操作
2.3 釋放內存
當我們前面定義好的變量或函數(分配的內存)已經不需要使用的時候,便需要釋放掉這些內存。這也是內存管理中最難的任務,因為我們不知道什么時候這些內存不使用。
很好的是,在高級語言解釋器中,已經嵌入“垃圾回收器”,用來跟蹤內存的分配和使用,以便在內存不使用時自動釋放(這并不是百分百跟蹤到,只是個近似過程)。
3.垃圾回收機制
就像前面提到的,“垃圾回收器”只能解決一般情況,接下來我們需要了解主要的垃圾回收算法和它們局限性。
3.1 引用
垃圾回收算法主要依賴于引用的概念。
即在內存管理環境中,一個對象如果有權限訪問另一個對象,不論顯式還是隱式,稱為一個對象引用另一個對象。
例如:一個JS對象具有對它原型的引用(隱式引用)和對它屬性的引用(顯式引用)。 注意:
這里的對象,不僅包含JS對象,也包含函數作用域(或全局詞法作用域)。
3.2 引用計數垃圾收集
這個算法,把“對象是否不再需要”定義為:當一個對象沒有被其他對象所引用的時候,回收該對象。這是最初級的垃圾收集算法。
var obj={ leo : { age : 18 }; };
這里創建2個對象,一個作為leo的屬性被引用,另一個被分配給變量obj。
// 省略上面的代碼 /* 我們將前面的 { leo : { age : 18 }; }; 稱為“這個對象” */ var obj2=obj; // obj2變量是第二個對“這個對象”的引用 obj='pingan'; // 將“這個對象”的原始是引用obj換成obj2 var leo2=obj2.leo; // 引用“這個對象”的leo屬性
可以看出,現在的“這個對象”已經有2個引用,一個是obj2,另一個是leo2。
obj2='hi'; // 將obj2變成零引用,因此,obj2可以被垃圾回收 // 但是它的屬性leo還在被leo2對象引用,所以還不能回收 leo2=null; // 將leo變成零引用,這樣obj2和leo2都可以被垃圾回收
這個算法有個限制:
無法處理循環引用。即兩個對象創建時相互引用形成一個循環。
function fun(){ var obj1={}, obj2={}; obj1.leo=obj2; // obj1引用obj2 obj2.leo=obj1; // obj2引用obj1 return 'hi pingan'; } fun();
可以看出,它們被調用之后,會離開函數作用域,已經沒有用了可以被回收,然而引用計數算法考慮到它們之間相互至少引用一次,所以它們不會被回收。
實際案例:
在IE6,7中,使用引用計數方式對DOM對象進行垃圾回收,常常造成對象被循環引用導致內存泄露:
var obj; window.onload=function(){ obj=document.getElementById('myId'); obj.leo=obj; obj.data=new Array(100000).join(''); };
可以看出,DOM元素obj中的leo屬性引用了自己obj,造成循環引用,若該屬性(leo)沒有移除或設置為null,垃圾回收器總是且至少有一個引用,并一直占用內存,即使從DOM樹刪除,如果這個DOM元素含大量數據(如data屬性)則會導致占用內存永遠無法釋放,出現內存泄露。
3.3 標記清除算法
這個算法,將“對象是否不再需要”定義為:對象是否可以獲得。
標記清除算法,是假定設置一個根對象(root),在JS中是全局對象。垃圾回收器定時找所有從根開始引用的對象,然后再找這些對象引用的對象...直到找到所有可以獲得的對象和搜集所有不能獲得的對象。
它比引用計數垃圾收集更好,因為“有零引用的對象”總是不可獲得的,但是相反卻不一定,參考“循環引用”。
循環引用不再是問題:
function fun(){ var obj1={}, obj2={}; obj1.leo=obj2; // obj1引用obj2 obj2.leo=obj1; // obj2引用obj1 return 'hi pingan'; } fun();
還是這個代碼,可以看出,使用標記清除算法來看,函數調用之后,兩個對象無法從全局對象獲取,因此將被回收。相同的,下面案例,一旦 obj 和其事件處理無法從根獲取到,他們將會被垃圾回收器回收。
var obj; window.onload=function(){ obj=document.getElementById('myId'); obj.leo=obj; obj.data=new Array(100000).join(''); };
注意: 那些無法從根對象查詢到的對象都將被清除。
3.4 個人小結
在日常開發中,應該注意及時切斷需要回收對象與根的聯系,雖然標記清除算法已經足夠強壯,就像下面代碼:
var obj,ele=document.getElementById('myId'); obj.div=document.createElement('div'); ele.appendChild(obj.div); // 刪除DOM元素 ele.removeChild(obj.div);
如果我們只是做小型項目開發,JS用的比較少的話,內存管理可以不用太在意,但是如果是大項目(SPA,服務器或桌面應用),那就需要考慮好內存管理問題了。
#
4.內存泄露(Memory Leak)
#
4.1 內存泄露概念
在計算機科學中,內存泄漏指由于疏忽或錯誤造成程序未能釋放已經不再使用的內存。內存泄漏并非指內存在物理上的消失,而是應用程序分配某段內存后,由于設計錯誤,導致在釋放該段內存之前就失去了對該段內存的控制,從而造成了內存的浪費。 ——維基百科
其實簡單理解:一些不再使用的內存無法被釋放。
當內存占用越來越多,不僅影響系統性能,嚴重的還會導致進程奔潰。
4.2 內存泄露案例
未定義的變量,會被定義到全局,當頁面關閉才會銷毀,這樣就造成內存泄露。如下:
function fun(){ name='pingan'; };
var data={}; setInterval(function(){ var render=document.getElementById('myId'); if(render){ render.innderHTML=JSON.stringify(data); } }, 1000);
var str=null; var fun=function(){ var str2=str; var unused=function(){ if(str2) console.log('is unused'); }; str={ my_str=new Array(100000).join('--'); my_fun=function(){ console.log('is my_fun'); }; }; }; setInterval(fun, 1000);
定時器中每次調用fun,str都會獲得一個包含巨大的數組和一個對于新閉包my_fun的對象,并且unused是一個引用了str2的閉包。
整個案例中,閉包之間共享作用域,盡管unused可能一直沒有調用,但my_fun可能被調用,就會導致內存無法回收,內存增長導致泄露。
var ele={ img : document.getElementById('my_img') }; function fun(){ ele.img.src="http://www.baidu.com/1.png"; }; function foo(){ document.body.removeChild(document.getElementById('my_img')); };
即使foo方法將my_img元素移除,但fun仍有引用,無法回收。
4.3 內存泄露識別方法
通過Chrome瀏覽器查看內存占用:
步驟如下:
如果內存占用基本平穩,接近水平,就說明不存在內存泄漏。
反之,就是內存泄漏了。
命令行可以使用 Node 提供的process.memoryUsage方法。
console.log(process.memoryUsage()); // { rss: 27709440, // heapTotal: 5685248, // heapUsed: 3449392, // external: 8772 }
process.memoryUsage返回一個對象,包含了 Node 進程的內存占用信息。該對象包含四個字段,單位是字節,含義如下。
判斷內存泄漏,以heapUsed字段為準。
公眾號:前端自習課
eb服務是.net中讓人激動的部分,幾乎所有你能叫出名字的服務都有一些執行服務器端代碼的機制:正巧每種語言都一個類庫,因此在HTTP中生成一個GET請求變得很簡單,解析出XML也有了些捷徑。
這種方案給你提供了一種跨平臺,跨語言,跨廠商乃至一切的方法,只要它們都在INTERNET上或是以其他的方式相連,我們就可以在某個程序的代碼中調用另外一個完全不同的機器上的代碼。
這就是隱藏在WEB服務背后的基本觀念。使用類似于WEB服務描述語言(說 wizdle會更酷一些)開發有一定的標準,它們涵蓋了這些技術細節。
如果你用Visual Studio.NET創建一個WEB服務,它將滿足這些標準。如果你只是需要WEB服務,而不管它是如何創建的,通過Visual Studio.NET,你會發現借用他人的代碼是如此簡單。
編寫一個WEB服務
為了編寫一個WEB服務,你至少要用一種方法寫一個類。這個類必須有WebService屬性,方法也要有WebMethod屬性。WEB方法能夠接受和返回任何可用的類型,包括你定義的對象實例。它們能做任何事情:維護數據庫數據的內外一致性,做任何形式的運算,甚至調用另外一個WEB方法來完成任務。
在Visual Studio.NET中創建一個新工程。在Visual C++工程模板中,選擇可管理的WEB服務。修改后是:
<@ WebService Class=Calculator.CalculatorService %>
我獲得了一個叫做HelloWorld()的方法,把它改成Add()很簡單——我僅僅更改了.cpp文件和.h文件的名稱,改變了簽名以便它能夠接受浮點數,然后加了些代碼以返回和。
類聲明的結束部分:
using <System.Web.Services.dll>
using namespace System;
using namespace System::Web;
using namespace System::Web::Services;
namespace Calculator
{
public __gc
class CalculatorService : public WebService
{
public:
[System::Web::Services::WebMethod]
double Add(double x, double y);
};
}
實現的部分:
#include "stdafx.h"
#include "Calculator.h"
#include "Global.asax.h"
namespace Calculator
{
double CalculatorService::Add(double x, double y)
{
return x + y;
}
}
#include <iostream>using namespace std;
int main (){
cout << "Content-type:text/html\r\n\r\n";
cout << "<html>\n";
cout << "<head>\n";
cout << "<title>Hello World - 第一個 CGI 程序</title>\n";
cout << "</head>\n";
cout << "<body>\n";
cout << "<h2>Hello World! 這是我的第一個 CGI 程序</h2>\n";
cout << "</body>\n";
cout << "</html>\n";
return 0;}
使用WEB服務
寫一個WEB服務相當簡單:你只需要一個類屬性,一個方法屬性和calculator.asmx文件,而這三個都由Visual Studio生成。
最簡單的方法是鍵入URL到Calculator.asmx然后按回車。你會看到和以前運行WEB服務工程時同樣的文件。點擊添加參數結束這個過程。
參數一旦添加,調用WEB服務就像調用任何C++類一樣。添加參數建立一個頭文件,這個文件在任何你想使用WEB服務時都可以包括進去。
近一直在開源社區瀏覽一些開源的后臺管理框架,從中找到一個自己中意的項目很難,不是后期開發太繁瑣,要不就是界面不友好,想了想還是自己搭建一個后臺管理模板,以后在開發過程中直接拿過來開發就可以了。
此項目是基于一個HEYUI搭建的一款后臺管理模板。
HeyUI 是一套基于 Vue2.0 的開源 UI 組件庫,主要服務于一些中后臺產品。
HeyUI提供的是一整套解決方案,所有的組件提供全局的可配置模式。真正的數據驅動、全局的配置模式、數據字典化
后臺布局通常采用的上左右布局方式
第一部分:登錄頁面
登錄頁面主要是靠設計,設計做的好,前端怎么實現出來都好看,這里的登錄界面也沒有經過專業的設計,反正還能看的過眼吧(請忽略開發的審美)。
功能點:選擇記住登錄賬號時,在登錄成功后會將賬號保存到本地存儲,下次進入頁面直接從本地存儲獲取登錄賬號,同時在點擊登錄時會將兩個輸入框屬性設置為只讀,按鈕出現加載效果。
第二部分:管理后臺頭部
相對于整個系統來說,這部分是最簡單的一部分,其中大多數要寫的只是css樣式,唯一一點就是登錄用戶這塊,用到了vuex狀態管理,如果你在某一處修改了用戶信息,只需要執行
this.$store.dispatch('userStore/setUserInfo', {name: '超級管理員'})
就可以修改頁面上顯示的登錄用戶的用戶名。
第三部分:左邊菜單管理
使用Menu組件即可,通過key匹配當前選中的菜單,執行
this.$refs.menu.select(this.$route.name)
選中當前菜單。
第四部分:內容區域
采用子路由加載方式 <router-view />
第五部分:模板主題配置
這里使用的是HEYUI的自定義主題,需要自己定義一個less文件做引用,在less文件中定義主題變量,覆蓋框架默認的主題,本模板就是采用自定義less變量更換主題的。
主題文件位置:src\style\themes.less
原始全局變量文件你可以在github上查看。
https://github.com/heyui/heyui/blob/master/themes/var.less
如果你還需要一些更細節化的調整,官方建議可以新建overwrite.less對已有的class進行覆蓋修改。
項目地址:https://gitee.com/yangon/vue-admin-frame
使用
git clone https://gitee.com/yangon/vue-admin-frame
cd vue-admin-frame
npm install
npm run dev
本項目引用到的框架KEYUI:https://www.heyui.top
如果本文對您有所幫助, 請關注科技男給與支持,后期為您帶來更多的技術文章。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。