JS-Web-API 知識點與高頻考題解析

JS-Web-API 知識點與高頻考題解析

除 ES 基礎之外,Web 前端經常會用到一些跟瀏覽器相關的 API,接下來我們一起梳理一下。

知識點梳理

  • BOM 操作
  • DOM 操作
  • 事件綁定
  • Ajax
  • 存儲

BOM

BOM(瀏覽器對象模型)是瀏覽器本身的一些信息的設置和獲取,例如獲取瀏覽器的寬度、高度,設置讓瀏覽器跳轉到哪個地址。

  • navigator
  • screen
  • location
  • history

這些對象就是一堆非常簡單粗暴的 API,沒任何技術含量,講起來一點意思都沒有,大家去 MDN 或者 w3school 這種網站一查就都明白了。面試的時候,面試官基本不會出太多這方面的題目,因爲只要基礎知識過關了,這些 API 即便你記不住,上網一查也都知道了。下面列舉一下常用功能的代碼示例

獲取瀏覽器特性(即俗稱的UA)然後識別客戶端,例如判斷是不是 Chrome 瀏覽器

var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)

獲取屏幕的寬度和高度

console.log(screen.width)
console.log(screen.height)

獲取網址、協議、path、參數、hash 等

// 例如當前網址是 https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.href)  // https://juejin.im/timeline/frontend?a=10&b=10#some
console.log(location.protocol) // https:
console.log(location.pathname) // /timeline/frontend
console.log(location.search) // ?a=10&b=10
console.log(location.hash) // #some

另外,還有調用瀏覽器的前進、後退功能等

history.back()
history.forward()


DOM

題目:DOM 和 HTML 區別和聯繫

什麼是 DOM

講 DOM 先從 HTML 講起,講 HTML 先從 XML 講起。XML 是一種可擴展的標記語言,所謂可擴展就是它可以描述任何結構化的數據,它是一棵樹!

<?xml version="1.0" encoding="UTF-8"?>
<note>
  <to>Tove</to>
  <from>Jani</from>
  <heading>Reminder</heading>
  <body>Don't forget me this weekend!</body>
  <other>
    <a></a>
    <b></b>
  </other>
</note>

HTML 是一個有既定標籤標準的 XML 格式,標籤的名字、層級關係和屬性,都被標準化(否則瀏覽器無法解析)。同樣,它也是一棵樹。

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div>
        <p>this is p</p>
    </div>
</body>
</html>

我們開發完的 HTML 代碼會保存到一個文檔中(一般以.html或者.htm結尾),文檔放在服務器上,瀏覽器請求服務器,這個文檔被返回。因此,最終瀏覽器拿到的是一個文檔而已,文檔的內容就是 HTML 格式的代碼。

但是瀏覽器要把這個文檔中的 HTML 按照標準渲染成一個頁面,此時瀏覽器就需要將這堆代碼處理成自己能理解的東西,也得處理成 JS 能理解的東西,因爲還得允許 JS 修改頁面內容呢。

基於以上需求,瀏覽器就需要把 HTML 轉變成 DOM,HTML 是一棵樹,DOM 也是一棵樹。對 DOM 的理解,可以暫時先拋開瀏覽器的內部因素,先從 JS 着手,即可以認爲 DOM 就是 JS 能識別的 HTML 結構,一個普通的 JS 對象或者數組。

DOM圖示

獲取 DOM 節點

最常用的 DOM API 就是獲取節點,其中常用的獲取方法如下面代碼示例:

// 通過 id 獲取
var div1 = document.getElementById('div1') // 元素

// 通過 tagname 獲取
var divList = document.getElementsByTagName('div')  // 集合
console.log(divList.length)
console.log(divList[0])

// 通過 class 獲取
var containerList = document.getElementsByClassName('container') // 集合

// 通過 CSS 選擇器獲取
var pList = document.querySelectorAll('p') // 集合

題目:property 和 attribute 的區別是什麼?

property

DOM 節點就是一個 JS 對象,它符合之前講述的對象的特徵 —— 可擴展屬性,因爲 DOM 節點本質上也是一個 JS 對象。因此,如下代碼所示,p可以有style屬性,有className nodeName nodeType屬性。注意,這些都是 JS 範疇的屬性,符合 JS 語法標準的

var pList = document.querySelectorAll('p')
var p = pList[0]
console.log(p.style.width)  // 獲取樣式
p.style.width = '100px'  // 修改樣式
console.log(p.className)  // 獲取 class
p.className = 'p1'  // 修改 class

// 獲取 nodeName 和 nodeType
console.log(p.nodeName)
console.log(p.nodeType)

attribute

property 的獲取和修改,是直接改變 JS 對象,而 attribute 是直接改變 HTML 的屬性,兩種有很大的區別。attribute 就是對 HTML 屬性的 get 和 set,和 DOM 節點的 JS 範疇的 property 沒有關係。

var pList = document.querySelectorAll('p')
var p = pList[0]
p.getAttribute('data-name')
p.setAttribute('data-name', 'juejin')
p.getAttribute('style')
p.setAttribute('style', 'font-size:30px;')

而且,get 和 set attribute 時,還會觸發 DOM 的查詢或者重繪、重排,頻繁操作會影響頁面性能。

題目:DOM 操作的基本 API 有哪些?

DOM 樹操作

新增節點

var div1 = document.getElementById('div1')

// 添加新節點
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1) // 添加新創建的元素

// 移動已有節點。注意,這裏是“移動”,並不是拷貝
var p2 = document.getElementById('p2')
div1.appendChild(p2)

獲取父元素

var div1 = document.getElementById('div1')
var parent = div1.parentElement

獲取子元素

var div1 = document.getElementById('div1')
var child = div1.childNodes

刪除節點

var div1 = document.getElementById('div1')
var child = div1.childNodes
div1.removeChild(child[0])

還有其他操作的API,例如獲取前一個節點、獲取後一個節點等,但是面試過程中經常考到的就是上面幾個。


事件

事件綁定

普通的事件綁定寫法如下:

var btn = document.getElementById('btn1')
btn.addEventListener('click', function (event) {
    // event.preventDefault() // 阻止默認行爲
    // event.stopPropagation() // 阻止冒泡
    console.log('clicked')
})

爲了編寫簡單的事件綁定,可以編寫通用的事件綁定函數。這裏雖然比較簡單,但是會隨着後文的講解,來繼續完善和豐富這個函數。

// 通用的事件綁定函數
function bindEvent(elem, type, fn) {
    elem.addEventListener(type, fn)
}
var a = document.getElementById('link1')
// 寫起來更加簡單了
bindEvent(a, 'click', function(e) {
    e.preventDefault() // 阻止默認行爲
    alert('clicked')
})

最後,如果面試被問到 IE 低版本兼容性問題,我勸你果斷放棄這份工作機會。現在互聯網流量都在 App 上, IE 佔比越來越少,再去爲 IE 浪費青春不值得,要儘量去做 App 相關的工作。

題目:什麼是事件冒泡?

事件冒泡

<body>
    <div id="div1">
        <p id="p1">激活</p>
        <p id="p2">取消</p>
        <p id="p3">取消</p>
        <p id="p4">取消</p>
    </div>
    <div id="div2">
        <p id="p5">取消</p>
        <p id="p6">取消</p>
    </div>
</body>

對於以上 HTML 代碼結構,要求點擊p1時候進入激活狀態,點擊其他任何<p>都取消激活狀態,如何實現?代碼如下,注意看註釋:

var body = document.body
bindEvent(body, 'click', function (e) {
    // 所有 p 的點擊都會冒泡到 body 上,因爲 DOM 結構中 body 是 p 的上級節點,事件會沿着 DOM 樹向上冒泡
    alert('取消')
})

var p1 = document.getElementById('p1')
bindEvent(p1, 'click', function (e) {
    e.stopPropagation() // 阻止冒泡
    alert('激活')
})


如果我們在p1 div1 body中都綁定了事件,它是會根據 DOM 的結構來冒泡,從下到上挨個執行的。但是我們使用e.stopPropagation()就可以阻止冒泡

題目:如何使用事件代理?有何好處?

事件代理

我們設定一種場景,如下代碼,一個<div>中包含了若干個<a>,而且還能繼續增加。那如何快捷方便地爲所有<a>綁定事件呢?

<div id="div1">
    <a href="#">a1</a>
    <a href="#">a2</a>
    <a href="#">a3</a>
    <a href="#">a4</a>
</div>
<button>點擊增加一個 a 標籤</button>

這裏就會用到事件代理。我們要監聽<a>的事件,但要把具體的事件綁定到<div>上,然後看事件的觸發點是不是<a>

var div1 = document.getElementById('div1')
div1.addEventListener('click', function (e) {
    // e.target 可以監聽到觸發點擊事件的元素是哪一個
    var target = e.target
    if (e.nodeName === 'A') {
        // 點擊的是 <a> 元素
        alert(target.innerHTML)
    }
})

我們現在完善一下之前寫的通用事件綁定函數,加上事件代理。

function bindEvent(elem, type, selector, fn) {
    // 這樣處理,可接收兩種調用方式 bindEvent(div1, 'click', 'a', function () {...}) 和 bindEvent(div1, 'click', function () {...}) 這兩種
    if (fn == null) {
        fn = selector
        selector = null
    }

    // 綁定事件
    elem.addEventListener(type, function (e) {
        var target
        if (selector) {
            // 有 selector 說明需要做事件代理
            // 獲取觸發時間的元素,即 e.target
            target = e.target
            // 看是否符合 selector 這個條件
            if (target.matches(selector)) {
                fn.call(target, e)
            }
        } else {
            // 無 selector ,說明不需要事件代理
            fn(e)
        }
    })
}

然後這樣使用,簡單很多。

// 使用代理,bindEvent 多一個 'a' 參數
var div1 = document.getElementById('div1')
bindEvent(div1, 'click', 'a', function (e) {
    console.log(this.innerHTML)
})

// 不使用代理
var a = document.getElementById('a1')
bindEvent(div1, 'click', function (e) {
    console.log(a.innerHTML)
})

最後,使用代理的優點如下:

  • 使代碼簡潔
  • 減少瀏覽器的內存佔用

Ajax

XMLHttpRequest

題目:手寫 XMLHttpRequest 不借助任何庫

這是很多奇葩的、個性的面試官經常用的手段。這種考查方式存在很多爭議,但是你不能完全說它是錯誤的,畢竟也是考查對最基礎知識的掌握情況。

var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
    // 這裏的函數異步執行,可參考之前 JS 基礎中的異步模塊
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            alert(xhr.responseText)
        }
    }
}
xhr.open("GET", "/api", false)
xhr.send(null)

當然,使用 jQuery、Zepto 或 Fetch 等庫來寫就更加簡單了,這裏不再贅述。

狀態碼說明

上述代碼中,有兩處狀態碼需要說明。xhr.readyState是瀏覽器判斷請求過程中各個階段的,xhr.status是 HTTP 協議中規定的不同結果的返回狀態說明。

xhr.readyState的狀態碼說明:

  • 0 -代理被創建,但尚未調用 open() 方法。
  • 1 -open() 方法已經被調用。
  • 2 -send() 方法已經被調用,並且頭部和狀態已經可獲得。
  • 3 -下載中, responseText 屬性已經包含部分數據。
  • 4 -下載操作已完成

題目:HTTP 協議中,response 的狀態碼,常見的有哪些?

xhr.status即 HTTP 狀態碼,有 2xx 3xx 4xx 5xx 這幾種,比較常用的有以下幾種:

  • 200 正常
  • 3xx
    • 301 永久重定向。如http://xxx.com這個 GET 請求(最後沒有/),就會被301http://xxx.com/(最後是/
    • 302 臨時重定向。臨時的,不是永久的
    • 304 資源找到但是不符合請求條件,不會返回任何主體。如發送 GET 請求時,head 中有If-Modified-Since: xxx(要求返回更新時間是xxx時間之後的資源),如果此時服務器 端資源未更新,則會返回304,即不符合要求
  • 404 找不到資源
  • 5xx 服務器端出錯了

看完要明白,爲何上述代碼中要同時滿足xhr.readyState == 4xhr.status == 200

Fetch API

目前已經有一個獲取 HTTP 請求更加方便的 API:Fetch,通過Fetch提供的fetch()這個全局函數方法可以很簡單地發起異步請求,並且支持Promise的回調。但是 Fetch API 是比較新的 API,具體使用的時候還需要查查 caniuse,看下其瀏覽器兼容情況。

看一個簡單的例子:

fetch('some/api/data.json', {
  method:'POST', //請求類型 GET、POST
  headers:{}, // 請求的頭信息,形式爲 Headers 對象或 ByteString
  body:{}, //請求發送的數據 blob、BufferSource、FormData、URLSearchParams(get 或head 方法中不能包含 body)
  mode:'', //請求的模式,是否跨域等,如 cors、 no-cors 或 same-origin
  credentials:'', //cookie 的跨域策略,如 omit、same-origin 或 include
  cache:'', //請求的 cache 模式: default、no-store、reload、no-cache、 force-cache 或 only-if-cached
}).then(function(response) { ... });

Fetch 支持headers定義,通過headers自定義可以方便地實現多種請求方法( PUT、GET、POST 等)、請求頭(包括跨域)和cache策略等;除此之外還支持 response(返回數據)多種類型,比如支持二進制文件、字符串和formData等。

跨域

題目:如何實現跨域?

瀏覽器中有 同源策略 ,即一個域下的頁面中,無法通過 Ajax 獲取到其他域的接口。例如有一個接口http://m.juejin.com/course/ajaxcourserecom?cid=459,你自己的一個頁面http://www.yourname.com/page1.html中的 Ajax 無法獲取這個接口。這正是命中了“同源策略”。如果瀏覽器哪些地方忽略了同源策略,那就是瀏覽器的安全漏洞,需要緊急修復。

url 哪些地方不同算作跨域?

  • 協議
  • 域名
  • 端口

但是 HTML 中幾個標籤能逃避過同源策略——<script src="xxx"><img src="xxxx"/><link href="xxxx">,這三個標籤的src/href可以加載其他域的資源,不受同源策略限制。

因此,這使得這三個標籤可以做一些特殊的事情。

  • <img>可以做打點統計,因爲統計方並不一定是同域的,在講解 JS 基礎知識異步的時候有過代碼示例。除了能跨域之外,<img>幾乎沒有瀏覽器兼容問題,它是一個非常古老的標籤。
  • <script><link>可以使用 CDN,CDN 基本都是其他域的鏈接。
  • 另外<script>還可以實現 JSONP,能獲取其他域接口的信息,接下來馬上講解。

但是請注意,所有的跨域請求方式,最終都需要信息提供方來做出相應的支持和改動,也就是要經過信息提供方的同意才行,否則接收方是無法得到它們的信息的,瀏覽器是不允許的。

解決跨域 - JSONP

首先,有一個概念你要明白,例如訪問http://coding.m.juejin.com/classindex.html的時候,服務器端就一定有一個classindex.html文件嗎?—— 不一定,服務器可以拿到這個請求,動態生成一個文件,然後返回。 同理,<script src="http://coding.m.juejin.com/api.js">也不一定加載一個服務器端的靜態文件,服務器也可以動態生成文件並返回。OK,接下來正式開始。

例如我們的網站和掘金網,肯定不是一個域。我們需要掘金網提供一個接口,供我們來獲取。首先,我們在自己的頁面這樣定義

<script>
window.callback = function (data) {
    // 這是我們跨域得到信息
    console.log(data)
}
</script>

然後掘金網給我提供了一個http://coding.m.juejin.com/api.js,內容如下(之前說過,服務器可動態生成內容)

callback({x:100, y:200})

最後我們在頁面中加入<script src="http://coding.m.juejin.com/api.js"></script>,那麼這個js加載之後,就會執行內容,我們就得到內容了。

解決跨域 - 服務器端設置 http header

這是需要在服務器端設置的,作爲前端工程師我們不用詳細掌握,但是要知道有這麼個解決方案。而且,現在推崇的跨域解決方案是這一種,比 JSONP 簡單許多。

response.setHeader("Access-Control-Allow-Origin", "http://m.juejin.com/");  // 第二個參數填寫允許跨域的域名稱,不建議直接寫 "*"
response.setHeader("Access-Control-Allow-Headers", "X-Requested-With");
response.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");

// 接收跨域的cookie
response.setHeader("Access-Control-Allow-Credentials", "true");


存儲

題目:cookie 和 localStorage 有何區別?

cookie

cookie 本身不是用來做服務器端存儲的(計算機領域有很多這種“狗拿耗子”的例子,例如 CSS 中的 float),它是設計用來在服務器和客戶端進行信息傳遞的,因此我們的每個 HTTP 請求都帶着 cookie。但是 cookie 也具備瀏覽器端存儲的能力(例如記住用戶名和密碼),因此就被開發者用上了。

使用起來也非常簡單,document.cookie = ....即可。

但是 cookie 有它致命的缺點:

  • 存儲量太小,只有 4KB
  • 所有 HTTP 請求都帶着,會影響獲取資源的效率
  • API 簡單,需要封裝才能用

localStorage 和 sessionStorage

後來,HTML5 標準就帶來了sessionStoragelocalStorage,先拿localStorage來說,它是專門爲了瀏覽器端緩存而設計的。其優點有:

  • 存儲量增大到 5MB
  • 不會帶到 HTTP 請求中
  • API 適用於數據存儲 localStorage.setItem(key, value) localStorage.getItem(key)

sessionStorage的區別就在於它是根據 session 過去時間而實現,而localStorage會永久有效,應用場景不同。例如,一些需要及時失效的重要信息放在sessionStorage中,一些不重要但是不經常設置的信息,放在localStorage中。

另外告訴大家一個小技巧,針對localStorage.setItem,使用時儘量加入到try-catch中,某些瀏覽器是禁用這個 API 的,要注意。


小結

本小節總結了 W3C 標準中 Web-API 部分,面試中常考的知識點,這些也是日常開發中最常用的 API 和知識。

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