用 CSS 最困難的部分之一是處理CSS的權重值,它可以決定到底哪條規則會最終被應用,尤其是如果你想在 Bootstrap 這樣的框架中覆蓋其已有樣式,更加顯得麻煩。不過隨著 CSS 層的引入,這一切都發生了變化。 這個新功能允許您創建自己的自定義 CSS 層,這是有史以來第一次確定所有 CSS 代碼權重的層次結構。 在本文中,我將剖析這對您意味著什么,它是如何工作的,以及您今天如何開始使用它。
什么是層(Layers)
創建您自己的自定義圖層是 CSS 的新功能,但圖層從一開始就存在于 CSS 中。 CSS 中有 3 個不同的層來管理所有樣式的工作方式。
瀏覽器(也稱為用戶代理)樣式 - user agent style
用戶樣式 - User Styles
作者樣式 - Author Styles
瀏覽器樣式是應用于瀏覽器的默認樣式。這就是為什么 Chrome 和 Safari 中的按鈕看起來不同的原因。在瀏覽器層中找到的樣式在瀏覽器之間是不同的,并且給每個瀏覽器一個獨特的外觀。
下一層是用戶樣式,這并不是您真正需要擔心的事情。這些通常是用戶可以編寫并注入瀏覽器的自定義樣式,但瀏覽器不再真正支持這些樣式。用戶可能會更改一些瀏覽器設置,這些設置會向該圖層添加樣式,但在大多數情況下,可以完全忽略該層。
最后,我們來到作者層。這是您最熟悉的層,因為您編寫的每一段 CSS 代碼都屬于這一層。
這些層分開的原因是因為它可以很容易地覆蓋瀏覽器樣式和用戶樣式中定義的代碼,因為層定義了自己的層次結構,完全忽略了權重的影響。
這 3 個 CSS 層是有序的(瀏覽器樣式、用戶樣式、然后是作者樣式),后面層中的每個樣式都將覆蓋前一層的任何樣式。這意味著即使瀏覽器樣式定義了一個超級特定的選擇器,例如#button.btn.super-specific,并且您的作者樣式定義了一個超級通用的選擇器,例如按鈕,您的作者樣式仍然會覆蓋瀏覽器樣式。
這實際上已經是您可能一直在使用而沒有意識到的東西。
* {
box-sizing: border-box;
}
上面的選擇器沒有權重,因為 * 符號對權重沒有貢獻。 這意味著例如使用 p 作為選擇器的 p 標簽的瀏覽器樣式在技術上比 * 選擇器更具體,權重更高。 但是,這一切并不重要,因為作者樣式位于比瀏覽器樣式層晚的層中,因此您的代碼將始終覆蓋瀏覽器樣式。
理解這一點至關重要,因為使用這個新的圖層 API,您可以在作者圖層中創建自己的圖層,從而更輕松地處理特定性。
如何創建你自己的層
下面來看個例子:
很明顯,這是我們正常理解的CSS, ID設置的顏色權重更高,所以按鈕顯示為紅色。讓我們使用@layer給它們加上兩個層,看看是什么效果:
按鈕變成藍色。為什么會這樣?
我們給兩條CSS分別建立了base和utilities層,很明顯,后面創建的層的樣式覆蓋了前面層的樣式,盡管前面層的樣式有更高的權重。這就是層的默認工作原理。當然層的順序是可以指定的,
@layer utilities, base;
@layer utilities, base;
您需要做的就是編寫@layer 關鍵字,后跟以逗號分隔的層列表。 這將按從左到右的順序定義所有層,其中列出的第一層到最后一層的權重是依次增加的。 然后,您可以稍后使用普通的@layer 語法向每個層添加代碼,而不必擔心定義層的順序,因為它們都在這一行中定義。 需要注意的是,這行代碼必須在定義任何層之前出現,所以我通常將它作為我的 CSS 文件中的第一行。如上圖,通過指定層的順序,我們讓base層應用在utilities層之后,所以按鈕又顯示為紅色。
導入層
上面這兩種方式都是導入bootstrap框架的CSS,并且把他們放在framework層中,這樣你如果想要覆蓋它已有的樣式,只需要新建一個自己的層,放置在framework層后面就行。像下面這樣。
匿名層
匿名層不常用,但它寫在后面可以覆蓋其他層的樣式,像下面可以把按鈕設為橙色。
不在層里的樣式
不在層里的樣式會有更高的權重,下面這個列表會讓你看得更清楚覆蓋是怎么發生的
層還可以重疊設置,不過很少用。具體的用法可以查閱相關文檔。
瀏覽器支持
自從IE死了以后,所有主流瀏覽器都已支持這一特性。大家請放心使用。
前置知識
絕大多數的程序語言,他們的內存生命周期基本一致:
對于所有的編程語言,第二部分都是明確的。而第一和第三部分在底層語言中是明確的。
但在像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字段為準。
公眾號:前端自習課
前言
該項目為前后端分離項目的前端部分,后端項目mall地址:傳送門。
項目介紹
mall-admin-web是一個電商后臺管理系統的前端項目,基于Vue+Element實現。 主要包括商品管理、訂單管理、會員管理、促銷管理、運營管理、內容管理、統計報表、財務管理、權限管理、設置等功能。
項目演示
項目在線演示地址:http://39.98.190.128/index.html
項目布局
src -- 源碼目錄 ├── api -- axios網絡請求定義 ├── assets -- 靜態圖片資源文件 ├── components -- 通用組件封裝 ├── icons -- svg矢量圖片文件 ├── router -- vue-router路由配置 ├── store -- vuex的狀態管理 ├── styles -- 全局css樣式 ├── utils -- 工具類 └── views -- 前端頁面 ├── home -- 首頁 ├── layout -- 通用頁面加載框架 ├── login -- 登錄頁 ├── oms -- 訂單模塊頁面 ├── pms -- 商品模塊頁面 └── sms -- 營銷模塊頁面
搭建步驟
https://github.com/macrozheng/mall-admin-web
*請認真填寫需求信息,我們會在24小時內與您取得聯系。