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