知識點梳理
- BOM 操作
- DOM 操作
- 事件綁定
- Ajax
- 存儲
BOM
BOM(瀏覽器對象模型)是瀏覽器本身的一些信息的設(shè)置和獲取,例如獲取瀏覽器的寬度、高度,設(shè)置讓瀏覽器跳轉(zhuǎn)到哪個地址。
navigator
screen
location
history
這些對象就是一堆非常簡單粗暴的 API,沒任何技術(shù)含量,講起來一點意思都沒有,大家去 MDN 或者 w3school 這種網(wǎng)站一查就都明白了。面試的時候,面試官基本不會出太多這方面的題目,因為只要基礎(chǔ)知識過關(guān)了,這些 API 即便你記不住,上網(wǎng)一查也都知道了。下面列舉一下常用功能的代碼示例
獲取瀏覽器特性(即俗稱的UA
)然后識別客戶端,例如判斷是不是 Chrome 瀏覽器
var ua = navigator.userAgent
var isChrome = ua.indexOf('Chrome')
console.log(isChrome)
獲取屏幕的寬度和高度
console.log(screen.width)
console.log(screen.height)
獲取網(wǎng)址、協(xié)議、path、參數(shù)、hash 等
// 例如當(dāng)前網(wǎng)址是 https://juejin.cn/timeline/frontend?a=10&b=10#some
console.log(location.href) // https://juejin.cn/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
另外,還有調(diào)用瀏覽器的前進、后退功能等
history.back()
history.forward()
DOM
題目:DOM 和 HTML 區(qū)別和聯(lián)系
什么是 DOM
講 DOM 先從 HTML 講起,講 HTML 先從 XML 講起。XML 是一種可擴展的標(biāo)記語言,所謂可擴展就是它可以描述任何結(jié)構(gòu)化的數(shù)據(jù),它是一棵樹!
<?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 是一個有既定標(biāo)簽標(biāo)準(zhǔn)的 XML 格式,標(biāo)簽的名字、層級關(guān)系和屬性,都被標(biāo)準(zhǔn)化(否則瀏覽器無法解析)。同樣,它也是一棵樹。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<p>this is p</p>
</div>
</body>
</html>
我們開發(fā)完的 HTML 代碼會保存到一個文檔中(一般以.html
或者.htm
結(jié)尾),文檔放在服務(wù)器上,瀏覽器請求服務(wù)器,這個文檔被返回。因此,最終瀏覽器拿到的是一個文檔而已,文檔的內(nèi)容就是 HTML 格式的代碼。
但是瀏覽器要把這個文檔中的 HTML 按照標(biāo)準(zhǔn)渲染成一個頁面,此時瀏覽器就需要將這堆代碼處理成自己能理解的東西,也得處理成 JS 能理解的東西,因為還得允許 JS 修改頁面內(nèi)容呢。
基于以上需求,瀏覽器就需要把 HTML 轉(zhuǎn)變成 DOM,HTML 是一棵樹,DOM 也是一棵樹。對 DOM 的理解,可以暫時先拋開瀏覽器的內(nèi)部因素,先從 JS 著手,即可以認為 DOM 就是 JS 能識別的 HTML 結(jié)構(gòu),一個普通的 JS 對象或者數(shù)組。
獲取 DOM 節(jié)點
最常用的 DOM API 就是獲取節(jié)點,其中常用的獲取方法如下面代碼示例:
// 通過 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 的區(qū)別是什么?
property
DOM 節(jié)點就是一個 JS 對象,它符合之前講述的對象的特征 —— 可擴展屬性,因為 DOM 節(jié)點本質(zhì)上也是一個 JS 對象。因此,如下代碼所示,p
可以有style
屬性,有className
nodeName
nodeType
屬性。注意,這些都是 JS 范疇的屬性,符合 JS 語法標(biāo)準(zhǔn)的。
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 的屬性,兩種有很大的區(qū)別。attribute 就是對 HTML 屬性的 get 和 set,和 DOM 節(jié)點的 JS 范疇的 property 沒有關(guān)系。
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 時,還會觸發(fā) DOM 的查詢或者重繪、重排,頻繁操作會影響頁面性能。
題目:DOM 操作的基本 API 有哪些?
DOM 樹操作
新增節(jié)點
var div1 = document.getElementById('div1')
// 添加新節(jié)點
var p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1) // 添加新創(chuàng)建的元素
// 移動已有節(jié)點。注意,這里是“移動”,并不是拷貝
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
刪除節(jié)點
var div1 = document.getElementById('div1')
var child = div1.childNodes
div1.removeChild(child[0])
還有其他操作的API,例如獲取前一個節(jié)點、獲取后一個節(jié)點等,但是面試過程中經(jīng)常考到的就是上面幾個。
事件
事件綁定
普通的事件綁定寫法如下:
var btn = document.getElementById('btn1')
btn.addEventListener('click', function (event) {
// event.preventDefault() // 阻止默認行為
// event.stopPropagation() // 阻止冒泡
console.log('clicked')
})
為了編寫簡單的事件綁定,可以編寫通用的事件綁定函數(shù)。這里雖然比較簡單,但是會隨著后文的講解,來繼續(xù)完善和豐富這個函數(shù)。
// 通用的事件綁定函數(shù)
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
var a = document.getElementById('link1')
// 寫起來更加簡單了
bindEvent(a, 'click', function(e) {
e.preventDefault() // 阻止默認行為
alert('clicked')
})
最后,如果面試被問到 IE 低版本兼容性問題,我勸你果斷放棄這份工作機會?,F(xiàn)在互聯(lián)網(wǎng)流量都在 App 上, IE 占比越來越少,再去為 IE 浪費青春不值得,要盡量去做 App 相關(guān)的工作。
題目:什么是事件冒泡?
事件冒泡
<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 代碼結(jié)構(gòu),要求點擊p1
時候進入激活狀態(tài),點擊其他任何<p>
都取消激活狀態(tài),如何實現(xiàn)?代碼如下,注意看注釋:
var body = document.body
bindEvent(body, 'click', function (e) {
// 所有 p 的點擊都會冒泡到 body 上,因為 DOM 結(jié)構(gòu)中 body 是 p 的上級節(jié)點,事件會沿著 DOM 樹向上冒泡
alert('取消')
})
var p1 = document.getElementById('p1')
bindEvent(p1, 'click', function (e) {
e.stopPropagation() // 阻止冒泡
alert('激活')
})
如果我們在p1
div1
body
中都綁定了事件,它是會根據(jù) DOM 的結(jié)構(gòu)來冒泡,從下到上挨個執(zhí)行的。但是我們使用e.stopPropagation()
就可以阻止冒泡
題目:如何使用事件代理?有何好處?
事件代理
我們設(shè)定一種場景,如下代碼,一個<div>
中包含了若干個<a>
,而且還能繼續(xù)增加。那如何快捷方便地為所有<a>
綁定事件呢?
<div id="div1">
<a href="#">a1</a>
<a href="#">a2</a>
<a href="#">a3</a>
<a href="#">a4</a>
</div>
<button>點擊增加一個 a 標(biāo)簽</button>
這里就會用到事件代理。我們要監(jiān)聽<a>
的事件,但要把具體的事件綁定到<div>
上,然后看事件的觸發(fā)點是不是<a>
。
var div1 = document.getElementById('div1')
div1.addEventListener('click', function (e) {
// e.target 可以監(jiān)聽到觸發(fā)點擊事件的元素是哪一個
var target = e.target
if (e.nodeName === 'A') {
// 點擊的是 <a> 元素
alert(target.innerHTML)
}
})
我們現(xiàn)在完善一下之前寫的通用事件綁定函數(shù),加上事件代理。
function bindEvent(elem, type, selector, fn) {
// 這樣處理,可接收兩種調(diào)用方式 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 說明需要做事件代理
// 獲取觸發(fā)時間的元素,即 e.target
target = e.target
// 看是否符合 selector 這個條件
if (target.matches(selector)) {
fn.call(target, e)
}
} else {
// 無 selector ,說明不需要事件代理
fn(e)
}
})
}
然后這樣使用,簡單很多。
// 使用代理,bindEvent 多一個 'a' 參數(shù)
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)
})
最后,使用代理的優(yōu)點如下:
- 使代碼簡潔
- 減少瀏覽器的內(nèi)存占用
Ajax
XMLHttpRequest
題目:手寫 XMLHttpRequest 不借助任何庫
這是很多奇葩的、個性的面試官經(jīng)常用的手段。這種考查方式存在很多爭議,但是你不能完全說它是錯誤的,畢竟也是考查對最基礎(chǔ)知識的掌握情況。
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function () {
// 這里的函數(shù)異步執(zhí)行,可參考之前 JS 基礎(chǔ)中的異步模塊
if (xhr.readyState == 4) {
if (xhr.status == 200) {
alert(xhr.responseText)
}
}
}
xhr.open("GET", "/api", false)
xhr.send(null)
當(dāng)然,使用 jQuery、Zepto 或 Fetch 等庫來寫就更加簡單了,這里不再贅述。
狀態(tài)碼說明
上述代碼中,有兩處狀態(tài)碼需要說明。xhr.readyState
是瀏覽器判斷請求過程中各個階段的,xhr.status
是 HTTP 協(xié)議中規(guī)定的不同結(jié)果的返回狀態(tài)說明。
xhr.readyState
的狀態(tài)碼說明:
- 0 -代理被創(chuàng)建,但尚未調(diào)用
open()
方法。 - 1 -
open()
方法已經(jīng)被調(diào)用。 - 2 -
send()
方法已經(jīng)被調(diào)用,并且頭部和狀態(tài)已經(jīng)可獲得。 - 3 -下載中,
responseText
屬性已經(jīng)包含部分數(shù)據(jù)。 - 4 -下載操作已完成
題目:HTTP 協(xié)議中,response 的狀態(tài)碼,常見的有哪些?
xhr.status
即 HTTP 狀態(tài)碼,有 2xx
3xx
4xx
5xx
這幾種,比較常用的有以下幾種:
-
200
正常 -
3xx
-
301
永久重定向。如http://xxx.com
這個 GET 請求(最后沒有/
),就會被301
到http://xxx.com/
(最后是/
) -
302
臨時重定向。臨時的,不是永久的 -
304
資源找到但是不符合請求條件,不會返回任何主體。如發(fā)送 GET 請求時,head 中有If-Modified-Since: xxx
(要求返回更新時間是xxx
時間之后的資源),如果此時服務(wù)器 端資源未更新,則會返回304
,即不符合要求
-
-
404
找不到資源 -
5xx
服務(wù)器端出錯了
看完要明白,為何上述代碼中要同時滿足xhr.readyState == 4
和xhr.status == 200
。
Fetch API
目前已經(jīng)有一個獲取 HTTP 請求更加方便的 API:Fetch
,通過Fetch
提供的fetch()
這個全局函數(shù)方法可以很簡單地發(fā)起異步請求,并且支持Promise
的回調(diào)。但是 Fetch API 是比較新的 API,具體使用的時候還需要查查 caniuse,看下其瀏覽器兼容情況。
看一個簡單的例子:
fetch('some/api/data.json', {
method:'POST', //請求類型 GET、POST
headers:{}, // 請求的頭信息,形式為 Headers 對象或 ByteString
body:{}, //請求發(fā)送的數(shù)據(jù) 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
自定義可以方便地實現(xiàn)多種請求方法( PUT、GET、POST 等)、請求頭(包括跨域)和cache
策略等;除此之外還支持 response(返回數(shù)據(jù))多種類型,比如支持二進制文件、字符串和formData
等。
跨域
題目:如何實現(xiàn)跨域?
瀏覽器中有 同源策略 ,即一個域下的頁面中,無法通過 Ajax 獲取到其他域的接口。例如有一個接口http://m.juejin.com/course/ajaxcourserecom?cid=459
,你自己的一個頁面http://www.yourname.com/page1.html
中的 Ajax 無法獲取這個接口。這正是命中了“同源策略”。如果瀏覽器哪些地方忽略了同源策略,那就是瀏覽器的安全漏洞,需要緊急修復(fù)。
url 哪些地方不同算作跨域?
- 協(xié)議
- 域名
- 端口
但是 HTML 中幾個標(biāo)簽?zāi)芴颖苓^同源策略——<script src="http://www.lilnong.top/urlTrans?group=juejin&url=xxx">
、<img src="http://www.lilnong.top/urlTrans?group=juejin&url=xxxx"/>
、<link href="xxxx">
,這三個標(biāo)簽的src/href
可以加載其他域的資源,不受同源策略限制。
因此,這使得這三個標(biāo)簽可以做一些特殊的事情。
-
<img>
可以做打點統(tǒng)計,因為統(tǒng)計方并不一定是同域的,在講解 JS 基礎(chǔ)知識異步的時候有過代碼示例。除了能跨域之外,<img>
幾乎沒有瀏覽器兼容問題,它是一個非常古老的標(biāo)簽。 -
<script>
和<link>
可以使用 CDN,CDN 基本都是其他域的鏈接。 - 另外
<script>
還可以實現(xiàn) JSONP,能獲取其他域接口的信息,接下來馬上講解。
但是請注意,所有的跨域請求方式,最終都需要信息提供方來做出相應(yīng)的支持和改動,也就是要經(jīng)過信息提供方的同意才行,否則接收方是無法得到它們的信息的,瀏覽器是不允許的。
解決跨域 - JSONP
首先,有一個概念你要明白,例如訪問http://coding.m.juejin.com/classindex.html
的時候,服務(wù)器端就一定有一個classindex.html
文件嗎?—— 不一定,服務(wù)器可以拿到這個請求,動態(tài)生成一個文件,然后返回。 同理,<script src="http://www.lilnong.top/urlTrans?group=juejin&url=http://coding.m.juejin.com/api.js">
也不一定加載一個服務(wù)器端的靜態(tài)文件,服務(wù)器也可以動態(tài)生成文件并返回。OK,接下來正式開始。
例如我們的網(wǎng)站和掘金網(wǎng),肯定不是一個域。我們需要掘金網(wǎng)提供一個接口,供我們來獲取。首先,我們在自己的頁面這樣定義
<script>
window.callback = function (data) {
// 這是我們跨域得到信息
console.log(data)
}
</script>
然后掘金網(wǎng)給我提供了一個http://coding.m.juejin.com/api.js
,內(nèi)容如下(之前說過,服務(wù)器可動態(tài)生成內(nèi)容)
callback({x:100, y:200})
最后我們在頁面中加入<script src="http://www.lilnong.top/urlTrans?group=juejin&url=http://coding.m.juejin.com/api.js"></script>
,那么這個js加載之后,就會執(zhí)行內(nèi)容,我們就得到內(nèi)容了。
解決跨域 - 服務(wù)器端設(shè)置 http header
這是需要在服務(wù)器端設(shè)置的,作為前端工程師我們不用詳細掌握,但是要知道有這么個解決方案。而且,現(xiàn)在推崇的跨域解決方案是這一種,比 JSONP 簡單許多。
response.setHeader("Access-Control-Allow-Origin", "http://m.juejin.com/"); // 第二個參數(shù)填寫允許跨域的域名稱,不建議直接寫 "*"
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 有何區(qū)別?
cookie
cookie 本身不是用來做服務(wù)器端存儲的(計算機領(lǐng)域有很多這種“狗拿耗子”的例子,例如 CSS 中的 float),它是設(shè)計用來在服務(wù)器和客戶端進行信息傳遞的,因此我們的每個 HTTP 請求都帶著 cookie。但是 cookie 也具備瀏覽器端存儲的能力(例如記住用戶名和密碼),因此就被開發(fā)者用上了。
使用起來也非常簡單,document.cookie = ....
即可。
但是 cookie 有它致命的缺點:
- 存儲量太小,只有 4KB
- 所有 HTTP 請求都帶著,會影響獲取資源的效率
- API 簡單,需要封裝才能用
localStorage 和 sessionStorage
后來,HTML5 標(biāo)準(zhǔn)就帶來了sessionStorage
和localStorage
,先拿localStorage
來說,它是專門為了瀏覽器端緩存而設(shè)計的。其優(yōu)點有:
- 存儲量增大到 5MB
- 不會帶到 HTTP 請求中
- API 適用于數(shù)據(jù)存儲
localStorage.setItem(key, value)
localStorage.getItem(key)
sessionStorage
的區(qū)別就在于它是根據(jù) session 過去時間而實現(xiàn),而localStorage
會永久有效,應(yīng)用場景不同。例如,一些需要及時失效的重要信息放在sessionStorage
中,一些不重要但是不經(jīng)常設(shè)置的信息,放在localStorage
中。
另外告訴大家一個小技巧,針對localStorage.setItem
,使用時盡量加入到try-catch
中,某些瀏覽器是禁用這個 API 的,要注意。文章來源:http://www.zghlxwxcb.cn/news/detail-489348.html
總結(jié)
本文總結(jié)了 W3C 標(biāo)準(zhǔn)中 Web-API 部分,面試中??嫉闹R點,這些也是日常開發(fā)中最常用的 API 和知識。文章來源地址http://www.zghlxwxcb.cn/news/detail-489348.html
到了這里,關(guān)于JS-Web-API知識點與高頻考題解析的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!