瀏覽器(一)
(瀏覽器對象和操作DOM)
不同的瀏覽器對JavaScript支持的差異主要是承匣,有些API的接口不一樣,比如AJAX斗忌,F(xiàn)ile接口闯第。對于ES6標準市栗,不同的瀏覽器對各個特性支持也不一樣。
在編寫JavaScript的時候咳短,就要充分考慮到瀏覽器的差異填帽,盡量讓同一份JavaScript代碼能運行在不同的瀏覽器中。
瀏覽器對象
JavaScript可以獲取瀏覽器提供的很多對象诲泌,并進行操作盲赊。
window
window
對象不但充當全局作用域铣鹏,而且表示瀏覽器窗口敷扫。
window
對象有innerWidth
和innerHeight
屬性,可以獲取瀏覽器窗口的內(nèi)部寬度和高度诚卸。內(nèi)部寬高是指除去菜單欄葵第、工具欄、邊框等占位元素后合溺,用于顯示網(wǎng)頁的凈寬高卒密。
兼容性:IE<=8不支持。
'use strict';
//可以調(diào)整瀏覽器窗口大小試試:
console.log('window inner size: ' + window.innerWidth + 'x' + window.innerHeight);
對應的棠赛,還有一個outerWidth
和outerHeight
屬性哮奇,可以獲取瀏覽器窗口的整個寬高
navigator
navigator
對象表示瀏覽器的信息,最常用的屬性包括:
navigator.appName:瀏覽器名稱睛约;
navigator.appVersion:瀏覽器版本鼎俘;
navigator.language:瀏覽器設置的語言;
navigator.platform:操作系統(tǒng)類型辩涝;
navigator.userAgent:瀏覽器設定的
User-Agent
字符串贸伐。
'use strict';
console.log('appName = ' + navigator.appName);
console.log('appVersion = ' + navigator.appVersion);
console.log('language = ' + navigator.language);
console.log('platform = ' + navigator.platform);
console.log('userAgent = ' + navigator.userAgent);
請注意,navigator
的信息可以很容易地被用戶修改怔揩,所以JavaScript讀取的值不一定是正確的捉邢。很多初學者為了針對不同瀏覽器編寫不同的代碼,喜歡用if
判斷瀏覽器版本商膊,例如:
var width:
if (getIEVersion(navigator.userAgent) < 9 {
width = document.body.clientWidth;
} else {
width = window.innerWidth;
}
但這樣既可能判斷不準確伏伐,也很難維護代碼。正確的方法是充分利用JavaScript對不存在屬性返回undefined
的特性晕拆,直接用短路運算符||
計算:
var width = window.innerWidth || document.body.clientWidth;
screen
screen
對象表示屏幕的信息秘案,常用的屬性有:
screen.width: 屏幕寬度,以像素為單位
screen.height: 屏幕高度,以像素為單位
screen.colorDepth: 返回顏色位數(shù)阱高,如8赚导、16、24
'use strict';
console.log('Screen size = ' + screen.width + 'x' + screen.height);
location
location
對象表示當前頁面的URL信息赤惊。例如吼旧,一個完整的URL
可以用location.href
獲取。要獲得URL各個部分的值未舟,可以這么寫:
location.protocol; //'http'
location.host; //'www.example.com'
location.port; //'8080'
location.pathname; // '/path/index.html'
location.search; // '?a=1&b=2'
location.hash; // 'TOP'
要加載一個新頁面圈暗,可以調(diào)用location.assign()
。如果要重新加載當前頁面裕膀,調(diào)用location.reload()
方法非常方便员串。
'use strict';
if (confirm('重新加載當前頁' + location.href + '?')) {
location.reload();
} else {
location.assign('/'); //設置一個新的URL地址
}
document
document
對象表示當前頁面。由于HTML在瀏覽器中以DOM形式表示為樹形結構昼扛,document
對象就是整個DOM樹的根節(jié)點寸齐。
document
的title
屬性是從HTML文檔中的<title>xxx</title>
讀取的,但是可以動態(tài)改變:
'use strict';
document.title = '努力學習JavaScript抄谐!'渺鹦;
請觀察瀏覽器窗口標題的變化。
要查找DOM樹的某個節(jié)點蛹含,需要從document
對象開始查找毅厚。最常用的查找是根據(jù)ID和Tag Name。
我們先準備HTML數(shù)據(jù):
<dl id="drink-menu" style="border:solid 1px #ccc; padding: 6px;">
<dt>摩卡</dt>
<dd>熱摩卡咖啡</dd>
<dt>酸奶</dt>
<dd>北京老酸奶</dd>
<dt>果汁</dt>
<dd>鮮榨蘋果汁</dd>
</dl>
用document
對象提供的getElementById()
和getElementsByTagName()
可以按ID獲得一個DOM節(jié)點和按Tag名稱獲得一組DOM節(jié)點:
'use strict';
var menu = documnet.getElementById('drink-menu');
var drinks = document.getElementByTagNAme('dt');
var i, s, menu, drinks;
menu = documnet.getElementById('drink-menu');
menu.tagName; //'DL'
s = '提供的飲料有:';
for (i = 0; i < drinks.length; i++) {
s = s + drinks[i].innerHTML + ',';
}
console,log(S);
document
對象還有一個cookie
屬性浦箱,可以獲取當前頁面的Cookie吸耿。
Cookie是由服務器發(fā)送的key-value標示符。因為HTTP協(xié)議是無狀態(tài)的酷窥,但是服務器要區(qū)分到底是哪個用戶發(fā)過來的請求咽安,就可以用Cookie來區(qū)分。當一個用戶成功登錄后竖幔,服務器發(fā)送一個Cookie給瀏覽器板乙,例如user=ABC123XYZ(加密的字符串)...
,此后拳氢,瀏覽器訪問該網(wǎng)站時募逞,會在請求頭附上這個Cookie,服務器根據(jù)Cookie即可區(qū)分出用戶馋评。
Cookie還可以存儲網(wǎng)站的一些設置放接,例如,頁面顯示的語言等等留特。
JavaScript可以通過document.cookie
讀取到當前頁面的Cookie:
document.cookie; //'v=123; remember = true;prefer = zh'
由于JavaScript能讀取到頁面的Cookie纠脾,而用戶的登錄信息通常也存在Cookie中玛瘸,這就造成了巨大的安全隱患,這是因為在HTML頁面中引入第三方的JavaScript代碼是允許的:
<!-- 當前頁面在wwwexample.com -->
<html>
<head>
<script src="http://www.foo.com/jquery.js"></script>
</head>
...
</html>
如果引入的第三方的JavaScript中存在惡意代碼苟蹈,則www.foo.com
網(wǎng)站將直接獲取到www.example.com
網(wǎng)站的用戶登錄信息糊渊。
為了解決這個問題,服務器在設置Cookie時可以使用httpOnly
慧脱,設定了httpOnly
的Cookie將不能被JavaScript讀取渺绒。這個行為由瀏覽器實現(xiàn),主流瀏覽器均支持httpOnly
選項菱鸥,IE從IE6 SP1開始支持宗兼。
為了確保安全,服務器端在設置Cookie時氮采,應該始終堅持使用httpOnly
殷绍。
history
history
對象保存了瀏覽器的歷史記錄,JavaScript可以調(diào)用history
對象的back()
或forward ()
鹊漠,相當于用戶點擊了瀏覽器的“后退”或“前進”按鈕主到。
這個對象屬于歷史遺留對象,對于現(xiàn)代Web頁面來說贸呢,由于大量使用AJAX和頁面交互镰烧,簡單粗暴地調(diào)用history.back()
可能會讓用戶感到非常憤怒拢军。
新手開始設計Web頁面時喜歡在登錄頁登錄成功時調(diào)用history.back()
楞陷,試圖回到登錄前的頁面。這是一種錯誤的方法茉唉。
任何情況固蛾,你都不應該使用history
這個對象了。
操作DOM
由于HTML文檔被瀏覽器解析后就是一棵DOM樹度陆,要改變HTML的結構艾凯,就需要通過JavaScript來操作DOM。
始終記住DOM是一個樹形結構懂傀。操作一個DOM節(jié)點實際上就是這么幾個操作:
- 更新:更新該DOM節(jié)點的內(nèi)容趾诗,相當于更新了該DOM節(jié)點表示的HTML的內(nèi)容;
- 遍歷:遍歷該DOM節(jié)點下的子節(jié)點蹬蚁,以便進行進一步操作恃泪;
- 添加:在該DOM節(jié)點下新增一個子節(jié)點,相當于動態(tài)增加了一個HTML節(jié)點犀斋;
- 刪除:將該節(jié)點從HTML中刪除贝乎,相當于刪掉了該DOM節(jié)點的內(nèi)容以及它包含的所有子節(jié)點。
在操作一個DOM節(jié)點前叽粹,我們需要通過各種方式先拿到這個DOM節(jié)點览效。最常用的方法是document.getElementById()
和document.getElementsByTagName()
却舀,以及CSS選擇器document.getElementsByClassName()
俐填。
由于ID在HTML文檔中是唯一的稽揭,所以document.getElementById()
可以直接定位唯一的一個DOM節(jié)點。document.getElementsByTagName()
和document.getElementsByClassName()
總是返回一組DOM節(jié)點克握。要精確地選擇DOM但校,可以先定位父節(jié)點篱昔,再從父節(jié)點開始選擇,以縮小范圍始腾。
例如:
// 返回ID為'test'的節(jié)點:
var test = document.getElementById('test');
// 先定位ID為'test-table'的節(jié)點州刽,再返回其內(nèi)部所有tr節(jié)點:
var trs = document.getElementById('test-table').getElementByTagName('tr');
// 先定位ID為'test-div'的節(jié)點,再返回其內(nèi)部所有class包含red的節(jié)點:
var reds = document.getElementById('test-div').getElementsByClassName('red');
// 獲取節(jié)點test下的所有直屬子節(jié)點:
var cs = test.children;
//獲取節(jié)點test下第一個浪箭、最后一個子節(jié)點:
var first = test.firstElementChild;
var last = test.lastElementChild;
第二種方法是使用querySelector()
和querySelectorAll()
穗椅,需要了解selector語法,然后使用條件來獲取節(jié)點奶栖,更加方便:
// 通過querySelector獲取ID為q1的節(jié)點:
var ql = document.querySelector('#ql');
// 通過querySelectorAll獲取q1節(jié)點內(nèi)的符合條件的所有節(jié)點:
var ps = ql.querySelectorAll('div.highlighted > p');
注意:低版本的IE<8不支持querySelector
和querySelectorAll
匹表。IE8僅有限支持。
嚴格地講宣鄙,我們這里的DOM節(jié)點是指Element
袍镀,但是DOM節(jié)點實際上是Node
,在HTML中冻晤,Node
包括Element
苇羡、Comment
、CDATA_SECTION
等很多種鼻弧,以及根節(jié)點Document
類型设江,但是,絕大多數(shù)時候我們只關心Element
攘轩,也就是實際控制頁面結構的Node
叉存,其他類型的Node
忽略即可。根節(jié)點Document
已經(jīng)自動綁定為全局變量document
度帮。
練習
如下的HTML結構:
JavaScript
Java
Python
Ruby
Swift
Scheme
Haskell
<!-- HTML結構 -->
<div id="test-div">
<div class="c-red">
<p id="test-p">JavaScript</p>
<p>Java</p>
</div>
<div class="c-red c-green">
<p>Python</p>
<p>Ruby</p>
<p>Swift</p>
</div>
<div class="c-green">
<p>Scheme</p>
<p>Haskell</p>
</div>
</div>
請選擇出指定條件的節(jié)點:
'use strict';
// 選擇<p>JavaScript</p>:
var js = document.getElementById('test-p');
// 選擇<p>Python</p>,<p>Ruby</p>,<p>Swift</p>:
var arr = document.getElementsByClassName("c-red")[1].getElementsByTagName("p");
// 選擇<p>Haskell</p>:
var haskell = document.getElementsByClassName('c-green')[1].lastElementChild;
// 測試:
if (!js || js.innerText !== 'JavaScript') {
alert('選擇JavaScript失敗!');
} else if (!arr || arr.length !== 3 || !arr[0] || !arr[1] || !arr[2] || arr[0].innerText !== 'Python' || arr[1].innerText !== 'Ruby' || arr[2].innerText !== 'Swift') {
console.log('選擇Python,Ruby,Swift失敗!');
} else if (!haskell || haskell.innerText !== 'Haskell') {
console.log('選擇Haskell失敗!');
} else {
console.log('測試通過!');
}
更新DOM
拿到一個DOM節(jié)點后歼捏,我們可以對它進行更新”颗瘢可以直接修改節(jié)點的文本瞳秽,方法有兩種:
一種是修改innerHTML
屬性,這個方式非常強大冕屯,不但可以修改一個DOM節(jié)點的文本內(nèi)容寂诱,還可以直接通過HTML片段修改DOM節(jié)點內(nèi)部的子樹:
// 獲取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 設置文本為abc:
p.innerHTML = 'ABC'; // <p id="p-id">ABC</p>
// 設置HTML:
p.innerHTML = 'ABC <span style="color:red">RED</span> XYZ';
// <p>...</p>的內(nèi)部結構已修改
用innerHTML
時要注意,是否需要寫入HTML安聘。如果寫入的字符串是通過網(wǎng)絡拿到了痰洒,要注意對字符編碼來避免XSS攻擊瓢棒。
第二種是修改innerText
或textContent
屬性,這樣可以自動對字符串進行HTML編碼丘喻,保證無法設置任何HTML標簽:
// 獲取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 設置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自動編碼脯宿,無法設置一個<script>節(jié)點:
// <p id="p-id"><script>alert("Hi")</script></p>
兩者的區(qū)別在于讀取屬性時,innerText
不返回隱藏元素的文本泉粉,而textContent
返回所有文本连霉。另外注意IE<9不支持textContent
。
修改CSS也是經(jīng)常需要的操作嗡靡。DOM節(jié)點的style
屬性對應所有的CSS跺撼,可以直接獲取或設置。因為CSS允許font-size
這樣的名稱讨彼,但它并非JavaScript有效的屬性名歉井,所以需要在JavaScript中改寫為駝峰式命名fontSize
:
// 獲取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 設置CSS:
p.style.color = '#ff0000';
p.style.fontSize = '20px';
p.style.paddingTop = '2em';
練習
有如下的HTML結構:
javascript
Java
<!-- HTML結構 -->
<div id="test-div">
<p id="test-js">javascript</p>
<p>Java</p>
</div>
請嘗試獲取指定節(jié)點并修改:
'use strict';
// 獲取<p>javascript</p>節(jié)點:
var js = document.getElementById('test-js');
// 修改文本為JavaScript:
js.innerText = 'JavaScript';
// 修改CSS為: color: #ff0000, font-weight: bold
js.style.color = '#ff0000';
js.style.fontWeight = 'bold';
// 測試:
if (js && js.parentNode && js.parentNode.id === 'test-div' && js.id === 'test-js') {
if (js.innerText === 'JavaScript') {
if (js.style && js.style.fontWeight === 'bold' && (js.style.color === 'red' || js.style.color === '#ff0000' || js.style.color === '#f00' || js.style.color === 'rgb(255, 0, 0)')) {
console.log('測試通過!');
} else {
console.log('CSS樣式測試失敗!');
}
} else {
console.log('文本測試失敗!');
}
} else {
console.log('節(jié)點測試失敗!');
}
插入DOM
當我們獲得了某個DOM節(jié)點哈误,想在這個DOM節(jié)點內(nèi)插入新的DOM哩至,應該如何做?
如果這個DOM節(jié)點是空的蜜自,例如菩貌,<div></div>
,那么重荠,直接使用innerHTML = '<span>child</span>'
就可以修改DOM節(jié)點的內(nèi)容箭阶,相當于“插入”了新的DOM節(jié)點。
如果這個DOM節(jié)點不是空的晚缩,那就不能這么做尾膊,因為innerHTML
會直接替換掉原來的所有子節(jié)點媳危。
有兩個辦法可以插入新的節(jié)點荞彼。一個是使用appendChild
,把一個子節(jié)點添加到父節(jié)點的最后一個子節(jié)點待笑。例如:
<!-- HTML結構 -->
<p id="js">JavaScript</p>
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
把<p id="js">JavaScript</p>
添加到<div id="list">
的最后一項:
var
js = document.getElementById('js'),
list = document.getElementById('list');
list.appendChild(js);
現(xiàn)在鸣皂,HTML結構變成了這樣:
<!-- HTML結構 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="js">JavaScript</p>
</div>
因為我們插入的js
節(jié)點已經(jīng)存在于當前的文檔樹,因此這個節(jié)點首先會從原先的位置刪除暮蹂,再插入到新的位置寞缝。
更多的時候我們會從零創(chuàng)建一個新的節(jié)點,然后插入到指定位置:
var
list = document.getElementById('list'),
haskell = document.createElement('p'); //新建p標簽
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.appendChild(haskell);
這樣我們就動態(tài)添加了一個新的節(jié)點:
<!-- HTML結構 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
<p id="haskell">Haskell</p>
</div>
動態(tài)創(chuàng)建一個節(jié)點然后添加到DOM樹中仰泻,可以實現(xiàn)很多功能荆陆。舉個例子,下面的代碼動態(tài)創(chuàng)建了一個<style>
節(jié)點集侯,然后把它添加到<head>
節(jié)點的末尾被啼,這樣就動態(tài)地給文檔添加了新的CSS定義:
var d = document.createElement('style');
d.setAttribute('type', 'text/css');
d.innerHTML = 'p { color: red }';
document.getElementsByTagName('head')[0].appendChild(d);
可以在Chrome的控制臺執(zhí)行上述代碼帜消,觀察頁面樣式的變化。
insertBefore
如果我們要把子節(jié)點插入到指定的位置怎么辦浓体?可以使用parentElement.insertBefore(newElement, referenceElement);
泡挺,子節(jié)點會插入到referenceElement
之前。
還是以上面的HTML為例命浴,假定我們要把Haskell
插入到Python
之前:
<!-- HTML結構 -->
<div id="list">
<p id="java">Java</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
可以這么寫:
var
list = document.getElementById('list'),
ref = document.getElementById('python'),
haskell = document.createElement('p');
haskell.id = 'haskell';
haskell.innerText = 'Haskell';
list.insertBefore(haskell, ref);
新的HTML結構如下:
<!-- HTML結構 -->
<div id="list">
<p id="java">Java</p>
<p id="haskell">Haskell</p>
<p id="python">Python</p>
<p id="scheme">Scheme</p>
</div>
可見娄猫,使用insertBefore
重點是要拿到一個“參考子節(jié)點”的引用。很多時候生闲,需要循環(huán)一個父節(jié)點的所有子節(jié)點媳溺,可以通過迭代children
屬性實現(xiàn):
var
i, c,
list = document.getElementById('list');
for (i = 0; i < list.children.length; i++) {
c = list.children[i]; //拿到第i個子節(jié)點
}
練習
對于一個已有的HTML結構:
- Scheme
- JavaScript
- Python
- Ruby
- Haskell
<!-- HTML結構 -->
<ol id="test-list">
<li class="lang">Scheme</li>
<li class="lang">JavaScript</li>
<li class="lang">Python</li>
<li class="lang">Ruby</li>
<li class="lang">Haskell</li>
</ol>
按字符串順序重新排序DOM節(jié)點:
'use strict';
//sort list
//第一種解法:利用insertBefore方法進行插入 較小的插入到較大的前面
var new_list = document.getElementById('test-list');
var new_list_child = document.getElementsByClassName('lang');
for(var i=0;i<new_list_child.length;i++){
for(var j = i+1;j<new_list_child.length;j++){
if(new_list_child[i].innerText > new_list_child[j].innerText){
new_list.insertBefore(new_list_child[j],new_list_child[i])
}
}
}
//第二種解法:通過 Array.protptype.slice.call().sort() 轉換數(shù)組 進行排序 forEach循環(huán)遍歷進行重新添加節(jié)點
var new_list = document.getElementById('test-list');
var new_list_child = new_list.children;
new_list_child = Array.prototype.slice.call(new_list_child).sort(function(a,b){
return a.innerHTML > b.innerHTML?1:-1;
});
new_list_child.forEach(function(el){
new_list.appendChild(el);
});
//注:代碼更新之后發(fā)現(xiàn) 存在只適用于同樣大小寫的情況,也就是說大寫之間進行排序碍讯,小寫之間進行排序褂删,如果混在一起的話,例如:AfBcdE 結果為ABEcdf 這個問題后面我會注意改過來重新追加
// 測試:
;(function () {
var
arr, i,
t = document.getElementById('test-list');
if (t && t.children && t.children.length === 5) {
arr = [];
for (i=0; i<t.children.length; i++) {
arr.push(t.children[i].innerText);
}
if (arr.toString() === ['Haskell', 'JavaScript', 'Python', 'Ruby', 'Scheme'].toString()) {
console.log('測試通過!');
}
else {
console.log('測試失敗: ' + arr.toString());
}
}
else {
console.log('測試失敗!');
}
})();
刪除DOM
刪除一個DOM節(jié)點就比插入要容易得多冲茸。
要刪除一個節(jié)點屯阀,首先要獲得該節(jié)點本身以及它的父節(jié)點,然后轴术,調(diào)用父節(jié)點的removeChild
把自己刪掉:
//拿到待刪除節(jié)點:
var self = document.getElementById('to-be-removed');
//拿到父節(jié)點:
var parent = self.parentElement;
// 刪除:
var removed = parent.removeChild(self);
removed === self; //true
注意到刪除后的節(jié)點雖然不在文檔樹中了难衰,但其實它還在內(nèi)存中,可以隨時再次被添加到別的位置逗栽。
當你遍歷一個父節(jié)點的子節(jié)點并進行刪除操作時盖袭,要注意,children
屬性是一個只讀屬性彼宠,并且它在子節(jié)點變化時會實時更新鳄虱。
例如,對于如下HTML結構:
<div id="parent">
<p>First</p>
<p>Second</p>
</div>
當我們用如下代碼刪除子節(jié)點時:
var parent = document.getElementById('parent');
parent.removeChild(parent.children[0]);
parent.removeChild(parent.children[1]); // <-- 瀏覽器報錯
瀏覽器報錯:parent.children[1]
不是一個有效的節(jié)點凭峡。原因就在于拙已,當<p>First</p>
節(jié)點被刪除后,parent.children
的節(jié)點數(shù)量已經(jīng)從2變?yōu)榱?摧冀,索引[1]
已經(jīng)不存在了倍踪。
因此,刪除多個節(jié)點時索昂,要注意children
屬性時刻都在變化建车。
練習
- JavaScript
- Swift
- HTML
- ANSI C
- CSS
- DirectX
<!-- HTML結構 -->
<ul id="test-list">
<li>JavaScript</li>
<li>Swift</li>
<li>HTML</li>
<li>ANSI C</li>
<li>CSS</li>
<li>DirectX</li>
</ul>
把與Web開發(fā)技術不相關的節(jié)點刪掉:
'use strict';
//答案1
var web = ["JavaScript", "HTML", "CSS"];
var ul = document.getElementById("test-list");
for (var li of ul.children) {
if (web.indexOf(li.innerText) === -1) {
ul.removeChild(li);
}
};
//答案2
var parent = document.getElementById('test-list');
var children = [].slice.call(parent.children); //Array.prototype.slice.call()
children.forEach((element) => {
for (var s of ['Swift', 'ANSI C', 'DirectX']) {
if (element.innerText == s) {
parent.removeChild(element);
}
}
});
// 測試:
;(function () {
var
arr, i,
t = document.getElementById('test-list');
if (t && t.children && t.children.length === 3) {
arr = [];
for (i = 0; i < t.children.length; i ++) {
arr.push(t.children[i].innerText);
}
if (arr.toString() === ['JavaScript', 'HTML', 'CSS'].toString()) {
console.log('測試通過!');
}
else {
console.log('測試失敗: ' + arr.toString());
}
}
else {
console.log('測試失敗!');
}
})();