zoukankan      html  css  js  c++  java
  • 前端笔记之JavaScript(九)定时器&JSON&同步异步/回调函数&函数节流&call/apply

    一、快捷位置和尺寸属性

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

    不能直接参与运算。

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

     offsetLeftoffsetTop

     offsetWidthoffsetHeight

     clinetWidthclinetHeight


    1.1 offsetWidthoffsetHeight

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

    一个盒子的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 clientWidthclientHeight

    全线兼容,就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%

    如果盒子没有高度,用内容撑开,IE6clientHeight0,其他浏览器都是合理数值。


    1.3 offsetLeftoffsetTop属性

    获取距离方法是一样,参考元素也是一样的,以offsetLeft举例。

    offsetLeft:偏移某个元素的左侧的距离。

    offsetTop:偏移某个元素的顶部的距离

    这两个属性兼容性非常差,不要急,慢慢看。

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

    IE6IE7offsetParent对象是谁,和高级浏览器有非常大的不同:

    情形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>

    IE8offsetParent是谁呢?和高级浏览器一致:

    无论自己是否定位,自己的offsetParent就是自己祖先元素中,离自己最近的已经定位的元素。

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

    总结:

    IE67

    IE8

    IE9IE9+、高级浏览器

    offsetParent

    如果自己没有定位,那么自己的父亲中有width或有height或者有定位的元素。

    如果自己有定位,那么就是和高级浏览器一致。

    和高级浏览器一致

    自己祖先元素中,离自己最近的已经定位的元素

    offsetLeft

    offsetTop

    和高级浏览器一致

    多算一条offsetParent(父亲)边框

    自己的边框外到offsetParent对象的边框内

    兼容性解决办法:不是能力检测,也不是版本检测,而是善用这个属性,确保顺序的使用条件:

    自定位,父无边(父亲也要定位)

    这样的话,所有浏览器的值都是引用的。

     

    总结:这6个属性要铭记于心,offsetLeftoffsetTop比较闹腾,但是合理使用,也没有兼容性问题。


    二、定时器

    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帧。

    IE6789只能支持最小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,所以盒子的运动轨迹是:100113126...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个图片,编号012345

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

     

    当你赋值的后半段火车的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 NotationJavaScript对象表示法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);

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

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

    异步:Asynchronous

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

    出输4,提前执行了,然后输出星星

    “异步”的意思:遇见一个特别耗费时间的事情,程序不会傻等,而是先执行后面的代码,再回头执行异步的依据。

    比如用洗衣机洗衣服,需要等很长时间,等待的过程就是,可以做别的事情,比如扫地、做饭。

    JS中的异步语句:setIntervalsetTimeoutAjaxNodejs都是异步的。

    如果有异步语句,那么一定的是通过“异步”的方式执行代码,如果没有异步语句,就是同步方式执行。


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

    七、callapply函数

    探讨普通函数中是否也有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]);

    说白了,callapply功能是引用的,都是让函数调用,并且给函数设置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);

  • 相关阅读:
    读后感四
    读后感五
    公文流转系统
    统计文件中单词的频率,给出前N的单词
    读入一个文件输出每个字母的频率
    小工到专家
    从小工到专家读后感
    动手动脑 类与对象
    海芋
    棕竹
  • 原文地址:https://www.cnblogs.com/rope/p/10603219.html
Copyright © 2011-2022 走看看