原生js中,我们通过document.cookie
可以获取cookie
,这样我们可以不用考虑兼容性,但是增,删,查我们最好通过封装的形式把他们区分开来,使得我们能够像操作对象一样去操作cookie,这样使用起来也直观方便,底层还是基于document.cookie来操作。
下面直接上代码加注释:
封装好后,我们只要导入这个文件,就可以用全局变量的方式去操作cookie了!
原生js中,我们通过document.cookie
可以获取cookie
,这样我们可以不用考虑兼容性,但是增,删,查我们最好通过封装的形式把他们区分开来,使得我们能够像操作对象一样去操作cookie,这样使用起来也直观方便,底层还是基于document.cookie来操作。
下面直接上代码加注释:
封装好后,我们只要导入这个文件,就可以用全局变量的方式去操作cookie了!
前端中对于ajax的使用越来越多,原生的ajax需要考虑兼容性,写法也较为复杂,所以打算自己封装一个。
原生ajax的使用可以分为4个步骤(3的位置不是固定的,只要在1后面即可):
XHR对象
open()
方法指定请求方法,请求资源和是否异步onreadystatechange函数
send()
发送请求下面一一介绍:
创建XHR对象很简单,一行代码搞定:
考虑要兼容IE浏览器,得像下面这样写:
当我们要跟服务器交互的时候,就需要像正常的http请求那样指定请求方式和请求的资源,这里XHR提供了一个open()方法,它可以接受3个参数:
当我们获取到资源时,我们希望能够通过回调的方式来处理它,这时就需要指定onreadystatechange方法了。在这之前,我们需要先了解两个东西,readyState
和status
。
首先是redayState,它一共有5个可能的值,代表的是请求的建立
到成功接受响应
的一系列过程:
每当xhr对象的readyState改变
时,onreadystatechange事件就会被触发
,从单词也很明显能看出了把!这样xhr请求的过程中,onreadystatechange事件就会被触发5次,一般我们只会判断readyState是否为4,因为这时候我们已经拿到了服务器的返回的信息(可能是资源也可能是其他)。
但是拿到了响应信息并不代表我们的请求就成功了,与服务器交互的结果有可能是404页面未找到,也有可能是500服务器内部错误等其他情况,这时候就需要通过另一个判断条件–status
来判断了
常见的状态码有这些:
当readyState为4且status为200时,我们就可以提取数据了,提取数据可以通过2个属性:
当我们使用post请求时,要发给服务端的数据要通过’&’连接放在send()方法里
|
|
整合以上代码并添加一些判断以及处理函数,我们自己封装的ajax就成了!
之前已经有博客介绍了js的事件,下面的作为补充。
移动端设备会有这样的现象,就是移动端页面对于触摸事件会有300ms的延迟,据研究表明,当延迟超过100ms时,用户就能感受到明显的卡顿,当延迟达到300ms,用户更能感受到界面响应速度的缓慢。
其实这应该追溯到手机开始支持双击缩放的时候了,ios上的safari为了能将pc端的大网页较好的展示在手机端上,开始使用双击缩放的方案。通过双击缩放,我们能够看清原本因为尺寸太小而看不清楚的字体,图片等。虽然这为我们带来了便利,但在另一方面却造成了困扰,要支持这个功能,浏览器就必须判断用户是否在极短的事件内双击,就算你只是想点击一个按钮,浏览器也会按照惯例等待300ms的时间来确定用户到底是不是要缩放页面,这也就是延迟的由来。
fastclick的使用方法简单,在window load事件后,在body上调用fastClick.attach()即可
当 FastClick 检测到当前页面使用meta设置了user-scalable=no或者 touch-action 属性的解决方案时,会静默退出.
tap事件的大致思路:
meta viewport中指定页面不可缩放,则click时不存在300ms延迟
区别:
在mouseover/mouseout
看来,它的事件都与后代元素有关,进入子元素和从子元素移出会触发父元素相应的mouseout和mouseover,且子元素事件会冒泡;而在mouseenter/mouseleave
中,一旦进入了元素,无论是子元素或者父元素,都看作单独的个体,各自触发事件且不会冒泡,从父元素进入子元素和从子元素移出到父元素都不会触发父元素的事件。
总结:这两组事件的区别总结起来就是:是否冒泡和子元素的移入移出是否会触发父元素的事件。牢记这两点就ok了!
当我们通过鼠标触发页面的鼠标事件时,就会产生一个事件对象e并传给我们绑定的事件函数,里面包含很多的内容。
通常鼠标操作的话,主要是要获取鼠标当前的坐标值,并利用坐标值经过一系列的计算来完成我们的目的,例如:音乐播放器的进度条的拉拽,页面上元素的拖拽以及边界的判断等等。
而事件对象e中就提供了很多有用的信息:最常用的还是clientX/clientY 和 pageX/pageY这两组属性。
client表示的是页面的可视区域(即当前浏览器窗口)
,而page表示的则是整个页面(包括超出窗口宽高的部分)
。
这样就很明了了,在没发生滚动的情况下,两组属性随便你选,但是一旦发生滚动,还是需要选择page开头的属性,才能获得正确的数据。
兼容性:IE不支持,需要用event.y和event.y获取
除了上面两组属性外,通过鼠标触发的事件e中还有screenX和screenY,不过不是很常用,就不解释了。
dom0级有两种形式:
看看下面的例子:
上面的例子执行后,你会发现只会弹出finished字样的提示框,这是因为同一类型的事件只能绑定一个,后面会覆盖前面的。
Dom2级事件有两个方法:addEventListener()和removeEventListener(),
都可传入3个参数:
我们将上面例子用Dom2级改写后就能顺序输出testing和finished字样了:
移除事件时需要传入和绑定事件时完全相同的参数,所以
匿名函数
绑定的事件不能被移除
IE 通过attachEvent()和detachEvent()来支持,只传入事件名(要加on前缀)和处理函数,只支持冒泡
。
js中我们有时候需要对数组或者对象这些复杂类型进行拷贝,当我们把他们赋予其他变量时就以为万事大吉了,可实际上当我们对改变这些变量时,原来的数组或者对象也会跟着改变,如下面的例子:
是的,最后打印出来的都会是相同的结果,这也证明了我们的拷贝与原来的对象并不是完全分离的,这其实是浅拷贝导致的问题。
但是有时候我们想要获得跟原对象完全不相干的拷贝,那应该怎么办呢,这时候就需要深拷贝了
ECMAScript中类型变量分为两类:
平时我们总听说堆栈,其实他们就是用来储存我们的变量和值的。
对于基本数据类型而言,它们是保存在栈内存里面的,包含变量名及对应的值。
结构大致如下:
变量 | 值 |
---|---|
a | 55 |
引用类型则是保存在堆内存中,栈内存中会保存有变量名以及引用类型在堆内存中的地址。当我们访问引用类型时,会先通过变量名在栈内存中找到对象在堆内存中的地址指针,然后再到相应的堆内存中查找数据。
现在栈内存的结构是这样的:
栈内存
变量 | 值 |
---|---|
a | 堆地址1 |
堆内存
栈内存中的堆地址1 —> 对内存中的obj1
值 |
---|
obj1 |
栈内存中存放的必须时大小固定的数据,而引用类型大小不固定,只能存放在堆中。基本数据类型是按值访问,而引用类型是按地址访问。
对于基本类型来说,当拷贝时,会将值也一同复制给新变量,当我们修改它时,是下面这样的:
从上面也可以看出,修改b后,并不会影响到a的值,因为它们是相互独立的。
栈内存中对于相同的基本数据类型值只会保存一份,并不会存放两个1,如果两个变量都指向1,那么他们就是相等的,指向不同值,则是不等(变量相互独立)的。函数参数的按值传递跟上面的例子原理一样,所以函数内就算重新修改了新变量也不会改变原来的变量。
|
|
虽然我们的本意是只想对拷贝后的b进行修改,但是由于我们拷贝的只是a的引用类型地址,所以其实他们指向的是同一个对象,这时候无论对谁进行修改都会导致两者都发生变化。
###深浅拷贝
聪明的你一定会想到下面这种方法:
很好,你会发现现在就算我们修改了新对象,原对象也并不会发生改动(浅复制),但如果你以为这样就成功了,那还有我们深拷贝什么事呢!
仔细看,你会发现我们的引用类型中的数据都是基本数据类型,那么换成引用类型又会发生什么呢?
现在,你会无奈的发现,新建了对象后并没有什么卵用,其实还是引用类型在作怪,谁让它是通过栈内存中地址来指向的呢。
很明显,外层的修改并不会有什么问题,但是一旦涉及到引用类型,那么又会回到前面的老问题上面!
既然一层的复制不行,那么我们就用递归的方式对下面嵌套的所有引用类型一一进行浅复制,直到最后都转换为基本数据类型的拷贝。
|
|
现在,深拷贝完成,a和b已经不会再产生交集了,随便我们怎么折腾,哈哈!
上面我们用自己写的方法递归实现了深复制,但是其实用对象提供的一些原生方法完全能够做到深浅拷贝,一起来记录一下吧!
数组有4种方法可以实现浅拷贝:concat()和slice(),Array.from(),扩展运算符
slice方法可用来在原数组上面分割形成新数组,第一个参数为0时,即切割全部,返回一个新数组
|
|
concat方法用来合并两个数组,不传入参数则深拷贝此数组
这时ES6中的方法,用来将类数组转换为真正的数组,当然也可以用来深拷贝
|
|
同样也是ES6的东西,能够将一个数组轻易的解构并按照同样的模式赋值给新对象,其实内部是用的迭代器遍历复制,跟我们之前遍历复制很像,不过代码量少得多,哈哈,这也是浅拷贝!
object.assign()用来将任意uoge对象自身的可枚举属性拷贝给目标对象,不过拷贝的只是对象的引用,而不是对象本身
,即浅拷贝。
将元对象转成JSON字符串再转回来,最简单粗暴的一种深拷贝方法!!!
jQuery.extend这个扩展对象的方法也可以用来进行深拷贝,需要传入true参数。
深浅拷贝的原理及方法已介绍完,如有遗漏,后续会补充上来!
前面有篇博客已经大致介绍了JS的事件流机制,包含了捕获/冒泡/事件委托,接下来就委托的优缺点进行分析记录。
一般情况下,我们如果想给元素绑定事件处理函数的话,都会采用DOM0级或者DOM2级提供的方法,如onclick或者addEventListener等。但是当我们在一个ul下面有很多个li元素,我们如果还用老方法一个个的进行绑定注册,这样不仅会增加我们的代码量,而且当我们要移除某个li元素时还得一个个地去解除元素和事件处理函数的绑定(关乎内存),正是这些问题的出现,才有了基于冒泡机制的事件委托
(代理)。
在js中,当我们移除某个元素但没有将元素和监听函数进行解绑时,事件处理函数依旧会留在内存中,无法被当成垃圾回收。
优点:
1.减少事件注册,节省内存,如:
2.减少了dom节点更新的操作,处理逻辑只需在委托元素上进行,如:
缺点:
1.事件委托基于冒泡,对于onfoucs和onblur等事件不支持
2.层级过多,冒泡过程中,可能会被某层阻止掉(建议就近委托)
总之一切都是基于冒泡的,只要事件不支持冒泡或者中途有event.stopPropagation()等,那么委托就会失败,所以并不适用于直接在document上进行委托。
上一篇博客在介绍浏览器请求资源的过程中提及了重绘和回流,也对重绘回流的概念进行了介绍。根据概念,至少会有一次回流和重绘发生在第一次页面加载时,且回流一定会导致重绘,重绘不一定引起回流
.
要进行性能优化,我们就必须搞清楚什么情况会导致回流和重绘,其中回流是重点,因为它的开销会比重绘高出很多。
当页面布局和集合属性发生变化时就需要回流,常见的有以下情况:
当我们在js中操作dom的一些样式是,通常都会引起页面的重绘和回流,如果每次一引起回流重绘浏览器就重新去渲染的话,这样会耗费大量的时间。所以很多浏览器会维护1个队列,里面存放着回流和重绘的操作,等队列满了或者到了一定的时间间隔,浏览器就会flush队列,进行一个批处理,所有的重绘回流就变成了一次。
但有时我们的代码会引起浏览器提前flush队列,比如,当我们向浏览器请求以下style信息时,就会提前让浏览器flush队列:
原因:
请求以上这些值时,浏览器需要清空队列,计算出最新的元素尺寸和位置样式信息(重绘回流),因为浏览器认为队列中的某些操作会造成我们获取的值并不是最精确的!
在没有维护队列的浏览器中,减少重绘回流就需要我们合并样式的修改
,尽量一次渲染到位
,而有优化策略的浏览器,我们就要好好的利用这一点,减少会提前flush队列的操作
。
1.将多个样式修改放到一个class中或者通过classText一次性修改:
2.避免访问会引起flush队列的属性,如要访问,利用缓存
3.动画效果应用到脱离文档流的元素上
元素脱离了文档流之后,不会影响其他元素的布局,所以只会导致重绘,可以减少开销
4.避免使用css表达式
5.让要操作的元素进行”离线处理”,处理完后一起更新
a) 使用DocumentFragment进行缓存操作,引发一次回流和重绘;
b) 使用display:none技术,只引发两次回流和重绘;
c) 使用cloneNode(true or false) 和 replaceChild 技术,引发一次回流和重绘;
当我们在浏览器地址栏输入像’www.google.com’这样的网址时,按下Enter键后一段时间后就能看到网页呈现在我们面前,其实中间发生了很多事情,大致的流程是这样的:
DNS解析过程其实就是查询域名与IP映射的过程,在网络上,IP才是计算机的唯一(通讯)标识,之所以会出现域名,是因为它对于用户来说方便记忆并且有比IP更高的可用性。既然底层的通信是通过ip来进行的,那么我们输入url后,势必就需要一个中间人来帮我们进行域名到ip之间的转换,这样我们才可能进行后续的步骤,这个中间人就是DNS解析。
在分析解析过程之前,我们需要域名的组成方式:
组成部分 | 说明 | 例子 |
---|---|---|
根域 | 位于域名的末尾,用句号(.)表示,表明最高级别的层次结构 | . |
顶级域 | 用来指示某个国家/地区或组织使用的名称的类型名称 | .com |
二级域 | 个人或组织在Internet上使用的注册名称 | google.com |
子域 | 已注册的二级域名的派生域名,即网站名 | www.google.com |
这里有几种不同的域名服务器分类:
1 主机向本地域名服务器的查询一般都是递归式查询
。递归查询:主机向本地服务器询问要查询域名对应的ip时,如果本地服务器不知道,便代替用户向根域名服务器发起请求。
2 本地域名服务器向根域名服务器的查询是迭代查询
。迭代查询:当根服务器收到本地服务器的请求时,要么给出所要查询的ip地址,要么告诉本地服务器向下一级的域名服务器去查询。本地域名服务器再向顶级域名服务器发起请求,结果类似,查询不到的情况下继续向权限服务器发起请求,最终将结果返回给主机
综上所述:域名的解析过程类似下面:
.->.com->google.com->www.google.com
现实生活中,淘宝双十一每分钟的请求量是无比巨大的,如果用户请求的都是同一台服务器的话,那么这对服务器的性能要求是非常之高的。但实际上,用户并不关心获取数据的来源,他们只关注更好的服务和更快更流畅的体验,这时我们就可以根据每台机器的负载量,该机器离用户地理位置的距离来给用户分配一个合适的服务器IP,这就叫做DNS负载均衡
,又称DNS重定向
,开发中经常涉及到的CDN
就是利用了这个原理。
反向代理的原理和DNS负载均衡很像,都是为了解决负载均衡。
由于HTTP是一个无状态要求可靠传输的协议,所以我们需要建立TCP而不是UDP链接。
TCP在建立链接的时候,需要经过三次握手:
TCP对于由于各种问题而丢失的数据包会进行重传,这让用户能够接收到完整且正确的信息,这也是选用TCP而不是UDP的原因!
三次握手建立tcp连接完毕后,就是给服务器发送http请求了。
http请求报文包括三个部分:
起始行中包括了请求方式,资源路径,http协议版本三部分
EXP: GET index.html HTTP/1.1
常见的方法有:GET,POST,PUT,DELETE
首部在请求报文中又称请求报头,里面包含客户端自身的信息和向服务器发送的附加信息。
常见的请求报头有: Accept, Accept-Charset, Accept-Encoding, Accept-Language, Content-Type, Authorization, Cookie, User-Agent等。
Accept用于指定客户端用于接受哪些类型的信息,Accept-Encoding与Accept类似,它用于指定接受的编码方式。Connection设置为Keep-alive用于告诉客户端本次HTTP请求结束之后并不需要关闭TCP连接,这样可以使下次HTTP请求使用相同的TCP通道,节省TCP连接建立的时间。
主体在请求报文中又称请求正文,对于GET方法来说他是空的,对于POST,PUT等方法来说,里面是要向服务器发送的数据,请求包头中有些字段与它有关,例如当主体中的数据格式为json时,这是要设置content-type为application/json。
与请求报文类似,响应报文也由三部分组成:
状态码都是3位的数字,百位上的数字代表了响应的类别,有5中可能的取值:
常见字段:server,connection
请求返回的文本信息(资源),如html,css,js,图片等文件就存放在里面。
浏览器对于页面的解析时至上而下的,通过解析html来构建DOM树,当解析到<link>标签或@import时,就会请求服务器获取css文件,在下载的同时浏览器还是会继续向下解析的,但当下载js文件和执行它时,解析器便会停止手头的工作,等待js的操作完成后再向下解析,这便是js的阻塞问题,也是为什么<link>标签可以放在<head>中,而引入的js文件最好放在</body>前面的原因,这样可以避免js阻塞了html的解析而导致页面短时间内无法呈现在用户面前的尴尬情况。
html5中提供了defer和async来实现js外联的无阻塞加载
<link>和@imoprt的区别:
前面已经说过,解析html的时候会生成DOM树,而解析css则会生成CSSOM树,前者描述内容,后者描述应用与内容的样式规则。
DOM树和CSSOM结合在一起会构成一棵渲染树,渲染树既包含了页面上所有的可视DOM节点,又包含了CSSOM中每个节点的样式信息。
渲染树的构建步骤:
从DOM树的根节点开始,遍历所有的可视节点,不可视节点有:
对于可视节点,从CSSOM中找到对应的样式规则,附加在节点上
通过渲染树,浏览器已经能知道可视内容的样式信息了,但是真正要渲染时,我们还需要获取节点的位置和尺寸,这是布局阶段要做的工作,也成为“回流”(reflow).
布局阶段的输出结果成为“盒模型”(box model),盒模型精确表达了窗口中元素的位置和大小,所有相对的度量单位都会被转化为屏幕上的绝对像素位置。
当以上步骤都完成后,浏览器就能把节点绘制成屏幕上每个真实的像素点了,此阶段为“绘制”或者“重绘”(resterizing)
其实从这里也能引出一个概念了:
回流必定导致重绘,重绘不一定导致回流^_^!
页面渲染的过程中至少会发生一次reflow和repaint,reflow的开销相对与repaint要高得多。一般来说如果一个元素的尺寸发生了改变,会对后面的已渲染的页面造成影响,那么就需要重新计算布局,即回流,而如果只是改变了外观的话,那么只需要进行重绘即可。
举个通用的例子来说明一下重绘与回流:
对于重绘回流的优化以及js阻塞的解决会在另一篇博客提及。
当浏览器获取到了所有想要的资源并且用户没有发起新的请求之前,为了不让tcp空耗着,我们会选择断开这个tcp链接,断开的主动方可以时服务器也可以是客户端。
假设有client发起终端请求,则过程是这样的:
需要四次挥手的原因:
当一方主动发起FIN请求是,可能另一方的数据还没发送完,故此只能返回一个ACK让他先等待,只有当自己确认已经发送完所有的数据后才会发起一个FIN来并当接收到一个回传的ACK时结束这个连接,俗称四次挥手。
HTML5中添加了很多新的标签,canvas就是其中之一,它被用来进行图形的绘制,关于图形的绘制我们需要通过javascript来完成,canvas标签仅是一个图形的容器。
IE9及其他现代浏览器基本都支持这个标签。
canvas由几组API组成,除了绘制基本图形的2D上下文,还有一个名为WebGL的3D上下文,不过浏览器支持还不够好,
使用\
这是我们的画布,也是我们绘制的图像的一个载体,要在画布上绘图,我们需要先通过getContext()方法取得绘图上下文:
使用toDataUrl()方法可以导出在canvas上绘制的图像。该方法接受一个MIME类型参数,如果我们需要取得画布中的一幅png图像,可以这样做:
默认情况下,MIME类型为PNG格式。
canvas以左上角为原点(0,0),坐标值都以原点为参照进行计算,与background背景图片的位置计算方法相同。第一个值代表离左边框的距离,第二个值代表离上边框的距离。
通过2D绘图上下文提供的方法,我们可以绘制矩形,弧线等2D图形。对于这些图形,我们可以选择绘制的方式。这里有两个属性,来决定是进行填充还是进行描边:fillStyle和strokeStyle
这两个属性的值可以是字符串、渐变对象、或模式对象,默认值都是“#000000”。我们可以用任何的颜色格式(rgb、rgba、hsl、hsla)来定义样式:
这样设置后,下面涉及到填充和描边的操作都会使用这两个样式,绘制过程中样式可更改。
与绘制矩形相关的方法有:fillRect()、strokeRect()、clearRect(),这些参数都接受4个参数:矩形x坐标、矩形y坐标、矩形宽度、矩形高度,单位都为像素。
绘制图形前都应先指定填充或者描边的样式:
此外,还有以下属性能够控制线条和线条末端的形状:
2D绘图上下文提供了很多用于绘制路径的方法。在绘制之前,需要先调用beginPath(),表示要开始绘制新路径。然后再调用下面的方法绘制实际路径。
arc(x,y,radius,startAngle,endAngle,conterclockwise):以(x,y)为圆心,radius为半径,起始和结束弧度分别为startAngle,endAngle来画圆弧,counterclockwise为false代表按逆时针计算角度。
我们用这个方法来绘制一个圆形:
绘制完一个图形要关闭路径,并且最后需要调用fill()和stroke()才能将图形绘制到画布上。
画圆弧还有另外一个方法,arcTo(x1,y1,x2,y2,radius):传入弧起点和终点的坐标以及弧的半径。
注意:
每次画线都是从moveTo的点到lineTo的点
如果没有moveTo,第一次lineTo的效果和moveTo相同
每次lineTo后如果没有moveTo,下次会从上次lineTo的终点开始画线
结合上面和圆弧来写一个时钟表盘的例子:
效果:
我们可以通过bezierCurveTo()方法来画贝塞尔曲线,该方法有6个参数:clx,cly,c2x,c2y,x,y。该方法会从上一点到(x,y)之间绘制一条曲线,并以(c1x,c1y)和(c2x,c2y)为控制点。
还有一个跟这个方法类似的方法–quadraticCurveTo(c1x,c2y,x,y),用法相同,区别在于只有一个控制点(c1x,c1y),这是一个二次曲线的绘制方法。
如下例:
效果:
绘制文本主要有两个方法:fillText()和strokeText()。这两个方法都接收4个参数:要绘制的字符串,x坐标,y坐标,最大像素宽度。
同时,这两个方法都以下面3个属性为基础:
这几个属性值都有默认值,fillText()会使用fillStyle来填充文字,strokeText()则使用strokeStyle为文字描边。
绘制文本比较复杂,特别是但我们需要将文本控制在某个区域中时,为此,2d上下文为我们提供了一个确定文本你大小的方法measureText(),返回的是个包含width属性的对象。
现在,如果我们想在一个150px宽度的矩形中绘制“hello canvas!”,我们可以这样做:
上面代码会从30像素开始递减,直到文本的宽度小于150px,即找到合适的字体大小。
canvas中的渐变我们通过canvasGradient实例来实现,生成渐变对象的方法有两个:
通过上述方法创建好实例后,需要用addColorStop方法来上色,
addColorStop()有两个参数:色标位置以及css颜色值,色标位置介于0到1之间。
可以根据需要添加多个色标。
来看2个例子:
效果:
2D上下文提供了几个绘制阴影的属性,这些属性会自动为图形或者路径添加阴影。
看一个例子:
效果:
2D绘制上下文支持我们对图像进行绘制变换,变换时我们需要用到变换矩阵,下面的方法会改变变换矩阵,从而导致不同的效果。
了解变形之前需要先知道两个在绘制图形时必不可少的方法–save()和restore(),他们是用来保存和回复canvas状态的,不需要传入参数。
canvas状态存储在栈中,当save()方法被调用时,当前的状态就会被保存到栈中。
canvas状态包括的内容有:
而restore()方法被调用时,则会从栈中弹出上一个保存的状态,恢复所有设定。
translate(x,y)方法接收新位置的坐标,x是左右偏移量,y是上下偏移量,表示将坐标原点移到(x,y)。
这里需要牢记偏移的是坐标原点,实际上所有变形操作的都是坐标原点。
在变形之前用save()保存状态是一个好的习惯,因为在一个循环中做位移但没有保存和回复canvas状态,最后有些东西会不见,因为它很可能超出了canvas范围之外了。
rotate()方法接收一个angle参数,通过这个方法我们可以围绕原点旋转图像。
通过旋转,我们可以绘制有趣的图形:
|
|
效果:
变形的另外一个方法是scale(x,y),即缩放。它接收两个参数,分别为x轴和y轴的缩放因子,默认都是1.
变形的最后一个方法是transform(m11,m12,m21,m22,dx,dy),这个方法会讲当前的变形矩阵乘上一个基于自身参数的矩阵,各个参数的意义如下:
setTransform(m11,m12,m21,m22,dx,dy)会将变化矩阵重置为单位矩阵然后再调用transform()。
resetTransform(m11,m12,m21,m22,dx,dy)将当前变形为单位矩阵。
tranlate()、scale()、rotate()三个方法调用的先后顺序不同,最后出现的效果也不同,但是只要记住这三个方法都是操作的坐标轴,就会好理解得多了。
|
|
效果:
(平移,缩放,旋转)和(平移,旋转,缩放)效果一样,(缩放,旋转,平移)和(旋转,缩放,平移)一样
对键盘事件的支持主要遵循的是DOM0级,DOM3中制定了新的规范。
键盘事件有三个:
虽然所有元素都支持这些事件,但一般在文本框输入时才会用到。
文本事件只有一个:textInput,此事件是对keypress的补充,在文本出入文本框之前会触发这事件
按下字符键时,键盘事件的触发顺序如下:
keydown和keypress都是文本框内容发生变化前触发,而keyup则是文本框发生变化后触发的。
如果用户按下的是字符键,那么会先触发keydown事件,然后就是keyup事件。
键盘事件也支持相同的修改键,所以键盘事件的事件对象中也有shiftKey、ctrlKey、altKey和metaKey属性。
在发生keydown和keyup事件时,event对象的keyCode属性中会包含一个键码,该键码的值与数字字母字符键对应的ASCII码相同,如A对应的keyCode值就是65,详细的对应表大家可以百度。
IE9+及现代浏览器(除Opera)都支持一个charCode属性,只有在发生keyPress事件时才会包含此属性,值为字符键所对对应的ASCII码或者为0(非字符键)。IE8之前版本和Opera并不支持这个属性,所以我们需要实现跨浏览器的方式来获取字符编码。
|
|
取得字符编码后,就可以用fromCharCode()将其转化为实际的字符。
DOM3级事件中作出了一些修改:
IE9支持key但不支持char属性。Safari 5 和Chrome支持KeyIdentifier的属性,于key属性类似,不过当按下字符键时,keyIdentifier的值是一个Unicode值,不再是ASCII码。由于存在跨浏览器的兼容问题,不推荐使用key,char,和keyIdentifier。
DOM规范并没有包含所有浏览器支持的所有事件,很多浏览器都实现了自定义的事件,为此,html5列出了浏览器应该支持的所有事件。
网页中,我们通过右键可以调出上下文菜单,有时我们需要屏蔽这个默认事件,转而使用我们自定义的菜单,这时就需要使用contextmenu事件。此事件是冒泡的,所以我们可以为document指定一个事件处理程序,用以处理页面中触发的所有此类事件。
实际应用中,我们需要结合事件对象的clientX和clietY来定位菜单出现的位置,同时当用户点击菜单时应该隐藏菜单。
beforeunload让我们有可能在页面卸载前阻止这一操作,继续使用原有页面。但是我们不能让用户无法离开当前页面,而是应该将决定权交给用户,只是在离开前提示一些信息,询问用户是否真的要离开。
如果要在用户关闭时弹出一个信息框,需要这样做:
|
|
在FF和Opera中有一个特性,往返缓存(back-foward cache 或bfcache),能够使用户在后退或者前进时加快页面加载速度,其实他们是把整个网页保存在了缓存中。但是这里会有一个问题,如果我们的网页在bfcache中,那么再次打开着页面是不会触发load事件的,这对于那些需要在页面加载完执行一些事件的页面来说,就有可能会造成页面显示不正确。
而pageshow事件在页面显示时会触发,无论该页面是不是存在于bfcache中。虽然事件的目标是document,但是必须要将其监听程序绑定在window上。
pageshow事件对象还包含一个persisted属性,如果值为true,则表示页面存放在bfcache中。
pagehide事件会在unload事件之前触发,如果页面在卸载之后是存放在bfcache中,那么persisted值为true。
IE9+及现代浏览器都支持这两个事件。
现如今,移动端已然成为了除PC端外另一个网络流量的汇聚地,其中ios和Adroid最为耀眼,但是这些设备没有鼠标也没有键盘,那么我们要怎么监听用户的操作呢?移动端其实主要还是通过用户的手指触摸(touch)来触发事件,为此,html5提供了touch系列的事件来实现我们的目的。
这些事件都是会冒泡,且event对象中也都包含常见的属性:clientX,clientY,bubbles,cancelable,detial,screenX,screenY等
除此之外,触摸事件还支持三个用于跟踪触摸的属性:
触摸事件和鼠标事件的触发顺序如下:
关于js事件较常用的都已经介绍完了,如果想更加详尽的学习,可以看javascript高级程序设计这本书,后面可能会通过一些例子或者项目将这些知识结合起来,敬请期待!
事件,像click,load,mouseover都是用户或者浏览器执行的某种动作,都会触发某些事件。在触发事件的时候,我们需要有一个事件监听函数(事件处理程序)来响应事件。事件监听函数的名字都是“on”+事件名组成的,像click的事件监听函数就是onclick。
对于元素支持的每种事件,都可以在元素中指定一个与事件监听函数同名的特性。特性的值是要执行的js代码,比如下面的例子:
|
|
如果函数体内容多的话,需要把js代码放到javascript标签内,如下:
|
|
但是这样指定事件监听函数让得HTML与Javascript紧密耦合,如果需要更换事件监听函数,那么HTML和javascripot代码都需要修改,所以为什么不干脆全部使用js来绑定和声明事件监听函数呢!
每个元素都有自己的事件处理程序属性,通常事件名都为小写,如:onclick,onmouseover,在绑定处理函数时需要先获得元素的引用,直接看一个简单的例子吧:
|
|
程序中的this指向的是当前元素,即btn,通过点取的方式,我们可以访问元素的任何属性和方法。
“DOM2级事件”定义了两个用于绑定事件和移除事件的方法:addEventListener()和removeEevntListener(),这两个方法在之前冒泡和捕获那一节已经介绍过了,就不过多说明了。
这两个方法都接收三个参数:
addEventListener()的优势在于可以为同一元素绑定多个方法,执行顺序由绑定先后决定。
在移除时传入的参数要与绑定时相同,这也意味着不能移除匿名函数
IE实现两个类似的方法:attachEvent()和detachEvent(),之前同样也介绍过,下面就说一下特别的地方。
这两个方法都接收两个参数:
DOM0级方法中事件处理程序会在元素的作用域内进行,而使用这两个方法时程序会在全局作用域中运行,因为this指向window。
和addEventListener()类似,attachEvent()可以为元素绑定多个方法,不过是后绑定的先运行。
为了让我们的代码可以在所有浏览器正常运行,我们可以自己编写一个兼容所有浏览器的事件处理程序。
这算一个比较重要的点了,我们触发某个事件时,会产生一个事件对象event,其中包含着所有与时间有关的信息,如事件的类型,事件的目标(事件委托应用到),以及与特定事件相关的信息。例如,鼠标事件中,有包含鼠标位置的信息,键盘事件中则包含于按键相关的信息。浏览器虽然都支持event对象,但支持方式不尽相同。
当触发了事件时,兼容DOM的浏览器都自动会给我们的事件处理程序传入一个event对象,我们要做的只是在声明监听函数的时候加上一个event参数以便浏览器传入就ok了,如下:
下面列举type的属性及方法:
在事件处理程序内部,this始终等于currentTarget的值,如果不使用事件委托,那么this,currentTarget,target的值相同.
访问IE中的event的方式有几种,取决于绑定监听函数的方式。
如果是使用DOM0级方法添加的事件监听函数,那么event对象会作为window对象的一个属性存在。
|
|
如果通过attachEevent方法来绑定监听函数,那么event对象会自动传入我们我们的事件处理函数中。
同样,IE下的event对象也有一些属性和方法,大多都与DOM下的event属性方法对应:
IE中事件处理程序中的this会随绑定方式的不同而变化,所以最好使用srcElement比较稳妥。
下面来实现事件对象的兼容写法:
|
|
介绍完基本的事件监听函数和事件对象,接下来就应该介绍种类繁多的事件类型及应用了。
DOM3级事件规定了一下事件:
包括IE9在内的主流浏览器斗殴支持DOM2级事件,IE9也支持DOM3事件
UI事件指的并不一定是与用户操作有关的事件,较常用的有:
这些事件都可以用HTML事件处理程序的方式实现。
根据DOM级事件规范,我们应该在document上处理load事件,但实际上所有浏览器在window上都实现了该事件,确保向后兼容。
焦点事件与document.hasFocus()及document.activeElement属性配合,可以知道用户在页面上的行踪。
焦点事件有以下这些:
在页面中当焦点从一个元素移到另一个焦点上时,事件的触发顺序是这样的:
要确定浏览器是否支持dom3的焦点事件,可以用以下代码
|
|
网页中用户大多的操作行为都是通过鼠标来实现的,DOM3级中实现了9个鼠标事件:
总结:除mouseenter和mouseleave外,其他函数都会冒泡,也可以被取消默认行为。只有相继触发mousedown和onmouseup才会触发click事件。连续两次触发click才会引发dblclick事件。
这四个事件触发的先后顺序如下:
要确定浏览器是否支持DOM3和DOM3的鼠标事件,可以用以下代码
|
|
鼠标能触发的还有一个滚轮事件,mousewheel,其实之前的博客中已经做了详细的介绍了。
鼠标事件中事件对象具有以下属性:
pageY:事件发生时鼠标在页面中的垂直坐标(包含了滚动的距离)
以pageY为例,使用clientY和滚动信息就可以计算出pageY,这里我们需要通过document.body(混杂模式)和document.documentElement(标准模式)中的scrollLeft和scrollTop。
pageY = event.clientY + document.body.scrollTop || document.documentElement.scrollTop
修改键:
mousewheel事件触发后,会冒泡到document或者window,此外,该事件对应的事件对象中有一个wheelDelta属性,当滑轮向上滚是,该属性值是120的倍数;向下滚时,该属性值是-120的倍数.
FF(fireFox)还支持一个DOMMouseScroll事件,与mousewheel类似,只不过该事件的信息存放在event.detail(Opera也是)中,属性值为-3的倍数时,为滑轮向前滚;为3的倍数时,为滑轮向后滚。
为避免篇幅过长,其它事件在下一篇博客进行介绍。