假如苏大强做前端优化

2,787

前言

上周末将自己的阿里面试经历写成博文发布到掘金后,发现很多小伙伴都撸起袖子自己动手,撸起代码毫不含糊!我也很喜欢这种学习分享的氛围,但是公开地泄露名企笔试题目会产生一丝丝不好的影响,对于努力付出的小伙伴也是一种不公平的待遇!正所谓授之于鱼不如授之于渔,我更希望通过我的分享能召集大家和我一起探讨问题所折射的知识点,而不是探讨问题本身。这样不是更有意思么?于是,我想把我的大厂面试经历通过故事的形式分享给大家,如有不对欢迎各路大佬批评、指正!

故事背景

最近火遍大江南北的电视剧《都挺好》相信大家都有看过,尤其是倪大红老师饰演的“苏大强”更是火爆全网。不知道大家对苏大强的印象是什么,但是这位老爷子却给我带来了太多的欢乐。苏家的老大是位集学历、才华于一身的程序员,已和我等屌丝大众程序员划清了界限!谁说不孤独、不加班的程序员就不是好程序员了?xxx过来挨打。。。

扯远了,我们回归到主题!一想到这里我就萌生出一种思想,如果苏大强也会编程,那么他肯定是一位优秀的程序员,生活中鸡毛蒜皮的事情都能记得清清楚楚,一块钱都花的明明白白。如果做了程序员,内存分配,代码优化肯定不在话下,想想都令人激动。

前端优化

又是一个风和日丽的周末,憋了一周的大强想找点事情做做,此时看到坐在电脑桌前编码的明哲,笑眯眯地凑了上去。

“明哲呀,你在干什么呢?”大强率先开口道。

“哦,爸,公司让我做一个网站,这不还没做完在加班呢,但是我写的网站就是没有京东主站做得好,烦死了!”明哲还在诉苦中,老爷子心思早已悄悄地转移到密密麻麻的电脑屏幕上。

“你这网站打开时间就这么慢,反正我是不会用。据一项研究表明:用户最满意的打开网页时间是2-5秒,如果等待超过10秒,99%的用户会关闭这个网页。所以要在网页加载优化上多花功夫!”老爷子一脸得意的说道,还没等苏明哲反映过来,老爷子又抢着说:“在TCP握手之前要进行DNS查询,也就是域名地址解析,DNS 解析也是需要时间的,可以通过预解析的方式来预先获得域名所对应的 IP”。老爷子嘴上一边说道,双手已经熟练地搭在了最新款MackBook Pro的键盘上,只见他疯狂地在html标签里敲下了如下代码:

<link rel="dns-prefetch" href="http://example.com" />

“再说说比较耗时的操作,比如HTTP的请求。你写的每一个linkimgscript标签都会向服务器发送请求,而每个浏览器同时向同一台服务器发送请求的个数是有限制的,请求数越多浏览器承受的压力就越大,自然效率就越低。对于图片资源你要这么做:”

  • 对于简单的图片如三角形、矩形等能用css实现的就用样式实现;
  • 把常用的图标打成iconfont来使用;
  • 由于浏览器限制了向同一台服务器同时发送请求的次数,所以尽量把你的图片资源放在多台CDN上,这里不限于图片,js和css等静态资源同样适用;
  • 对于小图片用base64编码后直接使用编码文本;
  • 使用CSS Sprites技术来将图片资源合并成雪碧图,webpack不是有这方面的插件吗?去GitHub上面搞它一个不就行了;
  • 多图片列表展示完全可以使用懒加载技术来控制图片的加载;
  • 选择正确的图片格式:WebP、PNG、JPEG。

“说完资源加载,再说说你的文档结构。如果把样式表放在文档底部浏览器会重绘页面元素,阻塞内容逐步呈现,从而造成白屏。JS文件的加载会阻塞DOM树的构建,也可能会导致白屏的出现。这里要给你普及一下script的知识点,当script标签加上defer属性以后,表示该JS文件会并行下载,但是会放到HTML解析完成后顺序执行,而script标签加上async属性以后,表示加载和渲染后续文档元素的过程将和JS文件的加载与执行并行进行。所以你要记住以下两点:”

  • 样式表放在头部;
  • 脚本放在底部。

看着一脸委屈的明哲,苏老无奈地将目标转向了JS代码。“还记得小时候跟你讲的事件的三个阶段吗?捕获阶段目标阶段冒泡阶段,可以看到会有事件冒泡,所以在绑定事件的时候要多使用事件委托而不要直接使用事件绑定,这样会大大提高事件的效率。你看下面这个例子:”

<ul id="ul">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

希望在点击每个LI的时候可以输出该标签的内容,使用事件委托可以这样写:

document.getElementById('ul').onclick = function(event) {
    var event = event || window.event;
    var target = event.target || event.srcElement;
    if (target.nodeName.toLowerCase() == 'li') {
        alert(target.innerHTML);
    }
}

“你看看这里监听了滚动事件,你这样写每次滚动条滚动都会触发该事件,从而执行相应的逻辑操作,这是相当耗时耗力的有一件事情,现在的年轻人写代码都不考虑性能问题吗?过高的触发频率会导致响应速度跟不上触发频率,出现延迟,假死或卡顿的现象,对于触发频率比较高的函数使用节流来限制,保证一定时间内核心代码只执行一次。说到这里,我看你们每次洗菜、洗手都会把水龙头开到最大,难道就不能适当拧紧一点减少不必要的浪费?”。说着顺势编写了个节流函数:

// 节流,每隔一段时间执行一次
function throttle(fn, wait) {
    var timer = null;

    return function() {
        if (!timer) {
            timer = setTimeout(() => {
                fn.apply(this, Array.prototype.slice.call(arguments, 0));
                timer = null;
            }, wait);
        }
    }
}

“你在看看你的搜索框,看样子是想实现输入文本自动联想匹配结果选择这样的功能吧?每次用户在keypress的时候都要异步请求接口,这不浪费吗?你就不会等用户输入完了再进行请求,防抖函数听过没?”

// 防抖,只执行最后一次
function debounce(fn, wait) {
    var timer = null;

    return function() {
        if (timer) clearTimeout(timer);

        timer = setTimeout(() => fn.apply(this, Array.prototype.slice.call(arguments, 0)), wait);
    }
}

此时的明哲被搞得一点颜面都没有,只好沉默不语。老爷子见势再次发起攻击。

“对于DOM操作的优化你也需要知道”。

  • 避免频繁的DOM操作,尽量将操作合并在一起一次性操作;
  • 使用class来代替样式的变更;
  • 使用css动画来代替js动画;
  • 使用requestAnimationFrame代替setInterval操作动画。

“这么做的目的都是为了减少浏览器的重绘与回流,requestAnimationFrame 最大的优势是由系统来决定回调函数的执行时机,具体一点讲,如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象,也不会导致动画出现卡顿的问题。另外css样式也不能乱写,好的css写法要遵循下面的原则:”

  • 正确使用css前缀,以解决浏览器的兼容性;
  • 对于可继承的属性尽量使用继承;
  • 避免css选择器嵌套过深,影响性能;
  • css reset的内容以及基本内容要单独提取出来方便所有页面公用。

“整个项目还需要优化的地方就是构建,同样你要遵循如下几点:”

  • js混淆;
  • 资源压缩,包括js、css、html和图片压缩;
  • 对于单页面应用要考虑公共代码的提取和分离,可以充分利用路由做到按需加载。

“浏览器还有一个比较重要的操作是会缓存站点资源,我们可以充分利用这个优势来优化我们的网站,减少不必要的流量开支。可以在构建的过程中通过hash来命名资源文件,只有内容发生了改动的文件hash才会改变,这样对于不变的文件浏览器就可以从本地缓存方便读取。除了这种操作之外,还可以借助http缓存,利用服务端来设置缓存策略,常见的缓存策略有 强制缓存(Cache-control、Expires)和 协商缓存(ETag、Last-Modified),通过设置不同的header来达到控制浏览器缓存的目的。”

“还有一点优化可能会被忽略,那就是前端的安全性。要注意防止XXS和XSRF攻击,这里给出一个比较详细的 issues 供你参考,性能优化就和理财一模一样,选择正确的理财方式就能...”

还没等苏老说完,手机短信音响起。苏老看着短信内容露出了满意的微笑。。。