- HTML并不簡單:Web前端開發(fā)精進(jìn)秘籍
- 張鑫旭
- 3842字
- 2024-07-24 13:21:50
2.1.1 rel屬性知多少
<a>元素支持一個(gè)名為rel的屬性,rel由簡簡單單三個(gè)字母構(gòu)成,看起來其貌不揚(yáng),但它其實(shí)是個(gè)大家族,包含各種各樣的功能和作用。
其支持的屬性值參見表2-1,總共有30多個(gè)。
表2-1 瀏覽器支持的rel屬性值

每一個(gè)rel屬性值都對應(yīng)一個(gè)Web應(yīng)用場景,比如資源的預(yù)加載、網(wǎng)站圖片和樣式的顯示等。
對于<a>元素,我們需要重點(diǎn)關(guān)注如下幾個(gè)值。
? nofollow
? noopener和opener
? noreferrer
1.rel="nofollow"
給鏈接元素設(shè)置rel="nofollow"是SEO中的常用策略,用來告訴搜索引擎不要追蹤這個(gè)鏈接。
在以下兩種情況下需要設(shè)置rel="nofollow":
? 目標(biāo)頁面顯示的均是無效信息,或者含有敏感信息;
? 目標(biāo)頁面屬于外站,不希望共享權(quán)重。
前者好理解,那么后者是什么意思呢?
SEO中有個(gè)策略,如果一個(gè)權(quán)重很高的網(wǎng)站,直接外鏈一個(gè)權(quán)重不高的中小網(wǎng)站,同時(shí)沒有設(shè)置rel="nofollow",那么這個(gè)中小網(wǎng)站的權(quán)重有一定的概率會被提高。
注意,是有一定的概率被提高,而并非一定,因?yàn)樗阉饕娴臋?quán)重策略是保密且動(dòng)態(tài)調(diào)整的。以谷歌為例,在過去,頁面的權(quán)重只會分配給允許跟蹤的鏈接,但是現(xiàn)在谷歌使用了一種更為復(fù)雜的分配規(guī)則,即使某個(gè)鏈接設(shè)置了不允許跟蹤,也有可能會有一定的權(quán)重分配。
因此,大型網(wǎng)站,尤其是新聞、問答或社區(qū)類的網(wǎng)站,通常會對外鏈設(shè)置rel="nofollow",以防止權(quán)重被外泄。
過去,這是前端開發(fā)中的常識,而如今卻是冷知識,知者甚少。
一方面是因?yàn)榍岸酥R圖譜更廣了,從業(yè)者學(xué)習(xí)的精力有限,無暇顧及這部分內(nèi)容;另一方面是產(chǎn)品形態(tài)更加多樣化,例如移動(dòng)端產(chǎn)品、工具類產(chǎn)品是不需要SEO知識的;當(dāng)然,還有一部分原因是,這些知識的匱乏對業(yè)務(wù)收益的影響是不可見的,或者可以通過其他策略規(guī)避因開發(fā)人員不懂rel="nofollow"給產(chǎn)品帶來的損失。
比如,將所有的外鏈都換成本站地址并進(jìn)行中轉(zhuǎn),A網(wǎng)站外鏈了B網(wǎng)站,則可以專門申請一個(gè)域名,例如link.aaa.com,然后將所有外鏈都換成這個(gè)域名。假設(shè)原來的鏈接是https://www.bbb.com/xxx,現(xiàn)在就可以換成https://link.aaa.com?target=https://www.bbb.com/xxx,這樣就可以避免權(quán)重被外泄,同時(shí)也可以統(tǒng)計(jì)外鏈的點(diǎn)擊量。
2.rel="noopener"與rel="opener"
例如,有這樣一個(gè)需求場景,主頁面有一個(gè)“登錄”按鈕,點(diǎn)擊后會開啟一個(gè)新窗口進(jìn)行登錄,現(xiàn)在希望用戶登錄成功后,主頁面自動(dòng)刷新,該如何實(shí)現(xiàn)?
有人會想到使用visibilitychange事件。

也有人會使用postMessage實(shí)現(xiàn)跨頁面通信。
但這兩種方法,要么有運(yùn)行冗余外加執(zhí)行時(shí)機(jī)滯后的問題,要么學(xué)習(xí)成本高,上手不易。
那有沒有執(zhí)行時(shí)機(jī)精準(zhǔn),同時(shí)又能輕松實(shí)現(xiàn)的方法呢?
有,只需要給鏈接元素設(shè)置rel="opener"即可,例如:

此時(shí),只需要在登錄成功后,執(zhí)行下面的JavaScript代碼就可以了:

這里的window.operer就是主頁面的全局window對象。
是不是簡單得超出想象?僅僅通過一個(gè)其貌不揚(yáng)的HTML屬性設(shè)置,就讓原本兩個(gè)獨(dú)立的瀏覽器窗口有效通信。
然而,window對象的權(quán)限太高了,不僅掛載在window對象上的屬性和方法可以被執(zhí)行,任何document對象的屬性和方法也可以被執(zhí)行,因?yàn)閐ocument對象可以通過window.document訪問。
這其實(shí)非常危險(xiǎn),有安全隱患,比如會被惡意修改主頁面的內(nèi)容,或者被人通過window.opener.document.cookie獲取用戶的敏感信息并進(jìn)行身份偽造等。
這么說肯定有人還是不以為意,所以,我專門做了個(gè)演示頁面,你一定要打開這個(gè)頁面點(diǎn)擊里面的鏈接,絕對會被驚訝到。

可以通過在瀏覽器中輸入地址https://www.htmlapi.cn/2/1-1.html或掃碼訪問來體驗(yàn)。
演示頁面的展示區(qū)中心有一個(gè)“目標(biāo)頁面”的<a>元素鏈接,點(diǎn)擊這個(gè)鏈接,會在新標(biāo)簽頁打開一個(gè)新頁面,此時(shí),無視這個(gè)頁面,再切換回剛才的入口頁面,就會發(fā)現(xiàn)頁面中多了一些內(nèi)容,如圖2-1所示,同時(shí)地址欄中也多了#hack字符。

圖2-1 頁面新增內(nèi)容示意
這種Web攻擊在技術(shù)上很低級,但效果卻很顯著,因?yàn)槠渚褪抢瞄_放的Web特性實(shí)現(xiàn)的,非常隱蔽,防不勝防,尤其在2021年之前。
為何是2021年之前呢?因?yàn)樵?021年,Chrome瀏覽器調(diào)整了對新窗口頁面的operer策略。
大家可能不知道,在2021年之前,不僅IE瀏覽器,就連Chrome瀏覽器,針對新窗口打開的鏈接,默認(rèn)都是放開window.operer的。
也就是說,如果你給任意的一個(gè)外站鏈接設(shè)置target="blank",就像下面這樣:

那么,只要這個(gè)域名是bbb.com的網(wǎng)站,就可以通過window.opener對來源頁面為所欲為,無論是跳轉(zhuǎn)到惡意網(wǎng)站,或是注入惡意信息,還是偽造用戶身份,都只能看bbb.com網(wǎng)站的臉色。
所以,在前端圈子中有這么一個(gè)技術(shù)常識,設(shè)置了target="blank"的外站一定要設(shè)置rel="noopener",這樣,外站頁面的window.operer就是null,也就沒有安全隱患了。
然而,還是那句話,過去的技術(shù)常識在如今的年輕前端開發(fā)群體中屬于冷知識,又有幾個(gè)人知道新窗口打開的外站鏈接一定要設(shè)置rel="noopener"呢?
所以,導(dǎo)致在整個(gè)互聯(lián)網(wǎng)中絕大多數(shù)的網(wǎng)頁都有這個(gè)安全隱患。
這顯然是會出大問題的呀,既然改變不了開發(fā)者,那么瀏覽器自己改,于是從2020年開始,Safari、Firefox及Chrome相繼調(diào)整了operer策略,即設(shè)置了target="blank"的鏈接元素默認(rèn)不再是operer,而是noopener。
但是,目前在市面上占據(jù)一定份額的IE瀏覽器和Edge瀏覽器(非Chromium渲染內(nèi)核的Edge瀏覽器)仍舊存在此安全隱患。
因此,即使是現(xiàn)在,也要切記,外鏈不信任的鏈接地址,只要是新窗口打開的,就一定要設(shè)置rel="noopener"。如果鏈接地址信得過,同時(shí)有跨窗體通信的需求,則可以設(shè)置rel="opener",這是最近幾年才支持的新特性,雖然IE瀏覽器并不認(rèn)識,但絲毫不影響其使用,因?yàn)镮E瀏覽器新窗口打開鏈接的默認(rèn)特性就是rel="opener"。
3.rel="noreferrer"
要想了解rel="noreferrer"的作用,就必須先了解document.referrer這個(gè)API。
document.referrer可以返回當(dāng)前頁面的來源地址。如果用戶直接在瀏覽器的地址欄輸入U(xiǎn)RL地址進(jìn)行訪問,或者通過設(shè)置了rel="noreferrer"的鏈接元素訪問,那么document.referrer就會是空字符串。
所有知名的網(wǎng)站分析工具,如百度統(tǒng)計(jì)和谷歌分析,都是通過document.referrer來判斷多少PV(頁面訪問量)來自搜索引擎,多少PV來自社交媒體,以及多少PV來自直接訪問的。圖2-2所示的就是某流量分析工具中各種流量來源對應(yīng)的名稱。

圖2-2 某流量分析工具中各種流量來源對應(yīng)的名稱
當(dāng)然,我們也可以借用document.referrer來完成一些日常開發(fā)任務(wù),都是與細(xì)節(jié)體驗(yàn)相關(guān)的。
舉兩個(gè)例子。
例一,在移動(dòng)端開發(fā)中,頁面左上角往往會有一個(gè)“返回”按鈕,但如果用戶是通過點(diǎn)擊某個(gè)分享鏈接進(jìn)入的,那么這個(gè)返回的邏輯就不對,因?yàn)椴]有上一頁,此時(shí)這個(gè)“返回”按鈕顯示為“主頁”按鈕更合適。此時(shí),我們就可以使用document.referrer來優(yōu)化此細(xì)節(jié),如果document.referrer是空字符串,則點(diǎn)擊左上角的按鈕會回到首頁;如果不是,則會返回上一頁。
例二,在某列表頁面,點(diǎn)擊任意列表會進(jìn)入詳情頁,然后希望再次返回(通過頁面內(nèi)鏈接,而非瀏覽器的“后退”按鈕)到列表頁的時(shí)候,頁面依然定位在之前的滾動(dòng)位置,但如果是從其他頁面進(jìn)入的,則滾動(dòng)置頂。
有經(jīng)驗(yàn)的前端開發(fā)人員應(yīng)該遇到過類似的需求,我想,肯定有人通過在href地址上做文章來實(shí)現(xiàn)此需求,例如加標(biāo)志量:

這也能滿足需求,但不夠優(yōu)雅,何不直接使用瀏覽器自帶的API呢?比如(假設(shè)詳情頁的地址是detail.html):

關(guān)于此需求,我專門做了個(gè)演示頁面,可通過在瀏覽器中輸入地址https://www.htmlapi.cn/2/1-2.html或掃碼訪問來體驗(yàn)。

可以看到,從詳情頁返回和從首頁返回時(shí)的列表的滾動(dòng)狀態(tài)是不一樣的,就是因?yàn)榱斜眄撌褂胐ocument.referrer判斷頁面來源并做了不同的處理。
那么什么時(shí)候需要給鏈接設(shè)置rel="noreferrer"呢?
首先,可以肯定的是,對于所有的本站鏈接都不要設(shè)置rel="noreferrer",因?yàn)闀绊懹脩粼L問路徑的追蹤,進(jìn)而影響頁面的流失率等數(shù)據(jù)的統(tǒng)計(jì)。
其次,對于外站鏈接,個(gè)人建議是,全部都設(shè)置rel="noreferrer",因?yàn)閁RL地址中也包含了大量的信息,甚至有隱私內(nèi)容(如搜索結(jié)果落地頁的URL會包含搜索關(guān)鍵詞),泄露出去會增加不必要的風(fēng)險(xiǎn)。
由于對于外站鏈接必須設(shè)置rel="noopener",因此,我們會經(jīng)常看到如下所示的HTML代碼,也就是noopener和noreferrer值同時(shí)設(shè)置,彼此使用空格分隔:

再次,如果你的產(chǎn)品是社交媒體類的,例如微博、知乎等,那么可以不設(shè)置rel="noreferrer",因?yàn)檫@些產(chǎn)品本身是開放的,以社交為主,更看重信息的傳播,暴露referrer信息反而有助于第三方網(wǎng)站溯源,可以間接增加訪問量和熱度。
上面兩種說法好像有矛盾,難道社交網(wǎng)站就不擔(dān)心隱私信息泄露了嗎?隱私泄露對于用戶也是不友好的,不是嗎?
沒錯(cuò),你所憂慮的情況確實(shí)存在,rel="noreferrer"的確存在不足,其設(shè)置與否的效果就像是0和1,并沒有折中的說法,也就是說,無法兼顧信息傳播和隱私保護(hù)。
所以,各大瀏覽器開發(fā)商遵循規(guī)范指引,從2016年開始,陸續(xù)支持一個(gè)全新的關(guān)于referrer策略的HTML屬性referrerpolicy,它可以讓我們兼顧信息傳播和隱私保護(hù),關(guān)于這一點(diǎn),會在第2.1.5節(jié)介紹,不容錯(cuò)過。
最后,雖然以下這個(gè)使用場景很少見,但確實(shí)存在,那就是鏈接地址直接是外部圖片,而這個(gè)圖片設(shè)置了防外鏈,此時(shí),給鏈接元素設(shè)置rel="noreferrer"會有神奇的作用。
HTML代碼示意:

點(diǎn)擊第一個(gè)鏈接,會發(fā)現(xiàn)圖片無法顯示,但是點(diǎn)擊第二個(gè)鏈接,圖片就可以顯示了,如圖2-3所示。

可以通過在瀏覽器中輸入地址https://www.htmlapi.cn/2/1-3.html或掃碼訪問來體驗(yàn)對應(yīng)的演示頁面,需要注意的是,一旦圖片被瀏覽器緩存,點(diǎn)擊第一個(gè)鏈接依然可以顯示,所以體驗(yàn)的時(shí)候,一定要先點(diǎn)擊第一個(gè)鏈接,或者及時(shí)清空瀏覽器的緩存。
至于原因,這里暫時(shí)賣個(gè)關(guān)子,會在第4.1.4節(jié)做詳細(xì)說明。

圖2-3 noreferrer的設(shè)置與圖片的顯示
4.relList對象
前文出現(xiàn)過一段HTML屬性設(shè)置,即rel="noopener noreferrer",對于這種寫法,不知道大家有沒有覺得有些不自然或者有些特別。
如果你有這樣的感覺,那就對了。
因?yàn)閞el屬性是所有HTML標(biāo)準(zhǔn)屬性中為數(shù)不多的支持使用空格分隔多個(gè)值的HTML屬性。另外一個(gè)常見的支持空格分隔的屬性就是class屬性,大家肯定都用過它,畢竟絕大多數(shù)的CSS樣式都是使用類選擇器進(jìn)行匹配的。
rel屬性和class屬性不僅在HTML層面的語法上一致,在DOM API層面也是極其相似的。
比如,頁面中有如下HTML元素:

我們可以使用名為classList的屬性對類名進(jìn)行增刪操作,例如下面這行JavaScript代碼就可以移除類名active。

同樣,如果我們希望移除rel屬性中的noreferrer,也使用類似的語法,只是屬性名不再是classList,而是relList,示意如下。

無論是classList,還是relList,都有一個(gè)專門的接口,叫作DOMTokenList,支持如下屬性和方法,上面演示的remove方法只是其中之一。
屬性:
DOMTokenList.length
DOMTokenList.value
方法:
DOMTokenList.item()
DOMTokenList.contains()
DOMTokenList.add()
DOMTokenList.remove()
DOMTokenList.replace()
DOMTokenList.supports()
DOMTokenList.toggle()
DOMTokenList.entries()
DOMTokenList.forEach()
DOMTokenList.keys()
DOMTokenList.values()
限于篇幅,這些屬性和方法的含義就不在這里展開介紹了,有興趣的讀者可以去查找對應(yīng)的文檔。
可見,rel屬性要比很多人預(yù)想的要強(qiáng)大得多,具有其他HTML屬性所沒有的、獨(dú)立的API。
- 自己動(dòng)手實(shí)現(xiàn)Lua:虛擬機(jī)、編譯器和標(biāo)準(zhǔn)庫
- Vue.js前端開發(fā)基礎(chǔ)與項(xiàng)目實(shí)戰(zhàn)
- Bootstrap 4:Responsive Web Design
- NetBeans IDE 8 Cookbook
- Julia for Data Science
- 時(shí)空數(shù)據(jù)建模及其應(yīng)用
- JavaScript程序設(shè)計(jì):基礎(chǔ)·PHP·XML
- AV1視頻編解碼標(biāo)準(zhǔn):原理與算法實(shí)現(xiàn)
- C指針原理揭秘:基于底層實(shí)現(xiàn)機(jī)制
- Sails.js Essentials
- Machine Learning for OpenCV
- Java Hibernate Cookbook
- Python全棧開發(fā):基礎(chǔ)入門
- Mastering Bootstrap 4
- Apache Solr for Indexing Data