前端筆記之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);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM