banner
DIYgod

Hi, DIYgod

写代码是热爱,写到世界充满爱!
github
twitter
follow
bilibili
telegram
email
steam
playstation
nintendo switch

百度前端技術學院編碼挑戰(TASK 0004)

任務 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:

task0004_1

task0004_2

切換到另一個錨點的時候,只顯示這個錨點對應的部分,其他部分用 display 隱藏起來。但是沒看懂具體是怎麼實現的。在 console 裡執行 location.href = '#mn'; 也可以自動修改 display,說明是綁定了錨點而不是通過點擊事件來切換的。

李勝菊告訴我是它通過監聽 url 實現,類似 MVC 中的路由。感覺自己實現起來挺困難的。。。但是想到了另外一種方法,簡單地說就是利用 CSS3 裡的 target 偽類,demo 如下:

See the Pen jPMgre by DIYgod (@DIYgod) on CodePen.

於是再改改 CSS,就輕鬆實現了移動端的適配。

task0004_3

task0004_4

task0004_5

task0004_6

又是李勝菊帶我飛,通過分析張鑫旭的 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 思維導圖:

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(/&amp;/g, "&amp;amp;")
              .replace(/</g, "&lt;")
              .replace(/>/g, "&gt;")
              .replace(/"/g, "&amp;quot;")
              .replace(/'/g, "&amp;#x27;")
              .replace(/\//g, "&amp;#x2f;")
              .replace(/\n/g, "<br>");
}

比如用戶輸入:

<iframe src=javascript:alert('xss');height=0 width=0></iframe>

保存後最終存儲的會是:

<iframe src=javascript:alert(&amp;#x27;xss&amp;#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 文件夾。

 

完成,等待畢業答辯喽~

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。