HTML5新特征
HTML5 离线存储
本章介绍一下 HTML5 新增的离线存储特性 Localstorage,主要包括 Localstorage 的发展史、兼容性、优缺点以及使用场景。
-
为什么要使用 Localstorage 呢:开发程序就要存储数据,但是 Web 开发场景比较特殊,数据正常情况下是要通过 HTTP 协议发送给服务器端,存储在服务器上,但是如果所有数据都存到服务器一方面会浪费服务器资源,另一方面也会降低网页响应速度,所以设计网页时会抽取一些不太重要的或者临时性的数据使用离线存储方式放在浏览器上。
-
总的来说,Localstorage 是一个简单的离线存储技术,通过官方提供的增删改查的 API 可以方便的操作它,需要考虑的难点是每个浏览器的容量限制,使用时做好容错即可。
1. 离线存储发展史
在早期的互联网发展中,浏览器制定了不同的标准用于存储离线数据,这些技术没有统一的标准,而且只适用于单一的浏览器,不能跨平台,所以没有收录在 HTML 标准中。HTML5 之前,Cookie 是唯一在 HTML 标准中用于离线存储的技术,但是 Cookie 有一些不太友好的特征限制了它的应用场景:
- Cookie 会被附加在 HTTP 协议中,每次请求都会被发送到服务器端,增加了不必要的流量损耗
- Cookie 大小限制在 4kb 左右(不同的浏览器有一些区别),对于一些复杂的业务场景可能不够
这两个缺点在 Localstorage 中得到了有效的解决,下面我们就开始学习 Localstorage。
总的来说 Localstorage 适用于业务简单的轻量级存储中,通过简单的 API 操作增删改查存储键值对,而且可以通过事件监听的方式获取 Localstorage 的操作事件,无需发送 HTTP 请求,真正实现了离线存储
2. 兼容性
截止目前为止,已经有大部分浏览器已经支持 Localstorage,包括 IE8。
具体浏览器是否支持 Localstorage 可以通过简单的 JavaScript 代码判断。
function testLocalstorage(){
if ( typeof window.localStorage == "object" ) return true;//判断localstorage对象是否定义
else return false;//未定义返回false
}
3. API 接口
Localstorage 是一个简单的 key/value 形式的数据库,以键值对的方式存储,所以提供的接口主要是基于 k/v 的操作。基于提供的接口只能存储简单的一维数组,但是有些业务场景可能会牵涉到多维数据甚至对象的存储,怎么办?
- 建议使用 JSON.stringify() 将数据转化成字符串方式再存储;
- 使用复杂的前端数据库,例如 indexDB,具体不做深入讨论。
3.1 存储数据
window.localStorage.setItem("test",1)//设置key=test的值为1
localStorage.setItem("test",1)//设置key=test的值为1,localstorage可以作为全局对象处理
localStorage.test = 1//可以通过属性值的方式直接操作localstorage的key
3.2 读取数据-按键值
getItem
var a = window.localStorage.getItem("test")//获取key=test的值
var a = localStorage.test //可以直接通过对象属性的方式操作
如果获取一个不存在的 key 返回 null ,下同。
3.3 读取数据-按位置
var a = window.localStorage.key(0)//可以根据key在localstorage的位置的方式操作,类似操作JavaScript的array的方式
3.4 删除数据
window.localStorage.removeItem("test")//删除key=test的值
window.localStorage.test = ''//可以通过赋空值的方式等价操作
3.5 整体清空
window.localStorage.clear()//clear函数清空整个localstorage
3.6 存储事件监听
当 localstorage 发生改变时,可以通过监听 storage 事件作出相应的业务处理
if (window.addEventListener) { //通过addEventListener方式监听事件,为了兼容IE
window.addEventListener("storage", function(e){//监听storage事件
//业务处理
}, false);
} else {
window.attachEvent("onstorage", function(e){//通过attachEvent方式监听事件
//业务处理
});
}
4. 适用场景及局限性
4.1 局限性
前边提到 Localstorage 相比较 Cookie 的优势是容量大和节省 HTTP 带宽,但是它还是有自身的缺点,下边罗列了它的缺点
- 5M 容量依然小,用过数据库的同学应该知道,MySQL 随便一个表加上索引很容易超过 5M
- 不能跨域名访问,同一个网站可能会牵涉到子域名
- 不能存储关系型数据
- 不能搜索
4.2 适用场景
那么以上缺点有没有解决方案,肯定是有的,例如 HTML 的 webSql 或者 indexDB,那肯定有人问了,为什么不直接用最复杂的数据库,跳过 Localstorage 呢?原因是技术没有最好的,只有最适合的,不同的业务场景应该选择最匹配的而且成本最小的解决方案。例如你在存储简单的业务场景中的临时数据时完全可以使用 Localstorage 甚至 Cookie 搞定,假如使用 indexDB 的话系统的开发成本以及维护成本会翻番,得不偿失。
所以说总结下来 Localstorage 的适用业务场景是:
- 数据关系简单明了
- 数据量小
- 数据无需持久化存储且不需要考虑安全性
- 无需跟服务器交互
5. 业务实战
5.1 项目使用场景
之前开发一个场馆管理系统时有一个功能是根据用户输入的关键字搜索场馆,业务方的需求是需要临时保留搜索关键词的历史记录,考虑到是临时保存,而且只保存关键字不需要复杂的数据结构存储且只保存 10 条最新的数据,项目组商量下来决定使用 Localstorage 保存,搜索成功之后添加到历史记录
function chooseClubItem(e) {
let mid = e.currentTarget.dataset.id
let findAddressInfo = this.data.markers.find(item => item.id === mid)
const historyList = window.localStorage.getItem('historyList') || []//获取localstorage需要操作的键值
if (findAddressInfo) {
const index = historyList.findIndex(history => history.id == findAddressInfo.id)
if (index !== -1) {
historyList.splice(index, 1)
}
if(historyList.length>=10)historyList.pop();//超过最大历史数目,删除最后一个
historyList.unshift(findAddressInfo)//加入到历史存储队列中
window.localStorage.setItem('historyList', historyList)//设置离线存储
}
},
5.2 使用第三方库
现实中考虑到浏览器对 Localstorage 毕竟不是百分之百兼容,而且 Localstorage 本身提供的 API 比较简单,所以实际项目中可以考虑使用第三方封装库操作,比如 store.js。
store.js 优先选择 localStorage 来进行存储,在 IE6 和 IE7 下降级使用 userData 来达到目的。 没有使用 flash ,不会减慢你的页面加载速度。也没有使用 Cookies ,不会使你的网络请求变得臃肿。store.js 依赖 JSON 来序列化数据进行存储。
HTML5 画布 Canvas
本章介绍 HTML 中用来绘图的元素画布。它是 HTML5 中新增的元素,通过使用 JavaScript 调用画布的函数可以控制画布中的每个像素,用来生成图形、字符或者图像。画布元素本身没有绘图功能,初始化定义的画布没有任何视觉效果,必须通过 JavaScript 拿到画布的 id,然后控制画布的绘制功能。所以想要使用画布,必须对 JavaScript 有一定的了解。画布牵涉到很多知识点,本章介绍简单的画布创建以及几种简单的基础形状绘制。
1. 画布基础操作
1.1 创建画布
通过声明 Canvas 标签可以创建一个画布元素,Canvas 支持高度、宽度属性。
<!--如果当前你的浏览器不支持 canvas 元素,则显示 canvas 标签内的文字。如果支持什么都不会显示出来-->
<canvas id="test" width="500" height="400" style='border:1px solid #ccc'>您的浏览器不支持canvas</canvas>
代码说明:如果当前你的浏览器不支持 Canvas 元素,则显示 Canvas 标签内的文字。 JavaScript 可以通过 Canvas 定义的 id 来寻找 Canvas 元素,进而操控它绘图。
var a = document.getElementById("test"); //根据id调用Canvas
2.2 坐标系
画布左上角(0,0)默认原点,x 坐标向右方增长,y 坐标则向下方延伸:
但是,Canvas 的坐标体系并不是一成不变的,原点是可改变的。坐标变换:可以对 Canvas 坐标系统进行移动 translate、旋转 rotate 和缩放等操作。坐标变换之后绘制的图形 x,y 偏移量都以新原点为准, 旋转角度,缩放比,以新坐标系角度为准。
2.3 获取画布 SDK 函数
声明完画布之后,画布标签本身除了高度和宽度之外基本上不再包含其他可以用于绘图的属性,所以想要操控画布必须获取到它提供的绘图 SDK 对象。
var context = a.getContext(contextID)
通过 getContext 函数可以获取画布的 SDK 对象,在 HTML 中它被称为 CanvasRenderingContext2D 对象。CanvasRenderingContext2D 提供了一系列用于绘图的函数,其中包含以下几大类。
- 颜色、样式、阴影
- 线条样式
- 矩形
- 路径
- 转换
- 文本
- 图像绘制
- 像素操作
- 合成
- 其他
2.4 创建一个矩形
通过函数 fillRect 可以创建一个矩形,使用 fillStyle 属性为矩形填充颜色。
<canvas id="test" width="500" height="400">您的浏览器不支持canvas</canvas>
<script>
var a = document.getElementById("test");
var ctx = a.getContext("2d");
ctx.fillStyle = "#eee"; //填充颜色使用rgb方式
ctx.fillRect(0,0,250,175); //定义矩形使用坐标点方式
</script>
2.5 绘制线条
使用 moveTo 函数定义线的开始坐标,lineTo 函数定义线的结束坐标,stroke 函数进行最终的绘制操作。
<canvas id="test" width="500" height="400">您的浏览器不支持canvas</canvas>
<script>
var a = document.getElementById("test");
var ctx = a.getContext("2d");
ctx.moveTo(10, 20); //开始坐标在 (10,20)
ctx.lineTo(20, 100); //线条移动到 (20,100)
ctx.lineTo(70, 100); //线条移动到 (70,100)
ctx.strokeStyle = "grey"; //线条颜色设置为灰色
ctx.stroke(); //绘制
</script>
2.6 绘制圆形
使用 arc 可以画出一个圆形。
<canvas id="test" width="500" height="400">您的浏览器不支持canvas</canvas>
<script>
var a = document.getElementById("test");
var ctx = a.getContext("2d");
ctx.arc(95,70,60,0,2*Math.PI); //圆心坐标是(95,70) 半径是60
ctx.stroke();
</script>
2.7 绘制文字
使用 strokeText 绘制文字。
<!DOCTYPE html>
<html>
<head>
<title>A Simple Canvas Example</title>
<style>
body{
background: #dddddd;
}
#canvas{
margin: 10px;
padding: 10px;
background: #ffffff;
border: thin inset #aaaaaa;
}
</style>
</head>
<body>
<canvas id="canvas" width="600" height="600">
Canvas not supported
</canvas>
<script>
var canvas=document.getElementById('canvas'),
context=canvas.getContext('2d');
context.font='38pt Arial';
context.fillStyle='cornflowerblue';
context.strokeStyle='blue';
context.fillText('Hello imooc',canvas.width/2-150,canvas.height/2+15);
context.strokeText('Hello imooc',canvas.width/2-150,canvas.height/2+15);
</script>
</body>
</html>
2.8 绘制渐变
使用 createLinearGradient 方法可以绘制线性的渐变,适用于矩形、圆形、线条、文本等。
<canvas id="test" width="200" height="200">您的浏览器不支持canvas</canvas>
<script>
var a = document.getElementById("test");
var ctx = a.getContext("2d");
var Gradient = ctx.createLinearGradient(0,0,270,0); //圆心坐标是(95,70) 半径是60
Gradient.addColorStop(0,"red");
Gradient.addColorStop(1,"black");
ctx.fillStyle = Gradient;
ctx.fillRect(20,20,150,100);
</script>
绘制渐变对象,必须使用两种或两种以上的颜色。停止颜色,使用 addColorStop 方法指定颜色停止,参数为 0 - 1
2.9 绘制阴影
使用 shadow 系列函数可以绘制阴影,shadowBlur 表示阴影效果如何延伸 double 值。浏览器在阴影运用高斯模糊时,将会用到该值,它与像素无关,只会被用到高斯模糊方程之中,其默认值为 0。shadowColor 定义颜色值,默认值是 rgba(0,0,0,0)。shadowOffsetX 定义阴影在 X 轴方向的偏移量,以像素为单位,默认值为 0,shadowOffsetY 定义阴影在 Y 轴方向的偏移量,以像素为单位,默认值是 0。
<canvas id="test" width="200" height="200">您的浏览器不支持canvas</canvas>
<script>
var canvas_2=document.getElementById("test");
var can2_context=canvas_2.getContext("2d");
var SHADOW_COLOR='rgba(0,0,0,0.7)'
can2_context.shadowColor=SHADOW_COLOR;
can2_context.shadowOffsetX=3;
can2_context.shadowOffsetY=3;
can2_context.shadowBlur=5
can2_context.fillStyle="red"
can2_context.fillRect(0,0,100,100)
</script>
2.10 纹理填充
填充纹理原理上是指图案的重复,通过 createPattern() 函数进行初始化。有两个参数 ,第一个是 Image 实例,第二个是形状中如何显示 repeat 图案。可以使用这个函数加载图像或者整个画布作为形状的填充图案。
<canvas id="test" width="500" height="500">您的浏览器不支持canvas</canvas>
<script>
var canvas = document.getElementById("test");
canvas.width = 800;
canvas.height = 600;
var context = canvas.getContext("2d");
var img = new Image();
img.src = "https://www.easyicon.net/api/resizeApi.php?id=1183257&size=16";
img.onload = function(){
var pattern = context.createPattern(img, "repeat");
context.fillStyle = pattern;
context.fillRect(0,0,500,400);
}
</script>
3. 画布实战- 五子棋小游戏
<!DOCTYPE html>
<html>
<head>
<title>五子棋小游戏</title>
<meta charset="UTF-8">
</head>
<body>
<canvas id="canvas" width="600" height="600" onclick="exec(event)" ></canvas>
<button onclick="reStart();">重新开始</button>
<button onclick="back();">悔棋</button>
</body>
<script type="text/javascript">
var c=document.getElementById("canvas");
var cxt=c.getContext("2d");
var data = [];//保存下棋的位置点
var clickCount = 0;//点击的次数
var canvasWidth = 600;//画布大小
var interval = 20;//棋盘间隔
var isEnd = false;//判断是否结束
var colorW = '#DAA520';
var colorH = '#000';
init();
function init() { //初始化棋盘
for (var i = 0; i < canvasWidth;) {
cxt.beginPath();
cxt.lineWidth="1";
cxt.strokeStyle="#8B4513";
cxt.moveTo(i,0);
cxt.lineTo(i,canvasWidth);
cxt.stroke();
cxt.beginPath();
cxt.lineWidth="1";
cxt.moveTo(0,i);
cxt.lineTo(canvasWidth,i);
cxt.stroke();
i = i+interval;
}
}
function exec(e) //执行下棋
{
if(isEnd) return;
var x1=e.clientX;
var y1=e.clientY;
var newX,newY;
for (var i = 0; i < canvasWidth;) {
if (x1>=i&&x1<i+interval/2) newX = i;
if (x1>=i+interval/2&&x1<i+interval) newX = i+interval;
if (y1>=i&&y1<i+interval/2) newY = i;
if (y1>=i+interval/2&&y1<i+interval) newY = i+interval;
i = i+interval;
}//计算落棋位置
if (!checkDataExists(newX,newY)) {//判断该点是否已经有棋子存在
var isTrue = true;
if (clickCount%2==0) {
cxt.fillStyle=colorW;
}else{
cxt.fillStyle=colorH;
isTrue = false;
}
cxt.beginPath();
cxt.arc(newX,newY,interval/2,0,Math.PI*2,true);
cxt.closePath();
cxt.fill();
data.push({'x':newX,'y':newY,'isTrue':isTrue});//绘制棋子
clickCount++;
if(isFinish(newX,newY,isTrue)){//判断是否已经结束
isEnd = true;
if (isTrue) alert('黄棋赢了');
else alert('黑棋赢了');
}
}else{
alert("当前点已经存在");
}
}
function reStart() {//比赛重新开始
cxt.clearRect(0,0,canvasWidth,canvasWidth);
init();
data = [];
clickCount=0;
isEnd = false;
}
function back() {//执行悔棋
cxt.clearRect(0,0,canvasWidth,canvasWidth);
init();
clickCount--;
data.pop();
isEnd = false;
for (var i = 0; i < clickCount; i++) {
cxt.beginPath();
cxt.fillStyle = i%2==0 ? colorW:colorH;
cxt.arc(data[i].x,data[i].y,10,0,Math.PI*2,true);
cxt.closePath();
cxt.fill();
}
}
function checkDataExists(x,y,isTrue){//判断当前落棋点是否已经存在棋子
for (var i = 0; i < data.length; i++) {
if (data[i].x ==x && data[i].y == y && (typeof(isTrue) == "undefined" || data[i].isTrue == isTrue)) return true;
}
return false;
}
function isFinish(x1,y1,isTrue) {//判断是否结束棋局
x2 = x3 = x4 = x5 = x1;
y2 = y3 = y4 = y5 = y1;
x2 = x1>=5*interval ? x1-5*interval : 0;
lineCount = 0;
for (var i = 0; i < 10; i++) {
tempx = x2+interval*i;
if (checkDataExists(tempx,y2,isTrue)) {
lineCount++;
if (lineCount==5) break;
}else lineCount=0;
}
if (lineCount>=5) return true;
if (y1>=5*interval) y3 = y1-5*interval;
else y3=0;
lineCount = 0;
for (var i = 0; i < 10; i++) {
tempy = y3+interval*i;
if (checkDataExists(x3,tempy,isTrue)) {
lineCount++;
if (lineCount==5) break;
}else lineCount=0;
}
if (lineCount>=5) return true;
x4 = x1-5*interval;
y4 = y1-5*interval;
lineCount = 0;
for (var i = 0; i < 10; i++) {
tempy = y4+interval*i;
tempx = x4+interval*i;
if (checkDataExists(tempx,tempy,isTrue)) {
lineCount++;
if (lineCount==5) break;
}else lineCount=0;
}
if (lineCount>=5) return true;
x5 = x1-5*interval;
y5 = y1+5*interval;
lineCount = 0;
for (var i = 0; i < 10; i++) {
tempy = y5-interval*i;
tempx = x5+interval*i;
if (checkDataExists(tempx,tempy,isTrue)) {
lineCount++;
if (lineCount==5) break;
}else lineCount=0;
}
if (lineCount>=5) return true;
}
</script>
</html>
上述代码使用 HTML 的画布功能实现了一个简单的五子棋功能,其中除了用到画布还使用到了一些简单的数据结构和算法,比如判断棋局是否结束等。
浏览器的多线程和单线程
学习过 JavaScript 的可能会了解,JavaScript 的宿主浏览器只有一个线程运行 JavaScript,除了 JavaScript 的线程,浏览器中单个页面还有一些其他线程,例如:UI 线程负责处理渲染 DOM 元素;GUI 线程用于处理与用户交互的逻辑;网络线程用于发送接收 HTTP 请求;file 线程用于读取文件;定时器线程处理定时任务等等。
早期的 JavaScript 由于考虑操作 DOM 的一致性问题,以及当时的网页没有过多的交互所以不需要大量的计算,所以只支持单线程。这在多核 CPU 时代的劣势愈发明显,所以 HTML5 中推出多线程解决这个问题。
1. 单线程原因
为什么不能像很多高级语言一样支持多线程呢?假定 JavaScript 同时有两个线程,一个线程在HTML中创建了一个标签元素,另一个线程删除了这个标签,这时浏览器应该执行什么操作?浏览器中 JavaScript 的主要用途是操作 DOM 。这决定了它只能是单线程,否则会带来很复杂的同步问题。为了避免复杂性,大部分主流浏览器的 JavaScript 运行环境只支持单线程。
2. JavaScript 的事件驱动
既然 JavaScript 只支持单线程,那么有人可能会好奇为什么浏览器中的 JavaScript 可以同时发送多个网络请求或者执行多个事件回调函数呢?
这是因为 JavaScript 是基于事件驱动,当需要进行网络请求时,JavaScript 线程会把请求发送给 network 线程执行,并等待执行结果;当进行文件读取时则调用 file 线程,然后等待结果。然后 JavaScript 会一直轮询事件库 event loop,直到有事件完成,这时浏览器会驱动 JavaScript 去执行事件的回调函数。这就是 JavaScript 的事件驱动模型。
3. web worker诞生
单线程的最大问题是不能利用多核 CPU 的优点,HTML5 推出的 Web Worker 标准,允许 JavaScript 创建多线程,但是子线程受主线程约束,且不得操作 DOM 。所以,这个新标准不会产生多线程同步的问题。
4. 适用场景
Web Worker 能解决传统的 JavaScript 单线程出现的执行阻塞问题,因而适合以下几种业务场景:
- 并行计算;
- ajax 轮询;
- 耗时的函数执行;
- 数据预处理/加载。
5. 函数介绍
5.1 创建
初始化一个 Web Worker,由于不是所有的浏览器都支持 Web Worker,所以需要判断一下浏览器是否支持:
if (window.Worker) {//判断浏览器是否支持web worker
var worker = new Worker('test.js');//创建一个线程,参数为需要执行的JavaScript文件
}
5.2 向线程传递参数
新的线程的上下文环境跟原宿主环境相对独立的,所以变量作用域不同,如果需要互相读取变量的话需要通过消息发送的方式传输变量,例如:
worker.postMessage('test'); //数据类型可以是字符串
worker.postMessage({method: 'echo', args: ['Work']});//数据类型可以是对象
5.3 主线程接受消息
跟上述场景类似,主线程也需要通过监听的方式获取辅线程的消息:
worker.onmessage = function (event) {
console.log('接收到消息: ' + event.data);
}
5.4 线程加载脚本
子线程内部也可以通过函数加载其他脚本:
importScripts('script1.js','script2.js');
5.5 关闭线程
// 主线程中关闭子线程
worker.terminate();
// 子线程关闭自身
self.close();
6. 使用 JavaScript 多线程实现非阻塞全排列
6.1 什么是全排列
从 n 个不同元素中任取 m(m≤n)个元素,按照一定的顺序排列起来,叫做从 n 个不同元素中取出 m 个元素的一个排列。当 m=n 时所有的排列情况叫全排列。
6.2 为什么使用多线程处理
这里并非突出使用 JavaScript 实现全排列的优势,而是在实际项目中类似这种科学运算相关的算法可能会消耗一定的 CPU,由于 JavaScript 是解释型语言,运算性能是它的弱项,而且浏览器中运行的 JavaScript 又是单线程的,所以一旦出现性能问题可能会导致线程阻塞,阻塞之后会导致页面卡顿,非常影响用户体验。使用 webworker 的多线程功能将这个运算函数单独 fork 出一个子线程去运行,运行完成之后发送结果给主线程,可以有效的避免性能问题。
6.3 代码示例
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;
charset=UTF-8">
<title>JavaScript实现全排列</title>
<script type="text/JavaScript">
function combine() {//点击按钮向webworker线程发送请求
var worker = new Worker('http://wiki-code.oss-cn-beijing.aliyuncs.com/html5/js/worker.js');
worker.postMessage(document.getElementById("str").value);
worker.onmessage= function (event) {
document.getElementById("result").innerHTML = event.data ; //监听JavaScript线程的结果
};
}
</script>
</head>
<body>
<input type="text" id="str" />
<button onclick="combine()">全排列</button>
结果是:<div id="result" style="width:500px;height:500px;word-break: break-all;"></div>
</body>
</html>
worker.js 代码如下:
function getGroup(data, index = 0, group = []) {//生成全排列
var need_apply = new Array();
need_apply.push(data[index]);
for(var i = 0; i < group.length; i++) {
need_apply.push(group[i] + data[index]);
}
group.push.apply(group, need_apply);
if(index + 1 >= data.length) return group;
else return getGroup(data, index + 1, group);
}
onmessage = function(message){//监听主线程的数据请求
var msg = message.data;
if(msg == "") postMessage("请输入正确的字符串");
else {
var data = msg.split("");//将字符串转数组
postMessage(getGroup(data));
}
}
上述代码实现了一个使用 JavaScript 的 Web Worker 实现的全排列的功能。上半部分是主线程的代码,主要实现了创建子线程、发送数据给子线程、接收子线程的消息这几个功能;下半部分是子线程,子线程主要负责运算,并将运算结果发送给主线程。
HTML5 数学标记语言
本章介绍一个比较专业性的 HTML 知识点 - 数学置标语言,它是一种基于 XML 的标准,用来在互联网上书写数学符号和公式的置标语言,由万维网联盟的数学工作组提出。
本章介绍了小众语法 mathml,由其发展历史以及适用场景引申开来,进而简单介绍了它的语法结构,最后说明了其兼容性问题以及兼容性解决方案,由于目前只有极少数浏览器支持,所以在需要使用时需要先进行兼容性判断。
1. 发展史
1.1 作用
MathML 包含两个子语言:Presentation MathML 和 Content MathML。Presentation MathML 主要负责描述数学表达式的布局。Content MathML 主要负责标记表达式的某些含义或数学结构。MathML 的这一方面受到 OpenMath 语言的很大影响,在 MathML3 中,与 OpenMath 更为贴近。为什么需要使用 MathML 呢? 我们要想在网页中插入一些数学公式,早期的方式只能通过特殊符号或者图片来实现,现在利用 MathML标签可以方便的实现所有数学公式的显示。
2.语法简介
介绍语法之前先看一个简单的 demo:
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<mrow>
<mi>x</mi>
<mo>+</mo>
<mi>y</mi>
</mrow>
<mo>=</mo>
<mn>10</mn>
</mrow>
</math>
是不是看起来有点眼熟,没错,它就是基于 XML 语法格式。math 元素之最顶层的根元素,每个实例都必须包含在 math标签内,一个 math 元素不能包含另一个 math 元素。
2.1 mrow
mrow 标签用于对表达式进行分组,至少由一个到多个运算符组成,此元素呈水平展示。良好的分组对数学表达式结构展示起到一定的改善效果。
2.2 mi
mi 标签表示运算表达式中的变量,例如
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<mi>a</mi> <!--表示变量-->
<mo>+</mo>
<mi>b</mi> <!--表示变量-->
</mrow>
</math>
2.3 mo
mo 标签用于表示运算符,例如:
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<mi>a</mi>
<mo>-</mo><!--减法运算符-->
<mi>b</mi>
</mrow>
</math>
2.4 mn
mn 标签用于显示表达式中的数字。
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<mn>5</mn><!--表示数字-->
<mo>+</mo>
<mn>1</mn>
</mrow>
</math>
2.5 mscarries
这个标签可用于创建基本数学中出现的进位,借位和交叉, mscarries 的子元素与mstack 的下一行中的元素相关联。 mscarries 的每个孩子除了 mscarry 之外或者 none 被视为被 mscarry 隐含地包围,举例说明:
<math>
<mscarries> expression <mscarry> <none/> </mscarry> </mscarries>
</math>
以上是简单的写法.
2.6 menclose
这个标签用于呈现由其表示法属性指定的封闭符号内的内容。它接受一个参数作为多个子元素的推断。它包含一个属性 notation,可选项有:
- longdiv - 精算;
- phasorangle - 激进;
- updiagonalstrike - 盒子;
- downdiagonalstrike - 圆盒;
- verticalstrike - 圆;
- horizontalstrike - 左;
- northeastarrow - 右;
- madruwb - 顶部;
- text - 底部;
2.7 mfenced
这个标签是一种使用 fencing 运算符 (如花括号,括号和括号) 而不是使用 标签的快捷方法。包含一个属性 expression,可选值有:
- open 指定开始分隔符,默认是 “(” ;
- close 指定结束分隔符,默认是 “)”;
- separators 指定零个或多个分隔符序列,默认是 “,”
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<mo>(</mo>
<mn>1</mn>
<mo>)</mo>
</mrow>
</math><!--这是不使用mfenced的方式-->
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mfenced>
<mn>1</mn>
</mfenced>
</math><!--使用mfenced快捷方式-->
通过上述代码可以看得出,使用这个标签可以减少代码量。
2.8 mfrac
这个标签用于绘制分数,它的子元素必须是2个,不然的话会出现报错,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mfrac>
<mi>y</mi>
<mn>10</mn>
</mfrac>
</math>
以上示例中的分母是 10,分子是 y。
2.9 mlongdiv
这个标签用于绘制长的分区,它的简单语法格式是: (请使用 Firefox 运行下面代码)
<mlongdiv> divisor dividend result expression </mlongdiv>
2.10 mtable
这个标签比较类似于 HTML 中的表格元素,结合使用 mtr ,mtd 可以绘制出表格,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mtable>
<mtr>
<mtd><mn>1</mn></mtd>
<mtd><mn>0</mn></mtd>
<mtd><mn>0</mn></mtd>
</mtr>
<mtr>
<mtd><mn>0</mn></mtd>
<mtd><mn>1</mn></mtd>
<mtd><mn>0</mn></mtd>
</mtr>
</mtable>
</math>
2.11 msgroup
用于分组,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mstack>
<msgroup>
<mn>123</mn>
<msrow>
<mo>×</mo>
<mn>321</mn>
</msrow>
</msgroup>
<msline/>
</mstack>
</math>
2.12 mover
这个标签用于绘制下标,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mover accent = "true">
<mrow>
<mi> 1 </mi>
<mo> + </mo>
<mi> 2 </mi>
<mo> + </mo>
<mi> 3 </mi>
</mrow>
<mo>⏞</mo>
</mover>
</math>
2.13 mpadded
这个标签用于在其内容周围添加填充或额外空间,它可用于调整尺寸和定位,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<mi>x</mi>
<mpadded lspace = "0.2em" voffset = "0.3ex">
<mi>y</mi>
</mpadded>
<mi>z</mi>
</mrow>
</math>
2.14 mphantom
这个标签用于渲染无形中保持相同的大小和其他维度,包括基线位置,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mfrac>
<mrow>
<mi> x </mi>
<mo> + </mo>
<mi> y </mi>
<mo> + </mo>
<mi> z </mi>
</mrow>
<mrow>
<mi> x </mi>
<mphantom>
<mo> + </mo>
</mphantom>
<mphantom>
<mi> y </mi>
</mphantom>
<mo> + </mo>
<mi> z </mi>
</mrow>
</mfrac>
</math>
2.15 msqrt
这个元素用于构造平方根,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<msqrt>
<mn>256</mn>
</msqrt>
</math>
2.16 msub
这个元素用于绘制下标表达式,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<msub>
<mi>y</mi>
<mn>10</mn>
</msub>
</math>
2.17 msubsup
这个元素用于将下标和上标附加到表达式,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<msubsup>
<mo> ∫</mo>
<mn> 0 </mn>
<mn> 1 </mn>
</msubsup>
<mrow>
<msup>
<mi> e</mi>
<mi> x </mi>
</msup>
<mo> ⁢</mo>
<mrow>
<mi> d</mi>
<mi> x </mi>
</mrow>
</mrow>
</mrow>
</math>
2.18 msup
这个元素用于将上标绘制到表达式,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<msup>
<mi>y</mi>
<mn>2</mn>
</msup>
</math>
2.19 munder
这个元素用于绘制下标,可用于在表达式中添加重音或者限制,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<munder accent="true">
<mrow>
<mi>a </mi>
<mo> + </mo>
<mi> b </mi>
<mo> + </mo>
<mi> c </mi>
</mrow>
<mo>ȿ</mo>
</munder>
</math>
2.20 munderover
这个元素用于绘制下方和上方,它支持在表达式上和下同事添加重音或者限制,例如: (请使用 Firefox 运行下面代码)
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<mrow>
<munderover>
<mo> ∫</mo>
<mn> 0 </mn>
<mi> ∞</mi>
</munderover>
</mrow>
</math>
3.兼容性
通过上图可以看出,基本上兼容性最好的是 Firefox 和Safari ,其他浏览器基本上不兼容。
4.第三方工具
由于 mathml 牵涉到的专业技术门槛较高,对数学知识要求较高,一般情况下如果只是项目中偶尔使用的话可以使用第三方工具降低开发成本。
4.1 在线转换
这个网站 https://webdemo.myscript.com/可以在线将数学公式转换成 mathml 代码
4.2 第三方库
通过调用第三方库,可以使用 mathml 语法在不支持的浏览器上进行兼容模拟,例如 mathml.js (下面例子可以使用 chorme 浏览器试试了,平方根也是可以显示出来的)
<script src="//fred-wang.github.io/mathml.css/mspace.js"></script>
<math xmlns = "http://www.w3.org/1998/Math/MathML">
<msqrt>
<mn>256</mn>
</msqrt>
</math>
矢量图形标记语言
本章介绍用于描述图像和绘制图形的标记语言 SVG,SVG 使用 XML 的语法标准,用于绘制和定义矢量图形,它符合 w3c 的标准。SVG 全称 scalable vector graphics ,使用它可以绘制三种类型的图形:矢量图形、图像、文本。SVG 是一整套矢量图形绘制协议,放在 HTML 中也可以是一个标准的 HTML 元素
1. 为什么使用 SVG
HTML 中的矢量图形绘制语言 SVG: SVG 在既能满足现有图片的功能的前提下,又是矢量图,在可访问性上面也非常不错,并且有利于 SEO(搜索引擎优化) 和无障碍,在性能和维护性方面也比 icon,font 要出色许多,简单的理解,它是图形的另一种格式例如它和常见的图片格式 png、jpg、gif 等是一类。
2. 语法标签简介
2.1 矩形
使用 rect 表示矩形,例如:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" version="1.1">
<rect x="60" y="10" rx="10" ry="10" width="150" height="150"/>
</svg>
包含6个属性
- x 用于表示矩形左上角坐标 x 值;
- y 用于表示矩形左上角坐标 y 值;
- width 表示矩形宽度;
- height 表示矩形高度;
- rx 用于实现圆角效果的圆角 x 轴半径;
- ry 用于实现圆角效果的圆角 y 轴半径。
3.2 圆形
使用 circle 表示圆形:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" version="1.1">
<circle cx="125" cy="175" r="120"/>
</svg>
其中包含 3 个属性
- r 圆的半径;
- cx 圆心坐标 x 值;
- cy 圆心坐标 y 值。
3.3 椭圆
使用 ellipse 标签表示椭圆:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" version="1.1">
<ellipse cx="175" cy="175" rx="120" ry="50"/>
</svg>
其中包含 4 个属性
- rx x 半径;
- ry y 半径;
- cx 圆心坐标 x 值;
- cy 圆心坐标 y 值。
3.4 直线
使用 line 元素表示直线:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" version="1.1">
<line xmlns="http://www.w3.org/2000/svg" x1="0" y1="0" x2="300" y2="300" style="stroke:rgb(99,99,99);stroke-width:2"/>
</svg>
它包含 4 个属性
- x1 起点的 x 坐标;
- y1 起点的 y 坐标;
- x2 终点的 x 坐标;
- y2 终点的 y 坐标。
3.5 折线
使用 polyline 表示折线:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" version="1.1">
<polyline points="0,170 484,170 88,440 240,4 391,439 0,170" fill-opacity="0.5" fill="red"></polyline>
</svg>
使用 points 属性表示折线的一系列的中间点:
3.6 路径
使用 path 表示路径:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" version="1.1">
<path d="M 20 230 Q 40 1205, 150 530 T 90230"/>
</svg>
使用这个元素可以实现任何其他图形。
4. 适用场景和优缺点
和使用图片相比,SVG 有很多优点:
- SVG 使用 xml 标记语言实现,具有可移植性;
- SVG 语法区分大小写,出现兼容性问题概率较小;
- SVG 和传统的 JPEG png 相比,尺寸更小;
- SVG 是矢量图,放大或缩小不影响其图像质量;
- 可以通过 img 的 src 属性引用。 总的来说 SVG 拥有很多优点,但是其复杂的语法决定了它的入门门槛较高。
5. 和 Canvas 比较
Canvas 是通过 JavaScript 调用的方式绘制图像,而 SVG 是使用标签的方式绘制图像,所以两种的渲染方式有很大差别。
6. 使用类库
直接使用 SVG 来绘制一个较复杂的图形的话可能入门门槛较高,使用第三方类库可以节省不少代码量,在这里推荐一个比较常用的 SVG 的类库 snap.svg,它的 GitHub 地址是 https://github.com/adobe-webplatform/Snap.svg。
<script src="https://wiki-code.oss-cn-beijing.aliyuncs.com/html5/js/snap.svg-min.js"></script>
<svg id="demo1" width="1000" height="1000"></svg>
<script>
let svg = Snap('#demo1');
let circle = svg.select('.circle'); //如果SVG中已有实际图形元素,直接选择器初始化
// 1S内矩形围绕矩形的中心旋转100次,完成旋转一周,动画效果是缓出
let rect = svg.paper.rect({x: 200, y: 200, width: 200, height: 200, fill: '#f00'});
Snap.animate(0, 100, (val) => {
let m = new Snap.Matrix();
m.rotate((val/100)*360, 300, 300); // 注意,旋转中心是矩形的中心
rect.transform(m); // 在rect节点应用matrix
}, 1000, mina.easeout(), () => {
});
</script>
上述代码使用 snap.svg 实现了一个图形旋转。
HTML5 地理位置
地理定位功能是 HTML5 新增的标准,早期的 HTML 和 JavaScript 没有操控硬件和文件的权限,因为页面交互效果比较简单;但是 HTML5 之后网页已经逐渐应用于各种复杂场景包括移动设备,所以增加了各种与硬件交互的 API 接口,地理位置就是其中之一。
本章介绍了移动开发的利器-地理位置功能,通过这个功能可以使用 HTML 直接跟移动设备硬件交互,很大程度上从丰富了网页的交互方式,不过需要用户授权之后才能使用;地理位置相关的函数只有 3 个,使用时需要考虑浏览器兼容性
1. 早期的方式
在 HTML5 之前,获取地理位置的解决方法是在已知 IP 位置的数据库中查找访问者的 IP 地址,然后根据 IP 数据库查找到对应的位置。
2. 检测是否支持地理位置
并非所有的浏览器或者硬件设备都支持地理位置功能,所以使用之前需要进行容错判断:
if (navigator.geolocation) {//判断地理位置是否支持
//业务代码
}
else{
x.innerHTML="该浏览器不支持获取地理位置。";
}
获取地理位置之前需要用户点击同意按钮,因为该功能牵涉到隐私。
- 地理位置 API 3.1 获取当前位置 使用 getCurrentPosition 函数获取用户当前的地理位置,这个函数有 3 个参数:
- 第一个参数设置成功获取的回调函数;
- 第二个参数设置失败之后的回调函数;
- 第三个参数设置一些可选参数项。
navigator.geolocation.getCurrentPosition(function(position) {
//TODO 成功时的处理
var timestamp = position.timestamp;
var coords = position.coords;
}, function(error) {
//TODO 失败时的处理
console.log(error);
}, {
//参数设置
})
成功获取之后的回调函数中通过参数传递的方式可以拿到地理位置的对象,它是一个 Geoposition对象,上述示例使用 position 变量表示,这个对象包含 2 个属性:
- timestamp 时间戳
- coords 一个coordinates 类型对象,包括
- accuracy 精度值
- altitude 海拔
- altitudeAccuracy 海拔的精度
- heading 设备前进方向
- latitude 经度
- longitude 纬度
- speed 前进速度
- PositionOptions 对象,它包含 3 个用于设置的属性:
- enableHighAccuracy 是否使用最高精度表示结果
- timeout 设置超时时间
- maximumAge 表示获取多久的缓存位置
3.2 监视位置
使用 watchPosition 函数可以定时获取用户地理位置信息,在用户设备的地理位置发生改变的时候自动被调用。这个函数跟 getCurrentPosition 函数的使用方式基本一致。
navigator.getlocation.watchPosition(function(pos){
//业务代码
},function(err){
},
{}
)
3.3 清除监视
使用 clearWatch 函数删除 watchPosition 函数注册的监听器:
var watch = navigator.geolocation.watchPosition(show_map, handle_error, {enableHighAccuracy: true,timeoout: 175000, maximumAge: 75000})
clearWatch(watch); //清除监视
4. 定位失败
由于获取地理位置功能依赖硬件信号,例如 GPS 信号、WiFi 信号等等,所以有时可能会出现获取不到位置的情况,在这里做了一下总结:
4.1 浏览器不支持原生定位接口
有些旧版本的浏览器不支持 HTML5,如 IE 较低版本的浏览器。这时调用定位接口会出现 error 信息,message 字段包含 Browser not Support html5 geolocation 信息。
4.2 用户禁用了定位权限
需要用户开启定位权限,error 信息的 message 字段包含 Geolocation permission denied。
4.3 浏览器禁止了非安全域的定位请求
比如 Chrome、IOS 10 已经陆续禁止,需要升级站点到 HTTPS,error 信息的 message 字段包含Geolocation permission denied信息。注意: Chrome 不会禁止 localhost 域名 HTTP 协议下的定位
4.4 定位超时
由于信号问题有时会出现超时问题,可以适当增加超时属性的设定值以减少这一现象。某个别浏览器本身对定位接口的友好程度较弱,也会超时返回失败,error 信息的 message 字段包含 Geolocation time out 信息。
4.5 定位服务问题
Chrome、Firefox 以及一些套壳浏览器接入的定位服务在国外,有较大的限制,也会造成定位失败,且失败率较高。
新增表单
之前的教程中已经简单的介绍过了表单,早期的网页中为了实现复杂的交互效果,通常需要使用 div+css 模拟复杂的表单类型实现类似日期、滑块条、颜色选择等效果。HTML5 标准中考虑到这种情况,增加了不少的复杂表单效果。本章主要介绍 HTML5 新增的几种增强的表单类型。
介绍了几种实时交互效果较强的表单控件及用法,弥补了早期 HTML 中的交互缺失的情况。
1. email
此类型的表单跟普通 text 类型的表单类型表现方式一致,只是在输入完成之后如果不符合 email 类型浏览器会有提示,且不允许提交,定义方式如下:
<input type=email>
以下实例使用 email 类型的表单实现了一个简单的注册功能:
<script>
function beforeSend(){
if(document.getElementById("password").value != document.getElementById("password1").value) return alert("请输入正确的密码");
}
</script>
<form method="post" action="/redister.html" onsubmit='beforeSend()'>
<p><label for="loginName">登录名:</label><input name='loginName' type='text' pattern=".{3,20}">(3~20个字符)</p>
<p><label for="password">登录密码:</label><input id='password' name='password' type='password' pattern=".{6,20}">(至少6位)</p>
<p><label for="password1">重复密码:</label><input id='password1' name='password1' type='password' pattern=".{6,20}">(至少6位)</p>
<p><label for="email">邮箱:</label><input name='email' type='email'>(请输入正确的邮箱地址)</p>
<input type='reset' value='重置'><button type=submit>注册</button>
</form>
提交后:
2. url
url 类型的表单视觉展现跟 text 类型的一致,只是在输入完成之后如果不符合 URL 类型浏览器会有提示,且不允许提交,语法如下:
<input type='url'>
以下示例展示了 url 表单的实际使用场景:
<form>
<label>姓名:</label><input type=text name='name' pattern=".{3,20}"><!--pattern属性用于约束输入值-->
<label>电话:</label><input type=tel name='phone' ><!--使用tel类型表单-->
<label>住址:</label><input type=text name='address' pattern=".{5,50}">
<label>个人主页:</label><input type=url name='url'><!--使用url类型表单-->
<input type='reset' value='重置'> <button type=submit>提交</button>
</form>
3. number
number 类型的表单也跟 text 表现形式一致,但是浏览器会强制不能输入非数字类型的字符,表单最后侧默认会有上下两个按钮,语法如下:
<input type=number>
4. tel
tel 类型要求输入一个电话号码,但实际上它并没有特殊的验证,与 text 类型没什么区别:
<input type=tel>
5. range
此类型将显示一个可拖动的滑块条,并可通过设定 max/min/step 值限定拖动范围。拖动时会反馈给 value 一个值。
<input type=range min=20 max=100 step=2 >
在实际项目中可以根据动态获取滑块的 value 值,来实现一定的效果。以下展示了一个使用 range 表单实现了一个动态缩放图片的功能:
<!DOCTYPE html>
<html>
<head>
<title>使用滑动条缩放图片</title>
</head>
<body style="">
<canvas id="canvas" style="display: block;margin: 0 auto;border: 1px solid #aaa;background:black">
你的浏览器不支持canvas。
</canvas>
<input type="range" id="range" min="0.5" max="5.0" step="0.01" value="1.0" style="display: block;margin: 20px auto;width: 800px;"/><!--定义滑动条-->
</body>
<script>
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var slider = document.getElementById("range");
var image = new Image();
window.onload = function(){//浏览器加载完成之后触发
canvas.width = 600;
canvas.height = 400;
image.src="https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png"; //加载图片
image.onload = function() {
slider.onmousemove = function(){//通过监听鼠标事件动态获取滑块的value值
var scale = slider.value;
var width = canvas.width * scale;
var height = canvas.height * scale;
var dx = canvas.width/2 - width/2;
var dy = canvas.height/2 - height/2;
context.clearRect(0, 0, canvas.width, canvas.height);
context.drawImage(image, dx, dy, width, height);//设置图片的缩放度
};
};
};
</script>
</html>
6. color
此类型表单,可让用户通过颜色选择器选择一个颜色值,并反馈到 value 中,可以设置默认值,语法如下:
<input type=color>
实际项目中,一般用来作为为画笔或者绘图选择颜色,以下示例展示了一个简单的颜色选择器表单:
<!DOCTYPE html>
<html>
<head>
<meta charset=" utf-8">
<title>颜色选择器</title>
</head>
<body>
<form name="test" id="test" method="post" action="test.php">
选择颜色:<input type="color" form="ant" name="color"/>
<input type="submit" value="提交">
</form>
</body>
</html>
7. 时间日期系列
这个类型的表单包含几种类型,用来实现繁琐的日历控件,效果各有不同,语法如下:
<p>日期:<input type=date ></p> <!-- 日期 -->
<p>时间:<input type=time ></p> <!-- 时间 -->
<p>日期+时间(已经废弃):<input type=datetime ></p> <!-- 日期+时间 (已经废弃)-->
<p>日期+时间:<input type=datetime-local ></p> <!-- 日期+时间 -->
<p>月份:<input type=month ></p> <!-- 月份 -->
<p>星期:<input type=week ></p> <!-- 星期 -->
8. search
此类型表示输入的将是一个搜索关键字,通过 results=s 或者 x-webkit-speech 可显示一个搜索小图标。语法如下:这个表单在实际项目中适用场景较少,所以没有示例可以参考。
<input type=search results=s >
web 数据库
之前的章节有讨论过 web 中的存储方式,包括传统的 cookie 和新的 localstorage,这两种方式实现了 HTML 中的离线存储,但是存储方式比较简单,在有些复杂的业务场景可能不能满足条件。本章我们介绍一个计算机中一个重要的学科数据库,以及它在 HTML5 中的支持。数据库是一个内容庞大的知识体系,本章只介绍一些简单的用法以及它在 HTML 中的适用场景。
1. 适用场景
既然适用 localstorage 也可以做简单的数据存储,那么为什么还需要适用数据库呢?假设一个业务场景中将所有用户信息临时存储到浏览器中,这些信息包括昵称、姓名、性别等,现在需要搜索出性别是男的所有用户。如果使用 localstorage 的话,需要将所有的数据提取出来,一条条遍历,得出结果。这样的搜索算法的时间复杂度是 O(n),性能较差。如果使用数据库存储的话,只需要给性别列加上索引,然后使用 SQL 搜索,时间复杂度是 O(lgn),性能提升了一个等量级。 关系型数据库的特点是:
- 数据模型基于关系,结构化存储,完整性约束;
- 支持事务,数据一致性;
- 支持 SQL,可以复杂查询; 缺点是:
- SQL 解析会影响性能;
- 无法适应非结构化存储;
- 横向扩展代价高;
- 入门门槛较高。
2. Web SQL
Web SQL不是 HTML5 标准中的一部分,它是一个独立的规范,引入了 SQL 的 api,关于 SQL 的语法可以参考第三方的教程,在此不做解释。Web SQL 有 3 个函数
2.1 openDatabase
这个函数用于打开一个数据库,如果数据库不存在就创建。它有 5 个参数,分别表示:
- 数据库名称;
- 版本号;
- 数据库备注;
- 初始化数据大小;
- 创建/打开成功回调函数
/**
* 创建数据库 或者此数据库已经存在 那么就是打开数据库
* name: 数据库名称
* version: 版本号
* displayName: 对数据库的描述
* estimatedSize: 设置数据的大小
* creationCallback: 回调函数(可省略)
*/
var db = openDatabase("MySql", "1.0", "数据库描述", 1024 * 1024);
2.2 transaction
这个函数使用事务执行 SQL 语句,它是一个闭包,例如:
dataBase.transaction( function(tx) {
tx.executeSql(
"create table if not exists test (id REAL UNIQUE, name TEXT)",
[],
function(tx,result){ alert('创建test表成功'); },
function(tx, error){ alert('创建test表失败:' + error.message);
});
});
2.3 executeSql
这个方法用于执行 SQL 语句。
tx.executeSql(
"update stu set name = ? where id= ?",
[name, id],
function (tx, result) {
},
function (tx, error) {
alert('更新失败: ' + error.message);
});
});
3. indexedDB
IndexedDB 是 HTML5 规范里新出现的浏览器里内置的数据库。它提供了类似数据库风格的数据存储和使用方式。存储在 IndexedDB 里的数据是永久保存,不像 cookies 那样只是临时的。IndexedDB 里提供了查询数据的功能,在线和离线模式下都能使用。
3.1 对比 Web SQL
跟 WebSQL 不同的是,IndexedDB 更像是一个 NoSQL 数据库,而 WebSQL 更像是关系型数据库。
3.2 判断浏览器是否支持
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
if(!window.indexedDB){
console.log("你的浏览器不支持IndexedDB");
}
3.3 创建库
使用 open 方法创建数据库。
var request = window.indexedDB.open("testDB", 2);//第一个参数是数据库的名称,第二个参数是数据库的版本号。版本号可以在升级数据库时用来调整数据库结构和数据
request.onsuccess = function(event){
console.log("成功打开DB");
}//成功之后的回调函数
3.4 添加数据
var transaction = db.transaction(["students"],"readwrite");//先创建事务,具有读写权限
transaction.oncomplete = function(event) {
console.log("Success");
};
transaction.onerror = function(event) {
console.log("Error");
};
var test = transaction.objectStore("test");
test.add({rollNo: rollNo, name: name});//添加数据
3.5查询
var request = db.transaction(["test"],"readwrite").objectStore("test").get(rollNo);//创建具备读写功能的事务
request.onsuccess = function(event){
console.log("结果 : "+request.result.name);
};//成功查询的回调函数
3.6 修改
var transaction = db.transaction(["test"],"readwrite");//创建事务
var objectStore = transaction.objectStore("test");
var request = objectStore.get(rollNo);
request.onsuccess = function(event){
console.log("Updating : "+request.result.name + " to " + name);
request.result.name = name;//修改数据
objectStore.put(request.result);//执行修改
};
3.7 删除
//创建事务,并删除数据
db.transaction(["students"],"readwrite").objectStore("students").delete(rollNo);
4. 实际项目应用
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>离线记事本</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script><!-- 引用jQuery插件 -->
</head>
<script>
var datatable = null;
var db = openDatabase("note", "", "notebook", 1024 * 100);
//初始化函数方法
function init() {
datatable = document.getElementById("datatable");
showAllData();
}
function removeAllData() {
for(var i = datatable.childNodes.length - 1; i >= 0; i--) {
datatable.removeChild(datatable.childNodes[i]);
}
var tr = document.createElement("tr");
var th1 = document.createElement("th");
var th2 = document.createElement("th");
var th3 = document.createElement("th");
th1.innerHTML = "标题";
th2.innerHTML = "内容";
th3.innerHTML = "时间";
tr.appendChild(th1);
tr.appendChild(th2);
tr.appendChild(th3);
datatable.appendChild(tr);
}
//显示数据库中的数据
function showData(row) {
var tr = document.createElement("tr");
var td1 = document.createElement("td");
td1.innerHTML = row.title;
var td2 = document.createElement("td");
td2.innerHTML = row.content;
var td3 = document.createElement("td");
var t = new Date();
t.setTime(row.time);
td3.innerHTML = t.toLocaleDateString() + " " + t.toLocaleTimeString();
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
datatable.appendChild(tr);
}
//显示所有的数据
function showAllData() {
db.transaction(function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS item(title TEXT,content TEXT,time INTEGER)", []);
tx.executeSql("SELECT * FROM item", [], function(tx, rs) {
removeAllData();
for(var i = 0; i < rs.rows.length; i++) {
showData(rs.rows.item(i))
}
})
})
}
//添加一条记事本数据
function addData(title, content, time) {
db.transaction(function(tx) {
tx.executeSql("INSERT INTO item VALUES (?,?,?)", [title, content, time], function(tx, rs) {
alert("保存成功!");
},
function(tx, error) {
alert(error.source + "::" + error.message);
}
)
})
}
//点击保存按钮
function saveData() {
var title = document.getElementById("name").value;
var content = document.getElementById("memo").value;
var time = new Date().getTime();
addData(title, content, time);
showAllData();
}
</script>
<body onload="init()">
<div data-role="page" id="pageone">
<div data-role="header" data-position="fixed">
<h1>离线记事本</h1>
</div>
<div data-role="main" class="ui-content">
<p align="center">记事</p>
<table data-role="table" class="ui-responsive">
<thead>
<tr>
<th>标题:</th>
<th>内容:</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="name"></td>
<td><input type="text" id="memo"></td>
</tr>
</tbody>
</table>
<button type="submit" onclick="saveData()">保存</button>
<table data-role="table" data-mode="" class="ui-responsive" id="datatable">
</table>
</div>
</div>
</body>
</html>
上述代码通过使用 websql 实现了一个简单的离线记事本的功能,数据库中保留 3 个字段,分别是标题、内容、时间,点击保存按钮调用insert 豫剧将数据添加到数据库,然后通过使用 select语句将数据库中的数据展示出来。如果浏览器不主动清空数据的情况下离线数据将会永久保存,这样的话借助 websql 可以实现与桌面应用相差无几的功能。
5. indexedDB 和 websql 对比
-
访问限制: indexdb 和 websql 一致,均是在创建数据库的域名下才能访问,且不能指定访问域名。
-
存储时间: 这两位的存储时间也是永久,除非用户清除浏览器数据,可以用作长效的存储。
-
大小限制: 理论上讲,这两种存储的方式是没有大小限制的。然而 indexeddb 的数据库超过50M的时候浏览器会弹出确认,基本上也相当于没有限制了。但是由于不同的浏览器的实现有一定的差别,实际使用中需要根据不同的浏览器做相应的容量判断容错。
-
性能测试: indexeddb 查询少量数据花费差不多 20MS 左右。大量数据的情况下,相对耗时会变长一些,但是也就在 30MS 左右,也是相当给力了,10W 数据+,毕竟 nosql。而 websql 的效率也不错,10w+ 数据,简单查询一下,只花费了20MS左右。
-
标准规范: Web SQL 数据库是一个独立的规范,因为安全性能等问题,官方现在也已经放弃了维护;indexedDB 则属于 W3C 标准。
6. 小结
回顾本章,由关系数据库的优缺点及适用场景引申到 HTML5 中的数据库解决方案,以及使用方法,需要注意的是在使用 HTML 数据库的过程中需要检测浏览器是否支持数据库。实际开发项目由于考虑前端数据库的安全性以及性能等问题,如果切实需要使用需要谨慎,毕竟一般项目中数据库保存的都是敏感数据,即使保存在服务器中也需要一定的安全加密措施,所以一般前端存储的都是一些临时的数据。
websocket
网页中的绝大多数请求使用的是 HTTP 协议,HTTP 是一个无状态的应用层协议,它有着即开即用的优点,每次请求都是相互独立的,这对于密集程度较低的网络请求来说是优点,因为无需创建请求的上下文条件,但是对于密集度或者实时性要求较高的网络请求(例如 IM 聊天)场景来说,可能 HTTP 会力不从心,因为每创建一个 HTTP 请求对服务器来说都是一个很大的资源开销。这时我们可以考虑一个相对性能较高的网络协议 Socket,他的网页版本被称为 Websocket。
1. 背景
近年来,随着 HTML5 和 w3c 的推广开来,WebSocket 协议被提出,它实现了浏览器与服务器的实时通信,使服务端也能主动向客户端发送数据。在 WebSocket 协议提出之前,开发人员若要实现这些实时性较强的功能,经常会使用一种替代性的解决方案——轮询。
轮询的原理是采用定时的方式不断的向服务端发送 HTTP 请求,频繁地请求数据。明显地,这种方法命中率较低,浪费服务器资源。伴随着 WebSocket 协议的推广,真正实现了 Web 的即时通信。
WebSocket 的原理是通过 JavaScript 向服务端发出建立 WebSocket 连接的请求,在 WebSocket 连接建立成功后,客户端和服务端可以实现一个长连接的网络管道。因为 WebSocket 本质上是 TCP 连接,它是一个长连接,除非断开连接否则无需重新创建连接,所以其开销相对 HTTP 节省了很多。
2. API
2.1 创建连接
通过使用新建一个 websocket 对象的方式创建一个新的连接,不过在创建之前需要检测一下浏览器是否支持 Websocket,因为只有支持 HTML5 的浏览器才能支持 Websocket,如下:
if(typeof window.WebSocket == 'function'){
var ws = new WebSocket('http://127.0.0.1:8003');//创建基于本地的8003端口的websocket连接
}else alert("您的浏览器不支持websocket");
上述代码会对本地的 8003 接口请求 Websocket 连接,前提是本地的服务器有进程监听 8003 端口,不然的话会连接失败。
2.2 创建成功
由于 JavaScript 的各种 IO 操作是基于事件回调的,所以 Websocket 也不例外,我们需要创建一个连接成功的回调函数来处理连接创建成功之后的业务处理,如下:
ws.onopen = function(){//通过监听 open 时间来做创建成功的回调处理
console.log('websocket连接创建成功')
//进行业务处理
}
2.3 接收消息
我们辛辛苦苦创建了长连接就是为了发送或者接收网络数据,那么怎么接收呢,跟上边提到的意义,还是需要在回调函数里处理,一不小心就陷入了回调地狱了:
ws.onmessage = function(event){
var d = event.data;
//接收到消息之后的业务处理
switch(typeof d){//判断数据的类型格式
case "String":
break;
case "blob":
break;
case "ArrayBuffer":
break;
default:
return;
}
}
上述实例通过监听 message 事件对 websocket 的消息进行一定的业务处理,这其中需要判断数据类型格式,因为 Websocket 是基于二进制流格式的,传输过来的消息可能不一定是基于 utf8 的字符串格式,因此需要对格式进行判断。
2.4 发送消息
客户端通过使用 send 函数向服务端发送数据,例如:
ws.send("一段测试消息");
可以发送文本格式,也可以发送二进制格式,例如:
var input = document.getElementById("file");
input.onchange = function(){
var file = this.files[0];
if(!!file){
//读取本地文件,以gbk编码方式输出
var reader = new FileReader();
reader.readAsBinaryString(file);
reader.onload = function(){
//读取完毕后发送消息
ws.send(this.result);
}
}
}
2.5 监听错误信息
类似上述提到的如果创建实例失败的情况,系统会出现异常,但是我们并不能准确判断出异常的信息,这时需要通过监听错误事件来获取报错信息,例如:
ws.onerror = function(event){
//这里处理错误信息
}
2.6 关闭连接
当服务端或者客户端关闭 websocket 连接时,系统会触发一个关闭事件,例如:
ws.onclose = function (event){
//这里处理关闭之后的业务
}
2.7 连接的状态
通过 websocket 对象的 readyState 属性可以获取到当前连接的状态,其中常用的有4种,通过 websocket 对象的几种定义常量对比判断:
switch (ws.readyState){
case WebSocket.CONNECTING:break;//处于正在连接中的状态
case WebSocket.OPEN:break;//表示已经连接成功
case WebSocket.CLOSING:break;//表示连接正在关闭
case WebSocket.CLOSE:break;//表示连接已经关闭,或者创建连接失败
default:break;
}
3. websocket 实例
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<style>
p {
text-align: left;
padding-left: 20px;
}
</style>
</head>
<body>
<div style="width: 700px;height: 500px;margin: 30px auto;text-align: center">
<h1>聊天室实战</h1>
<div style="width: 700px;border: 1px solid gray;height: 300px;">
<div style="width: 200px;height: 300px;float: left;text-align: left;">
<p><span>当前在线:</span><span id="user_num">0</span></p>
<div id="user_list" style="overflow: auto;">
</div>
</div>
<div id="msg_list" style="width: 598px;border: 1px solid gray; height: 300px;overflow: scroll;float: left;">
</div>
</div>
<br>
<textarea id="msg_box" rows="6" cols="50" onkeydown="confirm(event)"></textarea><br>
<input type="button" value="发送" onclick="send()">
</div>
</body>
</html>
<script type="text/javascript">
var uname = window.prompt('请输入用户名', 'user' + uuid(8, 16));
var ws = new WebSocket("ws://127.0.0.1:8081");
ws.onopen = function () {
var data = "系统消息:连接成功";
listMsg(data);
};
ws.onmessage = function (e) {
var msg = JSON.parse(e.data);
var data = msg.content;
listMsg(data);
};
ws.onerror = function () {
var data = "系统消息 : 出错了,请退出重试.";
listMsg(data);
};
function confirm(event) {
var key_num = event.keyCode;
if (13 == key_num) {
send();
} else {
return false;
}
}
/**
* 发送并清空消息输入框内的消息
*/
function send() {
var msg_box = document.getElementById("msg_box");
var content = msg_box.value;
var reg = new RegExp("\r\n", "g");
content = content.replace(reg, "");
var msg = {'content': content.trim(), 'type': 'user'};
sendMsg(msg);
msg_box.value = '';
}
/**
* 将消息内容添加到输出框中,并将滚动条滚动到最下方
*/
function listMsg(data) {
var msg_list = document.getElementById("msg_list");
var msg = document.createElement("p");
msg.innerHTML = data;
msg_list.appendChild(msg);
msg_list.scrollTop = msg_list.scrollHeight;
}
/**
* 将数据转为json并发送
* @param msg
*/
function sendMsg(msg) {
var data = JSON.stringify(msg);
ws.send(data);
}
</script>
上述实例通过使用 websocket 实现了一个简单的聊天室功能,功能上只实现了接受和发送消息的功能,在登录认证和安全性等问题上并没有做过多的处理,只是为了给大家连贯的展示一下 websocket 在实际项目中的使用。
4. 注意事项
实际项目中使用 websocket 需要注意一些问题 :
- websocket 创建之前需要使用 HTTP 协议进行一次握手请求,服务端正确回复相应的请求之后才能创建 websocket 连接; — 创建 websocket 时需要进行一些类似 token 之类的登录认证,不然任何客户端都可以向服务器进行 websocket 连接;
- websocket 是明文传输,敏感的数据需要进行加密处理; — 由于 websocket 是长连接,当出现异常时连接会断开,服务端的进程也会丢失,所以服务端最好有守护进程进行监控重启;
- 服务器监听的端口最好使用非系统性且不常使用的端口,不然可能会导致端口冲突
5. 小结
本章介绍了 websocket 的前世今生,详细说明其对应的 API 的调用方式,最后使用了一个简单的聊天室的例子来对其函数串通了一下,最后延伸了一下实际项目中使用 websocket 需要注意的地方,希望大家在实际开发中针对其优缺点来选择合适的使用场景。
HTML5 SSE 浏览器发送事件
在远古时代,网页大都是静态展示,服务器无需处理复杂且过多的请求,只需要静静地等待客户端的请求,将 HTML 代码通过 HTTP 的方式返回给客户端。因此服务器也没有主动推送数据给客户端的能力,毕竟 HTTP 是无状态的协议,即开即用。
后来随着互联网的发展,服务端有一些即时消息需要立即展示给客户端,早期的处理方式是通过客户端定时发起 HTTP 请求,这种方式命中率较低且浪费服务端资源。现在有了 HTML5 之后不需要那么麻烦了,可以使用 websocket 或者 SSE。SSE 全称 server-sent events 单项消息传递事件,相对于 websocket 这种双向协议,SSE 较为轻量,它只支持服务端向客户端推送消息。
1. 使用方式
1.1 创建实例
通过新建一个 sse 对象可以创建一个 SSE 实例,但是不要忘记检测浏览器的支持情况:
if(typeof(EventSource)!=="undefined"){
var source = new EventSource("http://127.0.0.1/test.php");
}
上述示例实现了一个创建 SSE 对象的功能,创建之前需要检测是否支持,目前 IE 之外的大部分浏览器都支持 SSE。sse 对象只有一个初始化参数,用于指定服务器的 url。
1.2 接收消息
创建实例成功之后,通过监听 message 事件来实时获取服务端的消息:
source.onmessage = function (event){
//处理业务请求
console.log(event.data)
}
1.3 服务端支持
服务器端需要对客户端发起的 HTTP 请求做相应的回复,主要是将 HTTP 报文头的 content-type 字段设置成 text/event-stream,下边以 PHP 举例:
header('content-type:text/event-stream');
while(true){
sleep(30000);
echo "message:".time();
//每隔半分钟返回一个时间戳
}
1.4 其他事件
除了监听 message 事件用于获取服务端的数据之外,还有 open 事件用于监听连接打开的状态, error 事件用于监听错误信息。
2. 几种常用的客户端-服务器消息传递方式
- http 最常用的协议,用于客户端主动向服务器发送请求,单向传递;
- ajax HTTP 的扩展版,底层还是 HTTP 协议,只不过客户端是无刷新的;
- comet 也是基于 HTTP 封装的,使用 HTTP 长连接的方式,原理大致是将 HTTP 的timeout 设置较长,服务器有数据变化时返回数据给客户端,同时断开连接,客户端处理完数据之后重新创建一个 HTTP 长连接,循环上述操作(这只是其中一种实现方式);
- websocket 这是 HTML5 中的新标准,基于 socket 的方式实现客户端与服务端双向通信,需要浏览器支持 HTML5;
- Adobe Flash Socket 这个也是使用 socket 的方式,需要浏览器支持 flash 才行,为了兼容老版本的浏览器;
- ActiveX object 只适用于 IE 浏览器;
- 目前尚没有一种方式能兼容所有的浏览器,只能针对软件的目标客户人群做一定的兼容。
- sse 服务端单向推送。
3. 适用场景
并非所有场景都适合使用 sse 处理,在消息推送接收不频繁的情况下选用 ajax 轮询或者 sse 或者 websocket 其实差别不太大。sse 应该适用于服务端向客户端发送消息频繁而客户端几乎无需向服务端发送数据的场景下,例如:
- 新邮件通知;
- 订阅新闻通知;
- 天气变化;
- 服务器异常通知;
- 网站公告;
- 等等。
sse 的优缺点:
- SSE 使用 HTTP 协议,除 IE 外的大部分浏览器都支持;
- SSE 属于轻量级,使用简单;
- SSE 默认支持断线重连;
- SSE 一般只用来传送文本,二进制数据需要编码后传送;
- SSE 支持自定义发送的消息类型。
4. 总结
本章介绍了 websocket 的轻量级版本 sse 协议,简述了 sse 协议的使用方法,对比了其他网页中常用的消息推送方式以及他们的优缺点,这些协议涵盖了大部分的使用场景,选用适合的协议类型可以避免不必要的资源和性能消耗。
HTML5 语义化元素
本章节我们来介绍一个抽象的知识点-语义化。什么是语义化,浅显的来说就是使用合适的语法来实现相应的功能,这里说的合适并非是从性能、数据结构、算法等深度层面,而是从阅读和维护方式等层面。
编程过程中实现一个相同的功能,往往可以使用多种不同的方式,选择一个合适的方式需要综合考虑可维护性、扩展性、性能等几种不同的维度,而可维护性是其中比较重要的一个因素。可维护性就是指书写的代码是否通俗易懂方便阅读,当大家都遵守一种统一的书写标准时,团队的开发效率、协调能力就能得到很大的提升。
1. HTML 的语义化
HTML 语义化是指使用恰当语义的 html 标签、class 类名、ID、属性名称 等内容,让页面具有良好的结构与含义,从而让人和机器都能快速理解网页内容。语义化的 HTML 页面一方面可以让搜索引擎高效的理解和搜集网页内容;另一方面方便开发维护人员读懂代码。总结起来就是:正确的标签做正确的事情,页面内容结构化方便开发人员阅读,便于浏览器搜索引擎解析。
2. 常用语义化标签
- header 定义某一部分段落或者文本的头部信息
- nav 导航信息
- main 呈现网页的主体结构
- article 用于文本分段
- section 用于对主题相关的内容分组
- footer 定义网页底部
- h1-h6 定义标题栏
- div 定义块
- title 页面标题
- ul 无序列表
- ol 有序列表
- aside 表示与当前内容无关的内容
- small 小号字体
- em 斜体字体
- Mark 黄色突出字体
- figure 独立的流内容
- figcaption 定义 figure 元素的标题
- cite 表示文本是对参考文献的引用
- blockquote 定义块引用
- q 短引述
- time 定义合法的日期或时间格式
- dfn 定义术语元素
- abbr 简称或缩写
- del 定义删除的内容
- ins 添加的内容
- code 标记代码
- meter 定义标量测量
- progress 定义运行中的进度 上述罗列了包含明确语义内容的标签,实际项目中应当根据实际场景选择对应的语义标签。
2.1 small
small 标签属于 HTML 中的格式元素,用于显示较小的文本,例如:
<small>用于定义小的文本</small>
2.2 em
em 用于显示斜体,它和 i 标签的效果类似, 不同的是 em 是语义化元素,用于强调斜体,例如:
<em>用于显示斜体</em>
2.3 Mark
Mark 标签用于显示黄色背景的文本,例如:
<mark>标记文本</mark>
2.4 figure
figure 标签用于在文档中插入图片、图标、照片、代码等流内容,例如:
<figure>
<img src="show.jpg" >
</figure>
2.5 figcaption
figcaption 标签用于 figure 标签的标题,它必须定义在 figure 内部,一个 figure 只能放一个 figcaption ,例如:
<figure>
<img src="actShare.png" alt="这是一张用于演示的图片">
<figcaption>演示</figcaption>
</figure>
2.6 cite
cite 标签用于表示对某个文献引用的文本定义,例如书籍、杂志等内容,它所展示的是斜体文本,是一个典型的语义化标签,例如:
<cite>语义化标签</cite>已经远远超过了改变它所包含的文本外观的作用,它使浏览器能够以各种实用的方式来向用户表达文档的内容
2.7 blockquote
blockquote 用于定义源于另一个块内容的引用,它的默认展示方式是左右两侧缩进,例如:
<blockquote>
<p>引用的段落1</p>
<p>引用的段落2</p>
</blockquote>
2.8 q
q 标签用于定义短引用,浏览器默认会为它左右显示引号,例如:
知识付费市场蓬勃兴起,但也存在<q>内容良莠不齐、服务跟不上等问题</q>,需要加以改进完善
2.9 time
time 标签用于表示 24 小时制时间或者公历日期,如果表示日期也可以包含时间和时区,这个标签用于搜索引擎的友好型,目前所有主流浏览器都不完全支持 time 标签,例如:
<time datetime="YYYY-MM-DDThh:mm:ssTZD">今天是端午节</time>
2.10 dfn
dfn 标签用于首次定义术语,仅仅包含术语,不必包含术语的定义,再次出现术语时可 abbr 元素表示,例如:
<p>The <dfn>GDO</dfn>is a device that allows off-world teams to open the iris.</p>
2.11 abbr
abbr 标签用于定义一个缩写内容,当鼠标停留在内容上时,浏览器会展示 title 属性的内容,例如:
乘风破浪这个词,语出南朝名将<abbr>宗悫</abbr>
2.12 del
del 标签用于定义带有删除线(下划线)的文本,例如:
<p>原价:<del>50</del>,促销价:10</p>
2.13 ins
ins 类似于 del,不同的是这个标签是用于插入新的内容,展现形式是文本下边加上下划线,例如:
<p>一打有 <del>二十</del> <ins>十二</ins> 件。</p>
2.14 code
code 标签用于展示计算机编程代码或者伪代码,专为软件开发人员设计的,文本将用等宽、类似电传打字机样式的字体(Courier)显示出来,例如:
<code>
document.getELementById("test");
</code>
2.15 meter
meter 元素用于度量给定范围内的数据,例如:
<meter value="20" min="0" max="100">百分之二十</meter>
2.16 progress
progress 标签是用于定义进度条,HTML5 之前的版本都是需要用 div 或者其他标签配合 css 以及 JavaScript 才能实现出来滚动条效果,现在只要定义一个标签就可以了,例如:
<progress value="22" max="100"></progress>
max 属性用于表示滚动条的最大长度,value 值表示当前完成了多少。
3. 语义化延伸
实际项目中应尽量按照如下标准,做到代码易扩展、易维护:
- 尽量做到标签语义化,少量使用没有语义的标签,例如 div、span;
- 熟悉每个标签的属性规范,属性值命名应当浅显易懂;
- 网页尽量使用结构化,例如区分头部、内容、尾部;
- 样式与内容分离,样式应尽力放到 css 文件中;
- 脚本 JavaScript 尽量与内容分离,包含到 JavaScript 引用中;
- 复杂的代码需要使用注释;
- 尽量使用 w3c 定义的标准语法,避免出现浏览器兼容性问题
4.举例
如下 div 布局及结构标签布局两个例子,在网页中展现一模一样。明显结构标签布局语义行更强,便于开发者理解和阅读:
<html lang="zh-cn">
<head>
<title>Insert a title</title>
<meta charset="utf-8">
</head>
<body>
<div id="header">顶部</div>
<div id="nav">导航</div>
<div id="banner">内容</div>
<div id="main">
<div id="left_side">左边栏</div>
页面主体
</div>
<div id="footer">页面底部</div>
</body></html>
<html lang="zh-cn">
<head>
<title>Insert a title</title>
<meta charset="utf-8">
</head>
<body>
<header>顶部</header>
<nav>导航</nav>
<div>内容</div>
<section>
<aside>左边栏</aside>
页面主体
</section>
<footer>页面底部</footer>
</body></html>
虽然使用 div 通过使用 css 样式可以实现大部分标签的效果,但是并不建议这样使用
扩展内容:css 简介
前边介绍 JavaScript 的简史,本章我们介绍另一个跟 HTML 形影不离的技术 CSS,它的存在感视乎没有 JavaScript 那么强。如果说 JavaScript 是用户交互的发动机,那么 CSS 应该就是衣服了,主要工作是修饰网页。
1. 历史
HåkonWium Lie 于 1994 年 10 月首次提出的,它被称为层叠样式表或 CSS。
1.3 css 出世
CSS 之所以脱颖而出,是因为它很简单,特别是与某些最早的竞争对手相比。CSS 遵循一种可预见的,宽容的格式,几乎任何人都可以使用它。但是,CSS 的比较特殊的是它允许样式层叠,层叠意味着样式可以继承并覆盖先前已声明的其他样式,同时它又允许在同一页面上使用多个样式表。 css 刚刚推出时,业界具有很多争议,争议点就在于刚刚提到的宽松性。 花费了几年时间,到 1996 年底第一版 css 定稿。
1.4 应用到浏览器
在 CSS 还只是一个草案的时候,网景的浏览器已经支持了上述提到的具有复杂属性标签的 HTML 元素,如 multicol,layer 和可怕的 blink 标签;另一方面,微软的 Internet Explorer 已经采用了一些 CSS 零碎的方式。但是他们的支持参差不齐,有时甚至是错误的。在最初的 CSS 版本推出五年后,还没有完全支持 CSS 的浏览器。 在 2002 年 3 月,他们交付了用于 Internet Explorer 5。第一个完全支持 CSS Level 1 的浏览器。
2. 原理
2.1 浏览器渲染
说到 css 的原理,就不能不提浏览器的渲染机制:
上图可以看出,浏览器渲染可以分两部分:
- HTML parser 生成 DOM 树;
- css parser 生成 style rules 最后,dom 树和 style rules 生成新的 render tree 渲染树,然后绘制到浏览器中,展示出来。
2.2 css 解析器
上边提到浏览器的渲染机制,可以看到 CSS 模块负责 CSS 脚本解析,并为每个 Element 计算出样式。Webkit 使用了自动代码生成工具生成了相应的代码,也就是说词法分析和语法分析这部分代码是自动生成的。这期间经历了以下几个步骤:
- 通过调用 CSSStyleSheet 的 parseString 函数,将上述 CSS 解析过程启动,解析完一遍后,把 Rule 都存储在对应的 CSSStyleSheet 对象中; — 由于目前规则依然是不易于处理的,还需要将之转换成 CSSRuleSet。也就是将所有的纯样式规则存储在对应的集合当中,这种集合的抽象就是 CSSRuleSet;
- CSSRuleSet 提供了一个 addRulesFromSheet 方法,能将 CSSStyleSheet 中的 rule 转换为 CSSRuleSet 中的 rule ;
- 基于这些个 CSSRuleSet 来决定每个页面中的元素的样式;
3. 优先、继承原则
css 的解析遵循 2 个原则,优先原则和继承原则。
- 优先原则:后解析的内容会覆盖前边解析的内容;
- 同一个选择器从上到下读取 css 样式;
- 同一类选择器从上往下执行;
- 不同类型的选择器优先级遵循先执行低优先级再执行高优先级;
- 外部样式与内部样式合并之后一起执行,遵循从上往下的执行顺序;
- 内联样式最后执行 - 优先级最高;
- 加!important 标识最后执行。
- 继承原则 - 嵌套的标签拥有外部标签的部分样式,子元素继承父元素的样式。
- 文本相关的样式被继承,其他的不能继承;
- 块元素的宽度默认继承父元素的宽度,高度则自适应。
4. 浏览器兼容性
4.1 原因
兼容性问题是网站技术中老生常谈的问题,包括 HTML、JavaScript、CSS 都会出现兼容性问题。导致这个问题的根本原因是不同的浏览器厂商的内核不同,导致对 CSS 的解析效果不一致,继而显示效果千差万别。这里不去过分讨论内核不兼容的深层次原理,而是讨论一下大的解决思路,主要包括 4 个方面:
- 浏览器 CSS 样式初始化
- 浏览器私有属性
- CSS hack 语法
- 第三方插件。
4.2 定义默认样式
由于浏览器的 CSS 默认的样式都不一致,所以简单有效的方式是手动定义其默认样式。定义默认样式主要针对父元素,因为子元素会继承父元素的样式,直接定义父元素的样式简单便捷,例如:
*{
margin: 5px;/*外间距*/
padding: 5px;/*内间距*/
}
html {
line-height: 150%;/**/
-webkit-text-size-adjust: 100%;
}
body {
margin: 10px;/*外间距*/
}
a {
background-color: transparent;/*背景色*/
}
img {
border-style: none; /*边框*/
}
以上代码针对全局的内外间距、HTML 元素的行高、body 元素的外间距、链接元素的背景色、图片元素的边框样式做同一定义。
4.3 浏览器特有样式
有些浏览器的内核有自己特有的 CSS 样式,这种样式只能被自己识别,利用这种特性可以定义一些 CSS 样式用于兼容特有的浏览器版本,例如:
-ms-transform:rotate(-3deg); /*只兼容IE*/
-o-transform:rotate(-3deg); /*只兼容Opera*/
-webkit-transform:rotate(-3deg); /*只兼容Chrome/Safari*/
-moz-transform:rotate(-3deg); /*只兼容Firefox*/
4.4 CSS Hack
Hack 类似于上边提到的特有 CSS 样式,区别在于 Hack 是针对标准的 CSS 加上特殊符号,用于被特有的浏览器识别,以下举例:
a {
_margin: 10px; /*前缀加 "_" 只能被 IE6 及以下版本 IE 识别*/
*margin: 15px; /*前缀加 "*" , 只能被 IE7 识别*/
margin: 12px\9; /*值后缀加 "\9" 被 IE6 及以上版本 IE 识别*/
margin: 16px\0;/*值后缀加 "\0" 被 IE8以上版本的IE 以及 Opera15 以下版本识别 */
}
以上举了几个代表性的 Hack 的例子,实际上花大力气解决这些兼容性问题, 无非是给各个浏览器厂商填坑罢了,开发项目中一般不推荐使用过多的 Hack ,因为这会导致代码生僻难懂,维护成本过高。
4.5 第三方库
引入第三方库可以解决一些兼容性问题,方便快捷,成本较低,使用时只需要通过引入其 CSS 库文件即可,不过具体是否能解决百分之百的兼容性问题还需要具体使用测试。以下列举一些第三库:
4.5.1 respond.js
有些浏览器不支持 CSS 媒体查询,要实现响应式设计难度较大,这个库可以帮助解决不兼容媒体查询,具体使用方式只要引用这个 JavaScript 库即可,它的 GitHub 地址是 https://github.com/scottjehl/Respond
4.5.2 rem.js
在 css3 中引入了字体单位 rem,这是一个相对单位,使用较为频繁,但是一些老的浏览器不支持,所以需要引入第三方库 https://github.com/chuckcarpenter/REM-unit-polyfill,使用时只要引入 JavaScript 库文件即可。
4.5.3 normalize.css
由于不同的浏览器对于一些 CSS 的默认样式是不相同的,但是有时我们为了兼容性需要它们的默认展现形式一致,所以简单的方案是引入一个 CSS 库,它的地址是 https://www.bootcdn.cn/normalize/。
非原创,参考文章:www.imooc.com/wiki/html5