JavaScript教程5
05 | 瀏覽器
瀏覽器對象
window
window對象不但充當全局作用域,而且表示瀏覽器窗口。
window對象有innerWidth和innerHeight屬性,可以獲取瀏覽器窗口的內部寬度和高度。內部寬高是指除去菜單欄、工具欄、邊框等佔位元素後,用於顯示網頁的淨寬高。內部寬高是指除去菜單欄、工具欄、邊框等佔位元素後,用於顯示網頁的淨寬高。
對應的,還有一個outerWidth和outerHeight屬性,可以獲取瀏覽器窗口的整個寬高。
navigator
navigator對象表示瀏覽器的信息,最常用的屬性包括:
- navigator.appName:瀏覽器名稱;
- navigator.appVersion:瀏覽器版本;
- navigator.language:瀏覽器設置的語言;
- navigator.platform:操作系統類型;
- navigator.userAgent:瀏覽器設定的User-Agent字符串。
screen
screen對象表示屏幕的信息,常用的屬性有:
- screen.width:屏幕寬度,以像素爲單位;
- screen.height:屏幕高度,以像素爲單位;
- screen.colorDepth:返回顏色位數,如8、16、24。
location
location對象表示當前頁面的URL信息。可以用location.href獲取
例如,一個完整的URL:http://www.example.com:8080/path/index.html?a=1&b=2#TOP
- 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’
要加載一個新頁面,可以調用location.assign()。如果要重新加載當前頁面,調用location.reload()方法非常方便。
document
document對象表示當前頁面。由於HTML在瀏覽器中以DOM形式表示爲樹形結構,document對象就是整個DOM樹的根節點。
用document對象提供的getElementById()和getElementsByTagName()可以按ID獲得一個DOM節點和按Tag名稱獲得一組DOM節點:
document對象還有一個cookie屬性,可以獲取當前頁面的Cookie。
document.cookie; // 'v=123; remember=true; prefer=zh'
問題:
由於JavaScript能讀取到頁面的Cookie,而用戶的登錄信息通常也存在Cookie中,這就造成了巨大的安全隱患,這是因爲在HTML頁面中引入第三方的JavaScript代碼是允許的:
解決:
爲了解決這個問題,服務器在設置Cookie時可以使用httpOnly,設定了httpOnly的Cookie將不能被JavaScript讀取。這個行爲由瀏覽器實現,主流瀏覽器均支持httpOnly選項,IE從IE6 SP1開始支持。
爲了確保安全,服務器端在設置Cookie時,應該始終堅持使用httpOnly。
history
history對象保存了瀏覽器的歷史記錄,JavaScript可以調用history對象的back()或forward (),相當於用戶點擊了瀏覽器的“後退”或“前進”按鈕。
這個對象屬於歷史遺留對象,對於現代Web頁面來說,由於大量使用AJAX和頁面交互,簡單粗暴地調用history.back()可能會讓用戶感到非常憤怒。
新手開始設計Web頁面時喜歡在登錄頁登錄成功時調用history.back(),試圖回到登錄前的頁面。這是一種錯誤的方法。
任何情況,你都不應該使用history這個對象了。
操作DOM
始終記住DOM是一個樹形結構。操作一個DOM節點實際上就是這麼幾個操作:
- 更新:更新該DOM節點的內容,相當於更新了該DOM節點表示的HTML的內容;
- 遍歷:遍歷該DOM節點下的子節點,以便進行進一步操作;
- 添加:在該DOM節點下新增一個子節點,相當於動態增加了一個HTML節點;
- 刪除:將該節點從HTML中刪除,相當於刪掉了該DOM節點的內容以及它包含的所有子節點。
在操作一個DOM節點前,我們需要通過各種方式先拿到這個DOM節點。最常用的方法是document.getElementById()和document.getElementsByTagName(),document.getElementsByClassName()以及CSS選擇器。
由於ID在HTML文檔中是唯一的,所以document.getElementById()可以直接定位唯一的一個DOM節點。document.getElementsByTagName()和document.getElementsByClassName()總是返回一組DOM節點。要精確地選擇DOM,可以先定位父節點,再從父節點開始選擇,以縮小範圍。
// 返回ID爲'test'的節點:
var test = document.getElementById('test');
// 先定位ID爲'test-table'的節點,再返回其內部所有tr節點:
var trs = document.getElementById('test-table').getElementsByTagName('tr');
// 先定位ID爲'test-div'的節點,再返回其內部所有class包含red的節點:
var reds = document.getElementById('test-div').getElementsByClassName('red');
// 獲取節點test下的所有直屬子節點:
var cs = test.children;
// 獲取節點test下第一個、最後一個子節點:
var first = test.firstElementChild;
var last = test.lastElementChild;
// 第二種方法是使用querySelector()和querySelectorAll(),需要了解selector語法,然後使用條件來獲取節點,更加方便:
// 通過querySelector獲取ID爲q1的節點:
var q1 = document.querySelector('#q1');
// 通過querySelectorAll獲取q1節點內的符合條件的所有節點:
var ps = q1.querySelectorAll('div.highlighted > p');
嚴格地講,我們這裏的DOM節點是指Element,但是DOM節點實際上是Node,在HTML中,Node包括Element、Comment、CDATA_SECTION等很多種,以及根節點Document類型,但是,絕大多數時候我們只關心Element,也就是實際控制頁面結構的Node,其他類型的Node忽略即可。根節點Document已經自動綁定爲全局變量document。
更新DOM
// 一種是修改innerHTML屬性,這個方式非常強大,不但可以修改一個DOM節點的文本內容,還可以直接通過HTML片段修改DOM節點內部的子樹:
// 獲取<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>的內部結構已修改
// 用innerHTML時要注意,是否需要寫入HTML。如果寫入的字符串是通過網絡拿到了,要注意對字符編碼來避免XSS攻擊。
// 第二種是修改innerText或textContent屬性,這樣可以自動對字符串進行HTML編碼,保證無法設置任何HTML標籤:
// 獲取<p id="p-id">...</p>
var p = document.getElementById('p-id');
// 設置文本:
p.innerText = '<script>alert("Hi")</script>';
// HTML被自動編碼,無法設置一個<script>節點:
// <p id="p-id"><script>alert("Hi")</script></p>
// 兩者的區別在於讀取屬性時,innerText不返回隱藏元素的文本,而textContent返回所有文本。另外注意IE<9不支持textContent。
// 修改CSS也是經常需要的操作。DOM節點的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';
插入DOM
知識點
- innerHTML: 這個DOM節點是空的,就可以修改DOM節點的內容;這個DOM節點不是空的,那就不能這麼做,因爲innerHTML會直接替換掉原來的所有子節點
- appendChild: 把一個子節點添加到父節點的最後一個子節點
- insertBefore: 可以使用parentElement.insertBefore(newElement, referenceElement);,子節點會插入到referenceElement之前。
練習
<!-- 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節點:
// sort list:
var ol = document.getElementById("test-list");
var list = ol.getElementsByTagName("li");
var arr = [];
var i;
for (i=0;i<list.length;i++){
arr.push(list[i].innerText);
}
arr.sort()
for (i=0;i<list.length;i++){
list[i].innerText = arr[i];
}
刪除DOM
- 要刪除一個節點,首先要獲得該節點本身以及它的父節點,然後,調用父節點的removeChild把自己刪掉:
- 因此,刪除多個節點時,要注意children屬性時刻都在變化。
操作表單
HTML表單的輸入控件
- 文本框,對應的,用於輸入文本;
- 口令框,對應的,用於輸入口令;
- 單選框,對應的,用於選擇一項;
- 複選框,對應的,用於選擇多項;
- 下拉框,對應的,用於選擇一項;
- 隱藏文本,對應的,用戶不可見,但表單提交時會把隱藏文本發送到服務器。
提交表單
方式一是通過元素的submit()方法提交一個表單,例如,響應一個的click事件,在JavaScript代碼中提交表單:
<!-- HTML -->
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
這種方式的缺點是擾亂了瀏覽器對form的正常提交。瀏覽器默認點擊時提交表單,或者用戶在最後一個輸入框按回車鍵。因此,第二種方式是響應本身的onsubmit事件,在提交form時作修改:
<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 繼續下一步:
return true;
}
</script>
// 注意要return true來告訴瀏覽器繼續提交,如果return false,瀏覽器將不會繼續提交form,這種情況通常對應用戶輸入有誤,提示用戶錯誤信息後終止提交form。
獲取值
// <input type="text" id="email">
var input = document.getElementById('email');
input.value; // '用戶輸入的值'
// 這種方式可以應用於text、password、hidden以及select。但是,對於單選框和複選框,value屬性返回的永遠是HTML預設的值,而我們需要獲得的實際是用戶是否“勾上了”選項,所以應該用checked判斷:
設置值
// 設置值和獲取值類似,對於text、password、hidden以及select,直接設置value就可以:
// <input type="text" id="email">
var input = document.getElementById('email');
input.value = '[email protected]'; // 文本框的內容已更新
// 對於單選框和複選框,設置checked爲true或false即可。
HTML5控件
// HTML5新增了大量標準控件,常用的包括date、datetime、datetime-local、color等,它們都使用<input>標籤:
<input type="date" value="2015-07-01">
<input type="datetime-local" value="2015-07-01T02:03:04">
<input type="color" value="#ff0000">
// 不支持HTML5的瀏覽器無法識別新的控件,會把它們當做type="text"來顯示。支持HTML5的瀏覽器將獲得格式化的字符串。例如,type="date"類型的input的value將保證是一個有效的YYYY-MM-DD格式的日期,或者空字符串。
提交表單
方式一是通過元素的submit()方法提交一個表單,例如,響應一個的click事件,在JavaScript代碼中提交表單:
<!-- HTML -->
<form id="test-form">
<input type="text" name="test">
<button type="button" onclick="doSubmitForm()">Submit</button>
</form>
<script>
function doSubmitForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 提交form:
form.submit();
}
</script>
這種方式的缺點是擾亂了瀏覽器對form的正常提交。瀏覽器默認點擊時提交表單,或者用戶在最後一個輸入框按回車鍵。
因此,第二種方式是響應本身的onsubmit事件,在提交form時作修改:
<!-- HTML -->
<form id="test-form" onsubmit="return checkForm()">
<input type="text" name="test">
<button type="submit">Submit</button>
</form>
<script>
function checkForm() {
var form = document.getElementById('test-form');
// 可以在此修改form的input...
// 繼續下一步:
return true;
}
</script>
注意要return true來告訴瀏覽器繼續提交,如果return false,瀏覽器將不會繼續提交form,這種情況通常對應用戶輸入有誤,提示用戶錯誤信息後終止提交form。
在檢查和修改時,要充分利用來傳遞數據。
例如,很多登錄表單希望用戶輸入用戶名和口令,但是,安全考慮,提交表單時不傳輸明文口令,而是口令的MD5。
操作文件
在HTML表單中,可以上傳文件的唯一控件就是。
HTML5的File API提供了File和FileReader兩個主要對象,可以獲得文件信息並讀取文件。
回調
AJAX
在現代瀏覽器上寫AJAX主要依靠XMLHttpRequest對象:
對於低版本的IE,需要換一個ActiveXObject對象:
function success(text) {
var textarea = document.getElementById('test-ie-response-text');
textarea.value = text;
}
function fail(code) {
var textarea = document.getElementById('test-ie-response-text');
textarea.value = 'Error code: ' + code;
}
var request;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest(); // 新建XMLHttpRequest對象
} else {
request = new ActiveXObject('Microsoft.XMLHTTP');// 新建Microsoft.XMLHTTP對象
}
request.onreadystatechange = function () { // 狀態發生變化時,函數被回調
if (request.readyState === 4) { // 成功完成
// 判斷響應結果:
if (request.status === 200) {
// 成功,通過responseText拿到響應的文本:
return success(request.responseText);
} else {
// 失敗,根據響應碼判斷失敗原因:
return fail(request.status);
}
} else {
// HTTP請求還在繼續...
}
}
// 發送請求:
request.open('GET', '/api/categories');
request.send();
alert('請求已發送,請等待響應...');
安全限制
這是因爲瀏覽器的同源策略導致的。默認情況下,JavaScript在發送AJAX請求時,URL的域名必須和當前頁面完全一致。
解決:
- 一是通過Flash插件發送HTTP請求,這種方式可以繞過瀏覽器的安全限制,但必須安裝Flash,並且跟Flash交互。不過Flash用起來麻煩,而且現在用得也越來越少了。
- 二是通過在同源域名下架設一個代理服務器來轉發,JavaScript負責把請求發送到代理服務器:’/proxy?url=http://www.sina.com.cn’ ,代理服務器再把結果返回,這樣就遵守了瀏覽器的同源策略。這種方式麻煩之處在於需要服務器端額外做開發。
- 第三種方式稱爲JSONP,它有個限制,只能用GET請求,並且要求返回JavaScript。這種方式跨域實際上是利用了瀏覽器允許跨域引用JavaScript資源:
CORS
CORS全稱Cross-Origin Resource Sharing,是HTML5規範定義的如何跨域訪問資源。
Origin表示本域,也就是瀏覽器當前頁面的域。當JavaScript向外域(如sina.com)發起請求後,瀏覽器收到響應後,首先檢查Access-Control-Allow-Origin是否包含本域,如果是,則此次跨域請求成功,如果不是,則請求失敗,JavaScript將無法獲取到響應的任何數據。
Promise
在JavaScript的世界中,所有代碼都是單線程執行的。
由於這個“缺陷”,導致JavaScript的所有網絡操作,瀏覽器事件,都必須是異步執行。異步執行可以用回調函數實現:
異步操作會在將來的某個時間點觸發一個函數調用。
AJAX就是典型的異步操作
鏈式寫法
古人云:“君子一諾千金”,這種“承諾將來會執行”的對象在JavaScript中稱爲Promise對象。
應用
-
過程與結果分離
可見Promise最大的好處是在異步執行的流程中,把執行代碼和處理結果的代碼清晰地分離了:
-
Promise可以串行執行異步任務
Promise還可以做更多的事情,比如,有若干個異步任務,需要先做任務1,如果成功後再做任務2,任何任務失敗則不再繼續並執行錯誤處理函數。
job1.then(job2).then(job3).catch(handleError);
// 其中,job1、job2和job3都是Promise對象。
- Promise還可以並行執行異步任務
// 試想一個頁面聊天系統,我們需要從兩個不同的URL分別獲得用戶的個人信息和好友列表,這兩個任務是可以並行執行的,用Promise.all()實現如下:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
// 同時執行p1和p2,並在它們都完成後執行then:
Promise.all([p1, p2]).then(function (results) {
console.log(results); // 獲得一個Array: ['P1', 'P2']
});
- 有些時候,多個異步任務是爲了容錯。
// 比如,同時向兩個URL讀取用戶的個人信息,只需要獲得先返回的結果即可。這種情況下,用Promise.race()實現:
var p1 = new Promise(function (resolve, reject) {
setTimeout(resolve, 500, 'P1');
});
var p2 = new Promise(function (resolve, reject) {
setTimeout(resolve, 600, 'P2');
});
Promise.race([p1, p2]).then(function (result) {
console.log(result); // 'P1'
});
Canvas
Canvas是HTML5新增的組件,它就像一塊幕布,可以用JavaScript在上面繪製各種圖表、動畫等。