前端筆記之JavaScript(九)定時器&JSON&同步非同步/回撥函式&函式節流&call/apply

mufengsm發表於2019-03-26

一、快捷位置和尺寸屬性

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

 

相關文章