1、什么是防抖和節(jié)流咸这?有什么區(qū)別夷恍?如何實(shí)現(xiàn)?
參考答案
防抖
觸發(fā)高頻事件后n秒內(nèi)函數(shù)只會(huì)執(zhí)行一次媳维,如果n秒內(nèi)高頻事件再次被觸發(fā)酿雪,則重新計(jì)算時(shí)間
- 思路:
每次觸發(fā)事件時(shí)都取消之前的延時(shí)調(diào)用方法
<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`function debounce(fn) {
let timeout = null; // 創(chuàng)建一個(gè)標(biāo)記用來存放定時(shí)器的返回值
return function () {
clearTimeout(timeout); // 每當(dāng)用戶輸入的時(shí)候把前一個(gè) setTimeout clear 掉
timeout = setTimeout(() => { // 然后又創(chuàng)建一個(gè)新的 setTimeout, 這樣就能保證輸入字符后的 interval 間隔內(nèi)如果還有字符輸入的話,就不會(huì)執(zhí)行 fn 函數(shù)
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖` </pre>
節(jié)流
高頻事件觸發(fā)侨艾,但在n秒內(nèi)只會(huì)執(zhí)行一次执虹,所以節(jié)流會(huì)稀釋函數(shù)的執(zhí)行頻率
- 思路:
每次觸發(fā)事件時(shí)都判斷當(dāng)前是否有等待執(zhí)行的延時(shí)函數(shù)
<pre data-tool="mdnice編輯器" style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">`function throttle(fn) {
let canRun = true; // 通過閉包保存一個(gè)標(biāo)記
return function () {
if (!canRun) return; // 在函數(shù)開頭判斷標(biāo)記是否為true港庄,不為true則return
canRun = false; // 立即設(shè)置為false
setTimeout(() => { // 將外部傳入的函數(shù)的執(zhí)行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout執(zhí)行完畢后再把標(biāo)記設(shè)置為true(關(guān)鍵)表示可以執(zhí)行下一次循環(huán)了绞铃。當(dāng)定時(shí)器沒有執(zhí)行的時(shí)候標(biāo)記永遠(yuǎn)是false棵譬,在開頭被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));` </pre>
2、 get請(qǐng)求傳參長(zhǎng)度的誤區(qū)当叭、get和post請(qǐng)求在緩存方面的區(qū)別
誤區(qū):我們經(jīng)常說get請(qǐng)求參數(shù)的大小存在限制,而post請(qǐng)求的參數(shù)大小是無限制的盖灸。
參考答案
實(shí)際上HTTP 協(xié)議從未規(guī)定 GET/POST 的請(qǐng)求長(zhǎng)度限制是多少蚁鳖。對(duì)get請(qǐng)求參數(shù)的限制是來源與瀏覽器或web服務(wù)器,瀏覽器或web服務(wù)器限制了url的長(zhǎng)度赁炎。為了明確這個(gè)概念醉箕,我們必須再次強(qiáng)調(diào)下面幾點(diǎn):
- HTTP 協(xié)議 未規(guī)定 GET 和POST的長(zhǎng)度限制
- GET的最大長(zhǎng)度顯示是因?yàn)?瀏覽器和 web服務(wù)器限制了 URI的長(zhǎng)度
- 不同的瀏覽器和WEB服務(wù)器钾腺,限制的最大長(zhǎng)度不一樣
- 要支持IE,則最大長(zhǎng)度為2083byte讥裤,若只支持Chrome放棒,則最大長(zhǎng)度 8182byte
補(bǔ)充補(bǔ)充一個(gè)get和post在緩存方面的區(qū)別:
- get請(qǐng)求類似于查找的過程,用戶獲取數(shù)據(jù)己英,可以不用每次都與數(shù)據(jù)庫連接间螟,所以可以使用緩存。
- post不同损肛,post做的一般是修改和刪除的工作厢破,所以必須與數(shù)據(jù)庫交互,所以不能使用緩存治拿。因此get請(qǐng)求適合于請(qǐng)求緩存摩泪。
3、模塊化發(fā)展歷程
可從IIFE劫谅、AMD加勤、CMD、CommonJS同波、UMD鳄梅、webpack(require.ensure)、ES Module未檩、<script type="module">
這幾個(gè)角度考慮戴尸。
參考答案
模塊化主要是用來抽離公共代碼,隔離作用域冤狡,避免變量沖突等孙蒙。
IIFE:使用自執(zhí)行函數(shù)來編寫模塊化,特點(diǎn):在一個(gè)單獨(dú)的函數(shù)作用域中執(zhí)行代碼悲雳,避免變量沖突挎峦。
(function(){
return {
data:[]
}
})()
AMD:使用requireJS 來編寫模塊化,特點(diǎn):依賴必須提前聲明好合瓢。
define('./index.js',function(code){
// code 就是index.js 返回的內(nèi)容
})
CMD:使用seaJS 來編寫模塊化坦胶,特點(diǎn):支持動(dòng)態(tài)引入依賴文件。
define(function(require, exports, module) {
var indexCode = require('./index.js');
})
CommonJS:nodejs 中自帶的模塊化晴楔。
var fs = require('fs');
UMD:兼容AMD顿苇,CommonJS 模塊化語法。
webpack(require.ensure):webpack 2.x 版本中的代碼分割税弃。
ES Modules:ES6 引入的模塊化纪岁,支持import 來引入另一個(gè) js 。
import a from 'a';
4则果、npm 模塊安裝機(jī)制幔翰,為什么輸入 npm install 就可以自動(dòng)安裝對(duì)應(yīng)的模塊漩氨?
參考答案
- npm 模塊安裝機(jī)制:
發(fā)出
npm install
命令查詢node_modules目錄之中是否已經(jīng)存在指定模塊
npm 向 registry 查詢模塊壓縮包的網(wǎng)址
下載壓縮包,存放在根目錄下的
.npm
目錄里解壓壓縮包到當(dāng)前項(xiàng)目的
node_modules
目錄若存在遗增,不再重新安裝
若不存在
- npm 實(shí)現(xiàn)原理
輸入 npm install 命令并敲下回車后才菠,會(huì)經(jīng)歷如下幾個(gè)階段(以 npm 5.5.1 為例):
-
執(zhí)行工程自身 preinstall
當(dāng)前 npm 工程如果定義了 preinstall 鉤子此時(shí)會(huì)被執(zhí)行。
-
確定首層依賴模塊
首先需要做的是確定工程中的首層依賴贡定,也就是 dependencies 和 devDependencies 屬性中直接指定的模塊(假設(shè)此時(shí)沒有添加 npm install 參數(shù))赋访。
工程本身是整棵依賴樹的根節(jié)點(diǎn),每個(gè)首層依賴模塊都是根節(jié)點(diǎn)下面的一棵子樹缓待,npm 會(huì)開啟多進(jìn)程從每個(gè)首層依賴模塊開始逐步尋找更深層級(jí)的節(jié)點(diǎn)蚓耽。
-
獲取模塊
獲取模塊是一個(gè)遞歸的過程,分為以下幾步:
- 獲取模塊信息旋炒。在下載一個(gè)模塊之前步悠,首先要確定其版本,這是因?yàn)?package.json 中往往是 semantic version(semver瘫镇,語義化版本)鼎兽。此時(shí)如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有該模塊信息直接拿即可,如果沒有則從倉庫獲取铣除。如 packaeg.json 中某個(gè)包的版本是 ^1.1.0谚咬,npm 就會(huì)去倉庫中獲取符合 1.x.x 形式的最新版本。
- 獲取模塊實(shí)體尚粘。上一步會(huì)獲取到模塊的壓縮包地址(resolved 字段)择卦,npm 會(huì)用此地址檢查本地緩存,緩存中有就直接拿郎嫁,如果沒有則從倉庫下載秉继。
- 查找該模塊依賴,如果有依賴則回到第1步泽铛,如果沒有則停止尚辑。
-
模塊扁平化(dedupe)
上一步獲取到的是一棵完整的依賴樹,其中可能包含大量重復(fù)模塊盔腔。比如 A 模塊依賴于 loadsh杠茬,B 模塊同樣依賴于 lodash。在 npm3 以前會(huì)嚴(yán)格按照依賴樹的結(jié)構(gòu)進(jìn)行安裝铲觉,因此會(huì)造成模塊冗余澈蝙。
從 npm3 開始默認(rèn)加入了一個(gè) dedupe 的過程。它會(huì)遍歷所有節(jié)點(diǎn)撵幽,逐個(gè)將模塊放在根節(jié)點(diǎn)下面,也就是 node-modules 的第一層礁击。當(dāng)發(fā)現(xiàn)有重復(fù)模塊時(shí)盐杂,則將其丟棄逗载。
這里需要對(duì)重復(fù)模塊進(jìn)行一個(gè)定義,它指的是模塊名相同且 semver 兼容链烈。每個(gè) semver 都對(duì)應(yīng)一段版本允許范圍厉斟,如果兩個(gè)模塊的版本允許范圍存在交集,那么就可以得到一個(gè)兼容版本强衡,而不必版本號(hào)完全一致擦秽,這可以使更多冗余模塊在 dedupe 過程中被去掉。
比如 node-modules 下 foo 模塊依賴 lodash@^1.0.0漩勤,bar 模塊依賴 lodash@^1.1.0感挥,則 ^1.1.0 為兼容版本。
而當(dāng) foo 依賴 lodash@^2.0.0越败,bar 依賴 lodash@^1.1.0触幼,則依據(jù) semver 的規(guī)則,二者不存在兼容版本究飞。會(huì)將一個(gè)版本放在 node_modules 中置谦,另一個(gè)仍保留在依賴樹里。
舉個(gè)例子亿傅,假設(shè)一個(gè)依賴樹原本是這樣:
node_modules
-- foo
---- lodash@version1
-- bar
---- lodash@version2
假設(shè) version1 和 version2 是兼容版本媒峡,則經(jīng)過 dedupe 會(huì)成為下面的形式:
node_modules
-- foo
-- bar
-- lodash(保留的版本為兼容版本)
假設(shè) version1 和 version2 為非兼容版本,則后面的版本保留在依賴樹中:
node_modules
-- foo
-- lodash@version1
-- bar
---- lodash@version2
-
安裝模塊
這一步將會(huì)更新工程中的 node_modules葵擎,并執(zhí)行模塊中的生命周期函數(shù)(按照 preinstall丝蹭、install、postinstall 的順序)坪蚁。
-
執(zhí)行工程自身生命周期
當(dāng)前 npm 工程如果定義了鉤子此時(shí)會(huì)被執(zhí)行(按照 install奔穿、postinstall、prepublish敏晤、prepare 的順序)贱田。
最后一步是生成或更新版本描述文件,npm install 過程完成嘴脾。
5男摧、ES5的繼承和ES6的繼承有什么區(qū)別?
參考答案
ES5的繼承時(shí)通過prototype或構(gòu)造函數(shù)機(jī)制來實(shí)現(xiàn)译打。ES5的繼承實(shí)質(zhì)上是先創(chuàng)建子類的實(shí)例對(duì)象耗拓,然后再將父類的方法添加到this上(Parent.apply(this))。
ES6的繼承機(jī)制完全不同奏司,實(shí)質(zhì)上是先創(chuàng)建父類的實(shí)例對(duì)象this(所以必須先調(diào)用父類的super()方法)乔询,然后再用子類的構(gòu)造函數(shù)修改this。
具體的:ES6通過class關(guān)鍵字定義類韵洋,里面有構(gòu)造方法竿刁,類之間通過extends關(guān)鍵字實(shí)現(xiàn)繼承黄锤。子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例報(bào)錯(cuò)食拜。因?yàn)樽宇悰]有自己的this對(duì)象鸵熟,而是繼承了父類的this對(duì)象,然后對(duì)其進(jìn)行加工负甸。如果不調(diào)用super方法流强,子類得不到this對(duì)象。
ps:super關(guān)鍵字指代父類的實(shí)例呻待,即父類的this對(duì)象打月。在子類構(gòu)造函數(shù)中,調(diào)用super后带污,才可使用this關(guān)鍵字僵控,否則報(bào)錯(cuò)。
6鱼冀、setTimeout报破、Promise、Async/Await 的區(qū)別
參考答案:
https://gongchenghuigch.github.io/2019/09/14/awat/
7千绪、定時(shí)器的執(zhí)行順序或機(jī)制充易?
參考答案
因?yàn)閖s是單線程的,瀏覽器遇到setTimeout或者setInterval會(huì)先執(zhí)行完當(dāng)前的代碼塊荸型,在此之前會(huì)把定時(shí)器推入瀏覽器的待執(zhí)行事件隊(duì)列里面盹靴,等到瀏覽器執(zhí)行完當(dāng)前代碼之后會(huì)看一下事件隊(duì)列里面有沒有任務(wù),有的話才執(zhí)行定時(shí)器的代碼瑞妇。所以即使把定時(shí)器的時(shí)間設(shè)置為0還是會(huì)先執(zhí)行當(dāng)前的一些代碼稿静。
function test(){
var aa = 0;
var testSet = setInterval(function(){
aa++;
console.log(123);
if(aa<10){
clearInterval(testSet);
}
},20);
var testSet1 = setTimeout(function(){
console.log(321)
},1000);
for(var i=0;i<10;i++){
console.log('test');
}
}
test()
輸出結(jié)果:
test //10次
undefined
123
321
8、['1','2','3'].map(parseInt) 輸出什么,為什么?
參考答案
輸出:[1, NaN, NaN]
- 首先讓我們回顧一下辕狰,map函數(shù)的第一個(gè)參數(shù)callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])
這個(gè)callback一共可以接收三個(gè)參數(shù)改备,其中第一個(gè)參數(shù)代表當(dāng)前被處理的元素,而第二個(gè)參數(shù)代表該元素的索引蔓倍。
- 而parseInt則是用來解析字符串的悬钳,使字符串成為指定基數(shù)的整數(shù)。
parseInt(string, radix)
接收兩個(gè)參數(shù)偶翅,第一個(gè)表示被處理的值(字符串)默勾,第二個(gè)表示為解析時(shí)的基數(shù)。 - 了解這兩個(gè)函數(shù)后聚谁,我們可以模擬一下運(yùn)行情況
- parseInt('1', 0) //radix為0時(shí)母剥,且string參數(shù)不以“0x”和“0”開頭時(shí),按照10為基數(shù)處理。這個(gè)時(shí)候返回1
- parseInt('2', 1) //基數(shù)為1(1進(jìn)制)表示的數(shù)中媳搪,最大值小于2铭段,所以無法解析骤宣,返回NaN
- parseInt('3', 2) //基數(shù)為2(2進(jìn)制)表示的數(shù)中秦爆,最大值小于3,所以無法解析憔披,返回NaN
- map函數(shù)返回的是一個(gè)數(shù)組等限,所以最后結(jié)果為[1, NaN, NaN]
9、Doctype作用? 嚴(yán)格模式與混雜模式如何區(qū)分芬膝?它們有何意義?
參考答案
Doctype聲明于文檔最前面望门,告訴瀏覽器以何種方式來渲染頁面,這里有兩種模式锰霜,嚴(yán)格模式和混雜模式筹误。
- 嚴(yán)格模式的排版和 JS 運(yùn)作模式是 以該瀏覽器支持的最高標(biāo)準(zhǔn)運(yùn)行。
- 混雜模式癣缅,向后兼容厨剪,模擬老式瀏覽器底靠,防止瀏覽器無法兼容頁面叁温。
10、fetch發(fā)送2次請(qǐng)求的原因
參考答案
fetch發(fā)送post請(qǐng)求的時(shí)候乃戈,總是發(fā)送2次屡立,第一次狀態(tài)碼是204直晨,第二次才成功?
原因很簡(jiǎn)單膨俐,因?yàn)槟阌胒etch的post請(qǐng)求的時(shí)候勇皇,導(dǎo)致fetch 第一次發(fā)送了一個(gè)Options請(qǐng)求,詢問服務(wù)器是否支持修改的請(qǐng)求頭焚刺,如果服務(wù)器支持敛摘,則在第二次中發(fā)送真正的請(qǐng)求。
http檩坚、瀏覽器對(duì)象
1着撩、HTTPS 握手過程中,客戶端如何驗(yàn)證證書的合法性
參考答案
-
首先什么是HTTP協(xié)議?
http協(xié)議是超文本傳輸協(xié)議匾委,位于tcp/ip四層模型中的應(yīng)用層拖叙;通過請(qǐng)求/響應(yīng)的方式在客戶端和服務(wù)器之間進(jìn)行通信;但是缺少安全性赂乐,http協(xié)議信息傳輸是通過明文的方式傳輸薯鳍,不做任何加密,相當(dāng)于在網(wǎng)絡(luò)上裸奔;容易被中間人惡意篡改挖滤,這種行為叫做中間人攻擊崩溪;
-
加密通信:
為了安全性,雙方可以使用對(duì)稱加密的方式key進(jìn)行信息交流斩松,但是這種方式對(duì)稱加密秘鑰也會(huì)被攔截伶唯,也不夠安全,進(jìn)而還是存在被中間人攻擊風(fēng)險(xiǎn)惧盹;
于是人們又想出來另外一種方式乳幸,使用非對(duì)稱加密的方式;使用公鑰/私鑰加解密钧椰;通信方A發(fā)起通信并攜帶自己的公鑰粹断,接收方B通過公鑰來加密對(duì)稱秘鑰;然后發(fā)送給發(fā)起方A嫡霞;A通過私鑰解密瓶埋;雙發(fā)接下來通過對(duì)稱秘鑰來進(jìn)行加密通信;但是這種方式還是會(huì)存在一種安全性诊沪;中間人雖然不知道發(fā)起方A的私鑰养筒,但是可以做到偷天換日,將攔截發(fā)起方的公鑰key;并將自己生成的一對(duì)公/私鑰的公鑰發(fā)送給B娄徊;接收方B并不知道公鑰已經(jīng)被偷偷換過闽颇;按照之前的流程,B通過公鑰加密自己生成的對(duì)稱加密秘鑰key2;發(fā)送給A寄锐;
這次通信再次被中間人攔截兵多,盡管后面的通信,兩者還是用key2通信橄仆,但是中間人已經(jīng)掌握了Key2;可以進(jìn)行輕松的加解密剩膘;還是存在被中間人攻擊風(fēng)險(xiǎn); 解決困境:權(quán)威的證書頒發(fā)機(jī)構(gòu)CA來解決盆顾;
制作證書:作為服務(wù)端的A怠褐,首先把自己的公鑰key1發(fā)給證書頒發(fā)機(jī)構(gòu),向證書頒發(fā)機(jī)構(gòu)進(jìn)行申請(qǐng)證書您宪;證書頒發(fā)機(jī)構(gòu)有一套自己的公私鑰奈懒,CA通過自己的私鑰來加密key1,并且通過服務(wù)端網(wǎng)址等信息生成一個(gè)證書簽名,證書簽名同樣使用機(jī)構(gòu)的私鑰進(jìn)行加密宪巨;制作完成后磷杏,機(jī)構(gòu)將證書發(fā)給A;
校驗(yàn)證書真?zhèn)危寒?dāng)B向服務(wù)端A發(fā)起請(qǐng)求通信的時(shí)候捏卓,A不再直接返回自己的公鑰极祸,而是返回一個(gè)證書;
說明:各大瀏覽器和操作系統(tǒng)已經(jīng)維護(hù)了所有的權(quán)威證書機(jī)構(gòu)的名稱和公鑰。B只需要知道是哪個(gè)權(quán)威機(jī)構(gòu)發(fā)的證書遥金,使用對(duì)應(yīng)的機(jī)構(gòu)公鑰浴捆,就可以解密出證書簽名;接下來稿械,B使用同樣的規(guī)則选泻,生成自己的證書簽名,如果兩個(gè)簽名是一致的溜哮,說明證書是有效的滔金;
簽名驗(yàn)證成功后色解,B就可以再次利用機(jī)構(gòu)的公鑰茂嗓,解密出A的公鑰key1;接下來的操作,就是和之前一樣的流程了科阎;
- 中間人是否會(huì)攔截發(fā)送假證書到B呢述吸?
因?yàn)樽C書的簽名是由服務(wù)器端網(wǎng)址等信息生成的,并且通過第三方機(jī)構(gòu)的私鑰加密中間人無法篡改锣笨;所以最關(guān)鍵的問題是證書簽名的真?zhèn)危?/p>
- https主要的思想是在http基礎(chǔ)上增加了ssl安全層蝌矛,即以上認(rèn)證過程;
2错英、TCP三次握手和四次揮手
參考答案
三次握手之所以是三次是保證client和server均讓對(duì)方知道自己的接收和發(fā)送能力沒問題而保證的最小次數(shù)入撒。
第一次client => server 只能server判斷出client具備發(fā)送能力
第二次 server => client client就可以判斷出server具備發(fā)送和接受能力。此時(shí)client還需讓server知道自己接收能力沒問題于是就有了第三次
第三次 client => server 雙方均保證了自己的接收和發(fā)送能力沒有問題
其中椭岩,為了保證后續(xù)的握手是為了應(yīng)答上一個(gè)握手茅逮,每次握手都會(huì)帶一個(gè)標(biāo)識(shí) seq,后續(xù)的ACK都會(huì)對(duì)這個(gè)seq進(jìn)行加一來進(jìn)行確認(rèn)判哥。
3献雅、img iframe script 來發(fā)送跨域請(qǐng)求有什么優(yōu)缺點(diǎn)?
參考答案
- iframe
優(yōu)點(diǎn):跨域完畢之后DOM操作和互相之間的JavaScript調(diào)用都是沒有問題的
缺點(diǎn):1.若結(jié)果要以URL參數(shù)傳遞塌计,這就意味著在結(jié)果數(shù)據(jù)量很大的時(shí)候需要分割傳遞挺身,巨煩。2.還有一個(gè)是iframe本身帶來的锌仅,母頁面和iframe本身的交互本身就有安全性限制章钾。
- script
優(yōu)點(diǎn):可以直接返回json格式的數(shù)據(jù),方便處理
缺點(diǎn):只接受GET請(qǐng)求方式
- 圖片ping
優(yōu)點(diǎn):可以訪問任何url热芹,一般用來進(jìn)行點(diǎn)擊追蹤贱傀,做頁面分析常用的方法
缺點(diǎn):不能訪問響應(yīng)文本,只能監(jiān)聽是否響應(yīng)
4剿吻、http和https的區(qū)別窍箍?
參考答案
http傳輸?shù)臄?shù)據(jù)都是未加密的,也就是明文的,網(wǎng)景公司設(shè)置了SSL協(xié)議來對(duì)http協(xié)議傳輸?shù)臄?shù)據(jù)進(jìn)行加密處理椰棘,簡(jiǎn)單來說https協(xié)議是由http和ssl協(xié)議構(gòu)建的可進(jìn)行加密傳輸和身份認(rèn)證的網(wǎng)絡(luò)協(xié)議纺棺,比http協(xié)議的安全性更高。主要的區(qū)別如下:
- Https協(xié)議需要ca證書邪狞,費(fèi)用較高祷蝌。
- http是超文本傳輸協(xié)議,信息是明文傳輸帆卓,https則是具有安全性的ssl加密傳輸協(xié)議巨朦。
- 使用不同的鏈接方式,端口也不同剑令,一般而言糊啡,http協(xié)議的端口為80,https的端口為443
- http的連接很簡(jiǎn)單吁津,是無狀態(tài)的棚蓄;HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議碍脏,比http協(xié)議安全梭依。
5、什么是Bom典尾?有哪些常用的Bom屬性役拴?
參考答案
Bom是瀏覽器對(duì)象
location對(duì)象
- location.href-- 返回或設(shè)置當(dāng)前文檔的URL
- location.search -- 返回URL中的查詢字符串部分。例如 http://www.dreamdu.com/dreamd... 返回包括(?)后面的內(nèi)容?id=5&name=dreamdu
- location.hash -- 返回URL#后面的內(nèi)容钾埂,如果沒有#河闰,返回空 location.host -- 返回URL中的域名部分,例如www.dreamdu.com
- location.hostname -- 返回URL中的主域名部分勃教,例如dreamdu.com
- location.pathname -- 返回URL的域名后的部分淤击。例如 http://www.dreamdu.com/xhtml/ 返回/xhtml/
- location.port -- 返回URL中的端口部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回8080
- location.protocol -- 返回URL中的協(xié)議部分故源。例如 http://www.dreamdu.com:8080/xhtml/ 返回(//)前面的內(nèi)容http:
- location.assign -- 設(shè)置當(dāng)前文檔的URL
- location.replace() -- 設(shè)置當(dāng)前文檔的URL污抬,并且在history對(duì)象的地址列表中移除這個(gè)URL location.replace(url);
- location.reload() -- 重載當(dāng)前頁面
history對(duì)象
- history.go() -- 前進(jìn)或后退指定的頁面數(shù)
- history.go(num); history.back() -- 后退一頁
- history.forward() -- 前進(jìn)一頁
Navigator對(duì)象
- navigator.userAgent -- 返回用戶代理頭的字符串表示(就是包括瀏覽器版本信息等的字符串)
- navigator.cookieEnabled -- 返回瀏覽器是否支持(啟用)cookie
6、Cookie绳军、sessionStorage印机、localStorage的區(qū)別
參考答案
共同點(diǎn):都是保存在瀏覽器端,并且是同源的
- Cookie:cookie數(shù)據(jù)始終在同源的http請(qǐng)求中攜帶(即使不需要)门驾,即cookie在瀏覽器和服務(wù)器間來回傳遞射赛。而sessionStorage和localStorage不會(huì)自動(dòng)把數(shù)據(jù)發(fā)給服務(wù)器,僅在本地保存奶是。cookie數(shù)據(jù)還有路徑(path)的概念楣责,可以限制cookie只屬于某個(gè)路徑下,存儲(chǔ)的大小很小只有4K左右竣灌。(key:可以在瀏覽器和服務(wù)器端來回傳遞,存儲(chǔ)容量小秆麸,只有大約4K左右)
- sessionStorage:僅在當(dāng)前瀏覽器窗口關(guān)閉前有效初嘹,自然也就不可能持久保持,localStorage:始終有效沮趣,窗口或?yàn)g覽器關(guān)閉也一直保存屯烦,因此用作持久數(shù)據(jù);cookie只在設(shè)置的cookie過期時(shí)間之前一直有效房铭,即使窗口或?yàn)g覽器關(guān)閉驻龟。(key:本身就是一個(gè)回話過程,關(guān)閉瀏覽器后消失缸匪,session為一個(gè)回話翁狐,當(dāng)頁面不同即使是同一頁面打開兩次,也被視為同一次回話)
- localStorage:localStorage 在所有同源窗口中都是共享的豪嗽;cookie也是在所有同源窗口中都是共享的谴蔑。(key:同源窗口都會(huì)共享,并且不會(huì)失效龟梦,不管窗口或者瀏覽器關(guān)閉與否都會(huì)始終生效)
補(bǔ)充說明一下cookie的作用:
- 保存用戶登錄狀態(tài)。例如將用戶id存儲(chǔ)于一個(gè)cookie內(nèi)窃躲,這樣當(dāng)用戶下次訪問該頁面時(shí)就不需要重新登錄了计贰,現(xiàn)在很多論壇和社區(qū)都提供這樣的功能。cookie還可以設(shè)置過期時(shí)間蒂窒,當(dāng)超過時(shí)間期限后躁倒,cookie就會(huì)自動(dòng)消失。因此洒琢,系統(tǒng)往往可以提示用戶保持登錄狀態(tài)的時(shí)間:常見選項(xiàng)有一個(gè)月秧秉、三個(gè) 月、一年等衰抑。
- 跟蹤用戶行為象迎。例如一個(gè)天氣預(yù)報(bào)網(wǎng)站,能夠根據(jù)用戶選擇的地區(qū)顯示當(dāng)?shù)氐奶鞖馇闆r呛踊。如果每次都需要選擇所在地是煩瑣的砾淌,當(dāng)利用了 cookie后就會(huì)顯得很人性化了,系統(tǒng)能夠記住上一次訪問的地區(qū)谭网,當(dāng)下次再打開該頁面時(shí)汪厨,它就會(huì)自動(dòng)顯示上次用戶所在地區(qū)的天氣情況。因?yàn)橐磺卸际窃诤?臺(tái)完成愉择,所以這樣的頁面就像為某個(gè)用戶所定制的一樣劫乱,使用起來非常方便
- 定制頁面织中。如果網(wǎng)站提供了換膚或更換布局的功能,那么可以使用cookie來記錄用戶的選項(xiàng)衷戈,例如:背景色抠璃、分辨率等。當(dāng)用戶下次訪問時(shí)脱惰,仍然可以保存上一次訪問的界面風(fēng)格搏嗡。
7、Cookie如何防范XSS攻擊
參考答案
XSS(跨站腳本攻擊)是指攻擊者在返回的HTML中嵌入javascript腳本拉一,為了減輕這些攻擊采盒,需要在HTTP頭部配上,set-cookie:
- httponly-這個(gè)屬性可以防止XSS,它會(huì)禁止javascript腳本來訪問cookie蔚润。
- secure - 這個(gè)屬性告訴瀏覽器僅在請(qǐng)求為https的時(shí)候發(fā)送cookie磅氨。
結(jié)果應(yīng)該是這樣的:Set-Cookie=.....
8、瀏覽器和 Node 事件循環(huán)的區(qū)別嫡纠?
參考答案
其中一個(gè)主要的區(qū)別在于瀏覽器的event loop 和nodejs的event loop 在處理異步事件的順序是不同的,nodejs中有micro event;其中Promise屬于micro event 該異步事件的處理順序就和瀏覽器不同.nodejs V11.0以上 這兩者之間的順序就相同了.
function test () {
console.log('start')
setTimeout(() => {
console.log('children2')
Promise.resolve().then(() => {console.log('children2-1')})
}, 0)
setTimeout(() => {
console.log('children3')
Promise.resolve().then(() => {console.log('children3-1')})
}, 0)
Promise.resolve().then(() => {console.log('children1')})
console.log('end')
}
test()
// 以上代碼在node11以下版本的執(zhí)行結(jié)果(先執(zhí)行所有的宏任務(wù)烦租,再執(zhí)行微任務(wù))
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1
// 以上代碼在node11及瀏覽器的執(zhí)行結(jié)果(順序執(zhí)行宏任務(wù)和微任務(wù))
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1
9、簡(jiǎn)述HTTPS中間人攻擊
參考答案
https協(xié)議由 http + ssl 協(xié)議構(gòu)成除盏,具體的鏈接過程可參考SSL或TLS握手的概述
中間人攻擊過程如下:
- 服務(wù)器向客戶端發(fā)送公鑰叉橱。
- 攻擊者截獲公鑰傻寂,保留在自己手上富稻。
- 然后攻擊者自己生成一個(gè)【偽造的】公鑰牲剃,發(fā)給客戶端疙咸。
- 客戶端收到偽造的公鑰后侮腹,生成加密hash值發(fā)給服務(wù)器斥废。
- 攻擊者獲得加密hash值井誉,用自己的私鑰解密獲得真秘鑰贡歧。
- 同時(shí)生成假的加密hash值抡句,發(fā)給服務(wù)器探膊。
- 服務(wù)器用私鑰解密獲得假秘鑰。
- 服務(wù)器用加秘鑰加密傳輸信息
防范方法:
- 服務(wù)端在發(fā)送瀏覽器的公鑰中加入CA證書待榔,瀏覽器可以驗(yàn)證CA證書的有效性
10逞壁、說幾條web前端優(yōu)化策略
參考答案
(1). 減少HTTP請(qǐng)求數(shù)
這條策略基本上所有前端人都知道,而且也是最重要最有效的究抓。都說要減少HTTP請(qǐng)求猾担,那請(qǐng)求多了到底會(huì)怎么樣呢?首先刺下,每個(gè)請(qǐng)求都是有成本的绑嘹,既包 含時(shí)間成本也包含資源成本。一個(gè)完整的請(qǐng)求都需要經(jīng)過DNS尋址橘茉、與服務(wù)器建立連接工腋、發(fā)送數(shù)據(jù)姨丈、等待服務(wù)器響應(yīng)、接收數(shù)據(jù)這樣一個(gè)“漫長(zhǎng)”而復(fù)雜的過程擅腰。時(shí)間成本就是用戶需要看到或者“感受”到這個(gè)資源是必須要等待這個(gè)過程結(jié)束的蟋恬,資源上由于每個(gè)請(qǐng)求都需要攜帶數(shù)據(jù),因此每個(gè)請(qǐng)求都需要占用帶寬趁冈。
另外歼争,由于瀏覽器進(jìn)行并發(fā)請(qǐng)求的請(qǐng)求數(shù)是有上限的,因此請(qǐng)求數(shù)多了以后渗勘,瀏覽器需要分批進(jìn)行請(qǐng)求沐绒,因此會(huì)增加用戶的等待時(shí)間,會(huì)給 用戶造成站點(diǎn)速度慢這樣一個(gè)印象旺坠,即使可能用戶能看到的第一屏的資源都已經(jīng)請(qǐng)求完了乔遮,但是瀏覽器的進(jìn)度條會(huì)一直存在。減少HTTP請(qǐng)求數(shù)的主要途徑包括:
(2). 從設(shè)計(jì)實(shí)現(xiàn)層面簡(jiǎn)化頁面
如果你的頁面像百度首頁一樣簡(jiǎn)單取刃,那么接下來的規(guī)則基本上都用不著了蹋肮。保持頁面簡(jiǎn)潔、減少資源的使用時(shí)最直接的璧疗。如果不是這樣坯辩,你的頁面需要華麗的皮膚,則繼續(xù)閱讀下面的內(nèi)容病毡。
(3). 合理設(shè)置HTTP緩存
緩存的力量是強(qiáng)大的濒翻,恰當(dāng)?shù)木彺嬖O(shè)置可以大大的減少HTTP請(qǐng)求。以有啊首頁為例啦膜,當(dāng)瀏覽器沒有緩存的時(shí)候訪問一共會(huì)發(fā)出78個(gè)請(qǐng)求,共600多K 數(shù)據(jù)(如圖1.1)淌喻,而當(dāng)?shù)诙卧L問即瀏覽器已緩存之后訪問則僅有10個(gè)請(qǐng)求僧家,共20多K數(shù)據(jù)(如圖1.2)。(這里需要說明的是裸删,如果直接F5刷新頁面 的話效果是不一樣的八拱,這種情況下請(qǐng)求數(shù)還是一樣,不過被緩存資源的請(qǐng)求服務(wù)器是304響應(yīng)涯塔,只有Header沒有Body肌稻,可以節(jié)省帶寬)
怎樣才算合理設(shè)置?原則很簡(jiǎn)單匕荸,能緩存越多越好爹谭,能緩存越久越好。例如榛搔,很少變化的圖片資源可以直接通過HTTP Header中的Expires設(shè)置一個(gè)很長(zhǎng)的過期頭诺凡;變化不頻繁而又可能會(huì)變的資源可以使用Last-Modifed來做請(qǐng)求驗(yàn)證东揣。盡可能的讓資源能夠 在緩存中待得更久。
(4). 資源合并與壓縮
如果可以的話腹泌,盡可能的將外部的腳本嘶卧、樣式進(jìn)行合并,多個(gè)合為一個(gè)凉袱。另外芥吟,CSS、Javascript专甩、Image都可以用相應(yīng)的工具進(jìn)行壓縮钟鸵,壓縮后往往能省下不少空間。
(5). CSS Sprites
合并CSS圖片配深,減少請(qǐng)求數(shù)的又一個(gè)好辦法携添。
(6). Inline Images
使用data: URL scheme的方式將圖片嵌入到頁面或CSS中,如果不考慮資源管理上的問題的話篓叶,不失為一個(gè)好辦法烈掠。如果是嵌入頁面的話換來的是增大了頁面的體積,而且無法利用瀏覽器緩存缸托。使用在CSS中的圖片則更為理想一些左敌。
(7). Lazy Load Images
這條策略實(shí)際上并不一定能減少HTTP請(qǐng)求數(shù),但是卻能在某些條件下或者頁面剛加載時(shí)減少HTTP請(qǐng)求數(shù)俐镐。對(duì)于圖片而言矫限,在頁面剛加載的時(shí)候可以只 加載第一屏,當(dāng)用戶繼續(xù)往后滾屏的時(shí)候才加載后續(xù)的圖片佩抹。這樣一來叼风,假如用戶只對(duì)第一屏的內(nèi)容感興趣時(shí),那剩余的圖片請(qǐng)求就都節(jié)省了棍苹。有啊首頁曾經(jīng)的做法 是在加載的時(shí)候把第一屏之后的圖片地址緩存在Textarea標(biāo)簽中无宿,待用戶往下滾屏的時(shí)候才“惰性”加載。
11枢里、你了解的瀏覽器的重繪和回流導(dǎo)致的性能問題
參考答案
重繪(Repaint)和回流(Reflow)
重繪和回流是渲染步驟中的一小節(jié)孽鸡,但是這兩個(gè)步驟對(duì)于性能影響很大。
- 重繪是當(dāng)節(jié)點(diǎn)需要更改外觀而不會(huì)影響布局的栏豺,比如改變
color
就叫稱為重繪 - 回流是布局或者幾何屬性需要改變就稱為回流彬碱。
回流必定會(huì)發(fā)生重繪,重繪不一定會(huì)引發(fā)回流奥洼∠锾郏回流所需的成本比重繪高的多,改變深層次的節(jié)點(diǎn)很可能導(dǎo)致父節(jié)點(diǎn)的一系列回流溉卓。
所以以下幾個(gè)動(dòng)作可能會(huì)導(dǎo)致性能問題:
- 改變 window 大小
- 改變字體
- 添加或刪除樣式
- 文字改變
- 定位或者浮動(dòng)
- 盒模型
很多人不知道的是皮迟,重繪和回流其實(shí)和 Event loop 有關(guān)搬泥。
- 當(dāng) Event loop 執(zhí)行完 Microtasks 后,會(huì)判斷 document 是否需要更新伏尼。因?yàn)闉g覽器是 60Hz 的刷新率忿檩,每 16ms 才會(huì)更新一次。
- 然后判斷是否有
resize
或者scroll
爆阶,有的話會(huì)去觸發(fā)事件燥透,所以resize
和scroll
事件也是至少 16ms 才會(huì)觸發(fā)一次,并且自帶節(jié)流功能辨图。 - 判斷是否觸發(fā)了 media query
- 更新動(dòng)畫并且發(fā)送事件
- 判斷是否有全屏操作事件
- 執(zhí)行
requestAnimationFrame
回調(diào) - 執(zhí)行
IntersectionObserver
回調(diào)班套,該方法用于判斷元素是否可見,可以用于懶加載上故河,但是兼容性不好 - 更新界面
- 以上就是一幀中可能會(huì)做的事情吱韭。如果在一幀中有空閑時(shí)間,就會(huì)去執(zhí)行
requestIdleCallback
回調(diào)鱼的。
減少重繪和回流
- 使用
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
理盆,因?yàn)榍罢咧粫?huì)引起重繪,后者會(huì)引發(fā)回流(改變了布局)把 DOM 離線后修改凑阶,比如:先把 DOM 給
display:none
(有一次 Reflow)猿规,然后你修改100次,然后再把它顯示出來不要把 DOM 結(jié)點(diǎn)的屬性值放在一個(gè)循環(huán)里當(dāng)成循環(huán)里的變量
<pre style="margin: 10px 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 5px; box-shadow: rgba(0, 0, 0, 0.55) 0px 2px 10px;">
for(let i = 0; i < 1000; i++) { // 獲取 offsetTop 會(huì)導(dǎo)致回流宙橱,因?yàn)樾枰カ@取正確的值 console.log(document.querySelector('.test').style.offsetTop) }
</pre> 不要使用 table 布局姨俩,可能很小的一個(gè)小改動(dòng)會(huì)造成整個(gè) table 的重新布局
動(dòng)畫實(shí)現(xiàn)的速度的選擇,動(dòng)畫速度越快师郑,回流次數(shù)越多环葵,也可以選擇使用
requestAnimationFrame
CSS 選擇符從右往左匹配查找,避免 DOM 深度過深
將頻繁運(yùn)行的動(dòng)畫變?yōu)閳D層宝冕,圖層能夠阻止該節(jié)點(diǎn)回流影響別的元素积担。比如對(duì)于
video
標(biāo)簽,瀏覽器會(huì)自動(dòng)將該節(jié)點(diǎn)變?yōu)閳D層猬仁。
react、Vue
1先誉、寫 React / Vue 項(xiàng)目時(shí)為什么要在列表組件中寫 key湿刽,其作用是什么?
參考答案
vue和react都是采用diff算法來對(duì)比新舊虛擬節(jié)點(diǎn)褐耳,從而更新節(jié)點(diǎn)诈闺。在vue的diff函數(shù)中(建議先了解一下diff算法過程)。
在交叉對(duì)比中铃芦,當(dāng)新節(jié)點(diǎn)跟舊節(jié)點(diǎn)頭尾交叉對(duì)比
沒有結(jié)果時(shí)雅镊,會(huì)根據(jù)新節(jié)點(diǎn)的key去對(duì)比舊節(jié)點(diǎn)數(shù)組中的key襟雷,從而找到相應(yīng)舊節(jié)點(diǎn)(這里對(duì)應(yīng)的是一個(gè)key => index 的map映射)。如果沒找到就認(rèn)為是一個(gè)新增節(jié)點(diǎn)仁烹。而如果沒有key耸弄,那么就會(huì)采用遍歷查找的方式去找到對(duì)應(yīng)的舊節(jié)點(diǎn)。一種一個(gè)map映射卓缰,另一種是遍歷查找计呈。相比而言。map映射的速度更快征唬。
vue部分源碼如下:
// vue項(xiàng)目 src/core/vdom/patch.js -488行
// 以下是為了閱讀性進(jìn)行格式化后的代碼
// oldCh 是一個(gè)舊虛擬節(jié)點(diǎn)數(shù)組
if (isUndef(oldKeyToIdx)) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
if(isDef(newStartVnode.key)) {
// map 方式獲取
idxInOld = oldKeyToIdx[newStartVnode.key]
} else {
// 遍歷方式獲取
idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
}
創(chuàng)建map函數(shù)
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
遍歷尋找
// sameVnode 是對(duì)比新舊節(jié)點(diǎn)是否相同的函數(shù)
function findIdxInOld (node, oldCh, start, end) {
for (let i = start; i < end; i++) {
const c = oldCh[i]
if (isDef(c) && sameVnode(node, c)) return i
}
}
2捌显、React 中 setState 什么時(shí)候是同步的,什么時(shí)候是異步的总寒?
參考答案
在React中扶歪,如果是由React引發(fā)的事件處理(比如通過onClick引發(fā)的事件處理),調(diào)用setState不會(huì)同步更新this.state摄闸,除此之外的setState調(diào)用會(huì)同步執(zhí)行this.state善镰。所謂“除此之外”,指的是繞過React通過addEventListener直接添加的事件處理函數(shù)贪薪,還有通過setTimeout/setInterval產(chǎn)生的異步調(diào)用媳禁。
原因:在React的setState函數(shù)實(shí)現(xiàn)中,會(huì)根據(jù)一個(gè)變量isBatchingUpdates判斷是直接更新this.state還是放到隊(duì)列中回頭再說画切,而isBatchingUpdates默認(rèn)是false竣稽,也就表示setState會(huì)同步更新this.state,但是霍弹,有一個(gè)函數(shù)batchedUpdates毫别,這個(gè)函數(shù)會(huì)把isBatchingUpdates修改為true,而當(dāng)React在調(diào)用事件處理函數(shù)之前就會(huì)調(diào)用這個(gè)batchedUpdates典格,造成的后果岛宦,就是由React控制的事件處理過程setState不會(huì)同步更新this.state。
3耍缴、下面輸出什么
class Example extends React.Component {
constructor() {
super();
this.state = {
val: 0
};
}
componentDidMount() {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 1 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 2 次 log
setTimeout(() => {
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 3 次 log
this.setState({val: this.state.val + 1});
console.log(this.state.val); // 第 4 次 log
}, 0);
}
render() {
return null;
}
};
1砾肺、第一次和第二次都是在 react 自身生命周期內(nèi),觸發(fā)時(shí) isBatchingUpdates 為 true防嗡,所以并不會(huì)直接執(zhí)行更新 state变汪,而是加入了 dirtyComponents,所以打印時(shí)獲取的都是更新前的狀態(tài) 0蚁趁。
2裙盾、兩次 setState 時(shí),獲取到 this.state.val 都是 0,所以執(zhí)行時(shí)都是將 0 設(shè)置成 1番官,在 react 內(nèi)部會(huì)被合并掉庐完,只執(zhí)行一次。設(shè)置完成后 state.val 值為 1徘熔。
3门躯、setTimeout 中的代碼,觸發(fā)時(shí) isBatchingUpdates 為 false近顷,所以能夠直接進(jìn)行更新生音,所以連著輸出 2,3窒升。
輸出: 0 0 2 3
4缀遍、為什么虛擬dom會(huì)提高性能?
參考答案
虛擬dom相當(dāng)于在js和真實(shí)dom中間加了一個(gè)緩存,利用dom diff算法避免了沒有必要的dom操作饱须,從而提高性能域醇。
具體實(shí)現(xiàn)步驟如下:
用 JavaScript 對(duì)象結(jié)構(gòu)表示 DOM 樹的結(jié)構(gòu);然后用這個(gè)樹構(gòu)建一個(gè)真正的 DOM 樹蓉媳,插到文檔當(dāng)中
當(dāng)狀態(tài)變更的時(shí)候譬挚,重新構(gòu)造一棵新的對(duì)象樹。然后用新的樹和舊的樹進(jìn)行比較酪呻,記錄兩棵樹差異
把2所記錄的差異應(yīng)用到步驟1所構(gòu)建的真正的DOM樹上减宣,視圖就更新了。
css
1玩荠、分析比較 opacity: 0漆腌、visibility: hidden、display: none 優(yōu)劣和適用場(chǎng)景
參考答案
結(jié)構(gòu):
display:none: 會(huì)讓元素完全從渲染樹中消失阶冈,渲染的時(shí)候不占據(jù)任何空間, 不能點(diǎn)擊闷尿,
visibility: hidden:不會(huì)讓元素從渲染樹消失,渲染元素繼續(xù)占據(jù)空間女坑,只是內(nèi)容不可見填具,不能點(diǎn)擊
opacity: 0: 不會(huì)讓元素從渲染樹消失式镐,渲染元素繼續(xù)占據(jù)空間肪凛,只是內(nèi)容不可見,可以點(diǎn)擊
繼承:
display: none和opacity: 0:是非繼承屬性柄沮,子孫節(jié)點(diǎn)消失由于元素從渲染樹消失造成碉就,通過修改子孫節(jié)點(diǎn)屬性無法顯示枢泰。
visibility: hidden:是繼承屬性,子孫節(jié)點(diǎn)消失由于繼承了hidden铝噩,通過設(shè)置visibility: visible;可以讓子孫節(jié)點(diǎn)顯式。
性能:
displaynone : 修改元素會(huì)造成文檔回流,讀屏器不會(huì)讀取display: none元素內(nèi)容,性能消耗較大
visibility:hidden: 修改元素只會(huì)造成本元素的重繪,性能消耗較少讀屏器讀取visibility: hidden元素內(nèi)容
opacity: 0 :修改元素會(huì)造成重繪骏庸,性能消耗較少
聯(lián)系:它們都能讓元素不可見
2毛甲、清除浮動(dòng)的方式有哪些?比較好的是哪一種?
參考答案
常用的一般為三種.clearfix
, clear:both
,overflow:hidden
;
比較好是 .clearfix
,偽元素萬金油版本,后兩者有局限性.
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
<!--
為毛沒有 zoom ,_height 這些,IE6,7這類需要 csshack 不再我們考慮之內(nèi)了
.clearfix 還有另外一種寫法,
-->
.clearfix:before, .clearfix:after {
content:"";
display:table;
}
.clearfix:after{
clear:both;
overflow:hidden;
}
.clearfix{
zoom:1;
}
<!--
用display:table 是為了避免外邊距margin重疊導(dǎo)致的margin塌陷,
內(nèi)部元素默認(rèn)會(huì)成為 table-cell 單元格的形式
-->`
clear:both
:若是用在同一個(gè)容器內(nèi)相鄰元素上,那是賊好的,有時(shí)候在容器外就有些問題了, 比如相鄰容器的包裹層元素塌陷
overflow:hidden
:這種若是用在同個(gè)容器內(nèi),可以形成 BFC
避免浮動(dòng)造成的元素塌陷
4、css sprite 是什么,有什么優(yōu)缺點(diǎn)
參考答案
概念:將多個(gè)小圖片拼接到一個(gè)圖片中具被。通過 background-position 和元素尺寸調(diào)節(jié)需要顯示的背景圖案玻募。
優(yōu)點(diǎn):
- 減少 HTTP 請(qǐng)求數(shù),極大地提高頁面加載速度
- 增加圖片信息重復(fù)度一姿,提高壓縮比七咧,減少圖片大小
- 更換風(fēng)格方便,只需在一張或幾張圖片上修改顏色或樣式即可實(shí)現(xiàn)
缺點(diǎn):
- 圖片合并麻煩
- 維護(hù)麻煩叮叹,修改一個(gè)圖片可能需要重新布局整個(gè)圖片艾栋,樣式
5、link
與@import
的區(qū)別
參考答案
-
link
是 HTML 方式蛉顽,@import
是 CSS 方式 -
link
最大限度支持并行下載蝗砾,@import
過多嵌套導(dǎo)致串行下載,出現(xiàn)FOUC -
link
可以通過rel="alternate stylesheet"
指定候選樣式 - 瀏覽器對(duì)
link
支持早于@import
携冤,可以使用@import
對(duì)老瀏覽器隱藏樣式 -
@import
必須在樣式規(guī)則之前悼粮,可以在 css 文件中引用其他文件 - 總體來說:link 優(yōu)于@import
6、display: block;
和display: inline;
的區(qū)別
參考答案
block
元素特點(diǎn):
1.處于常規(guī)流中時(shí)曾棕,如果width
沒有設(shè)置扣猫,會(huì)自動(dòng)填充滿父容器 2.可以應(yīng)用margin/padding
3.在沒有設(shè)置高度的情況下會(huì)擴(kuò)展高度以包含常規(guī)流中的子元素 4.處于常規(guī)流中時(shí)布局時(shí)在前后元素位置之間(獨(dú)占一個(gè)水平空間) 5.忽略vertical-align
inline
元素特點(diǎn)
1.水平方向上根據(jù)direction
依次布局
2.不會(huì)在元素前后進(jìn)行換行
3.受white-space
控制
4.margin/padding
在豎直方向上無效,水平方向上有效
5.width/height
屬性對(duì)非替換行內(nèi)元素?zé)o效翘地,寬度由元素內(nèi)容決定
6.非替換行內(nèi)元素的行框高由line-height
確定申尤,替換行內(nèi)元素的行框高由height
,margin
,padding
,border
決定
7.浮動(dòng)或絕對(duì)定位時(shí)會(huì)轉(zhuǎn)換為block
8.vertical-align
屬性生效
7、容器包含若干浮動(dòng)元素時(shí)如何清理浮動(dòng)
參考答案
- 容器元素閉合標(biāo)簽前添加額外元素并設(shè)置
clear: both
- 父元素觸發(fā)塊級(jí)格式化上下文(見塊級(jí)可視化上下文部分)
- 設(shè)置容器元素偽元素進(jìn)行清理
/**
* 在標(biāo)準(zhǔn)瀏覽器下使用
* 1 content內(nèi)容為空格用于修復(fù)opera下文檔中出現(xiàn)
* contenteditable屬性時(shí)在清理浮動(dòng)元素上下的空白
* 2 使用display使用table而不是block:可以防止容器和
* 子元素top-margin折疊,這樣能使清理效果與BFC子眶,IE6/7
* zoom: 1;一致
**/
.clearfix:before,
.clearfix:after {
content: " "; /* 1 */
display: table; /* 2 */
}
.clearfix:after {
clear: both;
}
/**
* IE 6/7下使用
* 通過觸發(fā)hasLayout實(shí)現(xiàn)包含浮動(dòng)
**/
.clearfix {
*zoom: 1;
}
8瀑凝、PNG,GIF,JPG 的區(qū)別及如何選
參考答案
GIF:
- 8 位像素,256 色
- 無損壓縮
- 支持簡(jiǎn)單動(dòng)畫
- 支持 boolean 透明
- 適合簡(jiǎn)單動(dòng)畫
JPEG:
- 顏色限于 256
- 有損壓縮
- 可控制壓縮質(zhì)量
- 不支持透明
- 適合照片
PNG:
- 有 PNG8 和 truecolor PNG
- PNG8 類似 GIF 顏色上限為 256臭杰,文件小粤咪,支持 alpha 透明度,無動(dòng)畫
- 適合圖標(biāo)渴杆、背景寥枝、按鈕
9、display,float,position 的關(guān)系
參考答案
- 如果
display
為 none磁奖,那么 position 和 float 都不起作用囊拜,這種情況下元素不產(chǎn)生框 - 否則,如果 position 值為 absolute 或者 fixed比搭,框就是絕對(duì)定位的冠跷,float 的計(jì)算值為 none,display 根據(jù)下面的表格進(jìn)行調(diào)整。
- 否則蜜托,如果 float 不是 none抄囚,框是浮動(dòng)的,display 根據(jù)下表進(jìn)行調(diào)整
- 否則橄务,如果元素是根元素幔托,display 根據(jù)下表進(jìn)行調(diào)整
- 其他情況下 display 的值為指定值 總結(jié)起來:絕對(duì)定位、浮動(dòng)蜂挪、根元素都需要調(diào)整display
10重挑、如何水平居中一個(gè)元素
參考答案
如果需要居中的元素為常規(guī)流中 inline 元素,為父元素設(shè)置
text-align: center;
即可實(shí)現(xiàn)如果需要居中的元素為常規(guī)流中 block 元素棠涮,1)為元素設(shè)置寬度谬哀,2)設(shè)置左右 margin 為 auto。3)IE6 下需在父元素上設(shè)置
text-align: center;
,再給子元素恢復(fù)需要的值
<body>
<div class="content">
aaaaaa aaaaaa a a a a a a a a
</div>
</body>
<style>
body {
background: #DDD;
text-align: center; /* 3 */
}
.content {
width: 500px; /* 1 */
text-align: left; /* 3 */
margin: 0 auto; /* 2 */
background: purple;
}
</style>
- 如果需要居中的元素為浮動(dòng)元素故爵,1)為元素設(shè)置寬度玻粪,2)
position: relative;
,3)浮動(dòng)方向偏移量(left 或者 right)設(shè)置為 50%诬垂,4)浮動(dòng)方向上的 margin 設(shè)置為元素寬度一半乘以-1
<body>
<div class="content">
aaaaaa aaaaaa a a a a a a a a
</div>
</body>
<style>
body {
background: #DDD;
}
.content {
width: 500px; /* 1 */
float: left;
position: relative; /* 2 */
left: 50%; /* 3 */
margin-left: -250px; /* 4 */
background-color: purple;
}
</style>
- 如果需要居中的元素為絕對(duì)定位元素劲室,1)為元素設(shè)置寬度,2)偏移量設(shè)置為 50%结窘,3)偏移方向外邊距設(shè)置為元素寬度一半乘以-1
<body>
<div class="content">
aaaaaa aaaaaa a a a a a a a a
</div>
</body>
<style>
body {
background: #DDD;
position: relative;
}
.content {
width: 800px;
position: absolute;
left: 50%;
margin-left: -400px;
background-color: purple;
}
</style>
- 如果需要居中的元素為絕對(duì)定位元素很洋,1)為元素設(shè)置寬度,2)設(shè)置左右偏移量都為 0,3)設(shè)置左右外邊距都為 auto
<body>
<div class="content">
aaaaaa aaaaaa a a a a a a a a
</div>
</body>
<style>
body {
background: #DDD;
position: relative;
}
.content {
width: 800px;
position: absolute;
margin: 0 auto;
left: 0;
right: 0;
background-color: purple;
}
</style>
JavaScript
1隧枫、JS有幾種數(shù)據(jù)類型,其中基本數(shù)據(jù)類型有哪些?
參考答案
七種數(shù)據(jù)類型
- Boolean
- Null
- Undefined
- Number
- String
- Symbol (ECMAScript 6 新定義)
- Object
(ES6之前)其中5種為基本類型:string
,number
,boolean
,null
,undefined
,
ES6出來的Symbol
也是原始數(shù)據(jù)類型 喉磁,表示獨(dú)一無二的值
Object
為引用類型(范圍挺大),也包括數(shù)組、函數(shù),
2官脓、Promise 構(gòu)造函數(shù)是同步執(zhí)行還是異步執(zhí)行协怒,那么 then 方法呢?
參考答案
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
輸出結(jié)果是:
1
2
4
3
promise構(gòu)造函數(shù)是同步執(zhí)行的卑笨,then方法是異步執(zhí)行的
Promise new的時(shí)候會(huì)立即執(zhí)行里面的代碼 then是微任務(wù) 會(huì)在本次任務(wù)執(zhí)行完的時(shí)候執(zhí)行 setTimeout是宏任務(wù) 會(huì)在下次任務(wù)執(zhí)行的時(shí)候執(zhí)行
3孕暇、JS的四種設(shè)計(jì)模式
參考答案
工廠模式
簡(jiǎn)單的工廠模式可以理解為解決多個(gè)相似的問題;
function CreatePerson(name,age,sex) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.sex = sex;
obj.sayName = function(){
return this.name;
}
return obj;
}
var p1 = new CreatePerson("longen",'28','男');
var p2 = new CreatePerson("tugenhua",'27','女');
console.log(p1.name); // longen
console.log(p1.age); // 28
console.log(p1.sex); // 男
console.log(p1.sayName()); // longen
console.log(p2.name); // tugenhua
console.log(p2.age); // 27
console.log(p2.sex); // 女
console.log(p2.sayName()); // tugenhua
單例模式
只能被實(shí)例化(構(gòu)造函數(shù)給實(shí)例添加屬性與方法)一次
// 單體模式
var Singleton = function(name){
this.name = name;
};
Singleton.prototype.getName = function(){
return this.name;
}
// 獲取實(shí)例對(duì)象
var getInstance = (function() {
var instance = null;
return function(name) {
if(!instance) {//相當(dāng)于一個(gè)一次性閥門,只能實(shí)例化一次
instance = new Singleton(name);
}
return instance;
}
})();
// 測(cè)試單體模式的實(shí)例,所以a===b
var a = getInstance("aa");
var b = getInstance("bb");
沙箱模式
將一些函數(shù)放到自執(zhí)行函數(shù)里面,但要用閉包暴露接口,用變量接收暴露的接口,再調(diào)用里面的值,否則無法使用里面的值
let sandboxModel=(function(){
function sayName(){};
function sayAge(){};
return{
sayName:sayName,
sayAge:sayAge
}
})()
發(fā)布者訂閱模式
就例如如我們關(guān)注了某一個(gè)公眾號(hào),然后他對(duì)應(yīng)的有新的消息就會(huì)給你推送,
//發(fā)布者與訂閱模式
var shoeObj = {}; // 定義發(fā)布者
shoeObj.list = []; // 緩存列表 存放訂閱者回調(diào)函數(shù)
// 增加訂閱者
shoeObj.listen = function(fn) {
shoeObj.list.push(fn); // 訂閱消息添加到緩存列表
}
// 發(fā)布消息
shoeObj.trigger = function() {
for (var i = 0, fn; fn = this.list[i++];) {
fn.apply(this, arguments);//第一個(gè)參數(shù)只是改變fn的this,
}
}
// 小紅訂閱如下消息
shoeObj.listen(function(color, size) {
console.log("顏色是:" + color);
console.log("尺碼是:" + size);
});
// 小花訂閱如下消息
shoeObj.listen(function(color, size) {
console.log("再次打印顏色是:" + color);
console.log("再次打印尺碼是:" + size);
});
shoeObj.trigger("紅色", 40);
shoeObj.trigger("黑色", 42);
代碼實(shí)現(xiàn)邏輯是用數(shù)組存貯訂閱者, 發(fā)布者回調(diào)函數(shù)里面通知的方式是遍歷訂閱者數(shù)組,并將發(fā)布者內(nèi)容傳入訂閱者數(shù)組
4、列舉出集中創(chuàng)建實(shí)例的方法
參考答案
1.字面量
let obj={'name':'張三'}
2.Object構(gòu)造函數(shù)創(chuàng)建
let Obj=new Object()
Obj.name='張三'
3.使用工廠模式創(chuàng)建對(duì)象
function createPerson(name){
var o = new Object();
o.name = name;
};
return o;
}
var person1 = createPerson('張三');
4.使用構(gòu)造函數(shù)創(chuàng)建對(duì)象
function Person(name){
this.name = name;
}
var person1 = new Person('張三');
5赤兴、簡(jiǎn)述一下前端事件流
參考答案
HTML中與javascript交互是通過事件驅(qū)動(dòng)來實(shí)現(xiàn)的妖滔,例如鼠標(biāo)點(diǎn)擊事件onclick、頁面的滾動(dòng)事件onscroll等等桶良,可以向文檔或者文檔中的元素添加事件偵聽器來預(yù)訂事件座舍。想要知道這些事件是在什么時(shí)候進(jìn)行調(diào)用的,就需要了解一下“事件流”的概念陨帆。
什么是事件流:事件流描述的是從頁面中接收事件的順序,DOM2級(jí)事件流包括下面幾個(gè)階段曲秉。
- 事件捕獲階段
- 處于目標(biāo)階段
- 事件冒泡階段
addEventListener:addEventListener是DOM2 級(jí)事件新增的指定事件處理程序的操作采蚀,這個(gè)方法接收3個(gè)參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個(gè)布爾值岸浑。最后這個(gè)布爾值參數(shù)如果是true搏存,表示在捕獲階段調(diào)用事件處理程序;如果是false矢洲,表示在冒泡階段調(diào)用事件處理程序。
IE只支持事件冒泡缩焦。
6读虏、Function._proto_(getPrototypeOf)是什么?
參考答案
獲取一個(gè)對(duì)象的原型袁滥,在chrome中可以通過proto的形式盖桥,或者在ES6中可以通過Object.getPrototypeOf的形式。
那么Function.proto是什么么题翻?也就是說Function由什么對(duì)象繼承而來揩徊,我們來做如下判別。
Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true
我們發(fā)現(xiàn)Function的原型也是Function嵌赠。
我們用圖可以來明確這個(gè)關(guān)系:
7塑荒、簡(jiǎn)述一下原型 / 構(gòu)造函數(shù) / 實(shí)例
參考答案
- 原型
(prototype)
: 一個(gè)簡(jiǎn)單的對(duì)象,用于實(shí)現(xiàn)對(duì)象的 屬性繼承姜挺〕菟埃可以簡(jiǎn)單的理解成對(duì)象的爹。在 Firefox 和 Chrome 中炊豪,每個(gè)JavaScript
對(duì)象中都包含一個(gè)__proto__
(非標(biāo)準(zhǔn))的屬性指向它爹(該對(duì)象的原型)凌箕,可obj.__proto__
進(jìn)行訪問。 - 構(gòu)造函數(shù): 可以通過
new
來 新建一個(gè)對(duì)象的函數(shù)词渤。 - 實(shí)例: 通過構(gòu)造函數(shù)和
new
創(chuàng)建出來的對(duì)象牵舱,便是實(shí)例。實(shí)例通過proto指向原型缺虐,通過constructor指向構(gòu)造函數(shù)芜壁。
這里來舉個(gè)栗子,以Object
為例志笼,我們常用的Object
便是一個(gè)構(gòu)造函數(shù)沿盅,因此我們可以通過它構(gòu)建實(shí)例。
// 實(shí)例
const instance = new Object()
則此時(shí)纫溃, 實(shí)例為instance, 構(gòu)造函數(shù)為Object腰涧,我們知道,構(gòu)造函數(shù)擁有一個(gè)prototype
的屬性指向原型紊浩,因此原型為:
// 原型
const prototype = Object.prototype
這里我們可以來看出三者的關(guān)系:
實(shí)例.__proto__ === 原型
原型.constructor === 構(gòu)造函數(shù)
構(gòu)造函數(shù).prototype === 原型
// 這條線其實(shí)是是基于原型進(jìn)行獲取的窖铡,可以理解成一條基于原型的映射線
// 例如:
// const o = new Object()
// o.constructor === Object --> true
// o.__proto__ = null;
// o.constructor === Object --> false
實(shí)例.constructor === 構(gòu)造函數(shù)
8疗锐、簡(jiǎn)述一下JS繼承,并舉例
參考答案
在 JS 中费彼,繼承通常指的便是 原型鏈繼承滑臊,也就是通過指定原型,并可以通過原型鏈繼承原型上的屬性或者方法箍铲。
- 最優(yōu)化: 圣杯模式
var inherit = (function(c,p){
var F = function(){};
return function(c,p){
F.prototype = p.prototype;
c.prototype = new F();
c.uber = p.prototype;
c.prototype.constructor = c;
}
})();
- 使用 ES6 的語法糖
class / extends
9雇卷、函數(shù)柯里化
參考答案
在函數(shù)式編程中,函數(shù)是一等公民颠猴。那么函數(shù)柯里化是怎樣的呢关划?
函數(shù)柯里化指的是將能夠接收多個(gè)參數(shù)的函數(shù)轉(zhuǎn)化為接收單一參數(shù)的函數(shù),并且返回接收余下參數(shù)且返回結(jié)果的新函數(shù)的技術(shù)翘瓮。
函數(shù)柯里化的主要作用和特點(diǎn)就是參數(shù)復(fù)用贮折、提前返回和延遲執(zhí)行。
在一個(gè)函數(shù)中资盅,首先填充幾個(gè)參數(shù)调榄,然后再返回一個(gè)新的函數(shù)的技術(shù),稱為函數(shù)的柯里化呵扛。通趁壳欤可用于在不侵入函數(shù)的前提下,為函數(shù) 預(yù)置通用參數(shù)择份,供多次重復(fù)調(diào)用扣孟。
const add = function add(x) {
return function (y) {
return x + y
}
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21
10、說說bind荣赶、call凤价、apply 區(qū)別?
參考答案
call
和 apply
都是為了解決改變 this
的指向拔创。作用都是相同的利诺,只是傳參的方式不同。
除了第一個(gè)參數(shù)外剩燥,call
可以接收一個(gè)參數(shù)列表慢逾,apply
只接受一個(gè)參數(shù)數(shù)組。
let a = {
value: 1
}
function getValue(name, age) {
console.log(name)
console.log(age)
console.log(this.value)
}
getValue.call(a, 'yck', '24')
getValue.apply(a, ['yck', '24'])
bind
和其他兩個(gè)方法作用也是一致的灭红,只是該方法會(huì)返回一個(gè)函數(shù)侣滩。并且我們可以通過 bind
實(shí)現(xiàn)柯里化。
(下面是對(duì)這三個(gè)方法的擴(kuò)展介紹)
如何實(shí)現(xiàn)一個(gè) bind 函數(shù)
對(duì)于實(shí)現(xiàn)以下幾個(gè)函數(shù)变擒,可以從幾個(gè)方面思考
- 不傳入第一個(gè)參數(shù)君珠,那么默認(rèn)為
window
- 改變了 this 指向,讓新的對(duì)象可以執(zhí)行該函數(shù)娇斑。那么思路是否可以變成給新的對(duì)象添加一個(gè)函數(shù)策添,然后在執(zhí)行完以后刪除材部?
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一個(gè)函數(shù)
return function F() {
// 因?yàn)榉祷亓艘粋€(gè)函數(shù),我們可以 new F()唯竹,所以需要判斷
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
如何實(shí)現(xiàn)一個(gè)call函數(shù)
Function.prototype.myCall = function (context) {
var context = context || window
// 給 context 添加一個(gè)屬性
// getValue.call(a, 'yck', '24') => a.fn = getValue
context.fn = this
// 將 context 后面的參數(shù)取出來
var args = [...arguments].slice(1)
// getValue.call(a, 'yck', '24') => a.fn('yck', '24')
var result = context.fn(...args)
// 刪除 fn
delete context.fn
return result
}
如何實(shí)現(xiàn)一個(gè)apply函數(shù)
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判斷是否存儲(chǔ)第二個(gè)參數(shù)
// 如果存在乐导,就將第二個(gè)參數(shù)展開
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
11、箭頭函數(shù)的特點(diǎn)
參考答案
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
箭頭函數(shù)其實(shí)是沒有 this
的浸颓,這個(gè)函數(shù)中的 this
只取決于他外面的第一個(gè)不是箭頭函數(shù)的函數(shù)的 this
物臂。在這個(gè)例子中,因?yàn)檎{(diào)用 a
符合前面代碼中的第一個(gè)情況产上,所以 this
是 window
鹦聪。并且 this
一旦綁定了上下文,就不會(huì)被任何代碼改變蒂秘。
程序閱讀題
1、下面程序輸出的結(jié)果是什么淘太?
function sayHi() {
console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
- A:
Lydia
和undefined
- B:
Lydia
和ReferenceError
- C:
ReferenceError
和21
- D:
undefined
和ReferenceError
參考答案
在函數(shù)中姻僧,我們首先使用var
關(guān)鍵字聲明了name
變量。這意味著變量在創(chuàng)建階段會(huì)被提升(JavaScript
會(huì)在創(chuàng)建變量創(chuàng)建階段為其分配內(nèi)存空間)蒲牧,默認(rèn)值為undefined
撇贺,直到我們實(shí)際執(zhí)行到使用該變量的行。我們還沒有為name
變量賦值冰抢,所以它仍然保持undefined
的值松嘶。
使用let
關(guān)鍵字(和const
)聲明的變量也會(huì)存在變量提升,但與var
不同挎扰,初始化沒有被提升翠订。在我們聲明(初始化)它們之前,它們是不可訪問的遵倦。這被稱為“暫時(shí)死區(qū)”尽超。當(dāng)我們?cè)诼暶髯兞恐皣L試訪問變量時(shí),JavaScript
會(huì)拋出一個(gè)ReferenceError
梧躺。
關(guān)于let
的是否存在變量提升似谁,我們何以用下面的例子來驗(yàn)證:
let name = 'ConardLi'
{
console.log(name) // Uncaught ReferenceError: name is not defined
let name = 'code秘密花園'
}
let
變量如果不存在變量提升,console.log(name)
就會(huì)輸出ConardLi
掠哥,結(jié)果卻拋出了ReferenceError
巩踏,那么這很好的說明了,let
也存在變量提升续搀,但是它存在一個(gè)“暫時(shí)死區(qū)”塞琼,在變量未初始化或賦值前不允許訪問。
變量的賦值可以分為三個(gè)階段:
- 創(chuàng)建變量目代,在內(nèi)存中開辟空間
- 初始化變量屈梁,將變量初始化為
undefined
- 真正賦值
關(guān)于let
嗤练、var
和function
:
-
let
的「創(chuàng)建」過程被提升了,但是初始化沒有提升在讶。 -
var
的「創(chuàng)建」和「初始化」都被提升了煞抬。 -
function
的「創(chuàng)建」「初始化」和「賦值」都被提升了。
2构哺、下面代碼輸出什么
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
依次輸出:undefined -> 10 -> 20
在立即執(zhí)行函數(shù)中革答,var a = 20; 語句定義了一個(gè)局部變量 a,由于js的變量聲明提升機(jī)制曙强,局部變量a的聲明會(huì)被提升至立即執(zhí)行函數(shù)的函數(shù)體最上方残拐,且由于這樣的提升并不包括賦值,因此第一條打印語句會(huì)打印undefined碟嘴,最后一條語句會(huì)打印20溪食。
由于變量聲明提升,a = 5; 這條語句執(zhí)行時(shí)娜扇,局部的變量a已經(jīng)聲明错沃,因此它產(chǎn)生的效果是對(duì)局部的變量a賦值,此時(shí)window.a 依舊是最開始賦值的10雀瓢,
3枢析、下面的輸出結(jié)果是什么?
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
}
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}
const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
- A:
orange
- B:
purple
- C:
green
- D:
TypeError
答案: D
colorChange
方法是靜態(tài)的刃麸。靜態(tài)方法僅在創(chuàng)建它們的構(gòu)造函數(shù)中存在醒叁,并且不能傳遞給任何子級(jí)。由于freddie
是一個(gè)子級(jí)對(duì)象泊业,函數(shù)不會(huì)傳遞把沼,所以在freddie
實(shí)例上不存在freddie
方法:拋出TypeError
。
4脱吱、下面代碼中什么時(shí)候會(huì)輸出1迂求?
var a = ?;
if(a == 1 && a == 2 && a == 3){
conso.log(1);
}
參考答案
因?yàn)?=會(huì)進(jìn)行隱式類型轉(zhuǎn)換 所以我們重寫toString方法就可以了
var a = {
i: 1,
toString() {
return a.i++;
}
}
if( a == 1 && a == 2 && a == 3 ) {
console.log(1);
}
5窜醉、下面的輸出結(jié)果是什么?
var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
參考答案
1.使用第一次push,obj對(duì)象的push方法設(shè)置 obj[2]=1;obj.length+=1
2.使用第二次push舷丹,obj對(duì)象的push方法設(shè)置 obj[3]=2;obj.length+=1
3.使用console.log輸出的時(shí)候痹升,因?yàn)閛bj具有 length 屬性和 splice 方法沈自,故將其作為數(shù)組進(jìn)行打印
4.打印時(shí)因?yàn)閿?shù)組未設(shè)置下標(biāo)為 0 1 處的值毡代,故打印為empty,主動(dòng) obj[0] 獲取為 undefined
6间校、下面代碼輸出的結(jié)果是什么矾克?
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x)
console.log(b.x)`
參考答案
undefined
{n:2}
首先,a和b同時(shí)引用了{(lán)n:2}對(duì)象憔足,接著執(zhí)行到a.x = a = {n:2}語句胁附,盡管賦值是從右到左的沒錯(cuò)酒繁,但是.的優(yōu)先級(jí)比=要高,所以這里首先執(zhí)行a.x控妻,相當(dāng)于為a(或者b)所指向的{n:1}對(duì)象新增了一個(gè)屬性x州袒,即此時(shí)對(duì)象將變?yōu)閧n:1;x:undefined}。之后按正常情況弓候,從右到左進(jìn)行賦值郎哭,此時(shí)執(zhí)行a ={n:2}的時(shí)候,a的引用改變菇存,指向了新對(duì)象{n:2},而b依然指向的是舊對(duì)象夸研。之后執(zhí)行a.x = {n:2}的時(shí)候,并不會(huì)重新解析一遍a依鸥,而是沿用最初解析a.x時(shí)候的a亥至,也即舊對(duì)象,故此時(shí)舊對(duì)象的x的值為{n:2}贱迟,舊對(duì)象為 {n:1;x:{n:2}}抬闯,它被b引用著。
后面輸出a.x的時(shí)候关筒,又要解析a了,此時(shí)的a是指向新對(duì)象的a杯缺,而這個(gè)新對(duì)象是沒有x屬性的蒸播,故訪問時(shí)輸出undefined;而訪問b.x的時(shí)候袍榆,將輸出舊對(duì)象的x的值,即{n:2}塘揣。
7包雀、下面代碼的輸出是什么?
function checkAge(data) {
if (data === { age: 18 }) {
console.log("You are an adult!");
} else if (data == { age: 18 }) {
console.log("You are still an adult.");
} else {
console.log(`Hmm.. You don't have an age I guess`);
}
}
checkAge({ age: 18 });
參考答案
Hmm.. You don't have an age I guess
在比較相等性,原始類型通過它們的值進(jìn)行比較亲铡,而對(duì)象通過它們的引用進(jìn)行比較才写。JavaScript
檢查對(duì)象是否具有對(duì)內(nèi)存中相同位置的引用。
我們作為參數(shù)傳遞的對(duì)象和我們用于檢查相等性的對(duì)象在內(nèi)存中位于不同位置奖蔓,所以它們的引用是不同的赞草。
這就是為什么{ age: 18 } === { age: 18 }
和 { age: 18 } == { age: 18 }
返回 false
的原因。
8吆鹤、下面代碼的輸出是什么?
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
所有對(duì)象鍵(不包括Symbols
)都會(huì)被存儲(chǔ)為字符串厨疙,即使你沒有給定字符串類型的鍵。這就是為什么obj.hasOwnProperty('1')
也返回true
疑务。
上面的說法不適用于Set
沾凄。在我們的Set
中沒有“1”
:set.has('1')
返回false
梗醇。它有數(shù)字類型1
,set.has(1)
返回true
撒蟀。
9叙谨、下面代碼的輸出是什么?
// 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]);
參考答案
這題考察的是對(duì)象的鍵名的轉(zhuǎn)換。
- 對(duì)象的鍵名只能是字符串和 Symbol 類型牙肝。
- 其他類型的鍵名會(huì)被轉(zhuǎn)換成字符串類型唉俗。
- 對(duì)象轉(zhuǎn)字符串默認(rèn)會(huì)調(diào)用 toString 方法。
// example 1
var a={}, b='123', c=123;
a[b]='b';
// c 的鍵名會(huì)被轉(zhuǎn)換成字符串'123'配椭,這里會(huì)把 b 覆蓋掉虫溜。
a[c]='c';
// 輸出 c
console.log(a[b]);
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
// b 是 Symbol 類型,不需要轉(zhuǎn)換股缸。
a[b]='b';
// c 是 Symbol 類型衡楞,不需要轉(zhuǎn)換。任何一個(gè) Symbol 類型的值都是不相等的敦姻,所以不會(huì)覆蓋掉 b瘾境。
a[c]='c';
// 輸出 b
console.log(a[b]);
// example 3
var a={}, b={key:'123'}, c={key:'456'};
// b 不是字符串也不是 Symbol 類型,需要轉(zhuǎn)換成字符串镰惦。
// 對(duì)象類型會(huì)調(diào)用 toString 方法轉(zhuǎn)換成字符串 [object Object]迷守。
a[b]='b';
// c 不是字符串也不是 Symbol 類型,需要轉(zhuǎn)換成字符串旺入。
// 對(duì)象類型會(huì)調(diào)用 toString 方法轉(zhuǎn)換成字符串 [object Object]兑凿。這里會(huì)把 b 覆蓋掉。
a[c]='c';
// 輸出 c
console.log(a[b]);
10茵瘾、下面代碼的輸出是什么?
(() => {
let x, y;
try {
throw new Error();
} catch (x) {
(x = 1), (y = 2);
console.log(x);
}
console.log(x);
console.log(y);
})();
參考答案
1
undefined
2
catch
塊接收參數(shù)x
礼华。當(dāng)我們傳遞參數(shù)時(shí),這與變量的x
不同拗秘。這個(gè)變量x
是屬于catch
作用域的圣絮。
之后,我們將這個(gè)塊級(jí)作用域的變量設(shè)置為1
雕旨,并設(shè)置變量y
的值“缃常現(xiàn)在,我們打印塊級(jí)作用域的變量x
凡涩,它等于1
餐禁。
在catch
塊之外,x
仍然是undefined
突照,而y
是2
帮非。當(dāng)我們想在catch
塊之外的console.log(x)
時(shí),它返回undefined
,而y
返回2
末盔。
11筑舅、下面代碼的輸出結(jié)果是什么?
function Foo() {
Foo.a = function() {
console.log(1)
}
this.a = function() {
console.log(2)
}
}
Foo.prototype.a = function() {
console.log(3)
}
Foo.a = function() {
console.log(4)
}
Foo.a();
let obj = new Foo();
obj.a();
Foo.a();
參考答案
輸出順序是 4 2 1
function Foo() {
Foo.a = function() {
console.log(1)
}
this.a = function() {
console.log(2)
}
}
// 以上只是 Foo 的構(gòu)建方法陨舱,沒有產(chǎn)生實(shí)例翠拣,此刻也沒有執(zhí)行
Foo.prototype.a = function() {
console.log(3)
}
// 現(xiàn)在在 Foo 上掛載了原型方法 a ,方法輸出值為 3
Foo.a = function() {
console.log(4)
}
// 現(xiàn)在在 Foo 上掛載了直接方法 a 游盲,輸出值為 4
Foo.a();
// 立刻執(zhí)行了 Foo 上的 a 方法误墓,也就是剛剛定義的,所以
// # 輸出 4