My Princess 別低頭 皇冠會掉
When she was younger she would pretend
在她小的時候 她會假裝
That her bedroom was a castle she was fairest in the land
她的卧室是一座城堡 她是這個國度裏最美麗的女孩
And she got older and it all changed
她漸漸長大 一切都物是人非
There was no time for make believe and all the magic slipped away
沒有時間徜徉在幻想裏 所有魔力悄然流逝
Until the light in her eyes it was all but gone
直到她眼中的光芒消失殆盡
‘Cause all the dreams that she had turned out to be wrong
因為她擁有的所有夢想不過都是幼稚可笑的錯誤
So keep your head up princess ‘fore your crown falls
昂首闊步 別讓頭頂的王冠掉落
Know these voices in your head will be your downfall
縈繞腦海裏那些質疑聲音 只會讓你的心不堪一擊
I know it gets so hard but you don’t got far to go
我知道這萬分艱難 而前路並非遙不可及
Yeah keep your head up princess it’s a long road
昂首闊步 這是條漫長的道路
And the path leads right to where they won’t go
是他們都不會選擇去走的一條路
I know it hurts right now but I know you’ll make it home
路程艱辛 但我相信你定會到達終點 載譽歸來
So keep your head up
堅定腳步
Yeah keep your head up
不忘初心
And now she’s grown up works at a bar
現在她長大成人 在酒吧找到一份工作
She traded makeshift gowns for serving rounds from sunrise ‘til it’s dark
她穿着簡單質樸的衣服 披星戴月地賣力工作
And all her friends got someone to hold
她所有的朋友都已找到歸宿
And she’s got no one else still not prepared to make it on her own
可她孑然一身 依然沒有準備好自力更生
And now the light in her eyes it’s now all but gone
現在她眼中的光芒消失殆盡
‘Cause all the dreams that she had turned out to be wrong
因為她擁有的所有夢想不過都是幼稚可笑的錯誤
So keep your head up princess ‘fore your crown falls
昂首闊步 別讓頭頂的王冠掉落
Know these voices in your head will be your downfall
縈繞腦海裏那些質疑聲音 只會讓你的心不堪一擊
I know it gets so hard but you don’t got far to go
我知道這萬分艱難 而前路並非遙不可及
Yeah keep your head up princess it’s a long road
昂首闊步 這是條漫長的道路
And the path leads right to where they won’t go
是他們都不會選擇去走的一條路
I know it hurts right now but I know you’ll make it home
路程艱辛 但我相信你定會到達終點 載譽歸來
So keep your head up
堅定腳步
Yeah keep your head up
不忘初心
One day you’ll find your way back to the start
總有一天 你會找到方向 回到原點
One day you’ll live in your dreams
總有一天 你會沉浸在你的美夢裏
One day you’ll wake up and girl you’ll be a queen
總有一天 你會醒來 你將成為無與倫比的女王
So keep your head up princess ‘fore your crown falls
昂首闊步 別讓頭頂的王冠掉落
Know these voices in your head will be your downfall
縈繞腦海裏那些質疑聲音 只會讓你的心不堪一擊
I know it gets so hard but you don’t got far to go
我知道這萬分艱難 而前路並非遙不可及
Yeah keep your head up princess it’s a long road
昂首闊步 這是條漫長的道路
And the path leads right to where they won’t go
是他們都不會選擇去走的一條路
I know it hurts right now but I know you’ll make it home
路程艱辛 但我相信你定會到達終點 載譽歸來
So keep your head up
堅定腳步
Yeah keep your head up
不忘初心
immyw.com
, 還沒到一年,又把域名換到了 crazywong.com
。之前爲了安全起見,給 Godaddy 開了兩步認證,選擇了 Microsoft Authenticator 儲存驗證。然後倒霉的事情來了,一次不小心的操作,把 Microsoft Authenticator 給刪了,本想著有備份,重新下載軟件再恢復就行。結果可想而知,備份丟了。無奈之下,只能一個個去申訴回來。
Godaddy 的要求比較嚴格,需要提供由政府發行之含照身份證明卡片的彩色影本。能提供的不多,無非就是身份證和護照。不明白現在的網絡,動不動就需要實名認證,上傳身份證。甚至有些還要人臉識別才能使用。本著能不提交就不提交的態度,就算是大公司,也有可能泄露個人資料的時候。
似乎沒有別的方法恢復我的賬號了,只能按要求傳了護照過去。等待了 3.4 天之後,收到了 godaddy 的回信。要求上傳一張手持護照的照片。
手持照片這事就超過我的承受範圍。早知道會這樣,一開始就不會傳護照照片過去了。最終還是考慮放棄這個域名,還好當初有把 renew 給取消了。
心儀的域名都被注冊了,最後選擇了 crazywong.com
.
Wong 是黃(姓)
爲了方便域名記憶,我把博客名字也改成 CrazyWong.
以後設置兩步認證時,真要把二維碼也保存一份,不然就真的 Game Over 了。
]]>本文轉自微信公衆號 - 大海我來了
原文地址 1.5 萬字 CSS 基礎拾遺(核心知識、常見需求)
本篇文章圍繞了 CSS 的核心知識點和項目中常見的需求來展開。雖然行文偏長,但偏基礎,適合初級中級前端閲讀,閲讀的時候請適當跳過已經掌握的部分。
這篇文章斷斷續續寫了比較久,也參考了許多優秀的文章,但或許文章裏還是存在不好或不對的地方,請多多指教,可以評論裏直接提出來哈。
小 tip:後續內容更精彩哦。
CSS 的核心功能是將 CSS 屬性設定為特定的值。一個屬性與值的鍵值對被稱為聲明(declaration)。
1 | color: red; |
而如果將一個或者多個聲明用 {}
包裹起來後,那就組成了一個聲明塊(declaration block)。
1 | { |
聲明塊如果需要作用到對應的 HTML 元素,那還需要加上選擇器。選擇器和聲明塊組成了 CSS 規則集(CSS ruleset),常簡稱為 CSS 規則。
1 | span { |
規則集中最後一條聲明可以省略分號,但是並不建議這麼做,因為容易出錯。
CSS 中的註釋:
1 | /* 單行註釋 */ |
在 CSS 文件中,除了註釋、CSS 規則集以及 @規則 外,定義的一些別的東西都將被瀏覽器忽略。
CSS 規則是樣式表的主體,通常樣式表會包括大量的規則列表。但有時候也需要在樣式表中包括其他的一些信息,比如字符集,導入其它的外部樣式表,字體等,這些需要專門的語句表示。
而 @規則 就是這樣的語句。CSS 裏包含了以下 @規則:
@namespace 告訴 CSS 引擎必須考慮 XML 命名空間。
@media, 如果滿足媒體查詢的條件則條件規則組裏的規則生效。
@page, 描述打印文檔時佈局的變化.
@font-face, 描述將下載的外部的字體。
@keyframes, 描述 CSS 動畫的關鍵幀。
@document, 如果文檔樣式表滿足給定條件則條件規則組裏的規則生效。 (推延至 CSS Level 4 規範)
除了以上這幾個之外,下面還將對幾個比較生澀的 @規則 進行介紹。
@charset 用於定義樣式表使用的字符集。它必須是樣式表中的第一個元素。如果有多個 @charset
被聲明,只有第一個會被使用,而且不能在 HTML 元素或 HTML 頁面的 <style>
元素內使用。
注意:值必須是雙引號包裹,且和
1 | @charset "UTF-8"; |
平時寫樣式文件都沒寫 @charset 規則,那這個 CSS 文件到底是用的什麼字符編碼的呢?
某個樣式表文件到底用的是什麼字符編碼,瀏覽器有一套識別順序(優先級由高到低):
文件開頭的 Byte order mark 字符值,不過一般編輯器並不能看到文件頭裏的 BOM 值;
HTTP 響應頭裏的 content-type
字段包含的 charset
所指定的值,比如:
1 | Content-Type: text/css; charset=utf-8 |
CSS 文件頭裏定義的 @charset 規則裏指定的字符編碼;
<link>
標籤裏的 charset 屬性,該條已在 HTML5 中廢除;
默認是 UTF-8
。
@import 用於告訴 CSS 引擎引入一個外部樣式表。
link 和 @import 都能導入一個樣式文件,它們有什麼區別嘛?
link 是 HTML 標籤,除了能導入 CSS 外,還能導入別的資源,比如圖片、腳本和字體等;而 @import 是 CSS 的語法,只能用來導入 CSS;
link 導入的樣式會在頁面加載時同時加載,@import 導入的樣式需等頁面加載完成後再加載;
link 沒有兼容性問題,@import 不兼容 ie5 以下;
link 可以通過 JS 操作 DOM 動態引入樣式表改變樣式,而 @import 不可以。
@supports 用於查詢特定的 CSS 是否生效,可以結合 not、and 和 or 操作符進行後續的操作。
1 | /* 如果支持自定義屬性,則把 body 顏色設置為變量 varName 指定的顏色 */ |
層疊樣式表,這裏的層疊怎麼理解呢?其實它是 CSS 中的核心特性之一,用於合併來自多個源的屬性值的算法。比如説針對某個 HTML 標籤,有許多的 CSS 聲明都能作用到的時候,那最後誰應該起作用呢?層疊性説的大概就是這個。
針對不同源的樣式,將按照如下的順序進行層疊,越往下優先級越高:
用户代理樣式表中的聲明 (例如,瀏覽器的默認樣式,在沒有設置其他樣式時使用)。
用户樣式表中的常規聲明 (由用户設置的自定義樣式。由於 Chrome 在很早的時候就放棄了用户樣式表的功能,所以這裏將不再考慮它的排序。)。
作者樣式表中的常規聲明 (這些是我們 Web 開發人員設置的樣式)。
作者樣式表中的 !important 聲明。
用户樣式表中的 !important 聲明 S。
理解層疊性的時候需要結合 CSS 選擇器的優先級以及繼承性來理解。比如針對同一個選擇器,定義在後面的聲明會覆蓋前面的;作者定義的樣式會比默認繼承的樣式優先級更高。
CSS 選擇器無疑是其核心之一,對於基礎選擇器以及一些常用偽類必須掌握。下面列出了常用的選擇器。 想要獲取更多選擇器的用法可以看 MDN CSS Selectors。
標籤選擇器:h1
類選擇器:.checked
ID 選擇器:#picker
通配選擇器:*
屬性選擇器
[attr]
:指定屬性的元素;
[attr=val]
:屬性等於指定值的元素;
[attr*=val]
:屬性包含指定值的元素;
[attr^=val]
:屬性以指定值開頭的元素;
[attr$=val]
:屬性以指定值結尾的元素;
[attr~=val]
:屬性包含指定值 (完整單詞) 的元素(不推薦使用);
[attr|=val]
:屬性以指定值 (完整單詞) 開頭的元素(不推薦使用);
相鄰兄弟選擇器:A + B
普通兄弟選擇器:A ~ B
子選擇器:A > B
後代選擇器:A B
條件偽類
:lang()
:基於元素語言來匹配頁面元素;
:dir()
:匹配特定文字書寫方向的元素;
:has()
:匹配包含指定元素的元素;
:is()
:匹配指定選擇器列表裏的元素;
:not()
:用來匹配不符合一組選擇器的元素;
行為偽類
:active
:鼠標激活的元素;
:hover
: 鼠標懸浮的元素;
::selection
:鼠標選中的元素;
狀態偽類
:target
:當前錨點的元素;
:link
:未訪問的鏈接元素;
:visited
:已訪問的鏈接元素;
:focus
:輸入聚焦的表單元素;
:required
:輸入必填的表單元素;
:valid
:輸入合法的表單元素;
:invalid
:輸入非法的表單元素;
:in-range
:輸入範圍以內的表單元素;
:out-of-range
:輸入範圍以外的表單元素;
:checked
:選項選中的表單元素;
:optional
:選項可選的表單元素;
:enabled
:事件啟用的表單元素;
:disabled
:事件禁用的表單元素;
:read-only
:只讀的表單元素;
:read-write
:可讀可寫的表單元素;
:blank
:輸入為空的表單元素;
:current()
:瀏覽中的元素;
:past()
:已瀏覽的元素;
:future()
:未瀏覽的元素;
結構偽類
:root
:文檔的根元素;
:empty
:無子元素的元素;
:first-letter
:元素的首字母;
:first-line
:元素的首行;
:nth-child(n)
:元素中指定順序索引的元素;
:nth-last-child(n)
:元素中指定逆序索引的元素;;
:first-child
:元素中為首的元素;
:last-child
:元素中為尾的元素;
:only-child
:父元素僅有該元素的元素;
:nth-of-type(n)
:標籤中指定順序索引的標籤;
:nth-last-of-type(n)
:標籤中指定逆序索引的標籤;
:first-of-type
:標籤中為首的標籤;
:last-of-type
:標籤中為尾標籤;
:only-of-type
:父元素僅有該標籤的標籤;
::before
:在元素前插入內容;
::after
:在元素後插入內容;
優先級就是分配給指定的 CSS 聲明的一個權重,它由匹配的選擇器中的每一種選擇器類型的數值決定。為了記憶,可以把權重分成如下幾個等級,數值越大的權重越高:
10000:!important;
01000:內聯樣式;
00100:ID 選擇器;
00010:類選擇器、偽類選擇器、屬性選擇器;
00001:元素選擇器、偽元素選擇器;
00000:通配選擇器、後代選擇器、兄弟選擇器;
可以看到內聯樣式(通過元素中 style 屬性定義的樣式)的優先級大於任何選擇器;而給屬性值加上 !important
又可以把優先級提至最高,就是因為它的優先級最高,所以需要謹慎使用它,以下有些使用注意事項:
一定要優先考慮使用樣式規則的優先級來解決問題而不是 !important;
只有在需要覆蓋全站或外部 CSS 的特定頁面中使用 !important;
永遠不要在你的插件中使用 !important;
永遠不要在全站範圍的 CSS 代碼中使用 !important;
在 CSS 中有一個很重要的特性就是子元素會繼承父元素對應屬性計算後的值。比如頁面根元素 html 的文本顏色默認是黑色的,頁面中的所有其他元素都將繼承這個顏色,當申明瞭如下樣式後,H1 文本將變成橙色。
1 | body { |
設想一下,如果 CSS 中不存在繼承性,那麼我們就需要為不同文本的標籤都設置一下 color,這樣一來的後果就是 CSS 的文件大小就會無限增大。
CSS 屬性很多,但並不是所有的屬性默認都是能繼承父元素對應屬性的,那哪些屬性存在默認繼承的行為呢?一定是那些不會影響到頁面佈局的屬性,可以分為如下幾類:
字體相關:font-family
、font-style
、font-size
、font-weight
等;
文本相關:text-align
、text-indent
、text-decoration
、text-shadow
、letter-spacing
、word-spacing
、white-space
、line-height
、color
等;
列表相關:list-style
、list-style-image
、list-style-type
、list-style-position
等;
其他屬性:visibility
、cursor
等;
對於其他默認不繼承的屬性也可以通過以下幾個屬性值來控制繼承行為:
inherit
:繼承父元素對應屬性的計算值;
initial
:應用該屬性的默認值,比如 color 的默認值是 #000
;
unset
:如果屬性是默認可以繼承的,則取 inherit
的效果,否則同 initial
;
revert
:效果等同於 unset
,兼容性差。
在 CSS 的世界中,會把內容按照從左到右、從上到下的順序進行排列顯示。正常情況下會把頁面分割成一行一行的顯示,而每行又可能由多列組成,所以從視覺上看起來就是從上到下從左到右,而這就是 CSS 中的流式佈局,又叫文檔流。文檔流就像水一樣,能夠自適應所在的容器,一般它有如下幾個特性:
塊級元素默認會佔滿整行,所以多個塊級盒子之間是從上到下排列的;
內聯元素默認會在一行裏一列一列的排布,當一行放不下的時候,會自動切換到下一行繼續按照列排布;
如何脱離文檔流呢?
脱流文檔流指節點脱流正常文檔流後,在正常文檔流中的其他節點將忽略該節點並填補其原先空間。文檔一旦脱流,計算其父節點高度時不會將其高度納入,脱流節點不佔據空間。有兩種方式可以讓元素脱離文檔流:浮動和定位。
使用浮動(float)會將元素脱離文檔流,移動到容器左 / 右側邊界或者是另一個浮動元素旁邊,該浮動元素之前佔用的空間將被別的元素填補,另外浮動之後所佔用的區域不會和別的元素之間發生重疊;
使用絕對定位(position: absolute;
)或者固定定位(position: fixed;
)也會使得元素脱離文檔流,且空出來的位置將自動被後續節點填補。
在 CSS 中任何元素都可以看成是一個盒子,而一個盒子是由 4 部分組成的:內容(content)、內邊距(padding)、邊框(border)和外邊距(margin)。
盒模型有 2 種:標準盒模型和 IE 盒模型,本別是由 W3C 和 IExplore 制定的標準。
如果給某個元素設置如下樣式:
1 | .box { |
標準盒模型認為:盒子的實際尺寸 = 內容(設置的寬 / 高) + 內邊距 + 邊框
所以 .box
元素內容的寬度就為 200px
,而實際的寬度則是 width
+ padding-left
+ padding-right
+ border-left-width
+ border-right-width
= 200 + 10 + 10 + 1 + 1 = 222。
IE 盒模型認為:盒子的實際尺寸 = 設置的寬 / 高 = 內容 + 內邊距 + 邊框
.box
元素所佔用的實際寬度為 200px
,而內容的真實寬度則是 width
- padding-left
- padding-right
- border-left-width
- border-right-width
= 200 - 10 - 10 - 1 - 1 = 178。
現在高版本的瀏覽器基本上默認都是使用標準盒模型,而像 IE6 這種老古董才是默認使用 IE 盒模型的。
在 CSS3 中新增了一個屬性 box-sizing
,允許開發者來指定盒子使用什麼標準,它有 2 個值:
content-box
:標準盒模型;
border-box
:IE 盒模型;
視覺格式化模型(Visual formatting model)是用來處理和在視覺媒體上顯示文檔時使用的計算規則。CSS 中一切皆盒子,而視覺格式化模型簡單來理解就是規定這些盒子應該怎麼樣放置到頁面中去,這個模型在計算的時候會依賴到很多的因素,比如:盒子尺寸、盒子類型、定位方案(是浮動還是定位)、兄弟元素或者子元素以及一些別的因素。
從上圖中可以看到視覺格式化模型涉及到的內容很多,有興趣深入研究的可以結合上圖看這個 W3C 的文檔 Visual formatting model。所以這裏就簡單介紹下盒子類型。
盒子類型由 display 決定,同時給一個元素設置 display 後,將會決定這個盒子的 2 個顯示類型(display type):
outer display type(對外顯示):決定了該元素本身是如何佈局的,即參與何種格式化上下文;
inner display type(對內顯示):其實就相當於把該元素當成了容器,規定了其內部子元素是如何佈局的,參與何種格式化上下文;
對外顯示方面,盒子類型可以分成 2 類:block-level box(塊級盒子) 和 inline-level box(行內級盒子)。
依據上圖可以列出都有哪些塊級和行內級盒子:
塊級盒子:display 為 block、list-item、table、flex、grid、flow-root 等;
行內級盒子:display 為 inline、inline-block、inline-table 等;
所有塊級盒子都會參與 BFC,呈現垂直排列;而所有行內級盒子都參會 IFC,呈現水平排列。
除此之外,block、inline 和 inline-block 還有什麼更具體的區別呢?
block
佔滿一行,默認繼承父元素的寬度;多個塊元素將從上到下進行排列;
設置 width/height 將會生效;
設置 padding 和 margin 將會生效;
inline
不會佔滿一行,寬度隨着內容而變化;多個 inline 元素將按照從左到右的順序在一行裏排列顯示,如果一行顯示不下,則自動換行;
設置 width/height 將不會生效;
設置豎直方向上的 padding 和 margin 將不會生效;
inline-block
是行內塊元素,不單獨佔滿一行,可以看成是能夠在一行裏進行左右排列的塊元素;
設置 width/height 將會生效;
設置 padding 和 margin 將會生效;
對內方面,其實就是把元素當成了容器,裏面包裹着文本或者其他子元素。container box 的類型依據 display 的值不同,分為 4 種:
block container:建立 BFC 或者 IFC;
flex container:建立 FFC;
grid container:建立 GFC;
ruby container:接觸不多,不做介紹。
值得一提的是如果把 img 這種替換元素(replaced element)申明為 block 是不會產生 container box 的,因為替換元素比如 img 設計的初衷就僅僅是通過 src 把內容替換成圖片,完全沒考慮過會把它當成容器。
參考:
格式化上下文(Formatting Context)是 CSS2.1 規範中的一個概念,大概説的是頁面中的一塊渲染區域,規定了渲染區域內部的子元素是如何排版以及相互作用的。
不同類型的盒子有不同格式化上下文,大概有這 4 類:
BFC (Block Formatting Context) 塊級格式化上下文;
IFC (Inline Formatting Context) 行內格式化上下文;
FFC (Flex Formatting Context) 彈性格式化上下文;
GFC (Grid Formatting Context) 格柵格式化上下文;
其中 BFC 和 IFC 在 CSS 中扮演着非常重要的角色,因為它們直接影響了網頁佈局,所以需要深入理解其原理。
塊格式化上下文,它是一個獨立的渲染區域,只有塊級盒子參與,它規定了內部的塊級盒子如何佈局,並且與這個區域外部毫不相干。
BFC 渲染規則
內部的盒子會在垂直方向,一個接一個地放置;
盒子垂直方向的距離由 margin 決定,屬於同一個 BFC 的兩個相鄰盒子的 margin 會發生重疊;
每個元素的 margin 的左邊,與包含塊 border 的左邊相接觸 (對於從左往右的格式化,否則相反),即使存在浮動也是如此;
BFC 的區域不會與 float 盒子重疊;
BFC 就是頁面上的一個隔離的獨立容器,容器裏面的子元素不會影響到外面的元素。反之也如此。
計算 BFC 的高度時,浮動元素也參與計算。
如何創建 BFC?
根元素:html
非溢出的可見元素:overflow 不為 visible
設置浮動:float 屬性不為 none
設置定位:position 為 absolute 或 fixed
定義成塊級的非塊級元素:display: inline-block/table-cell/table-caption/flex/inline-flex/grid/inline-grid
BFC 應用場景
1、 自適應兩欄佈局
應用原理:BFC 的區域不會和浮動區域重疊,所以就可以把側邊欄固定寬度且左浮動,而對右側內容觸發 BFC,使得它的寬度自適應該行剩餘寬度。
1 | <div class="layout"> |
1 | .aside { |
2、清除內部浮動
浮動造成的問題就是父元素高度坍塌,所以清除浮動需要解決的問題就是讓父元素的高度恢復正常。而用 BFC 清除浮動的原理就是:計算 BFC 的高度時,浮動元素也參與計算。只要觸發父元素的 BFC 即可。
1 | .parent { |
3、 防止垂直 margin 合併
BFC 渲染原理之一:同一個 BFC 下的垂直 margin 會發生合併。所以如果讓 2 個元素不在同一個 BFC 中即可阻止垂直 margin 合併。那如何讓 2 個相鄰的兄弟元素不在同一個 BFC 中呢?可以給其中一個元素外面包裹一層,然後觸發其包裹層的 BFC,這樣一來 2 個元素就不會在同一個 BFC 中了。
1 | <div class="layout"> |
1 | .demo3 .a, |
針對以上 3 個 示例 ,可以結合這個 BFC 應用示例 配合觀看更佳。
參考:CSS 原理 - Formatting Context
IFC 的形成條件非常簡單,塊級元素中僅包含內聯級別元素,需要注意的是當 IFC 中有塊級元素插入時,會產生兩個匿名塊將父元素分割開來,產生兩個 IFC。
IFC 渲染規則
子元素在水平方向上一個接一個排列,在垂直方向上將以容器頂部開始向下排列;
節點無法聲明寬高,其中 margin 和 padding 在水平方向有效在垂直方向無效;
節點在垂直方向上以不同形式對齊;
能把在一行上的框都完全包含進去的一個矩形區域,被稱為該行的線盒(line box)。線盒的寬度是由包含塊(containing box)和與其中的浮動來決定;
IFC 中的 line box 一般左右邊貼緊其包含塊,但 float 元素會優先排列。
IFC 中的 line box 高度由 line-height 計算規則來確定,同個 IFC 下的多個 line box 高度可能會不同;
當內聯級盒子的總寬度少於包含它們的 line box 時,其水平渲染規則由 text-align 屬性值來決定;
當一個內聯盒子超過父元素的寬度時,它會被分割成多盒子,這些盒子分佈在多個 line box 中。如果子元素未設置強制換行的情況下,inline box 將不可被分割,將會溢出父元素。
針對如上的 IFC 渲染規則,你是不是可以分析下下面這段代碼的 IFC 環境是怎麼樣的呢?
1 | <p>It can get <strong>very complicated</storng> once you start looking into it.</p> |
對應上面這樣一串 HTML 分析如下:
p 標籤是一個 block container,對內將產生一個 IFC;
由於一行沒辦法顯示完全,所以產生了 2 個線盒(line box);線盒的寬度就繼承了 p 的寬度;高度是由裏面的內聯盒子的 line-height 決定;
It can get:匿名的內聯盒子;
very complicated:strong 標籤產生的內聯盒子;
once you start:匿名的內聯盒子;
looking into it.:匿名的內聯盒子。
IFC 應用場景
水平居中:當一個塊要在環境中水平居中時,設置其為 inline-block 則會在外層產生 IFC,通過 text-align 則可以使其水平居中。
垂直居中:創建一個 IFC,用其中一個元素撐開父元素的高度,然後設置其 vertical-align: middle,其他行內元素則可以在此父元素下垂直居中。
偷個懶,demo 和圖我就不做了。
在電腦顯示屏幕上的顯示的頁面其實是一個三維的空間,水平方向是 X 軸,豎直方向是 Y 軸,而屏幕到眼睛的方向可以看成是 Z 軸。眾 HTML 元素依據自己定義的屬性的優先級在 Z 軸上按照一定的順序排開,而這其實就是層疊上下文所要描述的東西。
我們對層疊上下文的第一印象可能要來源於 z-index,認為它的值越大,距離屏幕觀察者就越近,那麼層疊等級就越高,事實確實是這樣的,但層疊上下文的內容遠非僅僅如此:
z-index 能夠在層疊上下文中對元素的堆疊順序其作用是必須配合定位才可以;
除了 z-index 之外,一個元素在 Z 軸上的顯示順序還受層疊等級和層疊順序影響;
在看層疊等級和層疊順序之前,我們先來看下如何產生一個層疊上下文,特定的 HTML 元素或者 CSS 屬性產生層疊上下文,MDN 中給出了這麼一個列表,符合以下任一條件的元素都會產生層疊上下文:
html 文檔根元素
聲明 position: absolute/relative 且 z-index 值不為 auto 的元素;
聲明 position: fixed/sticky 的元素;
flex 容器的子元素,且 z-index 值不為 auto;
grid 容器的子元素,且 z-index 值不為 auto;
opacity 屬性值小於 1 的元素;
mix-blend-mode 屬性值不為 normal 的元素;
以下任意屬性值不為 none 的元素:
transform
filter
perspective
clip-path
mask / mask-image / mask-border
isolation 屬性值為 isolate 的元素;
-webkit-overflow-scrolling 屬性值為 touch 的元素;
will-change 值設定了任一屬性而該屬性在 non-initial 值時會創建層疊上下文的元素;
contain 屬性值為 layout、paint 或包含它們其中之一的合成值(比如 contain: strict、contain: content)的元素。
層疊等級
層疊等級指節點在三維空間 Z 軸上的上下順序。它分兩種情況:
在同一個層疊上下文中,它描述定義的是該層疊上下文中的層疊上下文元素在 Z 軸上的上下順序;
在其他普通元素中,它描述定義的是這些普通元素在 Z 軸上的上下順序;
普通節點的層疊等級優先由其所在的層疊上下文決定,層疊等級的比較只有在當前層疊上下文中才有意義,脱離當前層疊上下文的比較就變得無意義了。
層疊順序
在同一個層疊上下文中如果有多個元素,那麼他們之間的層疊順序是怎麼樣的呢?
以下這個列表越往下層疊優先級越高,視覺上的效果就是越容易被用户看到(不會被其他元素覆蓋):
層疊上下文的 border 和 background
z-index < 0 的子節點
標準流內塊級非定位的子節點
浮動非定位的子節點
標準流內行內非定位的子節點
z-index: auto/0 的子節點
z-index > 0 的子節點
如何比較兩個元素的層疊等級?
在同一個層疊上下文中,比較兩個元素就是按照上圖的介紹的層疊順序進行比較。
如果不在同一個層疊上下文中的時候,那就需要比較兩個元素分別所處的層疊上下文的等級。
如果兩個元素都在同一個層疊上下文,且層疊順序相同,則在 HTML 中定義越後面的層疊等級越高。
參考:徹底搞懂 CSS 層疊上下文、層疊等級、層疊順序、z-index
CSS 的聲明是由屬性和值組成的,而值的類型有許多種:
數值:長度值 ,用於指定例如元素 width、border-width、font-size 等屬性的值;
百分比:可以用於指定尺寸或長度,例如取決於父容器的 width、height 或默認的 font-size;
顏色:用於指定 background-color、color 等;
座標位置:以屏幕的左上角為座標原點定位元素的位置,比如常見的 background-position、top、right、bottom 和 left 等屬性;
函數:用於指定資源路徑或背景圖片的漸變,比如 url()、linear-gradient() 等;
而還有些值是需要帶單位的,比如 width: 100px,這裏的 px 就是表示長度的單位,長度單位除了 px 外,比較常用的還有 em、rem、vw/vh 等。那他們有什麼區別呢?又應該在什麼時候使用它們呢?
屏幕分辨率是指在屏幕的橫縱方向上的像素點數量,比如分辨率 1920×1080 意味着水平方向含有 1920 個像素數,垂直方向含有 1080 個像素數。
而 px 表示的是 CSS 中的像素,在 CSS 中它是絕對的長度單位,也是最基礎的單位,其他長度單位會自動被瀏覽器換算成 px。但是對於設備而言,它其實又是相對的長度單位,比如寬高都為 2px,在正常的屏幕下,其實就是 4 個像素點,而在設備像素比 (devicePixelRatio) 為 2 的 Retina 屏幕下,它就有 16 個像素點。所以屏幕尺寸一致的情況下,屏幕分辨率越高,顯示效果就越細膩。
講到這裏,還有一些相關的概念需要理清下:
設備像素(Device pixels)
設備屏幕的物理像素,表示的是屏幕的橫縱有多少像素點;和屏幕分辨率是差不多的意思。
設備像素比(DPR)
設備像素比表示 1 個 CSS 像素等於幾個物理像素。
計算公式:DPR = 物理像素數 / 邏輯像素數;
在瀏覽器中可以通過 window.devicePixelRatio 來獲取當前屏幕的 DPR。
像素密度(DPI/PPI)
像素密度也叫顯示密度或者屏幕密度,縮寫為 DPI(Dots Per Inch) 或者 PPI(Pixel Per Inch)。從技術角度説,PPI 只存在於計算機顯示領域,而 DPI 只出現於打印或印刷領域。
計算公式:像素密度 = 屏幕對角線的像素尺寸 / 物理尺寸
比如,對於分辨率為 750 * 1334 的 iPhone 6 來説,它的像素密度為:
1 | Math.sqrt(750 * 750 + 1334 * 1334) / 4.7 = 326ppi |
設備獨立像素(DIP)
DIP 是特別針對 Android 設備而衍生出來的,原因是安卓屏幕的尺寸繁多,因此為了顯示能儘量和設備無關,而提出的這個概念。它是基於屏幕密度而計算的,認為當屏幕密度是 160 的時候,px = DIP。
計算公式:dip = px * 160 / dpi
em 是 CSS 中的相對長度單位中的一個。居然是相對的,那它到底是相對的誰呢?它有 2 層意思:
在 font-size 中使用是相對於父元素的 font-size 大小,比如父元素 font-size: 16px,當給子元素指定 font-size: 2em 的時候,經過計算後它的字體大小會是 32px;
在其他屬性中使用是相對於自身的字體大小,如 width/height/padding/margin 等;
我們都知道每個瀏覽器都會給 HTML 根元素 html 設置一個默認的 font-size,而這個值通常是 16px。這也就是為什麼 1em = 16px 的原因所在了。
em 在計算的時候是會層層計算的,比如:
1 | <div> |
1 | div { font-size: 2em; } |
對於如上一個結構的 HTML,由於根元素 html 的字體大小是 16px,所以 p 標籤最終計算出來後的字體大小會是 16 * 2 * 2 = 64px
rem(root em) 和 em 一樣,也是一個相對長度單位,不過 rem 相對的是 HTML 的根元素 html。
rem 由於是基於 html 的 font-size 來計算,所以通常用於自適應網站或者 H5 中。
比如在做 H5 的時候,前端通常會讓 UI 給 750px 寬的設計圖,而在開發的時候可以基於 iPhone X 的尺寸 375px * 812px 來寫頁面,這樣一來的話,就可以用下面的 JS 依據當前頁面的視口寬度自動計算出根元素 html 的基準 font-size 是多少。
1 | (function (doc, win) { |
比如當視口是 375px 的時候,經過計算 html 的 font-size 會是 100px,這樣有什麼好處呢?好處就是方便寫樣式,比如從設計圖量出來的 header 高度是 50px 的,那我們寫樣式的時候就可以直接寫:
1 | header { |
每個從設計圖量出來的尺寸只要除於 100 即可得到當前元素的 rem 值,都不用經過計算,非常方便。偷偷告訴你,如果你把上面那串計算 html 標籤 font-size 的 JS 代碼中的 200 替換成 2,那在計算 rem 的時候就不需要除於 100 了,從設計圖量出多大 px,就直接寫多少個 rem。
vw 和 vh 分別是相對於屏幕視口寬度和高度而言的長度單位:
1vw = 視口寬度均分成 100 份中 1 份的長度;
1vh = 視口高度均分成 100 份中 1 份的長度;
在 JS 中 100vw = window.innerWidth,100vh = window.innerHeight。
vw/vh 的出現使得多了一種寫自適應佈局的方案,開發者不再侷限於 rem 了。
相對視口的單位,除了 vw/vh 外,還有 vmin 和 vmax:
vmin:取 vw 和 vh 中值較小的;
vmax:取 vw 和 vh 中值較大的;
CSS 中用於表示顏色的值種類繁多,足夠構成一個體系,所以這裏就專門拿出一個小節來講解它。
根據 CSS 顏色草案 中提到的顏色值類型,大概可以把它們分為這幾類:
顏色關鍵字
transparent 關鍵字
currentColor 關鍵字
RGB 顏色
HSL 顏色
顏色關鍵字(color keywords)是不區分大小寫的標識符,它表示一個具體的顏色,比如 white(白),黑(black)等;
可接受的關鍵字列表在 CSS 的演變過程中發生了改變:
CSS 標準 1 只接受 16 個基本顏色,稱為 VGA 顏色,因為它們來源於 VGA 顯卡所顯示的顏色集合而被稱為 VGA colors (視頻圖形陣列色彩)。
CSS 標準 2 增加了 orange 關鍵字。
從一開始,瀏覽器接受其它的顏色,由於一些早期瀏覽器是 X11 應用程序,這些顏色大多數是 X11 命名的顏色列表,雖然有一點不同。SVG 1.0 是首個正式定義這些關鍵字的標準;CSS 色彩標準 3 也正式定義了這些關鍵字。它們經常被稱作擴展的顏色關鍵字, X11 顏色或 SVG 顏色 。
CSS 顏色標準 4 添加可 rebeccapurple 關鍵字來紀念 web 先鋒 Eric Meyer。
如下這張圖是 16 個基礎色,又叫 VGA 顏色。截止到目前為止 CSS 顏色關鍵字總共有 146 個,這裏可以查看 完整的色彩關鍵字列表。
需要注意的是如果聲明的時候的顏色關鍵字是錯誤的,瀏覽器會忽略它。
transparent 關鍵字表示一個完全透明的顏色,即該顏色看上去將是背景色。從技術上説,它是帶有 alpha 通道為最小值的黑色,是 rgba(0,0,0,0) 的簡寫。
透明關鍵字有什麼應用場景呢?
實現三角形
下面這個圖是用 4 條邊框填充的正方形,看懂了它你大概就知道該如何用 CSS 寫三角形了。
1 | div { |
用 transparent 實現三角形的原理:
首先寬高必須是 0px,通過邊框的粗細來填充內容;
那條邊需要就要加上顏色,而不需要的邊則用 transparent;
想要什麼樣姿勢的三角形,完全由上下左右 4 條邊的中有顏色的邊和透明的邊的位置決定;
等腰三角形:設置一條邊有顏色,然後緊挨着的 2 邊是透明,且寬度是有顏色邊的一半;直角三角形:設置一條邊有顏色,然後緊挨着的任何一邊透明即可。
看下示例:
增大點擊區域
常常在移動端的時候點擊的按鈕的區域特別小,但是由於現實效果又不太好把它做大,所以常用的一個手段就是通過透明的邊框來增大按鈕的點擊區域:
1 | .btn { |
currentColor 會取當前元素繼承父級元素的文本顏色值或聲明的文本顏色值,即 computed 後的 color 值。
比如,對於如下 CSS,該元素的邊框顏色會是 red:
1 | .btn { |
RGB[A] 顏色是由 R(red)-G(green)-B(blue)-A(alpha) 組成的色彩空間。
在 CSS 中,它有兩種表示形式:
十六進制符號;
函數符;
十六進制符號
RGB 中的每種顏色的值範圍是 00~ff,值越大表示顏色越深。所以一個顏色正常是 6 個十六進制字符加上 # 組成,比如紅色就是 #ff0000。
如果 RGB 顏色需要加上不透明度,那就需要加上 alpha 通道的值,它的範圍也是 00~ff,比如一個帶不透明度為 67% 的紅色可以這樣寫 #ff0000aa。
使用十六進制符號表示顏色的時候,都是用 2 個十六進制表示一個顏色,如果這 2 個字符相同,還可以縮減成只寫 1 個,比如,紅色 #f00;帶 67% 不透明度的紅色 #f00a。
函數符
當 RGB 用函數表示的時候,每個值的範圍是 0255 或者 0%100%,所以紅色是 rgb(255, 0, 0), 或者 rgb(100%, 0, 0)。
如果需要使用函數來表示帶不透明度的顏色值,值的範圍是 01 及其之間的小數或者 0%100%,比如帶 67% 不透明度的紅色是 rgba(255, 0, 0, 0.67) 或者 rgba(100%, 0%, 0%, 67%)
需要注意的是 RGB 這 3 個顏色值需要保持一致的寫法,要嘛用數字要嘛用百分比,而不透明度的值的可以不用和 RGB 保持一致寫法。比如 rgb(100%, 0, 0) 這個寫法是無效的;而 rgb(100%, 0%, 0%, 0.67) 是有效的。
在第 4 代 CSS 顏色標準中,新增了一種新的函數寫法,即可以把 RGB 中值的分隔逗號改成空格,而把 RGB 和 alpha 中的逗號改成 /,比如帶 67% 不透明度的紅色可以這樣寫 rgba(255 0 0 / 0.67)。另外還把 rgba 的寫法合併到 rgb 函數中了,即 rgb 可以直接寫帶不透明度的顏色。
HSL[A] 顏色是由色相 (hue)- 飽和度 (saturation)- 亮度 (lightness)- 不透明度組成的顏色體系。
色相(H)是色彩的基本屬性,值範圍是 0360 或者 0deg360deg, 0 (或 360) 為紅色, 120 為綠色, 240 為藍色;
飽和度(S)是指色彩的純度,越高色彩越純,低則逐漸變灰,取 0~100% 的數值;0% 為灰色, 100% 全色;
亮度(L),取 0~100%,0% 為暗,100% 為白;
不透明度(A),取 0100%,或者 01 及之間的小數;
寫法上可以參考 RGB 的寫法,只是參數的值不一樣。
給一個按鈕設置不透明度為 67% 的紅色的 color 的寫法,以下全部寫法效果一致:
1 | button { |
小提示:在 Chrome DevTools 中可以按住 shift + 鼠標左鍵可以切換顏色的表示方式。
媒體查詢是指針對不同的設備、特定的設備特徵或者參數進行定製化的修改網站的樣式。
你可以通過給 <link>
加上 media 屬性來指定該樣式文件只能對什麼設備生效,不指定的話默認是 all,即對所有設備都生效:
1 | <link rel="stylesheet" src="styles.css" media="screen" /> |
都支持哪些設備類型?
all:適用於所有設備;
print:適用於在打印預覽模式下在屏幕上查看的分頁材料和文檔;
screen:主要用於屏幕;
speech:主要用於語音合成器。
需要注意的是:通過 media 指定的 資源儘管不匹配它的設備類型,但是瀏覽器依然會加載它。
除了通過 <link>
讓指定設備生效外,還可以通過 @media
讓 CSS 規則在特定的條件下才能生效。響應式頁面就是使用了 @media 才讓一個頁面能夠同時適配 PC、Pad 和手機端。
1 | @media (min-width: 1000px) {} |
媒體查詢支持邏輯操作符:
and:查詢條件都滿足的時候才生效;
not:查詢條件取反;
only:整個查詢匹配的時候才生效,常用語兼容舊瀏覽器,使用時候必須指定媒體類型;
逗號或者 or:查詢條件滿足一項即可匹配;
媒體查詢還支持眾多的媒體特性,使得它可以寫出很複雜的查詢條件:
1 | /* 用户設備的最小高度為680px或為縱向模式的屏幕設備 */ |
之前我們通常是在預處理器裏才可以使用變量,而現在 CSS 裏也支持了變量的用法。通過自定義屬性就可以在想要使用的地方引用它。
自定義屬性也和普通屬性一樣具有級聯性,申明在 :root 下的時候,在全文檔範圍內可用,而如果是在某個元素下申明自定義屬性,則只能在它及它的子元素下才可以使用。
自定義屬性必須通過 --x
的格式申明,比如:–theme-color: red; 使用自定義屬性的時候,需要用 var 函數。比如:
1 | <!-- 定義自定義屬性 --> |
上圖這個是使用 CSS 自定義屬性配合 JS 實現的動態調整元素的 box-shadow,具體可以看這個 codepen demo。
Retina 顯示屏比普通的屏幕有着更高的分辨率,所以在移動端的 1px 邊框就會看起來比較粗,為了美觀通常需要把這個線條細化處理。這裏有篇文章列舉了 7 中方案可以參考一下:7 種方法解決移動端 Retina 屏幕 1px 邊框問題
而這裏附上最後一種通過偽類和 transform 實現的相對完美的解決方案:
只設置單條底部邊框:
1 | .scale-1px-bottom { |
同時設置 4 條邊框:
1 | .scale-1px { |
什麼是浮動:浮動元素會脱離文檔流並向左 / 向右浮動,直到碰到父元素或者另一個浮動元素。
為什麼要清楚浮動,它造成了什麼問題?
因為浮動元素會脱離正常的文檔流,並不會佔據文檔流的位置,所以如果一個父元素下面都是浮動元素,那麼這個父元素就無法被浮動元素所撐開,這樣一來父元素就丟失了高度,這就是所謂的浮動造成的父元素高度坍塌問題。
父元素高度一旦坍塌將對後面的元素佈局造成影響,為了解決這個問題,所以需要清除浮動,讓父元素恢復高度,那該如何做呢?
這裏介紹兩種方法:通過 BFC 來清除、通過 clear 來清除。
前面介紹 BFC 的時候提到過,計算 BFC 高度的時候浮動子元素的高度也將計算在內,利用這條規則就可以清楚浮動。
假設一個父元素 parent 內部只有 2 個子元素 child,且它們都是左浮動的,這個時候 parent 如果沒有設置高度的話,因為浮動造成了高度坍塌,所以 parent 的高度會是 0,此時只要給 parent 創造一個 BFC,那它的高度就能恢復了。
而產生 BFC 的方式很多,我們可以給父元素設置 overflow: auto 來簡單的實現 BFC 清除浮動,但是為了兼容 IE 最好用 overflow: hidden。
1 | .parent { |
通過 overflow: hidden 來清除浮動並不完美,當元素有陰影或存在下拉菜單的時候會被截斷,所以該方法使用比較侷限。
我先把結論貼出來:
1 | .clearfix { |
這種寫法的核心原理就是通過 ::after 偽元素為在父元素的最後一個子元素後面生成一個內容為空的塊級元素,然後通過 clear 將這個偽元素移動到所有它之前的浮動元素的後面,畫個圖來理解一下。
可以結合這個 codepen demo 一起理解上圖的 clear 清楚浮動原理。
上面這個 demo 或者圖裏為了展示需要所以給偽元素的內容設置為了 ::after,實際使用的時候需要設置為空字符串,讓它的高度為 0,從而父元素的高度都是由實際的子元素撐開。
該方式基本上是現在人人都在用的清除浮動的方案,非常通用。
針對同一個類型的 HTML 標籤,不同的瀏覽器往往有不同的表現,所以在網站製作的時候,開發者通常都是需要將這些瀏覽器的默認樣式清除,讓網頁在不同的瀏覽器上能夠保持一致。
針對清除瀏覽器默認樣式這件事,在很早之前 CSS 大師 Eric A. Meyer 就幹過。它就是寫一堆通用的樣式用來重置瀏覽器默認樣式,這些樣式通常會放到一個命名為 reset.css 文件中。比如大師的 reset.css 是這麼寫的:
1 | html, body, div, span, applet, object, iframe, |
他的這份 reset.css 據説是被使用最廣泛的重設樣式的方案了。
除了 reset.css 外,後來又出現了 Normalize.css 。關於 Normalize.css, 其作者 necolas 專門寫了一篇文章介紹了它,並談到了它和 reset.css 的區別。這個是他寫那篇文章的翻譯版:讓我們談一談 Normalize.css。
文章介紹到:Normalize.css 只是一個很小的 CSS 文件,但它在默認的 HTML 元素樣式上提供了跨瀏覽器的高度一致性。相比於傳統的 CSS reset,Normalize.css 是一種現代的、為 HTML5 準備的優質替代方案,現在已經有很多知名的框架和網站在使用它了。
Normalize.css 的具體樣式可以看這裏 Normalize.css
區別於 reset.css,Normalize.css 有如下特點:
reset.css 幾乎為所有標籤都設置了默認樣式,而 Normalize.css 則是有選擇性的保護了部分有價值的默認值;
修復了很多瀏覽器的 bug,而這是 reset.css 沒做到的;
不會讓你的調試工具變的雜亂,相反 reset.css 由於設置了很多默認值,所以在瀏覽器調試工具中往往會看到一大堆的繼承樣式,顯得很雜亂;
Normalize.css 是模塊化的,所以可以選擇性的去掉永遠不會用到的部分,比如表單的一般化;
Normalize.css 有詳細的説明文檔;
默認:字符太長溢出了容器
字符超出部分換行
字符超出位置使用連字符
單行文本超出省略
多行文本超出省略
查看以上這些方案的示例: codepen demo
有意思的是剛好前兩天看到 chokcoco 針對文本溢出也寫了一篇文章,主要突出的是對整塊的文本溢出處理。啥叫整塊文本?比如,下面這種技術標籤就是屬於整塊文本:
另外他還對 iOS/Safari 做了兼容處理,感興趣的可以去閲讀下:CSS 整塊文本溢出省略特性探究。
讓元素在父元素中呈現出水平垂直居中的形態,無非就 2 種情況:
單行的文本、inline 或者 inline-block 元素;
固定寬高的塊級盒子;
不固定寬高的塊級盒子;
以下列到的所有水平垂直居中方案這裏寫了個 codepen demo,配合示例閲讀效果更佳。
水平居中
此類元素需要水平居中,則父級元素必須是塊級元素 (block level
),且父級元素上需要這樣設置樣式:
1 | .parent { |
垂直居中
方法一:通過設置上下內間距一致達到垂直居中的效果:
1 | .single-line { |
方法二:通過設置 height
和 line-height
一致達到垂直居中:
1 | .single-line { |
方法一:absolute + 負 margin
方法二:absolute + margin auto
方法三:absolute + calc
這裏列了 6 種方法,參考了顏海鏡 寫的文章 ,其中的兩種 line-height 和 writing-mode 方案看後讓我驚呼:還有這種操作?學到了學到了。
方法一:absolute + transform
方法二:line-height + vertical-align
方法三:writing-mode
方法四:table-cell
方法五:flex
方法六:grid
針對以下這些方案寫了幾個示例: codepen demo
方法一:float + overflow(BFC 原理)
方法二:float + margin
方法三:flex
方法四:grid
針對以下這些方案寫了幾個示例: codepen demo
方法一:聖盃佈局
方法二:雙飛翼佈局
方法三:float + overflow(BFC 原理)
方法四:flex
方法五:grid
結合示例閲讀更佳:codepen demo
方法一:padding + 負 margin
方法二:設置父級背景圖片
列了 4 種方法,都是基於如下的 HTML 和 CSS 的,結合示例閲讀效果更佳:codepen demo
1 | <div class="layout"> |
1 | html, |
方法一:calc
方法二:absolute
方法三:flex
方法四:grid
這是我斷斷續續寫了 2 周完成的文章,算是自己對 CSS 的一個總結,雖然寫得很長,但不足以覆蓋所有 CSS 的知識,比如動畫和一些 CSS3 的新特性就完全沒涉及,因為這要寫下來估計得有大幾萬字(其實就是懶 😝 )。
碼字作圖不易,如果喜歡或者對你有絲毫幫助的話,幫忙點個👍 哈,點贊就是我的動力。同時也希望自己能堅持認真的寫下去,因為在總結提升自己的同時如果也能幫助更多的前端 er,那將會讓我感覺很開心。
]]>Date 類型使用自 UTC 世界協調時間 1970 年 1 月 1 日午夜零時開始經過的毫秒數
來保存日期
通過使用 new 操作符和 Date 構造函數來創建日期對象
var now = new Date()
Date()
可以選擇傳入參數,如果不傳入參數
不傳入參數
會依據系統設置的當前時間來創建一個Date對象,返回當前的日期和時間
傳入參數 Unix 时间戳
即 从1970-1-1 00:00:00 UTC 到该日期對象(UTC時間)的毫秒数
傳入 时间戳字符串
與下文的 Date.parse()
所需的參數一樣
傳入 日期成員
與 下文的 Date.UTC()
所需的參數一樣,但是 Date() 會以 本地時間 來處理參數,而不是 UTC。
1 | // 可見最終的結果 兩個不一樣, 原因是 Date() 會以本地時間來處理參數 |
以一個函數的形式來調用 Date 對象(即不使用 new 操作符)會返回一個代表當前日期和時間的字符串
1 | var d1 = new Date() |
JavaScript 提供了兩種方法可以快速獲取毫秒數
Date.parse()
該方法接受一個表示日期的字符串參數(符合RFC 2822 和 ISO 8601 的日期格式,其他格式也支持,但是結果可能會與預期不同),然後根據字符串返回相應的日期的毫秒數(从1970-1-1 00:00:00 UTC 到该日期對象(UTC時間)的毫秒数)。
如果傳入的參數無法識別,則會返回 NaN
如果參數沒有指定時區,默認使用本地時區。
在 ISO 8601 格式中
- ‘2011-02-22’ 這種僅日期格式的,將會使用 UTC 時區來處理解析這個參數
- 如果是日期加時間的,則使用本地時間處理
這個方法可能在不同瀏覽器和地區,有不一樣的結果
1 | console.log(Date.parse('2012-02-02')) // 1328140800000 (utc 時間 2012-02-02 00:00:00) 以 UTC 時間處理 |
Date.UTC()
跟Date.parse()
一樣都是返回相應的日期的毫秒數(从1970-1-1 00:00:00 UTC 到该日期對象(UTC時間)的毫秒数),區別在於傳入的參數不同,其可以傳入很多參數。其傳入的參數會以 UTC 時區
處理。
Date.UTC(year,month[,date[,hrs[,min[,sec[,ms]]]]])
【必須】year 年份
可以是完整格式的年份,如 2022。
如果是 0 到 99 之間,代表著 1900 年後的日期(傳入 8,會被渲染為 1908年)
【必須】month 月份
0 到 11 之間的整數
date 天數
1 到 31 之間的整數
hrs 小時
0 到 23 之間的整數
min 分鐘
0 到 59 之間的整數
sec 秒
0 到 59 之間的整數
ms 毫秒
0 到 999 之間的整數
注意: 如果沒有傳入 date 天數,則默認爲 1。
而省略其它參數,這些參數都默認為 0
如果有一個指定的參數超出其合理範圍,則 UTC 方法會通過更新其他參數直到該參數在合理範圍內。例如,為月份指定 15,則年份將會加 1,然後月份將會使用 3。
1 | console.log(Date.UTC(2021,1)) // 1612137600000 (utc 時間 2021-02-01 00:00:00) |
返回自 1970 年 1 月 1 日 00:00:00 (UTC) 到当前时间的毫秒数。
等同於 new Date().getTime()
1 | console.log(new Date().getTime()) // 1615389429001 |
方法 | 解釋 |
---|---|
getFullYear() | 根据本地时间返回指定日期对象的年份(四位数年份时返回四位数字) |
getMonth() | 根据本地时间返回指定日期对象的月份(0-11) |
getDate() | 根据本地时间返回指定日期对象的月份中的第几天(1-31) |
getHours() | 根据本地时间返回指定日期对象的小时(0-23) |
getMinutes() | 根据本地时间返回指定日期对象的分钟(0-59) |
getSeconds() | 根据本地时间返回指定日期对象的秒数(0-59) |
getMilliseconds() | 根据本地时间返回指定日期对象的毫秒(0-999) |
getDay() | 根据本地时间返回指定日期对象的星期中的第几天(0-6) 0 表示 星期日 |
getTime() | 返回从1970-1-1 00:00:00 UTC(协调世界时)到该日期经过的毫秒数,对于1970-1-1 00:00:00 UTC之前的时间返回负值。 |
getTimezoneOffset() | 返回协调世界时(UTC)相对于当前时区的时间差值,单位为分钟 |
getUTCFullYear() | 根据世界时返回特定日期对象所在的年份(4位数) |
getUTCMonth() | 根据世界时返回特定日期对象的月份(0-11) 0 代表 1 月,依此類推 |
getUTCDate() | 根据世界时返回特定日期对象一个月的第几天(1-31) |
getUTCHours() | 根据世界时返回特定日期对象当前的小时(0-23) |
getUTCMinutes() | 根据世界时返回特定日期对象的分钟数(0-59) |
getUTCSeconds() | 根据世界时返回特定日期对象的秒数(0-59) |
getUTCMilliseconds() | 根据世界时返回特定日期对象的毫秒数(0-999) |
getUTCDay() | 根据世界时返回特定日期对象一个星期的第几天(0-6) |
1 | const today = new Date(2021,2,10,22,55,21,44) |
方法 | 解釋 |
---|---|
setDate() | 根据本地时间为指定的日期对象设置月份中的第几天 |
setFullYear() | 根据本地时间为指定日期对象设置完整年份(四位数年份是四个数字) |
setMonth() | 根据本地时间为指定日期对象设置月份 |
setHours() | 根据本地时间为指定日期对象设置小时数 |
setMinutes() | 根据本地时间为指定日期对象设置分钟数 |
setSeconds() | 根据本地时间为指定日期对象设置秒数 |
setMilliseconds() | 根据本地时间为指定日期对象设置毫秒数 |
setTime() | 通过指定从 1970-1-1 00:00:00 UTC 开始经过的毫秒数来设置日期对象的时间,对于早于 1970-1-1 00:00:00 UTC的时间可使用负值 |
setUTCDate() | 根据世界时设置 Date 对象中月份的一天 (1 ~ 31) |
setUTCFullYear() | 根据世界时设置 Date 对象中的年份(四位数字) |
setUTCMonth() | 根据世界时设置 Date 对象中的月份 (0 ~ 11) |
setUTCHours() | 根据世界时设置 Date 对象中的小时 (0 ~ 23) |
setUTCMinutes() | 根据世界时设置 Date 对象中的分钟 (0 ~ 59) |
setUTCSeconds() | 根据世界时设置 Date 对象中的秒钟 (0 ~ 59) |
setUTCMilliseconds() | 根据世界时设置 Date 对象中的毫秒 (0 ~ 999) |
方法 | 解釋 |
---|---|
toDateString() | 以美式英语和人类易读的形式返回该日期对象日期部分 的字符串 |
toISOString() | 把一个日期转换为符合 ISO 8601 扩展格式的字符串。 格式:YYYY-MM-DDTHH:mm:ss.sssZ ,时区总是UTC |
toJSON() | 調用 toISOString() 返回一个表示该日期的字符串。为了在 JSON.stringify() 方法中使用 |
toString() | 返回一个表示该日期对象的字符串。覆盖了Object.prototype.toString() 方法 |
toTimeString() | 以人类易读格式返回日期对象时间部分的字符串 |
toUTCString() | 把一个日期对象转换为一个以UTC时区计时的字符串 |
valueOf() | 返回一个日期对象的原始值。覆盖了 Object.prototype.valueOf() 方法 |
toLocaleString() | 返回一个表示该日期对象的字符串,该字符串与系统设置的地区关联(locality sensitive)。覆盖了 Object.prototype.toLocaleString() 方法 |
toLocaleTimeString() | 返回一个表示该日期对象时间部分的字符串,该字符串格式与系统设置的地区关联(locality sensitive) |
toLocaleDateString() | 返回一个表示该日期对象日期部分的字符串,该字符串格式与系统设置的地区关联(locality sensitive) |
1 | const today = new Date(2021,2,10,22,55,21,44) |
toLocaleString()
toLocaleTimeString()
和 toLocaleDateString()
這3個方法都接受兩個參數
【可選】locales
指定語言代碼,例如 ‘zh-HK’
具體語言代碼 可參考 MDN Intl 文檔
當沒有指定語言代碼時,會返回一個使用運行時默認的語言環境和格式(options)的格式化字符串
【可選】options
指定日期時間等輸出(文字/格式 等)
具體 options 可參考 MDN 文檔
1 | var today = new Date(2021,2,10,22,55,21,44) |
ECMAScript 數組的每一項可以保存任何類型的數據。
ECMAScript 數組的大小是可以動態調整的,既可以隨著數據的添加自動增長以容納新數據。
使用 Array 構造函數
使用 new Array()
, new 可以省略不寫。
1 | var colors = new Array(); |
使用數組字面量表示法
1 | var color = ['red','blue'] |
1 | var color = ['red','blue','green'] |
可以通過 length
獲取長度。也可以用 length
來從數組的末尾移除項或向數字添加項。
1 | var colors = ['red','yello','blue'] |
可以通過 Array.isArray() 來判斷是不是數組
1 | const arr = [1,2,3,4] |
所有的對象都具有 toLocaleString()
、 toString()
和 valueOf()
方法。
在這四個方法中,如果一個元素為 undefined 或 null,它會被轉換為空字符串。
valueOf()
返回對象的原始值,即數組本身
1 | var a = [1,2,3,4] |
toString()
返回一個字符串,表示指定的數組和元素,用逗號相連。
1 | var ab = [1,2,'3a','ab'] |
toLocaleString()
返回一個字符串表示數組中的元素。數組中的元素將使用各自的 toLocaleString() 方法轉成字符串,這些字符串將使用一個特定語言環境的字符串(例如一個逗號 “,”)隔開
1 | const array1 = [1, 'a', new Date('21 Dec 1997 14:12:00 UTC')]; |
join()
將一個數組的所有元素連接成一個字符串並返回這個字符串。默認為 逗號(,)
1 | var val = [1,2,3,4] |
push()
接受一個或多個參數,並將其添加到數組末尾,並返回修改後的數組長度。
1 | var colors = ['red','yellow','blue'] |
pop()
刪除數組中的最後一個元素,並返回被刪除的元素值。
1 | var colors = ["red", "yellow", "blue", "red", "black"] |
shift()
刪除數組中的第一個元素,並返回該元素。
1 | var colors = ["red", "yellow", "blue", "red", "black"] |
unshift()
接受一個或多個參數,並將其添加到數組開頭,並返回修改後的數組長度。
1 | var colors = ["blue", "red", "black"] |
reverse()
會反轉數組的順序,並返回新的數組。
1 | var val = [1,2,3,4,5] |
sort()
會用 原地算法 對數組的元素進行排序,並返回數組。默認的排序會將元素轉為字符串,然後比較它們的 UTF-16代碼單元值序列時構建的
由於它一定具體實現,因此無法保證排序的時間和空間複雜性。
sort 會調用每個數組項的 toString() 轉型方法,然後比較得到的字符串。每個字符串由左到右進行對比。
1 |
|
sort([compareFunction])
可以接收一個比較函數作為參數,用來指定按某種順序進行排列的函數。如果省略,元素按照轉換為的字符串的各個字符的Unicode位點進行排序
如果沒有指明 compareFunction ,那麼元素會按照轉換為的字符串的諸個字符的Unicode位點進行排序。例如 “Banana” 會被排列到 “cherry” 之前。當數字按由小到大排序時,9 出現在80 之前,但因為(沒有指明compareFunction),比較的數字會先被轉換為字符串,所以在Unicode順序上”80” 要比”9” 要靠前。
如果指明瞭 compareFunction ,那麼數組會按照調用該函數的返回值排序。即 a 和 b 是兩個將要被比較的元素:
以數字比較為例
1 | var val4 = [1,21,3,10,8] |
concat()
基於當前數組中的所有項創建一個新數組。concat()
並不會修改原數組
這個方法會創建當前數組的一個副本,並把接收到的參數添加到副本的末尾。
concat方法創建一個新的數組,它由被調用的對像中的元素組成,每個參數的順序依次是該參數的元素(如果參數是數組)或參數本身(如果參數不是數組)。它不會遞歸到嵌套數組參數中。
concat方法不會改變this或任何作為參數提供的數組,而是返回一個淺拷貝,它包含與原始數組相結合的相同元素的副本。原始數組的元素將復製到新數組中,如下所示:
對象引用(而不是實際對象):concat將對象引用複製到新數組中。原始數組和新數組都引用相同的對象。也就是説,如果引用的對像被修改,則更改對於新數組和原始數組都是可見的。這包括也是數組的數組參數的元素。
數據類型如字符串,數字和布爾(不是String,Number 和 Boolean 對象):concat將字符串和數字的值複製到新數組中。
1 | var val = [1,2,3,4] |
slice()
它能夠基於當前數組中的一個或多個項創建一個新數組。
slide(begin,end) 可以接受一個或兩個參數。這方法不會影響原始數組
一個參數
當只有一個參數時,會返回該參數從該參數指定位置開始到當前數組末尾的所有項
如果省略 begin,則 slice 從索引 0 開始。
如果 begin 超出原數組的索引範圍,則會返回空數組。
兩個參數
有兩個參數時,會返回起始和結束位置之間的項,但不包括結束位置的項。
如果 end 被省略,則 slice 會一直提取到原數組末尾。
如果 end 大於數組的長度,slice 也會一直提取到原數組末尾。
1 | var val = ['a','b','c','d','e'] |
參數可能是負數,我們可以用數組長度加上該數來確認位置。 slice(-4,-1)
等同於 slice(1,4)
。
splice()
splice() 方法可以藉由刪除既有元素並/或加入新元素來改變一個陣列的內容。
其會返回被刪除的元素陣列,如沒有刪除,則返回空數組。
array.splice(start[, deleteCount[, item1[, item2[, ...]]]])
start:
陣列中要開始改動的元素索引(起始為 0)。若索引大於陣列長度,則實際開始的索引值會被設為陣列長度。若索引為負,則會從陣列中最後一個元素開始往前改動(起始為 -1)且若其絕對值大於陣列的長度,則會被設為 0。
deleteCount: 一個表示欲刪除的原陣列元素數量的整數。
若省略了 deleteCount
,或假如其值大於 array.length - start
(也就是 deleteCount
大於 start
算起的剩餘元素數量),則所有從 start
開始到陣列中最後一個元素都會被刪除。
若 deleteCount
為 0 或是負數,則不會有元素被刪除。 因此,你應該給定至少一個欲加入的新元素
item1, item2, …
從 start 開始,要加入到陣列的元素。 如果你沒有指定任何元素,則 splice() 只會依照 start 和 deleteCount 刪除陣列的元素。
1 | // 刪除 |
1 | // 插入 |
1 | // 替換 |
indexOf()
和 lastIndexOf()
都接受兩個參數: 要查找的項 和 (可選)表示查找起點位置的索引。
返回值: 在陣列中找到的第一個元素索引值;沒找到則為 -1
indexOf()
是從數組的開頭向後查找。
lastIndexOf()
則從數組的末尾開始向前查找。
1 | // indexOf() |
1 | // lastIndexOf() |
這5個方法都接受2個參數
要在每一項運行的函數
函數會接受三個參數
【可選】運行該函數的作用域對象 - 影響 this 的值
every()
對數組中的每一項運行給定的函數,如果該函數對每一項都返回 true, 則返回 true。(全部都要返回 true)
1 |
|
some()
對數組中的每一項運行給定的函數,至少有一項是返回 true, 則返回 true。(只要一個返回 true)
1 | var val = [1,2,3,4,5] |
filter()
利用指定的函數來返回數組中符合函數的數組元素。不會修改原數組。
1 | // 返回大於 3 的元素 |
map()
返回一個數組,而這個數組的每一項都是在原始數組中對應項上運行傳入函數的結果。不會修改原數組。
1 | // 返回一個數組 ,其每一項都是原數組對應項的值乘以 3 |
forEach()
它只是對數組中的每一項運行傳入的函數。這個數組沒有返回值。跟 for 循環本質一樣。
1 |
|
兩個方法都是接受兩個參數
用於處理每一個元素的函數 callback
該函數接受4個參數
【可選】initialValue 作為歸並基礎的初始值,如果沒有初始值,這數組的第一個元素將會作為初始值。
reduce()
將一個累加器及數組中每項元素(由左至右)傳入回呼函式,將陣列化為單一值。
1 | // 把數組的所有項從左到右相加 |
reduceRight()
將一個累加器及數組中每項元素(由右至左)傳入回呼函式,將陣列化為單一值。
1 | // // 把數組的所有項從右到左相加 |
copyWithin()
方法會對數組的一部分進行淺拷貝至同一數組的另一位置並回傳此陣列,而不修改其大小。
返回被修改後的數組,原數組也會被修改。
arr.copyWithin(target, start, end)
target
要複製序列到這個位置的索引
如果是負數,target 從末尾開始算
如果 targrt 大於等於 arr.length, 則沒有項目被複製
【可選】start
開始複製的起始元素索引(起始為 0 )
【可選】end
結束複製的元素索引,會從 start 複製到 end, 但是不包括 end 的值
如果省略 end,將會一路複製到數組末項
1 | var val = [1,2,3,4,5,6] |
會將數組中索引的第一個到最後一個的每個位置全部填入一個靜態的值。
arr.fill(value[, start[, end]])
value
想要插入的值
【可選】start
起始的位置,預設為 0
【可選】end
結束的位置,預設為 數組長度
注意:不包括 end 的位置
1 | // 沒有 start 和 end,所以從 0 到數組的長度 |
find()
返回第一個滿足函數條件的元素值,否則返回 undefined
arr.find(callback[, thisArg])
接受 2 個參數 callback 和 thisArg
callback 函數
會處理數組每一個元素的函數,它接受三個參數
element
正被處理的函數
【可選】index
正被處理的函數索引
【可選】array
呼叫 find 方法的數組
【可選】thisArg
執行 callback 函式時被當作 this 的物件
1 | // 返回符合 大於3 的第一個元素 |
findIndex()
返回第一個滿足函數條件的元素值索引,否則返回 -1
其與 find()
的區別在於,前者返回的元素值,後者返回的是元素值的索引值
arr.findIndex(callback[, thisArg])
接受 2 個參數 callback 和 thisArg
callback 函數
會處理數組每一個元素的函數,它接受三個參數
element
正被處理的函數
【可選】index
正被處理的函數索引
【可選】array
呼叫 find 方法的數組
【可選】thisArg
執行 callback 函式時被當作 this 的物件
1 | // 返回符合 大於3 的第一個元素索引 |
2018年6月,心血來潮去買了一個域名。當時看到一個 me 後綴,感覺挺適合做個人博客的。本來想買 jerry.me , 很顯然這麼好的域名早就被人註冊了。無奈之下,退而一步選擇了 jerryc。 jerryc 也是我很多賬號的名字(畢竟 jerry 註冊不到)。之後一直有人問我,是不是彈吉他的那位。我就納悶,我也不會彈吉他。去 Google 了之後才發現
原來 jerryc 是一個有名的吉他手啊,怪不得有人問我是不是他本人。
這也帶來另一個煩惱,就是搜索首頁都是他的內容,我自己的網站要到第2,3頁才顯示。畢竟網站的流量不高,那排在首頁的機會就越來越小的。加上 me 域名還是太小眾了,選擇 com 域名還是最好的選擇。
好的 com 域名基本已經被註冊掉了,曾聯繫過一個賣家詢問域名出售,然而報價太高,只能望而卻步。在 Godaddy 上試了很多域名,最終選擇了 immyw 這個域名。不得不説 com 域名比 me 域名便宜太多。
myw 是我的名字粵拼的縮寫,名在前,姓在後。不出意外這個域名會一直使用下去,雖然難記,但是挺有意義的域名。
]]>以下文章轉載自前端自習課,作者王平安
在《初中級前端 JavaScript 自測清單 - 1》部分中,和大家簡單過了一遍 JavaScript 的一些基礎知識,沒看過的朋友可以回顧一下 😁
本系列文章是我在我們團隊內部的 “「現代 JavaScript 突擊隊」”,第一期學習內容為《現代 JavaScript 教程》系列的「第二部分」輸出內容,希望這份自測清單,能夠幫助大家鞏固知識,温故知新。
本部分內容,以 「JavaScript 對象」為主,大致包括以下內容:
JavaScript 有八種數據額類型,有七種原始類型,它們值只包含一種類型(字符串,數字或其他),而對象是用來「保存鍵值對和更復雜實體。」我們可以通過使用帶有可選「屬性列表」的花括號 {...}
來創建對象,一個屬性就是一個鍵值對 {"key" : "value"}
,其中鍵( key
)是一個字符串(或稱屬性名),值( value
)可以是任何類型。
我們可以使用 2 種方式來創建一個新對象:
1 | // 1. 通過“構造函數”創建 |
創建對象時,可以初始化對象的一些屬性:
1 | let user = { |
然後可以對該對象進行屬性對「增刪改查」操作:
1 | // 增加屬性 |
當然對象的鍵( key
)也可以是多詞屬性,但必須加引號,使用的時候,必須使用方括號( []
)讀取:
1 | let user = { |
我們也可以在方括號中使用變量,來獲取屬性值:
1 | let key = 'name' |
創建對象時,可以在對象字面量中使用方括號,即 「計算屬性」 :
1 | let key = 'name' |
當然,計算屬性也可以是表達式:
1 | let key = 'name' |
實際開發中,可以將相同的屬性名和屬性值簡寫成更短的語法:
1 | // 原本書寫方式 |
也可以混用:
1 | // 原本書寫方式 |
該方法可以判斷「對象的自有屬性和繼承來的屬性」是否存在。
1 | let user = { name: 'leo' } |
該方法只能判斷「自有屬性」是否存在,對於「繼承屬性」會返回 false
。
1 | let user = {name: "leo"}; |
該方法可以判斷對象的「自有屬性和繼承屬性」。
1 | let user = {name: "leo"}; |
該方法存在一個問題,如果屬性的值就是 undefined
的話,該方法不能返回想要的結果:
1 | let user = {name: undefined}; |
1 | let user = {} |
當我們需要遍歷對象中每一個屬性,可以使用 for...in
語句來實現
for...in
語句以任意順序遍歷一個對象的除 Symbol
以外的可枚舉屬性。「注意」 :for...in
不應該應用在一個數組,其中索引順序很重要。
1 | let user = { |
ES7 中新增加的 Object.values()
和Object.entries()
與之前的Object.keys()
類似,返回數組類型。
返回一個數組,成員是參數對象自身的(不含繼承的)所有「可遍歷屬性」的健名。
1 | let user = { name: 'leo', age: 18 } |
返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷屬性的鍵值。
1 | let user = { name: 'leo', age: 18 } |
如果參數不是對象,則返回空數組:
1 | Object.values(10) // [] |
返回一個數組,成員是參數對象自身的(不含繼承的)所有「可遍歷屬性」的鍵值對數組。
1 | let user = { name: 'leo', age: 18 } |
手動實現Object.entries()
方法:
1 | // Generator函數實現: |
該方法返回一個數組,它包含了對象 Obj
所有擁有的屬性(「無論是否可枚舉」)的名稱。
1 | let user = { name: 'leo', age: 18 } |
參考文章《搞不懂 JS 中賦值 · 淺拷貝 · 深拷貝的請看這裏》
首先回顧下基本數據類型和引用數據類型:
基本類型
概念:基本類型值在內存中佔據固定大小,保存在棧內存
中(不包含閉包
中的變量)。常見包括:undefined, null, Boolean, String, Number, Symbol
引用類型
概念:引用類型的值是對象,保存在堆內存
中。而棧內存存儲的是對象的變量標識符以及對象在堆內存中的存儲地址 (引用),引用數據類型在棧中存儲了指針,該指針指向堆中該實體的起始地址。當解釋器尋找引用值時,會首先檢索其在棧中的地址,取得地址後從堆中獲得實體。常見包括:Object,Array,Date,Function,RegExp 等
在棧內存中的數據發生數據變化的時候,系統會自動為新的變量分配一個新的之值在棧內存中,兩個變量相互獨立,互不影響的。
1 | let user = 'leo' |
在 JavaScript 中,變量不存儲對象本身,而是存儲其 “內存中的地址”,換句話説就是存儲對其的 “引用”。如下面 leo
變量只是保存對user
對象對應引用:
1 | let user = { name: 'leo', age: 18 } |
其他變量也可以引用 user
對象:
1 | let leo1 = user |
但是由於變量保存的是引用,所以當我們修改變量 leo
\ leo1
\ leo2
這些值時,「也會改動到引用對象」 user
,但當 user
修改,則其他引用該對象的變量,值都會發生變化:
1 | leo.name = 'pingan' |
這個過程中涉及變量地址指針指向問題,這裏暫時不展開討論,有興趣的朋友可以網上查閲相關資料。
當兩個變量引用同一個對象時,它們無論是 ==
還是 ===
都會返回 true
。
1 | let user = { name: 'leo', age: 18 } |
但如果兩個變量是空對象 {}
,則不相等:
1 | let leo1 = {} |
概念:「新的對象複製已有對象中非對象屬性的值和對象屬性的引用」。也可以理解為:「一個新的對象直接拷貝已存在的對象的對象屬性的引用」,即淺拷貝。
淺拷貝「只對第一層屬性進行了拷貝」,當第一層的屬性值是基本數據類型時,新的對象和原對象互不影響,但是如果第一層的屬性值是複雜數據類型,那麼新對象和原對象的屬性值其指向的是同一塊內存地址。
通過示例代碼演示沒有使用淺拷貝場景:
1 | // 示例1 對象原始拷貝 |
從上面示例代碼可以看出:由於對象被直接拷貝,相當於拷貝 「引用數據類型」 ,所以在新對象修改任何值時,都會改動到源數據。
接下來實現淺拷貝,對比以下。
語法:Object.assign(target, ...sources)
ES6 中拷貝對象的方法,接受的第一個參數是拷貝的目標 target,剩下的參數是拷貝的源對象 sources(可以是多個)。詳細介紹,可以閲讀文檔《MDN Object.assign》。
1 | // 示例1 對象淺拷貝 |
從打印結果可以看出,淺拷貝只是在根屬性 (對象的第一層級) 創建了一個新的對象,但是對於屬性的值是對象的話只會拷貝一份相同的內存地址。
Object.assign()
使用注意:
- 只拷貝源對象的自身屬性(不拷貝繼承屬性); - 不會拷貝對象不可枚舉的屬性; - 屬性名為`Symbol` 值的屬性,可以被 Object.assign 拷貝; - `undefined`和`null`無法轉成對象,它們不能作為`Object.assign`參數,但是可以作為源對象。 1
2
3
4
5
6
7
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
Object.assign({}, undefined) // {}
Object.assign({}, null) // {}
let user = { name: 'leo' }
Object.assign(user, undefined) === user // true
Object.assign(user, null) === user // true
語法:arr.slice([begin[, end]])``slice()
方法返回一個新的數組對象,這一對象是一個由 begin
和 end
決定的原數組的淺拷貝(包括 begin
,不包括end
)。原始數組不會被改變。詳細介紹,可以閲讀文檔《MDN Array slice》。
1 | // 示例 數組深拷貝 |
語法:var new_array = old_array.concat(value1[, value2[, ...[, valueN]]])``concat()
方法用於合併兩個或多個數組。此方法不會更改現有數組,而是返回一個新數組。詳細介紹,可以閲讀文檔《MDN Array concat》。
1 | let user = [{ name: 'leo' }, { age: 18 }] |
Array.prototype.concat
也是一個淺拷貝,只是在根屬性 (對象的第一層級) 創建了一個新的對象,但是對於屬性的值是對象的話只會拷貝一份相同的內存地址。
語法:var cloneObj = { ...obj };
擴展運算符也是淺拷貝,對於值是對象的屬性無法完全拷貝成 2 個不同對象,但是如果屬性都是基本類型的值的話,使用擴展運算符也是優勢方便的地方。
1 | let user = { name: 'leo', skill: { JavaScript: 90, CSS: 80 } } |
實現原理:新的對象複製已有對象中非對象屬性的值和對象屬性的「引用」, 也就是説對象屬性並不複製到內存。
1 | function cloneShallow(source) { |
for…in 語句以任意順序遍歷一個對象自有的、繼承的、可枚舉的
、非 Symbol 的屬性。對於每個不同的屬性,語句都會被執行。
該函數返回值為布爾值,所有繼承了 Object 的對象都會繼承到 hasOwnProperty
方法,和 in
運算符不同,該函數會忽略掉那些從原型鏈上繼承到的屬性和自身屬性。語法:obj.hasOwnProperty(prop)
prop
是要檢測的屬性「字符串名稱」或者Symbol
。
複製變量值,對於引用數據,則遞歸至基本類型後,再複製。深拷貝後的對象「與原來的對象完全隔離」,互不影響,對一個對象的修改並不會影響另一個對象。
其原理是把一個對象序列化成為一個 JSON 字符串,將對象的內容轉換成字符串的形式再保存在磁盤上,再用JSON.parse()
反序列化將 JSON 字符串變成一個新的對象。
1 | let user = { name: 'leo', skill: { JavaScript: 90, CSS: 80 } } |
JSON.stringify()
使用注意:
undefined
, symbol
則經過 JSON.stringify()
` 序列化後的 JSON 字符串中這個鍵值對會消失;Date
引用類型會變成字符串;RegExp
引用類型會變成空對象;NaN
、 Infinity
和 -Infinity
,則序列化的結果會變成 null
;obj[key] = obj
)。核心思想是「遞歸」,遍歷對象、數組直到裏邊都是基本數據類型,然後再去複製,就是深度拷貝。實現代碼:
1 | const isObject = obj => typeof obj === 'object' && obj != null; |
該方法缺陷:遇到循環引用,會陷入一個循環的遞歸過程,從而導致爆棧。其他寫法,可以閲讀《如何寫出一個驚豔面試官的深拷貝?》 。
「淺拷貝」:將對象的每個屬性進行依次複製,但是當對象的屬性值是引用類型時,實質複製的是其引用,當引用指向的值改變時也會跟着變化。
「深拷貝」:複製變量值,對於引用數據,則遞歸至基本類型後,再複製。深拷貝後的對象「與原來的對象完全隔離」,互不影響,對一個對象的修改並不會影響另一個對象。
「深拷貝和淺拷貝是針對複雜數據類型來説的,淺拷貝只拷貝一層,而深拷貝是層層拷貝。」
垃圾回收(Garbage Collection,縮寫為 GC)是一種自動的存儲器管理機制。當某個程序佔用的一部分內存空間不再被這個程序訪問時,這個程序會藉助垃圾回收算法向操作系統歸還這部分內存空間。垃圾回收器可以減輕程序員的負擔,也減少程序中的錯誤。垃圾回收最早起源於 LISP 語言。目前許多語言如 Smalltalk、Java、C# 和 D 語言都支持垃圾回收器,我們熟知的 JavaScript 具有自動垃圾回收機制。
「在 JavaScript 中,原始類型的數據被分配到棧空間中,引用類型的數據會被分配到堆空間中。」
當函數 showName
調用完成後,通過下移 ESP(Extended Stack Pointer)指針,來銷燬 showName
函數,之後調用其他函數時,將覆蓋掉舊內存,存放另一個函數的執行上下文,實現垃圾回收。圖片來自《瀏覽器工作原理與實踐》
堆中數據垃圾回收策略的基礎是:「代際假説」(The Generational Hypothesis)。即:
這兩個特點不僅僅適用於 JavaScript,同樣適用於大多數的動態語言,如 Java、Python 等。V8 引擎將堆空間分為「新生代」(存放生存「時間短」的對象)和「老生代」(存放生存「時間長」的對象)兩個區域,並使用不同的垃圾回收器。
不管是哪種垃圾回收器,都使用相同垃圾回收流程:「標記活動對象和非活動對象,回收非活動對象的內存,最後內存整理。」
使用 Scavenge 算法處理,將新生代空間對半分為兩個區域,一個對象區域,一個空閒區域。圖片來自《瀏覽器工作原理與實踐》
執行流程:
當然,這也存在一些問題:若複製操作的數據較大則影響清理效率。JavaScript 引擎的解決方式是:將新生代區域設置得比較小,並採用對象晉升策略(經過兩次回收仍存活的對象,會被移動到老生區),避免因為新生代區域較小引起存活對象裝滿整個區域的問題。
分為:「標記 - 清除(Mark-Sweep)算法」,和「標記 - 整理(Mark-Compact)算法」。
「a) 標記 - 清除(Mark-Sweep)算法」 「過程:」
圖片來自《瀏覽器工作原理與實踐》
「b) 標記 - 整理(Mark-Compact)算法」 「過程:」
圖片來自《瀏覽器工作原理與實踐》
1.《圖解 Java 垃圾回收機制》2.《MDN 內存管理》
具體介紹可閲讀 《MDN 方法的定義》 。將作為對象屬性的方法稱為 “對象方法”,如下面 user
對象的 say
方法:
1 | let user = {} |
也可以使用更加簡潔的方法:
1 | let user = { |
當然對象方法的名稱,還支持計算的屬性名稱作為方法名:
1 | const hello = 'Hello' |
另外需要注意的是:所有方法定義不是構造函數,如果您嘗試實例化它們,將拋出TypeError
。
1 | let user = { |
當對象方法需要使用對象中的屬性,可以使用 this
關鍵字:
1 | let user = { |
當代碼 user.say()
執行過程中, this
指的是 user
對象。當然也可以直接使用變量名 user
來引用 say()
方法:
1 | let user = { |
但是這樣並不安全,因為 user
對象可能賦值給另外一個變量,並且將其他值賦值給 user
對象,就可能導致報錯:
1 | let user = { |
但將 user.name
改成 this.name
代碼便正常運行。
this
的值是在 「代碼運行時計算出來」 的,它的值取決於代碼上下文:
1 | let user = { name: 'leo' } |
規則:如果 obj.fun()
被調用,則 this
在 fun
函數調用期間是 obj
,所以上面的 this
先是 user
,然後是 admin
。
但是在全局環境中,無論是否開啟嚴格模式, this
都指向全局對象
1 | console.log(this == window) // true |
箭頭函數比較特別,沒有自己的 this
,如果有引用 this
的話,則指向外部正常函數,下面例子中, this
指向 user.say()
方法:
1 | let user = { |
詳細可以閲讀《js 基礎 - 關於 call,apply,bind 的一切》 。當我們想把 this
值綁定到另一個環境中,就可以使用 call
/ apply
/ bind
方法實現:
1 | var user = { name: 'leo' } |
注意:這裏的 var name = 'pingan';
需要使用 var
來聲明,使用 let
的話, window
上將沒有 name
變量。
三者語法如下:
1 | fun.call(thisArg, param1, param2, ...) |
構造函數的作用在於 「實現可重用的對象創建代碼」 。通常,對於構造函數有兩個約定:
new
運算符執行。「new
運算符」創建一個用户定義的對象類型的實例或具有構造函數的內置對象的實例。語法如下:
1 | new constructor[([arguments])] |
參數如下:
constructor
一個指定對象實例的類型的類或函數。arguments
一個用於被 constructor
調用的參數列表。舉個簡單示例:
1 | function User(name) { |
當一個函數被使用 new
運算符執行時,它按照以下步驟:
this
。this
,為其添加新的屬性。this
的值。以前面 User
方法為例:
1 | function User(name) { |
當我們執行 new User('leo')
時,發生以下事情:
User.prototype
的新對象被創建;User
,並將 this
綁定到新創建的對象;new
表達式的結果。如果構造函數沒有顯式返回一個對象,則使用步驟 1 創建的對象。「需要注意」:
new User
等同於 new User()
,只是沒有指定參數列表,即 User
不帶參數的情況;1 | let user = new User() // <-- 沒有參數 |
new
運算符運行。在構造函數中,也可以將方法綁定到 this
上:
1 | function User(name) { |
詳細介紹可以查看 《MDN 可選鏈操作符》 。
在實際開發中,常常出現下面幾種報錯情況:
1 | // 1. 對象中不存在指定屬性 |
在可選鏈 ?.
出現之前,我們會使用短路操作 &&
運算符來解決該問題:
1 | const leo = {}; |
這種寫法的缺點就是 「太麻煩了」 。
可選鏈 ?.
是一種 「訪問嵌套對象屬性的防錯誤方法」 。即使中間的屬性不存在,也不會出現錯誤。如果可選鏈 ?.
前面部分是 undefined
或者 null
,它會停止運算並返回 undefined
。
語法:
1 | obj?.prop |
「我們改造前面示例代碼:」
1 | // 1. 對象中不存在指定屬性 |
可選鏈雖然好用,但需要注意以下幾點:
我們應該只將 ?.
使用在一些屬性或方法可以不存在的地方,以上面示例代碼為例:
1 | const leo = {}; |
這樣寫會更好,因為 leo
對象是必須存在,而 name
屬性則可能不存在。
?.
之前的變量必須已聲明」;在可選鏈 ?.
之前的變量必須使用 let/const/var
聲明,否則會報錯:
1 | leo?.name |
1 | let object = {} |
1 | let arrayItem = arr?.[42] |
需要説明的是 ?.
是一個特殊的語法結構,而不是一個運算符,它還可以與其 ()
和 []
一起使用:
?.()
用於調用一個可能不存在的函數,比如:
1 | let user1 = { |
?.()
會檢查它左邊的部分:如果 admin 函數存在,那麼就調用運行它(對於 user1
)。否則(對於 user2
)運算停止,沒有錯誤。
?.[]
允許從一個可能不存在的對象上安全地讀取屬性。
1 | let user1 = { |
?.
語法總結可選鏈 ?.
語法有三種形式:
obj?.prop
—— 如果 obj
存在則返回 obj.prop
,否則返回 undefined
。obj?.[prop]
—— 如果 obj
存在則返回 obj[prop]
,否則返回 undefined
。obj?.method()
—— 如果 obj
存在則調用 obj.method()
,否則返回 undefined
。正如我們所看到的,這些語法形式用起來都很簡單直接。?.
檢查左邊部分是否為 null/undefined
,如果不是則繼續運算。?.
鏈使我們能夠安全地訪問嵌套屬性。
規範規定,JavaScript 中對象的屬性只能為 「字符串類型」 或者 「Symbol 類型」 ,畢竟我們也只見過這兩種類型。
ES6 引入Symbol
作為一種新的「原始數據類型」,表示「獨一無二」的值,主要是為了「防止屬性名衝突」。ES6 之後,JavaScript 一共有其中數據類型:Symbol
、undefined
、null
、Boolean
、String
、Number
、Object
。「簡單使用」:
1 | let leo = Symbol() |
Symbol 支持傳入參數作為 Symbol 名,方便代碼調試:**
1 | let leo = Symbol('leo') |
Symbol
函數不能用new
,會報錯。由於Symbol
是一個原始類型,不是對象,所以不能添加屬性,它是類似於字符串的數據類型。
1 | let leo = new Symbol() |
Symbol
都是不相等的,「即使參數相同」。1 | // 沒有參數 |
Symbol
不能與其他類型的值計算,會報錯。1 | let leo = Symbol('hello') |
Symbol
不能自動轉換為字符串,只能顯式轉換。1 | let leo = Symbol('hello'); |
Symbol
可以轉換為布爾值,但不能轉為數值:1 | let a1 = Symbol() |
Symbol
屬性不參與 for...in/of
循環。1 | let id = Symbol('id') |
在對象字面量中使用 Symbol
作為屬性名時,需要使用 「方括號」 ( []
),如 [leo]: "leo"
。好處:防止同名屬性,還有防止鍵被改寫或覆蓋。
1 | let leo = Symbol() |
「需要注意」 :Symbol 作為對象屬性名時,不能用點運算符,並且必須放在方括號內。
1 | let leo = Symbol() |
「常常還用於創建一組常量,保證所有值不相等」:
1 | let user = {} |
「魔術字符串」:指代碼中多次出現,強耦合的字符串或數值,應該避免,而使用含義清晰的變量代替。
1 | function fun(name) { |
常使用變量,消除魔術字符串:
1 | let obj = { |
使用Symbol
消除強耦合,使得不需關係具體的值:
1 | let obj = { |
Symbol 作為屬性名遍歷,不出現在for...in
、for...of
循環,也不被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
1 | let leo = Symbol('leo'), |
Object.getOwnPropertySymbols
方法返回一個數組,包含當前對象所有用做屬性名的 Symbol 值。
1 | let user = {} |
另外可以使用Reflect.ownKeys
方法可以返回所有類型的鍵名,包括常規鍵名和 Symbol 鍵名。
1 | let user = { |
由於 Symbol 值作為名稱的屬性不被常規方法遍歷獲取,因此常用於定義對象的一些非私有,且內部使用的方法。
「用於重複使用一個 Symbol 值」,接收一個「字符串」作為參數,若存在用此參數作為名稱的 Symbol 值,返回這個 Symbol,否則新建並返回以這個參數為名稱的 Symbol 值。
1 | let leo = Symbol.for('leo') |
Symbol()
和 Symbol.for()
區別:
1 | Symbol.for('leo') === Symbol.for('leo') // true |
「用於返回一個已使用的 Symbol 類型的 key」:
1 | let leo = Symbol.for('leo') |
ES6 提供 11 個內置的 Symbol 值,指向語言內部使用的方法:
當其他對象使用instanceof
運算符,判斷是否為該對象的實例時,會調用這個方法。比如,foo instanceof Foo
在語言內部,實際調用的是Foo[Symbol.hasInstance](foo)
。
1 | class P { |
P 是一個類,new P() 會返回一個實例,該實例的Symbol.hasInstance
方法,會在進行instanceof
運算時自動調用,判斷左側的運算子是否為Array
的實例。
值為布爾值,表示該對象用於Array.prototype.concat()
時,是否可以展開。
1 | let a = ['aa', 'bb'] |
指向一個構造函數,在創建衍生對象時會使用,使用時需要用get
取值器。
1 | class P extends Array { |
解決下面問題:
1 | // 問題: b應該是 Array 的實例,實際上是 P 的實例 |
當執行str.match(myObject)
,傳入的屬性存在時會調用,並返回該方法的返回值。
1 | class P { |
當該對象被String.prototype.replace
方法調用時,會返回該方法的返回值。
1 | let a = {} |
當該對象被String.prototype.search
方法調用時,會返回該方法的返回值。
1 | class P { |
當該對象被String.prototype.split
方法調用時,會返回該方法的返回值。
1 | // 重新定義了字符串對象的split方法的行為 |
對象進行for...of
循環時,會調用Symbol.iterator
方法,返回該對象的默認遍歷器。
1 | class P { |
該對象被轉為原始類型的值時,會調用這個方法,返回該對象對應的原始類型值。調用時,需要接收一個字符串參數,表示當前運算模式,運算模式有:
1 | let obj = { |
在該對象上面調用Object.prototype.toString
方法時,如果這個屬性存在,它的返回值會出現在toString
方法返回的字符串之中,表示對象的類型。也就是説,這個屬性可以用來定製[object Object]
或[object Array]
中object
後面的那個字符串。
1 | // 例一 |
該對象指定了使用 with 關鍵字時,哪些屬性會被 with 環境排除。
1 | // 沒有 unscopables 時 |
上面代碼通過指定Symbol.unscopables
屬性,使得with
語法塊不會在當前作用域尋找foo
屬性,即foo
將指向外層作用域的變量。
前面複習到字符串、數值、布爾值等的轉換,但是沒有講到對象的轉換規則,這部分就一起看看:。需要記住幾個規則:
true
,並且不存在轉換為布爾值的操作,只有字符串和數值轉換有。Date
對象可以相減,如 date1 - date2
結果為兩個時間的差值。alert(obj)
這種形式。當然我們可以使用特殊的對象方法,對字符串和數值轉換進行微調。下面介紹三個類型(hint)轉換情況:
對象到字符串的轉換,當我們對期望一個字符串的對象執行操作時,如 “alert”:
1 | // 輸出 |
對象到數字的轉換,例如當我們進行數學運算時:
1 | // 顯式轉換 |
少數情況下,「當運算符 “不確定” 期望值類型時」。例如,二進制加法 +
可用於字符串(連接),也可以用於數字(相加),所以字符串和數字這兩種類型都可以。因此,當二元加法得到對象類型的參數時,它將依據 "default"
來對其進行轉換。此外,如果對象被用於與字符串、數字或 symbol 進行 ==
比較,這時到底應該進行哪種轉換也不是很明確,因此使用 "default"
。
1 | // 二元加法使用默認 hint |
「為了進行轉換,JavaScript 嘗試查找並調用三個對象方法:」
obj[Symbol.toPrimitive](hint)
—— 帶有 symbol 鍵 Symbol.toPrimitive
(系統 symbol)的方法,如果這個方法存在的話,"string"
—— 嘗試 obj.toString()
和 obj.valueOf()
,無論哪個存在。"number"
或 "default"
—— 嘗試 obj.valueOf()
和 obj.toString()
,無論哪個存在。詳細介紹可閲讀《MDN | Symbol.toPrimitive》 。Symbol.toPrimitive
是一個內置的 Symbol 值,它是作為對象的函數值屬性存在的,當一個對象轉換為對應的原始值時,會調用此函數。簡單示例介紹:
1 | let user = { |
toString
/ valueOf()
是兩個比較早期的實現轉換的方法。當沒有 Symbol.toPrimitive
,那麼 JavaScript 將嘗試找到它們,並且按照下面的順序進行嘗試:
toString -> valueOf
。valueOf -> toString
。這兩個方法必須返回一個原始值。如果 toString
或 valueOf
返回了一個對象,那麼返回值會被忽略。默認情況下,普通對象具有 toString
和 valueOf
方法:
toString
方法返回一個字符串 "[object Object]"
。valueOf
方法返回對象自身。簡單示例介紹:
1 | const user = {name: "Leo"}; |
我們也可以結合 toString
/ valueOf()
實現前面第 5 點介紹的 user
對象:
1 | let user = { |
本文作為《初中級前端 JavaScript 自測清單》第二部分,介紹的內容以 JavaScript 對象為主,其中有讓我眼前一亮的知識點,如 Symbol.toPrimitive
方法。我也希望這個清單能幫助大家自測自己的 JavaScript 水平並查缺補漏,温故知新。
如果你喜歡這篇文章,可以關注原作者的公眾號 前端自習課
]]>以下文章轉載自前端自習課,作者王平安
最近與部門老大一起面試了許多前端求職者,其中「想換個學習氛圍較好的人佔多數」,但良好的學習氛圍也是需要一點點營造出來的🌺。
為此我們組建了我們團隊內部的 “「現代 JavaScript 突擊隊」”,第一期學習內容為《現代 JavaScript 教程》系列,幫助小組成員系統地進行學習鞏固,並「讓大家養成系統性學習和輸出學習總結的學習方式」。
本文作為我輸出的第一部分學習總結,希望作為一份自測清單,幫助大家鞏固知識,温故知新。
接下來開始分享自測清單的內容。
JavaScript 腳本引入方式有兩種:
<script>
標籤插入腳本;
<script>
標籤 src
設置腳本地址。
<script>
標籤有以下常用屬性:
src
:指定外部腳本的 URI, 如果設置了 src
特性,script 標籤內容將會被忽略;
1 | <script src="example-url.js"></script> |
type
:指定引用腳本的語言,屬性值為 MIME 類型,包括text/javascript
, text/ecmascript
, application/javascript
, 和application/ecmascript
。如果沒有定義這個屬性,腳本會被視作 JavaScript。
ES6 新增了屬性值 module
,代碼會被當做 JavaScript 模塊。
1 | <script type="text/javascript"></script> |
async
規定一旦腳本可用,則會異步執行。注意:async 屬性僅適用於外部腳本(「只有在使用 src 屬性時」)。有多種執行外部腳本的方法:如果 async="async"
:腳本相對於頁面的其餘部分異步地執行(當頁面繼續進行解析時,腳本將被執行);如果不使用 async
且 defer="defer"
:腳本將在頁面完成解析時執行;如果既不使用 async
也不使用 defer
:在瀏覽器繼續解析頁面之前,立即讀取並執行腳本;
1 | <script async="async"></script> |
defer
屬性規定是否對腳本執行進行延遲,直到頁面加載為止。
如果您的腳本不會改變文檔的內容,可將 defer 屬性加入到 <script>
標籤中,以便加快處理文檔的速度。因為瀏覽器知道它將能夠安全地讀取文檔的剩餘部分而不用執行腳本,它將推遲對腳本的解釋,直到文檔已經顯示給用户為止。
1 | <script defer="defer"></script> |
詳細介紹可以閲讀《 MDN <script>
章節 》。
語句是執行行為(action)的語法結構和命令。如:alert('Hello, world!')
這樣可以用來顯示消息的語句。
存在分行符時,多數情況下可以省略分號。但不全是,比如:
1 | alert(3 + |
建議新人最好不要省略分號。
「單行註釋以兩個正斜槓字符 //
開始。」
1 | // 註釋文本 |
「多行註釋以一個正斜槓和星號開始 “/*”
並以一個星號和正斜杆結束 “*/”
。」
1 | /* |
JavaScript 的嚴格模式是使用受限制的 JavaScript 的一種方式,從而隱式地退出 “草率模式”。
"use strict"
指令將瀏覽器引擎轉換為 “現代” 模式,改變一些內建特性的行為。
通過在腳本文件 / 函數開頭添加 "use strict";
聲明,即可啟用嚴格模式。全局開啟嚴格模式:
1 | // index.js |
函數內開啟嚴格模式:
1 | // index.js |
"use strict"
需要定義在腳本最頂部(函數內除外),否則嚴格模式可能無法啟用。
一旦進入了嚴格模式,就無法關閉嚴格模式。
啟用 "use strict"
後,為未定義元素賦值將拋出異常:
1 | "use strict"; |
啟用 "use strict"
後,試圖刪除不可刪除的屬性時會拋出異常:
1 | "use strict"; |
詳細介紹可以閲讀《MDN 嚴格模式章節 》。
變量是數據的 “命名存儲”。
目前定義變量可以使用三種關鍵字:var / let / const。三者區別可以閲讀《let 和 const 命令》 。
1 | let name = "leo"; |
變量命名有 2 個限制:
變量名稱必須僅包含「字母,數字,符號」 $
和 _
。
首字符必須「非數字」。變量命名還有一些建議:
常量一般用全大寫,如 const PI = 3.141592
;
使用易讀的命名,比如 userName
或者 shoppingCart
。
JavaScript 變量名稱區分大小寫,如變量 leo
與 Leo
是不同的;
JavaScript 變量名稱允許非英文字母,但不推薦,如 let 平安 = "leo"
;
避免使用 a
、b
、c
這種縮寫。
JavaScript 是一種「弱類型」或者説「動態語言」。這意味着你不用提前聲明變量的類型,在程序運行過程中,類型會被自動確定。這也意味着你可以使用同一個變量保存不同類型的數據:
1 | var foo = 42; // foo is a Number now |
詳細介紹可以閲讀《MDN JavaScript 數據類型和數據結構 》。
前七種為基本數據類型,也稱為原始類型(值本身無法被改變),而 object
為複雜數據類型。八大數據類型分別是:
number
用於任何類型的數字:整數或浮點數,在 ±2 範圍內的整數。
bigint
用於任意長度的整數。
string
用於字符串:一個字符串可以包含一個或多個字符,所以沒有單獨的單字符類型。
boolean
用於 true
和 false
。
null
用於未知的值 —— 只有一個 null
值的獨立類型。
undefined
用於未定義的值 —— 只有一個 undefined
值的獨立類型。
symbol
用於唯一的標識符。
object
用於更復雜的數據結構。「每個類型後面會詳細介紹。」
通過 typeof
運算符檢查:
兩種形式:typeof x
或者 typeof(x)
。
以字符串的形式返回類型名稱,例如 "string"
。
typeof null
會返回 "object"
—— 這是 JavaScript 編程語言的一個錯誤,實際上它並不是一個 object
。
1 | typeof "leo" // "string" |
JavaScript 變量可以轉換為新變量或其他數據類型:
通過使用 JavaScript 函數
通過 JavaScript 自身自動轉換
通過全局方法 String()
將 「其他類型數據(任何類型的數字,字母,布爾值,對象)」 轉換為 String 類型:
1 | String(123); // "123" |
通過以下幾種方式能將其他類型數據轉換為 Number 類型:
+
1 | const age = +"22"; // 22 |
Number
方法1 | const age = Number("22"); // 22 |
1 | // 布爾值 |
轉換規則如下:
直觀上為 “空” 的值(如 0
、空字符串、null
、undefined
和 NaN
)將變為 false
。
其他值變成 true
。
1 | Boolean(1); // true |
常見運算符如加法 +
、減法 -
、乘法 *
和除法 /
,舉一個例子,來介紹一些概念:
1 | let sum = 1 + 2; |
其中:
加法運算 1 + 2
中, 1
和 2
為 2 個運算元,左運算元 1
和右運算元 2
,即「運算元就是運算符作用的對象。」
1 + 2
運算式中包含 2 個運算元,因此也稱該運算式中的加號 +
為 「二元運算符。」
在 +18
中的加號 +
對應只有一個運算元,則它是 「一元運算符」 。
1 | let msg = "hello " + "leo"; // "hello leo" |
運算符的優先級決定了表達式中運算執行的先後順序,優先級高的運算符最先被執行。下面的表將所有運算符按照優先級的不同從高(20)到低(1)排列。
優先級 | 運算類型 | 關聯性 | 運算符 |
---|---|---|---|
20 | 圓括號 | n/a(不相關) | ( … ) |
19 | 成員訪問 | 從左到右 | … . … |
需計算的成員訪問 | 從左到右 | … [ … ] | |
new (帶參數列表) | n/a | new … ( … ) | |
函數調用 | 從左到右 | … ( … ) | |
可選鏈(Optional chaining) | 從左到右 | ?. | |
18 | new (無參數列表) | 從右到左 | new … |
17 | 後置遞增 (運算符在後) | n/a | |
… ++ | |||
後置遞減 (運算符在後) | … – | ||
16 | 邏輯非 | 從右到左 | ! … |
按位非 | ~ … | ||
一元加法 | + … | ||
一元減法 | - … | ||
前置遞增 | ++ … | ||
前置遞減 | – … | ||
typeof | typeof … | ||
void | void … | ||
delete | delete … | ||
await | await … | ||
15 | 冪 | 從右到左 | … ** … |
14 | 乘法 | 從左到右 | |
… * … | |||
除法 | … / … | ||
取模 | … % … | ||
13 | 加法 | 從左到右 | |
… + … | |||
減法 | … - … | ||
12 | 按位左移 | 從左到右 | … << … |
按位右移 | … >> … | ||
無符號右移 | … >>> … | ||
11 | 小於 | 從左到右 | … < … |
小於等於 | … <= … | ||
大於 | … > … | ||
大於等於 | … >= … | ||
in | … in … | ||
instanceof | … instanceof … | ||
10 | 等號 | 從左到右 | |
… == … | |||
非等號 | … != … | ||
全等號 | … === … | ||
非全等號 | … !== … | ||
9 | 按位與 | 從左到右 | … & … |
8 | 按位異或 | 從左到右 | … ^ … |
7 | 按位或 | 從左到右 | … |
6 | 邏輯與 | 從左到右 | … && … |
5 | 邏輯或 | 從左到右 | … |
4 | 條件運算符 | 從右到左 | … ? … : … |
3 | 賦值 | 從右到左 | … = … |
… += … | |||
… -= … | |||
… *= … | |||
… /= … | |||
… %= … | |||
… <<= … | |||
… >>= … | |||
… >>>= … | |||
… &= … | |||
… ^= … | |||
… | |||
2 | yield | 從右到左 | yield … |
yield* | yield* … | ||
1 | 展開運算符 | n/a | … … |
0 | 逗號 | 從左到右 | … , … |
1 | 3 > 2 && 2 > 1 |
在 JS 中的值的比較與數學很類型:
大於 / 小於 / 大於等於 / 小於等於:a>b
/ a<b
/ a>=b
/ a<=b
;
判斷相等:
1 | // 使用 ==,非嚴格等於,不關心值類型 |
(圖片來自:《MDN JavaScript 中的相等性判斷》)
!=
/ !==
。另外 ES6 新增 Object.is 方法判斷兩個值是否相同,語法如下:
1 | Object.is(value1, value2); |
以下任意項成立則兩個值相同:
兩個值都是 undefined
兩個值都是 null
兩個值都是 true
或者都是 false
兩個值是由相同個數的字符按照相同的順序組成的字符串
兩個值指向同一個對象
兩個值都是數字並且
都是正零 +0
都是負零 -0
都是 NaN
都是除零和 NaN
外的其它同一個數字 使用示例:
1 | Object.is('foo', 'foo'); // true |
兼容性 Polyfill 處理:
1 | if (!Object.is) { |
對於相等性判斷比較簡單:
1 | null == undefined; // true |
對於其他比較,它們會先轉換位數字:null
轉換為 0
, undefied
轉換為 NaN
。
1 | null > 0; // false 1 |
需要注意:null == 0; // false
這裏是因為:undefined
和 null
在相等性檢查 ==
中「不會進行任何的類型轉換」,它們有自己獨立的比較規則,所以除了它們之間互等外,不會等於任何其他的值。
1 | undefined > 0; // false 1 |
第 1、2 行都返回 false
是因為 undefined
在比較中被轉換為了 NaN
,而 NaN
是一個特殊的數值型值,它與任何值進行比較都會返回 false
。第 3 行返回 false
是因為這是一個相等性檢查,而 undefined
只與 null
相等,不會與其他值相等。
顯示一個警告對話框,上面顯示有指定的文本內容以及一個 “確定” 按鈕。「注意:彈出模態框,並暫停腳本,直到用户點擊 “確定” 按鈕。」
1 | // 語法 |
message
是要顯示在對話框中的文本字符串,如果傳入其他類型的值, 會轉換成字符串。
顯示一個對話框,對話框中包含一條文字信息,用來提示用户輸入文字。「注意:彈出模態框,並暫停腳本,直到用户點擊 “確定” 按鈕。」當點擊確定返回文本,點擊取消或按下 Esc 鍵返回 null
。語法如下:
1 | let result = window.prompt(text, value); |
result
用來存儲用户輸入文字的字符串,或者是 null。
text
用來提示用户輸入文字的字符串,如果沒有任何提示內容,該參數可以省略不寫。
value
文本輸入框中的默認值,該參數也可以省略不寫。不過在 Internet Explorer 7 和 8 中,省略該參數會導致輸入框中顯示默認值 “undefined”。
Window.confirm()
方法顯示一個具有一個可選消息和兩個按鈕 (確定和取消) 的模態對話框。「注意:彈出模態框,並暫停腳本,直到用户點擊 “確定” 按鈕。」語法如下:
1 | let result = window.confirm(message); |
message 是要在對話框中顯示的可選字符串。
result 是一個布爾值,表示是選擇確定還是取消 (true 表示 OK)。
當 if 語句當條件表達式,會將表達式轉換為布爾值,當為 truthy 時執行裏面代碼。轉換規則如:
數字 0
、空字符串 ""
、null
、undefined
和 NaN
都會被轉換成 false
。因為他們被稱為 “falsy” 值。
其他值被轉換為 true
,所以它們被稱為 “truthy”。
「條件(三元)運算符」是 JavaScript 僅有的使用三個操作數的運算符。一個條件後面會跟一個問號(?),如果條件為 truthy ,則問號後面的表達式 A 將會執行;表達式 A 後面跟着一個冒號(:),如果條件為 falsy ,則冒號後面的表達式 B 將會執行。本運算符經常作為 [if](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/if...else)
語句的簡捷形式來使用。語法:
1 | condition ? exprIfTrue : exprIfFalse |
condition 計算結果用作條件的表達式。
exprIfTrue 如果表達式 condition 的計算結果是 truthy(它和 true 相等或者可以轉換成 true ),那麼表達式 exprIfTrue 將會被求值。
exprIfFalse 如果表達式 condition 的計算結果是 falsy(它可以轉換成 false ),那麼表達式 exprIfFalse 將會被執行。示例:
1 | let getUser = function(name){ |
詳細可以閲讀《MDN 邏輯運算符》 。
邏輯運算符如下表所示 (其中_expr_
可能是任何一種類型, 不一定是布爾值):
運算符 | 語法 | 説明 |
---|---|---|
邏輯與,AND(&& ) | expr1 && expr2 | 若 expr**1** 可轉換為 true ,則返回 expr**2** ;否則,返回 expr**1** 。 |
邏輯或,OR(` | `) | |
邏輯非,NOT(! )!expr | 若 expr 可轉換為 true ,則返回 false ;否則,返回 true 。 |
如果一個值可以被轉換為 true
,那麼這個值就是所謂的 truthy,如果可以被轉換為 false
,那麼這個值就是所謂的 falsy。會被轉換為 false
的表達式有:
null
;
NaN
;
0
;
空字符串(""
or ''
or ````);
undefined
。儘管 &&
和 ||
運算符能夠使用非布爾值的操作數, 但它們依然可以被看作是布爾操作符,因為它們的返回值總是能夠被轉換為布爾值。如果要顯式地將它們的返回值(或者表達式)轉換為布爾值,請使用雙重非運算符(即!!
)或者 Boolean 構造函數。JavaScript 裏有三個邏輯運算符:||
(或),&&
(與),!
(非)。
1 | a1 = true && true // t && t 返回 true |
1 | o1 = true || true // t || t 返回 true |
1 | n1 = !true // !t 返回 false |
1 | n1 = !!true // !!truthy 返回 true |
1 | condi1 && confi2 |
1 | condi1 || condi2 |
由於邏輯表達式的運算順序是從左到右,也可以用以下規則進行 “短路” 計算:
(some falsy expression) && (_expr)_
短路計算的結果為假。
(some truthy expression) || _(expr)_
短路計算的結果為真。短路意味着上述表達式中的 expr 部分「不會被執行」,因此 expr 的任何副作用都不會生效(舉個例子,如果 expr 是一次函數調用,這次調用就不會發生)。造成這種現象的原因是,整個表達式的值在第一個操作數被計算後已經確定了。看一個例子:
1 | function A(){ console.log('called A'); return false; } |
與運算 &&
的優先級比或運算 ||
要高。所以代碼 a && b || c && d
完全跟 &&
表達式加了括號一樣:(a && b) || (c && d)
。
詳細可以閲讀《MDN while》 。「while 語句」可以在某個條件表達式為真的前提下,循環執行指定的一段代碼,直到那個表達式不為真時結束循環。如:
1 | var n = 0; |
當循環體為單行時,可以不寫大括號:
1 | let i = 3; |
詳細可以閲讀《MDN do…while》 。do...while
語句創建一個執行指定語句的循環,直到condition
值為 false。在執行statement
後檢測condition
,所以指定的statement
至少執行一次。如:
1 | var result = ''; |
詳細可以閲讀《MDN for》 。for
語句用於創建一個循環,它包含了三個可選的表達式,這三個表達式被包圍在圓括號之中,使用分號分隔,後跟一個用於在循環中執行的語句(通常是一個塊語句)。語法如:
1 | for (begin; condition; step) { |
示例:
1 | for (let i = 0; i < 3; i++) { |
描述:
begin | i = 0 | 進入循環時執行一次。 |
---|---|---|
condition | i < 3 | 在每次循環迭代之前檢查,如果為 false,停止循環。 |
body(循環體) | alert(i) | 條件為真時,重複運行。 |
step | i++ | 在每次循環體迭代後執行。 |
for
語句頭部圓括號中的所有三個表達式都是可選的。
1 | var i = 0; |
1 | for (var i = 0;; i++) { |
1 | var i = 0; |
詳細可以閲讀《MDN break》 。break 語句中止當前循環,switch
語句或label
語句,並把程序控制流轉到緊接着被中止語句後面的語句。在 while 語句中:
1 | function testBreak(x) { |
另外,也可以為代碼塊做標記,並在 break 中指定要跳過的代碼塊語句的 label:
1 | outer_block:{ |
需要注意的是:break 語句需要內嵌在它所應用的標籤或代碼塊中,否則報錯:
1 | block_1:{ |
continue 聲明終止當前循環或標記循環的當前迭代中的語句執行,並在下一次迭代時繼續執行循環。與 break
語句的區別在於, continue 並不會終止循環的迭代,而是:
在 while
循環中,控制流跳轉回條件判斷;
在 for
循環中,控制流跳轉到更新語句。注意:continue 也必須在對應循環內部,否則報錯。
1 | i = 0; |
帶 label:
1 | var i = 0, |
「禁止 「break/continue
」 在 ‘?’ 的右邊:」
1 | (i > 5) ? console.log(i) : continue; // continue 不允許在這個位置 |
這樣會提示語法錯誤。請注意非表達式的語法結構不能與三元運算符 ?
一起使用。特別是 break/continue
這樣的指令是不允許這樣使用的。
三種循環:
while
—— 每次迭代之前都要檢查條件。
do..while
—— 每次迭代後都要檢查條件。
for (;;)
—— 每次迭代之前都要檢查條件,可以使用其他設置。通常使用 while(true)
來構造 “無限” 循環。這樣的循環和其他循環一樣,都可以通過 break
指令來終止。如果我們不想在當前迭代中做任何事,並且想要轉移至下一次迭代,那麼可以使用 continue
指令。break/continue
支持循環前的標籤。標籤是 break/continue
跳出嵌套循環以轉到外部的唯一方法。
switch
語句用來將表達式的值與 case 語句匹配,並執行與情況對應的語句。switch
語句可以替代多個 if 判斷,為多個分支選擇的情況提供一個更具描述性的方式。
switch
語句至少包含一個 case
代碼塊和一個可選的 default
代碼塊:
1 | switch(expression) { |
當 expression
表達式的值與 value1
匹配時,則執行其中代碼塊。如果沒有 case
子句匹配,則會選擇 default
子句執行,若連 default
子句都沒有,則直接執行到 switch
結束。
所謂 case 分組,就是與多個 case 分支共享同一段代碼,如下面例子中 case 1
和 case 2
:
1 | let a = 2; |
expression
表達式的值與 case
值的比較是嚴格相等:」1 | function f(n){ |
break
,程序將不經過任何檢查就會繼續執行下一個 case
:」1 | let a = 2 + 2; |
default
放在 case
之上不影響匹配」1 | function f(n){ |
switch
語句中存在 let
/ const
重複聲明問題:」1 | // 以下定義會報錯 |
這是由於兩個 let
處於同一個塊級作用域,所以它們被認為是同一變量名的重複聲明。解決方式,只需要將 case
語句包裝在括號內即可解決:
1 | function f(n){ |
函數可以讓一段代碼被多次調用,避免重複代碼。如之前學習到的一些內置函數:alert(msg)
/ prompt(msg, default)
/ confirm(quesyion)
等。
定義函數有兩種方式:「函數聲明」和「函數表達式」。
如定義一個簡單 getUser
函數:
1 | function getUser(name){ |
通過函數聲明來定義函數時,需要由以下幾部分組成:
函數名稱 - getUser
;
函數參數列表 - name
;
函數的 JS 執行語句 - return 'hello ' + name
。
類似聲明變量,還是以 getUser
為例:
1 | let getUser = function(name){ |
另外,函數表達式也可以提供函數名,並用於函數內部指代函數本身:
1 | let fun = function f(n){ |
當定義一個函數後,它並不會自動執行,而是需要使用函數名稱進行調用,如上面例子:
1 | fun(3); // 3 |
「只要注意:」使用 「函數表達式」 定義函數時,調用函數的方法必須寫在定義之後,否則報錯:
1 | console.log(fun()); // Uncaught ReferenceError: fun is not defined |
而使用 「函數聲明」 則不會出現該問題:
1 | console.log(fun()); // 1 |
原因就是:函數提升僅適用於函數聲明,而不適用於函數表達式。
在函數中,可以使用局部變量和外部變量。
函數中聲明的變量只能在該函數內可見。
1 | let fun = function(){ |
函數內可以使用外部變量,並且可以修改外部變量的值。
1 | let name = 'leo'; |
當函數內也有與外部變量名稱相同的變量,會忽略外部變量:
1 | let name = 'leo'; |
從 ECMAScript 6 開始,有兩個新的類型的參數:默認參數,剩餘參數。
若函數沒有傳入參數,則參數默認值為undefined
,通常設置參數默認值是這樣做的:
1 | // ES6 之前,沒有設置默認值 |
可以將參數中不確定數量的參數表示成數組,如下:
1 | function f (a, ...b){ |
既然講到參數,那就不能少了 arguments 對象。
函數的實際參數會被保存在一個「類似數組的 arguments 對象」中。在函數內,我們可以使用 arguments 對象獲取函數的所有參數:
1 | let fun = function(){ |
以一個實際示例介紹,實現將任意數量參數連接成一個字符串,並輸出的函數:
1 | let argumentConcat = function(separator){ |
在函數任意位置,指定 return
指令來停止函數的執行,並返回函數指定的返回值。
1 | let sum = function(a, b){ |
默認空值的 return
或沒有 return
的函數返回值為 undefined
。
函數表達式是一種函數定義方式,在前面章節中已經介紹到了,這個部分將着重介紹 「函數表達式」 和 「函數聲明」 的區別:
1 | // 函數表達式 |
函數表達式會在代碼執行到達時被創建,並且僅從那一刻可用。而函數聲明被定義之前,它就可以被調用。
1 | // 函數表達式 |
建議優先考慮函數聲明語法,它能夠為組織代碼提供更多靈活性,因為我們可以在聲明函數前調用該函數。
「本章節簡單介紹箭頭函數基礎知識,後面章節會完整介紹。」
「函數箭頭表達式」是 ES6 新增的函數表達式的語法,也叫「胖箭頭函數」,變化:更簡潔的函數和this
。
1 | // 有1個參數 |
箭頭函數不存在this
;
箭頭函數不能當做「構造函數」,即不能用new
實例化;
箭頭函數不存在arguments
對象,即不能使用,可以使用rest
參數代替;
箭頭函數不能使用yield
命令,即不能用作 Generator 函數。一個簡單的例子:
1 | function Person(){ |
本文作為《初中級前端 JavaScript 自測清單》第一部分,介紹的內容以常用基礎知識為主,並在學習資料中,將知識點結合實際開發中遇到的場景進行展開介紹。希望能幫助大家自測自己的 JavaScript 水平並查缺補漏,温故知新。
如果你喜歡這篇文章,可以關注原作者的公眾號 前端自習課
]]>為了解決這個問題,採用自動化部署是一個絕佳的選擇。現如今提供自動化部署的平台越來越多。像Travis CI,還有最近發佈的Github Actions。我們只需要把Hexo的目錄推送到GitHub去,相關平台監測到對應的Repositories發佈變化,就會自動去執行編譯,並把編譯好的內容發佈到博客Repositories去。
Travis CI
有提供免費版和企業版,以下的教程是在免費版上操作。
免費版不支持私人倉庫(Private Repositories)部署
點擊Sign Up
會自動跳到Github上去授權登錄。只需授權登錄就行,完成後會進入操作界面。
接下來要選擇哪一個倉庫Repositories需要被監測。
點擊Activate all repositories using GitHub Apps
然後點擊Only select repositories
,選擇你想要的倉庫(hexo目錄的倉庫),點擊Approve & Install
選擇你想要的倉庫,點擊settings
在settings
界面,找到Environment Variables
。為了保護我們的隱私,一些重要的資料寫在這裏,然後通過${xxx}
進行引用。
在hexo根目錄創建一個travis 配置檔案。名稱一定要是.travis.yml
把以下內容複製到裏面去
1 | language: node_js |
cache
是緩存node_modules文件夾,這樣就可以不用每次部署都要去下載各種依賴,只有存在變更時,才去下載。
branches
是指需要監測的branch
,看你的hexo目錄放在哪裏,需要自己去修改
GIT_NAME
,GIT_EMAIL
,GH_TOKEN
這些是引入你在Environment Variables
所配置的內容。
1 | - git push --force --quiet "https://${GH_TOKEN}@${GH_REF}" master:master |
第一個是推送到GitHub去,例如jerryc127.github.io
倉庫。GH_TOKEN
需要自己去GitHub setting裏申請GH_REF
是指倉庫的git地址,例如:github.com/jerryc127/jerryc127.github.io.git
第二個是推送到Coding.me倉庫去。
CDT_TOKEN
需要到倉庫的項目設置,然後在開發者選項
的項目令牌
申請。
CDT_TOKEN
的內容為用户名:密碼
如果是推送到gh-pages
,需要把master:master
更改為master:gh-pages
現在你只要把Hexo的目錄推送到GitHub去,Travis會自動監測到變化,然後進行編譯。(編譯完成後會收到郵件通知是否編譯成功)
GitHub Actions
是GitHub於2019年底推出的自動化流程工具,它的功能很強大。而我們只需要用到它的構建。由於是GitHub自家的工具,我們無需再額外的註冊賬號,GitHub Actions也集成在了GitHub界面上,我們只需點擊Actions
進行創建。
在Hexo目錄的倉庫,點擊Actions Tab
,然後點擊Set up a workflow yourself
把以下代碼複製到代碼框去
1 | name: 自動部署 Hexo |
on
是要被監測的branch
,等同Travis-ci的branches
其它的都跟Travis-ci的配置差不多,這裏就不再仔細介紹。
為了不讓重要的資料暴露在公眾,需要把這些資料設置到secrets
裏,然後通過$引用。
在Settings
裏的Secrets
裏設置
現在你只要把Hexo的目錄推送到GitHub去,GitHub Actions會自動監測到變化,然後進行編譯。
從 Windows Terminal v1.5.10271.0 開始,官方已經支持添加鼠標右鍵菜單,而無須自己添加。
Windows上有很多命令行程序,例如CMD和PowerShell。微軟在Build 2019上推出了一款面向Windows10的命令行程序,這款程序集合了Windows上的PowerShell、CMD以及Windows Subsystem for Linux於一身,解決了不少惹人吐槽的毛病,甚至被稱為Windows下命令體驗的救世主。而我早在Preview版發佈時,就已經下載使用,現在也是我主要使用的命令行工具。然而畢竟現在還是體驗版的關係,所以並沒有集成在右鍵菜單上。在搜索了許久之後,終於在Github上找到了安裝方法。
Windows Terminal 現在還是 Preview 狀態,我們可以在 Microsoft Store 上下載安裝。當然有能力的人,可以下載Github的代碼自己編譯。
Windows Terminal (Preview) - Microsoft Store
下面的兩個變量後面的操作需要使用到。所以,先測試下是否正常。
1 | echo %USERPROFILE% |
如果有報錯,接下來的操作,請把對應的部分進行替換。
%USERPROFILE%
替換成 C:\Users\[userName]
%LOCALAPPDATA%
替換成 C:\Users\[userName]\AppData\Local
注意 [userName]
為自己的用户名
從以下地址下載圖標
圖標ico下載 , 打開網址,鼠標右鍵保存到電腦。
打開命令行,輸入
1 | mkdir "%USERPROFILE%\AppData\Local\terminal" |
這個命令是創建一個terminal
文件夾,把下載的圖標ico複製到這個文件夾。
創建一個txt文檔,並把檔後綴改為reg
。文檔的名字可自己創建,後綴名不可以錯。右鍵菜單出現Windows Terminal
有兩種方法。一種是按shift
+ 右鍵
,另一種是直接右鍵
。
shift
+ 右鍵
把下面的內容複製到reg去
1 | Windows Registry Editor Version 5.00 |
注意:請把[user_name]
改成自己電腦的用户名
把下面的內容複製到reg去
1 | Windows Registry Editor Version 5.00 |
注意:請把[user_name]
改成自己電腦的用户名
Windows Terminal
的profile.json
打開profile.json
把startingDirectory
改為null
,沒有的自己創建一個。
照著上面的方法操作,相信右鍵菜單已經出現Windows Terminal
的入口了。
本文轉載自【总结】调研实现高性能动画
本文是調研如何實現高性能動畫, 提升用户體驗的總結, 文章內容來源於對看過的相關技術文章的總結, 相關技術文章已列到文章末尾, 如有遺漏, 敬請諒解.
快速響應和高度交互的頁面往往能夠吸引大量的用户群體. 相反, 如果頁面存在性能低下的動畫, 動畫不流暢, 動畫過程中頁面閃爍等等, 如此粗糙的交互體驗必然喪失用户量.
對於大多數的設備而言, 屏幕以 60 次每秒的頻率刷新, 即60HZ
. 如果一個動畫中的某些幀超過了這個時間, 就會導致瀏覽器的刷新頻率跟不上設備的刷新頻率(跳幀現象), 出現頁面閃爍. 因此, 高性能的動畫都應該保持在60fps
左右.
接下來我們看幾種動畫的實現方式.
setTimeout
或者setInterval
實現的動畫本文轉自4ark
這個問題已經是老生常談了,更是經常被作為面試的壓軸題出現,網上也有很多文章,但最近閒的無聊,然後就自己做了一篇筆記,感覺比之前理解更透徹了。
這個問題已經是老生常談了,更是經常被作為面試的壓軸題出現,網上也有很多文章,但最近閒的無聊,然後就自己做了一篇筆記,感覺比之前理解更透徹了。
這篇筆記是我這兩天看了數十篇文章總結出來的,所以相對全面一點,但由於我是做前端的,所以會比較重點分析瀏覽器渲染頁面那一部分,至於其他部分我會羅列出關鍵詞,感興趣的可以自行查閲,
注意: 本文的步驟是建立在,請求的是一個簡單的 HTTP 請求,沒有 HTTPS、HTTP2、最簡單的 DNS、沒有代理、並且服務器沒有任何問題的基礎上,儘管這是不切實際的。
地址解析:
首先判斷你輸入的是一個合法的 URL 還是一個待搜索的關鍵詞,並且根據你輸入的內容進行自動完成、字符編碼等操作。
HSTS
由於安全隱患,會使用 HSTS 強制客户端使用 HTTPS 訪問頁面。詳見:你所不知道的 HSTS。
其他操作
瀏覽器還會進行一些額外的操作,比如安全檢查、訪問限制(之前國產瀏覽器限制 996.icu)。
檢查緩存
基本步驟
瀏覽器會先檢查是否在緩存中,沒有則調用系統庫函數進行查詢。
操作系統也有自己的 DNS 緩存,但在這之前,會向檢查域名是否存在本地的 Hosts 文件裏,沒有則向 DNS 服務器發送查詢請求。
路由器也有自己的緩存。
ISP DNS 就是在客户端電腦上設置的首選 DNS 服務器,它們在大多數情況下都會有緩存。
在前面所有步驟沒有緩存的情況下,本地 DNS 服務器會將請求轉發到互聯網上的根域,下面這個圖很好的詮釋了整個流程:
根域名服務器:維基百科
需要注意的點
TCP/IP 分為四層,在發送數據時,每層都要對數據進行封裝:
在前面的步驟我們已經得到服務器的 IP 地址,瀏覽器會開始構造一個 HTTP 報文,其中包括:
其中需要注意的點:
傳輸層會發起一條到達服務器的 TCP 連接,為了方便傳輸,會對數據進行分割(以報文段為單位),並標記編號,方便服務器接受時能夠準確地還原報文信息。
在建立連接前,會先進行 TCP 三次握手。
關於 TCP/IP 三次握手,網上已經有很多段子和圖片生動地描述了。
相關知識點:
- SYN 泛洪攻擊
將數據段打包,並加入源及目標的 IP 地址,並且負責尋找傳輸路線。
判斷目標地址是否與當前地址處於同一網絡中,是的話直接根據 Mac 地址發送,否則使用路由表查找下一跳地址,以及使用 ARP 協議查詢它的 Mac 地址。
注意:在 OSI 參考模型中 ARP 協議位於鏈路層,但在 TCP/IP 中,它位於網絡層。
以太網協議
根據以太網協議將數據分為以 “幀” 為單位的數據包,每一幀分為兩個部分:
Mac 地址
以太網規定了連入網絡的所有設備都必須具備 “網卡” 接口,數據包都是從一塊網卡傳遞到另一塊網卡,網卡的地址就是 Mac 地址。每一個 Mac 地址都是獨一無二的,具備了一對一的能力。
廣播
發送數據的方法很原始,直接把數據通過 ARP 協議,向本網絡的所有機器發送,接收方根據標頭信息與自身 Mac 地址比較,一致就接受,否則丟棄。
注意:接收方迴應是單播。
相關知識點:
- ARP 攻擊
服務器接受請求
接受過程就是把以上步驟逆轉過來,參見上圖。
大致流程
HTTPD
最常見的 HTTPD 有 Linux 上常用的 Apache 和 Nginx,以及 Windows 上的 IIS。
它會監聽得到的請求,然後開啟一個子進程去處理這個請求。
處理請求
接受 TCP 報文後,會對連接進行處理,對 HTTP 協議進行解析(請求方法、域名、路徑等),並且進行一些驗證:
重定向
假如服務器配置了 HTTP 重定向,就會返回一個 301
永久重定向響應,瀏覽器就會根據響應,重新發送 HTTP 請求(重新執行上面的過程)。
關於更多:詳見這篇文章
URL 重寫
然後會查看 URL 重寫規則,如果請求的文件是真實存在的,比如圖片、html、css、js 文件等,則會直接把這個文件返回。
否則服務器會按照規則把請求重寫到 一個 REST 風格的 URL 上。
然後根據動態語言的腳本,來決定調用什麼類型的動態文件解釋器來處理這個請求。
以 PHP 語言的 MVC 框架舉例,它首先會初始化一些環境的參數,根據 URL 由上到下地去匹配路由,然後讓路由所定義的方法去處理請求。
瀏覽器接收到來自服務器的響應資源後,會對資源進行分析。
首先查看 Response header,根據不同狀態碼做不同的事(比如上面提到的重定向)。
如果響應資源進行了壓縮(比如 gzip),還需要進行解壓。
然後,對響應資源做緩存。
接下來,根據響應資源裏的 MIME 類型去解析響應內容(比如 HTML、Image 各有不同的解析方式)。
瀏覽器內核
不同的瀏覽器內核,渲染過程也不完全相同,但大致流程都差不多。
基本流程
首先要知道瀏覽器解析是從上往下一行一行地解析的。
解析的過程可以分為四個步驟:
1. 解碼(encoding)
傳輸回來的其實都是一些二進制字節數據,瀏覽器需要根據文件指定編碼(例如 UTF-8)轉換成字符串,也就是 HTML 代碼。
2. 預解析(pre-parsing)
預解析做的事情是提前加載資源,減少處理時間,它會識別一些會請求資源的屬性,比如img
標籤的src
屬性,並將這個請求加到請求隊列中。
3. 符號化(Tokenization)
符號化是詞法分析的過程,將輸入解析成符號,HTML 符號包括,開始標籤、結束標籤、屬性名和屬性值。
它通過一個狀態機去識別符號的狀態,比如遇到<
,>
狀態都會產生變化。
4. 構建樹(tree construction)
注意:符號化和構建樹是並行操作的,也就是説只要解析到一個開始標籤,就會創建一個 DOM 節點。
在上一步符號化中,解析器獲得這些標記,然後以合適的方法創建DOM
對象並把這些符號插入到DOM
對象中。
1 | <html> |
瀏覽器容錯進制
你從來沒有在瀏覽器看過類似” 語法無效” 的錯誤,這是因為瀏覽器去糾正錯誤的語法,然後繼續工作。
事件
當整個解析的過程完成以後,瀏覽器會通過DOMContentLoaded
事件來通知DOM
解析完成。
一旦瀏覽器下載了 CSS,CSS 解析器就會處理它遇到的任何 CSS,根據語法規範解析出所有的 CSS 並進行標記化,然後我們得到一個規則表。
CSS 匹配規則
在匹配一個節點對應的 CSS 規則時,是按照從右到左的順序的,例如:div p { font-size :14px }
會先尋找所有的p
標籤然後判斷它的父元素是否為div
。
所以我們寫 CSS 時,儘量用 id 和 class,千萬不要過度層疊。
其實這就是一個 DOM 樹和 CSS 規則樹合併的過程。
注意:渲染樹會忽略那些不需要渲染的節點,比如設置了
display:none
的節點。
計算
通過計算讓任何尺寸值都減少到三個可能之一:auto
、百分比、px,比如把rem
轉化為px
。
級聯
瀏覽器需要一種方法來確定哪些樣式才真正需要應用到對應元素,所以它使用一個叫做specificity
的公式,這個公式會通過:
!important
然後得出一個權重值,取最高的那個。
渲染阻塞
當遇到一個script
標籤時,DOM 構建會被暫停,直至腳本完成執行,然後繼續構建 DOM 樹。
但如果 JS 依賴 CSS 樣式,而它還沒有被下載和構建時,瀏覽器就會延遲腳本執行,直至 CSS Rules 被構建。
所有我們知道:
為了避免這種情況,應該以下原則:
</body>
前另外,如果要改變阻塞模式,可以使用 defer 與 async,詳見:這篇文章
確定渲染樹種所有節點的幾何屬性,比如:位置、大小等等,最後輸入一個盒子模型,它能精準地捕獲到每個元素在屏幕內的準確位置與大小。
然後遍歷渲染樹,調用渲染器的 paint() 方法在屏幕上顯示其內容。
把以上繪製的所有圖片合併,最終輸出一張圖片。
迴流 (reflow)
當瀏覽器發現某個部分發現變化影響了佈局時,需要倒回去重新渲染,會從html
標籤開始遞歸往下,重新計算位置和大小。
reflow 基本是無法避免的,因為當你滑動一下鼠標、resize 窗口,頁面就會產生變化。
重繪 (repaint)
改變了某個元素的背景色、文字顏色等等不會影響周圍元素的位置變化時,就會發生重繪。
每次重繪後,瀏覽器還需要合併渲染層並輸出到屏幕上。
迴流的成本要比重繪高很多,所以我們應該儘量避免產生迴流。
比如:
display:none
會觸發迴流,而 visibility:hidden
只會觸發重繪。大致流程
可以分為三個階段:
JS 腳本加載完畢後,會首先進入語法分析階段,它首先會分析代碼塊的語法是否正確,不正確則拋出 “語法錯誤”,停止執行。
幾個步驟:
var a = 2
,,分成var
、a
、=
、2
這樣的詞法單元。JS 有三種運行環境:
每進入一個不同的運行環境都會創建一個對應的執行上下文,根據不同的上下文環境,形成一個函數調用棧,棧底永遠是全局執行上下文,棧頂則永遠是當前執行上下文。
創建執行上下文
創建執行上下文的過程中,主要做了以下三件事:
JS 線程
雖然 JS 是單線程的,但實際上參與工作的線程一共有四個:
其中三個只是協助,只有 JS 引擎線程是真正執行的
setInterval
和setTimeout
,用來計時,計時完畢後,則把定時器的處理函數推進事件隊列中,等待 JS 引擎線程。注:瀏覽器對同一域名的併發連接數是有限的,通常為 6 個。
宏任務
分為:
微任務
微任務是 ES6 和 Node 環境下的,主要 API 有:Promise
,process.nextTick
。
微任務的執行在宏任務的同步任務之後,在異步任務之前。
代碼例子
1 | console.log('1'); // 宏任務 同步 |
以上代碼輸出順序為:1,3,5,4,2
爲了方便集中管理和更新,Butterfly文檔已經遷移至Butterfly網站。
原docs.jerryc.me
頁面也將會刪除
同時,Valine的相關評論已經遷移到到Butterfly網站
1 | shutdown [/i | /l | /s | /sg | /r | /g | /a | /p | /h | /e | /o] [/hybrid] [/soft] [/fw] [/f][/m \\computer][/t xxx][/d [p|u:]xx:yy [/c "comment"]] |
也可以寫成
1 | shutdown [-i | -l | -s | -sg | -r | -g | -a | -p | -h | -e | -o] [-hybrid] [-soft] [-fw] [-f] [-m \\computer][-t xxx][-d [p|u:]xx:yy [-c "comment"]] |
參數 | 描述 |
---|---|
No args | 顯示説明。與輸入 /? 意義相同。 |
/? | 顯示説明。與不輸入任何選項意義相同。 |
/i | 顯示圖形化使用者介面 (GUI)。 這必須是第一個選項。 |
/l | 登出。不能和 /m 或 /d 選項一起使用。 |
/s | 將電腦關機。 |
/sg | 將電腦關機。如有啟用自動重新啟動登入, 會在系統重新開機後自動登入並鎖定上一個互動使用者。 登入後,會重新啟動所有已註冊應用程式。 |
/r | 將電腦完全關機並重新開機。 |
/g | 將電腦完全關機並重新啟動。如有啟用自動重新啟動登入, 會在系統重新開機後自動登入並鎖定上一個互動使用者。 登入後,會重新啟動所有已註冊應用程式。 |
/a | 中止系統關機。 只有在逾時期間可以使用這個選項。 與 /fw 結合以清除任何擱置中的開機到韌體作業。 |
/p | 沒有逾時或警告就關閉本機電腦 能和 /d 與 /f 選項一起使用。 |
/h | 讓本機電腦休眠。 能和 /f 選項一起使用。 |
/hybrid | 執行電腦關機作業,並準備電腦以用於快速啟動。 必須搭配 /s 選項使用。 |
/fw | 與關機選項結合,讓下一次開機能進入 韌體使用者介面。 |
/e | 記錄電腦意外關機的理由。 |
/o | 移至 [進階開機選項] 功能表並重新啟動電腦。 必須搭配 /r 選項使用。 |
/m \computer | 指定目標電腦。 |
/t xxx | 將關機前的逾時期間設定為 xxx 秒。 有效的範圍是 0-315360000 (10 年),預設值為 30。 若逾時期間大於 0,則會隱含 /f參數。 |
/c “comment” | 為重新啟動或關機理由加上註解。 最多僅允許 512 個字元。 |
/f | 強制關閉執行中的應用程式,而不事先警告使用者。 為 /t 參數指定大於 0 的值時, 會隱含 /f 參數。 |
/d [p|u:]xx:yy | 提供重新啟動或關機的理由。 p 代表重新啟動或關機是已計劃的。 u 代表理由是由使用者所定義。 若未指定 p 或 u,則重新啟動或關機 是非計劃性。 xx 是主要的理由編號 (小於 256 的正整數)。 yy 是次要的理由編號 (小於 65536 的正整數)。 |
shutdown /s /t 10
指定10s後自動關機shutdown /l /t 10
指定10s後自動登出賬號shutdown /i
本機
右鍵鼠標並選擇管理。動作
下的建立基本工作
啟動程序
shutdown
或者點擊右邊的瀏覽,選擇shutdown.exe,會變成C:\Windows\System32\shutdown.exe
。新增引數填-s
執行
程序按win
+R
,打開執行
程序,直接輸入shutdown指令。
CMD
或者PowerShell
打開CMD
或者PowerShell
,然後直接輸入shutdown指令。
轉自 Kevin Yang
字符編碼的問題看似很小,經常被技術人員忽視,但是很容易導致一些莫名其妙的問題。這裏總結了一下字符編碼的一些普及性的知識,希望對大家有所幫助。
説到字符編碼,不得不説ASCII碼的簡史。計算機一開始發明的時候是用來解決數字計算的問題,後來人們發現,計算機還可以做更多的事,例如文本處理。但由於計算機只識“數”,因此人們必須告訴計算機哪個數字來代表哪個特定字符,例如65代表字母‘A’,66代表字母‘B’,以此類推。但是計算機之間字符-數字的對應關係必須得一致,否則就會造成同一段數字在不同計算機上顯示出來的字符不一樣。因此美國國家標準協會ANSI制定了一個標準,規定了常用字符的集合以及每個字符對應的編號,這就是ASCII字符集(Character Set),也稱ASCII碼。
當時的計算機普遍使用8比特字節作為最小的存儲和處理單元,加之當時用到的字符也很少,26個大小寫英文字母還有數字再加上其他常用符號,也不到100個,因此使用7個比特位就可以高效的存儲和處理ASCII碼,剩下最高位1比特被用作一些通訊系統的奇偶校驗。
注意,字節代表系統能夠處理的最小單位,不一定是8比特。只是現代計算機的事實標準就是用8比特來代表一個字節。在很多技術規格文獻中,為了避免產生歧義,更傾向於使用8位組(Octet)而不是字節(Byte)這個術語來強調8個比特的二進制流。下文中為了便於理解,我會延用大家熟悉的“字節”這個概念。
ASCII字符集由95個可打印字符(0x20-0x7E)和33個控制字符(0x00-0x19,0x7F)組成。可打印字符用於顯示在輸出設備上,例如熒屏或者打印紙上,控制字符用於向計算機發出一些特殊指令,例如0x07會讓計算機發出嗶的一聲,0x00通常用於指示字符串的結束,0x0D和0x0A用於指示打印機的打印針頭退到行首(回車)並移到下一行(換行)。
那時候的字符編解碼系統非常簡單,就是簡單的查表過程。例如將字符序列編碼為二進制流寫入存儲設備,只需要在ASCII字符集中依次找到字符對應的字節,然後直接將該字節寫入存儲設備即可。解碼二進制流的過程也是類似。
當計算機開始發展起來的時候,人們逐漸發現,ASCII字符集裏那可憐的128個字符已經不能再滿足他們的需求了。人們就在想,一個字節能夠表示的數字(編號)有256個,而ASCII字符只用到了0x00~0x7F,也就是佔用了前128個,後面128個數字不用白不用,因此很多人打起了後面這128個數字的主意。可是問題在於,很多人同時有這樣的想法,但是大家對於0x80-0xFF這後面的128個數字分別對應什麼樣的字符,卻有各自的想法。這就導致了當時銷往世界各地的機器上出現了大量各式各樣的OEM字符集。
下面這張表是IBM-PC機推出的其中一個OEM字符集,字符集的前128個字符和ASCII字符集的基本一致(為什麼説基本一致呢,是因為前32個控制字符在某些情況下會被IBM-PC機當作可打印字符解釋),後面128個字符空間加入了一些歐洲國家用到的重音字符,以及一些用於畫線條畫的字符。
事實上,大部分OEM字符集是兼容ASCII字符集的,也就是説,大家對於0x000x7F這個範圍的解釋基本是相同的,而對於後半部分0x800xFF的解釋卻不一定相同。甚至有時候同樣的字符在不同OEM字符集中對應的字節也是不同的。
不同的OEM字符集導致人們無法跨機器交流各種文檔。例如職員甲發了一封簡歷résumés給職員乙,結果職員乙看到的卻是r?sum?s,因為é字符在職員甲機器上的OEM字符集中對應的字節是0x82,而在職員乙的機器上,由於使用的OEM字符集不同,對0x82字節解碼後得到的字符卻是?。
上面我們提到的字符集都是基於單字節編碼,也就是説,一個字節翻譯成一個字符。這對於拉丁語系國家來説可能沒有什麼問題,因為他們通過擴展第8個比特,就可以得到256個字符了,足夠用了。但是對於亞洲國家來説,256個字符是遠遠不夠用的。因此這些國家的人為了用上電腦,又要保持和ASCII字符集的兼容,就發明了多字節編碼方式,相應的字符集就稱為多字節字符集。例如中國使用的就是雙字節字符集編碼(DBCS,Double Byte Character Set)。
對於單字節字符集來説,代碼頁中只需要有一張碼錶即可,上面記錄着256個數字代表的字符。程序只需要做簡單的查表操作就可以完成編解碼的過程。
代碼頁是字符集編碼的具體實現,你可以把他理解為一張“字符-字節”映射表,通過查表實現“字符-字節”的翻譯。下面會有更詳細的描述。
而對於多字節字符集,代碼頁中通常會有很多碼錶。那麼程序怎麼知道該使用哪張碼錶去解碼二進制流呢?答案是,根據第一個字節來選擇不同的碼錶進行解析。
例如目前最常用的中文字符集GB2312,涵蓋了所有簡體字符以及一部分其他字符;GBK(K代表擴展的意思)則在GB2312的基礎上加入了對繁體字符等其他非簡體字符(GB18030字符集不是雙字節字符集,我們在講Unicode的時候會提到)。這兩個字符集的字符都是使用1-2個字節來表示。Windows系統採用936代碼頁來實現對GBK字符集的編解碼。在解析字節流的時候,如果遇到字節的最高位是0的話,那麼就使用936代碼頁中的第1張碼錶進行解碼,這就和單字節字符集的編解碼方式一致了。
當字節的高位是1的時候,確切的説,當第一個字節位於0x81–0xFE之間時,根據第一個字節不同找到代碼頁中的相應的碼錶,例如當第一個字節是0x81,那麼對應936中的下面這張碼錶:
(關於936代碼頁中完整的碼錶信息,參見MSDN:http://msdn.microsoft.com/en-us/library/cc194913%28v=MSDN.10%29.aspx.)
按照936代碼頁的碼錶,當程序遇到連續字節流0x81 0x40的時候,就會解碼為“丂”字符。
不同ASCII衍生字符集的出現,讓文檔交流變得非常困難,因此各種組織都陸續進行了標準化流程。例如美國ANSI組織制定了ANSI標準字符編碼(注意,我們現在通常説到ANSI編碼,通常指的是平台的默認編碼,例如英文操作系統中是ISO-8859-1,中文系統是GBK),ISO組織制定的各種ISO標準字符編碼,還有各國也會制定一些國家標準字符集,例如中國的GBK,GB2312和GB18030。
操作系統在發佈的時候,通常會往機器裏預裝這些標準的字符集還有平台專用的字符集,這樣只要你的文檔是使用標準字符集編寫的,通用性就比較高了。例如你用GB2312字符集編寫的文檔,在中國大陸內的任何機器上都能正確顯示。同時,我們也可以在一台機器上閲讀多個國家不同語言的文檔了,前提是本機必須安裝該文檔使用的字符集。
雖然通過使用不同字符集,我們可以在一台機器上查閲不同語言的文檔,但是我們仍然無法解決一個問題:在一份文檔中顯示所有字符。為了解決這個問題,我們需要一個全人類達成共識的巨大的字符集,這就是Unicode字符集。
Unicode字符集涵蓋了目前人類使用的所有字符,併為每個字符進行統一編號,分配唯一的字符碼(Code Point)。Unicode字符集將所有字符按照使用上的頻繁度劃分為17個層面(Plane),每個層面上有216=65536個字符碼空間。
其中第0個層面BMP,基本涵蓋了當今世界用到的所有字符。其他的層面要麼是用來表示一些遠古時期的文字,要麼是留作擴展。我們平常用到的Unicode字符,一般都是位於BMP層面上的。目前Unicode字符集中尚有大量字符空間未使用。
在Unicode出現之前,所有的字符集都是和具體編碼方案綁定在一起的,都是直接將字符和最終字節流綁定死了,例如ASCII編碼系統規定使用7比特來編碼ASCII字符集;GB2312以及GBK字符集,限定了使用最多2個字節來編碼所有字符,並且規定了字節序。這樣的編碼系統通常用簡單的查表,也就是通過代碼頁就可以直接將字符映射為存儲設備上的字節流了。例如下面這個例子:
這種方式的缺點在於,字符和字節流之間耦合得太緊密了,從而限定了字符集的擴展能力。假設以後火星人入住地球了,要往現有字符集中加入火星文就變得很難甚至不可能了,而且很容易破壞現有的編碼規則。
因此Unicode在設計上考慮到了這一點,將字符集和字符編碼方案分離開。
也就是説,雖然每個字符在Unicode字符集中都能找到唯一確定的編號(字符碼,又稱Unicode碼),但是決定最終字節流的卻是具體的字符編碼。例如同樣是對Unicode字符“A”進行編碼,UTF-8字符編碼得到的字節流是0x41,而UTF-16(大端模式)得到的是0x00 0x41。
如果要我們來實現Unicode字符集中BMP字符的編碼方案,我們會怎麼實現?由於BMP層面上有216=65536個字符碼,因此我們只需要兩個字節就可以完全表示這所有的字符了。
舉個例子,“中”的Unicode字符碼是0x4E2D(01001110 00101101),那麼我們可以編碼為01001110 00101101(大端)或者00101101 01001110 (小端)。
UCS-2和UTF-16對於BMP層面的字符均是使用2個字節來表示,並且編碼得到的結果完全一致。不同之處在於,UCS-2最初設計的時候只考慮到BMP字符,因此使用固定2個字節長度,也就是説,他無法表示Unicode其他層面上的字符,而UTF-16為了解除這個限制,支持Unicode全字符集的編解碼,採用了變長編碼,最少使用2個字節,如果要編碼BMP以外的字符,則需要4個字節結對,這裏就不討論那麼遠,有興趣可以參考維基百科:UTF-16/UCS-2。
Windows從NT時代開始就採用了UTF-16編碼,很多流行的編程平台,例如.Net,Java,Qt還有Mac下的Cocoa等都是使用UTF-16作為基礎的字符編碼。例如代碼中的字符串,在內存中相應的字節流就是用UTF-16編碼過的。
UTF-8應該是目前應用最廣泛的一種Unicode編碼方案。由於UCS-2/UTF-16對於ASCII字符使用兩個字節進行編碼,存儲和處理效率相對低下,並且由於ASCII字符經過UTF-16編碼後得到的兩個字節,高字節始終是0x00,很多C語言的函數都將此字節視為字符串末尾從而導致無法正確解析文本。因此一開始推出的時候遭到很多西方國家的抵觸,大大影響了Unicode的推行。後來聰明的人們發明了UTF-8編碼,解決了這個問題。
UTF-8編碼方案採用1-4個字節來編碼字符,方法其實也非常簡單。
(上圖中的x代表Unicode碼的低8位,y代表高8位)
對於ASCII字符的編碼使用單字節,和ASCII編碼一摸一樣,這樣所有原先使用ASCII編解碼的文檔就可以直接轉到UTF-8編碼了。對於其他字符,則使用2-4個字節來表示,其中,首字節前置1的數目代表正確解析所需要的字節數,剩餘字節的高2位始終是10。例如首字節是1110yyyy,前置有3個1,説明正確解析總共需要3個字節,需要和後面2個以10開頭的字節結合才能正確解析得到字符。
關於UTF-8的更多信息,參考維基百科:UTF-8。
任何能夠將Unicode字符映射為字節流的編碼都屬於Unicode編碼。中國的GB18030編碼,覆蓋了Unicode所有的字符,因此也算是一種Unicode編碼。只不過他的編碼方式並不像UTF-8或者UTF-16一樣,將Unicode字符的編號通過一定的規則進行轉換,而只能通過查表的手段進行編碼。
關於GB18030的更多信息,參考:GB18030。
Unicode是兩個字節嗎?
Unicode只是定義了一個龐大的、全球通用的字符集,併為每個字符規定了唯一確定的編號,具體存儲為什麼樣的字節流,取決於字符編碼方案。推薦的Unicode編碼是UTF-16和UTF-8。
帶簽名的UTF-8指的是什麼意思?
帶簽名指的是字節流以BOM標記開始。很多軟件會“智能”的探測當前字節流使用的字符編碼,這種探測過程出於效率考慮,通常會提取字節流前面若干個字節,看看是否符合某些常見字符編碼的編碼規則。由於UTF-8和ASCII編碼對於純英文的編碼是一樣的,無法區分開來,因此通過在字節流最前面添加BOM標記可以告訴軟件,當前使用的是Unicode編碼,判別成功率就十分準確了。但是需要注意,不是所有軟件或者程序都能正確處理BOM標記,例如PHP就不會檢測BOM標記,直接把它當普通字節流解析了。因此如果你的PHP文件是採用帶BOM標記的UTF-8進行編碼的,那麼有可能會出現問題。
Unicode編碼和以前的字符集編碼有什麼區別?
早期字符編碼、字符集和代碼頁等概念都是表達同一個意思。例如GB2312字符集、GB2312編碼,936代碼頁,實際上説的是同個東西。但是對於Unicode則不同,Unicode字符集只是定義了字符的集合和唯一編號,Unicode編碼,則是對UTF-8、UCS-2/UTF-16等具體編碼方案的統稱而已,並不是具體的編碼方案。所以當需要用到字符編碼的時候,你可以寫gb2312,codepage936,utf-8,utf-16,但請不要寫unicode(看過別人在網頁的meta標籤裏頭寫charset=unicode,有感而發)。
亂碼指的是程序顯示出來的字符文本無法用任何語言去解讀。一般情況下會包含大量?。亂碼問題是所有計算機用户或多或少會遇到的問題。造成亂碼的原因就是因為使用了錯誤的字符編碼去解碼字節流,因此當我們在思考任何跟文本顯示有關的問題時,請時刻保持清醒:當前使用的字符編碼是什麼。只有這樣,我們才能正確分析和處理亂碼問題。
例如最常見的網頁亂碼問題。如果你是網站技術人員,遇到這樣的問題,需要檢查以下原因:
注意,網頁解析的過程如果使用的字符編碼不正確,還可能會導致腳本或者樣式表出錯。具體細節可以參考我以前寫過的文章:文檔字符集導致的腳本錯誤和Asp.Net頁面的編碼問題。
不久前看到某技術論壇有人反饋,WinForm程序使用Clipboard類的GetData方法去訪問剪切板中的HTML內容時會出現亂碼的問題,我估計也是由於WinForm在獲取HTML文本的時候沒有用對正確的字符編碼導致的。Windows剪貼板只支持UTF-8編碼,也就是説你傳入的文本都會被UTF-8編解碼。這樣一來,只要兩個程序都是調用Windows剪切板API編程的話,那麼複製粘貼的過程中不會出現亂碼。除非一方在獲取到剪貼板數據之後使用了錯誤的字符編碼進行解碼,才會得到亂碼(我做了簡單的WinForm剪切板編程實驗,發現GetData使用的是系統默認編碼,而不是UTF-8編碼)。
關於亂碼中出現?或者?,這裏需要額外提一下,當程序使用特定字符編碼解析字節流的時候,一旦遇到無法解析的字節流時,就會用解碼失敗替換字符或者?來替代。因此,一旦你最終解析得到的文本包含這樣的字符,而你又無法得到原始字節流的時候,説明正確的信息已經徹底丟失了,嘗試任何字符編碼都無法從這樣的字符文本中還原出正確的信息來。
字符集(Character Set),字面上的理解就是字符的集合,例如ASCII字符集,定義了128個字符;GB2312定義了7445個字符。而計算機系統中提到的字符集準確來説,指的是已編號的字符的有序集合(不一定是連續)。
字符碼(Code Point)指的就是字符集中每個字符的數字編號。例如ASCII字符集用0-127這連續的128個數字分別表示128個字符;GBK字符集使用區位碼的方式為每個字符編號,首先定義一個94X94的矩陣,行稱為“區”,列稱為“位”,然後將所有國標漢字放入矩陣當中,這樣每個漢字就可以用唯一的“區位”碼來標識了。例如“中”字被放到54區第48位,因此字符碼就是5448。而Unicode中將字符集按照一定的類別劃分到0~16這17個層面(Planes)中,每個層面中擁有216=65536個字符碼,因此Unicode總共擁有的字符碼,也即是Unicode的字符空間總共有17*65536=1114112。
編碼的過程是將字符轉換成字節流。
解碼的過程是將字節流解析為字符。
字符編碼(Character Encoding)是將字符集中的字符碼映射為字節流的一種具體實現方案。例如ASCII字符編碼規定使用單字節中低位的7個比特去編碼所有的字符。例如‘A’的編號是65,用單字節表示就是0x41,因此寫入存儲設備的時候就是b’01000001’。GBK編碼則是將區位碼(GBK的字符碼)中的區碼和位碼的分別加上0xA0(160)的偏移(之所以要加上這樣的偏移,主要是為了和ASCII碼兼容),例如剛剛提到的“中”字,區位碼是5448,十六進制是0x3630,區碼和位碼分別加上0xA0的偏移之後就得到0xD6D0,這就是“中”字的GBK編碼結果。
代碼頁(Code Page)一種字符編碼具體形式。早期字符相對少,因此通常會使用類似表格的形式將字符直接映射為字節流,然後通過查表的方式來實現字符的編解碼。現代操作系統沿用了這種方式。例如Windows使用936代碼頁、Mac系統使用EUC-CN代碼頁實現GBK字符集的編碼,名字雖然不一樣,但對於同一漢字的編碼肯定是一樣的。
大小端的説法源自《格列佛遊記》。我們知道,雞蛋通常一端大一端小,小人國的人們對於剝蛋殼時應從哪一端開始剝起有着不一樣的看法。同樣,計算機界對於傳輸多字節字(由多個字節來共同表示一個數據類型)時,是先傳高位字節(大端)還是先傳低位字節(小端)也有着不一樣的看法,這就是計算機裏頭大小端模式的由來了。無論是寫文件還是網絡傳輸,實際上都是往流設備進行寫操作的過程,而且這個寫操作是從流的低地址向高地址開始寫(這很符合人的習慣),對於多字節字來説,如果先寫入高位字節,則稱作大端模式。反之則稱作小端模式。也就是説,大端模式下,字節序和流設備的地址順序是相反的,而小端模式則是相同的。一般網絡協議都採用大端模式進行傳輸,windows操作系統採用Utf-16小端模式。
參考鏈接:
轉發自知乎 盛世唐朝 https://www.zhihu.com/question/23374078
很久很久以前,有一群人,他們決定用8個可以開合的晶體管來組合成不同的狀態,以表示世界上的萬物。他們看到8個開關狀態是好的,於是他們把這稱為”字節“。再後來,他們又做了一些可以處理這些字節的機器,機器開動了,可以用字節來組合出很多狀態,狀態開始變來變去。他們看到這樣是好的,於是它們就這機器稱為”計算機“。
開始計算機只在美國用。八位的字節一共可以組合出256(2的8次方)種不同的狀態。
他們把其中的編號從0開始的32種狀態分別規定了特殊的用途,一但終端、打印機遇上約定好的這些字節被傳過來時,就要做一些約定的動作:
遇上0×10, 終端就換行;
遇上0×07, 終端就向人們嘟嘟叫;
遇上0x1b, 打印機就打印反白的字,或者終端就用彩色顯示字母。
他們看到這樣很好,於是就把這些0×20以下的字節狀態稱為”控制碼”。他們又把所有的空
格、標點符號、數字、大小寫字母分別用連續的字節狀態表示,一直編到了第127號,這樣計算機就可以用不同字節來存儲英語的文字了。大家看到這樣,都感覺很好,於是大家都把這個方案叫做 ANSI 的”Ascii”編碼(American Standard Code for Information Interchange,美國信息互換標準代碼)。當時世界上所有的計算機都用同樣的ASCII方案來保存英文文字。
後來,就像建造巴比倫塔一樣,世界各地都開始使用計算機,但是很多國家用的不是英文,他們的字母裏有許多是ASCII裏沒有的,為了可以在計算機保存他們的文字,他們決定採用 127號之後的空位來表示這些新的字母、符號,還加入了很多畫表格時需要用下到的橫線、豎線、交叉等形狀,一直把序號編到了最後一個狀態255。從128 到255這一頁的字符集被稱”擴展字符集“。從此之後,貪婪的人類再沒有新的狀態可以用了,美帝國主義可能沒有想到還有第三世界國家的人們也希望可以用到計算機吧!
等中國人們得到計算機時,已經沒有可以利用的字節狀態來表示漢字,況且有6000多個常用漢字需要保存呢。但是這難不倒智慧的中國人民,我們不客氣地把那些127號之後的奇異符號們直接取消掉, 規定:一個小於127的字符的意義與原來相同,但兩個大於127的字符連在一起時,就表示一個漢字,前面的一個字節(他稱之為高字節)從0xA1用到0xF7,後面一個字節(低字節)從0xA1到0xFE,這樣我們就可以組合出大約7000多個簡體漢字了。在這些編碼裏,我們還把數學符號、羅馬希臘的字母、日文的假名們都編進去了,連在 ASCII 裏本來就有的數字、標點、字母都統統重新編了兩個字節長的編碼,這就是常説的”全角”字符,而原來在127號以下的那些就叫”半角”字符了。中國人民看到這樣很不錯,於是就把這種漢字方案叫做 “GB2312“。GB2312 是對 ASCII 的中文擴展。
但是中國的漢字太多了,我們很快就就發現有許多人的人名沒有辦法在這裏打出來,特別是某些很會麻煩別人的國家領導人。於是我們不得不繼續把GB2312 沒有用到的碼位找出來老實不客氣地用上。後來還是不夠用,於是乾脆不再要求低字節一定是127號之後的內碼,只要第一個字節是大於127就固定表示這是一個漢字的開始,不管後面跟的是不是擴展字符集裏的內容。結果擴展之後的編碼方案被稱為GBK 標準,GBK包括了GB2312 的所有內容,同時又增加了近20000個新的漢字(包括繁體字)和符號。 後來少數民族也要用電腦了,於是我們再擴展,又加了幾千個新的少數民族的字,GBK擴成了 GB18030。從此之後,中華民族的文化就可以在計算機時代中傳承了。 中國的程序員們看到這一系列漢字編碼的標準是好的,於是通稱他們叫做 “DBCS“(Double Byte Charecter Set 雙字節字符集)。在DBCS系列標準裏,最大的特點是兩字節長的漢字字符和一字節長的英文字符並存於同一套編碼方案裏,因此他們寫的程序為了支持中文處理,必須要注意字串裏的每一個字節的值,如果這個值是大於127的,那麼就認為一個雙字節字符集裏的字符出現了。那時候凡是受過加持,會編程的計算機僧侶們都要每天念下面這個咒語數百遍: “一個漢字算兩個英文字符!一個漢字算兩個英文字符……”
因為當時各個國家都像中國這樣搞出一套自己的編碼標準,結果互相之間誰也不懂誰的編碼,誰也不支持別人的編碼,連大陸和台灣這樣只相隔了150海里,使用着同一種語言的兄弟地區,也分別採用了不同的 DBCS 編碼方案——當時的中國人想讓電腦顯示漢字,就必須裝上一個”漢字系統”,專門用來處理漢字的顯示、輸入的問題,像是那個台灣的愚昧封建人士寫的算命程序就必須加裝另一套支持 BIG5 編碼的什麼”倚天漢字系統”才可以用,裝錯了字符系統,顯示就會亂了套!這怎麼辦?而且世界民族之林中還有那些一時用不上電腦的窮苦人民,他們的文字又怎麼辦? 真是計算機的巴比倫塔命題啊!
正在這時,大天使加百列及時出現了——一個叫 ISO(國際標誰化組織)的國際組織決定着手解決這個問題。他們採用的方法很簡單:廢了所有的地區性編碼方案,重新搞一個包括了地球上所有文化、所有字母和符號 的編碼!他們打算叫它”Universal Multiple-Octet Coded Character Set”,簡稱 UCS, 俗稱 “unicode“。
unicode開始制訂時,計算機的存儲器容量極大地發展了,空間再也不成為問題了。於是 ISO 就直接規定必須用兩個字節,也就是16位來統一表示所有的字符,對於ASCII裏的那些”半角”字符,unicode包持其原編碼不變,只是將其長度由原來的8位擴展為16位,而其他文化和語言的字符則全部重新統一編碼。由於”半角”英文符號只需要用到低8位,所以其高8位永遠是0,因此這種大氣的方案在保存英文文本時會多浪費一倍的空間。
這時候,從舊社會裏走過來的程序員開始發現一個奇怪的現象:他們的 strlen 函數靠不住了,一個漢字不再是相當於兩個字符了,而是一個!是的,從unicode開始,無論是半角的英文字母,還是全角的漢字,它們都是統一的”一個字符“!同時,也都是統一的”兩個字節“,請注意”字符“和”字節“兩個術語的不同,”字節“是一個8位的物理存貯單元,而”字符“則是一個文化相關的符號。在unicode中,一個字符就是兩個字節。一個漢字算兩個英文字符的時代已經快過去了。
unicode同樣也不完美,這裏就有兩個的問題,一個是,如何才能區別unicode和ascii?計算機怎麼知道三個字節表示一個符號,而不是分別表示三個符號呢?第二個問題是,我們已經知道,英文字母只用一個字節表示就夠了,如果unicode統一規定,每個符號用三個或四個字節表示,那麼每個英文字母前都必然有二到三個字節是0,這對於存儲空間來説是極大的浪費,文本文件的大小會因此大出二三倍,這是難以接受的。
unicode在很長一段時間內無法推廣,直到互聯網的出現,為解決unicode如何在網絡上傳輸的問題,於是面向傳輸的眾多 UTF(UCS Transfer Format)標準出現了,顧名思義,UTF-8就是每次8個位傳輸數據,而UTF-16就是每次16個位。UTF-8就是在互聯網上使用最廣的一種unicode的實現方式,這是為傳輸而設計的編碼,並使編碼無國界,這樣就可以顯示全世界上所有文化的字符了。UTF-8最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度,當字符在ASCII碼的範圍時,就用一個字節表示,保留了ASCII字符一個字節的編碼做為它的一部分,注意的是unicode一箇中文字符佔2個字節,而UTF-8一箇中文字符佔3個字節)。從unicode到utf-8並不是直接的對應,而是要過一些算法和規則來轉換。
Unicode符號範圍(十六進制) | UTF-8編碼方式(二進制) |
---|---|
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
最後簡單總結一下:
2019年5月28號更新
微博圖床已經開始現在外鏈了,導致在網站上使用微博圖床的圖片無法顯示,如果使用微博作爲圖床的,應考慮轉向其它的圖床。
對於運營靜態網站的人來説,圖片存儲在哪裏是一個值得深思的問題。
要考慮到連接速度和存儲持久問題。
像七牛、騰訊雲這些服務商,想要使用存儲服務,就需要付費使用。
就算有提供免費的存儲,但奈何存儲空間大小限制,加上要實名認證,就足以將人拒之門外。
只能考慮一些即免費又沒有空間限制的服務商。
國外的一些圖片上傳網站考慮到在中國大陸的連接速度和有可能被和諧的問題,暫時不考慮。
像sm.ms這些圖床,因爲怕隨時會關掉而導致所有鏈接失效,同時上傳後又沒有備份,所以也在排除之外。
偶然在Github上看到這個新浪圖床上傳工具,有上傳存儲記錄。同時,新浪作爲一個用戶量很大的網站,也不用擔心會被關閉的問題。
這個圖床上傳工具叫做 Weibo-Picture-Store,由Semibold開發。是一款上傳圖片到微博並生成外鏈的 Chrome 瀏覽器擴展。
chrome webstore 下載地址: https://chrome.google.com/webstore/detail/微博图床/pinjkilghdfhnkibhcangnpmcpdpmehk
Github: https://github.com/Semibold/Weibo-Picture-Store
支持單張上傳和多張上傳
因為上傳的時候會讀取cookies,所以你要提前登錄新浪微博。當然你也可以在擴展中填寫微博賬號和密碼(不推薦)。
上傳後的圖片會存儲在新浪微博-我的相冊裏
圖片上傳到哪兒了?
上傳到用户的微博上了
在擴展中填寫微博賬號和密碼,這樣做是否安全?
你填寫的賬號和密碼存在本地,不會上傳到任何服務器上
我的常用微博賬號是A,我可以在擴展中填寫另一個賬號B嗎?
可以但不推薦,因為一個瀏覽器同時只能有一個微博的登錄狀態,在使用B時,A可能會被強制登出。若有同時登錄兩個賬户的需求,請使用 Chrome 的多用户模式來避免上述這種情況
可以上傳的最大圖片大小是多少?
目前是 20MB
如何設置微博圖片水印?
請參考官方教程設置微博圖片水印
如何管理已上傳的圖片?
上傳記錄或者微相冊均可以管理
如何刪除已上傳的圖片?
目前無法刪除,微相冊中的刪除是針對相冊的操作,對圖片本身沒有影響
粘貼上傳沒有效果?
粘貼上傳只支持複製圖片文件,在資源文件管理器中的複製文件並粘貼是沒有效果的
上傳的是 PNG 圖片,返回的卻是 JPG 後綴的地址?
微博不支持 PNG 後綴,後綴對於瀏覽器判斷圖片的格式是沒有影響的
如何使用自定義裁剪?
自定義裁剪的格式需要微博支持,否則生成的地址是不能訪問的
裁剪操作對圖片的影響?
裁剪適用於 JPEG 格式的圖片。PNG 圖片裁剪後會丟失透明通道,GIF 則會變成靜態圖片
微相冊同步圖片的最大數量是多少?
1000 張。如果達到這個數量後繼續使用,會創建新的相冊,如果相冊也滿了,則不再同步圖片
微相冊數量已達到上限 100 個,不能同步圖片了怎麼辦?
前往微相冊清理陳舊的相冊即可
為什麼通過複製粘貼的方式上傳 GIF 會變成靜態圖片?
在瀏覽器或操作系統中複製 GIF 時,只有其中一幀被複制到了剪切板,因此上傳後會變成靜態圖片
Weibo-Picture-Store:https://github.com/Semibold/Weibo-Picture-Store
]]>面向對象的程序由對象組成的,每個對象包含對用户公開的特定功能部分和隱藏的實現部分。
oop將數據擺在第一位,然後考慮操作數據的算法。
要使用OOP,要瞭解對象的三個主要特性:
依賴(“uses-a”)
如果一個類的方法操作另一個類的對象,我們就説一個類依賴另一個類
聚合(“has-a”)
聚合意味著類A的對象包含類B的對象
繼承(“is-a”)
類A擴展類B,類A不但包含從類B繼承的方法,還會擁有一些額外的功能
要想使用對象,就必須首先構造對象,並指定其初始狀態。然後,對對象應用方法。構造器(constructor)
用來構造新實例。構造器是一種特殊的方法,用來構造並初始化對象。
構造器的名字應與類名相同。
以Date類為例,Date類的構造器名為Date。構造一個Date對象,需要在構造器前面加上new操作符。new Date()
這個表達式構造一個新對象,這個對象被初始化啊為當前的日期和時間。
為了讓構造的對象能多次使用,將對象存放在一個變量Date birthday = new Date()
下圖顯示了引用新構造的對象變量birthday
在對象與對象變量之間存在一個重要的區別。例如
Date deadline //deadline doesn’t refer to any object
定義了一個對象變量deadline,它可以引用Date類型的對象。
但是,變量deadline不是一個對象,實際上也沒有引用對象。
必須初始化變量deadline,有兩個選擇。一是用新構造的對象初始化這個變量deadline = new Date()
二是讓這個變量引用一個已存在的對象:deadline = birthday
現在兩個變量引用同一個對象
一個對象變量並沒有實際包含一個對象,而僅僅引用一個對象。
在Java中,任何對象變量的值都是對存儲在另外一個地方的一個對象的引用
Date deadline = new Date()
表達式new Date()構造了一個Date類型的對象,並且它的值是對新創建對象的引用。這個引用存儲在變量deadline中。
可以將對象變量設為null,表明這個對象變量目前沒有引用任何對象。deadline = null;
類型 | 儲存需求 | 取值範圍 | |
---|---|---|---|
boolean | 1字節 (8 bit) | true, false | |
char | 2字節(16 bit) | 0 ~ 216-1 | |
byte | 整型 | 1字節 (8 bit) | -128 ~ 127 |
short | 整型 | 2字節 (16 bit) | -32 768 ~ 32 767 (-215 ~ 215-1) |
int | 整型 | 4字節 (32 bit) | -2 147 483 648 ~ 2 147 483 647 (-231 ~ 231-1) |
long | 整型 | 8字節 (64 bit) | -9 223 372 036 854 776 808 ~ 9 223 372 036 854 775 807 (-263 ~ 263-1) |
float | 浮點類型 | 4字節 (32 bit) | 大約±3.402 823 47E + 38F(有效位數為6~7位) |
double | 浮點類型 | 8字節 (64 bit) | 大約±1.797 693 134 862 315 70E + 308(有效位數為15位) |
final關鍵字表示變量只能被賦值一次,一旦被賦值後,就不能被更改。
例如:
1 | Final double CMCC = 2.54 |
如果希望某個常量可以在一個類中的多個方法中使用,稱這個常量為類常量。可以使用關鍵字static final 設置一個類常量,定義的位置在main方法外部。因此,同一個類的其他方法中也可以使用這個常量。如果一個常量被聲明為public,那麼其他類的方法也可以使用這個常量。
在java中,算術運算符可以用+ - * / 來代表加減乘除。
當參與除(/)運算的兩個操作數為整數時,表示整數除法,答案為整數。否則,表示浮點除法。
求餘數可以用%
例如 10%3等於1,10%3.0等於1.0
注意:整數除以0將會產生一個異常,而浮點數除以0將會得到無窮大或NaN結果。
1 | int a; |
總結一下
三元操作符Condition? expression1: expression2
當Condition為true時,計算或返回第一個表達式expression1,如果為false,則計算第二個表達式expression2.
如 x <y ? x: y
返回x和y中較小的那個值。
整型、實型(常量)、字符型數據可以混合運算。運算中,不同類型的數據先轉化為同一類型,然後進行運算。轉換過程中可能導致溢出或損失精度
1 | 低----------------------------------------------------------高 |
必須滿足轉換前的數據類型的位數要低於轉換後的數據類型。
自動轉換由低到高的順序轉換
1 | int n = 123456789; |
當使用上面兩個數值進行二元操作時(例如 n+f , n是整數,f是浮點數),先要將兩個操作數轉換為同一種類型,然後再進行計算。
如果兩個操作數中有一個是double類型,另一個操作數就會轉換為double類型;
否則,如果其中一個操作數是float類型,另一個操作數將會轉換為float類型;
否則,如果其中一個操作數是long類型,另一個操作數將會轉換為long類型。
否則,兩個操作數都將被轉換為int類型。
高的向低的順序轉換
1 | double x = 3.94; |
x的結果為3,強制類型轉換通過截斷小數部分將浮點值轉為整型。
如果想要四捨五入,得到最接近的整數。可以使用Math.round方法
1 | double x = 3.14; |
結果為 4,當調用round時,仍然需要使用強制類型轉換(int).其原理是因為round方法返回的結果為long類型。由於存在信息丟失的可能性,所以只有使用顯式的強制類型轉換才能夠將long類型轉換成int類型。
不要在boolean類型與任何類型之間進行強制類型轉換,這樣可以防止發生錯誤。
| 優先級 | 運算符 | 簡介 | 結合性 |
| —— | ——————————————————————– | ———————————————————- | ——– | ——– |
| 1 | [ ]、 .、 ( ) | 方法調用,屬性獲取 | 從左向右 |
| 2 | !、~、 ++、 – | 一元運算符 | 從右向左 |
| 3 | * 、/ 、% | 乘、除、取模(餘數) | 從左向右 |
| 4 | + 、 - | 加減法 | 從左向右 |
| 5 | <<、 >>、 >>> | 左位移、右位移、無符號右移 | 從左向右 |
| 6 | < 、<= 、>、 >=、 instanceof | 小於、小於等於、大於、大於等於,對象類型判斷是否屬於同類型 | 從左向右 |
| 7 | == 、!= | 2個值是否相等,2個值是否不等於。 下面有詳細的解釋 | 從左向右 |
| 8 | & | 按位與 | 從左向右 |
| 9 | ^ | 按位異或 | 從左向右 |
| 10 | | | 按位或 | 從左向右 |
| 11 | && | 短路與 | 從左向右 |
| 12 | || | 短路或 | 從左向右 |
| 13 | ?: | 條件運算符 | 從右向左 |
| 14 | =、 += 、-= 、*= 、/=、 %=、 &=、 |=、 ^=、 <、<= 、>、>= 、>>= | 混合賦值運算符 | 從右向左 |
String類的substring方法可以從一個較大的字符串提取出一個子串。
substring(a,b)
例如
1 | String greeting = "hello"; |
substring的一個優點:容易計算子串的長度,長度為b-a。
例如 hel的長度為 3-0=3
可以使用equals來檢測兩個字符串是否相等。s.equals(t)
如果相等,會返回true,否則,返回false. s和t可以是字符串變量,也可以是字符串常量。
eg: “hello”.equals(greeting)
檢測是否相等,而且不區分大小寫,可以使用equalsIgnoreCase方法。
eg: “hello”.equalsIgnoreCase(“HeLLO)
不能使用==運算符來檢測兩個字符串是否相同,這個運算符只能夠確定兩個字符串是否放置在同一個位置。
空串是一個Java對象,有自己的串長度(0)和內容(空)。可以通過調用以下代碼檢查字符串是否為空。
1 | if(str.length()==0) |
null
表示目前沒有任何對象與該變量關聯。可以通過調用以下代碼檢查字符串是否為null
1 | if (str == null) |
length方法會返回採用UTF-16編碼表示的給定字符串所需的代碼單元數量。
想要得到實際的長度,即代碼點數量,可以調用
1 | xxx.codePointCount(0,xxx.length()) |
調用s.chatAt(n)將返回位置n的代碼單元,n介於0~ s.length()-1 之間。
eg:
1 | String greeting = "Hello" |
想得到i個的代碼點,可以使用
1 | int index= greeting.offsetByCodePoints(0,i); |
為了能夠讀取用户在控制枱的輸入,首先需要創建一個Scanner對象,並與“標準輸入流”System.in關聯。
1 | Scanner in = new Scanner (System.in) |
記得要import java.util.*;
1 | import java.util.*; |
System.out.print(X)會將以x對應的數據類型所允許的最大非0數字位數打印輸出x
1 | double x = 10000.0/3.0; |
為了能夠方便格式輸出結果,可以使用printf()。
1 | double x = 10000.0/3.0; |
用於printf的轉換符
轉換符 | 類型 | 舉例 |
---|---|---|
%s | 字符串類型 | “Hello” |
%c | 字符類型 | ‘H’ |
%b | 布爾類型 | true OR false |
%d | 整數類型(十進制) | 10 |
%x | 整數類型(十六進制) | 9f |
%o | 整數類型(八進制) | 237 |
%f | 浮點類型 | 15.9 |
%a | 十六進制浮點類型 | 0x1.fccdp3 |
%e | 指數類型 | 6.23e+24 |
%g | 通用浮點類型(f和e類型中較短的) | 42.5000 |
%h | 散列碼 | 42628b2 |
%% | 百分比類型 | % |
%n | 換行符 | 相當於”\n”換行作用 |
%tx | 日期與時間類型(x代表不同的日期與時間轉換符) | 見博文下表 |
如果基本的整數和浮點數精度不夠滿足需求,那麼可以使用java.math包中的兩個很有用的類:BigInteger和BigDecimal。這兩個類可以處理包含任意長度數字序列的數值。
把普通的數值轉換成大數值,可以使用靜態的valueOf()方法。
1 | BigInteger a = BigInteger.valueOf(100); |
大數值的運算不能使用常用的算術運算符(如+,*)處理,而是要使用大數值類中的add
和multiply
1 | BigInteger c = a.add(b); // c=a+b |
其他的運算包括有 subtract(減)
,divide(除)
和mod(餘數)
數組是一種數據結構,用來存儲同一類型值的集合
數組聲明方式:
1 | int[] array 或者int array[] |
數組初始化
1 | int[] array = new int[100]; |
創建一個數字數組時,所有的元素都初始化為0。
boolean數組的元素會初始化為false
對象數組的元素會初始化為一個特殊值null
1 | public class Array { |
輸出的結果會是: 0 0 0 0 0
重要:
一旦創據數組,就不能再改變數組的大小。如果需要在運行中擴展數組的大小,則需要使用另一種數據結構——數組列表(array list)
Java有一種很強的循環結構,可以用來依次處理數組中的每個元素而不必為指定下標值而分心。
格式:for(variable:collection) statement
定義一個變量用於暫存集合中的每一個元素,並執行相應的語句。
collection這一集合表達式必須必須是一個數組或者是一個實現了Iterable接口的類對象(例如Arraylist)。
eg:
1 | for(int element :a) |
在java中,允許將一個數組變量拷貝到給另一個數組變量。這時,兩個變量將引用同一個數組
1 | int[] a = {2,3,5,7,11,12}; |
如果想將一個數組的所有值拷貝到一個新的數組去,使用Arrays類的copyTo方法。
1 | int[] copieda = Arrays.copyTo(a,a.length); |
第二個參數是新數組的長度,可以通過這個方法來增加數組的大小。
eg: Arrays.copyTo(a,2*a.length);
增加到2倍大
如果數組元素是數字數組時,多餘的元素都賦值為0。
如果數組元素是boolean數組,多餘的元素會賦值為false
如果數組元素是對象數組的元素,多餘的元素賦值為特殊值null
如果長度小於原始數組的長度,則只拷貝前面的數據元素
想要對數組排序,可以使用Arrays類的sort方法Arrays.sort(數組a)
抽獎遊戲
1 | import java.util.Arrays; |
多維數組適用於表示表格或者更加複雜的排列方式。
聲明:type arrayName[][]
或者type[][] arrayName
初始化:
1 | - balance=new double[rownumber][columnNumber] |
Java語言中,由於把二維數組看作是數組的數組,數組空間不是連續分配的,所以不要求二維數組每一維的大小相同。
]]>最近在微博上看到有一位科技博主推薦了一款Visual Studio Code插件,名字叫做 VSC Netease Music。
Visual Studio Code的插件真是越來越多樣化,看漫畫、看小説,現在連聼音樂也都有了。
這款插件對我來説最吸引的,應該就是無地區版權限制了。畢竟因為版權原因,網易雲音樂早就把我拒之門外了。
插件是一位叫做nondanee的第三方開發者開發的。
插件github地址: https://github.com/nondanee/vsc-netease-music
插件marketplace: https://marketplace.visualstudio.com/items?itemName=nondanee.vsc-netease-music
我在電腦上可以正常地使用
但是Github上看到可能需要替換一些文件才可以正常使用。
Github原文:
VS Code for Windows 自 1.31.0 起自帶完整的 ffmpeg 動態鏈接庫 (可能是 bug),無需替換;macOS 與 Linux 平台仍需替換。
VS Code 使用的 Electron 版本不包含 ffmpeg,需替換自帶的 ffmpeg 動態鏈接庫才能正常播放 (每次更新 VS Code 都需重新替換)
通過 VS Code 版本在 https://raw.githubusercontent.com/Microsoft/vscode/%version%/.yarnrc 查看其使用的 Electron 版本,並於 https://github.com/electron/electron/releases/tag/%version% 下載對應的 Electron 完整版本進行替換
1 | 下載 electron-%version%-win32-%arch%.zip |
1 | 下載 electron-%version%-darwin-x64.zip |
1 | 下載 electron-%version%-linux-%arch%.zip |
使用 Python 腳本替換 (Python 2/3 均可,絕大部分發行版自帶環境)
默認安裝位置下 Linux 和 Windows 需要以管理員身份運行,macOS 不需要
1 | Invoke-RestMethod https://gist.githubusercontent.com/nondanee/f157bbbccecfe29e48d87273cd02e213/raw | python |
1 | curl https://gist.githubusercontent.com/nondanee/f157bbbccecfe29e48d87273cd02e213/raw | python |
如果 VS Code 使用默認配置安裝,腳本會自動尋找並替換,若自定義了安裝位置,請自行修改 installation
按F1或者Ctrl Shift P打開命令面板
輸入命令前綴 網易雲音樂
或 NeteaseMusic
就可以使用。
界面底部還會顯示歌詞和操作欄
一邊編程,一邊聽歌,是一種很享受的行為,不用另外開多一個音樂軟件。
不過涉及到音樂版權問題,這款插件感覺命不久矣。
以下方法只適用於沒有刪除 hexo blog 文件夾
因為重裝系統後,Hexo 相關依賴插件/軟件和在 C 盤的緩存資料都會被刪除,以至於 Hexo 的相關命令都無法運行。所有,在重裝系統後,都要重新部署 Hexo。但是重新部署並不難,只需要幾個步驟就行。
因為我的 hexo blog 文件夾不存儲於 C 盤,並沒有因為重裝系統被刪掉。所有重新部署很容易。
git for windows: https://git-scm.com/
Node.js: https://nodejs.org/en/
配置 git 個人信息和生成 ssh 密鑰
打開 git bash,輸入
1 | git config --global user.name "xxxxx" |
把上面的 xxxx 換成自己的資料,然後一直 Enter 就行。
當上面的運行完成後,會在 C:\Users\主用户
裏出現.ssh
文件夾,裏面有 id_rsa 和 id_rsa.pub 兩個文件,複製 id_rsa.pub 的內容。
打開 Github 網頁,依次是 右上角Settings - SSH and GPG keys - New SSH key
把複製的內容粘貼到 Key
欄,然後保存。
PS:如果你有把 blog 同步到 coding 的,記得同樣把 id_rsa.pub 的內容複製到 coding 的 ssh 公鑰去,具體為右上角個人設置 - SSH公鑰 - 新增公鑰
git bash 上 輸入 npm install hexo-cli -g
打開你原有的 blog 文件夾,只需保留_config.yml,theme/,source/,scaffolds/,package.json,.gitignore 這些項目,刪除其他的文件。
git bash 上 輸入 npm install
git bash 上 輸入 npm install hexo-deployer-git --save
最後 運行 hexo clean && hexo g && hexo d 看看是否成功。
]]>本文章參考 https://helloqingfeng.github.io/2017/02/25/hexo-rebuilding/
電腦系統: Windows 10 1809
Java版本: 1.8.0_181
第一步当然是安装Java文件。Java現在已經發現到Java11了,不一定要安裝最新的版本,可以根據自己的需要安裝對應的版本。
本文安裝的 Java SE 8u181
下載Java可以到oracle的官網:https://www.oracle.com/technetwork/java/javase/downloads/index.html
右鍵This PC(即此電腦),選擇Properties(屬性)選項。
點擊左邊欄的Advanced sysyem settings(高級系統設置),點擊下面的Environment Variables(環境變量)
點擊System variable(系統變量)下的New(新建)。
Variable name(變量名) 填爲 Java_Home
Variable value(變量值) 爲Java的安裝路徑。例如我的是 C:\Program Files\Java\jdk1.8.0_181
然後點擊OK(確定)
在System variable(系統變量)下找到Path
並雙擊。
在打開的窗口中點擊右邊的編輯文本。
把 %Java_Home%\bin;%Java_Home%\jre\bin;
複製到 Variable value(變量值) 的開頭並保存。
點擊System variable(系統變量)下的New(新建)。
Variable name(變量名) 填爲 CLASSPATH
Variable value(變量值) 填爲 .;%Java_Home%\bin;%Java_Home%\lib\dt.jar;%Java_Home%\lib\tools.jar
然後點擊OK(確定)
上面已經把相關的環境變量給配置好了,接下來測試下是否配置正確。
打開CMD或者PowerShell
輸入Java
接着輸入 java -version
接着輸入 javac
如果你的結果跟上面圖片的結果一樣或者相似的話,恭喜你,Java的環境變量配置已經成功了。
]]>Adapter常用的實現類:
修改activity_main.xml
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
android:divider:
可以實現分割線,可以用圖片或者顏色android:dividerHeight
設置分割線的高度android:headerDividersEnabled
是否顯示頭部的分割線,默認是true
修改MainActivity.java
1 | public class MainActivity extends AppCompatActivity { |
ArrayAdapter<String> adapter1 = new ArrayAdapter<String>(this,R.layout.array_item,arr1);
ArrayAdapter傳入了三個參數:
Context: 代表了訪問整個Android應用的接口。
textViewResourceId: 一個TextView的資源ID,該TextView組件將作為ArrayAdapter的列表項組件。
數組或者List: 提供數據。
以上代碼可以看到,該數組或List包含多少個元素,就將會生成多少個列表項,每個列表項都是TextView組件。
arr1有三個數據,則會生成一個包含三個列表項的ArrayAdapter,每個列表項的組件外觀由R.layout.array_item佈局文件(該佈局文件只是一個TextView)控制。
新建array_item.xml
1 |
|
TextView-shadow 陰影實現方式
android:shadowColor:陰影的顏色
android:shadowDx:水平方向上的偏移量
android:shadowDy:垂直方向上的偏移量
android:shadowRadius:是陰影的的半徑大小,值也大,陰影越大
新建 checked_item.xml
1 |
|
運行結果:
如果程序僅僅只是顯示一個列表,那麼可以無需通過設置xml來實現,而是直接讓Activity繼承ListActivity來實現。
修改MainActivity.java
1 |
|
可以看到,不用使用setContentView()去調用佈局文件。
運行結果:
]]>9Patch圖片是一種特殊的png圖片,以.9.png結尾,它在原始的圖片四周各添加一個寬度為1像素的像條,這4條線條決定了該圖片的縮放規則、內容顯示規則。
在Android Studio 上編輯.9.png
,具體是在Android Studio上右鍵點擊你要編輯的照片,選擇Create 9-Patch file
就可以進入編輯界面。
這就是9-Patch的編輯界面
Optional controls include:
這3個從上到下依次是
9Patch的規則:
上側和左側的黑色線段共同決定了圖片的縮放區域
下側和右側的黑色線段共同決定了圖片的內容顯示區域
在Android的設計過程中,為了適配不同的手機分辨率,圖片大多需要拉伸或者壓縮,這樣就出現了可以任意調整大小的一種圖片格式“.9.png”。這種圖片是用於Android開發的一種特殊的圖片格式,它的好處在於可以用簡單的方式把一張圖片中哪些區域可以拉伸,哪些區域不可以拉伸設定好,同時可以把顯示內容區域的位置標示清楚。
本文結合一些具體的例子來看下.9.png的具體用法。
首先看下普通的.png資源與.9.png的資源區別:
普通的png資源就不多介紹了,可以明顯看到.9.png的外圍是有一些黑色的線條的,那這些線條是用來做什麼的呢?我們來看下放大的圖像:
放大後可以比較明顯的看到上下左右分別有一個像素的黑色線段,這裏分別標註了序號。簡單來説,序號1和2標識了可以拉伸的區域,序號3和4標識了內容區域。當設定了按鈕實際應用的寬和高之後,橫向會拉伸1區域的像素,縱向會拉伸2區域的像素。如下圖:
拉伸的含義應該比較容易理解,但是內容區域的標註有什麼意義呢?我們來看下圖:
這裏程序設置的文字垂直居中,水平居左的對齊方式。對齊方式是沒有問題的,但是對於這種大圓角同時又有些不規則邊框的的圖形來説,錯誤的標註方式會讓排版看起來很混亂。所以我們需要修正內容區域的線段位置和長度。
把橫向的內容區域縮短到圓角以內,縱向的內容區域控制在輸入框的高度以內,這樣文字就可以正常顯示了。
這裏還有一種特殊情況,就是本身是.9.png的資源,但是在修改過程中你希望這張.9.png不能被拉伸(在做皮膚的情況中有可能會遇到),那怎麼辦呢?只要把拉伸區域的點點在透明像素的地方就可以了,這樣拉伸的時候會拉伸透明部分的像素,而不會拉伸圖像本身。如下圖:
大家可以看到拉伸區域的黑點是可以不連續的。
説了半天.9.png的用法,那.9.png如何輸出呢?有很多種方式可以輸出.9.png,比如説用draw9patch.bat這個工具,或者簡單一點,用photoshop直接輸出。輸出的方式是先輸出普通的png資源,然後擴大畫布大小,上下左右各空出一個像素,再用一個像素的鉛筆工具(顏色選擇純黑色),上下左右分別畫點就可以了,保存的時候注意把後綴修改為.9.png。
有兩點需要特別注意下:
1.最外圍的一圈像素必須要麼是純黑色,要麼是透明,一點點的半透明的像素都不可以有,比如説99%的黑色或者是1%的投影都不可以有;
2.文件的後綴名必須是.9.png,不能是.png或者是.9.png.png,這樣的命名都會導致編譯失敗。
While I was working on my first Android app, I found 9-patch (aka 9.png) to be confusing and poorly documented. After a little while, I finally picked up on how it works and decided to throw together something to help others figure it out.
Basically, 9-patch uses png transparency to do an advanced form of 9-slice or scale9. The guides are straight, 1-pixel black lines drawn on the edge of your image that define the scaling and fill of your image. By naming your image file name.9.png, Android will recognize the 9.png format and use the black guides to scale and fill your bitmaps.
Here’s a basic guide map:
9-patch-guides
As you can see, you have guides on each side of your image. The TOP and LEFT guides are for scaling your image (i.e. 9-slice), while the RIGHT and BOTTOM guides define the fill area.
The black guide lines are cut-off/removed from your image - they won’t show in the app. Guides must only be one pixel wide, so if you want a 48x48 button, your png will actually be 50x50. Anything thicker than one pixel will remain part of your image. (My examples have 4-pixel wide guides for better visibility. They should really be only 1-pixel).
Your guides must be solid black (#000000). Even a slight difference in color (#000001) or alpha will cause it to fail and stretch normally. This failure won’t be obvious either*, it fails silently! Yes. Really. Now you know.
Also you should keep in mind that remaining area of the one-pixel outline must be completely transparent. This includes the four corners of the image - those should always be clear. This can be a bigger problem than you realize. For example, if you scale an image in Photoshop it will add anti-aliased pixels which may include almost-invisible pixels which will also cause it to fail*. If you must scale in Photoshop, use the Nearest Neighbor setting in the Resample Image pulldown menu (at the bottom of the Image Size pop-up menu) to keep sharp edges on your guides.
This is actually a “fix” in the latest dev kit. Previously it would manifest itself as all of your other images and resources suddenly breaking, not the actually broken 9-patch image.
scalable-area
The TOP and LEFT guides are used to define the scalable portion of your image - LEFT for scaling height, TOP for scaling width. Using a button image as an example, this means the button can stretch horizontally and vertically within the black portion and everything else, such as the corners, will remain the same size. The allows you to have buttons that can scale to any size and maintain a uniform look.
It’s important to note that 9-patch images don’t scale down - they only scale up. So it’s best to start as small as possible.
Also, you can leave out portions in the middle of the scale line. So for example, if you have a button with a sharp glossy edge across the middle, you can leave out a few pixels in the middle of the LEFT guide. The center horizontal axis of your image won’t scale, just the parts above and below it, so your sharp gloss won’t get anti-aliased or fuzzy.
Fill area guides are optional and provide a way define the area for stuff like your text label. Fill determines how much room there is within your image to place text, or an icon, or other things. 9-patch isn’t just for buttons, it works for background images as well.
The above button & label example is exaggerated simply to explain the idea of fill - the label isn’t completely accurate. To be honest, I haven’t experienced how Android does multi-line labels since a button label is usually a single row of text.
Finally, here’s a good demonstration of how scale and fill guides can vary, such as a LinearLayout with a background image & fully rounded sides:
With this example, the LEFT guide isn’t used but we’re still required to have a guide. The background image don’t scale vertically; it just scales horizontally (based on the TOP guide). Looking at the fill guides, the RIGHT and BOTTOM guides extend beyond where they meet the image’s curved edges. This allows me to place my round buttons close to the edges of the background for a tight, fitted look.
So that’s it. 9-patch is super easy, once you get it. It’s not a perfect way to do scaling, but the fill-area and multi-line scale-guides does offer more flexibility than traditional 9-slice and scale9. Give it a try and you’ll figure it out quickly.
因為RecyclerView
屬於新增的控件,Android將RecyclerView定義在support庫裏。若要使用RecyclerView,第一步是要在build.gradle
中添加對應的依賴庫。
在app/build.gradle
中的dependencies閉包
添加以下內容:
1 | implementation 'com.android.support:recyclerview-v7:27.1.1' |
然後點擊頂部的Sync Now進行同步
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
由於RecyclerView
不是內置在系統SDK中,需要把其完整的包名路徑寫出來
1 | public class Fruit { |
創建ImageView來顯示水果圖片,TextView來顯示水果名字。
1 | <LinearLayout |
為RecyclerView
新增適配器FruitAdapter
,並讓其繼承於RecyclerView.Adapter
,把泛型指定為FruitAdapter.ViewHolder
。
1 | public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { |
定義內部類ViewHolder
,並繼承RecyclerView.ViewHolder
。傳入的View參數通常是RecyclerView子項的最外層佈局。
FruitAdapter構造函數,用於把要展示的數據源傳入,並賦予值給全局變量mFruitList。
FruitAdapter繼承RecyclerView.Adapter。因為必須重寫onCreateViewHolder()
,onBindViewHolder()
和getItemCount()
三個方法
onCreateViewHolder()
用於創建ViewHolder實例,並把加載的佈局傳入到構造函數去,再把ViewHolder實例返回。onBindViewHolder()
則是用於對子項的數據進行賦值,會在每個子項被滾動到屏幕內時執行。position
得到當前項的Fruit實例。getItemCount()
返回RecyclerView的子項數目。1 | public class MainActivity extends AppCompatActivity { |
LayoutManager
用於指定RecyclerView的佈局方式。LinearLayoutManager
指的是線性佈局。
運行效果:
1 | <LinearLayout |
把LinearLayout改成垂直排列,因為水果名字長度不一樣,把寬度改為100dp。
ImageView和TextView都改為水平居中
1 |
|
通過調用setOrientation()
把佈局的排列方向改為水平排列。
得益於RecyclerView的設計,我們可以通過LayoutManager實現各種不同的排列方式的佈局。
運行結果:
除了LinearLayoutManager
,RecyclerView
還提供了GridLayoutManager(網格佈局)
和StaggeredGridLayoutManager(瀑布流佈局)
GridLayoutManager(網格佈局)
修改 MainActivity.java
,把
1 | LinearLayoutManager layoutManager = new LinearLayoutManager(this); |
換成
1 | GridLayoutManager layoutManager = new GridLayoutManager(this,5); |
GridLayoutManager (Context context, int spanCount)
運行結果:
StaggeredGridLayoutManager(瀑布流佈局)
1 |
|
把LinearLayout的寬度設為match_parent
是因為瀑布流的寬度是 根據佈局的列數來自動適配的,而不是固定值 。(GridLayoutManager也是 根據佈局的列數來自動適配的)
1 | public class MainActivity extends AppCompatActivity { |
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL);
StaggeredGridLayoutManager傳入2個參數,第一個是佈局的列數,第二個是佈局的排列方向。
random.nextInt(20)+1
產生1-20的隨機數
運行效果:
左圖是GridLayoutManager,右圖是StaggeredGridLayout。
當從顯示效果來看,已經一目瞭然。
GridLayoutManager是會固定高度的,所以會留下很多空白區域。
相反,StaggeredGridLayout並不會固定高度,以至於就算子項的高度不一致,下一行的會自動靠攏上一行。
1 | public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> { |
修改ViewHolder,添加fruitView變量來保存子項最外層佈局的實例。
運行效果:
在themes\hiker\layout\_partial
新建文件copyright.ejs
打開copyright.ejs
,添加一下內容。
1 | <div> |
打開themes\hiker\layout\_partial\article.ejs
添加一下內容,位置介於donate和comment之間
1 | <% if (!index && theme.donate.enable){ %> |
修改themes\hiker\source\css\_partial\article.styl
,在末端添加以下內容。
1 | .post-copyright { |
在themes\hiker\languages中,找到你應用的語言文件,例如zh-TW,打開並添加以下內容。
1 | copyright: |
打開themes\hiker\_config.yml
,添加以下內容。
1 | #版權信息 |
最後當然是 hexo clean && hexo g && hexo d 就可以看到結果了
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
android:layout_width
和android:layout_height
設置為match_parent
可以把ListView佔滿整個布局的空間。
1 | public class MainActivity extends AppCompatActivity { |
例子中以數組的方式來傳遞數據,裏面包含很多水果的名字。
數組中的數據無法直接傳遞給ListView,這時需要藉助適配器(Adapter)來完成。
Android中提供了很多適配器,這裏使用的是ArrayAdapter,它可以通過泛型來指定要適配的數據類型,然後在構造函數中把要適配的數據傳入。
因為數據都是字符串,所以ArrayAdapter的泛型指定為String。
ArrayAdapter的構造函數傳入3個數據:
android.R.layout.simple_list_item_1
作為ListView子項布局的ID,這時安卓內置的布局文件。裏面只有一個TextView,可用於顯示一段文本。
最後調用ListView的setAdapter()方法將構建好的適配器對象傳遞進去。
運行效果:
對ListView界面進行定製,讓其可以顯示更豐富的內容。
為每個水果加上圖片
定義一個實體類,作為ListView適配器的適配類型。
1 | public class Fruit { |
創建ImageView來顯示水果圖片,TextView來顯示水果名字。並讓TextView在垂直方向上居中顯示。
1 | <LinearLayout |
1 | public class FruitAdapter extends ArrayAdapter<Fruit> { |
FruitAdapter 重寫了父類的構造函數,用於將上下文,ListView子項布局的ID和要適配的數據傳遞進去。
重寫getView()方法,這個方法在每個子項被滾動到屏幕內的時候會被調用。
首先通過getItem()得到當前的項的Fruit實例,然後使用LayoutInflater來為這個子項加載我們傳入的布局。
LayoutInflater.from(getContext()).inflate(resourseId,parent,false);
inflate接受三個參數
false
表示只讓我們在父布局中聲明的layout屬性生效,但不會為這個view添加父布局。1 | public class MainActivity extends AppCompatActivity { |
添加initFruit()方法來初始化所有的水果數據。
運行結果:
上面的代碼中,FruitAdapter的getView()每次都將布局重新加載了一遍,當ListView快速滾動時,就會成為性能的瓶頸。
為了解決這問題,需要對ListView進行優化。
修改FruitAdapter.java
1 |
|
getView中的convertView參數,用於將之前加載好的布局進行緩存,以便以後可以進行重用。
內部類ViewHolder用於對控件的實例進行緩存。
修改MainActivity.java
1 |
|
使用setOnItemClickListener()為ListView註冊了一個監聽器。
通過position參數判斷出用户點擊的是哪一個子項。
常見控件和布局的繼承結構
所有的控件都是直接或者間接繼承於 View
所有的布局都是直接或者間接繼承於 ViewGroup
View 是 Android 中最基本的一種 UI 控件,它可以在屏幕上繪製一塊矩形區域,並能響應這塊區域的各種事件。
ViewGroup 是特殊的一種 View,是一個用於放置控件和布局的容器
以添加 iPhone 風格的標題欄為例:
當多個活動界面都要使用這個標題欄時,我們可以通過引入布局的方式,這樣可以避免每個活動界面都要寫一遍同樣的標題代碼,減少代碼重複。
新建一個 title.xml
1 |
|
android:background 用於為控件或布局制定一個背景,可以使用顏色或者圖片
android:layout_margin 指定控件在上下左右方向上偏移的距離
在創建了布局後,我們要引入布局。在需要引入界面的 Activity 界面布局中,添加<include layout="@layout/title"/>
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
通過調用 getSupportActionBar()方法來獲得 ActionBar 的實例,然後再調用 ActionBar 的 hide()方法將標題欄隱藏。
1 | public class MainActivity extends AppCompatActivity { |
引入布局的方式雖然可以減少很多重複的布局代碼,但是當布局中的控件要求能夠響應事件,我們還是需要在每個活動中去為這些控件單獨編寫事件響應代碼。以標題欄為例,這些控件在每一個布局中所需的功能都是一樣的,這時我們可以使用自定義控件的方式,避免每個活動都要去編寫同樣的代碼。
新建 TitleLayout 並繼承 LinearLayout。
1 | public class TitleLayout extends LinearLayout { |
重寫 LinearLayout 中帶有兩個參數的構造函數。
通過 LayoutInflater 來對標題欄進行動態加載。LayoutInflater.from(context).inflate(R.layout.title,this);
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
添加自定義控件需要先指明控件的完整類名,如代碼中的com.example.hwy01.uicustomviews.TitleLayout
修改 TitleLayout
1 | public class TitleLayout extends LinearLayout { |
LinerLayout, 中文名為線性布局。這個布局會將它所包含的控件在線性方向上依次排列。
我們可以通過android:orientation
屬性來指定排列方向。
vertical
為垂直方向,horizontal
為水平方向
1 | <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
運行效果:
注意:如果是vertical
垂直方向,則内部的控件不能將android:layout_height
指定為match_parent
,因爲這樣的話,單獨的一個控件就已經把整個垂直方向佔據了,接下來的控件就沒有可以放置的位置了,而導致其它的控件無法顯示。同理,如果是horizontal
水平方向,則内部的控件不能將android:layout_width
指定為match_parent
android:layout_gravity
用於指定控件在布局中的對齊方式。
當vertical
垂直方向,只有水平方向上的對齊方式才會生效。
當horizontal
水平方向,只有垂直方向上的對齊方式才會生效。
1 | <Button |
運行效果:
android:layout_weight 可以用比例的方式來指定控件的大小,其在手機屏幕的適配上起到很重要的作用。
1 | <EditText |
運行效果:
RelativeLayout又稱爲相對布局,通過相對定位的方式讓控件出現在布局的任何地方。
父容器定位屬性示意圖
圖片來自runoob
1 | <RelativeLayout |
運行效果:
以上是相對于父布局定位的。Button1和父布局的左上角對齊,Button2和父布局的右上角對齊,Button3居中顯示,Button4和父布局的左下角對齊,Button5和父布局的左下角對齊。
1 | <Button |
運行效果:
以上是每個控件都是以Button3 控件進行定位的。
android:layout_above
讓一個控件位於另一個控件上方,需要指定相對控件id的引用。上方為android:layout_above="@id/button3"
在Button3的上方。android:layout_below
讓一個控件位於另一個控件下方。android:layout_toLeftOf
讓一個控件位於另一個控件左側。android:layout_toRightOf
讓一個控件位於另一個控件右側FrameLayout又稱爲幀布局,所有的控件都會默認擺放在布局的左上角。
1 | <FrameLayout |
運行效果:
所有的控件都位於布局的左上角,而且按照順序曡在一起。
我們可以通過android:layout_gravity
去指定控件在布局中的對齊方式。
只有LinearLayout
支持使用layout_weight屬性來實現按比例指定控件大小的功能,其他的布局并不支持這屬性。因此,Android引入了一種全新的布局方式來解決這個問題—–百分比布局。可以直接指定控件在布局中所占的百分比。
百分比布局為FrameLayout
和RelativeLayout
進行了功能擴展,提供了PercentFrameLayout
和PercentRelativeLayout
兩個全新的布局。
在build.gradle
添加百分比布局的依賴。
打開app/build.gradle
,在dependencies閉包添加以下内容:
implementation 'com.android.support:percent:25.3.0'
1 | dependencies { |
運行效果:
可以看到通過app:layout_heightPercent
和app:layout_widthPercent
兩個參數進行百分比設定。
1 | <android.support.percent.PercentFrameLayout |
運行效果:
其它的屬性還有
可以參考android-percent-support-lib-sample和Android 百分比布局库(percent-support-lib) 解析与扩展
]]>效果如圖
文章内
網頁頂部
1 | npm install hexo-symbols-count-time --save |
站點配置文件
1 | symbols_count_time: |
主題配置文件
1 | # Post wordcount display settings |
效果如圖
修改 主題配置文件
1 | # 把 enable: 設爲true |
只需要把 主題配置文件
的相關資料改爲false就行
1 | footer: |
效果如圖
修改custom.styl
文件,具體為themes/next/source/css/_custom/custom.styl
1 | // 為文章添加陰影效果 |
效果如圖
在站點根目錄,打開Git Bash,安裝hexo-helper-live2d
1 | npm install --save hexo-helper-live2d |
站點配置文件
或者主題配置文件
添加以下内容1 | live2d: |
1 | npm install {packagename} |
如效果圖所示的寵物名為haruto, 則為 npm install live2d-widget-model-haruto
,其他寵物包點擊live2d-widget-models。如果需要修改寵物的位置,可以在display
下添加
1 | # 水平位置 |
詳細内容可參考hexo-helper-live2d
效果如圖
在themes/layout/_parrials/footer.swig
中添加
1 | <span id="sitetime"></span> |
把代碼放在你想要的位置,插入位置不同,效果顯示的位置也會不同。若想要顯示為簡體或者英文,只要把對應的文字修改就行。
效果如圖
在next/layout/page.swig
中,找到
1 | <div class="tag-cloud-tags"> |
如果你想標籤頁先顯示標簽云,再顯示基本的標籤頁,可以在這段代碼之前添加
1 | {% if site.tags.length > 1 %} |
如果是先顯示預設的標籤頁,再顯示標簽云,則把上面代碼添加到後面。
如果你只想顯示標簽云就行,可以把
1 | <div class="tag-cloud-tags"> |
刪掉就行。
Next主題默認的設置,兩邊留白的區域很大。當然我們可以修改設置
在themes\next\source\css\_custom
的custom.styl
添加下面參數
1 | // 屏幕寬度小於1600px |
修改對應的參數就行,此方法不適用於Pisces主題。
找到博客根目錄,打開scripts文件夾(沒有的話,自己創建一個)。
創建一個JavaScript文件,可任意命名。
打開所創建的JavaScript文件,輸入以下内容
1 | var spawn = require('child_process').exec; |
注意: markdown編輯器絕對路徑 格式為(例如打開Typora)
1 | C:\\Program Files\\Typora\\Typora.exe |
一個在綫的聯係功能:DaoVoice
點擊 DaoVoice ,並點擊注冊。可以填入邀請碼:98657237
得到 app_id
修改next\layout_partials\head\head.swig 文件, 添加以下内容
1 | {% if theme.daovoice %} |
在主題配置文件
中,添加以下内容
# daovoice 配置 daovoice: true daovoice_app_id: # 填入剛才的app_id
hexo clean && hexo g && hexo d 就能看到效果了,網頁右下角多了個 圖標
具體修改圖表的樣式,位置。可以在daovoice網頁中 應用設置-聊天設置 中配置
]]>1 | buildscript { |
兩處的repositories的閉包中都聲明了jcenter() 這行配置。
jcenter是一個代碼托管倉庫,很多Androdi開源項目都會選擇將代碼托管到jcenter上,聲明了這個配置之後,可以在項目中引用任何jcenter上的開源項目。
dependencies閉包使用classpath聲明一個gradle插件。gradle并不是專門為構建android項目而開發,使用它時,需要聲明com.android.tools.build:gradle+版本號
1 |
|
第一行是應用了一個插件,一般有2個值可選:
應用程序模塊和庫模塊的最大區別是,一個可以直接運行,一個衹能作爲代碼庫依附于別的應用程序模塊來運行。
buildTypes閉包用於指定生成安裝文件的相關配置。通常衹有2個子閉包:debug和release
proguard-android.txt
是在Android SDK目錄下的,裏面是所有項目通用的混淆規則。proguard-rules.pro
是在當前項目根目錄下的,裏面可以編寫當前項目特有的混淆規則
通過Android Studio直接運行項目生成的都是測試版安裝文件
dependencies閉包
指定當前項目所有的依賴關係
通常Android Studio項目一共有3種依賴方式:
可以對本地的jar包或目錄添加依賴關係
可以對項目中的庫模塊添加依賴關係
可以對jcenter庫上的開源項目添加依賴關係
compile 'com.android.support:appcompat-v7:25.2.0'
標準的遠程依賴庫格式com.android.support
是域名部分,用於和其他公司的庫做區分。appcompat-v7
是組名稱,用於和同一個公司中不同的庫做區分。25.2.0
是版本號,用於和同一個庫不同的版本做區分。
主要放置的都是Android studio自動生成的一些文件。
項目的代碼資源等內容都在這個目錄
包含gradle wrapper的配置文件
用來將指定的目錄或文件排除在版本控制之外的
這是項目全局的gradle構建腳本。
這個文件是全局的gradle的配置文件,在這裏配置的屬性將會影響到項目中所有的gradle編譯腳本。
這兩個文件是用來在令行介面中執行gradle 命令的,其中gradlew 是在linux和mac 系統中使用,而gradlew.bat是在windows系統中使用。
用來指定本機中的Android sdk路徑,通常內容都是自動生成,我們並不需要修改。
用於指定項目中所有引入的模塊。通常情況下模塊的引入都是自動完成的,需要我們手動去修改的這個文件的場景可能比較少。
.iml文件是所有IntelliJ IDEA 項目都會自動生成的一個文件,用於標識這是一個IntelliJ IDEA項目,我們不需要修改這個文件中的任何內容。
主要是包含了一些在編譯中自動生成的文件。
如果你的項目中使用了第三方jar包,就需要把這些jar包都放在libs目錄下,放在這個目錄下的jar包都會被自動添加到構建路徑里去。
用來編寫Android Test測試用例的,可以對項目進行一些自動化測試。
放置java代码的地方
為 resource 的縮寫,專案所需的 UI 相關檔案,也就是非程式的資源,如 layout、圖像與文字。
整個Android項目的配置文件,在程序中自定義的所有四大組建都需要在這個文件裏註冊,另外還可以在這個文件中給應用程序添加權限聲明。
用來編寫Unit Test測試用例的,是對項目進行自動化測試的另一種方式。
用來將指定的目錄或文件排除在版本控制之外的.
這首app模塊的gradle構建腳本,這個文件中會指定很多項目構建相關的配置。
這個文件用於指定項目代碼的混淆規則,當代碼開發完成後打開安裝包文件,如果不希望代碼被別人破解,通常會將代碼進行混淆,從而讓破解者難以閱讀。
]]>verbose 級別,用於打印那些最爲瑣碎的、意義最小的日志信息。級別最低的一種。
debug 級別,用於打印一些調試信息。
info 級別,用於打印一些比較重要的數據可以分析用戶行爲的數據。
warm 級別,用於打印一些警告信息,提示程序在這個地方可能會有潛在的風險,最好去修復一下。
error 級別,用於打印程序中的錯誤信息。
]]>