前端笔记知识点整合之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply
一、快捷位置和尺寸属性
dom已经提供给我们计算后的样式,但是还是觉得不方便,因为计算后的样式属性值都是字符串类型。
不能直接参与运算。
所以dom又提供了一些api:得到的就是number类型的数据,不需要parseint(),直接可以参与运算。
offsetleft和offsettop offsetwidth和offsetheight clinetwidth和clinetheight |
1.1 offsetwidth和offsetheight
全线兼容,是自己的属性,和别的盒子无关的。
一个盒子的offsetwidth值就是自己的width+左右padding+左右border的宽度
一个盒子的offsetheight值就是自己的height+上下padding+上下border的宽度
var obox = document.getelementbyid("box"); alert(obox.offsetwidth) alert(obox.offsetheight) |
如果盒子没有宽度,那么浏览器都将px值当做offsetwidth,而不是100%
如果盒子没有高度,用内容撑开,那么浏览器都将px值当做offsetwidth。
1.2 clientwidth和clientheight
全线兼容,就ie6有一点点问题。
var obox = document.getelementbyid("box"); alert(obox.clientwidth) alert(obox.clientheight) |
clientwidth就是自己的width+padding的值,也就是说,比offsetwidth少了border
clientheight就是自己的height+padding的值,也就是说,比offsetheight少了border
如果盒子没有宽度,那么浏览器都将px值当做clientwidth,而不是100%
如果盒子没有高度,用内容撑开,ie6的clientheight是0,其他浏览器都是合理数值。
1.3 offsetleft和offsettop属性
获取距离方法是一样,参考元素也是一样的,以offsetleft举例。
offsetleft:偏移某个元素的左侧的距离。
offsettop:偏移某个元素的顶部的距离
这两个属性兼容性非常差,不要急,慢慢看。
ie9、ie9+、chrome等高级浏览器中:
一个元素的offsetleft值,就是这个元素的左边框外,到自己offsetparent对象的左边框内的距离(number类型)
offsetparent是:自己祖先元素中,已经定位的元素,不用考虑自己是否定位。
每个元素,天生都有一属性,叫“offsetparent”,表示自己的“偏移参考盒子”。offsetparent就是自己祖先元素中,离自己最近的已经定位的元素,如果自己祖先元素中,没有任何定位的盒子,那么offsetparent对象就是body。
<body class="body"> <div class="box1"> <div class="box2"> <div class="box3"> <p></p> </div> </div> </div> </body>
var op = document.getelementsbytagname('p')[0]; alert(op.offsetleft); alert(op.offsetparent.classname);
ie6、ie7中 offsetparent对象是谁,和高级浏览器有非常大的不同:
情形1:如果自己没有定位,那么自己的offsetparent对象就是自己的祖先元素中,离自己最近的有width或有height的元素
<body class="body"> <div class="box1"> → 有宽高,不是离的最近的 <div class="box2"> → ,有宽高,offsetparent <div class="box3"> → ,没有宽高 <p></p> → 没有定位 </div> </div> </div> </body>
情形2:自己如果有定位属性,那么自己的offsetparent就是自己祖先元素中离自己最近的有定位的元素,如果父亲都没有定位就的html元素。
<body class="body"> <div class="box1"> <div class="box2"> → 有宽高,有定位,offsetparent <div class="box3"> → 有宽高,没有定位 <p></p> → 有定位,找定位的父亲,没定位就找有宽高的父亲 </div> </div> </div> </body>
ie8的offsetparent是谁呢?和高级浏览器一致:
无论自己是否定位,自己的offsetparent就是自己祖先元素中,离自己最近的已经定位的元素。
这一点,没有任何兼容问题!但是,多算了一条父亲的边框。
总结:
|
ie6、7 |
ie8 |
ie9、ie9+、高级浏览器 |
offsetparent |
如果自己没有定位,那么自己的父亲中有width或有height或者有定位的元素。 如果自己有定位,那么就是和高级浏览器一致。 |
和高级浏览器一致 |
自己祖先元素中,离自己最近的已经定位的元素 |
offsetleft offsettop |
和高级浏览器一致 |
多算一条offsetparent(父亲)边框 |
自己的边框外到offsetparent对象的边框内 |
兼容性解决办法:不是能力检测,也不是版本检测,而是善用这个属性,确保顺序的使用条件:
自定位,父无边(父亲也要定位)
这样的话,所有浏览器的值都是引用的。
总结:这6个属性要铭记于心,offsetleft和offsettop比较闹腾,但是合理使用,也没有兼容性问题。
二、定时器
2.1定时器
window.setinterval(匿名函数,间隔时间); //间隔定时器 window.settimeout(匿名函数,间隔时间); //单次定时器 |
第一个参数:函数,既可以是一个函数的函数名引用,也可以是一个匿名函数。不能加()。
第二个参数:时间间隔,单位是毫秒,1秒钟等于1000毫秒
能够使每间隔时间,调用函数一次。习惯叫做定时器,按理说叫做“间隔器”。
var i = 0; window.setinterval(function(){ i++; //每间隔1000毫秒,执行一次函数 console.log("间隔定时器,2秒执行一次,i的值:" + i ); },1000);
间隔时间是以毫秒为单位,1000毫秒就是1秒。
“毫”就是千分之一,
“厘”就是百分之一,
“分”就是十分之一
第一参数是函数,所以可以把一个匿名函数往里放,更可以用一个有名的函数的引用放里面:
function fun(){ alert("你好!"); } window.setinterval(fun,1000);
函数执行的方法:
①函数名或变量名加()执行。
②将一个函数绑定给某个事件,事件被触发,自动执行函数。
③将函数传给定时器的第一个参数,每隔时间间隔,自动执行函数。
定时器的开启不需要任何关键字,只要程序能够执行到定时器部分,就会立即被开启,到第一个时间间隔后,会第一次执行函数。
定时器是window对象的方法,可以省略window,所以:
setinterval(function(){ alert("你好!"); },1000);
单次定时器:
settimeout(function(){ alert("boom~~~没有了"); },1000);
2.2简单运动模型
视觉暂留:是一种视觉欺诈效果,人眼有视觉残留,每移动一步足够短,连续起来看起来就像在运动。残留时间是0.1秒到0.4秒。把连续相关的画面,连续播放,就是运动了。
信号量编程:定义一个全局信号量,定义一个定时器,函数内部每执行一次,就让信号量自加,给css属性随时赋值。最终看起来就是在运动。
var obox = document.getelementbyid("box"); var nowleft = 0; //初始值 setinterval(function(){ //开启定时器,每间隔20毫秒执行一次函数,函数内部进行变量自加,并赋值 nowleft += 5; console.log(nowleft); obox.style.left = nowleft + "px"; },20);
间隔时间是20毫秒,那么1秒执行函数50次,也就是说,这个动画是每秒50帧。
控制简单运动速度的方法:
1、增加每一步的步长可以加快速度,更改信号量自加的值。
2、缩短间隔时间,相当于每一秒走的次数增加,1秒钟走的距离越远。flash中有一个帧频的概念fps,每间隔多长时间走一帧,时间间隔如果是100毫秒,fps就是10。
注意性能问题:
chrome浏览器能够支持最小5的间隔时间,每秒200帧。
ie6、7、8、9只能支持最小50的间隔时间,每秒执行20帧。
var nowleft = 0; //初始值 setinterval(function(){ nowleft += 5; },50);
注意:简单运动,不需要知道走的总步长,只要知道每一步走的步长和间隔时间,就能实现。
2.3清除定时器
clearinterval() 清除间隔定时器 cleartimeout() 清除单次定时器 |
清除定时器时,要将定时器赋值给某个变量,停止时只要清除变量的引用即可。
例如:clearinterval(定时器变量)
var btn = document.getelementsbytagname("button"); //开启间隔定时器 var timer01 = null; var timer02 = null; btn[0].onclick = function(){ timer01 = setinterval(function(){ alert("2秒执行一次间隔定时器"); },2000); } //开启单次定时器 btn[1].onclick = function(){ timer02 = settimeout(function(){ alert("单次定时器"); },2000); } //清除间隔定时器 btn[2].onclick = function(){ clearinterval(timer01); } //清除间隔定时器 btn[3].onclick = function(){ cleartimeout(timer02); }
2.4简单运动需要注意的事项
问题1:如果将开启定时器的代码放在一个点击事件中,点击事情被多次触发,相当于开启了多个定时器,在一个时间点上有多个函数同时执行。而且timer变量是全局变量,点击一次相当于重新赋值,变量内部永远只能存最小的定时器,原来的定时器就没有了,不论怎么去定时器timer,都不能停止前面的定时器。
var obox = document.getelementbyid("box"); var btn = document.getelementsbytagname("button"); var now = 0; //全局信号量 var timer = null; //存储定时器 // 开启定时器运动 btn[0].onclick = function(){ // 每点击一次,开启定时器,让元素运动 timer = setinterval(function(){ now += 10; obox.style.left = now + "px"; },50); } // 停止定时器 btn[1].onclick = function(){ clearinterval(timer); }
解决方法:设表先关,在事件内部定义应定时器之前,关掉之前的定时器,这样每次开启事件时,都会先清除一下timer之前存的定时器,放入一个新的定时器,后面停止时只需要停止最新定时器。
// 开启定时器运动 btn[0].onclick = function(){ // 设表先关,避免多次点击按钮累加定时器,先关掉之前开启的定时器 clearinterval(timer); //当点击事件触发后,立即清除timer // 每点击一次,开启定时器,让元素运动 timer = setinterval(function(){ now += 10; obox.style.left = now + "px"; },50); }
问题2:当盒子到终点,自己停止,但是有时候步长设置不合理,不能正好停在固定值位置。
下面方法是错误的:
var obox = document.getelementbyid("box"); var nowleft = 100; //初始值 var timer = setinterval(function(){ if(nowleft < 600){//判断是否走到固定的位置,没走到继续,超过了停止。 nowleft += 13; }else{ clearinterval(timer); } obox.style.left = nowleft + "px"; },50);
初始值100,所以盒子的运动轨迹是:100、113、126...607停止,盒子停下来的位置不是600,而是607。
解决方法:拉终停止,在定时器函数内部每次都要判断是否走到终点,走到终点先将变量值直接赋值一个终点值,然后停止定时器,拉到终点,停止定时器。
var timer = setinterval(function(){ nowleft += 13; if(nowleft > 600){//判断是否走到固定的位置,没走到继续,超过了停止。 nowleft = 600; //强制拉到终点 clearinterval(timer); //停止定时器 } obox.style.left = nowleft + "px"; },50);
三、无缝连续滚动
3.1简单无缝滚动
原理:页面上是6个图片,编号0、1、2、3、4、5。
复制一倍在后面,长长的火车在移动:
当你赋值的后半段火车的0号头贴到了盒子的左边框的时候,那么就
瞬间移动到原点,重新执行动画:
视觉欺诈效果:连个0的位置发生了互换,所有元素一样,看不出变化。
var rolling = document.getelementbyid("rolling"); var unit = document.getelementbyid("unit"); //得到图片的数量,计算折返点,折返点就是210 * 图片数量(没复制之前的数量) var lislength = unit.getelementsbytagname("li").length; //图片的数量 var html = unit.innerhtml += unit.innerhtml; //复制一倍的li var timer = null; //存储定时器 var nowleft = 0;//初始值 //鼠标移入停止定时器 rolling.onmouseenter = function(){ clearinterval(timer); } // 离开重新开启定时器 rolling.onmouseleave = function(){ move(); } function move(){ timer = setinterval(function(){ nowleft -= 3; //后验收,如果到了折返点,立即让left回到0的位置 if(nowleft < -210 * lislength){ nowleft = 0; } unit.style.left = nowleft + "px"; },10); } move();
3.2高级无缝滚动
简单无缝轮播,使用的一些标签都是手动复制的,而且一些数值都是确定的值,如果一个标签发生变化,需要改的地方很多。程序耦合性太强,不能多个情况使用同一段js代码。
改善:
①html结构中重复的代码,用js动态添加。
②折返点:不用计算,通过页面加载效果自动获取宽度,折返点的宽度应该等于ul内部所有元素宽度的一半。
方法:li不要添加宽度,浮动元素被img自动撑宽,ul也不加宽度,绝对定位的元素用内部的li元素撑宽。
下面的红箭头的长度,就是折返点的数值:
解决方法有两个:
方法1:遍历前半部分(复制一倍之前)所有的li,进行宽度累加,累加之后就是折返点。
上午学的offsetwidth,这个方法不带margin。所以累加的时候,需要得到计算后的margin十分麻烦。所以不考虑方法1。
方法2:折返点就是假火车第1张图的offsetleft值。所以,如果原来的li个数是lilength,那么假火车的第1张图就是lis[length]
chrome、火狐、ie10开始,不等图片加载完毕就执行代码,所以轮播图的li都没有宽度,li浮动了,浮动的父元素需要被子元素撑开宽高,图片有多宽li就有多宽。
chrome运行的时候,图片没有加载到,js就急着读取offsetleft值,如何解决?
解决方法:
1、如需图片撑开元素宽度,保证图片是加载完毕,将所有代码写在window.onload事件中。
2、图片加载事件image.onload
var rolling = document.getelementbyid("rolling"); var unit = document.getelementbyid("unit"); //得到图片的数量,计算折返点,折返点就是210 * 图片数量(没复制之前的数量) var zhefandian; //折返点,图片原来的数量 var html = unit.innerhtml += unit.innerhtml; //复制一倍的li var lis = unit.getelementsbytagname("li"); //得到li元素 var imgs = document.getelementsbytagname('img'); //获取所有图片 var lislength = lis.length;//图片的数量 // 判断图片是否加载完毕,如果加载完毕再计算offsetleft值 // 计算折返点,每个li宽度不同,所以家火车开头元素的offsetleft就是折返点,这个元素lis[lislength / // 但是由于浏览器执行代码不等图片加载完,所以要保证图片加载完后读取。 var count = 0; //累加图片个数 for(var i = 0; i < imgs.length;i++){ imgs[i].onload = function(){ count++; //如果加载成功累加1 if(count == imgs.length){ // 加载完毕得到折返点 zhefandian = lis[lislength / 2].offsetleft; move(); //所有图片加载完毕再开始运动 } } } var timer = null; //存储定时器 var nowleft = 0;//初始值 //鼠标移入停止定时器 rolling.onmouseenter = function(){ clearinterval(timer); } // 离开重新开启定时器 rolling.onmouseleave = function(){ move(); } function move(){ timer = setinterval(function(){ nowleft -= 3; //后验收,如果到了折返点,立即让left回到0的位置 if(nowleft < -zhefandian){ nowleft = 0; } unit.style.left = nowleft + "px"; },10); }
四、json
4.1 最简单的json示例
json叫做javascript object notation, javascript对象表示法。由js大牛douglas发明。
类似数组,内部也可以存放多条数据,数组只能通过下标获取某一项,有时不方便使用,json对象每一项数据都有自己的属性名和属性值,通过属性名可以调用属性值。
json对象是引用类型值,所有是存储内存地址。
之前学习过的数组:
var arr = ["东风","西风","南风","北风"] |
数组很好用,arr[0]就是南风。但是发现,数组的下标,只能是阿拉伯数字,不能是我们任意取的。
语法:
{ "k" : v, "k" : v } |
var obj = { "name":"小黑", "age":18, "sex":"不详", "height":190 } console.log(typeof obj); console.log(obj); console.log(obj.age); //18 console.log(obj["age"]);//18
调用某一项数据:
1、通过obj变量名打“点”调用对应属性的属性名
console.log(obj.age); |
2、将属性名的字符串格式放在[]进行调用
console.log(obj["age"]); |
更改obj对象的某一项属性:就是调用属性名,通过“=”赋值
obj.sex = "男"; |
4.2 json的嵌套
json里面的v,可以是任意类型的值
var obj = { "name":"黄晓明", "age":38, "sex":"不详", "height":160, "cp" :{ "name" : "angelababy", "age" :16, "height":168 } } // 所以想得到cp的age,以下写法都可以: console.log(obj) console.log(obj.cp) console.log(obj.cp.age); console.log(obj.cp["age"]); console.log(obj["cp"]["age"]);
4.3 json的添加和删除
如果想增加obj里面的项,那么就用“点”语法赋值:
var obj = { "name":"黄晓明", "age":38, "height":160, "cp" :"杨颖" } obj.age++; //改变属性值 obj.sex = "刚变性完"; //增加属性 console.log(obj); delete obj.cp; //删除obj的cp属性 console.log(obj);
新增属性:添加新的属性,就用json对象的变量打点添加新的属性名,等号赋值。
obj.cp = { "name" : "angelababy", "age" :16, "height":168 } console.log(obj)
删除某一个属性,使用delete关键字
delete obj.cp; |
4.4 json的遍历
for..in循环语句,用于遍历数组或者json对象的属性(对数组或者json对象的属性进行循环操作)。
for循环根据对象的属性名,从第一个开始进行遍历,直到遍历到最后一个属性,循环结束。
语法:
for(变量 in 对象){
} |
遍历到最后一项,循环结束。k会依次等价于obj里面的属性名,在循环语句里,用obj[k]来读取属性值。
var obj = { "name":"黄晓明", "age":38, "sex":"不详", "height":160, "cp" :{ "name" : "angelababy", "age" :16, "height":168 } } for(var k in obj){ console.log(k +"的值是:"+ obj[k]) }
创建一个新的json,属性名和属性值与原有旧的json完全相同,要求不是指向同一个内存地址。
不能直接用旧json的一个变量直接赋值给新的变量,否则就指向同一个内存地址。
方法:创建一个新的sjon,内部数据为空,通过循环遍历旧的json,得到所有的属性名添加给新的json,然后给新的属性赋值。
var obj1 = { "name":"黄晓明", "age":38, "sex":"不详", "height":160, "cp" :{ "name" : "angelababy", "age" :16, "height":168 } } // var obj2 = obj1; //这样会指向同一个内存地址,修改其中一个,两个变量的值都会改变 // console.log(obj1 == obj2); var obj2 = {} //创建新的json对象,内存地址就不一样了 // 遍历旧的json,获取所有的属性名和属性值 // 等号左侧,给obj2的json添加属性 // 等号右侧,将旧json属性取出来,赋值给新json对象的属性 for(var k in obj1){ obj2[k] = obj1[k]; console.log(obj2) } console.log(obj1 == obj2);//结果false,所以修改obj1或2都不会互相影响
五、同步异步和回调函数
5.1同步和异步
同步:synchronous
程序从上到下执行:
console.log(1); console.log(2); console.log(3); console.log(4); |
假如程序中有for循环,非常耗时间,但是浏览器会用“同步”的方式运行:
console.log(1); console.log(2); console.log(3); for(var i = 0;i < 1000;i++){ console.log("★"); } console.log(4);
同步的意思:for、while循环等很耗费时间,但是程序就傻等,等到1000个星星循环执行完毕,然后输出4。
比如用洗衣机洗衣服,需要等很长时间,等待的过程就是傻等,不同时做别的事情。
异步:asynchronous
console.log(1); console.log(2); console.log(3); setinterval(function(){ console.log("★"); },100); console.log(4);
出输4,提前执行了,然后输出星星
“异步”的意思:遇见一个特别耗费时间的事情,程序不会傻等,而是先执行后面的代码,再回头执行异步的依据。
比如用洗衣机洗衣服,需要等很长时间,等待的过程就是,可以做别的事情,比如扫地、做饭。
js中的异步语句:setinterval、settimeout、ajax、nodejs都是异步的。
如果有异步语句,那么一定的是通过“异步”的方式执行代码,如果没有异步语句,就是同步方式执行。
5.2回调函数
异步的事情做完了,我们想继续做点什么事情,此时怎么办?
回调函数:异步的语句做完后的事情。
var count = 0; var timer = setinterval(function(){ count++; //累加 // console.log(count); console.log("★"); if(count == 300){ clearinterval(timer); callback(); //回调函数,等异步语句结束后,执行函数 } },10); function callback(){ alert("所有星星输出完毕"); document.body.style.backgroundcolor = "#000"; }
六、函数节流
6.1 settimeout()方法
var obox = document.getelementbyid("box"); var otip = document.getelementbyid("tip"); obox.onmouseenter = function(){ otip.style.display = "block"; //鼠标移入显示 } obox.onmouseleave = function(){ settimeout(function(){ otip.style.display = "none"; //鼠标移出,延迟1秒隐藏 },1000); }
6.2函数节流
所谓的函数节流,就是我们希望一些函数不要连续的触发,甚至于规定,触发这个函数的最小间隔时间。
这个就是“函数节流”。
var lock = true; //开锁 btn.onclick = function(){ // 检测锁的开关情况,如果是false就执行return(后面的代码都不会执行),不执行这个事件 if(lock == false){ return; } lock = false; //上锁 console.log(math.random()); settimeout(function(){ lock = true; //2000毫秒后开锁 },2000); }
优化写法:
btn.onclick = function(){ //检测锁开关情况,如果是false就执行return(后面代码都不会执行),不执行这个事件 if(!lock){ return; } lock = false; //关掉锁 console.log(math.random()); settimeout(function(){ lock = true; //2000毫秒之后再开锁 },2000); }
七、call和apply函数
探讨普通函数中是否也有this关键字,发现普通函数的this的指向是window
普通函数中this指向window对象。
控制函数内部的this指向:
函数都可以打点调用call()和apply()方法,这两个方法可以帮我们指定函数内部的this指向谁。在函数调用过程使用这两种方法。
var obox = document.getelementbyid('box'); function fun(){ console.log(this); } // 两个作用 // 1、执行fun函数 // 2、在fun函数内部指定this指向div fun.call(obox); fun.apply(obox);
var obox = document.getelementbyid('box'); function fun(a,b){ this.style.backgroundcolor = "pink"; console.log(a,b); } fun.call(obox,10,20); fun.apply(obox,[10,20]);
说白了,call、apply功能是引用的,都是让函数调用,并且给函数设置this指向谁。
区别:函数传递参数的语法。
fun.call(obox,10,20,30,40,50); fun.apply(obox,[10,20,30,40,50]); |
call需要用逗号隔开罗列所有参数
apply是把所有参数写在数组中,即使只有一个参数,也必须写在数组中。
var obj = { "name":"小黑", "age" : 18, "sex" :"不详" } function showinfo(){ console.log(this.name); } showinfo.call(obj);
ps:尽量让它越来越规范,前期的文章都是本人的学习时的笔记整理,希望看完后可以指点一二,提提意见多多交流;
笔记流程:html>css>javascript>jquery>html5/css3>移动端>ajax>面向对象>canvas>nodejs>es678>vue>react>小程序>面试问题
意见请留言,邮箱:scarf666@163.com