文章轉(zhuǎn)載自公眾號:前端工匠(微信號:frontJS)
2020年注定是不平凡的一年,找工作的競爭壓力可想而知,如何從眾多面試者中脫穎而出呢,總結(jié)了一波前端常見面試題,希望對大家有所幫助!
1. javascript 作用域與預解析
什么是預解析?
分兩步執(zhí)行:
第一步:(代碼還沒有執(zhí)行。預覽頁面之前,寫完之后)
找程序中var
關(guān)鍵字,如果找到了提前給var
定義的變量賦值undefined
找程序中的普通函數(shù),如果找到了,函數(shù)提升,將整個函數(shù)賦值給函數(shù)名。
如果找的var
的名字和函數(shù)名字相同,函數(shù)優(yōu)先。
第二步: 逐行解析代碼。按照上下順序。如果碰到函數(shù)定義,忽略。
重點:函數(shù)內(nèi)部同樣適用于js
預解析。
我們通過幾道面試題,來了解下作用域和和預解析的原理
function fun(){ console.log(n); var n = 456; console.log(n); } var n = 123; fun(n);
猜一猜此題中輸出的結(jié)果是?可能并不是你想的結(jié)果,why?
代碼分析如下:
1-5行定義函數(shù)fun
6行定義變量 n
7行執(zhí)行函數(shù)并傳入變量 n
注意:fun
函數(shù)內(nèi)部有預解析。
預解析及執(zhí)行步驟:
Fun
函數(shù)開始執(zhí)行前,將var n
提前執(zhí)行,初始化為undefined
。
- 由于函數(shù)傳入?yún)?shù) n 并沒有使用,忽略。
- 開始執(zhí)行第2行,輸出為
undefined
。
- 執(zhí)行第3行,此時即n = 456,即將n值重置為456。
- 執(zhí)行第4行,輸出改變后的n。
通過以上步驟分析,即可得知預解析的原理了(針對有var的變量提前賦初始值)
下面再看一題,看看函數(shù)預解析
猜一猜此題中輸出的結(jié)果是?可能并不是你想的結(jié)果,why?
代碼分析如下:
29行定義一個全局變量
30-32行定義一個函數(shù) f1
33-36行定義一個函數(shù) f2
37行執(zhí)行函數(shù) f2
38行輸出結(jié)果 n
預解析及執(zhí)行步驟:
1.代碼執(zhí)行前,預解析先初始化變量 n, f1, f2,將它們都置為 undefined
.
2.接著執(zhí)行第29行,為變量n賦值
3.接著執(zhí)行第30-32行,為函數(shù)變量 f1 賦值,即 f1 為函數(shù)了
4.接著執(zhí)行第33-36行,為函數(shù)變量 f2 賦值
5.執(zhí)行第37行,即執(zhí)行 f2 函數(shù)。
- f2 函數(shù)執(zhí)行前,同樣預解析,先將 n 初始化為
undefined
,接著把 n 賦值為456,接著調(diào)用f1函數(shù)執(zhí)行。
- f1 在 f2 中執(zhí)行,那 f1 的作用域應該是 f2 ,應該輸出456?
8.35行執(zhí)行 f1 函數(shù)時無調(diào)用者,即 f1 函數(shù)為全局作用域,輸出全局 n 為 123
9.第38行直接輸出全局變量 n ,即123
繼續(xù)深入,再來一題:
猜一猜此題中輸出的結(jié)果是?可能并不是你想的結(jié)果,why?
代碼分析如下:
預解析只針對var
和function
定義的變量
預解析及執(zhí)行步驟:
1.預解析先初始化變量length
, obj
, f1
并賦值為undefined
2.接著為變量length
賦值為 100
3.接著為函數(shù)變量 f1 賦值為函數(shù)
4.接著為變量`obj 賦值為對象
5.第52行,調(diào)用對象obj
的 f2 函數(shù)執(zhí)行,傳入形參 f1 和1
6.第47行,f2 函數(shù)接收實參為 f1 , 接著執(zhí)行 f1 函數(shù)
7.同上,f1 函數(shù)執(zhí)行無調(diào)用者,作用域為全局,this
指向window
,輸出全局變量length
,即100
8.第47行,f2 函數(shù)接收實參 f1 ,若要取到所有實參則需要arguments
對象,第一個參數(shù)arguments[0]==f1
,第二個arguments[1]==1
,依此類推。
9.第49行,arguments[0]()
看上去是f1()
,那也應該輸出100?
10.注意arguments[0]
作用域與f1的作用域并不相同,第48行直接執(zhí)行f1,無調(diào)用者,作用域為全局作用域,但arguments[0]
作用域為arguments
對象,即this
為arguments
,則應輸出 2,因為arguments
對象的屬性length
為2。
2. 前端如何處理跨域
1、為什么會出現(xiàn)跨域問題
同源策略(Sameoriginpolicy)是一種約定,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,則瀏覽器的正常功能可能都會受到影響??梢哉f Web 是構(gòu)建在同源策略基礎(chǔ)之上的,瀏覽器只是針對同源策略的一種實現(xiàn)。同源策略會阻止一個域的 javascript 腳本和另外一個域的內(nèi)容進行交互。所謂同源(即指在同一個域)就是兩個頁面具有相同的協(xié)議(protocol),主機(host)和端口號(port)。
2、什么是跨域
當一個請求url的協(xié)議、域名、端口三者之間任意一個與當前頁面url不同即為跨域。
當前頁面url | 被請求頁面url | 是否跨域 | 原因 |
---|---|---|---|
http://m.hgci.cn/ | http://m.hgci.cn/index.html | 否 | 同源 |
http://m.hgci.cn/ | http://m.hgci.cn/index.html | 跨域 | 協(xié)議不同(http/https |
http://m.hgci.cn | http://www.baidu.cn/ | 跨域 | 主域名不同(w3cschool/baidu |
http://m.hgci.cn/ | http://123.w3cschool.cn | 跨域 | 子域名不同(www/123 |
www.test.com:8080/ | www.test.com:7001 | 跨域 | 端口號不同 |
3、跨域解決方法
【1】設置document.domain解決無法讀取非同源網(wǎng)頁的 Cookie問題
【2】跨文檔通信 API:window.postMessage()
【3】JSONP
【4】CORS
【5】Proxy
作為開發(fā)人員,最關(guān)心的跨域一般是2種交互的跨域,即Proxy
和CORS
,很多開發(fā)只圖一時方便,使用了Proxy
,在打包后就發(fā)現(xiàn)又有跨域了,不知道怎么解決,下面我們通過實例一點點給大家解析。
跨域出現(xiàn)
首先,需要重現(xiàn)跨域,先用node
寫一個簡單的接口,如下
使用命令node
啟動這個服務,則搭建了一個最簡單的后端服務接口,然后使用前端ajax
來請求這個接口,如下
創(chuàng)建一個簡單的html
頁面,再加上上面的簡單ajax
請求,在瀏覽器控制臺就看到了跨域error
了
從日志上看出現(xiàn)了“Access-Control-Allow-Origin”,表示是訪問源未被許可,即跨域了。
跨域解決之Proxy
現(xiàn)在項目一般都使用腳手架,即使用webpack
,那可以使用webpack
自帶的proxy
特性來處理跨域,下面我們來配置一個簡單的webpack
項目,如下
1.創(chuàng)建配置文件webpack.config.js
配置文件說明項目入口文件在src
中index.js
,打包輸出目錄為dist
,使用proxy
處理跨域,即前端所有請求會自動跳轉(zhuǎn)到target
指定的url
注意這里有一個前綴,若沒有可以不寫。
2.創(chuàng)建src
目錄及index.js
3.創(chuàng)建工程依賴文件package.json
依賴文件中配置了webpack
啟動命令
Npm run dev
啟動服務
Npm run start
啟動服務
Npm run build
打包命令
當啟動服務后,打開瀏覽器輸入 http://localhost:8080
,即可看到一個空白頁面,打開控制臺可以看到ajax
請求
拿到交互的數(shù)據(jù)了。
這種方式是開發(fā)最常用的,但是打包后就有問題了,因為打包后就不存在proxy
了,跨域還是會存在,那應該怎么解決?
跨域解決之CORS
這種方式是在后端配置,配置CORS
后,前端無需任何處理即可訪問后端的接口,無論是在開發(fā)時還是部署時都是OK的。
下面,我們把proxy
注釋掉,使用CORS
方式處理,如下:
配置了cors
后,接口就可以隨便訪問了。
此時,還需要把前端請求地址改一下,改為直接請求后端接口,如下
刷新頁面,打開控制臺可以看到請求地址為
通過此種方式,在開發(fā)階段或部署都沒有問題,這也是開發(fā)中最常用的2種方式。
3. 什么是閉包?如何理解
閉包(closure)是javascript
的一大難點,也是它的特色。很多高級應用都要依靠閉包來實現(xiàn)。
要理解閉包,首先要理解javascript
的全局變量和局部變量。
javascript
語言的特別之處就在于:函數(shù)內(nèi)部可以直接讀取全局變量,但是在函數(shù)外部無法讀取函數(shù)內(nèi)部的局部變量。
如何從外部讀取函數(shù)內(nèi)部的局部變量?
我們有時候需要獲取到函數(shù)內(nèi)部的局部變量,正常情況下,這是辦不到的!只有通過變通的方法才能實現(xiàn)。那就是在函數(shù)內(nèi)部,再定義一個函數(shù)。
1、閉包的概念
上面代碼中的 f2 函數(shù),就是閉包。
各種專業(yè)文獻的閉包定義都非常抽象,我的理解是: 閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
由于在javascript
中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,所以說,閉包可以簡單理解成“定義在一個函數(shù)內(nèi)部的函數(shù)“。
所以,在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來的橋梁。
2、閉包的用途
閉包可以用在許多地方。它的最大用處有兩個,一個是前面提到的可以讀取函數(shù)內(nèi)部的變量,另一個就是讓這些變量的值始終保持在內(nèi)存中,不會在 f1 調(diào)用后被自動清除。
為什么會這樣呢?原因就在于 f1 是 f2 的父函數(shù),而 f2 被賦給了一個全局變量,這導致 f2 始終在內(nèi)存中,而 f2 的存在依賴于 f1 ,因此 f1 也始終在內(nèi)存中,不會在調(diào)用結(jié)束后,被垃圾回收機制(garbage collection)回收。
在我們平時的代碼中經(jīng)常會用到閉包,比如在構(gòu)造函數(shù)中
//另一種寫法
3、常見閉包的寫法
另一種調(diào)用方法
//定義函數(shù)并立即調(diào)用
4、閉包的實際應用
使用閉包,我們可以做很多事情。比如模擬面向?qū)ο蟮拇a風格;更優(yōu)雅,更簡潔的表達出代碼;在某些方面提升代碼的執(zhí)行效率。
封裝
通過person.name
是無法獲取到name
的值,如果要獲取到name
的值可以通過
Console.log(person.getName()); //直接獲取到 張三 person.setName("李四"); //重新設置新的名字 print(person.getName()); //獲取 李四
繼承
總結(jié):閉包就是一個函數(shù)引用另外一個函數(shù)的變量,因為變量被引用著所以不會被回收,因此可以用來封裝一個私有變量。這是優(yōu)點也是缺點,不必要的閉包只會徒增內(nèi)存消耗!
以上就是W3Cschool編程獅
關(guān)于2020前端面試都會問啥?的相關(guān)介紹了,希望對大家有所幫助。