banner
DIYgod

Hi, DIYgod

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

百度前端技术学院编码挑战(TASK 0004)

任务 4(最终挑战)已经发布,任务 deadline 是 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 Processing#

“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 文件夹。

 

Done,等待毕业答辩喽~

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。