任務 4(最終挑戰)已經發布,任務截止日期是 6 月 10 日至 6 月 30 日。
TASK 0004 內容:https://github.com/baidu-ife/ife/tree/master/task/task0004
我做的:https://github.com/DIYgod/ife-work/tree/master/task0004
在線 Demo: https://www.anotherhome.net/file/ife/task0004
本次任務斷斷續續花費了 20 天(5.20-6.9)的時間。
下面是我做 TASK 0004 過程中的一些記錄。
移動端適配#
“往前推 2 到 3 年,前端工程師還在憂心忡忡地想,移動互聯網時代下,前端是不是沒有生存空間了。但今天一看,在我們團隊,前端工程師超過一半的工作都是在做移動端的 Web 或者 APP 的開發。移動 Web 或者 APP 在技術本質上是和做桌面端 Web 沒有本質區別,但是移動端的坑那是非常的多,通過學習這部分內容,讓你成為一名桌面移動通吃的前端開發工程師。”
要求整個產品為 SPA,剛開始完全沒思路,看了兩天 AngularJS,最後還是決定自己實現。
參考 Gmail:
切換到另一個錨點的時候,只顯示這個錨點對應的部分,其他部分用 display 隱藏起來。但是沒看懂具體是怎麼實現的。在 console 裡執行 location.href = '#mn';
也可以自動修改 display,說明是綁定了錨點而不是通過點擊事件來切換的。
李勝菊告訴我是它通過監聽 url 實現,類似 MVC 中的路由。感覺自己實現起來挺困難的。。。但是想到了另外一種方法,簡單地說就是利用 CSS3 裡的 target 偽類,demo 如下:
See the Pen jPMgre by DIYgod (@DIYgod) on CodePen.
於是再改改 CSS,就輕鬆實現了移動端的適配。
又是李勝菊帶我飛,通過分析張鑫旭的 Mobilebone 框架(官網),我找到了更好的實現,以上實現作廢 23333。
原理是這樣的:切換錨點時會觸發 onhashchange 事件,所以我就在 onhashchange 事件上綁定了一個函數,這個函數會記錄切換前的錨點和切換後的錨點,通過判斷前後錨點來做相應的動作,在切換過程中會給子頁面加上 slide out in reverse 中的某幾個 class,通過這幾個 class 實現滑動效果,具體實現見下面的 CSS 部分,切換完成後隱藏不需要顯示的子頁面和清除之前加上的 class。就這樣。
JS 部分:
/* 滑動效果 */
window.onhashchange = function () {
var newHash = location.hash;
var oldEle = $('.' + oldHash.substr(1));
var newEle = $('.' + newHash.substr(1));
if ((oldHash == '#type' && newHash == '#task') || (oldHash == '#task' && newHash == '#details') ) {
oldEle.className += ' slide out';
newEle.className += ' slide in';
newEle.style.display = 'block';
oldEle.style.display = 'block';
setTimeout(function () {
newEle.style.display = 'block';
oldEle.style.display = 'none';
oldEle.className = oldEle.className.replace(/ slide out/, '');
newEle.className = newEle.className.replace(/ slide in/, '');
}, 225);
}
else if ((oldHash == '#task' && newHash == '#type') || (oldHash == '#details' && newHash == '#task')) {
newEle.className += ' slide reverse in';
oldEle.className += ' slide reverse out';
oldEle.style.display = 'block';
newEle.style.display = 'block';
setTimeout(function () {
oldEle.style.display = 'none';
newEle.style.display = 'block';
newEle.className = newEle.className.replace(/ slide reverse in/, '');
oldEle.className = oldEle.className.replace(/ slide reverse out/, '');
}, 225);
}
oldHash = newHash;
}
CSS 部分:
/* 滑動效果 from mobilebone */
.slide.out, .slide.in {
animation-timing-function: ease-out;
animation-duration: 225ms;
}
.slide.in {
animation-name: slideinfromright;
}
.slide.out {
animation-name: slideouttoleft;
}
.slide.reverse.out {
animation-name: slideouttoright;
}
.slide.reverse.in {
animation-name: slideinfromleft;
}
/* keyframes for slidein from sides */
@-webkit-keyframes slideinfromright {
from {
-webkit-transform: translate3d(100%, 0, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
}
}
@keyframes slideinfromright {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@-webkit-keyframes slideinfromleft {
from {
-webkit-transform: translate3d(-100%, 0, 0);
}
to {
-webkit-transform: translate3d(0, 0, 0);
}
}
@keyframes slideinfromleft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
/* keyframes for slideout to sides */
@-webkit-keyframes slideouttoleft {
from {
-webkit-transform: translate3d(0, 0, 0);
}
to {
-webkit-transform: translate3d(-100%, 0, 0);
}
}
@keyframes slideouttoleft {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
@-webkit-keyframes slideouttoright {
from {
-webkit-transform: translate3d(0, 0, 0);
}
to {
-webkit-transform: translate3d(100%, 0, 0);
}
}
@keyframes slideouttoright {
from {
transform: translateX(0);
}
to {
transform: translateX(100%);
}
}
更新:實測這種方法在 Safari 下表現並不好。
再更新:補充上面:這種方法在貌似在移動端的 Safari 表現並不好,但在 Mac 端的 Safari 表現正常。
CSS 處理#
“CSS 語言由於其自身語言設計的問題,加上一些瀏覽器兼容性問題,往往會使得我們在寫它的時候,要寫很多冗餘代碼,或者為了兼容性對同一個樣式設定寫好幾遍。針對這些問題,誕生了 CSS 預處理和後處理的概念及相關方法、工具。
這些工具和方法幫助我們能夠更加高效地書寫可維護性更強的 CSS 代碼。”
經過調研,我最後決定使用更廣泛的 Less。
根據慕課網教程(less 即學即用)整理的 Less 思維導圖:
CSS 部分重構完畢,終於可以復用了,DRY (Don't repeat yourself)。
另外結合 Grunt 使用 autoprefixer 處理瀏覽器前綴,簡直不能再爽。
安全#
“安全是大家經常容易忽視,但其實一旦出現影響會非常大的問題,尤其對於沒有經歷過企業開發,或者沒有踩過坑的同學,如果等到公司工作,做實際項目後非常容易發生安全問題。”
現有程序存在漏洞,比如在任務內容裡輸入以下內容然後保存,就會執行我們自定義的 script 腳本。
<iframe src=javascript:alert('xss');height=0 width=0></iframe>
或
<img src="1" onerror=eval("\x61\x6c\x65\x72\x74\x28\x27\x78\x73\x73\x27\x29")>
所以要簡單做下 XSS 防護:
大多數情況對用戶的輸入進行處理,只允許輸入合法的值,其它值一概過濾掉。然而更進一步的話,可以對標籤進行轉換。
對輸入的內容做 Html encode 處理:
function htmlEncode(str) {
return str.replace(/&/g, "&amp;")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#x27;")
.replace(/\//g, "&#x2f;")
.replace(/\n/g, "<br>");
}
比如用戶輸入:
<iframe src=javascript:alert('xss');height=0 width=0></iframe>
保存後最終存儲的會是:
<iframe src=javascript:alert(&#x27;xss&#x27;);height=0 width=0></iframe>
之後在展現時瀏覽器會對這些字符轉換成文本內容顯示,而不是一段可執行的代碼。
另外自帶 SSL 加成 2333
性能優化#
“在自己做一些小項目時,可能是學校的一些網站項目,流量可能日均都不超過 500,而且大多是校園局域網內訪問;或者是開發一些實驗室的 MIS 系統,這輩子你都不會去使用你開發的這個系統。在這樣一些項目中,性能優化往往會被你忽視。
但是如果你是做一個日均 PV 數萬、數十萬、甚至更大的量級,開發的頁面會被全國各地,不同網絡條件的用戶來進行訪問。這個時候,性能問題就無法忽視了。在當今的網絡條件下,如果你的頁面 3 秒都無法完成首屏渲染,一定會讓你的网站流失很多用戶。
整個網站的性能優化有很多的環節和工作,大多數時候,不是前端工程師單獨就能完成的,尤其在職能劃分明確的公司中,往往需要前後端、運維、DBA 等多個職位協同完成。所以,在我們的課程中,主要讓你了解整個性能優化都涉及哪些方面的工作,同時,我們會專注介紹一些在前端領域可以重點關注的技術點。”
模塊化#
“對於一個複雜項目,特別是多人協作的複雜項目,如何合理劃分模塊,如何更加方便地進行模塊加載,如何管理模塊之間的依賴,是一個項目團隊都會面臨的問題,目前業界已經有了一些較為普遍的解決方案,如 AMD。這個部分希望你能夠通過學習 JavaScript 的模塊化,學習如何合理地規劃項目模塊,合理使用模塊化工具來優化你的項目代碼結構。”
經過調研,我決定使用 RequireJS 來實現。
將 JS 的引用方式改成這樣
<script src="scripts/require.js" data-main="scripts/main"></script>
再改寫下 JS,把 JS 分為四個模塊:主模塊 gtd util selector。但總感覺分得不太好。。。只能準備答辯時候問下導師了。
其中遇到了一個問題:
有這樣一個 HTML 結構
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Demo</title>
<style>
div {
height: 100px;
width: 100px;
background: #eee;
margin: 10px;
}
</style>
</head>
<body>
<div onclick="myClick();"></div>
<div onclick="myClick();"></div>
<div onclick="myClick();"></div>
<script src="js/main.js"></script>
</body>
</html>
對其進行模塊化改造之後,顯而易見的 myClick 不會再是全局函數,所以無法這樣調用。
於是我嘗試在模塊裡進行 click 事件綁定:
define(function () {
function init() {
var myDiv = document.getElementsByTagName('div');
for (var i = 0; i < myDiv.length; i++) {
myDiv[i].addEventListener('click', myClick(myDiv[i]));
}
myDiv[0].click();
}
function myClick(ele) {
ele.innerHTML = ele.innerHTML + 'click';
}
return {
init: init
}
});
然而喜聞樂見地綁定錯了,你們肯定看出來了,但是我當時沒看出來,而且由於第 5 行那句間接調用了 myClick (myDiv [i]),讓我誤以為是第 10 行調用的結果(這只是個 Demo,當時的情況比這個複雜一些,這兩句調用的結果的確差不多)。
這樣的結果就是 click 綁定的函數在模塊內可以調用(誤以為),但在頁面中點擊卻沒反應。
然後我自作聰明地進行了一番推理:click 事件綁定的 myClick 函數不是全局函數,只在模塊內有效,而在頁面中點擊時會在全局調用 myClick 函數,所以沒反應。
看似有道理卻不是這樣的,在 V2EX 發帖詢問之後,熱心網友 7anshuai 看出了綁定有誤的問題。。
然後改成這樣就好了:
define(function () {
function init() {
var myDiv = document.getElementsByTagName('div');
for (var i = 0; i < myDiv.length; i++) {
myDiv[i].addEventListener('click', myClick);
}
myDiv[0].click();
}
function myClick() {
this.innerHTML = this.innerHTML + 'click';
}
return {
init: init
}
});
期間我還嘗試過在模塊裡主動將函數暴露在全局空間裡,像這樣:
window.myClick = myClick;
雖然有效,但真是爛爆了,幸虧沒就這樣算了。。。
6. 前端工程化
“業界目前有非常多的前端開發工具,完成一些開發過程中可以自動化完成的工作,提高研發效率,並且可以提高多人協作時的開發過程一致性,提高整個項目的運維效率。”
經過調研,最終決定採用 Yeoman, Bower, Grunt 三個工具結合來進行工程化改造。
根據慕課網教程(Grunt-beginner 前端自動化工具)整理的思維導圖:
用 Yeoman 新建一個 webapp 項目(需翻牆),安裝其他需要的包,改改配置文件,然後就可以享受各種自動化工具帶來的無比高效、震撼的體驗啦~
我這裡主要對代碼做了 less 編譯 處理 CSS 前綴 HTML、CSS、JS 壓縮 文件名添加 md5 值 這幾個處理,其中處理前的文件在 app 文件夾,處理後的文件在 disk 文件夾。
完成,等待畢業答辯喽~