題 1 :什麼是防抖和節流?有什麼區別?如何實現?
防抖:觸發高頻事件後n秒內函數只會執行一次,如果n秒內高頻事件再次被觸發,則重新計算時間
思路:每次觸發事件時都取消之前的延時調用方法
節流:高頻事件觸發,但在 n 秒內只會執行一次,所以節流會稀釋函數的執行頻率
思路:每次觸發事件時都判斷當前是否有等待執行的延時函數
題 2 :get請求傳參長度的誤區、get和post請求在緩存方面的區別
誤區:我們經常說get請求參數的大小存在限制,而post請求的參數大小是無限制的。
實際上 HTTP 協議從未規定 GET/POST 的請求長度限制是多少。對 get 請求參數的限制是來源與瀏覽器或 web 服務器,瀏覽器或 web 服務器限制了 url 的長度。爲了明確這個概念,我們必須再次強調下面幾點:
-
HTTP 協議未規定 GET 和 POST 的長度限制
-
GET 的最大長度顯示是因爲瀏覽器和 web 服務器限制了 URI 的長度
-
不同的瀏覽器和 WEB 服務器,限制的最大長度不一樣
-
要支持 IE,則最大長度爲 2083byte,若只支持 Chrome,則最大長度 8182byte
補充補充一個 get 和 post 在緩存方面的區別:
-
get 請求類似於查找的過程,用戶獲取數據,可以不用每次都與數據庫連接,所以可以使用緩存。
-
post 不同,post 做的一般是修改和刪除的工作,所以必須與數據庫交互,所以不能使用緩存。因此 get 請求適合於請求緩存。
題 3:模塊化發展歷程
可從IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、<script type="module"> 這幾個角度考慮。
模塊化主要是用來抽離公共代碼,隔離作用域,避免變量衝突等。
IIFE:使用自執行函數來編寫模塊化,特點:在一個單獨的函數作用域中執行代碼,避免變量衝突。
AMD:使用 requireJS 來編寫模塊化,特點:依賴必須提前聲明好。
CMD:使用 seaJS 來編寫模塊化,特點:支持動態引入依賴文件。
CommonJS:nodejs 中自帶的模塊化。
UMD:兼容 AMD,CommonJS 模塊化語法。
webpack(require.ensure):webpack 2.x 版本中的代碼分割。
ES Modules:ES6 引入的模塊化,支持 import 來引入另一個 js 。
題 4:npm 模塊安裝機制,爲什麼輸入 npm install 就可以自動安裝對應的模塊?
1. npm 模塊安裝機制:
-
發出
npm install
命令 -
查詢node_modules目錄之中是否已經存在指定模塊
-
若存在,不再重新安裝
-
若不存在
-
-
-
-
npm 向 registry 查詢模塊壓縮包的網址
-
下載壓縮包,存放在根目錄下的
.npm
目錄裏 -
解壓壓縮包到當前項目的
node_modules
目錄
-
-
2. npm 實現原理
輸入 npm install 命令並敲下回車後,會經歷如下幾個階段(以 npm 5.5.1 爲例):
-
執行工程自身 preinstall
當前 npm 工程如果定義了 preinstall 鉤子此時會被執行。
-
確定首層依賴模塊
首先需要做的是確定工程中的首層依賴,也就是 dependencies 和 devDependencies 屬性中直接指定的模塊(假設此時沒有添加 npm install 參數)。
工程本身是整棵依賴樹的根節點,每個首層依賴模塊都是根節點下面的一棵子樹,npm 會開啓多進程從每個首層依賴模塊開始逐步尋找更深層級的節點。
-
獲取模塊
獲取模塊是一個遞歸的過程,分爲以下幾步:
-
-
獲取模塊信息。在下載一個模塊之前,首先要確定其版本,這是因爲 package.json 中往往是 semantic version(semver,語義化版本)。此時如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有該模塊信息直接拿即可,如果沒有則從倉庫獲取。如 packaeg.json 中某個包的版本是 ^1.1.0,npm 就會去倉庫中獲取符合 1.x.x 形式的最新版本。
-
獲取模塊實體。上一步會獲取到模塊的壓縮包地址(resolved 字段),npm 會用此地址檢查本地緩存,緩存中有就直接拿,如果沒有則從倉庫下載。
-
查找該模塊依賴,如果有依賴則回到第1步,如果沒有則停止。
-
-
模塊扁平化(dedupe)
上一步獲取到的是一棵完整的依賴樹,其中可能包含大量重複模塊。比如 A 模塊依賴於 loadsh,B 模塊同樣依賴於 lodash。在 npm3 以前會嚴格按照依賴樹的結構進行安裝,因此會造成模塊冗餘。
從 npm3 開始默認加入了一個 dedupe的過程。它會遍歷所有節點,逐個將模塊放在根節點下面,也就是 node-modules 的第一層。當發現有重複模塊時,則將其丟棄。
這裏需要對重複模塊進行一個定義,它指的是模塊名相同且 semver兼容。每個 semver 都對應一段版本允許範圍,如果兩個模塊的版本允許範圍存在交集,那麼就可以得到一個兼容版本,而不必版本號完全一致,這可以使更多冗餘模塊在 dedupe 過程中被去掉。
比如 node-modules 下 foo 模塊依賴 lodash@^1.0.0,bar 模塊依賴 lodash@^1.1.0,則 ^1.1.0 爲兼容版本。
而當 foo 依賴 lodash@^2.0.0,bar 依賴 lodash@^1.1.0,則依據 semver 的規則,二者不存在兼容版本。會將一個版本放在 node_modules 中,另一個仍保留在依賴樹裏。
-
安裝模塊
這一步將會更新工程中的 node_modules,並執行模塊中的生命週期函數(按照 preinstall、install、postinstall 的順序)。
-
執行工程自身生命周期
當前 npm 工程如果定義了鉤子此時會被執行(按照 install、postinstall、prepublish、prepare 的順序)。
最後一步是生成或更新版本描述文件,npm install 過程完成。
題 5:ES5 的繼承和 ES6 的繼承有什麼區別?
ES5的繼承時通過prototype或構造函數機制來實現。ES5的繼承實質上是先創建子類的實例對象,然後再將父類的方法添加到this上(Parent.apply(this))。
ES6的繼承機制完全不同,實質上是先創建父類的實例對象this(所以必須先調用父類的super()方法),然後再用子類的構造函數修改this。
具體的:ES6通過class關鍵字定義類,裏面有構造方法,類之間通過extends關鍵字實現繼承。子類必須在constructor方法中調用super方法,否則新建實例報錯。因爲子類沒有自己的this對象,而是繼承了父類的this對象,然後對其進行加工。如果不調用super方法,子類得不到this對象。
ps:super關鍵字指代父類的實例,即父類的this對象。在子類構造函數中,調用super後,纔可使用this關鍵字,否則報錯。
題 6:setTimeout、Promise、Async/Await 的區別
移步此處查看答案
https://gongchenghuigch.github.io/2019/09/14/awat/
題 7:定時器的執行順序或機制?
因爲js是單線程的,瀏覽器遇到setTimeout或者setInterval會先執行完當前的代碼塊,在此之前會把定時器推入瀏覽器的待執行事件隊列裏面,等到瀏覽器執行完當前代碼之後會看一下事件隊列裏面有沒有任務,有的話才執行定時器的代碼。所以即使把定時器的時間設置爲0還是會先執行當前的一些代碼。
輸出結果:
題 8:
['1','2','3'].map(parseInt) 輸出什麼,爲什麼?
輸出:[1, NaN, NaN]
-
首先讓我們回顧一下,map 函數的第一個參數 callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
這個 callback 一共可以接收三個參數,其中第一個參數代表當前被處理的元素,而第二個參數代表該元素的索引。
而 parseInt 則是用來解析字符串的,使字符串成爲指定基數的整數。
parseInt(string, radix)
接收兩個參數,第一個表示被處理的值(字符串),第二個表示爲解析時的基數。
-
瞭解這兩個函數後,我們可以模擬一下運行情況
-
parseInt('1', 0) //radix爲0時,且string參數不以“0x”和“0”開頭時,按照 10 爲基數處理。這個時候返回 1
-
parseInt('2', 1) //基數爲1(1進制)表示的數中,最大值小於2,所以無法解析,返回NaN
-
parseInt('3', 2) //基數爲 2(2進制)表示的數中,最大值小於 3,所以無法解析,返回 NaN
-
map 函數返回的是一個數組,所以最後結果爲[1, NaN, NaN]
題 9:Doctype 作用? 嚴格模式與混雜模式如何區分?它們有何意義?
Doctype聲明於文檔最前面,告訴瀏覽器以何種方式來渲染頁面,這裏有兩種模式,嚴格模式和混雜模式。
-
嚴格模式的排版和 JS 運作模式是 以該瀏覽器支持的最高標準運行。
-
混雜模式,向後兼容,模擬老式瀏覽器,防止瀏覽器無法兼容頁面。
題 10:fetch 發送 2 次請求的原因
fetch 發送 post 請求的時候,總是發送 2 次,第一次狀態碼是 204,第二次才成功?
原因很簡單,因爲你用fetch的post請求的時候,導致fetch 第一次發送了一個Options請求,詢問服務器是否支持修改的請求頭,如果服務器支持,則在第二次中發送真正的請求。
題 11 :TCP 三次握手和四次揮手
三次握手之所以是三次是保證 client 和 server 均讓對方知道自己的接收和發送能力沒問題而保證的最小次數。
第一次 client => server 只能 server 判斷出 client 具備發送能力
第二次 server => client client 就可以判斷出 server 具備發送和接受能力。此時 client 還需讓 server 知道自己接收能力沒問題於是就有了第三次
第三次 client => server 雙方均保證了自己的接收和發送能力沒有問題
其中,爲了保證後續的握手是爲了應答上一個握手,每次握手都會帶一個標識 seq,後續的 ACK 都會對這個 seq 進行加一來進行確認。
題 12 :img iframe script 來發送跨域請求有什麼優缺點?
-
iframe
優點:跨域完畢之後 DOM 操作和互相之間的 JavaScript 調用都是沒有問題的
缺點:1.若結果要以 URL 參數傳遞,這就意味着在結果數據量很大的時候需要分割傳遞,巨煩。2.還有一個是 iframe 本身帶來的,母頁面和 iframe 本身的交互本身就有安全性限制。
-
script
優點:可以直接返回 json 格式的數據,方便處理
缺點:只接受 GET 請求方式
-
圖片ping
優點:可以訪問任何 url,一般用來進行點擊追蹤,做頁面分析常用的方法
缺點:不能訪問響應文本,只能監聽是否響應
題 13:Cookie、sessionStorage、localStorage 的區別
共同點:都是保存在瀏覽器端,並且是同源的
-
Cookie:cookie數據始終在同源的http請求中攜帶(即使不需要),即cookie在瀏覽器和服務器間來回傳遞。而sessionStorage和localStorage不會自動把數據發給服務器,僅在本地保存。cookie數據還有路徑(path)的概念,可以限制cookie只屬於某個路徑下,存儲的大小很小隻有4K左右。(key:可以在瀏覽器和服務器端來回傳遞,存儲容量小,只有大約4K左右)
-
sessionStorage:僅在當前瀏覽器窗口關閉前有效,自然也就不可能持久保持,localStorage:始終有效,窗口或瀏覽器關閉也一直保存,因此用作持久數據;cookie只在設置的cookie過期時間之前一直有效,即使窗口或瀏覽器關閉。(key:本身就是一個回話過程,關閉瀏覽器後消失,session爲一個回話,當頁面不同即使是同一頁面打開兩次,也被視爲同一次回話)
-
localStorage:localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。(key:同源窗口都會共享,並且不會失效,不管窗口或者瀏覽器關閉與否都會始終生效)
題 14:Cookie 如何防範 XSS 攻擊
XSS(跨站腳本攻擊)是指攻擊者在返回的HTML中嵌入javascript腳本,爲了減輕這些攻擊,需要在HTTP頭部配上,set-cookie:
-
httponly-這個屬性可以防止XSS,它會禁止javascript腳本來訪問cookie。
-
secure - 這個屬性告訴瀏覽器僅在請求爲https的時候發送cookie。
結果應該是這樣的:Set-Cookie=...
題 15:瀏覽器和 Node 事件循環的區別?
其中一個主要的區別在於瀏覽器的 event loop 和 nodejs 的 event loop 在處理異步事件的順序是不同的,nodejs 中有 micro event;其中Promise屬於micro event 該異步事件的處理順序就和瀏覽器不同.nodejs V11.0以上 這兩者之間的順序就相同了。
題 16:簡述 HTTPS 中間人攻擊
HTTPS 協議由 HTTP + SSL 協議構成,具體的鏈接過程可參考SSL或TLS握手的概述https://github.com/lvwxx/blog/issues/3
中間人攻擊過程如下:
-
服務器向客戶端發送公鑰。
-
攻擊者截獲公鑰,保留在自己手上。
-
然後攻擊者自己生成一個【僞造的】公鑰,發給客戶端。
-
客戶端收到僞造的公鑰後,生成加密hash值發給服務器。
-
攻擊者獲得加密hash值,用自己的私鑰解密獲得真祕鑰。
-
同時生成假的加密hash值,發給服務器。
-
服務器用私鑰解密獲得假祕鑰。
-
服務器用加祕鑰加密傳輸信息
防範方法:服務端在發送瀏覽器的公鑰中加入 CA 證書,瀏覽器可以驗證 CA 證書的有效性。
題 17:說幾條 Web 前端優化策略
(1) 減少 HTTP 請求數
(2) 從設計實現層面簡化頁面
(3) 合理設置 HTTP 緩存
(4) 資源合併與壓縮
(5) CSS Sprites
(6) Inline Images
(7) Lazy Load Images
題 18:你瞭解的瀏覽器的重繪和迴流導致的性能問題
重繪(Repaint)和迴流(Reflow)
重繪和迴流是渲染步驟中的一小節,但是這兩個步驟對於性能影響很大。
-
重繪是當節點需要更改外觀而不會影響佈局的,比如改變
color
就叫稱爲重繪 -
迴流是佈局或者幾何屬性需要改變就稱爲迴流。
迴流必定會發生重繪,重繪不一定會引發迴流。迴流所需的成本比重繪高的多,改變深層次的節點很可能導致父節點的一系列迴流。
所以以下幾個動作可能會導致性能問題:
-
改變 window 大小
-
改變字體
-
添加或刪除樣式
-
文字改變
-
定位或者浮動
-
盒模型
很多人不知道的是,重繪和迴流其實和 Event loop 有關。
-
當 Event loop 執行完 Microtasks 後,會判斷 document 是否需要更新。因爲瀏覽器是 60Hz 的刷新率,每 16ms 纔會更新一次。
-
然後判斷是否有
resize
或者scroll
,有的話會去觸發事件,所以resize
和scroll
事件也是至少 16ms 纔會觸發一次,並且自帶節流功能。 -
判斷是否觸發了 media query
-
更新動畫並且發送事件
-
判斷是否有全屏操作事件
-
執行
requestAnimationFrame
回調 -
執行
IntersectionObserver
回調,該方法用於判斷元素是否可見,可以用於懶加載上,但是兼容性不好 -
更新界面
-
以上就是一幀中可能會做的事情。如果在一幀中有空閒時間,就會去執行
requestIdleCallback
回調。
減少重繪和迴流
-
使用
translate
替代top
<div class="test"></div>
<style>
.test {
position: absolute;
top: 10px;
width: 100px;
height: 100px;
background: red;
}
</style>
<script>
setTimeout(() => {
// 引起迴流
document.querySelector('.test').style.top = '100px'
}, 1000)
</script> -
使用
visibility
替換display: none
,因爲前者只會引起重繪,後者會引發迴流(改變了佈局)把 DOM 離線後修改,比如:先把 DOM 給
display:none
(有一次 Reflow),然後你修改100次,然後再把它顯示出來不要把 DOM 結點的屬性值放在一個循環裏當成循環裏的變量
for(let i = 0; i < 1000; i++) {
// 獲取 offsetTop 會導致迴流,因爲需要去獲取正確的值
console.log(document.querySelector('.test').style.offsetTop)
} -
不要使用 table 佈局,可能很小的一個小改動會造成整個 table 的重新佈局
-
動畫實現的速度的選擇,動畫速度越快,迴流次數越多,也可以選擇使用
requestAnimationFrame
-
CSS 選擇符從右往左匹配查找,避免 DOM 深度過深
-
將頻繁運行的動畫變爲圖層,圖層能夠阻止該節點回流影響別的元素。比如對於
video
標籤,瀏覽器會自動將該節點變爲圖層。
題 19:寫 React / Vue 項目時爲什麼要在列表組件中寫 key,其作用是什麼?
vue 和 react 都是採用 diff 算法來對比新舊虛擬節點,從而更新節點。在 vue 的 diff函數中(建議先了解一下 diff 算法過程)。
在交叉對比中,當新節點跟舊節點頭尾交叉對比
沒有結果時,會根據新節點的 key 去對比舊節點數組中的 key,從而找到相應舊節點(這裏對應的是一個 key => index 的 map 映射)。如果沒找到就認爲是一個新增節點。而如果沒有 key,那麼就會採用遍歷查找的方式去找到對應的舊節點。一種一個 map 映射,另一種是遍歷查找。相比而言,map 映射的速度更快。
題 20:React 中 setState 什麼時候是同步的,什麼時候是異步的?
在React中,如果是由React引發的事件處理(比如通過onClick引發的事件處理),調用setState不會同步更新this.state,除此之外的setState調用會同步執行this.state。所謂“除此之外”,指的是繞過React通過addEventListener直接添加的事件處理函數,還有通過setTimeout/setInterval產生的異步調用。
原因:在 React 的 setState 函數實現中,會根據一個變量isBatchingUpdates 判斷是直接更新 this.state 還是放到隊列中回頭再說,而 isBatchingUpdates 默認是 false,也就表示 setState 會同步更新 this.state,但是,有一個函數 batchedUpdates,這個函數會把 isBatchingUpdates 修改爲 true,而當 React 在調用事件處理函數之前就會調用這個 batchedUpdates,造成的後果,就是由 React 控制的事件處理過程 setState 不會同步更新 this.state。
題 21:爲什麼虛擬 dom 會提高性能?
虛擬 dom 相當於在 js 和真實 dom 中間加了一個緩存,利用 dom diff 算法避免了沒有必要的 dom 操作,從而提高性能。
具體實現步驟如下:
1. 用 JavaScript 對象結構表示 DOM 樹的結構;然後用這個樹構建一個真正的 DOM 樹,插到文檔當中。
2. 當狀態變更的時候,重新構造一棵新的對象樹。然後用新的樹和舊的樹進行比較,記錄兩棵樹差異。
把 2 所記錄的差異應用到步驟 1 所構建的真正的DOM樹上,視圖就更新了。
題 22:清除浮動的方式有哪些?比較好的是哪一種?
常用的一般爲三種:
.clearfix
, clear:both
,overflow:hidden
。
比較好是 .clearfix
,僞元素萬金油版本,後兩者有侷限性。
clear:both
:若是用在同一個容器內相鄰元素上,那是賊好的,有時候在容器外就有些問題了, 比如相鄰容器的包裹層元素塌陷
overflow:hidden
:這種若是用在同個容器內,可以形成 BFC
避免浮動造成的元素塌陷
題 23:分析比較 opacity: 0、visibility: hidden、display: none 優劣和適用場景
結構:
display:none: 會讓元素完全從渲染樹中消失,渲染的時候不佔據任何空間, 不能點擊,
visibility: hidden:不會讓元素從渲染樹消失,渲染元素繼續佔據空間,只是內容不可見,不能點擊
opacity: 0: 不會讓元素從渲染樹消失,渲染元素繼續佔據空間,只是內容不可見,可以點擊
繼承:
display: none和opacity: 0:是非繼承屬性,子孫節點消失由於元素從渲染樹消失造成,通過修改子孫節點屬性無法顯示。
visibility: hidden:是繼承屬性,子孫節點消失由於繼承了hidden,通過設置visibility: visible;可以讓子孫節點顯式。
性能:
displaynone : 修改元素會造成文檔迴流,讀屏器不會讀取display: none元素內容,性能消耗較大
visibility:hidden: 修改元素只會造成本元素的重繪,性能消耗較少讀屏器讀取visibility: hidden元素內容
opacity: 0 :修改元素會造成重繪,性能消耗較少
聯繫:它們都能讓元素不可見
題 24:css sprite 是什麼,有什麼優缺點
概念:將多個小圖片拼接到一個圖片中。通過 background-position 和元素尺寸調節需要顯示的背景圖案。
優點:
-
減少 HTTP 請求數,極大地提高頁面加載速度
-
增加圖片信息重複度,提高壓縮比,減少圖片大小
-
更換風格方便,只需在一張或幾張圖片上修改顏色或樣式即可實現
缺點:
-
圖片合併麻煩
-
維護麻煩,修改一個圖片可能需要重新佈局整個圖片,樣式
題 25:link 與 @import 的區別
-
link
是 HTML 方式,@import
是 CSS 方式 -
link
最大限度支持並行下載,@import
過多嵌套導致串行下載,出現FOUC -
link
可以通過rel="alternate stylesheet"
指定候選樣式 -
瀏覽器對
link
支持早於@import
,可以使用@import
對老瀏覽器隱藏樣式 -
@import
必須在樣式規則之前,可以在 css 文件中引用其他文件 -
總體來說:link 優於@import
題 26:容器包含若干浮動元素時如何清理浮動
-
容器元素閉合標籤前添加額外元素並設置
clear: both
-
父元素觸發塊級格式化上下文(見塊級可視化上下文部分)
-
設置容器元素僞元素進行清理推薦的清理浮動方法
題 27:display,float,position 的關係
-
如果
display
爲 none,那麼 position 和 float 都不起作用,這種情況下元素不產生框。 -
否則,如果 position 值爲 absolute 或者 fixed,框就是絕對定位的,float 的計算值爲 none,display 根據下面的表格進行調整。
-
否則,如果 float 不是 none,框是浮動的,display 根據下表進行調整。
-
否則,如果元素是根元素,display 根據下表進行調整。
-
其他情況下 display 的值爲指定值 總結起來:絕對定位、浮動、根元素都需要調整display。
題 28:JS 的四種設計模式
工廠模式:簡單的工廠模式可以理解爲解決多個相似的問題;
單例模式:只能被實例化(構造函數給實例添加屬性與方法)一次;
沙箱模式:將一些函數放到自執行函數裏面,但要用閉包暴露接口,用變量接收暴露的接口,再調用裏面的值,否則無法使用裏面的值;
發佈者訂閱模式:就例如如我們關注了某一個公衆號,然後他對應的有新的消息就會給你推送,代碼實現邏輯是用數組存儲訂閱者, 發佈者回調函數裏面通知的方式是遍歷訂閱者數組,並將發佈者內容傳入訂閱者數組。
題 29:下面代碼的輸出是什麼?
const obj = { 1: "a", 2: "b", 3: "c" };
const set = new Set([1, 2, 3, 4, 5]);
obj.hasOwnProperty("1");
obj.hasOwnProperty(1);
set.has("1");
set.has(1);
true
true
false
true
所有對象鍵(不包括Symbols
)都會被存儲爲字符串,即使你沒有給定字符串類型的鍵。這就是爲什麼obj.hasOwnProperty('1')
也返回true
。
上面的說法不適用於Set
。在我們的Set
中沒有“1”
:set.has('1')
返回false
。它有數字類型1
,set.has(1)
返回true
。
題 30:下面代碼的輸出是什麼?
// example 1
var a={}, b='123', c=123;
a[b]='b';
a[c]='c';
console.log(a[b]);
---------------------
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
a[b]='b';
a[c]='c';
console.log(a[b]);
---------------------
// example 3
var a={}, b={key:'123'}, c={key:'456'};
a[b]='b';
a[c]='c';
console.log(a[b]);
這題考察的是對象的鍵名的轉換。
-
對象的鍵名只能是字符串和 Symbol 類型。
-
其他類型的鍵名會被轉換成字符串類型。
-
對象轉字符串默認會調用 toString 方法。
// example 1
var a={}, b='123', c=123;
a[b]='b';
// c 的鍵名會被轉換成字符串'123',這裏會把 b 覆蓋掉。
a[c]='c';
// 輸出 c
console.log(a[b]);
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
// b 是 Symbol 類型,不需要轉換。
a[b]='b';
// c 是 Symbol 類型,不需要轉換。任何一個 Symbol 類型的值都是不相等的,所以不會覆蓋掉 b。
a[c]='c';
// 輸出 b
console.log(a[b]);
// example 3