每日一題 (一)

本博客轉(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 標(biāo)簽,看起來很像一個數(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] 獲取標(biāo)簽
但是 typeof document.all呐矾,在 IE 10 以下的值為 ‘object’信不,在其他瀏覽器的值均為 ‘undefined’

為何會這樣纤掸?那我們就要從很久很久以前說起了。多久呢浑塞,大概20年前。
那個時候,瀏覽器剛剛出現(xiàn)政己,很多功能不完善酌壕,比如 document.getElementById掏愁,都有很多瀏覽器不支持。甚至連 W3C 都還沒制定出 Web 標(biāo)準(zhǔn)卵牍,這個時候 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')
}

甚至某些程序員直接認(rèn)為 document.all 為真的瀏覽器释牺,就是 IE萝衩。也就是這樣:

if(document.all){
 // IE
}else{
 // 非 IE
}

其他瀏覽器(網(wǎng)景)覺得 IE 4 有的功能,我也要有才行没咙。于是也加上 document.all猩谊,功能一樣,可以獲取元素祭刚。但是呢牌捷,其他瀏覽器又不想被某些程序員認(rèn)為是 IE,于是 typeof document.all 的值定為 undefined涡驮。
  有人說 W3C 標(biāo)準(zhǔn)出來后暗甥,為什么瀏覽器不把 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崔梗?

如果你打開瀏覽器的控制臺夜只,輸入以下代碼并運行


Paste_Image.png

  奇怪,為什么不是 0.3蒜魄。這時可能你會去搜一下(很容易搜到結(jié)果)扔亥,但是如果你了解計算機是如何存儲小數(shù)(準(zhǔn)確地說是浮點數(shù))的話场躯,很容易推斷出原因。
  首先我們要用以下嘗試:

  1.計算機將所有數(shù)據(jù)以二進制的形式存儲
  2.計算機用有限的大小來存儲數(shù)據(jù)(因為現(xiàn)實生活中不存在無限大的內(nèi)存或硬盤)

好的,然后結(jié)合我們的問題來看旅挤。

計算機如何存儲 0.1 和 0.2踢关?

如果你對十進制轉(zhuǎn)二進制有興趣可以看下圖:

Paste_Image.png

  如果看不懂也可以直接看結(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

Paste_Image.png

在 chrome 下運行得到的結(jié)果還是 「undefined」,但在IE8以下賦值是生效的捶牢。
可以看看 MDN 里關(guān)于這部分的描述: undefined - JavaScript
Paste_Image.png

情景2:

  var obj = {};
  obj.undefined = 'hanbaoyi';
  console.log(obj.undefined) //  hanbaoyi

在標(biāo)準(zhǔn)瀏覽器下作為全局作用域下 window的一個屬性, undefined 不可修改巍耗;但對于一個普通對象秋麸,undefined可作為屬性且可以修改。
情景3:

  function fn(){ 
    var undefined = 100; 
    alert(undefined); //chrome: 100, ie8: 100
  }
  fn();

不管是標(biāo)準(zhǔn)瀏覽器炬太,還是老的 IE 瀏覽器灸蟆,在函數(shù)內(nèi)部 undefined 可作為局部變量重新賦值

情景4:

  undefined = 100;
  (function(global){ 
    alert(undefined); //chrome: undefined, ie8: 100
  })(window)

在標(biāo)準(zhǔn)瀏覽器下輸出的結(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女气,會這樣用

Paste_Image.png

但假如 str 這個變量沒聲明就會出現(xiàn)報錯杏慰,用下面的方式會更好一些

Paste_Image.png

有時候我們會看到這種寫法

  if(str === void 0){ 
    console.log('I am real undefind');
  }

那是因為 「void 0」的執(zhí)行結(jié)果永遠是「undefined」, 即使在某些老舊瀏覽器 或者在某個函數(shù)中 undefined被重新賦值,我們?nèi)匀豢梢酝ㄟ^ 「void 0」 得到真正的「undefined」炼鞠。

Paste_Image.png
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>
Paste_Image.png

這里 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>
Paste_Image.png

從上面的例子可以得出如下結(jié)論:

  • 「node.property」的方式不能獲取自定義屬性懂缕,「node.getAttribute()」的方式可以獲取自定義屬性
  • 「node. getAttribute()」獲取自定義屬性忽略屬性的大小寫
  • 「node.getAttribute()」獲取自定義屬性得到的值的類型總是字符串

場景3:

Paste_Image.png

Paste_Image.png

對于上面的例子允跑,HTML中只要出現(xiàn)了disabled 屬性,不管值是什么搪柑,對于 DOM property結(jié)果都是true聋丝, 而對于 attribute 獲取的則是把 HTML 里對應(yīng)屬性的值拿到轉(zhuǎn)換成字符串。
input 標(biāo)簽checked 也有類似的特性工碾。

場景4:

Paste_Image.png

對于 a 鏈接的 href弱睦, 使用 a.getAttribute('href') 就是從 HTML 里獲取對應(yīng)屬性的值轉(zhuǎn)化成字符串,而 a.href 則獲取有意義的真實地址倚喂。

場景5:

Paste_Image.png

對于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)


Paste_Image.png

在其他瀏覽器上效果卻不一樣:


Paste_Image.png

因為 Chrome 這款任性的瀏覽器做了如下限制:
  1. font-size 有一個最小值 12px(不同操作系統(tǒng)、不同語言可能限制不一樣)羡亩,低于 12px 的摩疑,一律按 12px 顯示。理由是 Chrome 認(rèn)為低于 12px 的中文對人類的不友好的畏铆。

  2. 但是允許你把 font-size 設(shè)置為 0.

  3. 這個 12px 的限制用戶是可以自行調(diào)整的雷袋,進入 chrome://settings/fonts 設(shè)置,滾動到最下方你就可以調(diào)整 12px 為其他值辞居。

Paste_Image.png

如果我一定要設(shè)置小于 12px 的字體怎么辦楷怒?

  1. Chrome 29 版本之前,你可以使用 -webkit-text-size-adjust: none; 來解除這個限制速侈。29 版本后率寡,就不能這樣做了。

  2. 你可以先設(shè)置 12px倚搬,然后使用 transform: scale(0.833333) 講元素縮小冶共,效果跟 10px 很接近。不過要注意的是每界,transform: scale chu了縮小 font-size畏陕,也會縮小其他一些屬性刽漂,需要多測試捕发。

7面哥、cookie、session趴樱、localStorage分別是什么馒闷?有什么作用?

<b>
Cookie 是什么</b>

  1. Cookie 是瀏覽器訪問服務(wù)器后叁征,服務(wù)器傳給瀏覽器的一段數(shù)據(jù)纳账。用來記錄某些當(dāng)頁面關(guān)閉或者刷新后仍然需要記錄的信息。在控制臺用 「document.cookie」查看你當(dāng)前正在瀏覽的網(wǎng)站的cookie捺疼。

  2. cookie可以使用 js 在瀏覽器直接設(shè)置(用于記錄不敏感信息疏虫,如用戶名), 也可以在服務(wù)端通使用 HTTP 協(xié)議規(guī)定的 set-cookie 來讓瀏覽器種下cookie,這是最常見的做法。(打開一個網(wǎng)站卧秘,清除全部cookie呢袱,然后刷新頁面,在network的Response headers試試找一找set-cookie吧)

  3. 每次網(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」起趾,表示購物車?yán)镉?A1 和 A2 兩樣商品了诗舰。這樣一來,當(dāng)用戶關(guān)閉網(wǎng)頁训裆,過三天再打開網(wǎng)頁的時候眶根,依然可以看到 A1、A2 躺在購物車?yán)锉吡穑驗闉g覽器并不會無緣無故地刪除這個 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ū)別微驶?
Paste_Image.png

短答案:

  1. 基本類型變量存的是值,復(fù)雜類型的變量存的是內(nèi)存地址开睡。

  2. 基本類型在賦值的時候拷貝值因苹,復(fù)雜類型在賦值的時候只拷貝地址,不拷貝值篇恒。

長答案:

在講解這個問題之前扶檐,我們需要看一下計算機是如何儲存變量的。
如果計算機要存儲一個整數(shù)胁艰,它可以用 32 個位(bit)來存儲蘸秘,一個小數(shù),可以用 64 位來存儲蝗茁。但不管怎樣,位數(shù)都是固定的寻咒。

假設(shè)有如下 JS 代碼:

  var a = 1.23;
  var b = 3.14;

對應(yīng)的內(nèi)存是這樣:


Paste_Image.png

接下來加幾行代碼:

  var obj = {}
  var c = 1.628

現(xiàn)在哮翘,計算機遇到一個難題,到底用多少位來存 obj 呢毛秘?


Paste_Image.png

用固定位數(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)該怎么存儲:


Paste_Image.png

如圖垒棋, 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é)果為:


Paste_Image.png

引用類型的賦值舉例:

  var a = {name: 'a'}
  var b = a

對應(yīng)內(nèi)存結(jié)果為:


Paste_Image.png

也就是說运褪,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 開口準(zhǔn)備說說技術(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)對象迈喉,一個標(biāo)題(現(xiàn)在被忽略了)绍刮,以及一個可選的URL地址。
state object — 狀態(tài)對象是一個由 pushState()方法創(chuàng)建的與歷史紀(jì)錄相關(guān)的JS對象弊添。

title — 火狐瀏覽器現(xiàn)在已經(jīng)忽略此參數(shù)录淡,將來也許可能被使用∮桶樱考慮到將來有可能的改變嫉戚,傳遞一個空字符串是安全的做法。當(dāng)然澈圈,你可以傳遞一個短標(biāo)題給你要轉(zhuǎn)變成的狀態(tài)彬檀。

URL — 這個參數(shù)提供了新歷史紀(jì)錄的地址。請注意瞬女,瀏覽器在調(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ù)')}()
    
Paste_Image.png
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晓褪,如下:


Paste_Image.png

那么怎么解決這個問題呢?用立即執(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 的理解?

問題
有如下代碼:

Paste_Image.png
Paste_Image.png
Paste_Image.png
  • 問題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的原型圖:

Paste_Image.png
  • (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 中的閉包是什么?

大名鼎鼎的閉包鲤孵!這一題終于來了壶栋,面試必問。請用自己的話簡述

  • 什么是「閉包」普监。
  • 「閉包」的作用是什么贵试。

首先來簡述什么是閉包

Paste_Image.png

  為簡明起見,上面代碼不使用全局變量凯正,你可以假設(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.死一條命() 來讓角色掉一條命袋励。

看到閉包在哪了嗎侥啤?

Paste_Image.png

  閉包是 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 的縮寫挎峦。

Paste_Image.png

簡單來說

  1. 正常用戶 A 提交正常內(nèi)容香追,顯示在另一個用戶 B 的網(wǎng)頁上,沒有問題坦胶。
  2. 惡意用戶 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í)行,有兩個可能

  1. 后臺模板問題
    <p>
    評論內(nèi)容:<?php echo $content; ?>
    </p>
    $content 的內(nèi)容鼎兽,沒有經(jīng)過任何過濾答姥,原樣輸出。
    要解決這個原因谚咬,只需要后臺輸出的時候鹦付,將可疑的符號 < 符號變成 '&lt'; (HTML實體)就行。

  2. 前端代碼問題
    $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 標(biāo)簽丝蹭,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做個比較盐捷,如果相等表示請求是合法的。

Paste_Image.png

Paste_Image.png

(上圖是某電商網(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 分為兩類瞪讼。

  1. 靜態(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 文件,原樣返回給用戶姿染。
  2. 動態(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 是一個前端必備的小技巧哦)

Paste_Image.png

假設(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ù),但映入眼簾的是幾行紅色的警告:


Paste_Image.png

「難道小饑在耍我良蒸?這人啊真不可信」技扼,小谷憤憤的把報錯截圖甩給了小饑伍玖,正要開口責(zé)問嫩痰,小饑說:「兄弟,這是跨域的問題窍箍,你用 JSONP 的方式調(diào)用吧串纺,接口我都給你做了支持,直接使用 callback回調(diào)」 說完便埋入自己的代碼里椰棘。

「啥 JSONP? callback? 跨域這個詞貌似聽過纺棺,我自己先查查別讓小饑那小子鄙視我」小谷暗暗的想,「原來是跨域啊邪狞,你不早說祷蝌,謝啦」,回到工位小谷趕緊打開谷歌帆卓。

同源策略(Same origin Policy)
瀏覽器出于安全方面的考慮巨朦,只允許與同域下的接口交互米丘。
同域指的是?

比如: 用戶打開了 頁面: 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 標(biāo)簽可以加載其他域下的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標(biāo)簽在加載數(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é)一下:」

  1. JSONP是通過 script 標(biāo)簽加載數(shù)據(jù)的方式去獲取數(shù)據(jù)當(dāng)做 JS 代碼來執(zhí)行
  2. 提前在頁面上聲明一個函數(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(無序的「鍵-值」集合)。
語法如下:

Paste_Image.png

下面三種寫法都可以表示 object

{}
{"key1": "value1"} // string 對應(yīng) "key1"褐桌,value 對應(yīng) "value1"衰抑,后面會講
{"key1": "value1", "key2": "value2"}

2. array(有序的值集合)

Paste_Image.png

下面三種寫法都可以表示 array

[]
[1]
[1,"hi"]

3. value

Paste_Image.png

value 對應(yīng)對象語法圖里的 value 和數(shù)組語法圖里 value,value 也可以是 object 或 array荧嵌,所以下面的語法成立:

{"key1": { "key2" : "value2" } }
[ 1, [ 2, 3 ] ]

另外值還可以是 string呛踊、number、true啦撮、false 和 null谭网。
string 的語法如下:

Paste_Image.png

你可能奇怪為什么 string 的語法這么復(fù)雜,我舉例來說明你就明白了:

"你好"
""你好""
"\你好\"
"/你好/"
"\b\f\n\r\t特殊符號"
"\u4f60用編碼表示字符"

上面都是合法的 string赃春。這也是「JSON 中字符串必須使用雙引號」的原因——規(guī)定如此愉择。

number 的語法如下,有興趣可以自己走一遍:

Paste_Image.png

另外需要特殊提醒一下聘鳞,true薄辅、false 和 null 都是合法的 JSON要拂。

JSON 和 JS Object 的區(qū)別

簡單來說抠璃,兩種沒有任何關(guān)聯(lián)。

JSON 語法的作者是道格拉斯(Douglas Crockford)脱惰,JS 語法的作者是布蘭登?艾奇(Brendan Eich)搏嗡。道格拉斯發(fā)明 JSON 的時候參考了 JS 的對象語法,僅此而已。

如果硬要說區(qū)別:

  1. JSON 的字符串必須用雙引號采盒。
  1. JSON 無法表示 undefined旧乞,只能表示 "undefined"
  2. JSON 無法表示函數(shù)
  3. 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

Paste_Image.png

你也可以給每個 Symbol 起一個名字:

var race = {
protoss: Symbol('protoss'),
terran: Symbol('terran'),
zerg: Symbol('zerg')
}

不過這個名字跟 Symbol 的值并沒有關(guān)系量没,你可以認(rèn)為這個名字就是個注釋。如下代碼可以證明 Symbol 的名字與值無關(guān):

var a1 = Symbol('a')
var a2 = Symbol('a')
a1 !== a2 // true

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末突想,一起剝皮案震驚了整個濱河市殴蹄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌猾担,老刑警劉巖袭灯,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異绑嘹,居然都是意外死亡稽荧,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進店門工腋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姨丈,“玉大人,你說我怎么就攤上這事擅腰◇瘢” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵趁冈,是天一觀的道長歼争。 經(jīng)常有香客問我拜马,道長,這世上最難降的妖魔是什么沐绒? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任俩莽,我火速辦了婚禮,結(jié)果婚禮上乔遮,老公的妹妹穿的比我還像新娘扮超。我一直安慰自己,他們只是感情好蹋肮,可當(dāng)我...
    茶點故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布瞒津。 她就那樣靜靜地躺著,像睡著了一般括尸。 火紅的嫁衣襯著肌膚如雪巷蚪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天濒翻,我揣著相機與錄音屁柏,去河邊找鬼。 笑死有送,一個胖子當(dāng)著我的面吹牛淌喻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雀摘,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼裸删,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了阵赠?” 一聲冷哼從身側(cè)響起涯塔,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎清蚀,沒想到半個月后匕荸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡枷邪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年榛搔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片东揣。...
    茶點故事閱讀 40,030評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡践惑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嘶卧,到底是詐尸還是另有隱情尔觉,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布脸候,位于F島的核電站穷娱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏运沦。R本人自食惡果不足惜泵额,卻給世界環(huán)境...
    茶點故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望携添。 院中可真熱鬧嫁盲,春花似錦、人聲如沸烈掠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽左敌。三九已至瘾蛋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間矫限,已是汗流浹背哺哼。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留叼风,地道東北人取董。 一個月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像无宿,于是被迫代替她去往敵國和親茵汰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容