Q1:什么是防抖和節(jié)流?有什么區(qū)別?如何實現(xiàn)?
防抖:觸發(fā)高頻事件后n秒內(nèi)函數(shù)只會執(zhí)行一次,如果n秒內(nèi)高頻事件再次被觸發(fā),則重新計算時間。實現(xiàn)原理就是利用定時器,函數(shù)第一次執(zhí)行時設(shè)定一個定時器,之后調(diào)用時發(fā)現(xiàn)已經(jīng)設(shè)定過定時器就清空之前的定時器祝钢,并重新設(shè)定一個新的定時器疤估,如果存在沒有被清空的定時器慷荔,當(dāng)定時器計時結(jié)束后觸發(fā)函數(shù)執(zhí)行磷雇。
function debounce(fn) {
let timeout = null; // 創(chuàng)建一個標(biāo)記用來存放定時器的返回值
return function () {
clearTimeout(timeout); // 每當(dāng)用戶輸入的時候把前一個 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又創(chuàng)建一個新的 setTimeout, 這樣就能保證輸入字符后的 interval 間隔內(nèi)如果還有字符輸入的話躏救,就不會執(zhí)行 fn 函數(shù)
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
節(jié)流:函數(shù)節(jié)流指的是某個函數(shù)在一定時間間隔內(nèi)(例如 3 秒)只執(zhí)行一次尿庐,在這 3 秒內(nèi) 無視后來產(chǎn)生的函數(shù)調(diào)用請求,也不會延長時間間隔皮假。3 秒間隔結(jié)束后第一次遇到新的函數(shù)調(diào)用會觸發(fā)執(zhí)行惹资,然后在這新的 3 秒內(nèi)依舊無視后來產(chǎn)生的函數(shù)調(diào)用請求猴誊,以此類推。
function throttle(fn) {
let canRun = true; // 通過閉包保存一個標(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)定時器沒有執(zhí)行的時候標(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));
節(jié)流的例子:eshop滾動的時候監(jiān)聽頁面是否滾動到所處的高度值持隧。
Q2:介紹下 Set只酥、Map、WeakSet 和 WeakMap 的區(qū)別
Set 和 Map 主要的應(yīng)用場景在于 數(shù)據(jù)重組 和 數(shù)據(jù)儲存呀狼。Set 是一種叫做集合的數(shù)據(jù)結(jié)構(gòu)裂允,Map 是一種叫做字典的數(shù)據(jù)結(jié)構(gòu).Set是一種構(gòu)造函數(shù)。
比較相等的方法是'Same-value-zero equality'哥艇。
WeakSet 對象允許你將弱引用對象儲存在一個集合中绝编。
WeakSet 只能儲存對象引用,不能存放值,而 Set 對象都可以
WeakSet 對象中儲存的對象值都是被弱引用的十饥,即垃圾回收機(jī)制不考慮 WeakSet 對該對象的應(yīng)用怎棱,如果沒有其他的變量或?qū)傩砸眠@個對象值,則這個對象將會被垃圾回收掉(不考慮該對象還存在于 WeakSet 中)绷跑,所以拳恋,WeakSet 對象里有多少個成員元素,取決于垃圾回收機(jī)制有沒有運行砸捏,運行前后成員個數(shù)可能不一致谬运,遍歷結(jié)束之后,有的成員可能取不到了(被垃圾回收了)垦藏,WeakSet 對象是無法被遍歷的(ES6 規(guī)定 WeakSet 不可遍歷)梆暖,也沒有辦法拿到它包含的所有元素。(看起來沒啥用)
Map
共同點:集合掂骏、字典 可以儲存不重復(fù)的值
不同點:集合 是以 [value, value]的形式儲存元素轰驳,字典 是以 [key, value] 的形式儲存。
const map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined
Map 的鍵實際上是跟內(nèi)存地址綁定的弟灼,只要內(nèi)存地址不一樣级解,就視為兩個鍵。
WeakMap 對象是一組鍵值對的集合田绑,其中的鍵是弱引用對象勤哗,而值可以是任意。
注意掩驱,WeakMap 弱引用的只是鍵名芒划,而不是鍵值。鍵值依然是正常引用欧穴。
Set
成員唯一民逼、無序且不重復(fù)
[value, value],鍵值與鍵名是一致的(或者說只有鍵值涮帘,沒有鍵名)
可以遍歷拼苍,方法有:add、delete焚辅、has
WeakSet
成員都是對象
成員都是弱引用映屋,可以被垃圾回收機(jī)制回收,可以用來保存DOM節(jié)點同蜻,不容易造成內(nèi)存泄漏
不能遍歷,方法有add早处、delete湾蔓、has
Map
本質(zhì)上是鍵值對的集合,類似集合
可以遍歷砌梆,方法很多可以跟各種數(shù)據(jù)格式轉(zhuǎn)換
WeakMap
只接受對象作為鍵名(null除外)默责,不接受其他類型的值作為鍵名
鍵名是弱引用贬循,鍵值可以是任意的,鍵名所指向的對象可以被垃圾回收桃序,此時鍵名是無效的
不能遍歷杖虾,方法有g(shù)et、set媒熊、has奇适、delete
Q3: Async/Await 如何通過同步的方式實現(xiàn)異步
async/await 是參照 Generator 封裝的一套異步處理方案,可以理解為 Generator 的語法糖芦鳍。而 Generator 又依賴于迭代器Iterator嚷往,而 Iterator 的思想呢又來源于單向鏈表。
鏈表是一種線性表柠衅,但是并不會按線性的順序儲存數(shù)據(jù)皮仁,而是在每一個節(jié)點里存到下一個節(jié)點的指針。
單向鏈表的思想: 鏈表中最簡單的一種菲宴,它包含兩個域贷祈,一個信息域和一個指針域。這個鏈接指向列表中的下一個節(jié)點喝峦,而最后一個節(jié)點則指向一個空值付燥。
迭代器協(xié)議:產(chǎn)生一個有限或無限序列的值,并且當(dāng)所有的值都已經(jīng)被迭代后愈犹,就會有一個默認(rèn)的返回值键科。一個對象要變成可迭代的,必須實現(xiàn) @@iterator 方法漩怎,即對象(或它原型鏈上的某個對象)必須有一個名字是 Symbol.iterator 的屬性
const returnData = arr => {
let nextIndex = 0;
return {
next: nextIndex < arr.length
? {value: arr[nextIndex++], done: false}
: {value:undefined, done: true}
}
}
const arr = ['星月','神話']
const it = returnData(arr);
it.next() --> 星月
it.next() ---> 神話
調(diào)用一個生成器函數(shù)并不會馬上執(zhí)行它里面的語句勋颖,而是返回一個這個生成器的迭代器對象,當(dāng)這個迭代器的 next() 方法被首次(后續(xù))調(diào)用時勋锤,其內(nèi)的語句會執(zhí)行到第一個(后續(xù))出現(xiàn) yield 的位置為止(讓執(zhí)行處于暫停狀)饭玲,yield 后緊跟迭代器要返回的值∪矗或者如果用的是 yield*(多了個星號)茄厘,則表示將執(zhí)行權(quán)移交給另一個生成器函數(shù)(當(dāng)前生成器暫停執(zhí)行),調(diào)用 next() (再啟動)方法時谈宛,如果傳入了參數(shù)次哈,那么這個參數(shù)會作為上一條執(zhí)行的 yield 語句的返回值
const promisify = require('util').promisify;
const path = require('path');
const fs = require('fs');
const readFile = promisify(fs.readFile);
function run(gen) {
const g = gen();
function next(data) {
const res = g.next(data);
// 深度遞歸,只要 `Generator` 函數(shù)還沒執(zhí)行到最后一步吆录,`next` 函數(shù)就調(diào)用自身
if (res.done) return res.value;
res.value.then(function(data) {
next(data);
});
}
next();
}
run(function*() {
const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
// {
// "a": 1
// }
const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
// {
// "b": 2
// }
});
// Generator
run(function*() {
const res1 = yield readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
const res2 = yield readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
});
// async/await
const readFile = async ()=>{
const res1 = await readFile(path.resolve(__dirname, '../data/a.json'), { encoding: 'utf8' });
console.log(res1);
const res2 = await readFile(path.resolve(__dirname, '../data/b.json'), { encoding: 'utf8' });
console.log(res2);
return 'done'窑滞;
}
const res = readFile();
當(dāng) await 后面跟的是 Promise 對象時,才會異步執(zhí)行,其它類型的數(shù)據(jù)會同步執(zhí)行哀卫。并且返回的仍然是一個promise對象
Q4: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)
執(zhí)行結(jié)果是:1此改,2趾撵,4,3 promise順序放在微隊列里面共啃,settimeOut任務(wù)是宏任務(wù)
Q5: 簡單講解一下http2的多路復(fù)用
在 HTTP/1 中占调,每次請求都會建立一次HTTP連接,也就是我們常說的3次握手4次揮手勋磕,這個過程在一次請求過程中占用了相當(dāng)長的時間妈候,即使開啟了 Keep-Alive ,解決了多次連接的問題挂滓,但是依然有兩個效率上的問題:
第一個:串行的文件傳輸苦银。當(dāng)請求a文件時,b文件只能等待赶站,等待a連接到服務(wù)器幔虏、服務(wù)器處理文件、服務(wù)器返回文件贝椿,這三個步驟想括。我們假設(shè)這三步用時都是1秒,那么a文件用時為3秒烙博,b文件傳輸完成用時為6秒瑟蜈,依此類推。(注:此項計算有一個前提條件渣窜,就是瀏覽器和服務(wù)器是單通道傳輸)
第二個:連接數(shù)過多铺根。我們假設(shè)Apache設(shè)置了最大并發(fā)數(shù)為300,因為瀏覽器限制乔宿,瀏覽器發(fā)起的最大請求數(shù)為6位迂,也就是服務(wù)器能承載的最高并發(fā)為50,當(dāng)?shù)?1個人訪問時详瑞,就需要等待前面某個請求處理完成掂林。
HTTP/2的多路復(fù)用就是為了解決上述的兩個性能問題。 在 HTTP/2 中坝橡,有兩個非常重要的概念泻帮,分別是幀(frame)和流(stream)。 幀代表著最小的數(shù)據(jù)單位驳庭,每個幀會標(biāo)識出該幀屬于哪個流刑顺,流也就是多個幀組成的數(shù)據(jù)流氯窍。 多路復(fù)用饲常,就是在一個 TCP 連接中可以存在多條流蹲堂。換句話說,也就是可以發(fā)送多個請求贝淤,對端可以通過幀中的標(biāo)識知道屬于哪個請求柒竞。通過這個技術(shù),可以避免 HTTP 舊版本中的隊頭阻塞問題播聪,極大的提高傳輸性能朽基。