前一段時間一直在不斷地面試,無奈我的技術能力有限、項目經驗缺少,最終都沒有進入到HR面試環節,全~~掛~~了~~javascript
面試了這麼多,結果不是太好,有點兒受打擊,也促使我近期靜下心來反思本身的問題:哪些技術知識掌握的還不錯,哪些還有待提升,哪些是須要去惡補的。php
阿里面試了三個部門,都是在二面掛的,網易和滴滴也是各兩輪技術面試,加一塊兒就是十次面試經歷。在此回憶總結一下,既是給社區朋友的一個參考,反饋社區,更是給本身一個好好的總結。css
HTML5新增了哪些內容或API,使用過哪些 html
新增特性整理前端
(1)文檔類型vue
<!DOCTYPE HTML>
之因此如此簡單,是由於HTML5再也不是SGML( Standard Generalized Markup language,標準通用標記語言)的一部分,而是獨立的標記語言,不須要再考慮文檔類型html5
(2)字符集java
<meta charset="UTF-8">
只須要utf-8便可node
<header>
<h1>HTML5新結構<h1/>
<nav>導航部分</nav>
<p></p>
</header>
<section> <h1></h1> <p>再也不全是div</p>
。。。 </section>
<footer>
</footer>
section元素 能夠認爲div是section元素,一個普通的分塊元素,可用來定義網站中的特定的可區別的區域mysql
header元素包括標題,logo,導航和其餘頁眉的內容,能夠在網站上加多個header,就像給內容加多個標題
hgroup元素 即將標題進行分組的元素
footer元素版權聲明和做者信息,包涵一些連接
nav元素主要用於主導航菜單
article元素獨立成文且以其餘格式重用的內容應該置於一個article元素中
aside元素用途包涵內容周圍的相關內容
<figure> <p>圖片</p> <img src="img1.jpg" width="100" height="100"> </figure>
figure元素一個典型用途是包含圖像,代碼和其餘內容對主要內容進行說明,刪除不會影響主內容
figcaption元素主要用於figure的標題
mark元素突出顯示以表示引用的內容,或者突出顯示與用戶當前活動相關的內容,他不一樣於en或strong元素
time元素,當須要在內容中顯示時間或者日期時,則建議使用time元素
time元素能夠包涵兩個屬性,一個datetime表示在元素中指定的確切日期和世家,pubdate表示文章或者整個文檔發佈時time元素所指定的日期和時間
meter元素用於定義度量衡,規定最大最小寬高,一般要結合css一塊兒做用,效果以下:
progress元素用於定義一個進度條,有max(完成值)和value(進度條當前值)兩個屬性。
用於表單中組織控件列表,經常使用屬性以下:
定義一個選擇列表的例子:
<menu> <li><input type="checkbox"/>a</li> <li><input type="checkbox"/>b</li> <li><input type="checkbox"/>c</li> </menu> <!--目前主流瀏覽器都不支持-->
用於指定異步執行的腳本
用於描述文檔或文檔某個部分的細節
<details> <summary>details</summary> <p>用於描述文檔細節<p> </details>
效果:
展開後:
HTML5 擁有多個新的表單輸入類型。這些新特性提供了更好的輸入控制和驗證。
autocomplete 屬性規定 form 或 input 域應該擁有自動完成功能。
當用戶在自動完成域中開始輸入時,瀏覽器應該在該域中顯示填寫的選項:
實例:
<form action="/example/html5/demo_form.asp" method="get" autocomplete="on">
First name:<input type="text" name="fname" /><br />
Last name: <input type="text" name="lname" /><br />
E-mail: <input type="email" name="email" autocomplete="off" /><br />
<input type="submit" />
</form>
<p>請填寫並提交此表單,而後重載頁面,來查看自動完成功能是如何工做的。</p>
結果:
1)canvas 能夠動態地繪製各類效果精美的圖形,結合js就能讓網頁圖形動起來
2)SVG 繪製可伸縮的矢量圖形
3)audio和 video 能夠不依賴任何插件播放音頻和視頻
離線緩存 Web Storage(爲HTML5開發移動應用提供了基礎)
傳統的web應用程序中,數據處理都由服務器處理,html只負責展現數據,cookie只能存儲少許的數據,跨域通訊只能經過web服務器。
HTML5擴充了文件存儲的能力,多達5MB,支持WebSQL等輕量級數據庫,能夠開發支持離線web應用程序。
HTML5 Web Storage API能夠看作是增強版的cookie,不受數據大小限制,有更好的彈性以及架構,能夠將數據寫入到本機的ROM中,還能夠在關閉瀏覽器後再次打開時恢復數據,以減小網絡流量。
同時,這個功能算得上是另外一個方向的後臺「操做記錄」,而不佔用任何後臺資源,減輕設備硬件壓力,增長運行流暢性。
新增Geolocation API,能夠經過瀏覽器獲取用戶的地理位置,再也不須要藉助第三方地址數據庫或專業的開發包,提供很大的方便。
標籤 | 標記意義及用法分析/示例 | 屬性/屬性值/描述 | |||||||||||||||||||||
<article> | 定義獨立的內容,如論壇帖子、報紙文章、博客條目、用戶評論等內容。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<aside> | 定義兩欄或多欄頁面的側邊欄內容,如聯繫咱們、客服、網站公告等內容。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<audio> | 定義音頻內容,如音樂或其餘音頻流。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<audio src=」audio.wav」> 您的瀏覽器不支持 audio 標籤。(注:能夠在開始標籤和結束標籤之間加上此文本內容,這樣若瀏覽器不支持此元素,就能夠顯示出這個信息。) </audio> |
|||||||||||||||||||||||
<canvas> | 定義圖形,如圖表和其餘圖像。(注:<canvas> 只是圖形容器,咱們必須使用腳原本繪製圖形。) |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<canvas id=」myCanvas」></canvas> <script type=」text/javascript」> var canvas=document.getElementById(‘myCanvas’); var ctx=canvas.getContext(‘2d’); ctx.fillStyle=’#FFFF00′; ctx.fillRect(0,0,20,30); </script> |
|||||||||||||||||||||||
<command> | 標記定義一個命令按鈕,好比單選按鈕、複選框或按鈕。只有當 command 元素位於 menu 元素內時,該元素纔是可見的。不然不會顯示這個元素,可是能夠用它規定鍵盤快捷鍵。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<menu> <command onclick=」alert(‘Hello!’)」>Click here.</command> </menu> |
|||||||||||||||||||||||
<datalist> | 定義選項列表,需與 input 元素配合使用,經過input 元素的 list 屬性來綁定,用來定義 input 可能的值。datalist 及其選項不會被顯示出來,它僅僅是合法的輸入值列表。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<input id=」fruits」 list=」fruits」 /> <datalist id=」fruits」> <option value=」Apple」> <option value=」Banana」> </datalist> |
|||||||||||||||||||||||
<details> | 用於描述文檔或文檔某個部分的細節。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<details> <summary>Some title.</summary> <p>Some details about the title.</p> </details> |
|||||||||||||||||||||||
<embed> | 定義外部的可交互的內容或插件。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<embed src=」someone.swf」 /> | |||||||||||||||||||||||
<figure> | 定義一組媒體內容(圖像、圖表、照片、代碼等)以及它們的標題。若是被刪除,則不該對文檔流產生影響。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<figure> <p>The title of the image.</p> <img src=」someimage.jpg」 width=」100″ height=」50″ /> </figure> |
|||||||||||||||||||||||
<footer> | 定義一個頁面或一個區域的頁腳。可包含文檔的做者姓名、創做日期或者聯繫信息。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<header> | 定義一個頁面或一個區域的頭部。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<hgroup> | 定義文件中一個區塊的相關信息,使用 <hgroup> 標籤對網頁或區段(section)的標題進行組合。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<hgroup> <h1>Welcome my world!</h1> <h2>This is a title.</h2> </hgroup> |
|||||||||||||||||||||||
<keygen> | 定義表單裏一個生成的鍵值。規定用於表單的密鑰對生成器字段。當提交表單時,私鑰存儲在本地,公鑰發送到服務器。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<form action=」demo_keygen.asp」 method=」get」> Username: <input type=」text」 name=」usr_name」 /> Encryption: <keygen name=」security」 /> <input type=」submit」 /> </form> |
|||||||||||||||||||||||
<mark> | 定義有標記的文本。請在須要突出顯示文本時使用此標籤。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<p>I like <mark>apple</mark> most.</p> | |||||||||||||||||||||||
<meter> | 定義度量衡。僅用於已知最大和最小值的度量。(注:必須定義度量的範圍,既能夠在元素的文本中,也能夠在 min/max 屬性中定義。) |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<meter min=」0″ max=」10″>2</meter> <meter>2 out of 5</meter> <meter>10%</meter> |
|||||||||||||||||||||||
<nav> | 定義導航連接。(注:若是文檔中有「先後」按鈕,則應該把它放到 <nav> 元素中。) | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<nav> <a href=」index.asp」>Home</a> <a href=」Previous.asp」>Previous</a> <a href=」Next.asp」>Next</a> </nav> |
|||||||||||||||||||||||
<output> | 定義不一樣類型的輸出,好比腳本的輸出。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<progress> | 定義任務(以下載)的過程,能夠使用此標籤來顯示 JavaScript 中耗費時間的函數的進度。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<progress> <span id=」progress」>15</span>% </progress> |
|||||||||||||||||||||||
<ruby> | 定義 ruby 註釋(中文註音或字符)。在東亞使用,顯示的是東亞字符的發音。ruby 元素由一個或多個字符(須要一個解釋/發音)和一個提供該信息的 rt 元素組成,還包括可選的 rp 元素,定義當瀏覽器不支持 「ruby」 元素時顯示的內容。 | 支持HTML5的全局屬性和事件屬性。 | |||||||||||||||||||||
<section> | 定義文檔中的節(section、區段)。好比章節、頁眉、頁腳或文檔中的其餘部分。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<source> | 爲媒介元素(好比 <video> 和 <audio>)定義媒介資源。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<time> | 定義一個日期/時間,該元素可以以機器可讀的方式對日期和時間進行編碼,舉例說,用戶代理可以把生日提醒或排定的事件添加到用戶日程表中,搜索引擎也可以生成更智能的搜索結果。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<p>你們都是早上 <time>9:00</time> 上班。</p> <p><time datetime=」2012-01-01″>元旦</time>晚會。</p> |
|||||||||||||||||||||||
<video> | 定義視頻,好比電影片斷或其餘視頻流。 |
支持HTML5的全局屬性和事件屬性。 |
|||||||||||||||||||||
<video src=」movie.ogg」 controls=」controls」> 您的瀏覽器不支持 video 標籤。(注:能夠在開始標籤和結束標籤之間加上此文本內容,這樣若瀏覽器不支持此元素,就能夠顯示出這個信息。) </video> |
input和textarea的區別
1.必定要指定type的值爲text;
2.經過size屬性指定顯示字符的長度,value屬性指定初始值,Maxlength屬性指定文本框能夠輸入的最長長度;
1 <input type="text" value="入門狗" size="10" Maxlength="15">
<textarea>元素
1.使用<textarea></textarea>標籤對
2.內容放在<textarea></textarea>標籤對中
3.使用row、col指定大小
1 <textarea row="3" col="4">入門狗的博客園</textarea>
區別:一個是單行文本框,一個是多行文本框。
用一個div模擬textarea的實現
若是你在BODY裏面加上contenteditable="true",能夠發現該屬性是多麼的神奇。所以咱們能夠給HTML標籤設置contenteditable="true"屬性則能夠對該標籤進行編輯。
contenteditable屬性兼容全部瀏覽器(IE6以前的版本是否兼容未測試)
在有些時候咱們徹底能夠用DIV去替代input或者textarea來達到一樣的效果,例如,在使用ajax的時候,在提交表單時咱們能夠獲取DIV的內容。
細心的人會發現,QQ空間中的發表說說的文本框其實就是一個DIV,而非textarea文本框。
Div+CSS如何模擬textarea文本域高度自適應以達到html5標準的contenteditable屬性
主要經過爲標籤添加HTML5中的contenteditable屬性達到此效果(contenteditable:規定是否容許用戶編輯內容),很棒的是,此屬性IE也會支持,因此不用再爲兼容問題太去糾結了。
附:
在FF瀏覽器下,容器獲取焦點時,光標的高度會與容器的高度同樣高或者不顯示光標. 此時若爲容器默認加個佔位符,好比<br/>或 能夠解決這一問題.
如今煜子給你們介紹另外一種可編輯可自動適應高度,但又不用加js代碼的好方法。讓你們開開眼界,煜子直接使用DIV也能夠當文本框用,相似於TextArea文本框,更重要的是DIV的用戶體驗更完美更帥。
Html中的contentEditable屬性能夠打開某些元素的可編輯狀態。也許你沒用過contentEditable屬性,甚至從未據說過,contentEditable的做用至關神奇。可讓div或整個網頁,以及span等等元素設置爲可寫.咱們最經常使用的輸入文本內容即是input與textarea 使用contentEditable屬性後,能夠在div,table,p,span,body,等等不少元素中輸入內容.特別是contentEditable已在html5標準中獲得有效的支持。你們來見證一下吧。
設置contentEditable=」true」屬性後,是否是至關的神奇。哈哈…
DEMO頁面: http://demo.jb51.net/js/2014/ContentEditable/
咱們來個特效吧,經過開啓div元素編輯,是否能插入圖片,這是須要用到js了。
代碼以下:
左右佈局:左邊定寬、右邊自適應,很多於3種方法
左側固定寬,右側自適應屏幕寬;
左右兩列,等高佈局;
左右兩列要求有最小高度,例如:200px;(當內容超出200時,會自動以等高的方式增高)
要求不用JS或CSS行爲實現;
仔細分析試題要求,要達到效果其實也並非太難,只是給人感受像有點蛋疼的問題同樣。可是你仔細看後你會以爲不是那麼回事:
左邊固定,右邊自適應佈局,這個第一點應該來講是很是的容易,實現的方法也是至關的多,那麼就能夠說第一條要求已不是什麼要求了;
左右兩列等高佈局,這一點相對來講要複雜一些,不過你要是瞭解了怎麼實現等高佈局,那麼也是不難。我我的認爲這個考題關鍵之處就在考這裏,考你如何實現等高佈局;因此這一點你須要整明白如何實現;
至於第三條要求,應該來講是很方面的,咱們隨處均可以看到實現最小高度的代碼;
第四條這個要求我想是考官想讓咱們面試的人不能使用js來實現等高佈局和最小高度的功能。
上面簡單的分析了一下實現過程,那麼最終關鍵之處應該是就是「讓你的代碼要能同時實現兩點,其一就是左邊固定,右邊自適應的佈局;其二就是實現兩列等高的佈局」,若是這兩個功能完成,那麼你也就能夠交做業了。那麼下面咱們就先來看看這兩 點是如合實現:
1、兩列布局:左邊固定寬度,右邊自適應寬度
這樣的佈局,其實不是難點,我想不少同窗都有實現過,那麼我就在此稍微介紹兩種經常使用的方法:
方法一:浮動佈局
這種方法我採用的是左邊浮動,右邊加上一個margin-left值,讓他實現左邊固定,右邊自適應的佈局效果
HTML Markup
<div id="left">Left sidebar</div>
<div id="content">Main Content</div>
CSS Code
<style type="text/css">
*{
margin: 0;
padding: 0;
}
#left {
float: left;
width: 220px;
green;
}
#content {
orange;
margin-left: 220px;/*==等於左邊欄寬度==*/
}
</style>
上面這種實現方法最關鍵之處就是自適應寬度一欄「div#content」的「margin-left」值要等於固定寬度一欄的寬度值,你們能夠點擊查看上面代碼的DEMO
方法二:浮動和負邊距實現
這個方法採用的是浮動和負邊距來實現左邊固定寬度右邊自適應寬度的佈局效果,你們能夠仔細對比一下上面那種實現方法,看看二者有什麼區別:
HTML Markup
<div id="left">
Left Sidebar
</div>
<div id="content">
<div id="contentInner">
Main Content
</div>
</div>
CSS Code
*{
margin: 0;
padding: 0;
}
#left {
green;
float: left;
width: 220px;
margin-right: -100%;
}
#content {
float: left;
width: 100%;
}
#contentInner {
margin-left: 220px;/*==等於左邊欄寬度值==*/
orange;
}
這種方法看上去要稍微麻煩一點,不過也是很是常見的一種方法,你們能夠看看他的DEMO效果。感受一下,和前面的DEMO有什麼不一樣之處。
我 在這裏就只展現這兩種方法,你們確定還有別的實現方法,我就不在多說了,由於咱們今天要說的不是這個問題。上面完成了試題的第一種效果,那麼你們就要想辦 法來實現第二條要求——兩列等高佈局。這一點也是本次面試題相當重要的一點,若是你要是不清楚如何實現等高佈局的話,我建議您先閱讀本站的《八種建立等高 列布局》,裏面詳細介紹了八種等高佈局的方法,並附有相關代碼,並且咱們後面的運用中也使用了其中的方法。
如今關鍵的兩點都完成了,那麼咱們就須要實現第三條要求,實現最小高度的設置,這個方法很簡單:
min-height: 200px;
height: auto !important;
height: 200px;
上面的代碼就輕鬆的幫咱們實現了跨瀏覽器的最小高度設置問題。這樣一來,咱們能夠交做業了,也完面了這個面試題的考試。爲了讓你們更能形象的瞭解,我在這裏爲你們準備了五種不一樣的實現方法:
方法一:
別的很少說,直接上代碼,或者參考在線DEMO,下面全部的DEMO都有HTML和CSS代碼,感興趣的同窗本身慢慢看吧。
HTML Markup
<div id="container">
<div id="wrapper">
<div id="sidebar">Left Sidebar</div>
<div id="main">Main Content</div>
</div>
</div>
CSS Code
<style type="text/css">
* {
margin: 0;
padding: 0;
}
html {
height: auto;
}
body {
margin: 0;
padding: 0;
}
#container {
background: #ffe3a6;
}
#wrapper {
display: inline-block;
border-left: 200px solid #d4c376;/*==此值等於左邊欄的寬度值==*/
position: relative;
vertical-align: bottom;
}
#sidebar {
float: left;
width: 200px;
margin-left: -200px;/*==此值等於左邊欄的寬度值==*/
position: relative;
}
#main {
float: left;
}
#maing,
#sidebar{
min-height: 200px;
height: auto !important;
height: 200px;
}
</style>
查看在線DEMO。
方法二
HTML Markup
<div id="container">
<div id="left" class="aside">Left Sidebar</div>
<div id="content" class="section">Main Content</div>
</div>
CSS Code
<style type="text/css">
*{margin: 0;padding: 0;}
#container {
overflow: hidden;
}
#left {
background: #ccc;
float: left;
width: 200px;
margin-bottom: -99999px;
padding-bottom: 99999px;
}
#content {
background: #eee;
margin-left: 200px;/*==此值等於左邊欄的寬度值==*/
margin-bottom: -99999px;
padding-bottom: 99999px;
}
#left,
#content {
min-height: 200px;
height: auto !important;
height: 200px;
}
</style>
查看在線的DEMO。
方法三:
HTML Markup
<div id="container">
<div id="content">Main Content</div>
<div id="sidebar">Left Sidebar</div>
</div>
CSS Code
*{margin: 0;padding: 0;}
#container{
overflow:hidden;
padding-left:220px; /* 寬度大小等與邊欄寬度大小*/
}
* html #container{
height:1%; /* So IE plays nice */
}
#content{
width:100%;
border-left:220px solid #f00;/* 寬度大小等與邊欄寬度大小*/
margin-left:-220px;/* 寬度大小等與邊欄寬度大小*/
float:right;
}
#sidebar{
width:220px;
float:right;
margin-left:-220px;/* 寬度大小等與邊欄寬度大小*/
}
#content,
#sidebar {
min-height: 200px;
height: auto !important;
height: 200px;
}
查看在線DEMO效果。
方法四:
HTML Markup
<div id="container2">
<div id="container1">
<div id="col1">Left Sidebar</div>
<div id="col2">Main Content</div>
</div>
</div>
CSS Code
*{padding: 0;margin:0;}
#container2 {
float: left;
width: 100%;
background: orange;
position: relative;
overflow: hidden;
}
#container1 {
float: left;
width: 100%;
background: green;
position: relative;
left: 220px;/* 寬度大小等與邊欄寬度大小*/
}
#col2 {
position: relative;
margin-right: 220px;/* 寬度大小等與邊欄寬度大小*/
}
#col1 {
width: 220px;
float: left;
position: relative;
margin-left: -220px;/* 寬度大小等與邊欄寬度大小*/
}
#col1,#col2 {
min-height: 200px;
height: auto !important;
height: 200px;
}
查看在線DEMO。
方法五:
HTML Markup
<div id="container1">
<div id="container">
<div id="left">Left Sidebar</div>
<div id="content">
<div id="contentInner">Main Content</div>
</div>
</div>
</div>
CSS Code
*{padding: 0;margin: 0;}
#container1 {
float: left;
width: 100%;
overflow: hidden;
position: relative;
#dbddbb;
}
#container {
orange;
width: 100%;
float: left;
position: relative;
left: 220px;/* 寬度大小等與邊欄寬度大小*/
}
#left {
float: left;
margin-right: -100%;
margin-left: -220px;/* 寬度大小等與邊欄寬度大小*/
width: 220px;
}
#content {
float: left;
width: 100%;
margin-left: -220px;/* 寬度大小等與邊欄寬度大小*/
}
#contentInner {
margin-left: 220px;/* 寬度大小等與邊欄寬度大小*/
overflow: hidden;
}
#left,
#content {
min-height: 200px;
height: auto !important;
height: 200px;
}
查看在線DEMO。
針對上面的面試題要求,我一共使用了五種不一樣的方法來實現,通過測試都能在各瀏覽器中運行,最後我有幾點須要特別提出:
上面全部DEMO中,要注意其方向性的配合,而且值要統一,若是您想嘗試使用本身佈局須要的寬度值,請對照相關代碼環節進行修改;
上面全部DEMO中,沒有設置他們之間的間距,若是您想讓他們之間有必定的間距,有兩種方法可能實現,其一在上面的DEMO基礎上修改相關參數,其二,在相應的裏面加上"div"標籤,並設置其「padding」值,這樣更安全,不至於打破你的佈局
由於咱們這裏有一列使用了自適應寬度,在部分瀏覽器下,當瀏覽器屏幕拉至到必定的大小時,給咱們帶來的感受是自適應寬度那欄內容像是被隱藏,在你的實際項目中最好能在「body」中加上一個「min-width」的設置。
CSS3用過哪些新特性
寫過 CSS 的人應該對 CSS 選擇器不陌生,咱們所定義的 CSS 屬性之因此能應用到相應的節點上,就是由於 CSS 選擇器模式。參考下述代碼:
1
2
3
4
5
|
Body > .mainTabContainer div > span[5]{
Border: 1px solod red;
Background-color: white;
Cursor: pointer;
}
|
此處的 CSS 選擇器即:「body > .mainTabContainer div span[5]」 表明這這樣一條路徑:
1. 「body」標籤直接子元素裏 class 屬性值爲「mainTabContainer」的全部元素 A
2. A 的後代元素(descendant)裏標籤爲 div 的全部元素 B
3. B 的直接子元素中的第 5 個標籤爲 span 的元素 C
這個 C 元素(可能爲多個)即爲選擇器定位到的元素,如上的 CSS 屬性也會所有應用到 C 元素上。
以上爲 CSS2 及以前版本所提供的主要定位方式。如今,CSS3 提供了更多更加方便快捷的選擇器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
Body > .mainTabContainer tbody:nth-child(even){
Background-color: white;
}
Body > .mainTabContainer tr:nth-child(odd){
Background-color: black;
}
:not(.textinput){
Font-size: 12px;
}
Div:first-child{
Border-color: red;
}
|
如上所示,咱們列舉了一些 CSS3 的選擇器,在咱們平常的開發中可能會常常用到,這些新的 CSS3 特性解決了不少咱們以前須要用 JavaScript 腳本才能解決的問題。
tbody: nth-child(even), nth-child(odd):此處他們分別表明了表格(tbody)下面的偶數行和奇數行(tr),這種樣式很是適用於表格,讓人能很是清楚的看到表格的行與行之間的差異,讓用戶易於瀏覽。
: not(.textinput):這裏即表示全部 class 不是「textinput」的節點。
div:first-child:這裏表示全部 div 節點下面的第一個直接子節點。
除此以外,還有不少新添加的選擇器:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
E:nth-last-child(n)
E:nth-of-type(n)
E:nth-last-of-type(n)
E:last-child
E:first-of-type
E:only-child
E:only-of-type
E:empty
E:checked
E:enabled
E:disabled
E::selection
E:not(s)
|
這裏不一一介紹。學會利用這些新特性能夠極大程度的減小咱們的無畏代碼,而且大幅度的提升程序的性能。
Font-face 能夠用來加載字體樣式,並且它還可以加載服務器端的字體文件,讓客戶端顯示客戶端所沒有安裝的字體。
先來看一個客戶端字體簡單的案例:
1
|
<
p
><
font
face
=
"arial"
>arial courier verdana</
font
></
p
>
|
咱們能夠經過這種方式直接加載字體樣式,由於這些字體(arial)已經安裝在客戶端了。清單 3 這種寫法的做用等同於清單 4:
1
|
<
p
><
font
style
=
"font-family: arial"
>arial courier verdana</
font
></
p
>
|
相信這種寫法你們應該再熟悉不過了。
接下來咱們看看如何使用服務端字體,即:未在客戶端安裝的字體樣式。
參看以下代碼:
1
2
3
4
5
6
7
8
9
10
11
|
@font-face {
font-family: BorderWeb;
src:url(BORDERW0.eot);
}
@font-face {
font-family: Runic;
src:url(RUNICMT0.eot);
}
.border { FONT-SIZE: 35px; COLOR: black; FONT-FAMILY: "BorderWeb" }
.event { FONT-SIZE: 110px; COLOR: black; FONT-FAMILY: "Runic" }
|
清單 5 中聲明的兩個服務端字體,其字體源指向「BORDERW0.eot」和「RUNICMT0.eot」文件,並分別冠以「BorderWeb」和「Runic」的字體名稱。聲明以後,咱們就能夠在頁面中使用了:「 FONT-FAMILY: "BorderWeb" 」 和 「 FONT-FAMILY: "Runic" 」。
這種作法使得咱們在開發中若是須要使用一些特殊字體,而又不肯定客戶端是否已安裝時,即可以使用這種方式。
先來看看 word-wrap 屬性,參考下述代碼:
1
2
3
4
5
6
7
8
|
<
div
style
=
"width:300px; border:1px solid #999999; overflow: hidden"
>
wordwrapbreakwordwordwrapbreakwordwordwrapbreakwordwordwrapbreakword
</
div
>
<
div
style
=
"width:300px; border:1px solid #999999; word-wrap:break-word;"
>
wordwrapbreakwordwordwrapbreakwordwordwrapbreakwordwordwrapbreakword
</
div
>
|
比較上述兩段代碼,加入了「word-wrap: break-word」,設置或檢索噹噹前行超過指定容器的邊界時是否斷開轉行,文字此時已被打散。因此可見以下的差異:
知道了 word-wrap 的原理,咱們再來看看 text-overflow,其實它與 word-wrap 是協同工做的,word-wrap 設置或檢索噹噹前行超過指定容器的邊界時是否斷開轉行,而 text-overflow 則設置或檢索噹噹前行超過指定容器的邊界時如何顯示,見以下示例:
1
2
3
4
5
6
7
8
9
10
11
12
|
.clip{text-overflow:clip; overflow:hidden; white-space:nowrap;
width:200px;background:#ccc;}
.ellipsis{text-overflow:ellipsis; overflow:hidden; white-space:nowrap;
width:200px; background:#ccc;}
<
div
class
=
"clip"
>
不顯示省略標記,而是簡單的裁切條
</
div
>
<
div
class
=
"ellipsis"
>
當對象內文本溢出時顯示省略標記
</
div
>
|
如清單 7 所示,這裏咱們均使用「overflow: hidden」,對於「text-overflow」屬性,有「clip」和「ellipsis」兩種可供選擇。見圖 3 的效果圖。
這裏咱們能夠看到,ellipsis 的顯示方式比較人性化,clip 方式比較傳統,咱們能夠依據需求進行選擇。
CSS3 裏面開始支持對文字的更深層次的渲染,咱們來看看下面的例子:
1
2
3
4
5
|
div {
-webkit-text-fill-color: black;
-webkit-text-stroke-color: red;
-webkit-text-stroke-width: 2.75px;
}
|
這裏咱們主要以 webkit 內核瀏覽器爲例,清單 8 的代碼效果如圖 4:
Text-fill-color: 文字內部填充顏色
Text-stroke-color: 文字邊界填充顏色
Text-stroke-width: 文字邊界寬度
CSS3 如今已經能夠作簡單的佈局處理了,這個 CSS3 新特性又一次的減小了咱們的 JavaScript 代碼量,參考以下代碼:
1
2
3
4
5
6
7
8
9
10
|
.multi_column_style{
-webkit-column-count: 3;
-webkit-column-rule: 1px solid #bbb;
-webkit-column-gap: 2em;
}
<
div
class
=
"multi_column_style"
>
.................
.................
</
div
>
|
這裏咱們仍是以 webkit 內核瀏覽器爲例:
Column-count:表示佈局幾列。
Column-rule:表示列與列之間的間隔條的樣式
Column-gap:表示列於列之間的間隔
清單 9 的代碼效果圖如圖 5:
關於顏色,CSS3 已經提供透明度的支持了:
1
2
|
color: rgba(255, 0, 0, 0.75);
background: rgba(0, 0, 255, 0.75);
|
這裏的「rgba」屬性中的「a」表明透明度,也就是這裏的「0.75」,同時 CSS3 還支持 HSL 顏色聲明方式及其透明度:
1
|
color: hsla( 112, 72%, 33%, 0.68);
|
對於 border,CSS3 提供了圓角的支持:
1
|
border-radius: 15px;
|
參見下面圓角效果:
左上(0% 0%)到右上(0% 100%)即從左到右水平漸變:
1
|
background-image:-webkit-gradient(linear,0% 0%,100% 0%,from(#2A8BBE),to(#FE280E));
|
這裏 linear 表示線性漸變,從左到右,由藍色(#2A8BBE)到紅色(#FE280E)的漸變。效果圖以下:
同理,也能夠有從上到下,任何顏色間的漸變轉換:
還有複雜一點的漸變,如:水平漸變,33% 處爲綠色,66% 處爲橙色:
1
2
|
background-image:-webkit-gradient(linear,0% 0%,100% 0%,from(#2A8BBE),
color-stop(0.33,#AAD010),color-stop(0.33,#FF7F00),to(#FE280E));
|
這裏的「color-stop」爲拐點,可見效果圖:
接下來咱們要介紹徑向漸變(radial),這不是從一個點到一個點的漸變,而從一個圓到一個圓的漸變。不是放射漸變而是徑向漸變。來看一個例子:
1
2
|
backgroud:
-webkit-gradient(radial,50 50,50,50 50,0,from(black),color-stop(0.5,red),to(blue));
|
前面「50,50,50」是起始圓的圓心座標和半徑,「50,50,0」藍色是目標圓的圓心座標和半徑,「color-stop(0.5,red)」是斷點的位置和色彩。這裏須要說明一下,和放射由內至外不同,徑向漸變恰好相反,是由外到內的漸變。清單 15 標識的是兩個同心圓,外圓半徑爲 50px,內圓半徑爲 0,那麼就是從黑色到紅色再到藍色的正圓形漸變。下面就是這段代碼的效果:
若是咱們給目標源一個大於 0 半徑,您會看到另一個效果:
1
2
|
backgroud:
-webkit-gradient(radial,50 50,50,50 50,10,from(black),color-stop(0.5,red),to(blue));
|
這裏咱們給目標圓半徑爲 10,效果圖以下:
您能夠看到,會有一個半徑爲 10 的純藍的圓在最中間,這就是設置目標圓半徑的效果。
如今我再改變一下,再也不是同心圓了,內圓圓心向右 20px 偏移。
1
2
|
backgroud:
-webkit-gradient(radial,50 50,50,70 50,10,from(black),color-stop(0.5,red),to(blue));
|
這裏咱們給目標圓半徑仍是 10,可是圓心偏移爲「70,50」(起始圓圓心爲「50,50」)效果圖以下:
想必您明白原理了,咱們能夠作一個來自頂部的漫射光,相似於開了盞燈:
1
|
backgroud:-webkit-gradient(radial,50 50,50,50 1,0,from(black),to(white));
|
其效果以下:
首先來講說陰影效果,陰影效果既可用於普通元素,也可用於文字,參考以下代碼:
1
2
3
4
5
6
7
|
.class1{
text-shadow:5px 2px 6px rgba(64, 64, 64, 0.5);
}
.class2{
box-shadow:3px 3px 3px rgba(0, 64, 128, 0.3);
}
|
設置很簡單,對於文字陰影:表示 X 軸方向陰影向右 5px,Y 軸方向陰影向下 2px, 而陰影模糊半徑 6px,顏色爲 rgba(64, 64, 64, 0.5)。其中偏移量能夠爲負值,負值則反方向。元素陰影也相似。參考一下效果圖:
接下來咱們再來談談反射,他看起來像水中的倒影,其設置也很簡單,參考以下代碼:
1
2
3
4
5
|
.classReflect{
-webkit-box-reflect: below 10px
-webkit-gradient(linear, left top, left bottom, from(transparent),
to(rgba(255, 255, 255, 0.51)));
}
|
設置也很簡單,你們主要關注「-webkit-box-reflect: below 10px」,他表示反射在元素下方 10px 的地方,再配上漸變效果,可見效果圖以下:
CSS3 多出了幾種關於背景(background)的屬性,咱們這裏會簡單介紹一下:
首先:「Background Clip」,該屬肯定背景畫區,有如下幾種可能的屬性:
* background-clip: border-box; 背景從 border 開始顯示 ;
* background-clip: padding-box; 背景從 padding 開始顯示 ;
* background-clip: content-box; 背景顯 content 區域開始顯示 ;
* background-clip: no-clip; 默認屬性,等同於 border-box;
一般狀況,咱們的背景都是覆蓋整個元素的,如今 CSS3 讓您能夠設置是否必定要這樣作。這裏您能夠設定背景顏色或圖片的覆蓋範圍。
其次:「Background Origin」,用於肯定背景的位置,它一般與 background-position 聯合使用,您能夠從 border、padding、content 來計算 background-position(就像 background-clip)。
* background-origin: border-box; 從 border. 開始計算 background-position;
* background-origin: padding-box; 從 padding. 開始計算 background-position;
* background-origin: content-box; 從 content. 開始計算 background-position;
還有,「Background Size」,經常使用來調整背景圖片的大小,注意別和 clip 弄混,這個主要用於設定圖片自己。有如下可能的屬性:
* background-size: contain; 縮小圖片以適合元素(維持像素長寬比)
* background-size: cover; 擴展元素以填補元素(維持像素長寬比)
* background-size: 100px 100px; 縮小圖片至指定的大小 .
* background-size: 50% 100%; 縮小圖片至指定的大小,百分比是相對包 含元素的尺寸 .
最後,「Background Break」屬性,CSS3 中,元素能夠被分紅幾個獨立的盒子(如使內聯元素 span 跨越多行),background-break 屬性用來控制背景怎樣在這些不一樣的盒子中顯示。
* background-break: continuous; 默認值。忽略盒之間的距離(也就是像元 素沒有分紅多個盒子,依然是一個總體一 樣)
* background-break: bounding-box; 把盒之間的距離計算在內;
* background-break: each-box; 爲每一個盒子單獨重繪背景。
這種屬性讓您能夠設定複雜元素的背景屬性。
最爲重要的一點,CSS3 中支持多背景圖片,參考以下代碼:
1
2
3
4
|
div {
background: url(src/zippy-plus.png) 10px center no-repeat,
url(src/gray_lines_bg.png) 10px center repeat-x;
}
|
此爲同一元素兩個背景的案例,其中一個重複顯示,一個不重複。參考一下效果圖:
盒子模型爲開發者提供了一種很是靈活的佈局方式,可是支持這一特性的瀏覽器並很少,目前只有 webkit 內核的新版本 safari 和 chrome 以及 gecko 內核的新版本 firefox。
下面咱們來介紹一下他是如何工做的,參考以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
<
div
class
=
"boxcontainer"
>
<
div
class
=
"item"
>
1
</
div
>
<
div
class
=
"item"
>
2
</
div
>
<
div
class
=
"item"
>
3
</
div
>
<
div
class
=
"item flex"
>
4
</
div
>
</
div
>
|
默認狀況下,若是「boxcontainer」和「item」兩個 class 裏面沒有特殊屬性的話,因爲 div 是塊狀元素,因此他的排列應該是這樣的:
下面,咱們加入相關 CSS3 盒子模型屬性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
.boxcontainer {
width: 1000px;
display: -webkit-box;
display: -moz-box;
-webkit-box-orient: horizontal;
-moz-box-orient: horizontal;
}
.item {
background: #357c96;
font-weight: bold;
margin: 2px;
padding: 20px;
color: #fff;
font-family: Arial, sans-serif;
}
|
注意這裏的「display: -webkit-box; display: -moz-box;」,它針對 webkit 和 gecko 瀏覽器定義了該元素的盒子模型。注意這裏的「-webkit-box-orient: horizontal;」,他表示水平排列的盒子模型。此時,咱們會看到以下效果:
細心的讀者會看到,「盒子」的右側多出來了很大一塊,這是怎麼回事呢?讓咱們再來看一個比較有特色的屬性:「flex」, 參考以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<
div
class
=
"boxcontainer"
>
<
div
class
=
"item"
>
1
</
div
>
<
div
class
=
"item"
>
2
</
div
>
<
div
class
=
"item"
>
3
</
div
>
<
div
class
=
"item flex"
>
4
</
div
>
</
div
>
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
}
|
您看到什麼區別了沒?在第四個「item 元素」那裏多了一個「flex」屬性,直接來看看效果吧:
第四個「item 元素」填滿了整個區域,這就是「flex」屬性的做用。若是咱們調整一下「box-flex」的屬性值,並加入更多的元素,見以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
<
div
class
=
"boxcontainer"
>
<
div
class
=
"item"
>
1
</
div
>
<
div
class
=
"item"
>
2
</
div
>
<
div
class
=
"item flex2"
>
3
</
div
>
<
div
class
=
"item flex"
>
4
</
div
>
</
div
>
.flex {
-webkit-box-flex: 1;
-moz-box-flex: 1;
}
.flex2 {
-webkit-box-flex: 2;
-moz-box-flex: 2;
}
|
咱們把倒數第二個元素(元素 3)也加上「box-flex」屬性,並將其值設爲 2,可見其效果圖以下:
因而可知,元素 3 和元素 4 按比例「2:1」的方式填充外層「容器」的餘下區域,這就是「box-flex」屬性的進階應用。
還有,「box-direction」能夠用來翻轉這四個盒子的排序,「box-ordinal-group」能夠用來改變每一個盒子的位置:一個盒子的 box-ordinal-group 屬性值越高,就排在越後面。盒子的對方方式能夠用「box-align」和「box-pack」來設定。
先說說 Transition,Transition 有下面些具體屬性:
transition-property:用於指定過渡的性質,好比 transition-property:backgrond 就是指 backgound 參與這個過渡
transition-duration:用於指定這個過渡的持續時間
transition-delay:用於制定延遲過渡的時間
transition-timing-function:用於指定過渡類型,有 ease | linear | ease-in | ease-out | ease-in-out | cubic-bezier
可能您以爲摸不着頭腦,其實很簡單,咱們用一個例子說明,參看一下以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
|
<
div
id
=
"transDiv"
class
=
"transStart"
> transition </
div
>
.transStart {
background-color: white;
-webkit-transition: background-color 0.3s linear;
-moz-transition: background-color 0.3s linear;
-o-transition: background-color 0.3s linear;
transition: background-color 0.3s linear;
}
.transEnd {
background-color: red;
}
|
這裏他說明的是,這裏 id 爲「transDiv」的 div,當它的初始「background-color」屬性變化時(被 JavaScript 修改),會呈現出一種變化效果,持續時間爲 0.3 秒,效果爲均勻變換(linear)。如:該 div 的 class 屬性由「transStart」改成「transEnd」,其背景會由白(white)漸變到紅(red)。
再來看看 Transform,其實就是指拉伸,壓縮,旋轉,偏移等等一些圖形學裏面的基本變換。見以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
.skew {
-webkit-transform: skew(50deg);
}
.scale {
-webkit-transform: scale(2, 0.5);
}
.rotate {
-webkit-transform: rotate(30deg);
}
.translate {
-webkit-transform: translate(50px, 50px);
}
.all_in_one_transform {
-webkit-transform: skew(20deg) scale(1.1, 1.1) rotate(40deg) translate(10px, 15px);
}
|
「skew」是傾斜,「scale」是縮放,「rotate」是旋轉,「translate」是平移。最後須要說明一點,transform 支持綜合變換。可見其效果圖以下:
如今您應該明白 Transform 的做用了吧。結合咱們以前談到的 Transition,將它們二者結合起來,會產生相似旋轉,縮放等等的效果,絕對能使人耳目一新。
最後,咱們來講說 Animation 吧。它能夠說開闢了 CSS 的新紀元,讓 CSS 脫離了「靜止」這一約定俗成的前提。以 webkit 爲例,見以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
@-webkit-keyframes anim1 {
0% {
Opacity: 0;
Font-size: 12px;
}
100% {
Opacity: 1;
Font-size: 24px;
}
}
.anim1Div {
-webkit-animation-name: anim1 ;
-webkit-animation-duration: 1.5s;
-webkit-animation-iteration-count: 4;
-webkit-animation-direction: alternate;
-webkit-animation-timing-function: ease-in-out;
}
|
首先,定義動畫的內容,如清單 28 所示,定義動畫「anim1」,變化方式爲由「透明」(opacity: 0)變到「不透明」(opacity: 1),同時,內部字體大小由「12px」變到「24px」。而後,再來定義 animation 的變化參數,其中,「duration」表示動畫持續時間,「iteration-count」表示動畫重複次數,direction 表示動畫執行完一次後方向的變化方式(如第一次從右向左,第二次則從左向右),最後,「timing-function」表示變化的模式。
其實,CSS3 動畫幾乎支持全部的 style 變化,能夠定義各類各樣的動畫效果以知足咱們用戶體驗的須要。
這裏,咱們介紹了 CSS3 的主要的新特性,這些特性在 Chrome 和 Safari 中基本都是支持的,Firefox 支持其中的一部分,IE 和 Opera 支持的較少。讀者們能夠根據集體狀況有選擇的使用。
BFC、IFC
BFC(Block Formatting Context)直譯爲「塊級格式化範圍」。
是 W3C CSS 2.1 規範中的一個概念,它決定了元素如何對其內容進行定位,以及與其餘元素的關係和相互做用。當涉及到可視化佈局的時候,Block Formatting Context提供了一個環境,HTML元素在這個環境中按照必定規則進行佈局。一個環境中的元素不會影響到其它環境中的佈局。好比浮動元素會造成BFC,浮動元素內部子元素的主要受該浮動元素影響,兩個浮動元素之間是互不影響的。這裏有點相似一個BFC就是一個獨立的行政單位的意思。也能夠說BFC就是一個做用範圍。能夠把它理解成是一個獨立的容器,而且這個容器的裏box的佈局,與這個容器外的絕不相干。
另外一個通俗點的解釋是:在普通流中的 Box(框) 屬於一種 formatting context(格式化上下文) ,類型能夠是 block ,或者是 inline ,但不能同時屬於這二者。而且, Block boxes(塊框) 在 block formatting context(塊格式化上下文) 裏格式化, Inline boxes(塊內框) 則在 inline formatting context(行內格式化上下文) 裏格式化。任何被渲染的元素都屬於一個 box ,而且不是 block ,就是 inline 。即便是未被任何元素包裹的文本,根據不一樣的狀況,也會屬於匿名的 block boxes 或者 inline boxes。因此上面的描述,便是把全部的元素劃分到對應的 formatting context 裏。
其通常表現規則,我整理了如下這幾個狀況:
一、在建立了 Block Formatting Context 的元素中,其子元素按文檔流一個接一個地放置。垂直方向上他們的起點是一個包含塊的頂部,兩個相鄰的元素之間的垂直距離取決於 ‘margin’ 特性。
根據 CSS 2.1 8.3.1 Collapsing margins 第一條,兩個相鄰的普通流中的塊框在垂直位置的空白邊會發生摺疊現象。也就是處於同一個BFC中的兩個垂直窗口的margin會重疊。
根據 CSS 2.1 8.3.1 Collapsing margins 第三條,生成 block formatting context 的元素不會和在流中的子元素髮生空白邊摺疊。因此解決這種問題的辦法是要爲兩個容器添加具備BFC的包裹容器。
二、在 Block Formatting Context 中,每個元素左外邊與包含塊的左邊相接觸(對於從右到左的格式化,右外邊接觸右邊), 即便存在浮動也是如此(儘管一個元素的內容區域會因爲浮動而壓縮),除非這個元素也建立了一個新的 Block Formatting Context 。
三、Block Formatting Context就是頁面上的一個隔離的獨立容器,容器裏面的子元素不會在佈局上影響到外面的元素,反之也是如此。
四、根據 CSS 2.1 9.5 Floats 中的描述,建立了 Block Formatting Context 的元素不能與浮動元素重疊。
表格的 border-box、塊級的替換元素、或是在普通流中建立了新的 block formatting context(如元素的 'overflow' 特性不爲 'visible' 時)的元素不能夠與位於相同的 block formatting context 中的浮動元素相重疊。
5 、當容器有足夠的剩餘空間容納 BFC 的寬度時,全部瀏覽器都會將 BFC 放置在浮動元素所在行的剩餘空間內。
六、 在 IE6 IE7 IE8 Chrome Opera 中,當 BFC 的寬度介於 "容器剩餘寬度" 與 "容器寬度" 之間時,BFC 會顯示在浮動元素的下一行;在 Safari 中,BFC 則仍然保持顯示在浮動元素所在行,而且 BFC 溢出容器;在 Firefox 中,當容器自己也建立了 BFC 或者容器的 'padding-top'、'border-top-width' 這些特性不都爲 0 時表現與 IE8(S)、Chrome 相似,不然表現與 Safari 相似。
經驗證,最新版本的瀏覽中只有firefox會在同一行顯示,其它瀏覽器均換行。
七、 在 IE6 IE7 IE8 Opera 中,當 BFC 的寬度大於 "容器寬度" 時,BFC 會顯示在浮動元素的下一行;在 Chrome Safari 中,BFC 則仍然保持顯示在浮動元素所在行,而且 BFC 溢出容器;在 Firefox 中,當容器自己也建立了 BFC 或者容器的 'padding- top'、'border-top-width' 這些特性不都爲 0 時表現與 IE8(S) 相似,不然表現與 Chrome 相似。
經驗證,最新版本的瀏覽中只有firefox會在同一行顯示,其它瀏覽器均換行。
八、根據CSS2.1 規範第10.6.7部分的高度計算規則,在計算生成了 block formatting context 的元素的高度時,其浮動子元素應該參與計算。
若是還有其它狀況,請各位回得中補充,我會及時更新!
下面先看一個比較典型的例子:
DOCTYPE HTML> <</span>html> <</span>head> <</span>meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <</span>title>無標題文檔</</span>title> <</span>style> * { padding:0; margin:0; } #red, #yellow, #orange, #green { width:100px; height:100px; float:left; } #red { background-color:red; } #yellow { background-color:yellow; } #orange { background-color:orange; } #green { background-color:green; } </</span>style> </</span>head> <</span>body> <</span>div id="c1"> <</span>div id="red"> </</span>div> <</span>div id="yellow"> </</span>div> </</span>div> <</span>div id="c2"> <</span>div id="orange"> </</span>div> <</span>div id="green"> </</span>div> </</span>div> <</span>p>Here is the text!</</span>p> </</span>body> </</span>html>
效果以下:
該段代碼本意要造成兩行兩列的佈局,可是因爲#red,#yellow,#orange,#green四個div在同一個佈局環境BFC中,所以雖然它們位於兩個不一樣的div(#c1和#c2)中,但仍然不會換行,而是一行四列的排列。
若要使之造成兩行兩列的佈局,就要建立兩個不一樣的佈局環境,也能夠說要建立兩個BFC。那到底怎麼建立BFC呢?
若是還其它方式,請在回覆中給出,我會及時更新!!
上面的例子,我再加兩行代碼,建立兩個BFC:
#c1{overflow:hidden;} #c2{overflow:hidden;}
效果以下:
上面建立了兩個佈局環境BFC。內部子元素的左浮動不會影響到外部元素。因此#c1和#c2沒有受浮動的影響,仍然各自佔據一行!
a、不和浮動元素重疊
若是一個浮動元素後面跟着一個非浮動的元素,那麼就會產生一個覆蓋的現象,不少自適應的兩欄佈局就是這麼作的。
看下面一個例子
DOCTYPE HTML> <</span>html> <</span>head> <</span>meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <</span>title>無標題文檔</</span>title> <</span>style> html,body {height:100%; } * { padding:0; margin:0; color:#fff; text-decoration:none; list-style:none; font-family:"微軟雅黑" } .aside{background:#f00;width:170px;float:left;height:300px;} .main{background:#090;height:100%;} </</span>style> </</span>head> <</span>body> <</span>div class="aside"> </</span>div> <</span>div class="main"> </</span>div> </</span>body> </</span>html>
效果圖以下:
很明顯,.aside和.mian重疊了。試分析一下,因爲兩個box都處在同一個BFC中,都是以BFC邊界爲起點,若是兩個box自己都具有BFC的話,會按順序一個一個排列布局,如今.main並不具有BFC,按照規則2,內部元素都會從左邊界開始,除非它自己具有BFC,按上面規則4擁有BFC的元素是不能夠跟浮動元素重疊的,因此只要爲.mian再建立一個BFC,就能夠解決這個重疊的問題。上面已經說過建立BFC的方法,能夠根據具體狀況選用不一樣的方法,這裏我選用的是加overflow:hidden。
因爲ie的緣由須要再加一個解發haslayout的zoom:1,有關haslayout後面會講到。
b、清除元素內部浮動
只要把父元素設爲BFC就能夠清理子元素的浮動了,最多見的用法就是在父元素上設置overflow: hidden樣式,對於IE6加上zoom:1就能夠了(IE Haslayout)。
看下面例子:
DOCTYPE HTML> <</span>html> <</span>head> <</span>meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <</span>title>無標題文檔</</span>title> <</span>style> html,body {height:100%; } * { padding:10px; margin:0; color:#000; text-decoration:none; list-style:none; font-family:"微軟雅黑" } .outer{width:300px;border:1px solid #666;padding:10px;} .innerLeft{height:100px;width:100px;float:left;background:#f00;} .innerRight{height:100px;width:100px;float:right;background:#090;} </</span>style> </</span>head> <</span>body> <</span>div class="outer"> <</span>div class="innerLeft"></</span>div> <</span>div class="innerRight"></</span>div> </</span>div> </</span>div> </</span>body> </</span>html>
效果圖以下:
根據 CSS2.1 規範第 10.6.3 部分的高度計算規則,在進行普通流中的塊級非替換元素的高度計算時,浮動子元素不參與計算。
同時 CSS2.1 規範第10.6.7部分的高度計算規則,在計算生成了 block formatting context 的元素的高度時,其浮動子元素應該參與計算。
因此,觸發外部容器BFC,高度將從新計算。好比給outer加上屬性overflow:hidden觸發其BFC。
c、解決上下相鄰兩個元素重疊
看下面例子:
DOCTYPE HTML> <</span>html> <</span>head> <</span>meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <</span>title>無標題文檔</</span>title> <</span>style> html,body {height:100%; } * { padding:0; margin:0; color:#fff; text-decoration:none; list-style:none; font-family:"微軟雅黑" } .rowone{background:#f00;height:100px;margin-bottom:20px;overflow:hidden;} .rowtow{background:#090;height:100px;margin-top:20px;position:relative} </</span>style> </</span>head> <</span>body> <</span>div class="rowone"> </</span>div> <</span>div class="rowtow"> </</span>div> </</span>body> </</span>html>
效果以下:
根據 CSS 2.1 8.3.1 Collapsing margins 第一條,兩個相鄰的普通流中的塊框在垂直位置的空白邊會發生摺疊現象。也就是處於同一個BFC中的兩個垂直窗口的margin會重疊。
根據 CSS 2.1 8.3.1 Collapsing margins 第三條,生成 block formatting context 的元素不會和在流中的子元素髮生空白邊摺疊。因此解決這種問題的辦法是要爲兩個容器添加具備BFC的包裹容器。
因此解這個問題的辦法就是,把兩個容器分別放在兩個據有BFC的包裹容器中,IE裏就是觸發layout的兩個包裹容器中!
DOCTYPE HTML> <</span>html> <</span>head> <</span>meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <</span>title>無標題文檔</</span>title> <</span>style> html, body { height:100%; } * { padding:0; margin:0; color:#fff; text-decoration:none; list-style:none; font-family:"微軟雅黑" } .mg {overflow:hidden; } .rowone { background:#f00; height:100px; margin-bottom:20px; } .rowtow { background:#090; height:100px; margin-top:20px; } </</span>style> </</span>head> <</span>body> <</span>div class="mg"> <</span>div class="rowone"> </</span>div> </</span>div> <</span>div class="mg"> <</span>div class="rowtow"> </</span>div> </</span>div> </</span>body> </</span>html>
效果以下:
上面的例子中咱們用到了IE的zoom:1;其實是觸發了IE的layout。Layout 是 IE 瀏覽器渲染引擎的一個內部組成部分。在 IE 瀏覽器中,一個元素要麼本身對自身的內容進行組織和計算大小, 要麼依賴於包含塊來計算尺寸和組織內容。爲了協調這兩種方式的矛盾,渲染引擎採用了 ‘hasLayout’ 屬性,屬性值能夠爲 true 或 false。 當一個元素的 ‘hasLayout’ 屬性值爲 true 時,咱們說這個元素有一個佈局(layout),或擁有佈局。能夠經過 hasLayout 屬性來判斷一個元素是否擁有 layout ,
如 object.currentStyle.hasLayout 。
hasLayout 與 BFC 有不少類似之處,但 hasLayout 的概念會更容易理解。在 Internet Explorer 中,元素使用「佈局」概念來控制尺寸和定位,分爲擁有佈局和沒有佈局兩種狀況,擁有佈局的元素由它控制自己及其子元素的尺寸和定位,而沒有佈局的元素則經過父元素(最近的擁有佈局的祖先元素)來控制尺寸和定位,而一個元素是否擁有佈局則由 hasLayout 屬性告知瀏覽器,它是個布爾型變量,true 表明元素擁有佈局,false 表明元素沒有佈局。簡而言之,hasLayout 只是一個 IE 下專有的屬性,hasLayout 爲 true 的元素瀏覽器會賦予它一系列的效果。
特別注意的是,hasLayout 在 IE 8 及以後的 IE 版本中已經被拋棄,因此在實際開發中只需針對 IE 8 如下的瀏覽器爲某些元素觸發 hasLayout。
一個元素觸發 hasLayout 會影響一個元素的尺寸和定位,這樣會消耗更多的系統資源,所以 IE 設計者默認只爲一部分的元素觸發 hasLayout (即默認有部分元素會觸發 hasLayout ,這與 BFC 基本徹底由開發者經過特定 CSS 觸發並不同),這部分元素以下:
<</span>html>, <</span>body> <</span>table>, <</span>tr>, <</span>th>, <</span>td> <</span>img> <</span>hr> <</span>input>, <</span>button>, <</span>select>, <</span>textarea>, <</span>fieldset>, <</span>legend> <</span>iframe>, <</span>embed>, <</span>object>, <</span>applet> <</span>marquee>
除了 IE 默認會觸發 hasLayout 的元素外,Web 開發者還能夠使用特定的 CSS 觸發元素的 hasLayout 。
經過爲元素設置如下任一 CSS ,能夠觸發 hasLayout (即把元素的 hasLayout 屬性設置爲 true)。
display: inline-block height: (除 auto 外任何值) width: (除 auto 外任何值) float: (left 或 right) position: absolute writing-mode: tb-rl zoom: (除 normal 外任意值) min-height: (任意值) min-width: (任意值) max-height: (除 none 外任意值) max-width: (除 none 外任意值) overflow: (除 visible 外任意值,僅用於塊級元素) overflow-x: (除 visible 外任意值,僅用於塊級元素) overflow-y: (除 visible 外任意值,僅用於塊級元素) position: fixed
對於內聯元素(能夠是默認被瀏覽器認爲是內聯元素的 span 元素,也能夠是設置了 display: inline 的元素),width 和 height 只在 IE5.x 下和 IE6 或更新版本的 quirks 模式下能觸發元素的 hasLayout ,可是對於 IE6,若是瀏覽器運行於標準兼容模式下,內聯元素會忽略 width 或 height 屬性,因此設置 width 或 height 不能在此種狀況下令該元素觸發 hasLayout 。但 zoom 除了在 IE 5.0 中外,老是能觸發 hasLayout 。zoom 用於設置或檢索元素的縮放比例,爲元素設置 zoom: 1 既能夠觸發元素的 hasLayout 同時不會對元素形成多餘的影響。所以綜合考慮瀏覽器之間的兼容和對元素的影響, 建議使用 zoom: 1 來觸發元素的 hasLayout 。
hasLayout表現出來的特性跟BFC很類似,因此能夠認爲是IE中的BFC。上面的規則幾乎都遵循,因此上面的問題在IE裏均可以經過觸發hasLayout來解決。
雖然 hasLayout 也會像 BFC 那樣影響着元素的尺寸和定位,但它卻又不是一套完整的標準,而且因爲它默認只爲某些元素觸發,這致使了 IE 下不少前端開發的 bugs ,觸發 hasLayout 更大的意義在於解決一些 IE 下的 bugs ,而不是利用它的一些「反作用」來達到某些效果。另外因爲觸發 hasLayout 的元素會出現一些跟觸發 BFC 的元素類似的效果,所以爲了統一元素在 IE 與支持 BFC 的瀏覽器下的表現,Kayo 建議爲觸發了 BFC 的元素同時觸發 hasLayout ,固然還須要考慮實際的狀況,也有可能只需觸發其中一個就能夠達到表現統一,下面會舉例介紹。
這裏首先列出觸發 hasLayout 元素的一些效果:
a、阻止外邊距摺疊
如上面例子:
DOCTYPE HTML> <</span>html> <</span>head> <</span>meta http-equiv="Content-Type" content="text/html; charset=gb2312"> <</span>title>無標題文檔</</span>title> <</span>style> html, body { height:100%; } * { padding:0; margin:0; color:#fff; text-decoration:none; list-style:none; font-family:"微軟雅黑" } .mg {zoom:1} .rowone { background:#f00; height:100px; margin-bottom:20px; } .rowtow { background:#090; height:100px; margin-top:20px; } </</span>style> </</span>head> <</span>body> <</span>div class="mg"> <</span>div class="rowone"> </</span>div> </</span>div> <</span>div class="mg"> <</span>div class="rowtow"> </</span>div> </</span>div> </</span>body> </</span>html>
須要觸發.mg的layout才能解決margin重疊問題
運行效果以下:
對柵格的理解
Bootstrap框架是現在最流行的前端框架之一,Bootstrap功能強大,簡單易學,很符合實際應用場景。
只是Bootstrap的內容較多,新手每每不能很快的熟練運用Bootstrap。
這裏,我就對Bootstrap中很是重要好用的柵格系統作一個以實例爲嚮導的總結:
(1)第一步:建立柵格系統的容器
<div class="container-fluid"> <div class="row"> ... </div> </div>
解釋:爲了寄予柵格系統合適的排列和padding,要把每一行「row」包含在一個容器中,而這個容器咱們用class名爲「container」或者「container-fluid」,這兩個class是Bootstrap爲咱們事先設計好的
.container是固定寬度,居中顯示:下面是Bootstrap中.container類的代碼

.container-fluid是 100% 寬度:下面是Bootstrap中.container-fluid類的代碼
(2)第二步:建立合適的柵格系統
<div class="row">
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
<div class="col-md-1">.col-md-1</div>
</div>
<div class="row">
<div class="col-md-8">.col-md-8</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
<div class="col-md-4">.col-md-4</div>
</div>
<div class="row">
<div class="col-md-6">.col-md-6</div>
<div class="col-md-6">.col-md-6</div>
</div>
解釋:上面這段是我從Bootstrap官網複製下來的,每個「row」表明一行,而內部的「col-md-數字」表明一個單元格;
Bootstrap把每一行分紅12等份,「col-md-數字」中的「數字」從1-12中取,數字等於幾,就佔幾份;
合理的選擇單元格的數字配置,再往單元格中添加咱們想要的內容,這樣一個柵格系統就完成了!
(3)進階:單元格的類還有其餘選擇 ,一共有四種:
.c0l-xs- 不管屏幕寬度如何,單元格都在一行,寬度按照百分比設置;試用於手機;
.col-sm- 屏幕大於768px時,單元格在一行顯示;屏幕小於768px時,獨佔一行;試用於平板;
.col-md- 屏幕大於992px時,單元格在一行顯示;屏幕小於992px時,獨佔一行;試用於桌面顯示器;
.col-lg- 屏幕大於1200px時,單元格在一行顯示;屏幕小於1200px時,獨佔一行;適用於大型桌面顯示器;
以上的狀況能夠經過下面的代碼清晰的理解:
<div class="container">
<div class="row">
<div class="col-xs-12 col-sm-6 col-md-8">.col-xs-12 .col-sm-6 .col-md-8</div>
<div class="col-xs-6 col-md-4">.col-xs-6 .col-md-4</div>
</div>
<div class="row">
<div class="col-xs-6 col-sm-4">.col-xs-6 .col-sm-4</div>
<div class="col-xs-6 col-sm-4">.col-xs-6 .col-sm-4</div>
<div class="col-xs-6 col-sm-4">.col-xs-6 .col-sm-4</div>
</div>
</div>
屏幕大於992px時:.col-md-8 和.col-md-4 還處於 做用範圍內,以下
屏幕在769px-992px之間時:.col-md-已失效,而.col-sm- 還處在 做用範圍內,以下
屏幕寬度小於768px時,.col-sm-已失效 只有.col-xs- 不受屏幕寬度影響,這時候就由.col-xs-起做用,以下
(水平)居中有哪些實現方式
1.用inline-block和vertical-align來實現居中:這種方法適合於未知寬度高度的狀況下。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #container{ text-align: center; height: 400px; background: #4dc71f; } #container:before{ content: ""; height: 100%; display: inline-block; vertical-align: middle; margin-right: -0.25em; } #center-div{ display: inline-block; vertical-align: middle; background: #2aabd2; } </style> </head> <body> <div id="container"> <div id="center-div"> xxx </div> </div> </body>
參考:https://css-tricks.com/centering-in-the-unknown/
2.用相對絕對定位和負邊距實現上下左右居中:高度和寬度已知
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>test</title> <style> .div2{ height: 600px; width: 600px; position: relative; border: 2px solid #000; } .img2{ height: 200px; width: 200px; position: relative; top: 50%; left: 50%; margin: -100px 0 0 -100px; } </style> </head> <body> <div class="div2"> <img class="img2" src="images/hongbao.png"> </div> </body> </html>
其實第二種要理解不能,首先相對父元素top,left定位在50%的位置,這時候只是圖片左上角居中,而中心點還沒居中,加上margin: -100px 0 0 -100px;利用負邊距把圖片的中心點位於父元素的中心點,從而實現垂直水平居中
3.利用絕對定位來實現居中:適合高度,寬度已知的狀況。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #container{ text-align: center; height: 400px; background: #4dc71f; position: relative; } #center-div{ position: absolute; margin: auto; top: 0; right: 0; left:0; bottom: 0; width: 200px; height: 200px; background: #2b669a; } </style> </head> <body> <div id="container"> <div id="center-div"> xxx </div> </div> </body> </html>
4.使用table-cell,inline-block實現水平垂直居中:適合高度寬度未知的狀況
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #container{ display: table-cell; text-align: center; vertical-align: middle; height: 300px; background: #ccc; } #center-div{ display: inline-block; } </style> </head> <body> <div id="container"> <div id="center-div"> xxx </div> </div> </body> </html>
5.使用css3中的transform來時實現水平垂直居中:適合高度寬度未知的狀況
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #container{ position: relative; height: 200px; background: #333; } #center-div{ position: absolute; top:50%; left: 50%; transform: translate(-50%,-50%); } </style> </head> <body> <div id="container"> <div id="center-div"> xxx </div> </div> </body> </html>
還能夠使用Flexbox來實現水平垂直居中;適合寬度高度未知狀況,可是要注意兼容性
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> #p_2{ display: flex; justify-content: center; align-items: center; width: 200px; height: 200px; background: #009f95; } </style> </head> <body> <div id="p_2"> <div id="c_2"> xxxxxxx </div> </div> </body> </html>
1像素邊框問題
圖片懶加載
在實際的項目開發中,咱們一般會碰見這樣的場景:一個頁面有不少圖片,而首屏出現的圖片大概就一兩張,那麼咱們還要一次性把全部圖片都加載出來嗎?顯然這是愚蠢的,不只影響頁面渲染速度,還浪費帶寬。這也就是們一般所說的首屏加載,技術上現實其中要用的技術就是圖片懶加載--到可視區域再加載。
將頁面裏全部img屬性src屬性用data-xx代替,當頁面滾動直至此圖片出如今可視區域時,用js取到該圖片的data-xx的值賦給src。
頁可見區域寬: document.body.clientWidth; 網頁可見區域高: document.body.clientHeight; 網頁可見區域寬: document.body.offsetWidth (包括邊線的寬); 網頁可見區域高: document.body.offsetHeight (包括邊線的寬); 網頁正文全文寬: document.body.scrollWidth; 網頁正文全文高: document.body.scrollHeight; 網頁被捲去的高: document.body.scrollTop; 網頁被捲去的左: document.body.scrollLeft; 網頁正文部分上: window.screenTop; 網頁正文部分左: window.screenLeft; 屏幕分辨率的高: window.screen.height; 屏幕分辨率的寬: window.screen.width; 屏幕可用工做區高度: window.screen.availHeight;
下載地址:https://github.com/helijun/helijun/blob/master/plugin/lazyLoad/jquery.lazyload.js
<section class="module-section" id="container"> <img class="lazy-load" data-original="../static/img/loveLetter/teacher/teacher1.jpg" width="640" height="480" alt="測試懶加載圖片"/> </section>
require.config({ baseUrl : "/static", paths: { jquery:'component/jquery/jquery-3.1.0.min' jqueryLazyload: 'component/lazyLoad/jquery.lazyload',//圖片懶加載 }, shim: { jqueryLazyload: { deps: ['jquery'], exports: '$' } } });
require( [ 'jquery', 'jqueryLazyload' ], function($){ $(document).ready(function() { $("img.lazy-load").lazyload({
effect : "fadeIn", //漸現,show(直接顯示),fadeIn(淡入),slideDown(下拉)
threshold : 180, //預加載,在圖片距離屏幕180px時提早載入
event: 'click', // 事件觸發時才加載,click(點擊),mouseover(鼠標劃過),sporty(運動的),默認爲scroll(滑動)
container: $("#container"), // 指定對某容器中的圖片實現效果
failure_limit:2 //加載2張可見區域外的圖片,lazyload默認在找到第一張不在可見區域裏的圖片時則再也不繼續加載,但當HTML容器混亂的時候可能出現可見區域內圖片並沒加載出來的狀況
});
});
});
爲了代碼可讀性,屬性值我都寫好了註釋。值得注意的是預製圖片屬性爲data-original,而且最好是給予初始高寬佔位,以避免影響佈局,固然這裏爲了演示我是寫死的640x480,若是是響應式頁面,高寬須要動態計算。
dome演示地址:http://h5.sztoda.cn/test/testLazyLoad
在前面「前端知識的一些總結」的博文中,介紹了一款很是簡單實用輕量級的圖片延時加載插件echo.js,若是你的項目中沒有依賴jquery,那麼這將是個不錯的選擇,50行代碼,壓縮後才1k。固然你徹底能夠集成到本身項目中去!
下載地址:https://github.com/helijun/helijun/tree/master/plugin/echo
<style> .demo img {
width: 736px;
height: 490px;
background: url(images/loading.gif) 50% no-repeat;} </style>
<div class="demo"> <img class="lazy" src="images/blank.gif" data-echo="images/big-1.jpg"> </div>
<script src="js/echo.min.js"></script> <script> Echo.init({ offset: 0,//離可視區域多少像素的圖片能夠被加載
throttle: 0 //圖片延時多少毫秒加載
});
</script>
說明:blank.gif是一張背景圖片,包含在插件裏了。圖片的寬高必須設定,固然,能夠使用外部樣式對多張圖片統一控制大小。data-echo指向的是真正的圖片地址。
二者都很是簡單,實現思路是同樣的,只是jquerylazyload多幾個屬性。其實經常使用的echo就足夠了,而且徹底能夠集成到本身項目中的公共js中,圖片懶加載是至關常見且簡單實用的功能,若是你的項目中仍是傻瓜式的一次性所有加載,那麼請花20分鐘優化下
1、什麼是圖片滾動加載?
通俗的講就是:當訪問一個頁面的時候,先把img元素或是其餘元素的背景圖片路徑替換成一張大小爲1*1px圖片的路徑(這樣就只需請求一次),只有當圖片出如今瀏覽器的可視區域內時,才設置圖片正真的路徑,讓圖片顯示出來。這就是圖片懶加載。
2、爲什要使用這個技術?
好比一個頁面中有不少圖片,如淘寶、京東首頁等等,若是一上來就發送這麼多請求,頁面加載就會很漫長,若是js文件都放在了文檔的底部,恰巧頁面的頭部又依賴這個js文件,那就很差辦了。更爲要命的是:一上來就發送百八十個請求,服務器可能就吃不消了(又不是隻有一兩我的在訪問這個頁面)。
所以優勢就很明顯了:不只能夠減輕服務器的壓力,並且可讓加載好的頁面更快地呈如今用戶面前(用戶體驗好)。
3、怎麼實現?
關鍵點以下:
一、頁面中的img元素,若是沒有src屬性,瀏覽器就不會發出請求去下載圖片(也就沒有請求咯,也就提升性能咯),一旦經過javascript設置了圖片路徑,瀏覽器纔會送請求。有點按需分配的意思,你不想看,就不給你看,你想看了就給你看~
二、如何獲取正真的路徑,這個簡單,如今正真的路徑存在元素的「data-url」(這個名字起個本身認識好記的就行)屬性裏,要用的時候就取出來,再設置;
三、開始比較以前,先了解一些基本的知識,好比說如何獲取某個元素的尺寸大小、滾動條滾動距離及偏移位置距離;
1)屏幕可視窗口大小:對應於圖中一、2位置處
原生方法:window.innerHeight 標準瀏覽器及IE9+ || document.documentElement.clientHeight 標準瀏覽器及低版本IE標準模式 ||
document.body.clientHeight 低版本混雜模式
jQuery方法: $(window).height()
2)瀏覽器窗口頂部與文檔頂部之間的距離,也就是滾動條滾動的距離:也就是圖中三、4處對應的位置;
原生方法:window.pagYoffset——IE9+及標準瀏覽器 || document.documentElement.scrollTop 兼容ie低版本的標準模式 ||
document.body.scrollTop 兼容混雜模式;
jQuery方法:$(document).scrollTop();
3)獲取元素的尺寸:對應於圖中五、6位置處;左邊jquery方法,右邊原生方法
$(o).width() = o.style.width;
$(o).innerWidth() = o.style.width+o.style.padding;
$(o).outerWidth() = o.offsetWidth = o.style.width+o.style.padding+o.style.border;
$(o).outerWidth(true) = o.style.width+o.style.padding+o.style.border+o.style.margin;
注意:要使用原生的style.xxx方法獲取屬性,這個元素必須已經有內嵌的樣式,如<div style="...."></div>;
若是原先是經過外部或內部樣式表定義css樣式,必須使用o.currentStyle[xxx] || document.defaultView.getComputedStyle(0)[xxx]來獲取樣式值
4)獲取元素的位置信息:對應與圖中七、8位置處
1)返回元素相對於文檔document頂部、左邊的距離;
jQuery:$(o).offset().top元素距離文檔頂的距離,$(o).offset().left元素距離文檔左邊緣的距離
原生:getoffsetTop(),高程上有具體說明,這邊就忽略了;
順便提一下返回元素相對於第一個以定位的父元素的偏移距離,注意與上面偏移距的區別;
jQuery:position()返回一個對象,$(o).position().left = style.left,$(o).position().top = style.top;
四、知道如何獲取元素尺寸、偏移距離後,接下來一個問題就是:如何判斷某個元素進入或者即將進入可視窗口區域?下面也經過一張圖來講明問題。
1)外面最大的框爲實際頁面的大小,中間淺藍色的框表明父元素的大小,對象1~8表明元素位於頁面上的實際位置;以水平方向來作以下說明!
2)對象8左邊界相對於頁面左邊界的偏移距離(offsetLeft)大於父元素右邊界相對於頁面左邊界的距離,此時可判讀元素位於父元素以外;
3)對象7左邊界跨過了父元素右邊界,此時:對象7左邊界相對於頁面左邊界的偏移距離(offsetLeft)小於 父元素右邊界相對於
頁面左邊界的距離,所以對象7就進入了父元素可視區;
4)在對象6的位置處,對象5的右邊界與頁面左邊界的距離 大於 父元素左邊界與頁面左邊界的距離;
5)在對象5位置處時,對象5的右邊界與頁面左邊界的距離 小於 父元素左邊界與頁面左邊界的距離;此時,可判斷元素處於父元素可視區外;
6)所以水平方向必須買足兩個條件,才能說明元素位於父元素的可視區內;同理垂直方向也必須知足兩個條件;具體見下文的源碼;
4、擴展爲jquery插件
使用方法:$("selector").scrollLoad({ 參數在代碼中有說明 })
(function($) {
$.fn.scrollLoading = function(options) { var defaults = { // 在html標籤中存放的屬性名稱; attr: "data-url", // 父元素默認爲window container: window, callback: $.noop }; // 無論有沒有傳入參數,先合併再說; var params = $.extend({}, defaults, options || {}); // 把父元素轉爲jquery對象; var container = $(params.container); // 新建一個數組,而後調用each方法,用於存儲每一個dom對象相關的數據; params.cache = []; $(this).each(function() { // 取出jquery對象中每一個dom對象的節點類型,取出每一個dom對象上設置的圖片路徑 var node = this.nodeName.toLowerCase(), url = $(this).attr(params["attr"]); //重組,把每一個dom對象上的屬性存爲一個對象; var data = { obj: $(this), tag: node, url: url }; // 把這個對象加到一個數組中; params.cache.push(data); }); var callback = function(call) { if ($.isFunction(params.callback)) { params.callback.call(call); } }; //每次觸發滾動事件時,對每一個dom元素與container元素進行位置判斷,若是知足條件,就把路徑賦予這個dom元素! var loading = function() { // 獲取父元素的高度 var contHeight = container.outerHeight(); var contWidth = container.outerWidth(); // 獲取父元素相對於文檔頁頂部的距離,這邊要注意了,分爲如下兩種狀況; if (container.get(0) === window) { // 第一種狀況父元素爲window,獲取瀏覽器滾動條已滾動的距離;$(window)沒有offset()方法; var contop = $(window).scrollTop(); var conleft = $(window).scrollLeft(); } else { // 第二種狀況父元素爲非window元素,獲取它的滾動條滾動的距離; var contop = container.offset().top; var conleft = container.offset().left; } $.each(params.cache, function(i, data) { var o = data.obj, tag = data.tag, url = data.url, post, posb, posl, posr; if (o) { //對象頂部與文檔頂部之間的距離,若是它小於父元素底部與文檔頂部的距離,則說明垂直方向上已經進入可視區域了; post = o.offset().top - (contop + contHeight); //對象底部與文檔頂部之間的距離,若是它大於父元素頂部與文檔頂部的距離,則說明垂直方向上已經進入可視區域了; posb = o.offset().top + o.height() - contop; // 水平方向上同理; posl = o.offset().left - (conleft + contWidth); posr = o.offset().left + o.width() - conleft; // 只有當這個對象是可視的,而且這四個條件都知足時,才能給這個對象賦予圖片路徑; if ( o.is(':visible') && (post < 0 && posb > 0) && (posl < 0 && posr > 0) ) { if (url) { //在瀏覽器窗口內 if (tag === "img") { //設置圖片src callback(o.attr("src", url)); } else { // 設置除img以外元素的背景url callback(o.css("background-image", "url("+ url +")")); } } else { // 無地址,直接觸發回調 callback(o); } // 給對象設置完圖片路徑以後,把params.cache中的對象給清除掉;對象再進入可視區,就再也不進行重複設置了; data.obj = null; } } }); }; //加載完畢即執行 loading(); //滾動執行 container.bind("scroll", loading); }; })(jQuery);
實現頁面加載進度條
網頁進度條可以更好的反應當前網頁的加載進度狀況,loading進度條可用動畫的形式從開始0%到100%完成網頁加載這一過程。可是目前的瀏覽器並無提供頁面加載進度方面的接口,也就是說頁面還沒法準確返回頁面實際加載的進度,本文中咱們使用jQuery來實現頁面加載進度條效果。
HTML
首先咱們要知道的是,目前沒有任何瀏覽器能夠直接獲取正在加載對象的大小。因此咱們沒法經過數據大小來實現0-100%的加載顯示過程。
所以咱們須要經過html代碼逐行加載的特性,在整頁代碼的若干個跳躍行數中設置節點,進行大概的模糊進度反饋來實現進度加載的效果。大體意思是:頁面每加載到指定區域,則返回(n)%的進度結果,經過設置多個節點,來達到一步一步顯示加載進度的目的。
假若有一個頁面,按區塊分爲頁頭、左側內容、右邊側欄、頁腳四部分,咱們把這四部分做爲四個節點,當頁面加載每個節點後,設置大概加載百分比,頁面結構以下:
1
2
3
4
5
6
7
8
9
10
11
12
|
<
div
id
=
"header"
>
頁頭部分
</
div
>
<
div
id
=
"mainpage"
>
左側內容
</
div
>
<
div
id
=
"sider"
>
右邊側欄
</
div
>
<
div
id
=
"footer"
>
頁腳部分
</
div
>
|
而後咱們在<body>下的第一行加上進度條.loading。
1
|
<
div
class
=
"loading"
></
div
>
|
CSS
咱們要設置loading進度條的樣式,設置背景色,高度,以及位置,優先級等。
1
2
3
4
5
6
7
|
.loading{
background:
#FF6100; //設置進度條的顏色
height:5px;
//設置進度條的高度
position:fixed;
//設定進度條跟隨屏幕滾動
top:0;
//將進度條固定在頁面頂部
z-index:99999
//提升進度條的優先層級,避免被其餘層遮擋
}
|
jQuery
接下來,咱們要在每一個節點後面加上進度動畫,使用jQuery來實現。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
<div id=
"header"
>
頁頭部分</div>
<script type=
"text/javascript"
>
$(
'.loading'
).animate({
'width'
:
'33%'
},50);
//第一個進度節點
</script>
<div id=
"mainpage"
>
左側內容
</div>
<script type=
"text/javascript"
>
$(
'.loading'
).animate({
'width'
:
'55%'
},50);
//第二個進度節點
</script>
<div id=
"sider"
>
右邊側欄
</div>
<script type=
"text/javascript"
>
$(
'.loading'
).animate({
'width'
:
'80%'
},50);
//第三個進度節點
</script>
<div id=
"footer"
>
頁腳部分
</div>
<script type=
"text/javascript"
>
$(
'.loading'
).animate({
'width'
:
'100%'
},50);
//第四個進度節點
</script>
|
能夠看出,沒加載一個節點後,jQuery調用animate()動畫方法實現進度條寬度的變化,每一個節點變化以50毫秒時間讓進度條看起來更流暢些,最終從0%變化到100%,完成了進度條的進度動畫。
當進度條達到100%後,頁面加載完成,最後還有一步要作的就是隱藏進度條。
1
2
3
|
$(document).ready(
function
(){
$(
'.loading'
).fadeOut();
});
|
事件委託
現在的JavaScript技術界裏最火熱的一項技術應該是‘事件委託(event delegation)’了。使用事件委託技術能讓你避免對特定的每一個節點添加事件監聽器;相反,事件監聽器是被添加到它們的父元素上。事件監聽器會分析從子元素冒泡上來的事件,找到是哪一個子元素的事件。基本概念很是簡單,但仍有不少人不理解事件委託的工做原理。這裏我將要解釋事件委託是如何工做的,並提供幾個純JavaScript的基本事件委託的例子。
假定咱們有一個UL
元素,它有幾個子元素:
<ul id="parent-list"> <li id="post-1">Item 1</li> <li id="post-2">Item 2</li> <li id="post-3">Item 3</li> <li id="post-4">Item 4</li> <li id="post-5">Item 5</li> <li id="post-6">Item 6</li> </ul>
咱們還假設,當每一個子元素被點擊時,將會有各自不一樣的事件發生。你能夠給每一個獨立的li
元素添加事件監聽器,但有時這些li
元素可能會被刪除,可能會有新增,監聽它們的新增或刪除事件將會是一場噩夢,尤爲是當你的監聽事件的代碼放在應用的另外一個地方時。可是,若是你將監聽器安放到它們的父元素上呢?你如何能知道是那個子元素被點擊了?
簡單:當子元素的事件冒泡到父ul
元素時,你能夠檢查事件對象的target屬性,捕獲真正被點擊的節點元素的引用。下面是一段很簡單的JavaScript代碼,演示了事件委託的過程:
// 找到父元素,添加監聽器...
document.getElementById("parent-list").addEventListener("click",function(e) { // e.target是被點擊的元素! // 若是被點擊的是li元素 if(e.target && e.target.nodeName == "LI") { // 找到目標,輸出ID! console.log("List item ",e.target.id.replace("post-")," was clicked!"); } });
第一步是給父元素添加事件監聽器。當有事件觸發監聽器時,檢查事件的來源,排除非li
子元素事件。若是是一個li
元素,咱們就找到了目標!若是不是一個li
元素,事件將被忽略。這個例子很是簡單,UL
和li
是標準的父子搭配。讓咱們試驗一些差別比較大的元素搭配。假設咱們有一個父元素div
,裏面有不少子元素,但咱們關心的是裏面的一個帶有」classA」 CSS類的A標記:
// 得到父元素DIV, 添加監聽器...
document.getElementById("myDiv").addEventListener("click",function(e) { // e.target是被點擊的元素 if(e.target && e.target.nodeName == "A") { // 得到CSS類名 var classes = e.target.className.split(" "); // 搜索匹配! if(classes) { // For every CSS class the element has... for(var x = 0; x < classes.length; x++) { // If it has the CSS class we want... if(classes[x] == "classA") { // Bingo! console.log("Anchor element clicked!"); // Now do something here.... } } } } });
上面這個例子中不只比較了標籤名,並且比較了CSS類名。雖然稍微複雜了一點,但仍是很具表明性的。好比,若是某個A標記裏有一個span
標記,則這個span
將會成爲target元素。這個時候,咱們須要上溯DOM樹結構,找到裏面是否有一個 A.classA 的元素。
由於大部分程序員都會使用jQuery等工具庫來處理DOM元素和事件,我建議你們都使用裏面的事件委託方法,由於這裏工具庫裏都提供了高級的委託方法和元素甄別方法。
但願這篇文章能幫助你理解JavaScript事件委託的幕後原理,但願你也感覺到了事件委託的強大用處!
1,什麼是事件委託:通俗的講,事件就是onclick,onmouseover,onmouseout,等就是事件,委託呢,就是讓別人來作,這個事件原本是加在某些元素上的,然而你卻加到別人身上來作,完成這個事件。
也就是:利用冒泡的原理,把事件加到父級上,觸發執行效果。
好處呢:1,提升性能。
咱們能夠看一個例子:須要觸發每一個li來改變他們的背景顏色。
<ul id="ul"> <li>aaaaaaaa</li> <li>bbbbbbbb</li> <li>cccccccc</li> </ul>
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName("li"); for(var i=0; i<aLi.length; i++){ aLi[i].onmouseover = function(){ this.style.background = "red"; } aLi[i].onmouseout = function(){ this.style.background = ""; } } }
這樣咱們就能夠作到li上面添加鼠標事件。
可是若是說咱們可能有不少個li用for循環的話就比較影響性能。
下面咱們能夠用事件委託的方式來實現這樣的效果。html不變
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName("li"); /* 這裏要用到事件源:event 對象,事件源,無論在哪一個事件中,只要你操做的那個元素就是事件源。 ie:window.event.srcElement 標準下:event.target nodeName:找到元素的標籤名 */ oUl.onmouseover = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; //alert(target.innerHTML); if(target.nodeName.toLowerCase() == "li"){ target.style.background = "red"; } } oUl.onmouseout = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; //alert(target.innerHTML); if(target.nodeName.toLowerCase() == "li"){ target.style.background = ""; } } }
好處2,新添加的元素還會有以前的事件。
咱們還拿這個例子看,可是咱們要作動態的添加li。點擊button動態添加li
如:
<input type="button" id="btn" /> <ul id="ul"> <li>aaaaaaaa</li> <li>bbbbbbbb</li> <li>cccccccc</li> </ul>
不用事件委託咱們會這樣作:
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName("li"); var oBtn = document.getElementById("btn"); var iNow = 4; for(var i=0; i<aLi.length; i++){ aLi[i].onmouseover = function(){ this.style.background = "red"; } aLi[i].onmouseout = function(){ this.style.background = ""; } } oBtn.onclick = function(){ iNow ++; var oLi = document.createElement("li"); oLi.innerHTML = 1111 *iNow; oUl.appendChild(oLi); } }
這樣作咱們能夠看到點擊按鈕新加的li上面沒有鼠標移入事件來改變他們的背景顏色。
由於點擊添加的時候for循環已經執行完畢。
那麼咱們用事件委託的方式來作。就是html不變
window.onload = function(){ var oUl = document.getElementById("ul"); var aLi = oUl.getElementsByTagName("li"); var oBtn = document.getElementById("btn"); var iNow = 4; oUl.onmouseover = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; //alert(target.innerHTML); if(target.nodeName.toLowerCase() == "li"){ target.style.background = "red"; } } oUl.onmouseout = function(ev){ var ev = ev || window.event; var target = ev.target || ev.srcElement; //alert(target.innerHTML); if(target.nodeName.toLowerCase() == "li"){ target.style.background = ""; } } oBtn.onclick = function(){ iNow ++; var oLi = document.createElement("li"); oLi.innerHTML = 1111 *iNow; oUl.appendChild(oLi); } }
實現extend函數
1.實現
2.調用
爲何會有跨域的問題以及解決方式
URL | 說明 | 是否容許通訊 |
---|---|---|
http://www.a.com/a.js http://www.a.com/b.js |
同一域名下 | 容許 |
http://www.a.com/lab/a.js http://www.a.com/script/b.js |
同一域名下不一樣文件夾 | 容許 |
http://www.a.com:8000/a.js http://www.a.com/b.js |
同一域名,不一樣端口 | 不容許 |
http://www.a.com/a.js https://www.a.com/b.js |
同一域名,不一樣協議 | 不容許 |
http://www.a.com/a.js http://70.32.92.74/b.js |
域名和域名對應ip | 不容許 |
http://www.a.com/a.js http://script.a.com/b.js |
主域相同,子域不一樣 | 不容許 |
http://www.a.com/a.js http://a.com/b.js |
同一域名,不一樣二級域名(同上) | 不容許(cookie這種狀況下也不容許訪問) |
http://www.cnblogs.com/a.js http://www.a.com/b.js |
不一樣域名 | 不容許 |
1) 在www.a.com/a.html中:
document.domain = 'a.com'; var ifr = document.createElement('iframe'); ifr.src = 'http://www.script.a.com/b.html'; ifr.display = none; document.body.appendChild(ifr); ifr.onload = function(){ var doc = ifr.contentDocument || ifr.contentWindow.document; //在這裏操做doc,也就是b.html ifr.onload = null; };
2) 在www.script.a.com/b.html中:
document.domain = 'a.com';
這個沒什麼好說的,由於script標籤不受同源策略的限制。
function loadScript(url, func) { var head = document.head || document.getElementByTagName('head')[0]; var script = document.createElement('script'); script.src = url; script.onload = script.onreadystatechange = function(){ if(!this.readyState || this.readyState=='loaded' || this.readyState=='complete'){ func(); script.onload = script.onreadystatechange = null; } }; head.insertBefore(script, 0); } window.baidu = { sug: function(data){ console.log(data); } } loadScript('http://suggestion.baidu.com/su?wd=w',function(){console.log('loaded')}); //咱們請求的內容在哪裏? //咱們能夠在chorme調試面板的source中看到script引入的內容
原理是利用location.hash來進行傳值。
假設域名a.com下的文件cs1.html要和cnblogs.com域名下的cs2.html傳遞信息。
1) cs1.html首先建立自動建立一個隱藏的iframe,iframe的src指向cnblogs.com域名下的cs2.html頁面
2) cs2.html響應請求後再將經過修改cs1.html的hash值來傳遞數據
3) 同時在cs1.html上加一個定時器,隔一段時間來判斷location.hash的值有沒有變化,一旦有變化則獲取獲取hash值
注:因爲兩個頁面不在同一個域下IE、Chrome不容許修改parent.location.hash的值,因此要藉助於a.com域名下的一個代理iframe
代碼以下:
先是a.com下的文件cs1.html文件:
function startRequest(){ var ifr = document.createElement('iframe'); ifr.style.display = 'none'; ifr.src = 'http://www.cnblogs.com/lab/cscript/cs2.html#paramdo'; document.body.appendChild(ifr); } function checkHash() { try { var data = location.hash ? location.hash.substring(1) : ''; if (console.log) { console.log('Now the data is '+data); } } catch(e) {}; } setInterval(checkHash, 2000);
cnblogs.com域名下的cs2.html:
//模擬一個簡單的參數處理操做 switch(location.hash){ case '#paramdo': callBack(); break; case '#paramset': //do something…… break; } function callBack(){ try { parent.location.hash = 'somedata'; } catch (e) { // ie、chrome的安全機制沒法修改parent.location.hash, // 因此要利用一箇中間的cnblogs域下的代理iframe var ifrproxy = document.createElement('iframe'); ifrproxy.style.display = 'none'; ifrproxy.src = 'http://a.com/test/cscript/cs3.html#somedata'; // 注意該文件在"a.com"域下 document.body.appendChild(ifrproxy); } }
a.com下的域名cs3.html
//由於parent.parent和自身屬於同一個域,因此能夠改變其location.hash的值 parent.parent.location.hash = self.location.hash.substring(1);
window.name 的美妙之處:name 值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值(2MB)。
1) 建立a.com/cs1.html
2) 建立a.com/proxy.html,並加入以下代碼
<head> <script> function proxy(url, func){ var isFirst = true, ifr = document.createElement('iframe'), loadFunc = function(){ if(isFirst){ ifr.contentWindow.location = 'http://a.com/cs1.html'; isFirst = false; }else{ func(ifr.contentWindow.name); ifr.contentWindow.close(); document.body.removeChild(ifr); ifr.src = ''; ifr = null; } }; ifr.src = url; ifr.style.display = 'none'; if(ifr.attachEvent) ifr.attachEvent('onload', loadFunc); else ifr.onload = loadFunc; document.body.appendChild(iframe); } </script> </head> <body> <script> proxy('http://www.baidu.com/', function(data){ console.log(data); }); </script> </body>
3 在b.com/cs1.html中包含:
<script> window.name = '要傳送的內容'; </script>
1) a.com/index.html中的代碼:
<iframe id="ifr" src="b.com/index.html"></iframe> <script type="text/javascript"> window.onload = function() { var ifr = document.getElementById('ifr'); var targetOrigin = 'http://b.com'; // 若寫成'http://b.com/c/proxy.html'效果同樣 // 若寫成'http://c.com'就不會執行postMessage了 ifr.contentWindow.postMessage('I was there!', targetOrigin); }; </script>
2) b.com/index.html中的代碼:
<script type="text/javascript"> window.addEventListener('message', function(event){ // 經過origin屬性判斷消息來源地址 if (event.origin == 'http://a.com') { alert(event.data); // 彈出"I was there!" alert(event.source); // 對a.com、index.html中window對象的引用 // 但因爲同源策略,這裏event.source不能夠訪問window對象 } }, false); </script>
CORS背後的思想,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從而決定請求或響應是應該成功,仍是應該失敗。
var xdr = new XDomainRequest(); xdr.onload = function(){ console.log(xdr.responseText); } xdr.open('get', 'http://www.baidu.com'); ...... xdr.send(null);
var xhr = new XMLHttpRequest(); xhr.onreadystatechange = function () { if(xhr.readyState == 4){ if(xhr.status >= 200 && xhr.status < 304 || xhr.status == 304){ console.log(xhr.responseText); } } } xhr.open('get', 'http://www.baidu.com'); ...... xhr.send(null);
function createCORS(method, url){ var xhr = new XMLHttpRequest(); if('withCredentials' in xhr){ xhr.open(method, url, true); }else if(typeof XDomainRequest != 'undefined'){ var xhr = new XDomainRequest(); xhr.open(method, url); }else{ xhr = null; } return xhr; } var request = createCORS('get', 'http://www.baidu.com'); if(request){ request.onload = function(){ ...... }; request.send(); }
JSONP包含兩部分:回調函數和數據。
回調函數是當響應到來時要放在當前頁面被調用的函數。
數據就是傳入回調函數中的json數據,也就是回調函數的參數了。
function handleResponse(response){ console.log('The responsed data is: '+response.data); } var script = document.createElement('script'); script.src = 'http://www.baidu.com/json/?callback=handleResponse'; document.body.insertBefore(script, document.body.firstChild); /*handleResonse({"data": "zhe"})*/ //原理以下: //當咱們經過script標籤請求時 //後臺就會根據相應的參數(json,handleResponse) //來生成相應的json數據(handleResponse({"data": "zhe"})) //最後這個返回的json數據(代碼)就會被放在當前js文件中被執行 //至此跨域通訊完成
jsonp雖然很簡單,可是有以下缺點:
1)安全問題(請求代碼中可能存在安全隱患)
2)要肯定jsonp請求是否失敗並不容易
web sockets是一種瀏覽器的API,它的目標是在一個單獨的持久鏈接上提供全雙工、雙向通訊。(同源策略對web sockets不適用)
web sockets原理:在js建立了web socket以後,會有一個HTTP請求發送到瀏覽器以發起鏈接。取得服務器響應後,創建的鏈接會使用HTTP升級從HTTP協議交換爲web sockt協議。
只有在支持web socket協議的服務器上才能正常工做。
var socket = new WebSockt('ws://www.baidu.com');//http->ws; https->wss socket.send('hello WebSockt'); socket.onmessage = function(event){ var data = event.data; }
jsonp原理、postMessage原理
首先提一下JSON這個概念,JSON是一種輕量級的數據傳輸格式,被普遍應用於當前Web應用中。JSON格式數據的編碼和解析基本在全部主流語言中都被實現,因此如今大部分先後端分離的架構都以JSON格式進行數據的傳輸。
那麼JSONP是什麼呢?
首先拋出瀏覽器同源策略這個概念,爲了保證用戶訪問的安全,現代瀏覽器使用了同源策略,即不容許訪問非同源的頁面,詳細的概念你們能夠自行百度。這裏你們只要知道,在ajax中,不容許請求非同源的URL就能夠了,好比www.a.com下的一個頁面,其中的ajax請求是不容許訪問www.b.com/c.php這樣一個頁面的。
JSONP就是用來解決跨域請求問題的,那麼具體是怎麼實現的呢?
ajax請求受同源策略影響,不容許進行跨域請求,而script標籤src屬性中的連接卻能夠訪問跨域的js腳本,利用這個特性,服務端再也不返回JSON格式的數據,而是返回一段調用某個函數的js代碼,在src中進行了調用,這樣實現了跨域。
1.首先看下ajax中若是進行跨域請求會如何。
前端代碼在域www.practice.com下面,使用ajax發送了一個跨域的get請求
<!DOCTYPE html> <html> <head> <title>GoJSONP</title> </head> <body> <script type="text/javascript"> function jsonhandle(data){ alert("age:" + data.age + "name:" + data.name); } </script> <script type="text/javascript" src="jquery-1.8.3.min.js"> </script> <script type="text/javascript"> $(document).ready(function(){ $.ajax({ type : "get", async: false, url : "http://www.practice-zhao.com/student.php?id=1", type: "json", success : function(data) { jsonhandle(data); } }); }); </script> </body> </html>
後端PHP代碼放在域www.practice-zhao.com下,簡單的輸出一段json格式的數據
jsonhandle({ "age" : 15, "name": "John", })
當訪問前端代碼http://www.practice.com/gojsonp/index.html 時 chrome報如下錯誤
提示了不一樣源的URL禁止訪問
2.下面使用JSONP,將前端代碼中的ajax請求去掉,添加了一個script標籤,標籤的src指向了另外一個域www.practice-zhao.com下的remote.js腳本
<!DOCTYPE html> <html> <head> <title>GoJSONP</title> </head> <body> <script type="text/javascript"> function jsonhandle(data){ alert("age:" + data.age + "name:" + data.name); } </script> <script type="text/javascript" src="jquery-1.8.3.min.js"> </script> <script type="text/javascript" src="http://www.practice-zhao.com/remote.js"></script> </body> </html>
這裏調用了跨域的remote.js腳本,remote.js代碼以下:
jsonhandle({ "age" : 15, "name": "John", })
也就是這段遠程的js代碼執行了上面定義的函數,彈出了提示框
3.將前端代碼再進行修改,代碼以下:
<!DOCTYPE html> <html> <head> <title>GoJSONP</title> </head> <body> <script type="text/javascript"> function jsonhandle(data){ alert("age:" + data.age + "name:" + data.name); } </script> <script type="text/javascript" src="jquery-1.8.3.min.js"> </script> <script type="text/javascript"> $(document).ready(function(){ var url = "http://www.practice-zhao.com/student.php?id=1&callback=jsonhandle"; var obj = $('<script><\/script>'); obj.attr("src",url); $("body").append(obj); }); </script> </body> </html>
這裏動態的添加了一個script標籤,src指向跨域的一個php腳本,而且將上面的js函數名做爲callback參數傳入,那麼咱們看下PHP代碼怎麼寫的:
<?php $data = array( 'age' => 20, 'name' => '張三', ); $callback = $_GET['callback']; echo $callback."(".json_encode($data).")"; return;
PHP代碼返回了一段JS語句,即
jsonhandle({ "age" : 15, "name": "張三", })
此時訪問頁面時,動態添加了一個script標籤,src指向PHP腳本,執行返回的JS代碼,成功彈出提示框。
因此JSONP將訪問跨域請求變成了執行遠程JS代碼,服務端再也不返回JSON格式的數據,而是返回了一段將JSON數據做爲傳入參數的函數執行代碼。
4.最後jQuery提供了方便使用JSONP的方式,代碼以下:
<!DOCTYPE html> <html> <head> <title>GoJSONP</title> </head> <body> <script type="text/javascript" src="jquery-1.8.3.min.js"> </script> <script type="text/javascript"> $(document).ready(function(){ $.ajax({ type : "get", async: false, url : "http://www.practice-zhao.com/student.php?id=1", dataType: "jsonp", jsonp:"callback", //請求php的參數名 jsonpCallback: "jsonhandle",//要執行的回調函數 success : function(data) { alert("age:" + data.age + "name:" + data.name); } }); }); </script> </body> </html>
本文講解SendMessage、PostMessage兩個函數的實現原理,分爲三個步驟進行講解,分別適合初級、中級、高級程序員進行理解,三個步驟分別爲:
一、SendMessage、PostMessage的運行機制。
二、SendMessage、PostMessage的運行內幕。
三、SendMessage、PostMessage的內部實現。
注:理解這篇文章以前,必須先了解Windows的消息循環機制。
一、SendMessage、PostMessage的運行機制
咱們先來看最簡單的。
SendMessage能夠理解爲,SendMessage函數發送消息,等待消息處理完成後,SendMessage才返回。稍微深刻一點,是等待窗口處理函數返回後,SendMessage就返回了。
PostMessage能夠理解爲,PostMessage函數發送消息,不等待消息處理完成,馬上返回。稍微深刻一點,PostMessage只管發送消息,消息有沒有被送到則並不關心,只要發送了消息,便馬上返回。
對於寫通常Windows程序的程序員來講,可以這樣理解也就足夠了。但SendMessage、PostMessage真的是一個發送消息等待、一個發送消息不等待嗎?具體細節,下面第2點將會講到。
二、SendMessage、PostMessage的運行內幕
在寫通常Windows程序時,如上第1點講到的足以應付,其實咱們能夠看看MSDN來肯定SendMessage、PostMessage的運行內幕。
在MSDN中,SendMessage解釋如爲:The SendMessage function sends the specified message to a window or windows. It calls the window procedure for the specified window and does not return until the window procedure has processed the message.
翻譯成中文爲:SendMessage函數將指定的消息發到窗口。它調用特定窗口的窗口處理函數,而且不會當即返回,直到窗口處理函數處理了這個消息。
再看看PostMessage的解釋:The PostMessage function places (posts) a message in the message queue associated with the thread that created the specified window and returns without waiting for the thread to process the message.
翻譯成中文爲:PostMessage函數將一個消息放入與建立這個窗口的消息隊列相關的線程中,並馬上返回不等待線程處理消息。
仔細看完MSDN解釋,咱們瞭解到,SendMessage的確是發送消息,而後等待處理完成返回,但發送消息的方法爲直接調用消息處理函數(即WndProc函數),按照函數調用規則,確定會等消息處理函數返回以後,SendMessage才返回。而PostMessage卻沒有發送消息,PostMessage是將消息放入消息隊列中,而後馬上返回,至於消息什麼時候被處理,PostMessage徹底不知道,此時只有消息循環知道被PostMessage的消息什麼時候被處理了。
至此咱們撥開了一層疑雲,原來SendMessage只是調用咱們的消息處理函數,PostMessage只是將消息放到消息隊列中。下一節將會更深刻這兩個函數,看看Microsoft到底是如何實現這兩個函數的。
三、SendMessage、PostMessage的內部實現
Windows內部運行原理、機制每每是咱們感興趣的東西,而這些東西又沒有被文檔化,因此咱們只能使用Microsoft提供的工具本身研究了。
首先,在基本Win32工程代碼中,咱們能夠直接看到消息處理函數、消息循環,因此創建一個基本Win32工程(本篇文章使用VS2005),爲了看到更多信息,咱們須要進行設置,讓VS2005載入Microsoft的Symbol(pdb)文件[1]。爲了方便,去除了一些多餘的代碼,加入了兩個菜單,ID分別爲:IDM_SENDMESSAGE、IDM_POSTMESSAGE。以下列出通過簡化後的必要的代碼。
消息循環:
Ln000:while (GetMessage(&msg, NULL, 0, 0))
Ln001:{
Ln002: TranslateMessage(&msg);
Ln003: DispatchMessage(&msg);
Ln004:}
消息處理函數:
Ln100:LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
Ln101:{
Ln102: int wmId, wmEvent;
Ln103: switch (message)
Ln104: {
Ln105: case WM_COMMAND:
Ln106: wmId = LOWORD(wParam);
Ln107: wmEvent = HIWORD(wParam);
Ln108: switch (wmId)
Ln109: {
Ln110: case IDM_EXIT:
Ln111: DestroyWindow(hWnd);
Ln112: break;
Ln113: case IDM_SENDMESSAGE:
Ln114: SendMessage(hWnd, WM_SENDMESSAGE, 0, 0);
Ln115: break;
Ln116: case IDM_POSTMESSAGE:
Ln117: PostMessage(hWnd, WM_POSTMESSAGE, 0, 0);
Ln118: break;
Ln119: default:
Ln120: return DefWindowProc(hWnd, message, wParam, lParam);
Ln121: }
Ln122: break;
Ln123:
Ln124: case WM_SENDMESSAGE:
Ln125: MessageBox(hWnd, L"SendMessage", L"Prompt", MB_OK);
Ln126: break;
Ln127:
Ln128: case WM_POSTMESSAGE:
Ln129: MessageBox(hWnd, L"PostMessage", L"Prompt", MB_OK);
Ln130: break;
Ln131:
Ln132: case WM_DESTROY:
Ln133: PostQuitMessage(0);
Ln134:
Ln135: default:
Ln136: return DefWindowProc(hWnd, message, wParam, lParam);
Ln137: }
Ln138: return 0;
Ln139:}
下面一步步分析這兩個函數的內部狀況,先討論 SendMessage。
第一步,在DispatchMessage(Ln003)函數處下個斷點,F5進行調試,當程序運行到斷點後,查看 CallStack 窗口,可得以下結果:
#003:MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, int nCmdShow=0x00000001) Line 49 C++
#002:MyProj.exe!__tmainCRTStartup() Line 589 + 0x35 bytes C
#001:MyProj.exe!wWinMainCRTStartup() Line 414 C
#000:kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
咱們能夠看到,進程先調用 kernel32.dll 中的 BaseProcessStart 函數,而後調用的 Startup Code 的函數 wWinMainCRTStartup,而後調用 _tmainCRTStartup 函數,最終調用咱們的 wWinMain函數,咱們的程序就運行起來了。
第二步,去除第一步下的斷點,在 WndProc(Ln101) 函數入口處下個斷點,F5 繼續運行,運行到新下的斷點處,查看 CallStack 窗口,可得以下結果:
#008:MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000101, unsigned int wParam=0x00000074, long lParam=0xc03f0001) Line 122 C++
#007:user32.dll!_InternalCallWinProc@20() + 0x28 bytes
#006:user32.dll!_UserCallWinProcCheckWow@32() + 0xb7 bytes
#005:user32.dll!_DispatchMessageWorker@8() + 0xdc bytes
#004:user32.dll!_DispatchMessageW@4() + 0xf bytes
#003:MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, #000:int nCmdShow=0x00000001) Line 49 + 0xc bytes C++
#002:MyProj.exe!__tmainCRTStartup() Line 589 + 0x35 bytes C
#001:MyProj.exe!wWinMainCRTStartup() Line 414 C
#000:kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
#000~#003 跟第一步相同,再也不解釋。在 #00四、#005,能夠看到,函數運行到DispatchMessage 的內部了,DispatchMessageW、DispatchMessageWorker 是 user32.dll 中處處的函數,並且函數前部字符串相等,在此猜測應該是 DispatchMessage 的內部處理。#008 爲咱們消息處理函數,因此推想而知,#00六、#007 是爲了調用咱們的消息處理函數而準備的代碼。
第三步,去除第二步下的斷點,在Ln00三、Ln11四、Ln11五、Ln125 處分別下一個斷點,在菜單中選擇對應項,使程序運行至 Ln114,F10下一步,能夠看到並無運行到 break(Ln115),直接跳到了 Ln125 處,由此可知目前 SendMessage 已經在等待了,查看 CallStack 窗口,可得以下結果:
#013:MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000500, unsigned int wParam=0x00000000, long lParam=0x00000000) Line 147 C++
#012:user32.dll!_InternalCallWinProc@20() + 0x28 bytes
#011:user32.dll!_UserCallWinProcCheckWow@32() + 0xb7 bytes
#010:user32.dll!_SendMessageWorker@20() + 0xc8 bytes
#009:user32.dll!_SendMessageW@16() + 0x49 bytes
#008:MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000111, unsigned int wParam=0x00008003, long lParam=0x00000000) Line 136 + 0x15 bytes C++
#007:user32.dll!_InternalCallWinProc@20() + 0x28 bytes
#006:user32.dll!_UserCallWinProcCheckWow@32() + 0xb7 bytes
#005:user32.dll!_DispatchMessageWorker@8() + 0xdc bytes
#004:user32.dll!_DispatchMessageW@4() + 0xf bytes
#003:MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, #000:int nCmdShow=0x00000001) Line 49 + 0xc bytes C++
#002:MyProj.exe!__tmainCRTStartup() Line 589 + 0x35 bytes C
#001:MyProj.exe!wWinMainCRTStartup() Line 414 C
#000:kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
#000~#008 跟上面的相同,再也不解釋。在 #00九、#010,能夠看到,函數調用到 SendMessage 內部了,在此猜測應該是 SendMessage 的內部處理。#0十一、#012 跟第二步中的 #00六、#007 同樣,在第二部中,這兩個函數是爲了調用消息處理函數而準備的代碼,#013 也是咱們的消息處理函數,因此此兩行代碼的功能相等。
至此,咱們證實了 SendMessage 的確是直接調用消息處理函數的,在消息處理函數返回前,SendMessage 等待。在全部的操做中,Ln003 斷點沒有去到,證實 SendMessage 不會將消息放入消息隊列中(在 PostMessage 分析中,此斷點將會跑到,接下來說述)。
第四步,F5繼續運行,此時彈出對話框,點擊對話框中的肯定後,運行到斷點 Ln115 處。查看CallStack 窗口,可得以下結果:
#008:MyProj.exe!WndProc(HWND__ * hWnd=0x00050860, unsigned int message=0x00000111, unsigned int wParam=0x00008003, long lParam=0x00000000) Line 137 C++
#007:user32.dll!_InternalCallWinProc@20() + 0x28 bytes
#006:user32.dll!_UserCallWinProcCheckWow@32() + 0xb7 bytes
#005:user32.dll!_DispatchMessageWorker@8() + 0xdc bytes
#004:user32.dll!_DispatchMessageW@4() + 0xf bytes
#003:MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, int nCmdShow=0x00000001) Line 49 + 0xc bytes C++
#002:MyProj.exe!__tmainCRTStartup() Line 589 + 0x35 bytes C
#001:MyProj.exe!wWinMainCRTStartup() Line 414 C
#000:kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
#000~008 跟第二步的徹底相同,此時 SendMessage 也已經返回,所調用的堆棧也清空了。
至此,咱們完全撥開了 SendMessage 的疑雲,瞭解了 SendMessage 函數的運行機制,綜述爲,SendMessage 內部調用 SendMessageW、SendMessageWorker 函數作內部處理,而後調用UserCallWinProcCheckWow、InternalCallWinProc 來調用咱們代碼中的消息處理函數,消息處理函數完成以後,SendMessage 函數便返回了。
SendMessage 討論完以後,如今討論 PostMessage,將上面的全部斷點刪除,關閉調試。
第一步,在DispatchMessage(Ln003)函數處下個斷點,F5進行調試,此處結果跟 SendMessage同樣,再也不說明。
第二步,去除第一步下的斷點,在 WndProc(Ln101) 函數入口處下個斷點,F5 繼續運行,此處結果跟 SendMessage 同樣,再也不說明。
第三步,去除第二步下的斷點,在 Ln00三、Ln11七、Ln11八、Ln129 處分別下一個斷點,在菜單中選擇對應項,使程序運行至 Ln117,F10 下一步,能夠看到已經運行到 break,PostMessage 函數返回了,此時 CallStack 沒有變化。
第四步,F5 繼續運行,此時程序運行到 Ln003,CallStack 跟第一步剛起來時同樣。
第五步,F5 繼續運行(因爲有多個消息,可能要按屢次),讓程序運行到 Ln129,此時CallStack 跟第二步相同,爲了方便說明,再次列舉以下:
#008:MyProj.exe!WndProc(HWND__ * hWnd=0x00070874, unsigned int message=0x00000501, unsigned int wParam=0x00000000, long lParam=0x00000000) Line 151 C++
#007:user32.dll!_InternalCallWinProc@20() + 0x28 bytes
#006:user32.dll!_UserCallWinProcCheckWow@32() + 0xb7 bytes
#005:user32.dll!_DispatchMessageWorker@8() + 0xdc bytes
#004:user32.dll!_DispatchMessageW@4() + 0xf bytes
#003:MyProj.exe!wWinMain(HINSTANCE__ * hInstance=0x00400000, HINSTANCE__ * hPrevInstance=0x00000000, wchar_t * lpCmdLine=0x000208e0, int nCmdShow=0x00000001) Line 49 + 0xc bytes C++
#002:MyProj.exe!__tmainCRTStartup() Line 589 + 0x35 bytes C
#001:MyProj.exe!wWinMainCRTStartup() Line 414 C
#000:kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
由此能夠看到,此調用是從消息循環中調用而來,DispatchMessageW、DispatchMessageWorker是 DispatchMessage 的內部處理,UserCallWinProcCheckWow、InternalCallWinProc是爲了調用咱們的消息處理函數而準備的代碼。
至此,咱們再次完全撥開了 PostMessage 的疑雲,瞭解了 PostMessage 函數的運行機制,綜述爲,PostMessage 將消息放入消息隊列中,本身馬上返回,消息循環中的 GetMessage(PeekMessage也可,本例中爲演示)處理到咱們發的消息以後,便按照普通消息處理方法進行處理。
實現拖拽功能,好比把5個兄弟節點中的最後一個節點拖拽到節點1和節點2之間
若是要設置物體拖拽,那麼必須使用三個事件,而且這三個事件的使用順序不能顛倒。
拖拽的基本原理就是根據鼠標的移動來移動被拖拽的元素。鼠標的移動也就是x、y座標的變化;元素的移動就是style.position的 top和left的改變。固然,並非任什麼時候候移動鼠標都要形成元素的移動,而應該判斷鼠標左鍵的狀態是否爲按下狀態,是不是在可拖拽的元素上按下的。
基本思路以下:部分實例代碼:
HTML部分CSS部分
js部分
動畫:setTimeout什麼時候執行,requestAnimationFrame的優勢
setTimeout()和setInterval()常常被用來處理延時和定時任務。setTimeout() 方法用於在指定的毫秒數後調用函數或計算表達式,而setInterval()則能夠在每隔指定的毫秒數循環調用函數或表達式,直到clearInterval把它清除。
從定義上咱們能夠看到兩個函數十分相似,只不過前者執行一次,然後者能夠執行屢次,兩個函數的參數也相同,第一個參數是要執行的code或句柄,第二個是延遲的毫秒數。
很簡單的定義,使用起來也很簡單,但有時候咱們的代碼並非按照咱們的想象精確時間被調用的,很讓人困惑
看個簡單的例子,簡單頁面在加載完兩秒後,寫下Delayed alert!
setTimeout('document.write("Delayed alert!");', 2000);
看起來很合理,咱們再看個setInterVal()方法的例子
var num = 0; var i = setInterval(function() { num++; var date = new Date(); document.write(date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds() + '<br>'); if (num > 10) clearInterval(i); }, 1000);
頁面每隔1秒記錄一次當前時間(分鐘:秒:毫秒),記錄十次後清除,再也不記錄。考慮到代碼執行時間可能記錄的不是執行時間,但時間間隔應該是同樣的,看看結果
43:38:116 43:39:130 43:40:144 43:41:158 43:42:172 43:43:186 43:44:200 43:45:214 43:46:228 43:47:242 43:48:256
時間間隔幾乎是1000毫秒,但不精確,這是爲何呢?緣由在於咱們對JavaScript定時器存在一個誤解,JavaScript實際上是運行在單線程的環境中的,這就意味着定時器僅僅是計劃代碼在將來的某個時間執行,而具體執行時機是不能保證的,由於頁面的生命週期中,不一樣時間可能有其餘代碼在控制JavaScript進程。在頁面下載完成後代碼的運行、事件處理程序、Ajax回調函數都是使用一樣的線程,實際上瀏覽器負責進行排序,指派某段程序在某個時間點運行的優先級。
咱們把效果放大一下看看,添加一個耗時的任務
function test() { for (var i = 0; i < 500000; i++) { var div = document.createElement('div'); div.setAttribute('id', 'testDiv'); document.body.appendChild(div); document.body.removeChild(div); } } setInterval(test, 10); var num = 0; var i = setInterval(function() { num++; var date = new Date(); document.write(date.getMinutes() + ':' + date.getSeconds() + ':' + date.getMilliseconds() + '<br>'); if (num > 10) clearInterval(i); }, 1000);
咱們又加入了一個定時任務,看看結果
47:9:222 47:12:482 47:16:8 47:19:143 47:22:631 47:25:888 47:28:712 47:32:381 47:34:146 47:35:565 47:37:406
這下效果明顯了,差距甚至都超過了3秒,並且差距很不一致。
咱們能夠能夠把JavaScript想象成在時間線上運行。當頁面載入的時候首先執行的是頁面生命週期後面要用的方法和變量聲明和數據處理,在這以後JavaScript進程將等待更多代碼執行。當進程空閒的時候,下一段代碼會被觸發
除了主JavaScript進程外,還須要一個在進程下一次空閒時執行的代碼隊列。隨着頁面生命週期推移,代碼會按照執行順序添加入隊列,例如當按鈕被按下的時候他的事件處理程序會被添加到隊列中,並在下一個可能時間內執行。在接到某個Ajax響應時,回調函數的代碼會被添加到隊列。JavaScript中沒有任何代碼是當即執行的,但一旦進程空閒則儘快執行。定時器對隊列的工做方式是當特定時間過去後將代碼插入,這並不意味着它會立刻執行,只能表示它儘快執行。
知道了這些後,咱們就能明白,若是想要精確的時間控制,是不能依賴於JavaScript的setTimeout函數的。
使用 setInterval() 建立的定時器能夠使代碼循環執行,到有指定效果的時候,清除interval就能夠,以下例
var my_interval = setInterval(function () { if (condition) { //.......... } else { clearInterval(my_interval); } }, 100);
但這個方式的問題在於定時器的代碼可能在代碼再次被添加到隊列以前尚未執行完成,結果致使循環內的判斷條件不許確,代碼多執行幾回,之間沒有停頓。不過JavaScript已經解決這個問題,當使用setInterval()時,僅當沒有該定時器的其餘代碼實例時纔將定時器代碼插入隊列。這樣確保了定時器代碼加入到隊列的最小時間間隔爲指定間隔。
這樣的規則帶來兩個問題
爲了不這兩個缺點,咱們能夠使用setTimeout()來實現重複的定時器
setTimeout(function () { //code setTimeout(arguments.callee, interval); }, interval)
這樣每次函數執行的時候都會建立一個新的定時器,第二個setTimeout()調用使用了agrument.callee 來獲取當前實行函數的引用,並設置另一個新定時器。這樣作能夠保證在代碼執行完成前不會有新的定時器插入,而且下一次定時器代碼執行以前至少要間隔指定時間,避免連續運行。
setTimeout(function () { var div = document.getElementById('moveDiv'); var left = parseInt(div.style.left) + 5; div.style.left = left + 'px'; if (left < 200) { setTimeout(arguments.callee, 50); } }, 50);
這段定時器代碼每次執行的時候,把一個div向右移動5px,當座標大於200的時候中止。
在瀏覽器動畫程序中,咱們一般使用一個定時器來循環每隔幾毫秒移動目標物體一次,來讓它動起來。現在有一個好消息,瀏覽器開發商們決定:「嗨,爲何咱們不在瀏覽器裏提供這樣一個API呢,這樣一來咱們能夠爲用戶優化他們的動畫。」因此,這個requestAnimationFrame()
函數就是針對動畫效果的API,你能夠把它用在DOM上的風格變化或畫布動畫或WebGL中。
瀏覽器能夠優化並行的動畫動做,更合理的從新排列動做序列,並把可以合併的動做放在一個渲染週期內完成,從而呈現出更流暢的動畫效果。好比,經過requestAnimationFrame()
,JS動畫可以和CSS動畫/變換或SVG SMIL動畫同步發生。另外,若是在一個瀏覽器標籤頁裏運行一個動畫,當這個標籤頁不可見時,瀏覽器會暫停它,這會減小CPU,內存的壓力,節省電池電量。
// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function( callback ){ window.setTimeout(callback, 1000 / 60); }; })(); // usage: // instead of setInterval(render, 16) .... (function animloop(){ requestAnimFrame(animloop); render(); })(); // place the rAF *before* the render() to assure as close to // 60fps with the setTimeout fallback.
Opera瀏覽器的技術師Erik Möller 把這個函數進行了封裝,使得它能更好的兼容各類瀏覽器。你能夠讀一讀這篇文章,但基本上他的代碼就是判斷使用4ms
仍是16ms
的延遲,來最佳匹配60fps。下面就是這段代碼,你能夠使用它,但請注意,這段代碼裏使用的是標準函數,我給它加上了兼容各類瀏覽器引擎前綴。
(function() { var lastTime = 0; var vendors = ['webkit', 'moz']; for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; } if (!window.requestAnimationFrame) window.requestAnimationFrame = function(callback, element) { var currTime = new Date().getTime(); var timeToCall = Math.max(0, 16 - (currTime - lastTime)); var id = window.setTimeout(function() { callback(currTime + timeToCall); }, timeToCall); lastTime = currTime + timeToCall; return id; }; if (!window.cancelAnimationFrame) window.cancelAnimationFrame = function(id) { clearTimeout(id); }; }());
window.requestAnimationFrame(function(/* time */ time){ // time ~= +new Date // the unix time });
回調函數裏的參數能夠傳入時間。
谷歌瀏覽器,火狐瀏覽器,IE10+都實現了這個函數,即便你的瀏覽器很古老,上面的對requestAnimationFrame封裝也能讓這個方法在IE8/9上不出錯。
手寫parseInt的實現:要求簡單一些,把字符串型的數字轉化爲真正的數字便可,但不能使用JS原生的字符串轉數字的API,好比Number()
編寫分頁器組件的時候,爲了減小服務端查詢次數,點擊「下一頁」怎樣能確保還有數據能夠加載(請求數據不會爲空)?
ES6新增了哪些特性,使用過哪些,也有當場看代碼說輸出結果的
ES6是即將到來的新版本JavaScript語言的標準,他給咱們帶來了更「甜」的語法糖(一種語法,使得語言更容易理解和更具備可讀性,也讓咱們編寫代碼更加簡單快捷),如箭頭函數(=>)、class等等。用一句話來講就是:
ES6給咱們提供了許多的新語法和代碼特性來提升javascript的體驗
不過遺憾的是,如今尚未瀏覽器可以很好的支持es6語法,點這裏查看瀏覽器支持狀況,因此咱們在開發中還須要用babel進行轉換爲CommonJS這種模塊化標準的語法。
由於下面我會講到一些es6新特性的例子,若是想要運行試試效果的話,能夠點這裏去測試es6的代碼。
而後我下面簡單的介紹一些很經常使用的語法特性,若是想完整的瞭解ES6,我推薦你們 點這裏
咱們先來看一個基本的新特性,在javascript中,定義函數須要關鍵字function,可是在es6中,還有更先進的寫法,咱們來看:
es6寫法:
var human = { breathe(name) { //不須要function也能定義breathe函數。 console.log(name + ' is breathing...'); } }; human.breathe('jarson'); //輸出 ‘jarson is breathing...’
轉成js代碼:
var human = { breathe: function(name) { console.log(name + 'is breathing...'); } }; human.breathe('jarson');
很神奇對不對?這樣一對比,就能夠看出es6的寫法讓人簡單易懂。彆着急,下面還有更神奇的。
咱們知道,javascript不像java是面向對象編程的語言,而只能夠說是基於對象編程的語言。因此在js中,咱們一般都是用function和prototype來模擬 類 這個概念。
可是如今有了es6,咱們能夠像java那樣‘明目張膽’的建立一個類了:
class Human { constructor(name) { this.name = name; } breathe() { console.log(this.name + " is breathing"); } } var man = new Human("jarson"); man.breathe(); //jarson is breathing
上面代碼轉爲js格式:
function Human(name) { this.name = name; this.breathe = function() { console.log(this.name + ' is breathing'); } } var man = new Human('jarson'); man.breathe(); //jarson is breathing
因此咱們看到,咱們能夠像java那樣語義化的去建立一個類。另外,js中的繼承父類,須要用prototype來實現。那麼在es6中,又有什麼新的方法來實現類的繼承呢?繼續看:
假如咱們要建立一個Man類繼承上面的Human類,es6代碼:
class Man extends Human { constructor(name, sex) { super(name); this.sex = sex; } info(){ console.log(this.name + 'is ' + this.sex); } } var xx = new Man('jarson', 'boy'); xx.breathe(); //jarson is breathing xx.info(); //arsonis boy
代碼很簡單,不做贅述,能夠使用文章裏提到的在線工具去試試效果就能明白了。須要注意的是: super() 是父類的構造函數。
在ES6標準中,javascript原生支持module了。將不一樣功能的代碼分別寫在不一樣文件中,各模塊只需 導出(export) 公共接口部分,而後在須要使用的地方經過模塊的 導入(import) 就能夠了。下面繼續看例子:
ES6模塊裏的對象可在建立它們的聲明中直接導出,一個模塊中可無數次使用export。
先看模塊文件 app.js :
export class Human{ constructor(name) { this.name = name; } breathe() { console.log(this.name + " is breathing"); } } export function run(){ console.log('i am runing'); } function eat() { console.log('i am eating'); }
例子中的模塊導出了兩個對象:Human類和run函數, eat函數沒有導出,則仍爲此模塊私有,不能被其餘文件使用。
另外,其實若是須要導出的對象不少的時候,咱們能夠在最後統一導出一組對象。
更改 app.js 文件:
class Human{ constructor(name) { this.name = name; } breathe() { console.log(this.name + " is breathing"); } } function run(){ console.log('i am runing'); } function eat() { console.log('i am eating'); } export {Human, run};
這樣的寫法功能和上面同樣,並且也很明顯,在最後能夠清晰的看到導出了哪些對象。
導出時使用關鍵字default,可將對象標註爲default對象導出。default關鍵字在每個模塊中只能使用一次。它既能夠用於內聯導出,也能夠用於一組對象導出聲明中。
查看導出default對象的語法:
... //建立類、函數等等 export default { //把Human類和run函數標註爲default對象導出。 Human, run };
若是模塊包含一些邏輯要執行,且不會導出任何對象,此類對象也能夠被導入到另外一模塊中,導入以後只執行邏輯。如:
import './module1.js';
使用Default導出方式導出對象,該對象在import聲明中將直接被分配給某個引用,以下例中的「app」。
import app from './module1.js';
上面例子中,默認 ./module1.js 文件只導出了一個對象;若導出了一組對象,則應該在導入聲明中一一列出這些對象,如:
import {Human, run} from './app.js'
在我看來,在es6新特性中,在定義變量的時候通通使用 let 來代替 var 就行了, const 則很直觀,用來定義常量,即沒法被更改值的變量。
for (let i=0;i<2;i++) { console.log(i); //輸出: 0,1 }
ES6中新增的箭頭操做符 => 簡化了函數的書寫。操做符左邊爲輸入的參數,而右邊則是進行的操做以及返回的值,這樣的寫法能夠爲咱們減小大量的代碼,看下面的實例:
let arr = [6, 8, 10, 20, 15, 9]; arr.forEach((item, i) => console.log(item, i)); let newArr = arr.filter((item) => (item<10)); console.log(newArr); //[6, 8, 9];
上面的 (item, i) 就是參數,後面的 console.log(item, i) 就是回到函數要執行的操做邏輯。
上面代碼轉爲js格式:
var arr = [6, 8, 10, 20, 15, 9]; arr.forEach(function(item, i) { return console.log(item, i); }); var newArr = arr.filter(function(item) { return (item < 10); }); console.log(newArr);
ES6中容許使用反引號 ` 來建立字符串,此種方法建立的字符串裏面能夠包含由美圓符號加花括號包裹的變量${vraible}。看一下實例就會明白了:
//產生一個隨機數 let num = Math.random(); //將這個數字輸出到console console.log(`your num is ${num}`);
若一個函數要返回多個值,常規的作法是返回一個對象,將每一個值作爲這個對象的屬性返回。在ES6中,利用解構這一特性,能夠直接返回一個數組,而後數組中的值會自動被解析到對應接收該值的變量中。咱們來看例子:
function getVal() { return [1, 2]; } var [x,y] = getVal(); //函數返回值的解構 console.log('x:'+x+', y:'+y); //輸出:x:1, y:2
如今能夠在定義函數的時候指定參數的默認值了,而不用像之前那樣經過邏輯或操做符來達到目的了。
function sayHello(name){ var name=name||'tom'; //傳統的指定默認參數的方式 console.log('Hello '+name); } //運用ES6的默認參數 function sayHello2(name='tom'){ //若是沒有傳這個參數,纔會有默認值, console.log(`Hello ${name}`); } sayHello();//輸出:Hello tom sayHello('jarson');//輸出:Hello jarson sayHello2();//輸出:Hello tom sayHello2('jarson');//輸出:Hello jarson
注意: sayHello2(name='tom') 這裏的等號,意思是沒有傳這個參數,則設置默認值,而不是給參數賦值的意思。
Proxy能夠監聽對象身上發生了什麼事情,並在這些事情發生後執行一些相應的操做。一會兒讓咱們對一個對象有了很強的追蹤能力,同時在數據綁定方面也頗有用處。
//定義被監聽的目標對象 let engineer = { name: 'Joe Sixpack', salary: 50 }; //定義處理程序 let interceptor = { set(receiver, property, value) { console.log(property, 'is changed to', value); receiver[property] = value; } }; //建立代理以進行偵聽 engineer = new Proxy(engineer, interceptor); //作一些改動來觸發代理 engineer.salary = 70;//控制檯輸出:salary is changed to 70
對於處理程序,是在被監聽的對象身上發生了相應事件以後,處理程序裏面的方法就會被調用。
總的來講,雖然支持es6的狀況到目前還不是很樂觀,但es6的新語法特性讓前端和後端的差別愈來愈小了,這是一個新時代的開始,咱們必需要了解這些新的前沿知識,才能跟上時代的步伐。
require.js的實現原理(若是使用過webpack,進一步會問,二者打包的異同及優缺點)
衆所周知,Javascript有一個很棒的模塊化庫requireJS,這個基於AMD規範的js庫受到愈來愈多的程序員喜好,那麼,下面就來談談我對requireJS的研究和理解。
1
2
3
4
5
6
7
8
9
10
|
require.config({
paths: {
"a": "a",
"b": "b"
}
});
require(['a'], function (a){
console.log('main');
//console.log(a);
});
|
1
2
3
4
5
6
|
define([
'b'], function(b){
console.log('a');
return {
'text' : 1
}
})
|
1
2
3
4
|
define(
function(){
console.log('b');
//return 1;
})
|
1
2
3
|
b b.js:
2
a a.js:
2
main main.js:
8
|
1
2
|
//Create default context.
req({});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
req = requirejs =
function (deps, callback, errback, optional) {
//Find the right context, use default
var context, config,
contextName = defContextName;
// Determine if have config object in the call.
if (!isArray(deps) && typeof deps !== 'string') {
// deps is a config object
config = deps;
if (isArray(callback)) {
// Adjust args if there are dependencies
deps = callback;
callback = errback;
errback = optional;
}
else {
deps = [];
}
}
if (config && config.context) {
contextName = config.context;
}
context = getOwn(contexts, contextName);
if (!context) {
context = contexts[contextName] = req.s.newContext(contextName);
}
if (config) {
context.configure(config);
}
var fg = context.require(deps, callback, errback);
return fg;
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
Module:
function (map) {
completeLoad:
function (moduleName) {
config:
Object
configure:
function (cfg) {
contextName:
"_"
defQueue:
Array[0]
defined:
Object
enable:
function (depMap) {
execCb:
function (name, callback, args, exports) {
load:
function (id, url) {
makeModuleMap:
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
makeRequire:
function (relMap, options) {
makeShimExports:
function (value) {
nameToUrl:
function (moduleName, ext, skipExt) {
nextTick:
function (fn) {
onError:
function onError(err, errback) {
onScriptError:
function (evt) {
onScriptLoad:
function (evt) {
registry:
Object
require: function localRequire(deps, callback, errback) {
urlFetched:
Object
__proto__:
Object
|
1
2
3
4
5
6
7
8
9
10
|
if (isBrowser) {
head = s.head =
document.getElementsByTagName('head')[0];
//If BASE tag is in play, using appendChild is a problem for IE6.
//When that browser dies, this can be removed. Details in this jQuery bug:
//http://dev.jquery.com/ticket/2709
baseElement =
document.getElementsByTagName('base')[0];
if (baseElement) {
head = s.head = baseElement.parentNode;
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
if (isBrowser && !cfg.skipDataMain) {
eachReverse(scripts(),
function (script) {
if (!head) {
head = script.parentNode;
}
dataMain = script.getAttribute(
'data-main');
if (dataMain) {
mainScript = dataMain;
if (!cfg.baseUrl) {
src = mainScript.split(
'/');
mainScript = src.pop();
subPath = src.length ? src.join(
'/') + '/' : './';
cfg.baseUrl = subPath;
}
mainScript = mainScript.replace(jsSuffixRegExp,
'');
if (req.jsExtRegExp.test(mainScript)) {
mainScript = dataMain;
}
cfg.deps = cfg.deps ? cfg.deps.concat(mainScript) : [mainScript];
return true;
}
});
}
|
1
2
3
4
5
6
7
|
Object {baseUrl: "./", deps: Array[1]}
baseUrl:
"./"
deps:
Array[1]
0: "main"
length:
1
__proto__:
Array[0]
__proto__:
Object
|
1
2
|
//Set up with config info.
req(cfg);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
function localRequire(deps, callback, errback) {
var id, map, requireMod;
if (options.enableBuildCallback && callback && isFunction(callback)) {
callback.__requireJsBuild =
true;
}
if (typeof deps === 'string') {
if (isFunction(callback)) {
//Invalid call
return onError(makeError('requireargs', 'Invalid require call'), errback);
}
if (relMap && hasProp(handlers, deps)) {
return handlers[deps](registry[relMap.id]);
}
if (req.get) {
return req.get(context, deps, relMap, localRequire);
}
//Normalize module name, if it contains . or ..
map = makeModuleMap(deps, relMap,
false, true);
id = map.id;
if (!hasProp(defined, id)) {
return onError(makeError('notloaded', 'Module name "' +
id +
'" has not been loaded yet for context: ' +
contextName +
(relMap ?
'' : '. Use require([])')));
}
return defined[id];
}
intakeDefines();
context.nextTick(
function () {
intakeDefines();
requireMod = getModule(makeModuleMap(
null, relMap));
requireMod.skipMap = options.skipMap;
requireMod.init(deps, callback, errback, {
enabled: true
});
checkLoaded();
});
return localRequire;
}
|
1
2
3
|
req.nextTick =
typeof setTimeout !== 'undefined' ? function (fn) {
setTimeout(fn,
4);
} :
function (fn) { fn(); };
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
context.nextTick(
function () {
//Some defines could have been added since the
//require call, collect them.
intakeDefines();
requireMod = getModule(makeModuleMap(
null, relMap));
//Store if map config should be applied to this require
//call for dependencies.
requireMod.skipMap = options.skipMap;
requireMod.init(deps, callback, errback, {
enabled: true
});
checkLoaded();
});
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
Module =
function (map) {
this.events = getOwn(undefEvents, map.id) || {};
this.map = map;
this.shim = getOwn(config.shim, map.id);
this.depExports = [];
this.depMaps = [];
this.depMatched = [];
this.pluginMaps = {};
this.depCount = 0;
/* this.exports this.factory
this.depMaps = [],
this.enabled, this.fetched
*/
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
init:
function (depMaps, factory, errback, options) {
options = options || {};
if (this.inited) {
return;
}
this.factory = factory;
if (errback) {
this.on('error', errback);
}
else if (this.events.error) {
errback = bind(
this, function (err) {
this.emit('error', err);
});
}
this.depMaps = depMaps && depMaps.slice(0);
this.errback = errback;
this.inited = true;
this.ignore = options.ignore;
if (options.enabled || this.enabled) {
//Enable this module and dependencies.
//Will call this.check()
this.enable();
}
else {
this.check();
}
},
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
req.load =
function (context, moduleName, url) {
var config = (context && context.config) || {},
node;
if (isBrowser) {
node = req.createNode(config, moduleName, url);
node.setAttribute(
'data-requirecontext', context.contextName);
node.setAttribute(
'data-requiremodule', moduleName);
if (node.attachEvent &&
!(node.attachEvent.toString && node.attachEvent.toString().indexOf(
'[native code') < 0) &&
!isOpera) {
useInteractive =
true;
node.attachEvent(
'onreadystatechange', context.onScriptLoad);
}
else {
node.addEventListener(
'load', context.onScriptLoad, false);
node.addEventListener(
'error', context.onScriptError, false);
}
node.src = url;
currentlyAddingScript = node;
if (baseElement) {
head.insertBefore(node, baseElement);
}
else {
head.appendChild(node);
}
currentlyAddingScript =
null;
return node;
}
else if (isWebWorker) {
try {
importScripts(url);
context.completeLoad(moduleName);
}
catch (e) {
context.onError(makeError(
'importscripts',
'importScripts failed for ' +
moduleName +
' at ' + url,
e,
[moduleName]));
}
}
};
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
context:
Module:
function (map) {
completeLoad:
function (moduleName) {
config:
Object
configure:
function (cfg) {
contextName:
"_"
defQueue:
Array[0]
defined:
Object
enable:
function (depMap) {
execCb:
function (name, callback, args, exports) {
load:
function (id, url) {
makeModuleMap:
function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
makeRequire:
function (relMap, options) {
makeShimExports:
function (value) {
nameToUrl:
function (moduleName, ext, skipExt) {
nextTick:
function (fn) {
onError:
function onError(err, errback) {
onScriptError:
function (evt) {
onScriptLoad:
function (evt) {
registry:
Object
require: function localRequire(deps, callback, errback) {
startTime:
1407753904901
urlFetched:
Object
moduleName:
main
url:
./main.js
|
1
|
<script type="text/javascript" charset="utf-8" async="" data-requirecontext="_" data-requiremodule="main" src="./main.js"></script>
|
區別不大,都是引用的寫法,
require是common.js的寫法,
import是ES6的寫法,
主要功能都是引入模塊,
寫法上:
var modal = require('...') import modal from '....'
不用這麼麻煩,簡單記住 require 是 webpack 1.x 的寫法,webpack 2.x 用 import 就好了
promise的實現原理,進一步會問async、await是否使用過
這兩天在熟悉 kissy 框架的時候,看到了 Promise
模塊。 Promise
對於一個Jser並不陌生, Promise
相似於一個事務管理器,它的做用就是將各類內嵌回調的事務用流水形式表達。利用 Promise
可讓異步編程更符合人的直覺,讓代碼邏輯更加清晰,把開發人員從回調地獄中釋放出來。這麼「高大上」的東西,之前寫 nodejs
代碼的時候只是簡單的用用,尚未理解其基本的實現原理,罪過!我的認爲,理解編程思想最好的途徑就是閱讀一份簡易的實現源碼。很幸運,網上有很多Promise的簡易實現,其中 這篇博文 介紹的實現方式很是贊,下面就來好好研究下吧!
目前, Promise
是 ECMAScript 6
規範的重要特性之一,各大瀏覽器也開始慢慢支持這一特性。固然,也有一些第三方內庫實現了該功能,如: Q 、 when 、 WinJS 、 RSVP.js 等。
Promise
對象用來進行延遲( deferred
)和異步( asynchronous
)計算.。一個 Promise
處於如下四種狀態之一:
fulfilled
或 rejected
Promise
對象有兩個重要的方法,一個是 then
,另外一個是 resolve
:
Promise
經常使用方式以下:
var p = new Promise(function(resolve, reject) { ... // 事務觸發 resovle(xxx); ... }); p.then(function(value) { // 知足 }, function(reason) { // 拒絕 }).then().then()...
示意圖以下:
1. Promise
其實就是一個狀態機。按照它的定義,咱們可從以下基礎代碼開始:
var PENDING = 0; // 進行中 var FULFILLED = 1; // 成功 var REJECTED = 2; // 失敗 function Promise() { // 存儲PENDING, FULFILLED或者REJECTED的狀態 var state = PENDING; // 存儲成功或失敗的結果值 var value = null; // 存儲成功或失敗的處理程序,經過調用`.then`或者`.done`方法 var handlers = []; // 成功狀態變化 function fulfill(result) { state = FULFILLED; value = result; } // 失敗狀態變化 function reject(error) { state = REJECTED; value = error; } }
2.下面是 Promise
的 resolve
方法實現:
注意: resolve
方法可接收的參數有兩種:一個普通的值/對象或者一個 Promise
對象。若是是普通的值/對象,則直接把結果傳遞到下一個對象;若是是一個 Promise
對象,則必須先等待這個子任務序列完成。
function Promise() { ... function resolve(result) { try { var then = getThen(result); // 若是是一個promise對象 if (then) { doResolve(then.bind(result), resolve, reject); return; } // 修改狀態,傳遞結果到下一個事務 fulfill(result); } catch (e) { reject(e); } } }
兩個輔助方法:
/** * Check if a value is a Promise and, if it is, * return the `then` method of that promise. * * @param {Promise|Any} value * @return {Function|Null} */ function getThen(value) { var t = typeof value; if (value && (t === 'object' || t === 'function')) { var then = value.then; if (typeof then === 'function') { return then; } } return null; } /** * Take a potentially misbehaving resolver function and make sure * onFulfilled and onRejected are only called once. * * Makes no guarantees about asynchrony. * * @param {Function} fn A resolver function that may not be trusted * @param {Function} onFulfilled * @param {Function} onRejected */ function doResolve(fn, onFulfilled, onRejected) { var done = false; try { fn(function(value) { if (done) return; done = true; onFulfilled(value); }, function(reason) { if (done) return; done = true; onRejected(reason); }); } catch(ex) { if (done) return; done = true; onRejected(ex); } }
3.上面已經完成了一個完整的內部狀態機,但咱們並無暴露一個方法去解析或則觀察 Promise
。如今讓咱們開始解析 Promise
:
function Promise(fn) { ... doResolve(fn, resolve, reject); }
如你所見,咱們複用了 doResolve
,由於對於初始化的 fn
也要對其進行控制。 fn
容許調用 resolve
或則 reject
屢次,甚至拋出異常。這徹底取決於咱們去保證 promise
對象僅被 resolved
或則 rejected
一次,且狀態不能隨意改變。
4.目前,咱們已經有了一個完整的狀態機,但咱們仍然沒有辦法去觀察它的任何變化。咱們最終的目標是實現 then
方法,但 done
方法彷佛更簡單,因此讓咱們先實現它。
咱們的目標是實現 promise.done(onFullfilled, onRejected)
:
onFulfilled
和 onRejected
二者只能有一個被執行,且執行次數爲一promise
鏈式調用結束promise
已經被解析,均可以調用該方法var PENDING = 0; // 進行中 var FULFILLED = 1; // 成功 var REJECTED = 2; // 失敗 function Promise() { // 存儲PENDING, FULFILLED或者REJECTED的狀態 var state = PENDING; // 存儲成功或失敗的結果值 var value = null; // 存儲成功或失敗的處理程序,經過調用`.then`或者`.done`方法 var handlers = []; // 成功狀態變化 function fulfill(result) { state = FULFILLED; value = result; handlers.forEach(handle); handlers = null; } // 失敗狀態變化 function reject(error) { state = REJECTED; value = error; handlers.forEach(handle); handlers = null; } function resolve(result) { try { var then = getThen(result); if (then) { doResolve(then.bind(result), resolve, reject) return } fulfill(result); } catch (e) { reject(e); } } // 不一樣狀態,進行不一樣的處理 function handle(handler) { if (state === PENDING) { handlers.push(handler); } else { if (state === FULFILLED && typeof handler.onFulfilled === 'function') { handler.onFulfilled(value); } if (state === REJECTED && typeof handler.onRejected === 'function') { handler.onRejected(value); } } } this.done = function (onFulfilled, onRejected) { // 保證異步 setTimeout(function () { handle({ onFulfilled: onFulfilled, onRejected: onRejected }); }, 0); } doResolve(fn, resolve, reject); }
當 Promise
被 resolved
或者 rejected
時,咱們保證 handlers
將被通知。
5.如今咱們已經實現了 done
方法,下面實現 then
方法就很容易了。須要注意的是,咱們要在處理程序中新建一個 Promise
。
this.then = function (onFulfilled, onRejected) { var self = this; return new Promise(function (resolve, reject) { return self.done(function (result) { if (typeof onFulfilled === 'function') { try { // onFulfilled方法要有返回值! return resolve(onFulfilled(result)); } catch (ex) { return reject(ex); } } else { return resolve(result); } }, function (error) { if (typeof onRejected === 'function') { try { return resolve(onRejected(error)); } catch (ex) { return reject(ex); } } else { return reject(error); } }); }); }
完成了上面的代碼,測試就很容易啦。偷個懶,測試實例來自MDN:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>promise test</title> <script src="./mypromise.js"></script> </head> <body> <button id="test">promise test</button> <div id="log"></div> <script> var promiseCount = 0; function testPromise() { var thisPromiseCount = ++promiseCount; var log = document.getElementById('log'); log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 開始(同步代碼開始)'); var p1 = new Promise( function(resolve, reject) { log.insertAdjacentHTML('beforeend', thisPromiseCount + ') Promise開始(異步代碼開始)'); window.setTimeout(function() { resolve(thisPromiseCount); }, Math.random() * 2000 + 1000); } ); p1.then( function(val) { log.insertAdjacentHTML('beforeend', val + ') Promise被知足了(異步代碼結束)'); } ); log.insertAdjacentHTML('beforeend', thisPromiseCount + ') 創建了Promise(同步代碼結束)'); } document.querySelector('button').addEventListener('click', testPromise); </script> </body> </html>
今天在stackOverflow網站看到一個很好的解釋,摘抄併發揮一下,
It works similarly to the yield return keyword in C# 2.0.
An asynchronous method is not actually an ordinary sequential method. It is compiled into a state machine (an object) with some state (local variables are turned into fields of the object). Each block of code between two uses of await is one "step" of the state machine.
This means that when the method starts, it just runs the first step and then the state machine returns and schedules some work to be done - when the work is done, it will run the next step of the state machine. For example this code:
實現gulp的功能
當一個大項目逐漸成型,或者一個框架又或者一個開發方式逐漸成型的時候,總會有一個所謂的「套路」,咱們在工做中每每遵循着這個套路走。因此更換一家公司或者一個部門團隊的時候,上手項目並不難,你只須要掌握這個團隊管用的「套路」就ok了,關鍵是:要想辦法優化這個「套路」。
以前一直在作內部框架的跨平臺和自動化構建的事,加上開發業務邏輯的頁面已經完成,就沒去優化這個開發套路,什麼套路呢?當項目中須要一個新的H5頁面的時候,就須要手動去copy以前的一個頁面代碼,而後逐個修改,改爲另一個頁面。去掉代碼中的業務邏輯,會發現除了名稱不一樣,其他的代碼所有相同,秉承着「上級命令必定要完成」總宗旨,非也,是秉承着「我是一個程序員」的宗旨,就應該將一切須要手工完成的工做變成自動化的。因此……因此就不吹NB了,好好寫……
一個古老的思路是,你應該有一套模板,當有新的頁面須要開發的時候,只需一條命令或者一個按鈕就能夠自動幫你基於這套模板建立一個可直接用於開發的環境。爲了達成這個目的,我是用到了:
1.gulp
gulp的教程這裏就不寫了,這裏講的很清楚 gulp教程
2.該功能主要使用到的gulp插件
gulp-load-plugins 加載gulp插件的插件
gulp-file-include 文件包含插件
gulp-data 提供數據,該數據可被其餘gulp插件使用
gulp-rename 重命名文件
gulp-template 渲染模板
上面的插件鏈接,點擊進去就是文檔。
筆者認爲最好的學習方式就是有一個能運行起來的項目,而後看着代碼一步步走,因此我把模板化從公司的項目中抽離出來,並作了刪減,提煉出一個完整的可運行的項目,並放在個人git倉庫,能夠運行一下命令查看效果,調試並學習:
git clone git@github.com:HcySunYang/modapp.git
npm install
gulp createapp --name=aaa
下面是gulpfile.js文件和package.json文件
var gulp = require('gulp');
var gulpLoadPlugins = require("gulp-load-plugins");
var plugins = gulpLoadPlugins();
var util = require("gulp-util");
var devPath = './html';
var appData = {};
/*
* @desc 組裝模板
* @src devPath
* @deps
* @dest devPath + '/tmod/app/dest'
*/
gulp.task('includeTpl',function () {
// 獲取 gulp 命令的 --name參數的值 (gulp createapp --name=aaa)
var appName = util.env.name || 'special';
// 首字母大寫
var appNameBig = appName.replace((/\w/), function(char){
return char.toUpperCase();
});
appData={
app: appName,
appapi: appNameBig,
appDo: appName + "Do",
title: appName
}
return gulp.src([
devPath + '/tmod/app/app.tpl',
devPath + '/tmod/app/appDo.tpl',
devPath + '/tmod/app/app.html',
devPath + '/tmod/app/appApi.tpl',
devPath + '/tmod/app/appapiInterFace.tpl'
])
.pipe(plugins.fileInclude({
prefix: '@@',
basepath: '@file'
}))
.pipe(gulp.dest(devPath + '/tmod/app/dest'));
});
/*
* @desc 解析模板
* @src devPath
* @deps includeTpl
* @dest devPath + '/tmod/app/dest'
*/
gulp.task('resolveTpl',["includeTpl"],function () {
return gulp.src([
devPath + '/tmod/app/dest/app.tpl',
devPath + '/tmod/app/dest/appDo.tpl',
devPath + '/tmod/app/dest/app.html',
devPath + '/tmod/app/dest/appApi.tpl',
devPath + '/tmod/app/dest/appapiInterFace.tpl'
])
.pipe(plugins.data(function () {
return {app: appData.app, appDo:appData.appDo,title:appData.title, appapi : appData.appapi};
}))
.pipe(plugins.template())
.pipe(gulp.dest(devPath + '/tmod/app/dest'));
});
/*
* @desc 建立部署
* @src devPath + '/tmod/app/dest
* @deps resolveTpl
* @dest devPath + '/modules/'
*/
gulp.task('createapp', ["resolveTpl"], function () {
// 建立部署入口js文件,如 index.js
gulp.src(devPath + '/tmod/app/dest/app.tpl')
.pipe(plugins.rename({
basename: appData.app,
extname: ".js"
}))
.pipe(gulp.dest(devPath + '/target/'+appData.app));
// 建立部署業務邏輯js文件,如 indexDo.js
gulp.src(devPath + '/tmod/app/dest/appDo.tpl')
.pipe(plugins.rename({
basename: appData.appDo,
extname: ".js"
}))
.pipe(gulp.dest(devPath + '/target/'+appData.app));
// 建立部署html頁面文件,如 index.html
gulp.src([devPath + '/tmod/app/dest/*.html'])
.pipe(plugins.rename({
basename: appData.app,
extname: ".html"
}))
.pipe(gulp.dest(devPath + '/target/'+appData.app));
// 建立部署api接口js文件,如 indexApi.js
gulp.src(devPath + '/tmod/app/dest/appApi.tpl')
.pipe(plugins.rename({
basename: appData.app + 'Api',
extname: ".js"
}))
.pipe(gulp.dest(devPath + '/target/clientApi'));
// 建立部署跨平臺接口js文件,如 indexapiInterFace.js
gulp.src(devPath + '/tmod/app/dest/appapiInterFace.tpl')
.pipe(plugins.rename({
basename: appData.app + 'apiInterFace',
extname: ".js"
}))
.pipe(gulp.dest(devPath + '/target/clientApi'));
});
{
"name": "app",
"project": "app",
"version": "1.0.0",
"host": "http://10.0.69.79",
"path": "/home/huangjian/workstation/bridge/newssdk/bin",
"devDependencies": {
"gulp": "^3.9.0",
"gulp-data": "^1.2.0",
"gulp-file-include": "^0.13.7",
"gulp-load-plugins": "^0.10.0",
"gulp-rename": "^1.2.0",
"gulp-template": "^2.1.0",
"gulp-util": "^3.0.6"
}
}
使用前端框架(angular/vue/react)帶來哪些好處,相對於使用jQuery
vue雙向數據綁定的實現
本文能幫你作什麼?
一、瞭解vue的雙向數據綁定原理以及核心代碼模塊
二、緩解好奇心的同時瞭解如何實現雙向綁定
爲了便於說明原理與實現,本文相關代碼主要摘自vue源碼, 並進行了簡化改造,相對較簡陋,並未考慮到數組的處理、數據的循環依賴等,也不免存在一些問題,歡迎你們指正。不過這些並不會影響你們的閱讀和理解,相信看完本文後對你們在閱讀vue源碼的時候會更有幫助<
本文全部相關代碼均在github上面可找到 https://github.com/DMQ/mvvm
<div id="mvvm-app"> <input type="text" v-model="word"> <p>{{word}}</p> <button v-on:click="sayHi">change model</button> </div> <script src="./js/observer.js"></script> <script src="./js/watcher.js"></script> <script src="./js/compile.js"></script> <script src="./js/mvvm.js"></script> <script> var vm = new MVVM({ el: '#mvvm-app', data: { word: 'Hello World!' }, methods: { sayHi: function() { this.word = 'Hi, everybody!'; } } }); </script>
效果:
目前幾種主流的mvc(vm)框架都實現了單向數據綁定,而我所理解的雙向數據綁定無非就是在單向綁定的基礎上給可輸入元素(input、textare等)添加了change(input)事件,來動態修改model和 view,並無多高深。因此無需太過介懷是實現的單向或雙向綁定。
實現數據綁定的作法有大體以下幾種:
發佈者-訂閱者模式(backbone.js)
髒值檢查(angular.js)
數據劫持(vue.js)
發佈者-訂閱者模式: 通常經過sub, pub的方式實現數據和視圖的綁定監聽,更新數據方式一般作法是 vm.set('property', value)
,這裏有篇文章講的比較詳細,有興趣可點這裏
這種方式如今畢竟太low了,咱們更但願經過 vm.property = value
這種方式更新數據,同時自動更新視圖,因而有了下面兩種方式
髒值檢查: angular.js 是經過髒值檢測的方式比對數據是否有變動,來決定是否更新視圖,最簡單的方式就是經過 setInterval()
定時輪詢檢測數據變更,固然Google不會這麼low,angular只有在指定的事件觸發時進入髒值檢測,大體以下:
DOM事件,譬如用戶輸入文本,點擊按鈕等。( ng-click )
XHR響應事件 ( $http )
瀏覽器Location變動事件 ( $location )
Timer事件( $timeout , $interval )
執行 $digest() 或 $apply()
數據劫持: vue.js 則是採用數據劫持結合發佈者-訂閱者模式的方式,經過Object.defineProperty()
來劫持各個屬性的setter
,getter
,在數據變更時發佈消息給訂閱者,觸發相應的監聽回調。
已經瞭解到vue是經過數據劫持的方式來作數據綁定的,其中最核心的方法即是經過Object.defineProperty()
來實現對屬性的劫持,達到監聽數據變更的目的,無疑這個方法是本文中最重要、最基礎的內容之一,若是不熟悉defineProperty,猛戳這裏
整理了一下,要實現mvvm的雙向綁定,就必需要實現如下幾點:
一、實現一個數據監聽器Observer,可以對數據對象的全部屬性進行監聽,若有變更可拿到最新值並通知訂閱者
二、實現一個指令解析器Compile,對每一個元素節點的指令進行掃描和解析,根據指令模板替換數據,以及綁定相應的更新函數
三、實現一個Watcher,做爲鏈接Observer和Compile的橋樑,可以訂閱並收到每一個屬性變更的通知,執行指令綁定的相應回調函數,從而更新視圖
四、mvvm入口函數,整合以上三者
上述流程如圖所示:
ok, 思路已經整理完畢,也已經比較明確相關邏輯和模塊功能了,let's do it
咱們知道能夠利用Obeject.defineProperty()
來監聽屬性變更
那麼將須要observe的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter
和getter
這樣的話,給這個對象的某個值賦值,就會觸發setter
,那麼就能監聽到了數據變化。。相關代碼能夠是這樣:
var data = {name: 'kindeng'}; observe(data); data.name = 'dmq'; // 哈哈哈,監聽到值變化了 kindeng --> dmq function observe(data) { if (!data || typeof data !== 'object') { return; } // 取出全部屬性遍歷 Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); }; function defineReactive(data, key, val) { observe(val); // 監聽子屬性 Object.defineProperty(data, key, { enumerable: true, // 可枚舉 configurable: false, // 不能再define get: function() { return val; }, set: function(newVal) { console.log('哈哈哈,監聽到值變化了 ', val, ' --> ', newVal); val = newVal; } }); }
這樣咱們已經能夠監聽每一個數據的變化了,那麼監聽到變化以後就是怎麼通知訂閱者了,因此接下來咱們須要實現一個消息訂閱器,很簡單,維護一個數組,用來收集訂閱者,數據變更觸發notify,再調用訂閱者的update方法,代碼改善以後是這樣:
// ... 省略 function defineReactive(data, key, val) { var dep = new Dep(); observe(val); // 監聽子屬性 Object.defineProperty(data, key, { // ... 省略 set: function(newVal) { if (val === newVal) return; console.log('哈哈哈,監聽到值變化了 ', val, ' --> ', newVal); val = newVal; dep.notify(); // 通知全部訂閱者 } }); } function Dep() { this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); } };
那麼問題來了,誰是訂閱者?怎麼往訂閱器添加訂閱者?
沒錯,上面的思路整理中咱們已經明確訂閱者應該是Watcher, 並且var dep = new Dep();
是在 defineReactive
方法內部定義的,因此想經過dep
添加訂閱者,就必需要在閉包內操做,因此咱們能夠在 getter
裏面動手腳:
// Observer.js // ...省略 Object.defineProperty(data, key, { get: function() { // 因爲須要在閉包內添加watcher,因此經過Dep定義一個全局target屬性,暫存watcher, 添加完移除 Dep.target && dep.addDep(Dep.target); return val; } // ... 省略 }); // Watcher.js Watcher.prototype = { get: function(key) { Dep.target = this; this.value = data[key]; // 這裏會觸發屬性的getter,從而添加訂閱者 Dep.target = null; } }
這裏已經實現了一個Observer了,已經具有了監聽數據和數據變化通知訂閱者的功能,完整代碼。那麼接下來就是實現Compile了
compile主要作的事情是解析模板指令,將模板中的變量替換成數據,而後初始化渲染頁面視圖,並將每一個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變更,收到通知,更新視圖,如圖所示:
由於遍歷解析的過程有屢次操做dom節點,爲提升性能和效率,會先將跟節點el
轉換成文檔碎片fragment
進行解析編譯操做,解析完成,再將fragment
添加回原來的真實dom節點中
function Compile(el) {
this.$el = this.isElementNode(el) ? el : document.querySelector(el); if (this.$el) { this.$fragment = this.node2Fragment(this.$el); this.init(); this.$el.appendChild(this.$fragment); } } Compile.prototype = { init: function() { this.compileElement(this.$fragment); }, node2Fragment: function(el) { var fragment = document.createDocumentFragment(), child; // 將原生節點拷貝到fragment while (child = el.firstChild) { fragment.appendChild(child); } return fragment; } };
compileElement方法將遍歷全部節點及其子節點,進行掃描解析編譯,調用對應的指令渲染函數進行數據渲染,並調用對應的指令更新函數進行綁定,詳看代碼及註釋說明:
Compile.prototype = {
// ... 省略
compileElement: function(el) {
var childNodes = el.childNodes, me = this;
[].slice.call(childNodes).forEach(function(node) { var text = node.textContent; var reg = /\{\{(.*)\}\}/; // 表達式文本 // 按元素節點方式編譯 if (me.isElementNode(node)) { me.compile(node); } else if (me.isTextNode(node) && reg.test(text)) { me.compileText(node, RegExp.$1); } // 遍歷編譯子節點 if (node.childNodes && node.childNodes.length) { me.compileElement(node); } }); }, compile: function(node) { var nodeAttrs = node.attributes, me = this; [].slice.call(nodeAttrs).forEach(function(attr) { // 規定:指令以 v-xxx 命名 // 如 <span v-text="content"></span> 中指令爲 v-text var attrName = attr.name; // v-text if (me.isDirective(attrName)) { var exp = attr.value; // content var dir = attrName.substring(2); // text if (me.isEventDirective(dir)) { // 事件指令, 如 v-on:click compileUtil.eventHandler(node, me.$vm, exp, dir); } else { // 普通指令 compileUtil[dir] && compileUtil[dir](node, me.$vm, exp); } } }); } }; // 指令處理集合 var compileUtil = { text: function(node, vm, exp) { this.bind(node, vm, exp, 'text'); }, // ...省略 bind: function(node, vm, exp, dir) { var updaterFn = updater[dir + 'Updater']; // 第一次初始化視圖 updaterFn && updaterFn(node, vm[exp]); // 實例化訂閱者,此操做會在對應的屬性消息訂閱器中添加了該訂閱者watcher new Watcher(vm, exp, function(value, oldValue) { // 一旦屬性值有變化,會收到通知執行此更新函數,更新視圖 updaterFn && updaterFn(node, value, oldValue); }); } }; // 更新函數 var updater = { textUpdater: function(node, value) { node.textContent = typeof value == 'undefined' ? '' : value; } // ...省略 };
這裏經過遞歸遍歷保證了每一個節點及子節點都會解析編譯到,包括了{{}}表達式聲明的文本節點。指令的聲明規定是經過特定前綴的節點屬性來標記,如<span v-text="content" other-attr
中v-text
即是指令,而other-attr
不是指令,只是普通的屬性。
監聽數據、綁定更新函數的處理是在compileUtil.bind()
這個方法中,經過new Watcher()
添加回調來接收數據變化的通知
至此,一個簡單的Compile就完成了,完整代碼。接下來要看看Watcher這個訂閱者的具體實現了
Watcher訂閱者做爲Observer和Compile之間通訊的橋樑,主要作的事情是:
一、在自身實例化時往屬性訂閱器(dep)裏面添加本身
二、自身必須有一個update()方法
三、待屬性變更dep.notice()通知時,能調用自身的update()方法,並觸發Compile中綁定的回調,則功成身退。
若是有點亂,能夠回顧下前面的思路整理
function Watcher(vm, exp, cb) { this.cb = cb; this.vm = vm; this.exp = exp; // 此處爲了觸發屬性的getter,從而在dep添加本身,結合Observer更易理解 this.value = this.get(); } Watcher.prototype = { update: function() { this.run(); // 屬性值變化收到通知 }, run: function() { var value = this.get(); // 取到最新值 var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); // 執行Compile中綁定的回調,更新視圖 } }, get: function() { Dep.target = this; // 將當前訂閱者指向本身 var value = this.vm[exp]; // 觸發getter,添加本身到屬性訂閱器中 Dep.target = null; // 添加完畢,重置 return value; } }; // 這裏再次列出Observer和Dep,方便理解 Object.defineProperty(data, key, { get: function() { // 因爲須要在閉包內添加watcher,因此能夠在Dep定義一個全局target屬性,暫存watcher, 添加完移除 Dep.target && dep.addDep(Dep.target); return val; } // ... 省略 }); Dep.prototype = { notify: function() { this.subs.forEach(function(sub) { sub.update(); // 調用訂閱者的update方法,通知變化 }); } };
實例化Watcher
的時候,調用get()
方法,經過Dep.target = watcherInstance
標記訂閱者是當前watcher實例,強行觸發屬性定義的getter
方法,getter
方法執行的時候,就會在屬性的訂閱器dep
添加當前watcher實例,從而在屬性值有變化的時候,watcherInstance就能收到更新通知。
ok, Watcher也已經實現了,完整代碼。
基本上vue中數據綁定相關比較核心的幾個模塊也是這幾個,猛戳這裏 , 在src
目錄可找到vue源碼。
最後來說講MVVM入口文件的相關邏輯和實現吧,相對就比較簡單了~
MVVM做爲數據綁定的入口,整合Observer、Compile和Watcher三者,經過Observer來監聽本身的model數據變化,經過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通訊橋樑,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據model變動的雙向綁定效果。
一個簡單的MVVM構造器是這樣子:
function MVVM(options) {
this.$options = options; var data = this._data = this.$options.data; observe(data, this); this.$compile = new Compile(options.el || document.body, this) }
可是這裏有個問題,從代碼中可看出監聽的數據對象是options.data,每次須要更新視圖,則必須經過var vm = new MVVM({data:{name: 'kindeng'}}); vm._data.name = 'dmq';
這樣的方式來改變數據。
顯然不符合咱們一開始的指望,咱們所指望的調用方式應該是這樣的:var vm = new MVVM({data: {name: 'kindeng'}}); vm.name = 'dmq';
因此這裏須要給MVVM實例添加一個屬性代理的方法,使訪問vm的屬性代理爲訪問vm._data的屬性,改造後的代碼以下:
function MVVM(options) { this.$options = options; var data = this._data = this.$options.data, me = this; // 屬性代理,實現 vm.xxx -> vm._data.xxx Object.keys(data).forEach(function(key) { me._proxy(key); }); observe(data, this); this.$compile = new Compile(options.el || document.body, this) } MVVM.prototype = { _proxy: function(key) { var me = this; Object.defineProperty(me, key, { configurable: false, enumerable: true, get: function proxyGetter() { return me._data[key]; }, set: function proxySetter(newVal) { me._data[key] = newVal; } }); } };
這裏主要仍是利用了Object.defineProperty()
這個方法來劫持了vm實例對象的屬性的讀寫權,使讀寫vm實例的屬性轉成讀寫了vm._data
的屬性值,達到魚目混珠的效果,哈哈
本文主要圍繞「幾種實現雙向綁定的作法」、「實現Observer」、「實現Compile」、「實現Watcher」、「實現MVVM」這幾個模塊來闡述了雙向綁定的原理和實現。並根據思路流程漸進梳理講解了一些細節思路和比較關鍵的內容點,以及經過展現部分關鍵代碼講述了怎樣一步步實現一個雙向綁定MVVM。
單頁應用,如何實現其路由功能
路由是每一個單頁面網站必需要有的,因此,理解一下原理,我以爲仍是比較重要的。
本篇,基本不會貼代碼,只講原理,代碼在頁底會有githup地址,主意,必定要放在服務本地服務器裏跑(由於有ajax),
但願能幫到你。
衆所周知單頁面網站的路徑跳轉全是經過js來控制的,下面我們來說講
這一種的狀況是url徹底不動,即你的頁面怎麼改變,怎麼跳轉url都不會改變
這種狀況的原理 就是純ajax拿到頁面後替換原頁面中的元素,
這種狀況沒什麼好講的,好的一言不和上代碼 demo(地址在頁底) 這裏有所有的代碼
這種類型的優勢就是刷新頁面,頁面也不會丟。
小明說:若是window有一個事件能讓我監聽url的變化,那我就能實先路由,
那樣我就能夠根據url的變化,來經過ajax請求參數來渲染頁面,
一個url對應一個頁面,也不會重複,多好了。
我:還真有,可是隻能監聽 #後面參數的變化。
小明說:唉,那就湊合一下把。
經過監聽 hash(#)的變化來執行js代碼 從而實現 頁面的改變
核心代碼:
window.addEventListener(‘hashchange‘,function(){
self.urlChange()
})
就是經過這個原理 只要#改變了 就能觸發這個事件,這也是不少單頁面網站的url中都也 (#)的緣由
demo在下面
這種類型是經過html5的最新history api來實現的 能正常的回退前進 很好
url是這樣的 www.ff.ff/jjkj/fdfd/fdf/fd 和普通的url同樣,可是也有缺點 ,就是一刷新頁面 頁面就會丟,
由於 只要刷新 這個url(www.ff.ff/jjkj/fdfd/fdf/fd)就會請求服務器,然而服務器上根本沒有這個資源,因此就會報404,解決方案就 配置一下服務器端(能夠百度一下)
用了 這幾個 api
history.pushState
history.replaceState
history.state
window.onpopstate事件
第一步:history.pushState(null,null,"/abc"); 改變url
第二部:執行一個函數(這個函數裏有改變頁面的代碼)
就這末簡單。
下面講一下這幾個api怎麼用
// @state狀態對象:記錄歷史記錄點的額外對象,能夠爲空
// @title頁面標題:目前全部瀏覽器都不支持
// @url可選的url:瀏覽器不會檢查url是否存在,只改變url,url必須同域,不能跨域
history.pushState的目的
SEO優化
更少的數據請求
更好的用戶體驗
replaceState是將指定的URL替換當前的URL,替換當前歷史記錄點
replaceState的api和pushState相似,不一樣之處在於replaceState不會在window.history裏新增歷史記錄點,而pushState會在歷史記錄點裏新增一個記錄點的
當前URL下對應的狀態信息。若是當前URL不是經過pushState或者replaceState產生的,那麼history.state是null。
state對象雖然能夠存儲不少自定義的屬性,但對於不可序列化的對象則不能存儲
window.onpopstate事件主要是監聽歷史記錄點,也就是說監聽URL的變化,但會忽略URL的hash部分。
history.go和history.back(包括用戶按瀏覽器歷史前進後退按鈕)觸發,而且頁面無刷的時候(因爲使用pushState修改了history)會觸發popstate事件,事件發生時瀏覽器會從history中取出URL和對應的state對象替換當前的URL和history.state。經過event.state也能夠獲取history.state。
注意點:
javascript腳本執行window.history.pushState和window.history.replaceState不會觸發onpopstate事件。
谷歌瀏覽器和火狐瀏覽器在頁面第一次打開的反應是不一樣的,谷歌瀏覽器奇怪的是回觸發onpopstate事件,而火狐瀏覽器則不會。
項目中使用過哪些優化方法
輸入一個URL,Enter以後發生了什麼
原文:http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/
做爲一個軟件開發者,你必定會對網絡應用如何工做有一個完整的層次化的認知,一樣這裏也包括這些應用所用到的技術:像瀏覽器,HTTP,HTML,網絡服務器,需求處理等等。
本文將更深刻的研究當你輸入一個網址的時候,後臺到底發生了一件件什麼樣的事~
導航的第一步是經過訪問的域名找出其IP地址。DNS查找過程以下:
DNS遞歸查找以下圖所示:
DNS有一點使人擔心,這就是像wikipedia.org 或者 facebook.com這樣的整個域名看上去只是對應一個單獨的IP地址。還好,有幾種方法能夠消除這個瓶頸:
大多數DNS服務器使用Anycast來得到高效低延遲的DNS查找。
由於像Facebook主頁這樣的動態頁面,打開後在瀏覽器緩存中很快甚至立刻就會過時,毫無疑問他們不能從中讀取。
因此,瀏覽器將把一下請求發送到Facebook所在的服務器:
GET http://facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: facebook.com
Cookie: datr=1265876274-[...]; locale=en_US; lsd=WW[...]; c_user=2101[...]
GET 這個請求定義了要讀取的URL: 「http://facebook.com/」。 瀏覽器自身定義 (User-Agent 頭), 和它但願接受什麼類型的相應 (Accept andAccept-Encoding 頭). Connection頭要求服務器爲了後邊的請求不要關閉TCP鏈接。
請求中也包含瀏覽器存儲的該域名的cookies。可能你已經知道,在不一樣頁面請求當中,cookies是與跟蹤一個網站狀態相匹配的鍵值。這樣cookies會存儲登陸用戶名,服務器分配的密碼和一些用戶設置等。Cookies會以文本文檔形式存儲在客戶機裏,每次請求時發送給服務器。
用來看原始HTTP請求及其相應的工具不少。做者比較喜歡使用fiddler,固然也有像FireBug這樣其餘的工具。這些軟件在網站優化時會幫上很大忙。
除了獲取請求,還有一種是發送請求,它常在提交表單用到。發送請求經過URL傳遞其參數(e.g.: http://robozzle.com/puzzle.aspx?id=85)。發送請求在請求正文頭以後發送其參數。
像「http://facebook.com/」中的斜槓是相當重要的。這種狀況下,瀏覽器能安全的添加斜槓。而像「http: //example.com/folderOrFile」這樣的地址,由於瀏覽器不清楚folderOrFile究竟是文件夾仍是文件,因此不能自動添加 斜槓。這時,瀏覽器就不加斜槓直接訪問地址,服務器會響應一個重定向,結果形成一次沒必要要的握手。
圖中所示爲Facebook服務器發回給瀏覽器的響應:
HTTP/1.1 301 Moved Permanently
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
Location: http://www.facebook.com/
P3P: CP="DSP LAW"
Pragma: no-cache
Set-Cookie: made_write_conn=deleted; expires=Thu, 12-Feb-2009 05:09:50 GMT;
path=/; domain=.facebook.com; httponly
Content-Type: text/html; charset=utf-8
X-Cnection: close
Date: Fri, 12 Feb 2010 05:09:51 GMT
Content-Length: 0
服務器給瀏覽器響應一個301永久重定向響應,這樣瀏覽器就會訪問「http://www.facebook.com/」 而非「http://facebook.com/」。
爲何服務器必定要重定向而不是直接發會用戶想看的網頁內容呢?這個問題有好多有意思的答案。
其中一個緣由跟搜索引擎排名有 關。你看,若是一個頁面有兩個地址,就像http://www.igoro.com/ 和http://igoro.com/,搜索引擎會認爲它們是兩個網站,結果形成每個的搜索連接都減小從而下降排名。而搜索引擎知道301永久重定向是 什麼意思,這樣就會把訪問帶www的和不帶www的地址歸到同一個網站排名下。
還有一個是用不一樣的地址會形成緩存友好性變差。當一個頁面有好幾個名字時,它可能會在緩存裏出現好幾回。
如今,瀏覽器知道了「http://www.facebook.com/」纔是要訪問的正確地址,因此它會發送另外一個獲取請求:
GET http://www.facebook.com/ HTTP/1.1
Accept: application/x-ms-application, image/jpeg, application/xaml+xml, [...]
Accept-Language: en-US
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; [...]
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Cookie: lsd=XW[...]; c_user=21[...]; x-referer=[...]
Host: www.facebook.com
頭信息以以前請求中的意義相同。
服務器接收到獲取請求,而後處理並返回一個響應。
這表面上看起來是一個順向的任務,但其實這中間發生了不少有意思的東西- 就像做者博客這樣簡單的網站,況且像facebook那樣訪問量大的網站呢!
舉 個最簡單的例子,需求處理能夠以映射網站地址結構的文件層次存儲。像http://example.com/folder1/page1.aspx這個地 址會映射/httpdocs/folder1/page1.aspx這個文件。web服務器軟件能夠設置成爲地址人工的對應請求處理,這樣 page1.aspx的發佈地址就能夠是http://example.com/folder1/page1。
所 有動態網站都面臨一個有意思的難點 -如何存儲數據。小網站一半都會有一個SQL數據庫來存儲數據,存儲大量數據和/或訪問量大的網站不得不找一些辦法把數據庫分配到多臺機器上。解決方案 有:sharding (基於主鍵值講數據表分散到多個數據庫中),複製,利用弱語義一致性的簡化數據庫。
委 託工做給批處理是一個廉價保持數據更新的技術。舉例來說,Fackbook得及時更新新聞feed,但數據支持下的「你可能認識的人」功能只須要每晚更新 (做者猜想是這樣的,改功能如何完善不得而知)。批處理做業更新會致使一些不過重要的數據陳舊,但能使數據更新耕做更快更簡潔。
圖中爲服務器生成並返回的響應:
HTTP/1.1 200 OK
Cache-Control: private, no-store, no-cache, must-revalidate, post-check=0,
pre-check=0
Expires: Sat, 01 Jan 2000 00:00:00 GMT
P3P: CP="DSP LAW"
Pragma: no-cache
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
X-Cnection: close
Transfer-Encoding: chunked
Date: Fri, 12 Feb 2010 09:05:55 GMT
2b3Tn@[...]
整個響應大小爲35kB,其中大部分在整理後以blob類型傳輸。
內容編碼頭告訴瀏覽器整個響應體用gzip算法進行壓縮。解壓blob塊後,你能夠看到以下指望的HTML:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
lang="en" id="facebook" class=" no_js">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-language" content="en" />
...
關於壓縮,頭信息說明了是否緩存這個頁面,若是緩存的話如何去作,有什麼cookies要去設置(前面這個響應裏沒有這點)和隱私信息等等。
請注意報頭中把Content-type設置爲「text/html」。報頭讓瀏覽器將該響應內容以HTML形式呈現,而不是以文件形式下載它。瀏覽器會根據報頭信息決定如何解釋該響應,不過同時也會考慮像URL擴展內容等其餘因素。
在瀏覽器沒有完整接受所有HTML文檔時,它就已經開始顯示這個頁面了:
在瀏覽器顯示HTML時,它會注意到須要獲取其餘地址內容的標籤。這時,瀏覽器會發送一個獲取請求來從新得到這些文件。
下面是幾個咱們訪問facebook.com時須要重獲取的幾個URL:
這些地址都要經歷一個和HTML讀取相似的過程。因此瀏覽器會在DNS中查找這些域名,發送請求,重定向等等...
但 不像動態頁面那樣,靜態文件會容許瀏覽器對其進行緩存。有的文件可能會不須要與服務器通信,而從緩存中直接讀取。服務器的響應中包含了靜態文件保存的期限 信息,因此瀏覽器知道要把它們緩存多長時間。還有,每一個響應均可能包含像版本號同樣工做的ETag頭(被請求變量的實體值),若是瀏覽器觀察到文件的版本 ETag信息已經存在,就立刻中止這個文件的傳輸。
試着猜猜看「fbcdn.NET」在地址中表明什麼?聰明的答案是"Facebook內容分發網絡"。Facebook利用內容分發網絡(CDN)分發像圖片,CSS表和JavaScript文件這些靜態文件。因此,這些文件會在全球不少CDN的數據中心中留下備份。
靜態內容每每表明站點的帶寬大小,也能經過CDN輕鬆的複製。一般網站會使用第三方的CDN。例如,Facebook的靜態文件由最大的CDN提供商Akamai來託管。
舉例來說,當你試着ping static.ak.fbcdn.net的時候,可能會從某個akamai.Net服務器上得到響應。有意思的是,當你一樣再ping一次的時候,響應的服務器可能就不同,這說明幕後的負載平衡開始起做用了。
在Web 2.0偉大精神的指引下,頁面顯示完成後客戶端仍與服務器端保持着聯繫。
以 Facebook聊天功能爲例,它會持續與服務器保持聯繫來及時更新你那些亮亮灰灰的好友狀態。爲了更新這些頭像亮着的好友狀態,在瀏覽器中執行的 javascript代碼會給服務器發送異步請求。這個異步請求發送給特定的地址,它是一個按照程式構造的獲取或發送請求。仍是在Facebook這個例 子中,客戶端發送給http://www.facebook.com/ajax/chat/buddy_list.PHP一個發佈請求來獲取你好友裏哪一個 在線的狀態信息。
提起這個模式,就必需要講講"AJAX"-- 「異步JavaScript 和 XML」,雖然服務器爲何用XML格式來進行響應也沒有個一清二白的緣由。再舉個例子吧,對於異步請求,Facebook會返回一些JavaScript的代碼片斷。
除了其餘,fiddler這個工具可以讓你看到瀏覽器發送的異步請求。事實上,你不只能夠被動的作爲這些請求的看客,還能主動出擊修改和從新發送它們。AJAX請求這麼容易被蒙,可着實讓那些計分的在線遊戲開發者們鬱悶的了。(固然,可別那樣騙人家~)
Facebook聊天功能提供了關於AJAX一個有意思的問題案例:把數據從服務器端推送到客戶端。由於HTTP是一個請求-響應協議,因此聊天服務器不能把新消息發給客戶。取而代之的是客戶端不得不隔幾秒就輪詢下服務器端看本身有沒有新消息。
這些狀況發生時長輪詢是個減輕服務器負載挺有趣的技術。若是當被輪詢時服務器沒有新消息,它就不理這個客戶端。而當還沒有超時的狀況下收到了該客戶的新消息,服務器就會找到未完成的請求,把新消息作爲響應返回給客戶端。
但願看了本文,你能明白不一樣的網絡模塊是如何協同工做的
(承上)頁面的渲染過程
詳解瀏覽器渲染頁面過程
1.解析HTML文件,建立DOM樹
自上而下,遇到任何樣式(link、style)與腳本(script)都會阻塞(外部樣式不阻塞後續外部腳本的加載)。
2.解析CSS
優先級:瀏覽器默認設置<用戶設置<外部樣式<內聯樣式<HTML中的style樣式;
特定級:id數*100+類或僞類數*10+tag名稱*1
3.將CSS與DOM合併,構建渲染樹(renderingtree)
DOM樹與HTML一一對應,渲染樹會忽略諸如head、display:none的元素
4.佈局和繪製,重繪(repaint)和重排(reflow)
重排:若渲染樹的一部分更新,且尺寸變化,就會發生重排;
重繪:部分節點須要更新,但不改變其餘集合形狀。如改變某個元素的顏色,就會發生重繪。
附:
1.重繪和重排什麼時候會發生:
(1)增長或刪除DOM節點;
(2)display:none(重排並重繪);visibility:hidden(重排);
(3)移動頁面中的元素;
(4)增長或修改樣式;
(5)用戶改變窗口大小,滾動頁面等。
2.如何減小重繪和重排以提高頁面性能:
(1)不要一個個修改屬性,應經過一個class來修改
錯誤寫法:div.style.width="50px";div.style.top="60px";
正確寫法:div.className+=" modify";
(2)clone節點,在副本中修改,而後直接替換當前的節點;
(3)若要頻繁獲取計算後的樣式,請暫存起來;
(4)下降受影響的節點:在頁面頂部插入節點將影響後續全部節點。而絕對定位的元素改變會影響較少的元素;
(5)批量添加DOM:多個DOM插入或修改,應組成一個長的字符串後一次性放入DOM。使用innerHTML永遠比DOM操做快。(特別注意:innerHTML不會執行字符串中的嵌入腳本,所以不會產生XSS漏洞)。
優化中會提到緩存的問題,問:靜態資源或者接口等如何作緩存優化
最近遇到項目優化的問題,因爲項目用到的框架,函數庫比較多,致使首次須要加載3.6M的文件,那麼問題來了,當網絡不好的時候,不少文件就會timeout.而後就掛了。因此就開始用到離線緩存,一些文件靜態的函數庫開始緩存起來,一些常常更新的文件每次上線加版本號更新。
下面說說離線緩存,長話短說,很簡單,只要完成簡單的幾個步驟
1,建立一個後綴名爲.appcache的文件(如:list.appcache),裏面配置項也很簡單,同上
CACHE MANIFEST:這裏面把你須要緩存的文件列出來,注意路徑哈。
NETWORK:指定只有經過聯網才能瀏覽的文件.通常寫通配符 * 號(*表明除了在CACHE中的文件)
FALLBACK: 當上面文件嘗試加載失敗時,轉換成下面列出的備用條目。
2.把list.appcache添加到頁面中得HTML中
1
|
<
html
lang="zh-cn" manifest="/list.appcache" type="text/cache-manifest">
|
3.咱們能夠經過調試器看 或者 chrome://appcache-internals/ 能夠訪問
小結:
離線訪問對基於網絡的應用而言愈來愈重要。雖然全部瀏覽器都有緩存機制,但它們並不可靠,也不必定總能起到預期的做用。HTML5 使用ApplicationCache 接口解決了由離線帶來的部分難題。前提是你須要訪問的web頁面至少被在線訪問過一次。
使用離線加載有幾大優點,首先能夠在沒有網絡的狀況下訪問緩存的資源,第二,能夠加快網頁加載速度。
此外, 若是報錯,首先檢測訪問文件地址是否正確(大部分是這個緣由致使),還有就是須要服務器支持,好比tomcat須要修改config文件(nginx我試過,是能夠識別,不用額外修改)
1
2
3
4
|
<
mime-mapping
>
<
extension
>manifest</
extension
>
<
mime-type
>text/cache-manifest</
mime-type
>
</
mime-mapping
>
|
頁面DOM節點太多,會出現什麼問題?如何優化?
這些大公司招聘都是高級工程師起步,因此對簡歷上的項目會刨根問底。不少不少問題都是由項目中拓展開的,像優化相關的東西,還有前面提到的require.js、promise、gulp,項目中用到了某項技術,高級工程師的要求是:不只會用,更要知道其原理。對本身的提醒:項目中用到的技術,不能說徹底掌握其原理吧,但大體的實現仍是有必要了解一下的。
介紹一下你作的這個項目,進一步細問:整個項目有哪些模塊,你主要負責哪些
你在項目中的角色
你在項目中作的最出彩的一個地方
碰到過什麼樣的困難,怎麼解決的
(若是你是這個項目的負責人),任務怎麼分配的,有沒有關注過團隊成員的成長問題
前端安全問題:CSRF和XSS
xss:跨站點攻擊。xss攻擊的主要目的是想辦法獲取目標攻擊網站的cookie,由於有了cookie至關於有了session,有了這些信息就能夠在任意能接進互聯網的PC登錄該網站,並以其餘人的身份登錄作破壞。預防措施防止下發界面顯示html標籤,把</>等符號轉義。
csrf:跨站點假裝請求。csrf攻擊的主要目的是讓用戶在不知情的狀況下攻擊本身已登陸的一個系統,相似於釣魚。如用戶當前已經登錄了郵箱或bbs,同時用戶又在使用另一個,已經被你控制的網站,咱們姑且叫它釣魚網站。這個網站上面可能由於某個圖片吸引你,你去點擊一下,此時可能就會觸發一個js的點擊事件,構造一個bbs發帖的請求,去往你的bbs發帖,因爲當前你的瀏覽器狀態已是登錄狀態,因此session登錄cookie信息都會跟正常的請求同樣,純自然的利用當前的登錄狀態,讓用戶在不知情的狀況下,幫你發帖或幹其餘事情。預防措施,請求加入隨機數,讓釣魚網站沒法正常僞造請求。
爲何選擇作前端(我靠,我都快轉前端兩年了,還在問這個問題啊…)
你但願進入一個什麼樣的團隊
你有什麼問題想問我(面試官)的嗎?
前先後後有兩個月時間,暫時只回憶起這麼多了,若是還有其餘的,後期我會補上。
webpack其實也是必問的,因爲我說還沒使用過webpack,只是瞭解,寫過demo,面試官就沒問太深。若是你的簡歷中有提到webpack,請提早準備好,好比webpack打包原理、如何寫webpack插件等。
面試阿里雲那個崗位的時候,有要求算法和數據結構,有能力者多多準備吧。
阿里、網易的面試幾乎都是圍繞項目展開的,因此提醒本身搬磚的時候多想一想、多看看,多站在一個高度去看整個項目:用到什麼技術,技術實現原理是什麼,項目框架怎麼搭建的,採起安全措施了嗎…
有幾個崗位感受就是掛在了項目上。本身作過一個先後端分離項目,可是通過幾回面試,發現這個項目還存在某些問題,好比:整個登陸註冊系統是不完善的,關於權限的處理上甚至是有很大缺陷的;這個項目的node層只是起到構建前端項目(gulp)、渲染index.ejs、代理轉發api接口等做用,可是面試官指出說你這個node也太簡單了,致使我都在懷疑這是個假的先後端分離…仍是須要大神帶多見見世面啊,求帶…
雖然五次面試都沒成功,但本身也收穫了不少不少:認識了大牛hb,一個超有文藝氣息的資深前端;多謝fw大大幫我內推阿里,十分感謝您對個人承認;也見到了平時只能在視頻上看到的cjf老師,謝謝您的指點;對高級前端工程師所具有的技能有了更清晰的認識;確定也增長了不少面試經驗…
再好好提高一下,打算過段時間從新上陣,也祝本身多點好運氣,早日進入心儀的企業,畢竟,當初來杭州的時候就是以網易、阿里爲目標的。