JavaScript教程5 - 瀏覽器

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">&lt;script&gt;alert("Hi")&lt;/script&gt;</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在上面繪製各種圖表動畫等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章