前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

时间:2022-05-30 03:56:43

一、快捷位置和尺寸属性

DOM已经提供给我们计算后的样式,但是还是觉得不方便,因为计算后的样式属性值都是字符串类型

不能直接参与运算。

所以DOM又提供了一些API:得到的就是number类型的数据,不需要parseInt(),直接可以参与运算。

offsetLeft和offsetTop

offsetWidth和offsetHeight

clinetWidth和clinetHeight


1.1 offsetWidth和offsetHeight

全线兼容,是自己的属性,和别的盒子无关的。

一个盒子的offsetWidth值就是自己的width+左右padding+左右border的宽度

一个盒子的offsetHeight值就是自己的height+上下padding+上下border的宽度

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

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是:自己祖先元素中,已经定位的元素,不用考虑自己是否定位。

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

每个元素,天生都有一属性,叫“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就是自己祖先元素中,离自己最近的已经定位的元素。

这一点,没有任何兼容问题!但是,多算了一条父亲的边框

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

总结:

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。

复制一倍在后面,长长的火车在移动:

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

当你赋值的后半段火车的0号头贴到了盒子的左边框的时候,那么就

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

瞬间移动到原点,重新执行动画:

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

视觉欺诈效果:连个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元素撑宽。

下面的红箭头的长度,就是折返点的数值:

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

解决方法有两个:

方法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); //
console.log(obj["age"]);//

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

调用某一项数据:

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);

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

新增属性:添加新的属性,就用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);

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

同步的意思:for、while循环等很耗费时间,但是程序就傻等,等到1000个星星循环执行完毕,然后输出4。

比如用洗衣机洗衣服,需要等很长时间,等待的过程就是傻等,不同时做别的事情。

异步:Asynchronous

console.log(1);
console.log(2);
console.log(3);
setInterval(function(){
console.log("★");
},100);
console.log(4);

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

出输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

前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

普通函数中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);