本篇博客記錄一下我在實習的公司的後臺管理系統開發框架中學習到的一種關於網站的緩存(Cache)的實現方法,我會在弄懂的基礎上,將該方法在.net core上進行實現。由於公司開發都是基於.net framework的,可是在.net 這一塊,.net framework正在逐漸被.net core所取代,而目前公司的前輩們因爲開發任務較重,並無着手使用.net core的打算,因此,我本身打算爲公司搭建一個基於.net core的後臺開發框架,這對本身是一個挑戰,但收穫仍是很大的,在這個過程當中,我學到了不少。下面我記錄一下咱們公司關於網站設計中Cache的一種設計與實現方法(先說在.net mvc下的實現方法,後續會寫另外一篇.net core的實現方法): css
-
整體設計:
咱們知道的緩存通常分爲3種,分別是 Cookies,Session和Cache,這三種的區別和使用場景我在這裏就不說了,網上有大神的博客說的很清楚。Cookies主要用於客戶端,Session和Cache用於服務端,本篇主要講Cahe的使用。Cache存儲於服務器的內存中,容許自定義如何緩存數據項,以及緩存時間有多長。當系統內存缺少時,緩存會自動移除不多使用或者使用優先級較低的緩存項以釋放內存。Cache的使用能夠提升整個系統的運行效率。html
Cache在使用上也是(key,value)形式的,關於插入、獲取、移除什麼的,均可以在Cache類中去查看,這裏貼出Cache這個類的內容:前端
#region 程序集 System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a // C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\System.Web.dll #endregion using System.Collections; using System.Reflection; namespace System.Web.Caching { // // 摘要: // Implements the cache for a Web application. This class cannot be inherited. [DefaultMember("Item")] public sealed class Cache : IEnumerable { public static readonly DateTime NoAbsoluteExpiration; public static readonly TimeSpan NoSlidingExpiration; public Cache(); public object this[string key] { get; set; } public int Count { get; } public long EffectivePrivateBytesLimit { get; } public long EffectivePercentagePhysicalMemoryLimit { get; } public object Add(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback); public object Get(string key); public IDictionaryEnumerator GetEnumerator(); public void Insert(string key, object value); public void Insert(string key, object value, CacheDependency dependencies); public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback); public object Remove(string key); } }
能夠看到裏面有Add,Get,Insert之類的東西,這些用法網上也有很清楚的講述,我也不贅述,也說不清楚,哈哈。git
下面,結合上面那張示意圖,來講明一下我要講的緩存設計,我的感受仍是比較好的。es6
<key,value>的形式,就是一個value對應一個key,經過key能夠設置value的值,也能夠獲取value的值。在這裏咱們把 每一個用戶登陸時生成的一個惟一的 id 作爲 cache的key,而後把但願放到緩存中的數據做爲value,進行緩存數據的處理。可是,咱們放到value中的值,多是有不一樣用途不一樣種類的一些值,好比,登陸用戶的基本信息,該系統存儲的菜單數據,這兩個就是用途徹底不相干的兩類數據,怎麼存儲呢?再另外使用一個key值不一樣的cache,這應該也是能夠的。可是,咱們這裏講的方法只使用一個Cache。github
具體作法呢,就是把這個value定義爲一個 Dictionary<key,value>類型的值,這樣在value裏面,咱們就能夠經過設置不一樣的key值,來存儲不一樣用途的緩存數據了。ajax
-
第一步
首先,先定義一個存儲value數據的類,代碼以下:正則表達式
UserCache.csjson
using System.Collections.Generic; namespace Common { public class UserCache { private readonly Dictionary<string, object> cacheDictionary = new Dictionary<string, object>(); private readonly object lockObj = new object(); /// <summary> /// 索引器 /// </summary> /// <param name="key">key</param> /// <returns>緩存對象</returns> public object this[string key] { get { lock (lockObj) { return cacheDictionary.ContainsKey(key) ? cacheDictionary[key] : null; } } set { lock(lockObj) { if (cacheDictionary.ContainsKey(key)) { cacheDictionary[key] = value; } else { cacheDictionary.Add(key, value); } } } } public void Remove(string key) { lock (lockObj) { if(cacheDictionary.ContainsKey(key)) { cacheDictionary.Remove(key); } } } public void Clear() { lock(lockObj) { cacheDictionary.Clear(); } } } }
上面的代碼,用到了一個索引器,這使得咱們能夠像數組那樣用 XXX[index]這樣的方法訪問和設置數據,從代碼中咱們能夠看到,這個類最終都實現對 cacheDictionary 這個字典的操做,由於咱們的數據都存儲在這個字典中。無論你想存儲什麼數據只須要定義一個key值,而後存儲到字典中便可。數組
-
第二步
定義好UserCache.cs後,咱們再來寫關於緩存操做的類:
先定義緩存操做類,而後書寫關於緩存操做的代碼:
WebCache.cs(部分)
using System; using System.Web; using System.Web.Caching; using Common; namespace Console { /// <summary> /// 緩存操做類 /// </summary> public class WebCache { #region 私有變量 private const string UserIdentifyKey = "CacheUserIdentifyKey"; #endregion #region 私有方法 private static string GetUserIdentify() { if (HttpContext.Current.Session[UserIdentifyKey] != null) return HttpContext.Current.Session[UserIdentifyKey].ToString(); var identify = Guid.NewGuid().ToString(); HttpContext.Current.Session[UserIdentifyKey] = identify; return identify; } private static UserCache GetUserCache() { var identify = GetUserIdentify(); if (HttpContext.Current.Cache.Get(identify) == null) { HttpContext.Current.Cache.Insert(identify, new UserCache(), null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 20, 0), CacheItemPriority.High, CacheRemovedCallback); } return HttpContext.Current.Cache.Get(identify) as UserCache; } /// <summary> /// 緩存被移除時觸發 /// </summary> /// <param name="key">被移除的緩存的key</param> /// <param name="value">被移除的緩存的值</param> /// <param name="reason">移除緣由</param> private static void CacheRemovedCallback(string key, object value, CacheItemRemovedReason reason) { // 緩存被移除時執行的操做 // 若是是手動移除,則不處理 //if (reason == CacheItemRemovedReason.Removed) // return; // 此處訪問頁面會報錯,暫時註釋掉 // ShowNotification(MessageType.Warning, "警告", "因爲您過久沒操做頁面已過時,請從新登陸!", true); } #endregion } }
首先看上面的代碼:
上面三段代碼中,核心的代碼是第二段,須要注意的是,都是靜態方法:
GetUserCache() 方法
固然,咱們仍是從第一段代碼開始提及,
GetUserIdentify()
private static string GetUserIdentify() { if (HttpContext.Current.Session[UserIdentifyKey] != null) return HttpContext.Current.Session[UserIdentifyKey].ToString(); var identify = Guid.NewGuid().ToString(); HttpContext.Current.Session[UserIdentifyKey] = identify; return identify; }
在一個用戶登陸之初,咱們首先給這個用戶生成一個惟一的用戶 id ,而後把這個id保存到 Session中 (Session也是<key,value>形式的)。在第二段代碼中,經過 GetUserIdentify()方法獲取用戶的惟一 id,而後把這個惟一 id做爲 Cache的key值。
而後,咱們來看第二段代碼:
GetUserCache():
private static UserCache GetUserCache() { var identify = GetUserIdentify(); if (HttpContext.Current.Cache.Get(identify) == null) { HttpContext.Current.Cache.Insert(identify, new UserCache(), null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 20, 0), CacheItemPriority.High, CacheRemovedCallback); } return HttpContext.Current.Cache.Get(identify) as UserCache; }
這段代碼,首先判斷Cache中是否有值(是否存在這個key的Cache),若不存在,則建立一個(代碼中的 new UserCache())。.net framework中Cache操做使用 HttpContext.Current.Cache,Insert後有若干個參數,意思分別是:
identify:key值;
new UserCache():value值;
第三個參數是:緩存依賴項 CacheDependency ,這裏是 null;
Cache.NoAbsoluteExpiration:絕對過時時間 ,這裏設置爲無絕對過時時間;
new TimeSpan(0, 20, 0):這是滑動過時時間,此處設置爲 20 minite;
CacheItemPriority.High:緩存優先級,此處爲 high;
CacheRemovedCallback: 緩存移除時的回調函數,這個回調函數的參數是固定寫法,必須按照規定寫,三個參數以及參數類型 不可缺乏也不可寫錯,不然會報錯;(具體可見上面的第三段代碼)
上面說到,若不存在,則建立一個 ,若存在,那麼就直接返回便可。
接下來,在WebCache.cs中定義一些公共方法,用來供外界的方法調用,以實現對緩存的操做,代碼以下:
WebCache.cs(全):
using System; using System.Web; using System.Web.Caching; using Common; namespace Console { /// <summary> /// 緩存操做類 /// </summary> public class WebCache { #region 私有變量 private const string UserIdentifyKey = "CacheUserIdentifyKey"; #endregion #region 公共方法 /// <summary> /// 獲取緩存 /// </summary> /// <param name="key">鍵</param> /// <returns></returns> public static object GetCache(string key) { return GetUserCache()[key]; } /// <summary> /// 設置緩存 /// </summary> /// <param name="key">鍵</param> /// <param name="value">值</param> /// <returns></returns> public static bool SetCache(string key, object value) { try { var userCache = GetUserCache(); userCache[key] = value; return true; } catch { return false; } } /// <summary> /// 清空緩存 /// </summary> /// <returns></returns> public static bool ClearCache() { try { // 只清除緩存內容 // GetUserCache().Clear(); // 直接從Cache裏移除 var identify = GetUserIdentify(); HttpContext.Current.Cache.Remove(identify); return true; } catch { return false; } } /// <summary> /// 移除緩存 /// </summary> /// <param name="key">鍵</param> /// <returns></returns> public static bool RemoveCache(string key) { try { GetUserCache().Remove(key); return true; } catch { return false; } } #endregion #region 私有方法 private static string GetUserIdentify() { if (HttpContext.Current.Session[UserIdentifyKey] != null) return HttpContext.Current.Session[UserIdentifyKey].ToString(); var identify = Guid.NewGuid().ToString(); HttpContext.Current.Session[UserIdentifyKey] = identify; return identify; } private static UserCache GetUserCache() { var identify = GetUserIdentify(); if (HttpContext.Current.Cache.Get(identify) == null) { HttpContext.Current.Cache.Insert(identify, new UserCache(), null, Cache.NoAbsoluteExpiration, new TimeSpan(0, 20, 0), CacheItemPriority.High, CacheRemovedCallback); } return HttpContext.Current.Cache.Get(identify) as UserCache; // as是一種強制類型轉化的方式 } /// <summary> /// 緩存被移除時觸發 /// </summary> /// <param name="key">被移除的緩存的key</param> /// <param name="value">被移除的緩存的值</param> /// <param name="reason">移除緣由</param> private static void CacheRemovedCallback(string key, object value, CacheItemRemovedReason reason) { // 緩存被移除時執行的操做 // 若是是手動移除,則不處理 //if (reason == CacheItemRemovedReason.Removed) // return; // 此處訪問頁面會報錯,暫時註釋掉 // ShowNotification(MessageType.Warning, "警告", "因爲您過久沒操做頁面已過時,請從新登陸!", true); } #endregion } }
依次定義了GetCache(),SetCache(),RemoveCache(),ClearCache()四個方法,供外界調用,來實現對緩存的操做。
到這裏,基本上關於這個Cache的實現就已經講完了,下面,給出一段代碼,作一個使用的示例。
private const string LoginUserKey = "CacheKey-LoginUserCacheKey"; /// <summary> /// 獲取或設置當前登陸用戶 /// </summary> public static User LoginUser { get { return WebCache.GetCache(LoginUserKey) as User; } set { WebCache.SetCache(LoginUserKey, value); } }
SetCache():
WebCache.SetCache(key, value);
RemoveCache():
RemoveCache(key); //移除字典中某個緩存值
ClearCache();
ClearCache(); //清空緩存的字典
關於這個緩存設計,就記錄到這裏了,關於.net core下的實現,由於.net core下並無System.Web這個類,因此它的Cache實現方式,與.net 下的實現方式有些區別,這個,我會另起一篇博客去記錄。
說明:本片博客並無完整的demo,全部的代碼都已貼出。
ES6學習筆記
ES6的標準發佈好久了,ES7和ES8已經出來了,直到如今纔開始學習,已經有點晚了,但願能夠趕得上吧。
(如下內容根據阮一峯老師的ES6標準入門進行整理,若要深刻學習,請點擊:http://es6.ruanyifeng.com/)
-
1.簡介
ES是ECMAScript的簡稱,ECMA是國際化標準化組織的簡稱,因此ECMAScript其實就是JavaScript的國際標準。寫Js代碼時,儘可能聽從標準。
-
2.let 和 const命令
(1)let
let用來聲明變量。它的用法相似於 var ,可是所聲明的變量,只在 let 命令所在的 代碼塊 (一個大括號包裹的區域,稱爲代碼塊{ 代碼塊內 })內有效;如:
var a = []; for(var i = 0;i < 10 ;i++){ a[i] = function(){ console.log(i); } } a[6]();
(該代碼爲在谷歌瀏覽器控制檯編寫)
上面代碼中,變量 i 是 var 命令聲明的,在全局範圍內都有效,因此全局只有一個變量 i 。每一次循環,變量 i 的值都會發生改變,而循環內被賦給數組 a的函數內部的 console.log(i) ,裏面的 i 指向的就是全局的 i 。也就是說,全部數組 a 的成員裏面的 i ,指向的都是同一個 i ,致使運行時輸出的是最後一輪的 i 的值,也就是 10。
使用let時:
var a = []; for(let i = 0;i < 10 ;i++){ a[i] = function(){ console.log(i); } } a[6]();
上面代碼中,變量 i 是 let 聲明的,當前的 i 只在本輪循環有效,因此每一次循環的 i 其實都是一個新的變量,因此最後輸出的是 6 。你可能會問,若是每一輪循環的變量 i 都是從新聲明的,那它怎麼知道上一輪循環的值,從而計算出本輪循環的值?這是由於 JavaScript 引擎內部會記住上一輪循環的值,初始化本輪的變量 i 時,就在上一輪循環的基礎上進行計算。
let與var的區別:
(1)let 聲明的變量必須嚴格遵照 先定義 後使用 的的原則,若是在定義一個變量以前,就已經使用它了,則會報錯;
var能夠在聲明一個變量以前使用一個變量,但這個變量的值會是 undefined; (這被稱爲 var 的 變量提高 現象 ,這一現象是很是奇怪的 ,let的出現就是爲了改變這一現象);
console.log(a); var a = 10;
(一個在線運行的代碼工具)
console.log(b); let b = 10;
(2)暫時性死區:
前面說了,let只在其所在的 代碼塊 內有效,因此一旦一個代碼塊聲明瞭一個let變量,那這個變量就和這個代碼塊綁定在一塊兒了,在代碼塊聲明這個變量以前使用這個變量就會報錯;
(3)不容許重複聲明
function func() { let a = 10; var a = 1; }
function func() { var a = 10; var a = 1; }
(不報錯)
ES6的 塊級做用域:
let爲ES6新增了塊級做用域:每個塊級裏的let變量互不干擾:
function func(){ let a = 10; if(true){ let a = 5; console.log(a); } console.log(a); }
(在兩個不一樣塊裏的內容互不干擾)
ES6中 塊級做用域中能夠聲明方法:
以下代碼,在ES5中是非法的,但在ES6中是合法的,只不過,塊級做用域中 聲明的方法 只能在塊級做用域中調用。
// 狀況一 if (true) { function f() {} } // 狀況二 try { function f() {} } catch(e) { // ... }
考慮到環境致使的行爲差別太大,應該避免 在塊級做用域內聲明函數。若是確實須要,也應該寫成函數表達式,而不是函數聲明語句。
// 塊級做用域內部的函數聲明語句,建議不要使用 { let a = 'secret'; function f() { return a; } } // 塊級做用域內部,優先使用函數表達式 { let a = 'secret'; let f = function () { return a; }; }
注意:ES6的塊級做用域必須有 大括號 ,不然JavaScript引擎就會認爲沒有 塊級做用域。
(2)const
1>const聲明一個只讀變量,一旦聲明,常量的值就不能改變;
2>const聲明的變量不得改變值,這意味着,const一旦聲明變量,就必須當即初始化,不能留到之後賦值;
3>const 的做用域和let 同樣,只在其所在的塊級做用域有效;
4>const 只能在聲明後才能使用;
5>const 變量也不能重複聲明;
const 的本質:
const保證的並非變量的值不能改動,而是變量指向的那個內存地址所保存的數據不得改動。對於一些簡單類型的數據(數值、字符串、布爾值),就至關因而保存的常量了,可是,對於複合類型的數據,如對象,數組,變量指向的內存地址,保存的只是一個指向實際數據的指針,const只能保證這個指針是固定的(即老是指向另外一個固定的地址),至於它指向的數據結構是否是可變的,就徹底不能控制了。所以,將一個對象聲明爲常量必須很是當心。
(3)ES6聲明變量的6中方法
ES5 > 兩種方法:var 和 funcion;
ES6 > 六種方法: (1) var (2) function (3)let (4) const (5) import (6) class;
3.變量的解構賦值
(1)數組的結構賦值
ES5賦值方法:
let a = 5; let b = 6; let c = 7;
ES6賦值方法:
let [a,b,c] = [5,6,7];
現象:
說明:ES6 容許按照必定模式,從數組和對象中提取值,對變量進行賦值,這被稱爲解構(Destructuring)。本質上,這種寫法屬於「模式匹配」,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值。
下面是一些例子:
let [foo, [[bar], baz]] = [1, [[2], 3]]; foo // 1 bar // 2 baz // 3 let [ , , third] = ["foo", "bar", "baz"]; third // "baz" let [x, , y] = [1, 2, 3]; x // 1 y // 3 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail // [2, 3, 4] let [x, y, ...z] = ['a']; x // "a" y // undefined z // []
解析不成功,變量的值就是undefined.
注意:上述方式定義變量,必需要求等式右邊是數組(或可遍歷的結構),如下寫法出錯:
// 報錯 let [foo] = 1; let [foo] = false; let [foo] = NaN; let [foo] = undefined; let [foo] = null; let [foo] = {};
默認值:
解構賦值容許指定默認值,以下:
let [foo = true] = []; foo // true let [x, y = 'b'] = ['a']; // x='a', y='b' let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'
當所要賦的值是 undefined 時,則會給變量賦默認值。
(2)對象的解構賦值
對象也能夠進行解構賦值,具體方法爲:
let { foo, bar } = { foo: 'aaa', bar: 'bbb' }; foo // "aaa" bar // "bbb"
對象與數組結構賦值的區別:
數組的元素是按次序排列的,變量的取值由它的位置決定;而對象的屬性沒有次序,變量必須與屬性同名,才能取到正確的值;
對象的結構賦值也能夠指定默認值,默認值生效的條件是,對象的屬性值嚴格等於undefined.
注意:
(1)若是要將一個已經聲明的變量用於解構賦值,必須很是當心;下面寫法出錯:
let x ; {x} = {x:1};
JavaScript引擎會把{x}解析成一個代碼塊,從而發生語法錯誤,只有不把{x}放在行首,才能解決這個問題;如下寫法正確:
let x; ({x} = {x:1});
(2)解構賦值容許等號左邊的模式之中,不放置任何變量名。所以,能夠寫出很是古怪的賦值表達式;
({} = [true, false]); ({} = 'abc'); ({} = []);
上面的表達式雖然毫無心義,可是語法是合法的,能夠執行。
(3)字符串解構賦值
字符串也能夠解構賦值。這是由於此時,字符串被轉換成了一個相似數組的對象;
const [a, b, c, d, e] = 'hello'; a // "h" b // "e" c // "l" d // "l" e // "o"
(4)函數參數的解構賦值
函數的參數也可使用解構賦值。以下:
function add([x, y]){ return x + y; } add([1, 2]); // 3
上面代碼中,函數add
的參數表面上是一個數組,但在傳入參數的那一刻,數組參數就被解構成變量x和y。對於函數內部的代碼來講,它們能感覺到的參數就是x和y。
(5)解構過程當中的圓括號的問題
只要有可能致使解構的歧義,就不得使用圓括號。
不能使用的狀況:
1> 變量聲明語句
// 所有報錯 let [(a)] = [1]; let {x: (c)} = {}; let ({x: c}) = {}; let {(x: c)} = {}; let {(x): c} = {}; let { o: ({ p: p }) } = { o: { p: 2 } };
上面 6 個語句都會報錯,由於它們都是變量聲明語句,模式不能使用圓括號。
2>函數參數
函數參數也屬於變量聲明,所以不能帶有圓括號。
// 報錯 function f([(z)]) { return z; } // 報錯 function f([z,(x)]) { return x; }
3>賦值語句的模式
// 所有報錯 ({ p: a }) = { p: 42 }; ([a]) = [5];
可使用的狀況:
可使用圓括號的狀況只有一種:賦值語句的非模式部分,可使用圓括號
[(b)] = [3]; // 正確 ({ p: (d) } = {}); // 正確 [(parseInt.prop)] = [3]; // 正確
(6)解構賦值的做用
1>交換變量的值:
let x = 5; let y = 10; [x,y] = [y,x]
2>從函數返回多個值
函數只能返回一個值,若是要返回多個值,只能將它們放在數組或對象裏返回。有了解構賦值,取出這些值就很是方便。
// 返回一個數組 function example() { return [1, 2, 3]; } let [a, b, c] = example(); // 返回一個對象 function example() { return { foo: 1, bar: 2 }; } let { foo, bar } = example();
3>函數參數的定義
解構賦值能夠方便地將一組參數與變量名對應起來。
// 參數是一組有次序的值 function f([x, y, z]) { ... } f([1, 2, 3]); // 參數是一組無次序的值 function f({x, y, z}) { ... } f({z: 3, y: 2, x: 1});
4>提取Json數據
解構賦值對提取 JSON 對象中的數據,尤爲有用。
let jsonData = { id: 42, status: "OK", data: [867, 5309] }; let { id, status, data: number } = jsonData; console.log(id, status, number); // 42, "OK", [867, 5309]
5>函數參數的默認值
jQuery.ajax = function (url, { async = true, beforeSend = function () {}, cache = true, complete = function () {}, crossDomain = false, global = true, // ... more config } = {}) { // ... do stuff };
指定參數的默認值,就避免了在函數體內部再寫var foo = config.foo || 'default foo';
這樣的語句。
6>遍歷Map解構
const map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); for (let [key, value] of map) { console.log(key + " is " + value); } // first is hello // second is world
4.字符串的新增方法
(1)String.fromCodePoint()
ES5 提供String.fromCharCode()方法,用於從 Unicode 碼點返回對應字符,可是這個方法不能識別碼點大於0xFFFF
的字符。
ES6 提供了String.fromCodePoint()方法,能夠識別大於0xFFFF
的字符,彌補了String.fromCharCode()
方法的不足。
(2)String.Raw()
ES6 還爲原生的 String 對象,提供了一個raw()方法。該方法返回一個斜槓都被轉義(即斜槓前面再加一個斜槓)的字符串,每每用於模板字符串的處理方法。
(3)實例方法:includes(),startWith(),endWith()
傳統方法,JavaScript只有一種方法 indexOf 能夠用來肯定一個字符串是否包含在另外一個字符串中,ES6新增了3個方法:
includes():返回布爾值,表示是否找到了 參數字符串;
startWith(): 返回布爾值,表示參數字符串是否在 原字符串的頭部;
endWith(): 返回布爾值,表示參數字符串是否在 原字符串的尾部;
這三個方法都支持 第二個參數,表示開始搜索的位置:
let s = 'Hello world!'; s.startsWith('world', 6) // true s.endsWith('Hello', 5) // true s.includes('Hello', 6) // false
上面代碼表示,使用第二個參數n
時,endsWith
的行爲與其餘兩個方法有所不一樣。它針對前n
個字符,而其餘兩個方法針對從第n
個位置直到字符串結束。
(4)實例方法repeat()
repeat()方法返回一個新字符串,表示將原字符串重複n次;
'x'.repeat(3) // "xxx" 'hello'.repeat(2) // "hellohello" 'na'.repeat(0) // "
(5)實例方法 padStart(),padEnd()
ES2017 引入了字符串補全長度的功能。若是某個字符串不夠指定長度,會在頭部或尾部補全。
padStart用於頭部補全,endStart用於尾部補全。
'x'.padStart(5, 'ab') // 'ababx' 'x'.padStart(4, 'ab') // 'abax' 'x'.padEnd(5, 'ab') // 'xabab' 'x'.padEnd(4, 'ab') // 'xaba'
1>若是原字符串的長度,等於或大於最大長度,則字符串補全不生效,返回原字符串;
2>若是用來補全的字符串與原字符串,二者的長度之和超過了最大長度,則會截去超出位數的補全字符串;
3>若是省略第二個參數,默認使用空格補全長度;
'x'.padStart(4) // ' x' 'x'.padEnd(4) // 'x '
(6)實例方法:trimStart(),trimEnd()
它們的行爲與trim()一致,trimStart()消除字符串頭部的空格,trimEnd()消除尾部的空格。它們返回的都是新字符串,不會修改原始字符串。
(7)實例方法:matchAll()
matchAll()方法返回一個正則表達式在當前字符串的全部匹配;
探祕 flex 上下文中神奇的自動 margin
探祕 flex 上下文中神奇的自動 margin
爲了引出本文的主題,先看看這個問題,最快水平垂直居中一個元素的方法是什麼?
水平垂直居中也算是 CSS 領域最爲常見的一個問題了,不一樣場景下的方法也各不相同,各有優劣。嗯,下面這種應該算是最便捷的了:
1
2
3
4
5
6
7
8
9
10
|
<div class=
"g-container"
>
<div class=
"g-box"
></div>
</div>
.g-container {
display
: flex;
}
.g-box {
margin
:
auto
;
}
|
上面的
display: flex
替換成display: inline-flex | grid | inline-grid
也是能夠的。
CodePen Demo -- 使用 margin auto 水平垂直居中元素
如何讓 margin: auto
在垂直方向上居中元素
嗯。這裏其實就涉及了一個問題,如何讓 margin: auto
在垂直方向上生效?
換句話說,傳統的 display: block
BFC(塊格式化上下文)下,爲何 margin: auto
在水平方向能夠居中元素在垂直方向卻不行?
一般咱們會使用這段代碼:
1
2
3
4
5
|
div {
width
:
200px
;
height
:
200px
;
margin
:
0
auto
;
}
|
讓元素相對父元素水平居中。可是若是咱們想讓元素相對父元素垂直居中的話,使用 margin: auto 0
是不生效的。
BFC 下 margin: auto
垂直方向沒法居中元素的緣由
查看 CSS 文檔,緣由以下,在 BFC 下:
If both margin-left and margin-right are auto, their used values are equal, causing horizontal centring.
—CSS2 Visual formatting model details: 10.3.3
If margin-top, or margin-bottom are auto, their used value is 0.
簡單翻譯下,在塊格式化上下文中,若是 margin-left
和 margin-right
都是 auto,則它們的表達值相等,從而致使元素的水平居中。( 這裏的計算值爲元素剩餘可用剩餘空間的一半)
而若是 margin-top
和 margin-bottom
都是 auto,則他們的值都爲 0,固然也就沒法形成垂直方向上的居中。
使用 FFC/GFC 使 margin: auto
在垂直方向上居中元素
OK,這裏要使單個元素使用 margin: auto
在垂直方向上可以居中元素,須要讓該元素處於 FFC(flex formatting context),或者 GFC(grid formatting context) 上下文中,也就是這些取值中:
1
2
3
4
5
6
|
{
display
: flex;
display
: inline-flex;
display
: grid;
display
: inline-grid;
}
|
FFC 下 margin: auto
垂直方向能夠居中元素的緣由
本文暫且不談 grid 佈局,咱們業務中需求中更多的多是使用 flex 佈局,下文將着重圍繞 flex 上下文中自動 margin 的一些表現。
嗯,也有不少前端被戲稱爲 flex 工程師,什麼佈局都 flex 一把梭。
查看 CSS 文檔,緣由以下,在 dispaly: flex
下:
- Prior to alignment via justify-content and align-self, any positive free space is distributed to auto margins in that dimension.
CSS Flexible Box Layout Module Level 1 -- 8.1. Aligning with auto margins
簡單翻譯一下,大意是在 flex 格式化上下文中,設置了 margin: auto
的元素,在經過 justify-content
和 align-self
進行對齊以前,任何正處於空閒的空間都會分配到該方向的自動 margin 中去
這裏,很重要的一點是,margin auto 的生效不只是水平方向,垂直方向也會自動去分配這個剩餘空間。
使用自動 margin 實現 flex 佈局下的 space-between | space-around
瞭解了上面最核心的這一句 :
- 在經過
justify-content
和align-self
進行對齊以前,任何正處於空閒的空間都會分配到該維度中的自動 margin 中去
以後,咱們就能夠在 flex 佈局下使用自動 margin 模擬實現 flex 佈局下的 space-between
以及 space-around
了。
自動 margin 實現 space-around
對於這樣一個 flex 佈局:
1
2
3
4
5
6
7
|
<
ul
class="g-flex">
<
li
>liA</
li
>
<
li
>liB</
li
>
<
li
>liC</
li
>
<
li
>liD</
li
>
<
li
>liE</
li
>
</
ul
>
|
若是它的 CSS 代碼是:
1
2
3
4
5
6
|
.g-flex {
display
: flex;
justify-
content
: space-around;
}
li { ... }
|
效果以下:
那麼下面的 CSS 代碼與上面的效果是徹底等同的:
1
2
3
4
5
6
7
8
|
.g-flex {
display
: flex;
// justify-
content
: space-around;
}
li {
margin
:
auto
;
}
|
CodePen Demo -- margin auto 實現 flex 下的 space-around
自動 margin 實現 space-between
同理,使用自動 margin,也很容易實現 flex 下的 space-between
,下面兩份 CSS 代碼的效果的同樣的:
1
2
3
4
5
6
|
.g-flex {
display
: flex;
justify-
content
: space-between;
}
li {...}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
.g-flex {
display
: flex;
// justify-
content
: space-between;
}
li {
margin
:
auto
;
}
li:first-child {
margin-left
:
0
;
}
li:last-child {
margin-right
:
0
;
}
|
CodePen Demo -- margin auto 實現 flex 下的 space-between
固然,值得注意的是,很重要的一點:
Note: If free space is distributed to auto margins, the alignment properties will have no effect in that dimension because the margins will have stolen all the free space left over after flexing.
CSS Flexible Box Layout Module Level 1 -- 8.1. Aligning with auto margins
意思是,若是任意方向上的可用空間分配給了該方向的自動 margin ,則對齊屬性(justify-content/align-self)在該維度中不起做用,由於 margin 將在排布後竊取該緯度方向剩餘的全部可用空間。
也就是使用了自動 margin 的 flex 子項目,它們父元素設置的 justify-content
已經它們自己的 align-self
將再也不生效,也就是這裏存在一個優先級的關係。
使用自動 margin 實現 flex 下的 align-self: flex-start | flex-end | center
自動 margin 能實現水平方向的控制,也能實現垂直方向的控制,原理是同樣的。
用 margin: auto
模擬 flex 下的 align-self: flex-start | flex-end | center
,能夠看看下面幾個 Demo:
不一樣方向上的自動 margin
OK,看完上面的一大段鋪墊以後,大概已經初步瞭解了 FFC 下,自動 margin 的神奇。
不管是多個方向的自動 margin,抑或是單方向的自動 margin,都是很是有用的。
再來看幾個有意思的例子:
使用 margin-left: auto
實現不規則兩端對齊佈局
假設咱們須要有以下佈局:
DOM 結構以下:
1
2
3
4
5
6
7
|
<
ul
class="g-nav">
<
li
>導航A</
li
>
<
li
>導航B</
li
>
<
li
>導航C</
li
>
<
li
>導航D</
li
>
<
li
class="g-login">登錄</
li
>
</
ul
>
|
對最後一個元素使用 margin-left: auto
,能夠很容易實現這個佈局:
1
2
3
4
5
6
7
|
.g-nav {
display
: flex;
}
.g-login {
margin-left
:
auto
;
}
|
此時, auto
的計算值就是水平方向上容器排列全部 li 以後的剩餘空間。
固然,不必定是要運用在第一個或者最後一個元素之上,例如這樣的佈局,也是徹底同樣的實現:
1
2
3
4
5
6
7
8
|
<
ul
class="g-nav">
<
li
>導航A</
li
>
<
li
>導航B</
li
>
<
li
>導航C</
li
>
<
li
>導航D</
li
>
<
li
class="g-login">登錄</
li
>
<
li
>註冊</
li
>
</
ul
>
|
1
2
3
4
5
6
7
|
.g-nav {
display
: flex;
}
.g-login {
margin-left
:
auto
;
}
|
Codepen Demo -- nav list by margin left auto
垂直方向上的多行居中
OK,又或者,咱們常常會有這樣的需求,一大段複雜的佈局中的某一塊,高度或者寬度不固定,須要相對於它所在的剩餘空間居中:
這裏有 5 行文案,咱們須要其中的第3、第四行相對於剩餘空間進行垂直居中。
這裏若是使用 flex 佈局,簡單的 align-self
或者 align-items
好像都無法快速解決問題。
而使用自動 margin,咱們只須要在須要垂直居中的第一個元素上進行 margin-top: auto
,最後一個元素上進行 margin-bottom: auto
便可,看看代碼示意:
1
2
3
4
5
6
7
|
<
div
class="g-container">
<
p
>這是第一行文案</
p
>
<
p
>這是第二行文案</
p
>
<
p
class="s-thirf">一、剩餘多行文案須要垂直居中剩餘空間</
p
>
<
p
class="s-forth">二、剩餘多行文案須要垂直居中剩餘空間</
p
>
<
p
>這是最後一行文案</
p
>
</
div
>
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
.g-container {
display
: flex;
flex-wrap: wrap;
flex-
direction
: column;
}
.s-thirf {
margin-top
:
auto
;
}
.s-forth {
margin-bottom
:
auto
;
}
|
固然,這裏將任意須要垂直居中剩餘空間的元素用一個 div 包裹起來,對該 div 進行
margin: auto 0
也是能夠的。
嗯,很是的好用且方便:CodePen Demo -- 自動margin快速垂直居中任意段落
使用 margin-top: auto
實現粘性 footer 佈局
OK,最後再來看這樣一個例子。
要求:頁面存在一個 footer 頁腳部分,若是整個頁面的內容高度小於視窗的高度,則 footer 固定在視窗底部,若是整個頁面的內容高度大於視窗的高度,則 footer 正常流排布(也就是須要滾動到底部才能看到 footer),算是粘性佈局的一種。
看看效果:
嗯,這個需求若是可以使用 flex 的話,使用 justify-content: space-between
能夠很好的解決,同理使用 margin-top: auto
也很是容易完成:
1
2
3
4
5
6
|
<
div
class="g-container">
<
div
class="g-real-box">
...
</
div
>
<
div
class="g-footer"></
div
>
</
div
>
|
1
2
3
4
5
6
7
8
9
10
11
12
|
.g-container {
height
:
100
vh;
display
: flex;
flex-
direction
: column;
}
.g-footer {
margin-top
:
auto
;
flex-shrink:
0
;
height
:
30px
;
background
: deeppink;
}
|
Codepen Demo -- sticky footer by flex margin auto
上面的例子旨在介紹更多自動 margin 的使用場景。固然,這裏不使用 flex 佈局也是能夠實現的,下面再給出一種不借助 flex 佈局的實現方式:
CodePen Demo -- sticky footer by margin/paddig
值得注意的點
自動 margin 仍是很實用的,可使用的場景也不少,有一些上面提到的點還須要再強調下:
-
塊格式化上下文中
margin-top
和margin-bottom
的值若是是 auto,則他們的值都爲 0 -
flex 格式化上下文中,在經過
justify-content
和align-self
進行對齊以前,任何正處於空閒的空間都會分配到該方向的自動 margin 中去 -
單個方向上的自動 margin 也很是有用,它的計算值爲該方向上的剩餘空間
-
使用了自動 margin 的 flex 子項目,它們父元素設置的
justify-content
以及它們自己的align-self
將再也不生效