本博客轉(zhuǎn)自:「作者:若愚鏈接:https://zhuanlan.zhihu.com/p/22361337來源:知乎著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
1、為什么document.write(1)有時候會重寫文檔,有時候不會?
當(dāng)你打開一個頁面,瀏覽器會:
1. 省略前面的步驟
2. 調(diào)用 document.open() 打開文檔
3. document.write(...) 講下載到的網(wǎng)頁內(nèi)容寫入文檔
4. 所有內(nèi)容寫完了,就調(diào)用 document.close()
5. 觸發(fā) dom ready 事件(DOMContentReady)
所以你如果在第四步之前 document.write(1) 那么你就直接追加內(nèi)容到當(dāng)前位置, 如果你在第4步之后 document.write(),那么由于 document 已經(jīng) close 了,所以必須重新 document.open() 來打開文檔,這一打開,內(nèi)容就被清空了。
不信你可以這樣驗證一下:
1. 打開 baidu.com 等頁面加載完
2. 在控制臺運行 document.write(1),會看到頁面清空,只有一個 1
3. 再次運行 document.write(1),會發(fā)現(xiàn)頁面沒有清空,1 變成了 11,因為追加了一個1
4. 運行 document.close(),這是文檔就關(guān)閉了。
5. 再次運行 document.write(1),你會發(fā)現(xiàn)文檔又清空了,變成了 1。
2、為什么 document.all 有時像一個對象(數(shù)組),有時又不像一個對象(數(shù)組)?
document.all 奇怪的地方在于它是一個數(shù)組(其實是對象),但是類型卻是undefined。
先看控制臺代碼
>>> document.all
HTMLAllCollection[1009] // 控制臺打印出一個數(shù)組
>>> document.all[0]
<html>...</html> // 第一個元素是 html 標簽,看起來很像一個數(shù)組
>>> typeof document.all
'undefined' // 咦,居然不是 object?
>>> if(document.all){console.log('document.all 為真')}else{console.log('document.all 為假')}
document.all 為假
![}IL3QCF08U]Q5)C8UY_{JGA.png](http://upload-images.jianshu.io/upload_images/1181204-13ef440a4d07c023.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
用各個瀏覽器測試一下結(jié)果如下:
所有瀏覽器都支持用 document.all[index] 或者 document.all[id] 獲取標簽
但是 typeof document.all,在 IE 10 以下的值為 ‘object’,在其他瀏覽器的值均為 ‘undefined’
為何會這樣?那我們就要從很久很久以前說起了。多久呢,大概20年前。
那個時候,瀏覽器剛剛出現(xiàn),很多功能不完善,比如 document.getElementById,都有很多瀏覽器不支持。甚至連 W3C 都還沒制定出 Web 標準,這個時候 IE 4推出了一些 API,只有 IE 4 支持,其中就包括我們今天說 document.all,它比document.getElementById 要好用一些,比如你可以用 document.all[‘topbar’] 來獲取元素,所以那個時候很多程序員會些這樣的代碼:
if(document.all){
// IE 4 代碼
document.all['topbar']
...
}else if(document.getElementById){
// 其他瀏覽器代碼
document.getElementById('topbar')
}
甚至某些程序員直接認為 document.all 為真的瀏覽器,就是 IE。也就是這樣:
if(document.all){
// IE
}else{
// 非 IE
}
其他瀏覽器(網(wǎng)景)覺得 IE 4 有的功能,我也要有才行。于是也加上 document.all,功能一樣,可以獲取元素。但是呢,其他瀏覽器又不想被某些程序員認為是 IE,于是 typeof document.all 的值定為 undefined。
有人說 W3C 標準出來后,為什么瀏覽器不把 document.all 糾正過來,去掉或者變成一個正常的對象都可以啊。太天真了,如果瀏覽器這么做了,那么當(dāng)時世界上會有很多頁面 JS 代碼都不能正常運行,到時候用戶只會怪瀏覽器不給力,然后卸載掉瀏覽器。
瀏覽器廠商才不想冒這樣的風(fēng)險。于是,最終,我們在所有瀏覽器上得到了一個類型為 undefined 的 document.all,只有 IE 10 以下,然后保留著 IE 4 的行為。
不過 document.all 現(xiàn)在已經(jīng)被棄用了,大家不要再使用它了。用 document.getElementById 或者 document.querySelector 就好 !
3、為什么 0.1 + 0.2 結(jié)果為 0.30000000000000004?
如果你打開瀏覽器的控制臺,輸入以下代碼并運行
奇怪,為什么不是 0.3。這時可能你會去搜一下(很容易搜到結(jié)果),但是如果你了解計算機是如何存儲小數(shù)(準確地說是浮點數(shù))的話,很容易推斷出原因。
首先我們要用以下嘗試:
1.計算機將所有數(shù)據(jù)以二進制的形式存儲
2.計算機用有限的大小來存儲數(shù)據(jù)(因為現(xiàn)實生活中不存在無限大的內(nèi)存或硬盤)
好的,然后結(jié)合我們的問題來看。
計算機如何存儲 0.1 和 0.2?
如果你對十進制轉(zhuǎn)二進制有興趣可以看下圖:
如果看不懂也可以直接看結(jié)論:十進制的 0.1 轉(zhuǎn)為二進制,得到一個無限循環(huán)小數(shù):0.00011…。
也就是說,二進制無法「用有限的位數(shù)」來表示 0.1。對于 0.2 也是一樣的。二進制能「用有限的位數(shù)」表示的有:0.5、0.25、0.125 等。
但是計算機只能用有限的位數(shù)來存一個數(shù),所以最終,計算機存的數(shù)是一個近似于 0.1 的小數(shù)。(具體轉(zhuǎn)換過程參考這里)所以當(dāng)我們計算 0.1 + 0.2 時,實際上算的是兩個近似值相加,得到的值當(dāng)然也是近似等于 0.3。
總結(jié)
1.問題的根源是十進制小數(shù)轉(zhuǎn)為二進制小數(shù)的過程中,會損失精度
2.你在寫代碼的過程中,遇到小數(shù)都要小心,比如下面的代碼會造成死循環(huán):
var i = 0.1
while(i!=1){
console.log(i)
i += 0.1
}
因為 i 加 9 次 0.1,得到的值是 1 的近似值,并不是 1。
3.你應(yīng)該對計算機存儲方式有一定的了解。
4、var undefined = 1 這樣賦值有效果嗎?在什么情況下有?Why?
情景1:
var undefined = 1;
alert(undefined); // chrome: undefined, ie8: 1
alert(window.undefined);//undefined
在 chrome 下運行得到的結(jié)果還是 「undefined」,但在IE8以下賦值是生效的。
可以看看 MDN 里關(guān)于這部分的描述: undefined - JavaScript
情景2:
var obj = {};
obj.undefined = 'hanbaoyi';
console.log(obj.undefined) // hanbaoyi
在標準瀏覽器下作為全局作用域下 window的一個屬性, undefined 不可修改;但對于一個普通對象,undefined可作為屬性且可以修改。
情景3:
function fn(){
var undefined = 100;
alert(undefined); //chrome: 100, ie8: 100
}
fn();
不管是標準瀏覽器,還是老的 IE 瀏覽器,在函數(shù)內(nèi)部 undefined 可作為局部變量重新賦值
情景4:
undefined = 100;
(function(global){
alert(undefined); //chrome: undefined, ie8: 100
})(window)
在標準瀏覽器下輸出的結(jié)果是undefined。而在ie8下為100(因為可被賦值)
所以1.x版本的 jquery 中我們會看到類似這樣的代碼
(function(global, undefined){
alert(undefined)
})(window)
這樣即使用戶使用 IE8瀏覽器,在全局修改了undefined的值,在匿名函數(shù)內(nèi)undefined 也不會發(fā)生變化。因為在匿名函數(shù)內(nèi)實參只傳遞一個 window,而形參多了個undefined(可以把這個undefined當(dāng)做一個普通參數(shù)名),這個未賦值的變量在匿名函數(shù)內(nèi)就是真正的undefined。
undefined 的其他邊邊角角
有時候我們需要判斷一個變量是不是undefined,會這樣用
但假如 str 這個變量沒聲明就會出現(xiàn)報錯,用下面的方式會更好一些
有時候我們會看到這種寫法
if(str === void 0){
console.log('I am real undefind');
}
那是因為 「void 0」的執(zhí)行結(jié)果永遠是「undefined」, 即使在某些老舊瀏覽器 或者在某個函數(shù)中 undefined被重新賦值,我們?nèi)匀豢梢酝ㄟ^ 「void 0」 得到真正的「undefined」。
5、button.disabled 和 button.getAttribute('disabled') 有什么區(qū)別?
「node.getAttribute('someAttribute')」獲取的是「attribute」,而「node.someAttribute」獲取的是元素的「property」,二者并不相同。 參考 properties-and-attributes-in-html
在大多數(shù)情況下「property」和「attribute」是同步的,如場景1。
場景1:
<input id="username" type="text">
<script>
var userInput = document.querySelector('#username');
console.log( userInput.id ); //"username"
console.log( userInput.getAttribute('id') ); //"username"
</script>
這里 userInput.id 和 userInput.getAttribute('id') 獲取的值相等。
當(dāng)然我們關(guān)注的是例外,如場景2、3、4.
場景2:
<input id="username" type="text" sex="male" age=26>
<script>
var userInput = document.querySelector('#username');
console.log( userInput.sex ); // undefined
console.log( userInput.getAttribute('sex') ); // "male"
console.log( userInput.getAttribute('SEX') ); // "male"
console.log( userInput.getAttribute('age')); // "26"
</script>
從上面的例子可以得出如下結(jié)論:
- 「node.property」的方式不能獲取自定義屬性,「node.getAttribute()」的方式可以獲取自定義屬性
- 「node. getAttribute()」獲取自定義屬性忽略屬性的大小寫
- 「node.getAttribute()」獲取自定義屬性得到的值的類型總是字符串
場景3:
對于上面的例子,HTML中只要出現(xiàn)了disabled 屬性,不管值是什么,對于 DOM property結(jié)果都是true, 而對于 attribute 獲取的則是把 HTML 里對應(yīng)屬性的值拿到轉(zhuǎn)換成字符串。
input 標簽的 checked 也有類似的特性。
場景4:
對于 a 鏈接的 href, 使用 a.getAttribute('href') 就是從 HTML 里獲取對應(yīng)屬性的值轉(zhuǎn)化成字符串,而 a.href 則獲取有意義的真實地址。
場景5:
對于input 的 value, 改變 property 不會同步到 atttribute 上,改變 attribute也不會同步到 value上, attribute對應(yīng) HTML, property 對應(yīng) DOM。
那到底用哪一種呢?
如果你只是想獲取非自定義的屬性,比如 id、name、src、href 、checked... 用 property 的方式比較符合日常習(xí)慣,如果需要獲取自定義屬性那只能使用 getAttribute。當(dāng)然具體用哪一種你只要了解二者的區(qū)別,大膽選用吧~
6、為什么不建議將 font-size 設(shè)置為 12px 以下?如果一定要設(shè)置為 12px 以下要怎么做?
先看看把 font-size 設(shè)置為 12px 以下時的效果:(瀏覽器為 Chrome 52)
在其他瀏覽器上效果卻不一樣:
因為 Chrome 這款任性的瀏覽器做了如下限制:
font-size 有一個最小值 12px(不同操作系統(tǒng)、不同語言可能限制不一樣),低于 12px 的,一律按 12px 顯示。理由是 Chrome 認為低于 12px 的中文對人類的不友好的。
但是允許你把 font-size 設(shè)置為 0.
這個 12px 的限制用戶是可以自行調(diào)整的,進入 chrome://settings/fonts 設(shè)置,滾動到最下方你就可以調(diào)整 12px 為其他值。
如果我一定要設(shè)置小于 12px 的字體怎么辦?
Chrome 29 版本之前,你可以使用
-webkit-text-size-adjust: none;
來解除這個限制。29 版本后,就不能這樣做了。你可以先設(shè)置 12px,然后使用 transform: scale(0.833333) 講元素縮小,效果跟 10px 很接近。不過要注意的是,transform: scale chu了縮小 font-size,也會縮小其他一些屬性,需要多測試。
7、cookie、session、localStorage分別是什么?有什么作用?
<b>
Cookie 是什么</b>
Cookie 是瀏覽器訪問服務(wù)器后,服務(wù)器傳給瀏覽器的一段數(shù)據(jù)。用來記錄某些當(dāng)頁面關(guān)閉或者刷新后仍然需要記錄的信息。在控制臺用 「document.cookie」查看你當(dāng)前正在瀏覽的網(wǎng)站的cookie。
cookie可以使用 js 在瀏覽器直接設(shè)置(用于記錄不敏感信息,如用戶名), 也可以在服務(wù)端通使用 HTTP 協(xié)議規(guī)定的 set-cookie 來讓瀏覽器種下cookie,這是最常見的做法。(打開一個網(wǎng)站,清除全部cookie,然后刷新頁面,在network的Response headers試試找一找set-cookie吧)
每次網(wǎng)絡(luò)請求 Request headers 中都會帶上cookie。所以如果 cookie 太多太大對傳輸效率會有影響。
4.一般瀏覽器存儲cookie 最大容量為4k,所以大量數(shù)據(jù)不要存到cookie。
5.設(shè)置cookie時的參數(shù):
path:表示 cookie 影響到的路徑,匹配該路徑才發(fā)送這個 cookie。expires 和 maxAge:告訴瀏覽器 cookie 時候過期,maxAge 是 cookie 多久后過期的相對時間。不設(shè)置這兩個選項時會產(chǎn)生 session cookie,session cookie 是 transient 的,當(dāng)用戶關(guān)閉瀏覽器時,就被清除。一般用來保存 session 的 session_id。
secure:當(dāng) secure 值為 true 時,cookie 在 HTTP 中是無效,在 HTTPS 中才有效
httpOnly:瀏覽器不允許腳本操作 document.cookie 去更改 cookie。一般情況下都應(yīng)該設(shè)置這個為 true,這樣可以避免被 xss 攻擊拿到 cookie。
<b>
如何使用 Cookie</b>
Cookie 一般有兩個作用。
- 第一個作用是識別用戶身份。
比如用戶 A 用瀏覽器訪問了 http://a.com,那么 http://a.com 的服務(wù)器就會立刻給 A 返回一段數(shù)據(jù)「uid=1」(這就是 Cookie)。當(dāng) A 再次訪問 http://a.com的其他頁面時,就會附帶上「uid=1」這段數(shù)據(jù)。
同理,用戶 B 用瀏覽器訪問 http://a.com時,http://a.com 發(fā)現(xiàn) B 沒有附帶 uid 數(shù)據(jù),就給 B 分配了一個新的 uid,為2,然后返回給 B 一段數(shù)據(jù)「uid=2」。B 之后訪問 http://a.com的時候,就會一直帶上「uid=2」這段數(shù)據(jù)。
借此,http://a.com 的服務(wù)器就能區(qū)分 A 和 B 兩個用戶了。 - 第二個作用是記錄歷史。
假設(shè) http://a.com 是一個購物網(wǎng)站,當(dāng) A 在上面將商品 A1 、A2 加入購物車時,JS 可以改寫 Cookie,改為「uid=1; cart=A1,A2」,表示購物車里有 A1 和 A2 兩樣商品了。這樣一來,當(dāng)用戶關(guān)閉網(wǎng)頁,過三天再打開網(wǎng)頁的時候,依然可以看到 A1、A2 躺在購物車里,因為瀏覽器并不會無緣無故地刪除這個 Cookie。
借此,就達到里記錄用戶操作歷史的目的了。
session
當(dāng)一個用戶打開淘寶登錄后,刷新瀏覽器仍然展示登錄狀態(tài)。服務(wù)器如何分辨這次發(fā)起請求的用戶是剛才登錄過的用戶呢?這里就使用了session保存狀態(tài)。用戶在輸入用戶名密碼提交給服務(wù)端,服務(wù)端驗證通過后會創(chuàng)建一個session用于記錄用戶的相關(guān)信息,這個 session 可保存在服務(wù)器內(nèi)存中,也可保存在數(shù)據(jù)庫中。
cookie 是存儲在瀏覽器里的一小段「數(shù)據(jù)」,而session是一種讓服務(wù)器能識別某個用戶的「機制」,session 在實現(xiàn)的過程中需要使用cookie。 二者不是同一維度的東西。創(chuàng)建session后,會把關(guān)聯(lián)的session_id 通過setCookie 添加到http響應(yīng)頭部中。
瀏覽器在加載頁面時發(fā)現(xiàn)響應(yīng)頭部有 set-cookie字段,就把這個cookie 種到瀏覽器指定域名下。當(dāng)下次刷新頁面時,發(fā)送的請求會帶上這條cookie, 服務(wù)端在接收到后根據(jù)這個session_id來識別用戶。
localStorage
localStorage HTML5本地存儲web storage特性的API之一,用于將大量數(shù)據(jù)(最大5M)保存在瀏覽器中,保存后數(shù)據(jù)永遠存在不會失效過期,除非用 js手動清除。
不參與網(wǎng)絡(luò)傳輸。
一般用于性能優(yōu)化,可以保存圖片、js、css、html 模板、大量數(shù)據(jù)。
8、JS里基本類型(值)和復(fù)雜類型(引用)有什么區(qū)別?
短答案:
基本類型變量存的是值,復(fù)雜類型的變量存的是內(nèi)存地址。
基本類型在賦值的時候拷貝值,復(fù)雜類型在賦值的時候只拷貝地址,不拷貝值。
長答案:
在講解這個問題之前,我們需要看一下計算機是如何儲存變量的。
如果計算機要存儲一個整數(shù),它可以用 32 個位(bit)來存儲,一個小數(shù),可以用 64 位來存儲。但不管怎樣,位數(shù)都是固定的。
假設(shè)有如下 JS 代碼:
var a = 1.23;
var b = 3.14;
對應(yīng)的內(nèi)存是這樣:
接下來加幾行代碼:
var obj = {}
var c = 1.628
現(xiàn)在,計算機遇到一個難題,到底用多少位來存 obj 呢?
用固定位數(shù)有一個問題,如果程序員之后往 obj 上添加屬性(如 obj.a = 1.23; obj.b = 2.34),那么 obj 用的位數(shù)可能放不下這兩個小數(shù)。
有人說,「那給 obj 分配足夠多的位數(shù)不就好了,比如一萬位,總夠用吧?」不行,這樣做一來浪費內(nèi)存,二來一萬位也不一定夠用,萬一 obj 里面的屬性非常多呢?
引入另一種內(nèi)存
為了解決 obj 存儲的問題,計算機將程序里的內(nèi)存分成兩種,一種是上圖所示,按順序使用用內(nèi),每個數(shù)據(jù)占據(jù)的位數(shù)是固定的,這種內(nèi)存叫做「棧內(nèi)存」;另一個,就是專門用來存儲位數(shù)不固定的數(shù)據(jù),存的時候不一樣按順序一個一個存,這種內(nèi)存叫做「堆內(nèi)存」。
我們來看 obj 應(yīng)該怎么存儲:
如圖, obj 在棧內(nèi)存那里,只占固定位數(shù)(32位或64位或其他都可以),里面存的并不是數(shù)據(jù){a:1.23,b:2.34},里面存的是數(shù)據(jù)「在內(nèi)存中的位置」(類似于引用或者指針)。
堆內(nèi)存里,會開辟一塊空間來存放 {a:1.23, b:2.34}。
「如果我再給 obj 添加一個屬性怎么辦呢?obj.c = 3.45」
好問題,那么 obj.c 依舊會放到堆內(nèi)存,同時占用的內(nèi)存空間也會動態(tài)的變化,以盛放 obj.a、obj.b 和 obj.c 三個小數(shù)。具體怎么動態(tài)變化,則是由 JS 引擎來負責(zé)的,暫且不表。
值 V.S. 引用
如果變量存儲的是原始值,那么這個變量就是值類型,在 JS 里也叫做基本類型。
如果變量存儲的是內(nèi)存位置,那么這個變量就是引用類型,在 JS 里也叫復(fù)雜類型,也就是對象。
值類型在賦值的時候是直接拷貝的,而引用類型則只拷貝地址。
值類型的賦值舉例:
var a = 1.23
var b = a
對應(yīng)的內(nèi)存結(jié)果為:
引用類型的賦值舉例:
var a = {name: 'a'}
var b = a
對應(yīng)內(nèi)存結(jié)果為:
也就是說,a 和 b 都存儲著「同一塊內(nèi)存」的地址!那么就會有一個問題:
當(dāng)我們修改 b.name 的時候,a.name 也會跟著變:
b.name = 'b'
a.name === 'b' // true
以上,就是對「值」和「引用」的區(qū)別的淺析。
9、如何在不刷新頁面的情況下改變URL?
問沒有具體業(yè)務(wù)場景的技術(shù)問題都是耍流氓,那在回答這個問題之前先簡單介紹一下業(yè)務(wù)場景。
下午6點半,小 H寫了一個下午的代碼揉揉眼睛伸個懶腰,「今天終于能早點回去了,先刷會知乎歇會」。突然,產(chǎn)品 小U一臉淫笑飄了過來,小 H 略感不妙。『嗨嗨~ 你這工作狀態(tài)不飽和啊,有個小需求來看看。現(xiàn)在需要做一個新聞?wù)故卷摚鞴δ軈^(qū)塊分為新聞列表和分頁兩部分。很簡單,兩天能搞定吧』小 U 說。小 H 看了看原型稿,心想確實不難。點擊分頁時把直接把分頁參數(shù)傳遞給后臺,頁面刷新后臺直接返回渲染后的數(shù)據(jù)就行了,模板寫的好的話甚至 js 都不需要了。正當(dāng)小 H 開口準備說說技術(shù)實現(xiàn)時,被小 U 打斷...『不過為了體驗好一些,在用戶點下一頁的時候別刷新頁面』小 U 說。『不刷新頁面沒關(guān)系,我用 ajax 可以實現(xiàn),不過時間嘛...』小 H 略有所思『果然是大牛啊,能實現(xiàn)我就放心了。時間好商量,不過這個項目特別急,晚上加加油啊』,說完小 U 就飄走了。『cao, 看來又走不成了』小 H 嘀咕著。兩分鐘后小 U 又跑了過來,『剛才忘了跟你說了,用戶點了下一頁后地址欄的地址要跟著變,這時候刷新頁面還能定位到當(dāng)前頁』『 ??x10000~~~』
整理下需求:
- 點擊分頁頁碼可實現(xiàn)無刷新頁面加載
- 同時 URL 在數(shù)據(jù)加載后會發(fā)生變化展示對應(yīng)頁碼
- 刷新頁面(帶頁碼參數(shù))會定位到當(dāng)前頁碼
效果如Demo 所示。
對于第1條,我們可以使用 ajax 動態(tài)獲取對應(yīng)頁碼的數(shù)據(jù)。
對于第2條,我們可以使用 html5的 api「history.pushState」,用于改變 URL。
-
對于第3條,我們可以根據(jù) URL 中頁碼參數(shù)獲取對應(yīng)頁碼的數(shù)據(jù)再做展示。
那history.pushState如何使用呢?比如當(dāng)用戶點擊頁碼按鈕時,可使用 ajax 獲取對應(yīng)頁碼的數(shù)據(jù),拼裝 DOM 放到頁面上,然后調(diào)用下面的 setUrl 方法實現(xiàn)瀏覽器 URL 的更新。function setUrl(page){ var url = location.pathname + '?page=' + page history.pushState({url: url, title: document.title}, document.title, url)
}
history.pushState() 帶有三個參數(shù):一個狀態(tài)對象,一個標題(現(xiàn)在被忽略了),以及一個可選的URL地址。
state object — 狀態(tài)對象是一個由 pushState()方法創(chuàng)建的與歷史紀錄相關(guān)的JS對象。
title — 火狐瀏覽器現(xiàn)在已經(jīng)忽略此參數(shù),將來也許可能被使用。考慮到將來有可能的改變,傳遞一個空字符串是安全的做法。當(dāng)然,你可以傳遞一個短標題給你要轉(zhuǎn)變成的狀態(tài)。
URL — 這個參數(shù)提供了新歷史紀錄的地址。請注意,瀏覽器在調(diào)用pushState()方法后不會去加載這個URL,但有可能在之后會這樣做,比如用戶重啟瀏覽器之后。新的URL可以是絕對地址,也可以是相對地址。新URL必須和當(dāng)前URL在同一個源下。
想看實現(xiàn)效果?參考這里一個無刷新分頁的 DEMO** ,建議看看源碼實現(xiàn)。
10、Fetch API 是什么?能代替 AJAX 嗎?
短答案:
Fetch 是瀏覽器提供的原生 AJAX 接口。使用 window.fetch 函數(shù)可以代替以前的 $.ajax、$.get 和 $.post。
長答案:
前端發(fā)展地越來越快,我們用了好幾年的 $.ajax,居然也漸漸變得過時了。
以前我們用 jQuery.ajax 發(fā)一個請求是這樣的:
$.ajax('/').then(function(response){
console.log(response)
}
現(xiàn)在我們用 fetch 發(fā)一個請求是這樣的:
fetch('/').then(function(response){
response.text().then(function(text){
console.log(text)
})
})
是不是感覺很像,像就對了,因為 Fetch API 就是瀏覽器提供的用來代替 jQuery.ajax 的工具。
AJAX 的原理
我們知道 jQuery.ajax 是使用 XMLHttpRequest 對象來發(fā)送異步請求的。
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
console.log(xhr.responseText)
}
};
xhr.open('GET',url,true)
xhr.send()
是不是很麻煩,是不是?
正是由于 XMLHttpRequest 使用起來相當(dāng)麻煩,所以大家才喜歡使用 jQuery 提供的 $.ajax 方法。
用 fetch 代替 $.ajax
隨著 React.js、Angular.js 和 Vue.js 這些前端框架的流行,很多單頁面應(yīng)用已經(jīng)不再使用 jQuery 了,這意味著你要自己對 XMLHttpRequest 進行封裝,而很多人選擇封裝一個跟 jQuery.ajax 差不多的接口。
Fetch API 的出現(xiàn),就是為了給類似的操作流程定一個接口規(guī)范。
換句話說,就是瀏覽器幫你把 jQuery.ajax 給實現(xiàn)了,以后大家都是用 fetch 來發(fā)送異步請求就好了。
Fetch API 提供的一組對象
window.fetch 函數(shù)只是 Fetch API 提供的眾多接口中的一個,還有很多有用的對象:
- window.Headers
- window.Response
- window.Request
……
要學(xué)的東西又多了起來……
Fetch API 的特點
- 基于 Promise(如果你沒有學(xué)過 Promise,強烈建議你學(xué)一學(xué))
- 不需要依賴第三方庫,就可以優(yōu)雅地使用 AJAX
Fetch API 的問題
- 使用 fetch 無法取消一個請求。這是因為 Fetch API 基于 Promise,而 Promise 無法做到這一點。不過相信很快就會有對策。
兼容性
有的瀏覽器沒有 Fetch API,沒有關(guān)系,只要引入一個 polyfill 就可以了:GitHub - github/fetch: A window.fetch JavaScript polyfill.
更多Fetch詳解請看 Fecth MDN
11、什么是立即執(zhí)行函數(shù)?有什么作用?
這是 JS 中的一個常見概念,面試時經(jīng)常會被問到,請「用自己的語言」簡述
- 立即執(zhí)行函數(shù)是什么?
- 立即執(zhí)行函數(shù)有什么用途?
回答:
1. 立即執(zhí)行函數(shù)是什么
立即執(zhí)行函數(shù)就是:
聲明一個匿名函數(shù)
-
馬上調(diào)用這個匿名函數(shù)
Paste_Image.png
上面是一個典型的立即執(zhí)行函數(shù)。
首先聲明一個匿名函數(shù) function(){alert('我是匿名函數(shù)')}。
-
然后在匿名函數(shù)后面接一對括號 (),調(diào)用這個匿名函數(shù)。
那么為什么還要用另一對括號把匿名函數(shù)包起來呢?
其實是為了兼容 JS 的語法。
如果我們不加另一對括號,直接寫成
function(){alert('我是匿名函數(shù)')}()
瀏覽器會報語法錯誤。
Paste_Image.png
想要通過瀏覽器的語法檢查,必須加點小東西,比如下面幾種:(function(){alert('我是匿名函數(shù)')} ()) // 用括號把整個表達式包起來(function(){alert('我是匿名函數(shù)')}) () //用括號把函數(shù)包起來 !function(){alert('我是匿名函數(shù)')}() // 求反,我們不在意值是多少,只想通過語法檢查。 +function(){alert('我是匿名函數(shù)')}() -function(){alert('我是匿名函數(shù)')}() ~function(){alert('我是匿名函數(shù)')}() void function(){alert('我是匿名函數(shù)')}() new function(){alert('我是匿名函數(shù)')}()
2、 立即執(zhí)行函數(shù)有什么用?
只有一個作用:創(chuàng)建一個獨立的作用域。這個作用域里面的變量,外面訪問不到(即避免「變量污染」)。以一個[著名的面試題](https://link.zhihu.com/? target=http%3A//js.jirengu.com/didu/1)為例:
var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
liList[i].onclick = function(){
alert(i) // 為什么 alert 出來的總是 6,而不是 0、1、2、3、4、5
}
}
為什么 alert 的總是 6 呢,因為 i 是貫穿整個作用域的,而不是給每個 li 分配了一個 i,如下:
那么怎么解決這個問題呢?用立即執(zhí)行函數(shù)給每個 li 創(chuàng)造一個獨立作用域即可(當(dāng)然還有其他辦法):
var liList = ul.getElementsByTagName('li')
for(var i=0; i<6; i++){
(function(ii){
liList[ii].onclick = function(){
alert(ii) // 0、1、2、3、4、5
}
})(i)
}
在立即執(zhí)行函數(shù)執(zhí)行的時候,i 的值被賦值給 ii,此后 ii 的值一直不變。i 的值從 0 變化到 5,對應(yīng) 6 個立即執(zhí)行函數(shù),這 6 個立即執(zhí)行函數(shù)里面的 ii 「分別」是 0、1、2、3、4、5。
以上,就是立即執(zhí)行函數(shù)的基本概念。
12、談?wù)勀銓υ汀⒃玩湣?Function、Object 的理解?
問題
有如下代碼:
- 問題1:畫出代碼1的原型圖?
- 問題2:從代碼2你能得出什么結(jié)論?試畫出原型圖?
- 問題3:解釋代碼3的原因?
解答:
在解答上面的問題之前,先記住下面幾句話,這幾句話能解釋一切關(guān)于原型方面的問題:
當(dāng) new 一個函數(shù)的時候會創(chuàng)建一個對象,『函數(shù).prototype』 等于 『被創(chuàng)建對象.proto』
一切函數(shù)都是由 Function 這個函數(shù)創(chuàng)建的,所以『Function.prototype === 被創(chuàng)建的函數(shù).proto』
一切函數(shù)的原型對象都是由 Object 這個函數(shù)創(chuàng)建的,所以『Object.prototype === 一切函數(shù).prototype.proto』
下面是代碼1的原型圖:
- (1)People函數(shù)創(chuàng)建了對象 p,所以People.prototype === p.proto;
- (2)Object函數(shù)創(chuàng)建了People.prototype對象,所以O(shè)bject.prototype === People.prototype.proto;
- (3)People 作為對象的角色被函數(shù)Function創(chuàng)建,所以 Function.prototype === People.proto
下面是代碼2的原型圖:
Paste_Image.png
- (1)任何函數(shù)都是 Function 創(chuàng)建,所以Function 創(chuàng)建了 Function,所以 Function.prototype === Function.proto;
- (2)Object 也是函數(shù)。所以Function創(chuàng)建了Object,所以 Function.prototype === Object.proto ;
- (3)Function.prototype 是普通對象,普通對象是由Object創(chuàng)建的,所以 Function.prototype.proto === Object.prototype
關(guān)于代碼3:
instanceof 的作用是判斷一個對象是不是一個函數(shù)的實例。比如 obj instanceof fn, 實際上是判斷fn的prototype是不是在obj的原型鏈上。比如: obj.proto === fn.prototype, obj.proto.proto === fn.prototype,obj.proto...proto_ === fn.prototype,只要一個成立即可。
所以(根據(jù)圖2來找)
- 對于 Function instanceof Function,因為 Function.proto === Function.prototype,所以為true。
- 對于 Object instanceof Object, 因為 Object.proto.proto === Function.prototype.proto === Object.prototype , 所以為true
- 對于 Function instanceof Object, 因為 Function.proto.proto === Function.prototype.proto === Object.prototype, 所以為 true
- 對于 Object instanceof Function, 因為 Object.proto === Function.prototype,所以為 true
13、JS 中的閉包是什么?
大名鼎鼎的閉包!這一題終于來了,面試必問。請用自己的話簡述
- 什么是「閉包」。
- 「閉包」的作用是什么。
首先來簡述什么是閉包
為簡明起見,上面代碼不使用全局變量,你可以假設(shè)上面三行代碼在一個立即執(zhí)行函數(shù)中。上圖一個三行代碼,一個局部變量 local,一個函數(shù) foo,foo 里面可以訪問到 local 變量。好了這就是一個閉包:
「函數(shù)」和「函數(shù)內(nèi)部能訪問到的變量」(也叫環(huán)境)的總和,就是一個閉包。
就這么簡單。
有的同學(xué)就疑惑了,閉包這么簡單么?「我聽說閉包是需要函數(shù)套函數(shù),然后 return 一個函數(shù)的呀!」,比如這樣:
function foo(){
var local = 1
function bar(){
local++
return local
}
return bar
}
var func = foo()
func()
這里面確實有閉包,local 變量和 bar 函數(shù)就組成了一個閉包(Closure)。
為什么要函數(shù)套函數(shù)呢?
是因為需要局部變量,所以才把 local 放在一個函數(shù)里,如果不把 local 放在一個函數(shù)里,local 就是一個全局變量了,達不到使用閉包的目的——隱藏變量(等會會講)。
有些人看到「閉包」這個名字,就一定覺得要用什么包起來才行。其實這是翻譯問題,閉包的原文是 Closure,跟「包」沒有任何關(guān)系。
所以函數(shù)套函數(shù)只是為了造出一個局部變量,跟閉包無關(guān)。
為什么要 return bar 呢?
因為如果不 return,你就無法使用這個閉包。把 return bar 改成 window.bar = bar 也是一樣的,只要讓外面可以訪問到這個 bar 函數(shù)就行了。所以 return bar 只是為了 bar 能被使用,也跟閉包無關(guān)。
閉包的作用
閉包常常用來「間接訪問一個變量」。換句話說,「隱藏一個變量」。
假設(shè)我們在做一個游戲,在寫其中關(guān)于「還剩幾條命」的代碼。如果不用閉包,你可以直接用一個全局變量:
window.lives = 30 // 還有三十條命
這樣看起來很不妥。萬一不小心把這個值改成 -1 了怎么辦。所以我們不能讓別人「直接訪問」這個變量。怎么辦呢?
用局部變量。
-
但是用局部變量別人又訪問不到,怎么辦呢?
暴露一個訪問器(函數(shù)),讓別人可以「間接訪問」。
代碼如下:!function(){ var lives = 50 window.獎勵一條命 = function(){ lives += 1 } window.死一條命 = function(){ lives -= 1 } }()
那么在其他的 JS 文件,就可以使用 window.獎勵一條命() 來漲命,使用 window.死一條命() 來讓角色掉一條命。
看到閉包在哪了嗎?
閉包是 JS 函數(shù)作用域的副產(chǎn)品。換句話說,正是由于 JS 的函數(shù)內(nèi)部可以使用函數(shù)外部的變量,所以這段代碼正好符合了閉包的定義。而不是 JS 故意要使用閉包。
很多編程語言也支持閉包,另外有一些語言則不支持閉包。
只要你懂了 JS 的作用域,你自然而然就懂了閉包,即使你不知道那就是閉包!
所謂閉包的作用。
那么請問,這算是閉包的作用嗎?
關(guān)于閉包的謠言,閉包會造成內(nèi)存泄露?
錯。
說這話的人根本不知道什么是內(nèi)存泄露。內(nèi)存泄露是指你用不到(訪問不到)的變量,依然占居著內(nèi)存空間,不能被再次利用起來。閉包里面的變量明明就是我們需要的變量(lives),憑什么說是內(nèi)存泄露?這個謠言是如何來的?
因為 IE。IE 有 bug,IE 在我們使用完閉包之后,依然回收不了閉包里面引用的變量。
這是 IE 的問題,不是閉包的問題。參見司徒正美的這篇文章**。
14、XSS 是什么?
新人經(jīng)常在不知不覺中寫出一個 XSS 漏洞,甚至連老司機也偶有濕鞋。請用自己的語言簡述:
- XSS 是什么(舉例說明)
- 如何防治 XSS
XSS 是什么?
是英文 Cross-Site Scripting 的縮寫。
簡單來說
- 正常用戶 A 提交正常內(nèi)容,顯示在另一個用戶 B 的網(wǎng)頁上,沒有問題。
- 惡意用戶 H 提交惡意內(nèi)容,顯示在另一個用戶 B 的網(wǎng)頁上,對 B 的網(wǎng)頁隨意篡改。
造成 XSS 有幾個要點:
1、惡意用戶可以提交內(nèi)容
2、提交的內(nèi)容可以顯示在另一個用戶的頁面上
3、這些內(nèi)容未經(jīng)過濾,直接運行在另一個用戶的頁面上
舉例說明
假設(shè)我們有一個評論系統(tǒng)。
用戶 A 提交評論「小谷你好」到服務(wù)器,然后用戶 B 來訪問網(wǎng)站,看到了 A 的評論「小谷你好」,這里沒有 XSS。
惡意用戶 H 提交評論「<script>console.log(document.cookie)</script>」,然后用戶 B 來訪問網(wǎng)站,這段腳本在 B 的瀏覽器直接執(zhí)行,惡意用戶 H 的腳本就可以任意操作 B 的 cookie,而 B 對此毫無察覺。有了 cookie,惡意用戶 H 就可以偽造 B 的登錄信息,隨意訪問 B 的隱私了。而 B 始終被蒙在鼓里。
XSS 的成因以及如何避免?
繼續(xù)上面例子,之所以惡意腳本能直接執(zhí)行,有兩個可能
后臺模板問題
<p>
評論內(nèi)容:<?php echo $content; ?>
</p>
$content 的內(nèi)容,沒有經(jīng)過任何過濾,原樣輸出。
要解決這個原因,只需要后臺輸出的時候,將可疑的符號 < 符號變成 '<'; (HTML實體)就行。前端代碼問題
$p.html(content)
或者
$p = $('<p>'+ content +'</p>')
content 內(nèi)容又被原樣輸出了。解決辦法就是不要自己拼 HTML,盡量使用 text 方法。如果一定要使用 HTML,就把可疑符號變成 HTML 實體。
示例代碼
以上,就是 XSS 的簡單介紹。
15、CSRF 是什么?
先從一個故事說起(故事純屬虛構(gòu),惡意模仿后果自負):
小谷最近遭遇電信詐騙被騙的傾家蕩產(chǎn),于是他想到了報復(fù)社會。在知乎上狂點800個沒有幫助1000個舉報后,他決定做點正事干票撈錢的生意。
「很多視頻網(wǎng)站都有贈送禮品的功能,假如所有人都贈送我個禮物,我再轉(zhuǎn)賣掉,不就發(fā)財啦」小谷尋思著。
說干就敢,經(jīng)過一番折騰測試,小谷發(fā)現(xiàn)視頻網(wǎng)站贈送禮物的接口是:
https://xxxx.com/gift/send?target=someone&giftId=ab231
原來只要用戶在登錄狀態(tài)下請求這個地址,就能給名為someone的用戶贈送禮品ab231。那如何才能讓其他用戶請求這個接口呢?其實只要用戶點擊就行,「色誘是最好的陷阱」回想起自己被騙的經(jīng)過,小谷猥瑣狠狠的打了一行文字 ——「想要的都在這里,今夜注定讓你無眠~~],然后在各個群組里回復(fù)。
經(jīng)過一天等待,有幾個上鉤的,但遠遠達不到預(yù)期,「有沒有更自動的辦法,讓用戶只要看到即使不點也能上鉤呢?」。小谷開始對整個網(wǎng)站功能逐一過濾,突然他眼前一亮,在用戶評論編輯框內(nèi)看到了上傳外鏈圖片的功能。「如果把這個 url 作為圖片填進去,上傳后會在評論區(qū)創(chuàng)建一個img 標簽,src 對應(yīng)的就是這個地址。當(dāng)用戶打開頁面后這個 img 就會自動加載圖片也就是發(fā)送這個請求,這樣一來凡是打開這個頁面的人不論是不是點擊這個鏈接都會給贈送禮物,perfect!」 小谷為自己的聰明才智驚嘆。
又過了一天,果然源源不斷的禮物送了過來。這個時候小谷隱隱有些擔(dān)心起來,雖然禮物送的都很小,但贈送都是有歷史記錄的,用戶查看歷史記錄肯定會起疑心,能不能幫用戶刪除這條贈送記錄呢?經(jīng)過測試,發(fā)現(xiàn)刪除贈送記錄的接口地址是
https://xxxx.com/gift/deleteRecord
接口類型為POST,請求參數(shù)為 { giftId:"ab231"}。 用戶無法通過點擊一個鏈接在不知情的情況下發(fā)送 POST 請求,怎么辦呢?于是,小谷構(gòu)造了一個頁面:
<body>
哈哈,給你開了個玩笑,莫生氣~
<iframe name="hiddenIframe" style="display:none"></iframe>
<form action="https://xxxx.com/gift/deleteRecord" id="form" method="post" style="visibility:hidden" target="hiddenIframe">
<input type="text" name="giftId" value="ab231">
</form>
<script>
document.getElementById('form').submit();
location.;
</script>
</body>
當(dāng)用戶點開這個頁面的鏈接后,會自動發(fā)送 POST 請求,然后跳轉(zhuǎn)到原始首頁。這樣用戶既在不知情的情況下贈送了禮品,又在不知情的情況下刪除了贈送記錄。大功告成后,小谷購買了個廣告機在各大論壇狂發(fā)....一周之后,警察??叔叔來敲門了,咚????
上面小谷的攻擊流程就是典型的 CSRF (Cross Site Request Forgery)攻擊,中文名:跨站請求偽造。其原理是攻擊者構(gòu)造網(wǎng)站后臺某個功能接口的請求地址,誘導(dǎo)用戶去點擊或者用特殊方法讓該請求地址自動加載。用戶在登錄狀態(tài)下這個請求被服務(wù)端接收后會被誤以為是用戶合法的操作。對于 GET 形式的接口地址可輕易被攻擊,對于 POST 形式的接口地址也不是百分百安全,攻擊者可誘導(dǎo)用戶進入帶 Form 表單可用POST方式提交參數(shù)的頁面。
后續(xù)......
xxxx視頻網(wǎng)站不斷接到用戶舉報,自己的禮品莫名丟失。經(jīng)過排查發(fā)現(xiàn)有攻擊者利用 CSRF 進行攻擊,報警后趕緊讓公司的安全部門的小饑來修復(fù)漏洞。
小饑梳理了一遍公司網(wǎng)站所有的接口,發(fā)現(xiàn)很多接口都存在這個問題。于是采用了anti-csrf-token的方案。 具體方案如下:
服務(wù)端在收到路由請求時,生成一個隨機數(shù),在渲染請求頁面時把隨機數(shù)埋入頁面(一般埋入 form 表單內(nèi),<input type="hidden" name="_csrf_token" value="xxxx">)
服務(wù)端設(shè)置setCookie,把該隨機數(shù)作為cookie或者session種入用戶瀏覽器
當(dāng)用戶發(fā)送 GET 或者 POST 請求時帶上_csrf_token參數(shù)(對于 Form 表單直接提交即可,因為會自動把當(dāng)前表單內(nèi)所有的 input 提交給后臺,包括_csrf_token)
后臺在接受到請求后解析請求的cookie獲取_csrf_token的值,然后和用戶請求提交的_csrf_token做個比較,如果相等表示請求是合法的。
(上圖是某電商網(wǎng)站的真實設(shè)置,這里頁面上設(shè)置的 token和session里設(shè)置的token 雖然不直接相等,但 md5('1474357164624') === '4bd4e512b0fbd9357150649adadedd4e',后臺還是很好計算的)
安全部的Leader 看了看小饑的方案,「方案出的很贊, 不過還有幾點需要注意一下」:
- Token 保存在 Session 中。假如 Token 保存在 Cookie 中,用戶瀏覽器開了很多頁面。在一些頁面 Token 被使用消耗掉后新的Token 會被重新種入,但那些老的 Tab 頁面對應(yīng)的 HTML 里還是老 Token。這會讓用戶覺得為啥幾分鐘前打開的頁面不能正常提交?
- 盡量少用 GET。假如攻擊者在我們的網(wǎng)站上傳了一張圖片,用戶在加載圖片的時候?qū)嶋H上是向攻擊者的服務(wù)器發(fā)送了請求,這個請求會帶有referer表示當(dāng)前圖片所在的頁面的 url。 而如果使用 GET 方式接口的話這個 URL 就形如:
https://xxxx.com/gift?giftId=aabbcc&_csrf_token=xxxxx
,那相當(dāng)于攻擊者就獲取了_csrf_token,短時間內(nèi)可以使用這個 token 來操作其他 GET 接口。
16、什么是 Web 服務(wù)器(server)?
首先我們來了解什么是服務(wù)器(server)
一般來說,server 有兩重意思:
- 有時候 server 表示硬件,也就是一臺機器。它還有另一個名字:「主機」。
- 更多時候,server 表示軟件程序,這種程序主要用來對外提供某些服務(wù),比如郵件服務(wù)、FTP 服務(wù)、數(shù)據(jù)庫服務(wù)、網(wǎng)頁服務(wù)等。
作為開發(fā)者,我們說 server 的時候,一般指的后者,也就是一個 24 小時運行的軟件程序。
一臺主機上面可以運行多個這樣的程序。
什么是 Web Server?
顧名思義,Web Server 就是提供 Web 服務(wù)的 Server。
比如我們訪問 http://baidu.com, 其實就是在使用百度的 Server 提供的服務(wù)。
一般來說, Web Server 對外提供的是 HTTP 服務(wù)(也可以是其他服務(wù)),這就是為什么我們的網(wǎng)址都以http:// 開頭。
如何提供 HTTP 服務(wù)?
下面是由Node.js 寫的一個最簡單的 HTTP server
// 文件名 index.js
// 使用 node index.js 可運行本程序
var http = require('http')
var server = http.createServer( function (request, response){
response.end('這是頁面內(nèi)容,你請求的路徑是:' + request.url)
})
server.listen(8080, function(){ console.log("正在監(jiān)聽 %s 端口", 8080);});
你不用看懂這段程序,你只需要知道兩件事情:
- 1、這段程序監(jiān)聽了當(dāng)前機器的 8080 端口。
- 2、一旦外部訪問當(dāng)前機器的 8080 端口,這段程序就會返回一段文字。
這就是一個最簡單的 HTTP server。
分類
提供 HTTP 服務(wù)的 server 分為兩類。
- 靜態(tài)文件服務(wù)器
這種服務(wù)器簡單地根據(jù)訪問路徑,返回對應(yīng)的文件。
比如用戶訪問 http:// 123.123.123.123:8080/a/b/c/d.html,那么這種服務(wù)器就會在網(wǎng)站根目錄找到 a/b/c/d.html 文件,原樣返回給用戶。 - 動態(tài)內(nèi)容服務(wù)器
這種服務(wù)器返回的內(nèi)容一般不是文件,而是動態(tài)生成的字符串(比如從數(shù)據(jù)庫中獲取的字符串)。
比如用戶訪問 http://weibo.com/home, 那么這種 http://weibo.com 的服務(wù)器則會返回當(dāng)前用戶最新的微博消息。顯然每個用戶得到的內(nèi)容是不一樣的。
以上,就是 Web 服務(wù)器的簡單描述。
17、Proxy 對象是做什么用的?
打開 Chrome 控制臺,輸入 window.Proxy ,你會發(fā)現(xiàn) JavaScript 已經(jīng)內(nèi)置了一個全局的 Proxy 對象,請問這個對象是做什么用的?
其實你用關(guān)鍵詞「Proxy MDN」搜索一下,就能得到一個詳細的教程**。(在關(guān)鍵詞后面加 MDN 是一個前端必備的小技巧哦)
假設(shè)我們有一個數(shù)據(jù)(對象)data,內(nèi)容為
var data = { username: 'Hanbaoyi', age: 26}
現(xiàn)在我們給 data 創(chuàng)建一個代理 proxy
var proxy = new Proxy(data, {set: function(){...}, get: function(){...} })
那么,「proxy 就全權(quán)代理 data 了」,這話是什么意思呢?
意思就是 data 放假去了,如果你有任何事情要找 data,直接找 proxy 就好了,proxy 現(xiàn)在是 data 的秘書、代理人。
比如原本你如果要改 username,那么應(yīng)該寫 data.username = 'hanbaoyi';
那么現(xiàn)在你只需要寫 proxy.username = 'hanbaoyi' 就好了。
原本你如果想寫 console.log(data.username),現(xiàn)在也只需要 console.log(proxy.username) 就可以了。
這樣做什么意義?
意義就是你能監(jiān)控每一次對 data 的讀寫操作。
proxy.username = 'frank' 這句話實際上會運行 set 方法。set 方法可以對傳入的值進行監(jiān)控和過濾。
假設(shè) PM 要求「username 前后不能含有空格」,用 Proxy 就很好實現(xiàn)這一需求,只需要把 set 寫成這樣:
set: function(obj, prop, value){
obj[prop] = value.trim()
}
再假設(shè) PM 要求「統(tǒng)計 username 被讀取的次數(shù)」,那么我們只需要把 get 寫成這樣:
get: function(obj, prop){
if(prop === 'username'){
count += 1
}
return obj[prop]
}
雙向綁定
既然用 Proxy 能監(jiān)控一個變量的讀寫情況,那么我們就很容易實現(xiàn)一個雙向綁定了。
具體代碼看這里。
以上,就是 Proxy 的簡介了。
18、JSONP 是什么?
問題:JSONP 是什么?補充如下代碼,實現(xiàn)一個JSONP方法。
function jsonp(setting){
//補充代碼
}
jsonp({
url: 'http://photo.sina.cn/aj/index',
key: 'jsoncallback',
data: {
page: 1,
cate: 'recommend'
},
callback: function(ret){
console.log(ret)
}
})
背景:
小谷同學(xué)在學(xué)習(xí) ajax 后想做一個簡單的天氣預(yù)報應(yīng)用,但找不到合適的天氣接口,便向小饑求助。應(yīng)小谷的要求小饑寫了一個獲取當(dāng)前訪問者所在地天氣的接口發(fā)布到線上,接口URL: http://api.jirengu.com/weather.php。 小谷把 URL復(fù)制到瀏覽器瀏覽器地址欄按下回車鍵,頁面很神奇地展示了小谷所在城市的天氣數(shù)據(jù)。于是小谷立即寫了個頁面,使用 ajax 調(diào)用當(dāng)前接口,代碼如下:
$.get('http://api.jirengu.com/weather.php')
.then(function(ret){
console.log(ret)
})
小谷打開控制臺,滿心期待想看到返回的天氣數(shù)據(jù),但映入眼簾的是幾行紅色的警告:
「難道小饑在耍我?這人啊真不可信」,小谷憤憤的把報錯截圖甩給了小饑,正要開口責(zé)問,小饑說:「兄弟,這是跨域的問題,你用 JSONP 的方式調(diào)用吧,接口我都給你做了支持,直接使用 callback回調(diào)」 說完便埋入自己的代碼里。
「啥 JSONP? callback? 跨域這個詞貌似聽過,我自己先查查別讓小饑那小子鄙視我」小谷暗暗的想,「原來是跨域啊,你不早說,謝啦」,回到工位小谷趕緊打開谷歌。
同源策略(Same origin Policy)
瀏覽器出于安全方面的考慮,只允許與同域下的接口交互。
同域指的是?
- 同協(xié)議:如都是http或者https
- 同域名:如都是http://jirengu.com/a 和http://jirengu.com/b
- 同端口:如都是80端口
比如: 用戶打開了 頁面: http://jirengu.com/blog, 當(dāng)前頁面下的 js 向 http://jirengu.com/xxx的接口發(fā) ajax 請求,瀏覽器是允許的。但假如向: http://hunger-valley.com/xxx 發(fā)ajax請求則會被瀏覽器阻止掉,因為存在跨域調(diào)用。
「原來如此,怪不得瀏覽器會報錯。跨域不過如此嘛!那 JSONP是什么呢?」
HTML 中 script 標簽可以加載其他域下的js,比如我們經(jīng)常引入一個其他域下線上cdn的jQuery。那如何利用這個特性實現(xiàn)從其他域下獲取數(shù)據(jù)呢?
可以先這樣試試:
<script src="http://api.jirengu.com/weather.php"></script>
這時候會向天氣接口發(fā)送請求獲取數(shù)據(jù),獲取數(shù)據(jù)后做為 js 來執(zhí)行。但這里有個問題, 數(shù)據(jù)是 JSON 格式的數(shù)據(jù),直接作為 JS 運行的話我如何去得到這個數(shù)據(jù)來操作呢?
這樣試試:
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
這個請求到達后端后,后端會去解析callback這個參數(shù)獲取到字符串showData,在發(fā)送數(shù)據(jù)做如下處理:
之前后端返回數(shù)據(jù): {"city": "hangzhou", "weather": "晴天"}
現(xiàn)在后端返回數(shù)據(jù): showData({"city": "hangzhou", "weather": "晴天"})
前端script標簽在加載數(shù)據(jù)后會把 「showData({"city": "hangzhou", "weather": "晴天"})」做為 js 來執(zhí)行,這實際上就是調(diào)用showData這個函數(shù),同時參數(shù)是 {"city": "hangzhou", "weather": "晴天"}。用戶只需要在加載提前在頁面定義好showData這個全局函數(shù),在函數(shù)內(nèi)部處理參數(shù)即可。
<script>
function showData(ret){ console.log(ret); }
</script>
<script src="http://api.jirengu.com/weather.php?callback=showData"></script>
「原來這就是 JSONP(JSON with padding),總結(jié)一下:」
- JSONP是通過 script 標簽加載數(shù)據(jù)的方式去獲取數(shù)據(jù)當(dāng)做 JS 代碼來執(zhí)行
- 提前在頁面上聲明一個函數(shù),函數(shù)名通過接口傳參的方式傳給后臺,后臺解析到函數(shù)名后在原始數(shù)據(jù)上「包裹」這個函數(shù)名,發(fā)送給前端。換句話說,JSONP 需要對應(yīng)接口的后端的配合才能實現(xiàn)。
「原理很簡單,但用起來代碼好丑陋,我做個封裝讓小饑看看」
function jsonp(setting){
setting.data = setting.data || {}
setting.key = setting.key||'callback'
setting.callback = setting.callback||function(){}
setting.data[setting.key] = 'onGetData'
window.onGetData = function(data){
setting.callback (data);
}
var script = document.createElement('script')
var query = []
for(var key in setting.data){
query.push( key + '='+ encodeURIComponent(setting.data[key]) )
}
script.src = setting.url + '?' + query.join('&')
document.head.appendChild(script)
document.head.removeChild(script)
}
jsonp({
url: 'http://api.jirengu.com/weather.php',
callback: function(ret){
console.log(ret)
}
})
jsonp({
url: 'http://photo.sina.cn/aj/index',
key: 'jsoncallback',
data: {
page: 1, cate: 'recommend'
},
callback: function(ret){
console.log(ret)
}
})
19、JSON 是什么?
JSON 絕對不是對象,請問
- 1、JSON 是什么?
- 2、"null" 是 JSON 嗎?
- 3、"1" 是 JSON 嗎?
- 4、JSON 與 JS 對象的區(qū)別是什么?
JSON 是什么?
如果你在 Google 搜索 JSON,那么一眼就會看到 JSON 的官網(wǎng)http://json.org
官網(wǎng)會明明白白的告訴你,JSON 是一種數(shù)據(jù)格式。什么是格式?你可以理解為語法。JSON 的格式靈感來自于 JS 對象字面量的語法,但是兩者沒有任何關(guān)聯(lián)。這種格式可以描述三種數(shù)據(jù)。
1. object(無序的「鍵-值」集合)。
語法如下:
下面三種寫法都可以表示 object
{}
{"key1": "value1"} // string 對應(yīng) "key1",value 對應(yīng) "value1",后面會講
{"key1": "value1", "key2": "value2"}
2. array(有序的值集合)
下面三種寫法都可以表示 array
[]
[1]
[1,"hi"]
3. value
value 對應(yīng)對象語法圖里的 value 和數(shù)組語法圖里 value,value 也可以是 object 或 array,所以下面的語法成立:
{"key1": { "key2" : "value2" } }
[ 1, [ 2, 3 ] ]
另外值還可以是 string、number、true、false 和 null。
string 的語法如下:
你可能奇怪為什么 string 的語法這么復(fù)雜,我舉例來說明你就明白了:
"你好"
""你好""
"\你好\"
"/你好/"
"\b\f\n\r\t特殊符號"
"\u4f60用編碼表示字符"
上面都是合法的 string。這也是「JSON 中字符串必須使用雙引號」的原因——規(guī)定如此。
number 的語法如下,有興趣可以自己走一遍:
另外需要特殊提醒一下,true、false 和 null 都是合法的 JSON。
JSON 和 JS Object 的區(qū)別
簡單來說,兩種沒有任何關(guān)聯(lián)。
JSON 語法的作者是道格拉斯(Douglas Crockford),JS 語法的作者是布蘭登?艾奇(Brendan Eich)。道格拉斯發(fā)明 JSON 的時候參考了 JS 的對象語法,僅此而已。
如果硬要說區(qū)別:
- JSON 的字符串必須用雙引號。
- JSON 無法表示 undefined,只能表示 "undefined"
- JSON 無法表示函數(shù)
- JSON 的對象語法不能有引用
20、JS 中的 Symbol 是什么?
ES 6 引入了一個新的數(shù)據(jù)類型 Symbol,它是用來做什么的呢?
為了說明 Symbol 的作用,我們先來描述一個使用場景。
我們在做一個游戲程序,用戶需要選擇角色的種族。
var race = {
protoss: 'protoss', // 神族
terran: 'terran', // 人族
zerg: 'zerg' // 蟲族
}
function createRole(type){
if(type === race.protoss){創(chuàng)建神族角色}
else if(type === race.terran){創(chuàng)建人族角色}
else if(type === race.zerg){創(chuàng)建蟲族角色}
}
那么用戶選擇種族后,就需要調(diào)用 createRole 來創(chuàng)建角色:
// 傳入字符串
createRole('zerg')
// 或者傳入變量
createRole(race.zerg)
如果使用 createRole(race.zerg),那么聰明的讀者會發(fā)現(xiàn)一個問題:race.protoss、race.terran、race.zerg 的值為多少并不重要。
改為如下寫法,對 createRole(race.zerg) 毫無影響:
var race = {
protoss: 'askdjaslkfjas;lfkjas;flkj', // 神族
terran: ';lkfalksjfl;askjfsfal;skfj', // 人族
zerg: 'qwieqwoirqwoiruoiwqoisrqwroiu' // 蟲族
}
也就是說:
race.zerg 的值是多少無所謂,只要它的值跟 race.protoss 和 race.terran 的值不一樣就行。
Symbol 的用途就是如此:Symbol 可以創(chuàng)建一個獨一無二的值(但并不是字符串)。
用 Symbol 來改寫上面的 race:
var race = {
protoss: Symbol(),
terran: Symbol(),
zerg: Symbol()
}
race.protoss !== race.terran // true
race.protoss !== race.zerg // true
你也可以給每個 Symbol 起一個名字:
var race = {
protoss: Symbol('protoss'),
terran: Symbol('terran'),
zerg: Symbol('zerg')
}
不過這個名字跟 Symbol 的值并沒有關(guān)系,你可以認為這個名字就是個注釋。如下代碼可以證明 Symbol 的名字與值無關(guān):
var a1 = Symbol('a')
var a2 = Symbol('a')
a1 !== a2 // true