社招中級前端筆試面試題總結

語言: CN / TW / HK

typeof null 的結果是什麼,為什麼?

typeof null 的結果是Object。

在 JavaScript 第一個版本中,所有值都儲存在 32 位的單元中,每個單元包含一個小的 型別標籤(1-3 bits) 以及當前要儲存值的真實資料。型別標籤儲存在每個單元的低位中,共有五種資料型別:

```javascript 000: object - 當前儲存的資料指向一個物件。 1: int - 當前儲存的資料是一個 31 位的有符號整數。 010: double - 當前儲存的資料指向一個雙精度的浮點數。 100: string - 當前儲存的資料指向一個字串。 110: boolean - 當前儲存的資料是布林值。

```

如果最低位是 1,則型別標籤標誌位的長度只有一位;如果最低位是 0,則型別標籤標誌位的長度佔三位,為儲存其他四種資料型別提供了額外兩個 bit 的長度。

有兩種特殊資料型別:

  • undefined的值是 (-2)30(一個超出整數範圍的數字);
  • null 的值是機器碼 NULL 指標(null 指標的值全是 0)

那也就是說null的型別標籤也是000,和Object的型別標籤一樣,所以會被判定為Object。

常見瀏覽器所用核心

(1) IE 瀏覽器核心:Trident 核心,也是俗稱的 IE 核心;

(2) Chrome 瀏覽器核心:統稱為 Chromium 核心或 Chrome 核心,以前是 Webkit 核心,現在是 Blink核心;

(3) Firefox 瀏覽器核心:Gecko 核心,俗稱 Firefox 核心;

(4) Safari 瀏覽器核心:Webkit 核心;

(5) Opera 瀏覽器核心:最初是自己的 Presto 核心,後來加入谷歌大軍,從 Webkit 又到了 Blink 核心;

(6) 360瀏覽器、獵豹瀏覽器核心:IE + Chrome 雙核心;

(7) 搜狗、遨遊、QQ 瀏覽器核心:Trident(相容模式)+ Webkit(高速模式);

(8) 百度瀏覽器、世界之窗核心:IE 核心;

(9) 2345瀏覽器核心:好像以前是 IE 核心,現在也是 IE + Chrome 雙核心了;

(10)UC 瀏覽器核心:這個眾口不一,UC 說是他們自己研發的 U3 核心,但好像還是基於 Webkit 和 Trident ,還有說是基於火狐核心。

一個 tcp 連線能發幾個 http 請求?

如果是 HTTP 1.0 版本協議,一般情況下,不支援長連線,因此在每次請求傳送完畢之後,TCP 連線即會斷開,因此一個 TCP 傳送一個 HTTP 請求,但是有一種情況可以將一條 TCP 連線保持在活躍狀態,那就是通過 Connection 和 Keep-Alive 首部,在請求頭帶上 Connection: Keep-Alive,並且可以通過 Keep-Alive 通用首部中指定的,用逗號分隔的選項調節 keep-alive 的行為,如果客戶端和服務端都支援,那麼其實也可以傳送多條,不過此方式也有限制,可以關注《HTTP 權威指南》4.5.5 節對於 Keep-Alive 連線的限制和規則。

而如果是 HTTP 1.1 版本協議,支援了長連線,因此只要 TCP 連線不斷開,便可以一直髮送 HTTP 請求,持續不斷,沒有上限; 同樣,如果是 HTTP 2.0 版本協議,支援多用複用,一個 TCP 連線是可以併發多個 HTTP 請求的,同樣也是支援長連線,因此只要不斷開 TCP 的連線,HTTP 請求數也是可以沒有上限地持續傳送

對瀏覽器的理解

瀏覽器的主要功能是將使用者選擇的 web 資源呈現出來,它需要從伺服器請求資源,並將其顯示在瀏覽器視窗中,資源的格式通常是 HTML,也包括 PDF、image 及其他格式。使用者用 URI(Uniform Resource Identifier 統一資源識別符號)來指定所請求資源的位置。

HTML 和 CSS 規範中規定了瀏覽器解釋 html 文件的方式,由 W3C 組織對這些規範進行維護,W3C 是負責制定 web 標準的組織。但是瀏覽器廠商紛紛開發自己的擴充套件,對規範的遵循並不完善,這為 web 開發者帶來了嚴重的相容性問題。

瀏覽器可以分為兩部分,shell 和 核心。其中 shell 的種類相對比較多,核心則比較少。也有一些瀏覽器並不區分外殼和核心。從 Mozilla 將 Gecko 獨立出來後,才有了外殼和核心的明確劃分。

  • shell 是指瀏覽器的外殼:例如選單,工具欄等。主要是提供給使用者介面操作,引數設定等等。它是呼叫核心來實現各種功能的。
  • 核心是瀏覽器的核心。核心是基於標記語言顯示內容的程式或模組。

深淺拷貝

1. 淺拷貝的原理和實現

自己建立一個新的物件,來接受你要重新複製或引用的物件值。如果物件屬性是基本的資料型別,複製的就是基本型別的值給新物件;但如果屬性是引用資料型別,複製的就是記憶體中的地址,如果其中一個物件改變了這個記憶體中的地址,肯定會影響到另一個物件

方法一:object.assign

object.assign是 ES6 中 object 的一個方法,該方法可以用於 JS 物件的合併等多個用途,其中一個用途就是可以進行淺拷貝。該方法的第一個引數是拷貝的目標物件,後面的引數是拷貝的來源物件(也可以是多個來源)。

javascript object.assign 的語法為:Object.assign(target, ...sources)

object.assign 的示例程式碼如下:

javascript let target = {}; let source = { a: { b: 1 } }; Object.assign(target, source); console.log(target); // { a: { b: 1 } };

但是使用 object.assign 方法有幾點需要注意

  • 它不會拷貝物件的繼承屬性;
  • 它不會拷貝物件的不可列舉的屬性;
  • 可以拷貝 Symbol 型別的屬性。

javascript let obj1 = { a:{ b:1 }, sym:Symbol(1)}; Object.defineProperty(obj1, 'innumerable' ,{ value:'不可列舉屬性', enumerable:false }); let obj2 = {}; Object.assign(obj2,obj1) obj1.a.b = 2; console.log('obj1',obj1); console.log('obj2',obj2);

從上面的樣例程式碼中可以看到,利用 object.assign 也可以拷貝 Symbol 型別的物件,但是如果到了物件的第二層屬性 obj1.a.b 這裡的時候,前者值的改變也會影響後者的第二層屬性的值,說明其中依舊存在著訪問共同堆記憶體的問題,也就是說這種方法還不能進一步複製,而只是完成了淺拷貝的功能

方法二:擴充套件運算子方式

  • 我們也可以利用 JS 的擴充套件運算子,在構造物件的同時完成淺拷貝的功能。
  • 擴充套件運算子的語法為:let cloneObj = { ...obj };

javascript /* 物件的拷貝 */ let obj = {a:1,b:{c:1}} let obj2 = {...obj} obj.a = 2 console.log(obj) //{a:2,b:{c:1}} console.log(obj2); //{a:1,b:{c:1}} obj.b.c = 2 console.log(obj) //{a:2,b:{c:2}} console.log(obj2); //{a:1,b:{c:2}} /* 陣列的拷貝 */ let arr = [1, 2, 3]; let newArr = [...arr]; //跟arr.slice()是一樣的效果

擴充套件運算子 和 object.assign 有同樣的缺陷,也就是實現的淺拷貝的功能差不多,但是如果屬性都是基本型別的值,使用擴充套件運算子進行淺拷貝會更加方便

方法三:concat 拷貝陣列

陣列的 concat 方法其實也是淺拷貝,所以連線一個含有引用型別的陣列時,需要注意修改原陣列中的元素的屬性,因為它會影響拷貝之後連線的陣列。不過 concat 只能用於陣列的淺拷貝,使用場景比較侷限。程式碼如下所示。

javascript let arr = [1, 2, 3]; let newArr = arr.concat(); newArr[1] = 100; console.log(arr); // [ 1, 2, 3 ] console.log(newArr); // [ 1, 100, 3 ]

方法四:slice 拷貝陣列

slice 方法也比較有侷限性,因為它僅僅針對陣列型別slice方法會返回一個新的陣列物件,這一物件由該方法的前兩個引數來決定原陣列擷取的開始和結束時間,是不會影響和改變原始陣列的。

javascript slice 的語法為:arr.slice(begin, end);

javascript let arr = [1, 2, {val: 4}]; let newArr = arr.slice(); newArr[2].val = 1000; console.log(arr); //[ 1, 2, { val: 1000 } ]

從上面的程式碼中可以看出,這就是淺拷貝的限制所在了——它只能拷貝一層物件。如果存在物件的巢狀,那麼淺拷貝將無能為力。因此深拷貝就是為了解決這個問題而生的,它能解決多層物件巢狀問題,徹底實現拷貝

手工實現一個淺拷貝

根據以上對淺拷貝的理解,如果讓你自己實現一個淺拷貝,大致的思路分為兩點:

  • 對基礎型別做一個最基本的一個拷貝;
  • 對引用型別開闢一個新的儲存,並且拷貝一層物件屬性。

javascript const shallowClone = (target) => { if (typeof target === 'object' && target !== null) { const cloneTarget = Array.isArray(target) ? []: {}; for (let prop in target) { if (target.hasOwnProperty(prop)) { cloneTarget[prop] = target[prop]; } } return cloneTarget; } else { return target; } }

利用型別判斷,針對引用型別的物件進行 for 迴圈遍歷物件屬性賦值給目標物件的屬性,基本就可以手工實現一個淺拷貝的程式碼了

2. 深拷貝的原理和實現

淺拷貝只是建立了一個新的物件,複製了原有物件的基本型別的值,而引用資料型別只拷貝了一層屬性,再深層的還是無法進行拷貝。深拷貝則不同,對於複雜引用資料型別,其在堆記憶體中完全開闢了一塊記憶體地址,並將原有的物件完全複製過來存放。

這兩個物件是相互獨立、不受影響的,徹底實現了記憶體上的分離。總的來說,深拷貝的原理可以總結如下

將一個物件從記憶體中完整地拷貝出來一份給目標物件,並從堆記憶體中開闢一個全新的空間存放新物件,且新物件的修改並不會改變原物件,二者實現真正的分離。

方法一:乞丐版(JSON.stringify)

JSON.stringify() 是目前開發過程中最簡單的深拷貝方法,其實就是把一個物件序列化成為 JSON 的字串,並將物件裡面的內容轉換成字串,最後再用 JSON.parse() 的方法將 JSON 字串生成一個新的物件

javascript let a = { age: 1, jobs: { first: 'FE' } } let b = JSON.parse(JSON.stringify(a)) a.jobs.first = 'native' console.log(b.jobs.first) // FE

但是該方法也是有侷限性的

  • 會忽略 undefined
  • 會忽略 symbol
  • 不能序列化函式
  • 無法拷貝不可列舉的屬性
  • 無法拷貝物件的原型鏈
  • 拷貝 RegExp 引用型別會變成空物件
  • 拷貝 Date 引用型別會變成字串
  • 物件中含有 NaNInfinity 以及 -InfinityJSON 序列化的結果會變成 null
  • 不能解決迴圈引用的物件,即物件成環 (obj[key] = obj)。

javascript function Obj() { this.func = function () { alert(1) }; this.obj = {a:1}; this.arr = [1,2,3]; this.und = undefined; this.reg = /123/; this.date = new Date(0); this.NaN = NaN; this.infinity = Infinity; this.sym = Symbol(1); } let obj1 = new Obj(); Object.defineProperty(obj1,'innumerable',{ enumerable:false, value:'innumerable' }); console.log('obj1',obj1); let str = JSON.stringify(obj1); let obj2 = JSON.parse(str); console.log('obj2',obj2);

使用 JSON.stringify 方法實現深拷貝物件,雖然到目前為止還有很多無法實現的功能,但是這種方法足以滿足日常的開發需求,並且是最簡單和快捷的。而對於其他的也要實現深拷貝的,比較麻煩的屬性對應的資料型別,JSON.stringify 暫時還是無法滿足的,那麼就需要下面的幾種方法了

方法二:基礎版(手寫遞迴實現)

下面是一個實現 deepClone 函式封裝的例子,通過 for in 遍歷傳入引數的屬性值,如果值是引用型別則再次遞迴呼叫該函式,如果是基礎資料型別就直接複製

javascript let obj1 = { a:{ b:1 } } function deepClone(obj) { let cloneObj = {} for(let key in obj) { //遍歷 if(typeof obj[key] ==='object') { cloneObj[key] = deepClone(obj[key]) //是物件就再次呼叫該函式遞迴 } else { cloneObj[key] = obj[key] //基本型別的話直接複製值 } } return cloneObj } let obj2 = deepClone(obj1); obj1.a.b = 2; console.log(obj2); // {a:{b:1}}

雖然利用遞迴能實現一個深拷貝,但是同上面的 JSON.stringify 一樣,還是有一些問題沒有完全解決,例如:

  • 這個深拷貝函式並不能複製不可列舉的屬性以及 Symbol 型別;
  • 這種方法只是針對普通的引用型別的值做遞迴複製,而對於 Array、Date、RegExp、Error、Function 這樣的引用型別並不能正確地拷貝;
  • 物件的屬性裡面成環,即迴圈引用沒有解決

這種基礎版本的寫法也比較簡單,可以應對大部分的應用情況。但是你在面試的過程中,如果只能寫出這樣的一個有缺陷的深拷貝方法,有可能不會通過。

所以為了“拯救”這些缺陷,下面我帶你一起看看改進的版本,以便於你可以在面試種呈現出更好的深拷貝方法,贏得面試官的青睞。

方法三:改進版(改進後遞迴實現)

針對上面幾個待解決問題,我先通過四點相關的理論告訴你分別應該怎麼做。

  • 針對能夠遍歷物件的不可列舉屬性以及 Symbol 型別,我們可以使用 Reflect.ownKeys 方法;
  • 當引數為 Date、RegExp 型別,則直接生成一個新的例項返回;
  • 利用 ObjectgetOwnPropertyDescriptors 方法可以獲得物件的所有屬性,以及對應的特性,順便結合 Object.create 方法建立一個新物件,並繼承傳入原物件的原型鏈;
  • 利用 WeakMap 型別作為 Hash 表,因為 WeakMap 是弱引用型別,可以有效防止記憶體洩漏(你可以關注一下 MapweakMap 的關鍵區別,這裡要用 weakMap),作為檢測迴圈引用很有幫助,如果存在迴圈,則引用直接返回 WeakMap 儲存的值

如果你在考慮到迴圈引用的問題之後,還能用 WeakMap 來很好地解決,並且向面試官解釋這樣做的目的,那麼你所展示的程式碼,以及你對問題思考的全面性,在面試官眼中應該算是合格的了

實現深拷貝

```javascript const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)

const deepClone = function (obj, hash = new WeakMap()) { if (obj.constructor === Date) { return new Date(obj) // 日期物件直接返回一個新的日期物件 }

if (obj.constructor === RegExp){ return new RegExp(obj) //正則物件直接返回一個新的正則物件 }

//如果迴圈引用了就用 weakMap 來解決 if (hash.has(obj)) { return hash.get(obj) } let allDesc = Object.getOwnPropertyDescriptors(obj)

//遍歷傳入引數所有鍵的特性 let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)

// 把cloneObj原型複製到obj上 hash.set(obj, cloneObj)

for (let key of Reflect.ownKeys(obj)) { cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key] } return cloneObj } ```

```javascript // 下面是驗證程式碼 let obj = { num: 0, str: '', boolean: true, unf: undefined, nul: null, obj: { name: '我是一個物件', id: 1 }, arr: [0, 1, 2], func: function () { console.log('我是一個函式') }, date: new Date(0), reg: new RegExp('/我是一個正則/ig'),

}; Object.defineProperty(obj, 'innumerable', { enumerable: false, value: '不可列舉屬性' } ); obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj)) obj.loop = obj // 設定loop成迴圈引用的屬性 let cloneObj = deepClone(obj) cloneObj.arr.push(4) console.log('obj', obj) console.log('cloneObj', cloneObj) ```

我們看一下結果,cloneObjobj 的基礎上進行了一次深拷貝,cloneObj 裡的 arr 陣列進行了修改,並未影響到 obj.arr 的變化,如下圖所示

點選重新整理按鈕或者按 F5、按 Ctrl+F5 (強制重新整理)、位址列回車有什麼區別?

  • 點選重新整理按鈕或者按 F5: 瀏覽器直接對本地的快取檔案過期,但是會帶上If-Modifed-Since,If-None-Match,這就意味著伺服器會對檔案檢查新鮮度,返回結果可能是 304,也有可能是 200。
  • 使用者按 Ctrl+F5(強制重新整理): 瀏覽器不僅會對本地檔案過期,而且不會帶上 If-Modifed-Since,If-None-Match,相當於之前從來沒有請求過,返回結果是 200。
  • 位址列回車: 瀏覽器發起請求,按照正常流程,本地檢查是否過期,然後伺服器檢查新鮮度,最後返回內容。

參考 前端進階面試題詳細解答

對this物件的理解

this 是執行上下文中的一個屬性,它指向最後一次呼叫這個方法的物件。在實際開發中,this 的指向可以通過四種呼叫模式來判斷。

  • 第一種是函式呼叫模式,當一個函式不是一個物件的屬性時,直接作為函式來呼叫時,this 指向全域性物件。
  • 第二種是方法呼叫模式,如果一個函式作為一個物件的方法來呼叫時,this 指向這個物件。
  • 第三種是構造器呼叫模式,如果一個函式用 new 呼叫時,函式執行前會新建立一個物件,this 指向這個新建立的物件。
  • 第四種是 apply 、 call 和 bind 呼叫模式,這三個方法都可以顯示的指定呼叫函式的 this 指向。其中 apply 方法接收兩個引數:一個是 this 繫結的物件,一個是引數陣列。call 方法接收的引數,第一個是 this 繫結的物件,後面的其餘引數是傳入函式執行的引數。也就是說,在使用 call() 方法時,傳遞給函式的引數必須逐個列舉出來。bind 方法通過傳入一個物件,返回一個 this 綁定了傳入物件的新函式。這個函式的 this 指向除了使用 new 時會被改變,其他情況下都不會改變。

這四種方式,使用構造器呼叫模式的優先順序最高,然後是 apply、call 和 bind 呼叫模式,然後是方法呼叫模式,然後是函式呼叫模式。

常見的瀏覽器核心比較

  • Trident: 這種瀏覽器核心是 IE 瀏覽器用的核心,因為在早期 IE 佔有大量的市場份額,所以這種核心比較流行,以前有很多網頁也是根據這個核心的標準來編寫的,但是實際上這個核心對真正的網頁標準支援不是很好。但是由於 IE 的高市場佔有率,微軟也很長時間沒有更新 Trident 核心,就導致了 Trident 核心和 W3C 標準脫節。還有就是 Trident 核心的大量 Bug 等安全問題沒有得到解決,加上一些專家學者公開自己認為 IE 瀏覽器不安全的觀點,使很多使用者開始轉向其他瀏覽器。
  • Gecko: 這是 Firefox 和 Flock 所採用的核心,這個核心的優點就是功能強大、豐富,可以支援很多複雜網頁效果和瀏覽器擴充套件介面,但是代價是也顯而易見就是要消耗很多的資源,比如記憶體。
  • Presto: Opera 曾經採用的就是 Presto 核心,Presto 核心被稱為公認的瀏覽網頁速度最快的核心,這得益於它在開發時的天生優勢,在處理 JS 指令碼等指令碼語言時,會比其他的核心快3倍左右,缺點就是為了達到很快的速度而丟掉了一部分網頁相容性。
  • Webkit: Webkit 是 Safari 採用的核心,它的優點就是網頁瀏覽速度較快,雖然不及 Presto 但是也勝於 Gecko 和 Trident,缺點是對於網頁程式碼的容錯性不高,也就是說對網頁程式碼的相容性較低,會使一些編寫不標準的網頁無法正確顯示。WebKit 前身是 KDE 小組的 KHTML 引擎,可以說 WebKit 是 KHTML 的一個開源的分支。
  • Blink: 谷歌在 Chromium Blog 上發表部落格,稱將與蘋果的開源瀏覽器核心 Webkit 分道揚鑣,在 Chromium 專案中研發 Blink 渲染引擎(即瀏覽器核心),內置於 Chrome 瀏覽器之中。其實 Blink 引擎就是 Webkit 的一個分支,就像 webkit 是KHTML 的分支一樣。Blink 引擎現在是谷歌公司與 Opera Software 共同研發,上面提到過的,Opera 棄用了自己的 Presto 核心,加入 Google 陣營,跟隨谷歌一起研發 Blink。

Virtual Dom 的優勢在哪裡?

Virtual Dom 的優勢」其實這道題目面試官更想聽到的答案不是上來就說「直接操作/頻繁操作 DOM 的效能差」,如果 DOM 操作的效能如此不堪,那麼 jQuery 也不至於活到今天。所以面試官更想聽到 VDOM 想解決的問題以及為什麼頻繁的 DOM 操作會效能差。

首先我們需要知道:

DOM 引擎、JS 引擎 相互獨立,但又工作在同一執行緒(主執行緒) JS 程式碼呼叫 DOM API 必須 掛起 JS 引擎、轉換傳入引數資料、啟用 DOM 引擎,DOM 重繪後再轉換可能有的返回值,最後啟用 JS 引擎並繼續執行若有頻繁的 DOM API 呼叫,且瀏覽器廠商不做“批量處理”優化, 引擎間切換的單位代價將迅速積累若其中有強制重繪的 DOM API 呼叫,重新計算佈局、重新繪製圖像會引起更大的效能消耗。

其次是 VDOM 和真實 DOM 的區別和優化:

  1. 虛擬 DOM 不會立馬進行排版與重繪操作
  2. 虛擬 DOM 進行頻繁修改,然後一次性比較並修改真實 DOM 中需要改的部分,最後在真實 DOM 中進行排版與重繪,減少過多DOM節點排版與重繪損耗
  3. 虛擬 DOM 有效降低大面積真實 DOM 的重繪與排版,因為最終與真實 DOM 比較差異,可以只渲染區域性

畫一條0.5px的線

  • 採用transform: scale()的方式,該方法用來定義元素的2D 縮放轉換:

```css transform: scale(0.5,0.5);

```

  • 採用meta viewport的方式

```css

```

這樣就能縮放到原來的0.5倍,如果是1px那麼就會變成0.5px。viewport只針對於移動端,只在移動端上才能看到效果

CSS 如何阻塞文件解析?

理論上,既然樣式表不改變 DOM 樹,也就沒有必要停下文件的解析等待它們。然而,存在一個問題,JavaScript 指令碼執行時可能在文件的解析過程中請求樣式資訊,如果樣式還沒有載入和解析,指令碼將得到錯誤的值,顯然這將會導致很多問題。所以如果瀏覽器尚未完成 CSSOM 的下載和構建,而我們卻想在此時執行指令碼,那麼瀏覽器將延遲 JavaScript 指令碼執行和文件的解析,直至其完成 CSSOM 的下載和構建。也就是說,在這種情況下,瀏覽器會先下載和構建 CSSOM,然後再執行 JavaScript,最後再繼續文件的解析。

氣泡排序--時間複雜度 n^2

題目描述:實現一個氣泡排序

實現程式碼如下:

```javascript function bubbleSort(arr) { // 快取陣列長度 const len = arr.length; // 外層迴圈用於控制從頭到尾的比較+交換到底有多少輪 for (let i = 0; i < len; i++) { // 內層迴圈用於完成每一輪遍歷過程中的重複比較+交換 for (let j = 0; j < len - 1; j++) { // 若相鄰元素前面的數比後面的大 if (arr[j] > arr[j + 1]) { // 交換兩者 [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; } } } // 返回陣列 return arr; } // console.log(bubbleSort([3, 6, 2, 4, 1]));

```

程式碼輸出結果

```javascript var obj = { say: function() { var f1 = () => { console.log("1111", this); } f1(); }, pro: { getPro:() => { console.log(this); } } } var o = obj.say; o(); obj.say(); obj.pro.getPro();

```

輸出結果:

```javascript 1111 window物件 1111 obj物件 window物件

```

解析:

  1. o(),o是在全域性執行的,而f1是箭頭函式,它是沒有繫結this的,它的this指向其父級的this,其父級say方法的this指向的是全域性作用域,所以會打印出window;
  2. obj.say(),誰呼叫say,say 的this就指向誰,所以此時this指向的是obj物件;
  3. obj.pro.getPro(),我們知道,箭頭函式時不繫結this的,getPro處於pro中,而物件不構成單獨的作用域,所以箭頭的函式的this就指向了全域性作用域window。

JSONP

JSONP 核心原理:script 標籤不受同源策略約束,所以可以用來進行跨域請求,優點是相容性好,但是隻能用於 GET 請求;

``javascript const jsonp = ({ url, params, callbackName }) => { const generateUrl = () => { let dataSrc = '' for (let key in params) { if (params.hasOwnProperty(key)) { dataSrc +=${key}=${params[key]}&} } dataSrc +=callback=${callbackName}return${url}?${dataSrc}` } return new Promise((resolve, reject) => { const scriptEle = document.createElement('script') scriptEle.src = generateUrl() document.body.appendChild(scriptEle) window[callbackName] = data => { resolve(data) document.removeChild(scriptEle) } }) }

```

HTTP 1.0 和 HTTP 1.1 之間有哪些區別?

HTTP 1.0和 HTTP 1.1 有以下區別

  • 連線方面,http1.0 預設使用非持久連線,而 http1.1 預設使用持久連線。http1.1 通過使用持久連線來使多個 http 請求複用同一個 TCP 連線,以此來避免使用非持久連線時每次需要建立連線的時延。
  • 資源請求方面,在 http1.0 中,存在一些浪費頻寬的現象,例如客戶端只是需要某個物件的一部分,而伺服器卻將整個物件送過來了,並且不支援斷點續傳功能,http1.1 則在請求頭引入了 range 頭域,它允許只請求資源的某個部分,即返回碼是 206(Partial Content),這樣就方便了開發者自由的選擇以便於充分利用頻寬和連線。
  • 快取方面,在 http1.0 中主要使用 header 裡的 If-Modified-Since、Expires 來做為快取判斷的標準,http1.1 則引入了更多的快取控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供選擇的快取頭來控制快取策略。
  • http1.1 中新增了 host 欄位,用來指定伺服器的域名。http1.0 中認為每臺伺服器都繫結一個唯一的 IP 地址,因此,請求訊息中的 URL 並沒有傳遞主機名(hostname)。但隨著虛擬主機技術的發展,在一臺物理伺服器上可以存在多個虛擬主機,並且它們共享一個IP地址。因此有了 host 欄位,這樣就可以將請求發往到同一臺伺服器上的不同網站。
  • http1.1 相對於 http1.0 還新增了很多請求方法,如 PUT、HEAD、OPTIONS 等。

call() 和 apply() 的區別?

它們的作用一模一樣,區別僅在於傳入引數的形式的不同。

  • apply 接受兩個引數,第一個引數指定了函式體內 this 物件的指向,第二個引數為一個帶下標的集合,這個集合可以為陣列,也可以為類陣列,apply 方法把這個集合中的元素作為引數傳遞給被呼叫的函式。
  • call 傳入的引數數量不固定,跟 apply 相同的是,第一個引數也是代表函式體內的 this 指向,從第二個引數開始往後,每個引數被依次傳入函式。

let、const、var的區別

(1)塊級作用域: 塊作用域由 { }包括,let和const具有塊級作用域,var不存在塊級作用域。塊級作用域解決了ES5中的兩個問題:

  • 內層變數可能覆蓋外層變數
  • 用來計數的迴圈變數洩露為全域性變數

(2)變數提升: var存在變數提升,let和const不存在變數提升,即在變數只能在宣告之後使用,否在會報錯。

(3)給全域性新增屬性: 瀏覽器的全域性物件是window,Node的全域性物件是global。var宣告的變數為全域性變數,並且會將該變數新增為全域性物件的屬性,但是let和const不會。

(4)重複宣告: var宣告變數時,可以重複宣告變數,後宣告的同名變數會覆蓋之前宣告的遍歷。const和let不允許重複宣告變數。

(5)暫時性死區: 在使用let、const命令宣告變數之前,該變數都是不可用的。這在語法上,稱為暫時性死區。使用var宣告的變數不存在暫時性死區。

(6)初始值設定: 在變數宣告時,var 和 let 可以不用設定初始值。而const宣告變數必須設定初始值。

(7)指標指向: let和const都是ES6新增的用於建立變數的語法。 let建立的變數是可以更改指標指向(可以重新賦值)。但const宣告的變數是不允許改變指標的指向。

| 區別 | var | let | const | | --------- | ------- | ------- | --------- | | 是否有塊級作用域 | × | ✔️ | ✔️ | | 是否存在變數提升 | ✔️ | × | × | | 是否新增全域性屬性 | ✔️ | × | × | | 能否重複宣告變數 | ✔️ | × | × | | 是否存在暫時性死區 | × | ✔️ | ✔️ | | 是否必須設定初始值 | × | × | ✔️ | | 能否改變指標指向 | ✔️ | ✔️ | × |

有哪些可能引起前端安全的問題?

  • 跨站指令碼 (Cross-Site Scripting, XSS): ⼀種程式碼注⼊⽅式, 為了與 CSS 區分所以被稱作 XSS。早期常⻅於⽹絡論壇, 起因是⽹站沒有對⽤戶的輸⼊進⾏嚴格的限制, 使得攻擊者可以將指令碼上傳到帖⼦讓其他⼈瀏覽到有惡意指令碼的⻚⾯, 其注⼊⽅式很簡單包括但不限於 JavaScript / CSS / Flash 等;
  • iframe的濫⽤: iframe中的內容是由第三⽅來提供的,預設情況下他們不受控制,他們可以在iframe中運⾏JavaScirpt指令碼、Flash外掛、彈出對話方塊等等,這可能會破壞前端⽤戶體驗;
  • 跨站點請求偽造(Cross-Site Request Forgeries,CSRF): 指攻擊者通過設定好的陷阱,強制對已完成認證的⽤戶進⾏⾮預期的個⼈資訊或設定資訊等某些狀態更新,屬於被動攻擊
  • 惡意第三⽅庫: ⽆論是後端伺服器應⽤還是前端應⽤開發,絕⼤多數時候都是在藉助開發框架和各種類庫進⾏快速開發,⼀旦第三⽅庫被植⼊惡意程式碼很容易引起安全問題。

如何優化動畫?

對於如何優化動畫,我們知道,一般情況下,動畫需要頻繁的操作DOM,就就會導致頁面的效能問題,我們可以將動畫的position屬性設定為absolute或者fixed,將動畫脫離文件流,這樣他的迴流就不會影響到頁面了。

選擇排序--時間複雜度 n^2

題目描述:實現一個選擇排序

實現程式碼如下:

```javascript function selectSort(arr) { // 快取陣列長度 const len = arr.length; // 定義 minIndex,快取當前區間最小值的索引,注意是索引 let minIndex; // i 是當前排序區間的起點 for (let i = 0; i < len - 1; i++) { // 初始化 minIndex 為當前區間第一個元素 minIndex = i; // i、j分別定義當前區間的上下界,i是左邊界,j是右邊界 for (let j = i; j < len; j++) { // 若 j 處的資料項比當前最小值還要小,則更新最小值索引為 j if (arr[j] < arr[minIndex]) { minIndex = j; } } // 如果 minIndex 對應元素不是目前的頭部元素,則交換兩者 if (minIndex !== i) { [arr[i], arr[minIndex]] = [arr[minIndex], arr[i]]; } } return arr; } // console.log(quickSort([3, 6, 2, 4, 1]));

```