总的来说,这本书给我感觉帮助不大,比较适合新手,对新手养成好的习惯有些帮助。更深层次的东西比较少,而且由于书的发行周期,对于前端这种日新月异的技术来说,过时、与新技术新理念脱节,是没法避免的事情(2017.12.24)
第二章 高效Web前端开发
前端代码重构过程
- 删除无用代码,精简代码,主要是已经不起作用的CSS样式和废弃的JS函数
- 前端代码规范化
- 整理基础类库
- 前端代码模块化
- 提高页面加载性能
前端重构最佳实践
- 重构前预估风险,最好在重构之前完善自动化测试
- 明确重构的目的和范围,目的主要是提高代码的可维护性、可读性和性能
- 先易后难,循序渐进
- 重构过程中持续测试
- 针对性能提升的重构,要事先检测代码整体性能并量化,找出性能瓶颈。冲重构过程中要持续监控性能,对比性能提升效果
降低浏览器兼容性问题
使用html5shiv框架,支持html5标签,html5shiv框架的引入要在head部分
使用Modernizr库,首先在html标签上添加一个no-js的类,可以检测当前浏览器是否支持CSS3属性,如果不支持会在html上增加对应的以no-为前缀的类名,在CSS中就可以通过使用这些类添加向后兼容的代码
代码和资源压缩
- 服务器开启Gzip压缩
- JSdiamante压塑机
- CSS代码压缩
- HTML代码压缩(意义不大)
- 图片压缩
推荐在开发后期,甚至是在网站的发布阶段进行压缩
命名规范
- html中标签名和属性全部小写,根据语义和上下级关系命名
- id使用下划线_
- class用中横线-
- JS变量使用驼峰式
- class或ID仅作为JS的钩子,增加JS前缀
- JS代码使用单引号
第三章 标准的HTML代码
noscript 标签
noscript 标签的常规用法是当JS脚本被禁用时显示提示信息,此标签缺陷是只在JS被禁用时才会起作用,对其他JS不生效的情况无能为力
最佳实践是,提示用户JS被禁用,并同时提供一个功能简单、不依赖于JS的页面供用户继续浏览,做到平稳降级。
<noscript>
<meta http-equiv="refresh" content="0 url=//www.baidu.com">
</noscript>
在现如今的情况下,意义不大,没有JS,好多基础功能无法实现,也就没有继续支持的意义。并且不支持JS的情况也少之又少
meta 标签
charset属性用于描述字符编码格式
<meta charset="UTF-8">
name属性用于描述网站信息,keywords值和description值是搜索引擎的主要手段之一,设置这两个meta的content时,要使用简介和语义明确的词语, viewport设置页面显示
<meta name="keywords" content="test, ok">
<meta name="description" content="test, ok">
<meta name="viewport" content="width=device-width,initial-scale=1">
http-equiv 用于设置特定的HTTP指令,refresh值为刷新页面,X-UA-Compatible值设置页面的兼容模式,
<meta http-equiv="refresh" content="0 url=//www.baidu.com">
<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1"/>
第四章 高可读性的HTML
要做到标签语义化,首先要尽量少使用div和span标签,尽量找到更有语义的标签来代代替
用text-indent实现文字的隐藏:
.text{
text-indent: -9999px
}
语义化的HTML最佳实践
1、熟悉标签,理解语义
2、熟悉标签上规范属性,设置必要属性,比如img的alt属性
3、样式和结构分离,比如用after和before在开始或结尾处添加修饰性文字或外观, 用after清楚浮动
注意,before和after是在元素内部开始或结尾添加元素,而不是在元素外部的前或者后添加元素
4、br标签仅仅适用于文本内容中的换行
5、如果图片是作为页面内容的一部分,应该使用img标签,如果图片仅仅是起到装饰作用,则使用背景图
设置网页标题层次
标题使用h标签,一个页面只有一个h1标签,用h1标签作为页面标题,页面各模块从h2标签开始
不要跳级使用h标签,不要通过h标签设置内容样式
label标签
label标签的目的就是现实在输入控件旁边的说明性文字,当用户点击label元素定义的文本标签时,该文本标签关联的输入控件会获得焦点。
input的事件并绑定在input标签上,不该绑定在label上,点击label也会触发input绑定的事件,如果绑定在label上,会导致事件被触发两次
fieldset和legend
使用fieldset元素给表单控件分组,legend用来定义空间组的标题,作为fieldset元素的第一项
fieldset和legend具有浏览器默认样式,根据需要重置
require
require属性用来校验表单是否填写,校验的时刻是点击form内的input或者button触发提交表单的action的时刻
第五章 积极拥抱HTML5
script标签的异步执行
script标签默认是同步执行的,也就是说阻塞页面的渲染,等待脚本下载完成之后才会进行下一步动作
在script标签中有两个属性,可以更改脚本加载时机:defer和async
这两个属性不应该用在内联脚本上(尽管很多浏览器支持)
defer属性和async属性
defer属性表明代码可以延迟执行,以并行的方式下载脚本,在脚本加载完成之后,浏览器会按照之前的引用顺序运行JavaScript脚本
async属性通知浏览器以异步方式下载JavaScript代码,不必等待页面解析结束,脚本下载结束后立即执行代码
二者的区别是:async属性后不能保证脚本按照顺序加载和执行,脚本加载后悔立即执行,设置defer后脚本还是会按照原有顺序执行
因此,如果脚本之间有依赖关系,则不能使用async属性
一般情况下,可以使用async属性的场合也可以使用defer属性
base标签
为页面上的所有链接规定默认地址或默认目标,写在head标签之间,属性值:
- href: 规定页面中所有相对链接的基准 URL。
- target: 用来设置这个网页所有链接的目标窗口
<base href="//www.baidu.com/" target="_blank">
<a href="search/error.html">123</a>
设置了如上的base标签后,点击123超链接,会在新窗口打开页面,页面地址是 www.baidu.com/search/error.html
自定义属性
在html中使用date-前缀添加自定义属性,原生JS使用dateset对自定义属性进行处理(IE7及以下不支持),jQuery使用$.data()处理自定义属性
<div id="first" class="first" data-value="value1">1</div>
var first = document.getElementById('first');
console.log(first.dataset['value']); // value1
first.dataset['value'] = 'value2';
console.log(first.dataset['value']); // value2
console.log($(first).data('value')); // value2
$(first).data('value', 'value3');
console.log($(first).data('value')); // value3
音频、视频的兼容
要让所有的浏览器都可以播放audio标签的音频,最佳的方式是提供MP3和Ogg Vorbis两种格式
<audio>
<source src="test.mp3">
<source src="test.ogg">
</audio>
要让所有的浏览器都可以播放video标签的音频,最佳的方式是提供WebM和MP3两种格式
<video>
<source src="test.webm">
<source src="test.mp4">
</video>
第六章 高维护性的CSS
高效的组织CSS代码
所有的CSS分为两大类:通用类和业务类,二者应该止于不同的目录中
在通用类中,分为几种:
- default.css:用来重置元素的默认样式,类似的有reset.css或normalize.css
- common.css:防止通用模块和基础样式
- 通用模块包括页面对话框、提示框等组件以及页面头部、底部等通用UI模块
- 基础样式包括全局的box-sizing设置、字体设置等
- 还包括一些共有的class,比如设置文字不可见的class等
- ie-style.css:用来防止兼容旧版IE浏览器的样式
不要把兼容旧版本浏览器的CSS代码和正常的代码放在一起,因为会增加其他版本浏览器下载的样式文件的大小,另一方面不容易维护,当不再支持旧的浏览器时直接取消引用即可
<!--[if lt IE8]> <link href="ie-style.css"> <![endif]-->
业务类的CSS文件,应该放置在不同的文件夹中,按照功能模块划分代码只是为了方便在开发阶段的开发和维护,发布时会压缩为一个文件
CSS Reset
* {
margin: 0;
padding: 0;
}
这种充值方案性能不佳,页面元素很多时会影响渲染性能。
CSS排序
可以使用CSS排序工具:CSScomb,可以安装对应的插件到webstorm
CSS权重
选择器 | 权重 |
---|---|
内联样式 | 1 0 0 0 |
ID选择器 | 0 1 0 0 |
类选择器(包括属性选择器和伪类选择器) | 0 0 1 0 |
标签选择器(包括伪对象选择器) | 0 0 0 1 |
!important | 拥有最高的优先级,不能再被覆盖(所以要慎重使用) |
* 通配符 | 忽略 |
伪类选择器
:hover
,伪对象选择器:before
定义选择器的原则就是:尽量使用权重低的选择器,目的是保证样式在应用于多个元素时容易被覆盖:
- 尽量不使用ID选择器
- 减少子选择器的层级(同时也减少了HTML结构的以来)(使用LESS时也要把通用的样式尽量提取出来)
- 多个CSS类选择器组合使用,多组合,少继承
继承的缺点是:如果有任何一点新的变化,并且无法用现有的类显示,就需要重新定义一个类,有可能导致类的爆炸式增长
组合可以避免这个缺点,组合把一个复杂的父类中的2可变部分和稳定部分分割开来,稳定部分作为主体类,可变部分按照逻辑分为几个简单的类,类与类之间没有继承关系。这些容易变化的类作为主体类的属性呢,实现了组合
组合的好处是减少了类的数量,这也是面向对象编程的一个法则
尺寸设置
局部的元素尺寸要尽量使用相对尺寸,即局部自适应,这样当整体模块的尺寸更改时就不需要更改模块内部子模块的尺寸了
页面整体的尺寸是否自适应,取决于页面的设计
em和百分比
如果期望尺寸随着字体的改变而改变,就应该使用em,比如line-height
如果期望尺寸随着父元素尺寸的改变而改变,就应该使用百分比,比如宽度和高度
rem是相对于html元素的字体尺寸设定的
第七章 高性能的CSS
CSS的性能一般不会成为瓶颈,因此应该把CSS可维护性放在首位,然后才考虑性能因素
CSS选择器的匹配原理
CSS选择器的匹配过程是从右向左的,因此在定义选择器时,要尽量让第一次匹配时的数量达到最好,并且让整体的匹配查找次数最少
最右边的选择器为关键选择器,它是整个选择符整体匹配次数的最主要的决定者
选择器定义的最佳实践:
- 避免使用通配符,因为通配符计算量很大
- 避免使用标签选择器及单个属性选择器作为关键选择期
- 不要在ID选择器前使用标签名
- 不要再选择符中定义过多层级,最好不要超过三层(与DOM结构解耦)
CSS Sprite
CSS Sprite可以减少网络请求,加快页面加载速度,但是最大的问题除了开发、维护的繁琐之外,过大的雪碧图会导致页面内存消耗过大
使用CSS Sprite技术的最佳实践:
- 在项目后期应用CSS Sprite
- 合理组织雪碧图,相同模块的图标放在雪碧图同一区域
- 控制雪碧图尺寸和大小,长度和宽度相乘不要超过2500,大小不要超过200KB
- 合理控制背景图单元之间的距离和位置,位于左侧元素的图片在背景图上放在右侧
- 使用工具(例如smartsprite)
第八章 CSS3相关最佳实践
针对Less的模版文件,可以自动添加相关的浏览器属性前缀
第九章 高维护性的JavaScript
避免定义全局变量
- 将变量和方法封装在一个变量对象上
- 通过一个自执行的匿名函数内,将希望暴漏的变量和方法return出来
- 使用ES6定义的let和const
相等比较
利用==进行相等比较时(不推荐):
1、undefined 和 null与自己比较、相互比较时都是true, 与其他值比较为false
console.log( null == null); // true
console.log( undefined == undefined); // true
console.log( null == undefined); // true
console.log( null == true); // false
console.log( null == false); // false
2、NaN与任何之比较都是false
3、原始类型(数值、布尔和字符串)相互比较,会先转换为数值在比较
避免使用eval
eval会接受一个字符串参数,把字符串内容作为代码执行,存在着严重的安全性问题
类似的还有setTimeOut和setInterval,使用这两个函数时,应当避免使用字符串类型参数
严格模式
推荐使用严格模式
不要再全局模式使用严格模式,除非项目代码有明确的的规定,使用严格模式,否则应该将严格模式限定在函数作用于范围内
function doSomething(){
'user strict';
// 这个函数中的代码将会运行在严格模式
}
使用JSLint或者JSHint,在webstorm中可以进行配置
配置数据和代码逻辑分离
很多数据是在代码中写死的,比如URL、提示信息以及其他固定值,这些无逻辑的数据(配置数据)可以与业务逻辑分离,可以提高代码的可维护性
将代码中的配置数据部分抽取出来,抽取的原则是这些数据是在代码中写死的,并且在后期中可能会变化
这种配置数据分离的方式也可以应用于前后端数据的交互上,后端会根据当前用户或场景返回不同的配置数据,供前端逻辑使用
逻辑与结构样式分离
最佳的做法是,在JavaScript代码中,仅仅是设置元素的class,而不是更改具体的样式
借助成熟的AJAX框架,但不要忘记原生的AJAX方法
function getNewContent() {
var request = new XMLHttpRequest();
if (request) {
request.open("get", "example.txt", true);
request.onreadystatechange = function() {
if (request.readyState == 4) {
var para = document.createElement("p");
var text = document.createTextNode(request.responseText);
para.appendChild(text);
document.getElementById("new").appendChild(para);
}
};
request.send(null);
} else {
alert("什么傻逼浏览器")
}
}
// XMLHttpRequest对象是Ajax的核心,目的是实现页面的异步加载
// XMLHttpRequest.open最为常用,用来指定要访问的服务器的文件,共有三个参数,参数1指定请求类型,参数2为文件地址,参数3为指定是否以异步方式发送和处理
// request.onreadystatechange是一个事件处理函数,在服务器对XMLHttpRequest给出响应时执行对应的函数
// 依赖于服务器响应的代码都应转到指定给request.onreadystatechange的函数中,这样才能保证请求的异步性,即同时进行
// request.readyState表示请求进行的状态,由服务器更新,由0-4共五个可能值,4表示完成
// request.responseText用于保存文本字符串形式的数据
// request.send用来发送请求
在AJAX操作过程中做好和用户的交互
在AJAX操作过程中禁用触发此操作的按钮,添加蒙版,或者在代码中添加表明正在操作中的标志位(在ajax回调函数中解除标志位),可防止用户重复操作
在此过程中要有合适的页面提示,比如添加一个加载动画,告知用户操作进行中
AJAX请求完成后,成功或者失败都应告知用户并提示后续操作
AJAX应有超时限定,如果在此时间内未完成,则提醒用户超时
第十章 高性能的JavaScript
加快JS文件加载速度
推荐的做法将script放在body的底部,让页面其他元素有限解析,JS文件加载和解析推迟到整个页面的元素解析完成后进行
提高代码运行速度
- 嵌套循环时将大循环改为内循环
- 避免在循环内定义变量
- 在条件分支内创建只在分支中才需要用到的变量
- 使用对象字面量代替构造函数创建对象
- 缓存计算结果减少重复计算
少用for…in循环
for…in 效率是最低的。这是因为 for…in 有一些特殊的要求,包括:
- 遍历所有属性,不仅是 own properties 也包括原型链上的所有属性。
- 忽略 enumerable 为 false 的属性。
- 必须按特定顺序遍历,先遍历所有数字键,然后按照创建属性的顺序遍历剩下的。
所以请优先使用 for…of 。
缓存属性值
如果在代码中要频繁取得某个对象的属性值,尤其是此属性来自于对象的原型对象上,最佳的做法是把属性值存在局部变量中,提高读取对象属性的性能
for (var i = 0; i < a.length; i++) {
a.push(i)
}
上面的代码会反复读取a.length,所以使用一个局部变量缓存此属性值:
for (let i = 0, length = a.length; i < length; i++) {
a.push(i)
}
高效的DOM操作
前端性能优化的一个主要的关注点就是DOM操作的优化,DOM操作优化的总体原则就是尽量减少DOM的操作。
DOM操作对性能影响最大是是因为它导致了浏览器的重绘(repaint)和重排(reflow)
浏览器绘制过程中,首先会生成DOM树和CSS规则树,根据二者构建渲染树,同时CSS会根据选择器匹配HTML元素。最后浏览器根据元素的坐标和大小计算每个元素的额位置,并绘制这些元素到页面上。
渲染树建立后,浏览器重新绘制页面上受影响的元素,重排的代价比重绘的代价高很多,重绘会影响部分元素,重排有可能影响全部元素
这些DOM操作会导致重绘或重排:
- 增加、修改、删除DOM元素
- 页面初始化渲染
- 移动DOM元素
- 修改CSS样式,改变DOM元素尺寸
- DOM元素内容改变,改变DOM元素尺寸
- 浏览器窗口尺寸改变
- 浏览器窗口滚动
可以遵循一些最佳时间来降低重排或重绘的影响:
- 合并多次DOM操作为单次DOM操作
- 有动画效果的DOM元素的position属性设置为absolute或fixed,脱离页面布局流
- 将DOM元素的布局信息进行缓存,减少获取次数
- 使用事件托管绑定事件,减少事件绑定个数
第十一章 高安全性的JavaScript
常见的前端攻击方式
- XSS:(cross site scripting),即跨站点脚本工具,发生在用户的浏览器端。特点就是尽一切办法在目标网站上执行非原有的脚本
- CSRF:(corss site request forgery):跨站伪造请求,伪造不是来自于用户的意愿,诱导用户发起请求
不要轻易信任外部传入的数据
一定要针对用户输入做相关的格式检查、过滤等操作,防止任何可能的前端注入
针对跨域使用HTML5中引入的CORS,它解决了JSONP只能用GET请求的限制,它在服务器端是指Access-Control-Allows-Origin头,可以先订服务请求的发起端:
Access-Control-Allows-Origin: http://www.baidu.com
Access-Control-Allows-Origin头尽量不要设置为*,接收方接收到数据后,一定要进行必要的数据格式和完整性校验,并且返回的内容只能作为数据,不要作为代码
window.postMessage
也可以使用window.postMessage接口实现跨域:
otherWindow.postMessage('这是要传输的数据', 'https://secure.example.net')
在接收页面:
function receiveMessage(event){
// 校验数据来源
if(event.origin !== 'http://example.org'){
return;
}
console.log(event.source); // otherWindow
console.log(event.data); // 这是要传输的数据
}
window.addEventListener('message', receiveMessage, false)
第十二章 移动Web
触摸事件
触摸事件可以分为:
- touchstart
- touchend
- touchmove
- touchconcel
触发的顺序:
touchstart - touchmove - touchend - mouseover- mousedown - mouseup - click
每个触摸事件对应的对象又包括:
- touches: 当前屏幕上所有触摸点的列表;
- targetTouches: 当前对象上所有触摸点的列表;
- changedTouches: 涉及当前(引发)事件的触摸点的列表
用active代替hover
处理和桌面端网站的交互
如果在移动智能设备的浏览器输入桌面端网站的地址,应该要自动跳转到移动端的网站
检测移动设备的方式是通过判断设备浏览器的User-Agent来实现。
User-Agent会包含设备的信息,如果期望通过简单的通过JavaScript来判断是否为移动设备,则可以使用isMobile和mobile-detect库
判断移动设备的简易代码:
//判断PC端还是移动端
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
window.location = 'page2.html';
}
设计移动站点为单页模式,避免页面跳转
开启远程调试
IOS和 Andorid都可以通过数据线连接Safari或chrome浏览器远程调试,前提是移动设备开启Debug模式
谨慎使用Timer
在移动端,当页面处于隐藏状态(用户点击返回主菜单,或者打开其他应用,或者打开了新的tab,或者设备关闭屏幕)时,正在浏览的页面会处于锁定状态,页面的JavaScript代码会停止执行
因此,如果在移动Web应用设计了计时器任务,有可能出现不可预期的结果。