前端页面优化
学习目标
了解为何进行前端页面的优化
了解SEO,掌握前端页面语义性的评判标准与规则
从代码可读性角度进行页面优化
为何进行前端优化
- 加载速度更快 —— 速度就是王道
- 成本耕地 —— 人力与时间成本
- 网站制作完成 ≠ 开发结束
- 扩展性更强 —— 前端工作的非独立性
前端页面的优化角度
代码语义性(与SEO相关)
- 语义性的重要性
- 语义性对SEO以及网站自然排名的影响
- SEO是什么
- 对语义性的要求使得网站发生了什么变化
- table布局 -> DIV + CSS(HTML + CSS)
- Flash类网站的消亡
- 搜索引擎爬虫的评判因素
- 良好的缩进与规范的格式
- 良好的扩展性(代码量小)
- 合理的标签语义
- 合理的标签的嵌套
- 404页面必不可少
- 标签选择
- title与meta
- 标签语义化
- div、span
- h1-h6、p
- ul、ol、li、dl、dt、dd
- a、img、table
- 特殊属性
- img添加alt和title属性
- a添加title的属性
代码可读性
- 标签的嵌套规则
- ins和del(行内元素)可以包含块级元素或者行内元素,其他任何行内元素都不允许包含块级元素
- p、h1-h6可以直接包含行内元素的文本信息,但是不允许包含块级元素
- dl元素只允许包含dt和dd,同时dt不能包含块状元素,只允许包含行内元素,dd可以包含任何元素
- 不建议from元素直接包含input元素
- table元素建议直接包含caption、colgroup、col、thead、tbody、tfoot,不建议直接包含tr货主其他任何元素
- CSS代码书写规范
- 命名采用更简明有语义的英文单词进行组合
- 针对单词可以进行适当缩写
- 采用小写字母加中划线的方式进行命名,尽量不使用下划线或大写字母(看公司需求和个人爱好)
- CSS代码的书写顺序遵循CSS样式的渲染顺序
- 显示属性 — display、float、position等
- 自身属性 — width、height、margin、padding、border
- 文本属性 — font-size、line-height、text-align等
- 其他(含CSS3等)
- JS命名规范与推荐
- 标识符命名规范
- 区分大小写
- 第一个字符必须是一个字母、下划线(_)或一个美元符号($),其他字符可以是字母、下划线、美元符号或数字
- 不能含有空格,不能以关键字或保留字命名
- 标识符命名推荐
- 遵循小驼峰命名法(如:currentTime),除了第一个首字母之外,其他的单词首字母大写
- 变量或者属性以名词开头,方法或者函数以动词开头
- 常量命名:字母全部大写,多个单词之间试用下划线分割(如:PI_API)
- 构造函数名称:首字母大写,遵循大驼峰命名法
- 标识符命名规范
- 合理的注释与空格
- 在绝大多数的操作符前后,需要添加空格
- 在数组的逗号后面需要加空格
- 在对象属性的都好之后,属性名和属性值分开的冒号前后,均需要加空格
- 函数声明的大括号之前(参数括号之后)需要加空格
- 函数中,分隔各个参数的逗号之后需要加空格
- 合理添加注释
- 注释符号与注释内容之间加空格
代码扩展性
学习目标
- 掌握前后台的数据传递时存在的问题
- 合理处理文本或图片的超出问题
- 灵活应用margin负值、伪类选择器等技术,解决代码的扩展性问题
- 掌握伪元素并能够合理应用
- 理解a标签的可触区,并能够合理设置
-
前后台数据交互的常见问题
- 代码扩展性不足引发的问题
-
图片尺寸与文本溢出
-
为合理处理“相似元素的不同样式”
-
- 代码扩展性不足引发的问题
-
img标签的宽高与文本超出
图片、文本问题处理方法
-
为图片设置合理的宽高
-
文本超出隐藏
overflow: hidden;
-
文本超出省略号
overflow: hidden;
text-overflow: ellipsis;
word-break: keep-all;
white-space: nowrap;
-
-
代码的扩展性 — 伪类选择器
-
代码扩展性 — margin的负值
-
伪元素的应用
- :before 在元素之前添加内容
- :after 在元素之后添加内容
- 伪元素的常见用途
- 实现清浮动
- 实现背景图
-
a标签的可触区问题
- 移动端尽可能大一些,让用户体验更好
页面加载速度
- 文件大小、请求次数
- 加载方式、代码性能
文件合并与压缩
学习目标
- 能够输出文件压缩与文件合并,对前端优化的意义
- 连接传统文件合并与压缩的方法
- 掌握webpack的使用方法
- 能够使用webpack实现文件合并于压缩
文件合并、压缩与前端优化的关系
- 文件合并:涉及服务器请求次数
- 文件压缩:涉及文件大小
传统文件合并:手动合并
传统文件压缩:借助在线工具
文件压缩与合并 — 新技术 webpack
webpack是一个模块打包器,主要作业是打包JavaScript文件,可借助丰富的插件,实现html、css等其他资源的打包
webpack的安装
-
安装cnpm(淘宝镜像可选)
npm install -g cnpm --registry=https://registry.npm.taobao.org
-
生成package.json文件
npm init y
-
局部安装webpack
npm install -D webpack webpack-cli
打包压缩js文件
新建webpack.config.js
const path = require('path')
module.exports = {
// 入口
entry: {
index: './src/js/index.js',
},
// 出口
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../out')
}
// 模式
mode: "development"
}
配置package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
//加入以下代码
"start": "webpack --config config/webpack.config.js"
}
运行命令npm run start 便可打包js文件
被依赖的js文件,使用import和export进行处理,引入到响应js文件中,再次打包即可实现js文件的合并
function a() {
console.log('被依赖的js')
}
export default a;
import a from './inde.js'
a()
console.log('不被依赖的js')
再次输入npm run start 便可合并js文件
HTML文件的压缩
-
安装插件
html-webpack-plugin
npm install -D html-webpack-plugin
-
在
webpack.config.js
配置文件中引入const htmlwpPlug = require('html-webpack-plugin') module.exports = { plugins: [ new htmlwpPlug({ filename: './cn/index.html', template: './src/index.html', chunks: ['index'], minify: { collapseWhitespace: true, removeComments: true } }) ] }
-
根据插件,调整配置信息后,打包运行
CSS文件压缩、CSS文件独立打包、合并及压缩
- 安装相关插件
css-loader
和style-loader
uglifyjs-webpack-plugin
optimize-css-assets-webpack-plugin
修改相应js文件,引入css文件
- 单独打包安装插件
mini-css-extract-plugin
完整webpack.config.js文件
const path = require('path')
const htmlwpPlug = require('html-webpack-plugin')
const UglifyJsPlug = require('uglifyjs-webpack-plugin')
const OptimizeCSSAssetsPlug = require('optimize-css-assets-webpack-plugin')
const MiniCSSExtractPlug = require('mini-css-extract-plugin')
module.exports = {
entry: {
index: './src/js/index.js',
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, '../out')
},
mode: "development",
plugins: [
new htmlwpPlug({
filename: './cn/index.html',
template: './src/index.html',
chunks: ['index'],
minify: {
collapseWhitespace: true,
removeComments: true
}
}),
new MiniCSSExtractPlug({
filename: "[name].css",
chunkFilename: "[id].css"
})
],
optimization: {
minimizer: [
new UglifyJsPlug({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlug({})
]
},
module:{
rules: [{
test: /\.css$/,
use: [MiniCSSExtractPlug.loader, 'css-loader']
}]
}
}
图片与特殊字体
学习目标
- 能够使用@font-face为文字设置特殊字体
- 掌握呢书字体的”缩小“方法
- 能够根据需求为图片选择合理的格式
- 能够使用在线工具,实现图片压缩与处理
- 掌握webp与srcset的使用方法
- 能够利用背景图合并技术,降低服务器请求次数
特殊字体
-
传统特殊字体的实现方法与问题
- 实现方法:截图法
- 存在的问题:文件过大
- 传统方法的局限性:内容部分无法使用特殊字体
-
@font-face的使用
- @font-face的核心语法
@font-face{ font-family: <YourWebFontName>; src: <source> [<format>][<source>[<format>]]*; }
- 字体格式转换:https://transfonter.org
- @font-face的问题-中文类字体文件较大
-
处理特殊字体的工具——fontmin
-
不同情境下,对于特殊字体的处理方法
- “非数据部分”的特殊字体
- 大量的特殊字体
- 极少量的特殊字体
- ”数据部分“,不含中文的特殊字体
- ”数据部分“,总问类特殊字体
- “非数据部分”的特殊字体
图片
-
图片类型
- gif:全透明或不透明,对像素要求很低的小图标、动画
- jpg:不透明的,对像素要求一般或较低的图片
- png-8:半透明或不透明,对像素要救一般的图片
- png-24:半透明或不透明,对象素要求较高的图片
-
图片压缩
-
压缩原则
-
在线压缩地址
智图:https://zhitu.isux.us
tinypng: https://tinypng.com/
等等
-
-
webp与srcset
-
webp图片类型
-
webp与jpg的比较
-
转换webp格式的图片的软件/方法
iSparta软件
智图
- 如何实现全浏览器兼容-借助srcset
-
-
背景图片合并技术
- 背景图和数据图区分方法
- 什么是背景图合并
- 为何进行背景图合并
- 背景图合并的核心原理
文件放置位置
学习目标
- 能够将CSS、JS文件放置在合理位置
- 能够说出页面回流与页面重绘
CSS与JS的位置
- css代码放置位置
- 为放置在head标签中的css代码依旧生效
- css代码的推荐放置位置
- js代码放置位置
- 加js代码放置在顶部的潜在隐患
- js代码的推荐放置位置
页面回流与重绘
-
页面回流
当render tree(DOM Tree 和样式结构体组合后构建)中的一部分(或全部)因为元素的规模尺寸,布局,隐藏的改变引起的页面重新渲染(或者交做重新构建绘制)
-
页面重绘
当render tree中的一些元素需要更新属性,但这些属性之影响元素的外观,风格,而不会影响到元素的布局,此类页面渲染叫做页面重绘
预加载按需加载和懒加载
学习目标
通过JS实现图片预加载
掌握按需加载的基本原理和实现方法
图片预加载
- 图片预加载是什么
- 为何要用图片预加载
- 图片预加载的核心原理
- new Image()动态创建img
- 设置图片src,并使用onload方法回调鱼仔完成的事件
<body>
<div id="img"></div>
<script>
var box = document.getElementById('box')
var img = document.getElementById('img')
var loadImg = [];
loadImg.push('img/1.jpg')
loadImg.push('img/2.jpg')
loadImg.push('img/3.jpg')
loadImg.push('img/4.jpg')
loadImg.push('img/5.jpg')
var imgsNum = loadImg.length
var nowNum = 0
var nowPer = 0
for (let i = 0; i < imgsNum; i++) {
console.log(i)
var newImg = new Image();
newImg.src = loadImg[i];
newImg.onload = (function(){
nowNum++
img.appendChild(newImg);
nowPer = nowNum/imgsNum *100
console.log(nowPer + '%')
if (nowNum == imgsNum) {
console.log('加载完成')
return
}
})()
}
</script>
</body>
按需加载
-
按需加载HTML内容
- 核心原理:利用js,在符合某种条件是,将script标签的内容去除,让HTML内容生效
- 核心代码
<body> <div id="box"> <script id="hide" type="text/x-template"> <div> <img src="img/1.jpg" alt=""> <img src="img/2.jpg" alt=""> <img src="img/3.jpg" alt=""> </div> </script> <div id="con">点击我看效果</div> </div> <script> var con = document.getElementById('con') var hide = document.getElementById('hide') var box = document.getElementById('box') con.onclick = function(){ box.innerHTML = hide.innerHTML con.onclick = null } </script> </body>
-
按需加载图片
- 核心原理:利用切换图片标签的src属性,实现图片的按需加载
- 核心代码
<body> <div id="box"> <div id="tit">点我加载</div> <div id="con"> <img src="" alt="图片1" data-src="img/1.jpg" alt=""> <img src="" alt="图片1" data-src="img/2.jpg" alt=""> </div> </div> <script> var imgs = document.getElementById('box').getElementsByTagName('img') var tit = document.getElementById('tit') tit.onclick = function(){ var imgLen = imgs.length for (let i = 0; i < imgLen; i++) { var relSrc = imgs[i].getAttribute('data-src') imgs[i].setAttribute('src',relSrc) } } </script> </body>
-
按照整个屏幕进行加载
- 核心原理:将加载的代码放置于textarea当中,再合适的时候使用DOM处理该内容
- 核心代码
<textarea id="add1">//具体代码</textarea>
-
利用Ajax实现页面懒加载
-
核心原理
在符合某种条件时,触发懒加载
使用Ajax实现前后台数据交互,一部请求数据
使用DOM将Ajax异步获取的数据加载到HTML当中
-
JavaScript代码性能优化
学习目标
- 掌握标签查找(遍历)此时对代码执行速度的影响
- 掌握大量DOM节点操做的处理方法
- 掌握DOM2级时间绑定方法
- 能够合理使用事件委托
- 了解利用css与css3属性实现动画的不同之处
- 掌握函数缓存
- 能使用惰性载入函数实现DOM2级的事件绑定
- 能使用函数柯里化实现DOM2级的事件绑定
- 能借助Canvas,优化图片上传方法
标签查找(遍历)次数
-
获取标签
-
遍历查找标签列表的长度
var test = document.getElementById("rest"); var lists = test.document.getElementsByTsgName("li"); var len = lists.length; for(va i=0; i<len; i++){ console.log('for中具体代码'); }
大量DOM节点插入时的操作方法
-
获取大量数据信息后,DOM节点动态创建方法
- 方法1:没处理一条,即可创建一个DOM节点,并将DOM节点防止于DOM书当中
var list = document.getElementById("list"); for (var i=0; i<2000; i++) { list.innerHTML = '<li>' + i + '</li>' };
- 方法2:将所有DOM节点处理完毕之后,一次性将DOM放置于DOM树当中(推荐远快于第一种)
var list = document.getElementById("list"); var str = '' for (var i=0; i<2000; i++) { str += '<li>' + i + '</li>' }; list.innerHTML = str;
事件绑定
-
DOM0级 事件绑定问题:同标签 类型事件只能绑定一次
-
DOM2级 事件绑定: 同标签同类型事件可绑定多次
-
DOM2级 事件绑定方法
- addEventlistener(‘事件名’,函数,true/false);
- attchEvent(‘on+事件名’,函数);
事件委托
-
事件委托:减少功能函数的数量
-
事件委托基本原理
- 采用DOM2级事件绑定方法
- 利用事件冒泡的基本原理
- 检测事件目标对象,执行响应功能函数
// 传统DOM0级事件绑定
var list = document.getElementById("list");
var listItems = list.getElementBytagName("li");
var listlen = listItems.length;
for(var i = 0; i < listlen; i++) {
listItems[i].onclick = function() {
for(var i = 0; i < listlen; i++) {
listItems[i].className = '';
}
this.className = 'select'
}
}
// 事件委托
list.onclick = function(e) {
var target = e.target;
for(var i = 0; i < listlen; i++) {
listItems[i].className = '';
};
target.className = 'select';
}
//DOM2级事件绑定
list.addEventlistener('click',function(e) {
var target = e.target;
for(var i = 0; i < listlen; i++) {
listItems[i].className = '';
};
target.className = 'select';
}),false);
- CSS3属性实现动画性能会比较好
函数缓存
- 函数缓存的主要应用场景
- 解决同参数的多次执行问题
- 函数缓存的核心原理
- 将一个函数对于给定参数的返回值缓存起来
- 用内存换取性能
DOM2级事件兼容处理方法
-
传统实现方法
<body> <div id="btn1">btn1</div> <div id="btn2">btn2</div> <script> var btn1 = document.getElementById('btn1') var btn2 = document.getElementById('btn2') // 传统事件绑定 function eHandler(ele, type, ftn) { if (window.addEventListener) { ele.addEventListener(type, function(e){ ftn.call(ele, e) },false); }else if (window.attachEvent) { ele.attachEvent('on' + type, function (e) { ftn.call(ele, e) }) } } eHandler(btn1, 'click', function() { console.log('元素被点击了') }); eHandler(btn2, 'click', function() { console.log('第二个div被点击了') }); </script> </body>
-
毒性载入函数
- 核心原理:闭包
<body> <div id="btn1">btn1</div> <div id="btn2">btn2</div> <script> var btn1 = document.getElementById('btn1') var btn2 = document.getElementById('btn2') //惰性载入函数 function eHandler(ele, type, ftn) { if (window.addEventListener) { console.log('if语句被执行') eHandler = function(ele, type, ftn){ ele.addEventListener(type, function(e){ ftn.call(ele, e) },false) } }else if (window.attachEvent) { console.log('IE if语句被执行') eHandler = function(ele, type, ftn){ ele.attachEvent('on' + type, function(e) { ftn.call(ele, e) }) } } return eHandler(ele, type, ftn) } eHandler(btn1, 'click', function() { console.log('元素被点击了') }); eHandler(btn2, 'click', function() { console.log('第二个div被点击了') }); </script> </body>
-
函数柯里化
- 核心原理:闭包
<body> <div id="btn1">btn1</div> <div id="btn2">btn2</div> <script> var btn1 = document.getElementById('btn1') var btn2 = document.getElementById('btn2') //函数柯里化 var addEvent = (function(){ if (window.addEventListener) { console.log('是否运行多次') return function(ele, type, ftn){ ele.addEventListener(type, function(e){ ftn.call(ele, e) },false) } }else if (window.attachEvent) { console.log('是否运行多次') return function(ele, type, ftn){ ele.attachEvent('on' + type, function(e) { ftn.call(ele, e) }) } } })(); addEvent(btn1, 'click', function() { console.log('元素被点击了') }); addEvent(btn2, 'click', function() { console.log('第二个div被点击了') }); </script> </body>
图片上传
- 传统方法实现图片上传
- 传统实现方法 — 核心原理
- 使用file类型的input标签
- 点击之后获取图片路径
- 将图片加载出来
- 传统实现方法 — 核心原理
- 传统实现方法存在的问题
- 加载慢
-
Canvas优化图片上传
- canvas实现方法 - 核心原理
- 使用file类型的input标签
- 点击之后获取图片路径
- 通过canvas进行图片绘制
- 将canvas绘制的图片加载出来
<body> <div> <img id="pic" src="" alt=""> </div> <div> <input type="file" id="fileBtn"> </div> <script> var pic = document.getElementById('pic') var fileBtn = document.getElementById('fileBtn') var canvas = document.createElement('canvas') var context = canvas.getContext('2d') var relPic = new Image() fileBtn.onchange = function (e) { showPic(e) } function showPic(e) { var fileUrl = fileBtn.files[0] var fileType = fileUrl.type var reader = new FileReader() reader.readAsDataURL(fileUrl) reader.onload = function(e) { var fileSrc = e.target.result relPic.src = fileSrc relPic.onload = getSize } function getSize() { var w = this.width var h = this.height var imgSize = 400 var cw = w var ch = h if(cw >= ch) { cw = imgSize ch = cw / w * h }else{ ch = imgSize cw = ch / h * w } canvas.width = cw canvas.height = ch context.clearRect(0, 0, 400, 400) context.drawImage(this, 0, 0, cw, ch) var newImg = canvas.toDataURL(fileType, 0.8) pic.setAttribute('src', newImg) } } </script> </body>
- canvas实现方法 - 核心原理
面向对象
- 避免全局作用域被污染
- 提升代码可读性
- 提升代码复用性
- 减少储存空间(函数)的占用
面向对象实现功能需求
-
面向对象案例功能需求
- 创建一个people的基本信息
- 信息包括用户姓名、用户年龄、用户信息三项内容及一个方法(方法是对用户年龄进行判断,当用户年龄处于不同的范围时,设置不同的用户信息)
// 面向过程传统方法 var name = "张三" var age = 28 var info function changeInfo() { if (age < 18) { info = name + '未满18岁' }else{ info = name + '当前的年龄为:' + age } } changeInfo() console.log(info)
-
面向对象 — 使用对象实现初步优化
- 传统实现代码“污染全局作用域”的问题
- 使用对象,解决全局作用域被污染的问题
// 面向对象 var peo = { name: "张三", age: 28, info: '', changeInfo: function() { if (this.age < 18) { this.info = this.name + '未满18周岁' }else { this.info = this.name + '当前的年龄为:' + this.age } } } peo.changeInfo() console.log(peo.info)
-
面向对象 — 工厂模式
- 工厂模式
- 入口——函数参数
- 加工——具体函数功能
- 出口——函数返回值
- 工厂模式的不足之处 — 可读性与空间性问题
function user(name, age) { var peo = {}; peo.name = name peo.age = age peo.info = "" peo.changeInfo = function() { if (this.age < 18) { this.info = this.name + '未满18周岁' }else { this.info = this.name + '当前的年龄为:' + this.age } } return peo } var peo = user('张三', 28) peo.changeInfo() console.log(peo.info)
- 工厂模式
-
面向对象 — 构造模式与原型模式
- 构造模式核心原理
- new实例化对象
- 将构造函数的作用域赋给新对象(this)
- 原型模式核心原理
- 多个实例化对象的方法公用同一个空间
- 引用类型变量的相关问题
//构造模式 function User(name, age) { this.name = name this.age = age this.info = "" this.changeInfo = function() { if (this.age < 18) { this.info = this.name + '未满18周岁' }else { this.info = this.name + '当前的年龄为:' + this.age } } } var peo = new User('张三', 28) peo.changeInfo() console.log(peo.info) //原型模式 function User() { } User.prototype.name = 'user' User.prototype.age = 8 User.prototype.info = '' User.prototype.changeInfo = function() { if (this.age < 18) { this.info = this.name + '未满18周岁' }else { this.info = this.name + '当前的年龄为:' + this.age } } var peo = new User() peo.name = '张三' peo.age = 28 peo.changeInfo() console.log(peo.info)
- 构造模式核心原理
-
面向对象 — 混合模式
- 混合模式核心原理
- 利用构造模式与原型模式各自的优势
- 构造模式利用参数为每个对象创建相应属性
- 原型模式实例化后对象的方法公用一个空间
// 混合模式 function User(name, age) { this.name = name this.age = age this.info = "" } User.property.changeInfo = function() { if (this.age < 18) { this.info = this.name + '未满18周岁' }else { this.info = this.name + '当前的年龄为:' + this.age } } var peo = new User('张三', 28) peo.changeInfo() console.log(peo.info)
- 混合模式核心原理