一、快捷位置和尺寸屬性
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);