TCP粘包是怎么回事旬盯,如何處理?
默認(rèn)情況下, TCP 連接會啟?延遲傳送算法 (Nagle 算法), 在數(shù)據(jù)發(fā)送之前緩存他們. 如果短時間有多個數(shù)據(jù)發(fā)送, 會緩沖到?起作?次發(fā)送 (緩沖??? socket.bufferSize ), 這樣可以減少 IO 消耗提?性能.
如果是傳輸?件的話, 那么根本不?處理粘包的問題, 來?個包拼?個包就好了通孽。但是如果是多條消息, 或者是別的?途的數(shù)據(jù)那么就需要處理粘包.
下面看?個例?, 連續(xù)調(diào)?兩次 send 分別發(fā)送兩段數(shù)據(jù) data1 和 data2, 在接收端有以下?種常?的情況:
A. 先接收到 data1, 然后接收到 data2 .
B. 先接收到 data1 的部分?jǐn)?shù)據(jù), 然后接收到 data1 余下的部分以及 data2 的全部.
C. 先接收到了 data1 的全部數(shù)據(jù)和 data2 的部分?jǐn)?shù)據(jù), 然后接收到了 data2 的余下的數(shù)據(jù).
D. ?次性接收到了 data1 和 data2 的全部數(shù)據(jù).
其中的 BCD 就是我們常?的粘包的情況. ?對于處理粘包的問題, 常?的解決?案有:
- 多次發(fā)送之前間隔?個等待時間:只需要等上?段時間再進?下?次 send 就好, 適?于交互頻率特別低的場景. 缺點也很明顯, 對于?較頻繁的場景??傳輸效率實在太低樟遣,不過?乎不?做什么處理.
- 關(guān)閉 Nagle 算法:關(guān)閉 Nagle 算法, 在 Node.js 中你可以通過 socket.setNoDelay() ?法來關(guān)閉 Nagle 算法, 讓每?次 send 都不緩沖直接發(fā)送。該?法?較適?于每次發(fā)送的數(shù)據(jù)都?較? (但不是?件那么?), 并且頻率不是特別?的場景屿储。如果是每次發(fā)送的數(shù)據(jù)量?較?, 并且頻率特別?的, 關(guān)閉 Nagle 純屬?廢武功。另外, 該?法不適?于?絡(luò)較差的情況, 因為 Nagle 算法是在服務(wù)端進?的包合并情況, 但是如果短時間內(nèi)客戶端的?絡(luò)情況不好, 或者應(yīng)?層由于某些原因不能及時將 TCP 的數(shù)據(jù) recv, 就會造成多個包在客戶端緩沖從?粘包的情況司训。 (如果是在穩(wěn)定的機房內(nèi)部通信那么這個概率是?較?可以選擇忽略的)
- 進?封包/拆包: 封包/拆包是?前業(yè)內(nèi)常?的解決?案了刁岸。即給每個數(shù)據(jù)包在發(fā)送之前, 于其前/后放?些有特征的數(shù)據(jù), 然后收到數(shù)據(jù)的時 候根據(jù)特征數(shù)據(jù)分割出來各個數(shù)據(jù)包。
::before 和 :after 的雙冒號和單冒號有什么區(qū)別果漾?
(1)冒號(:
)用于CSS3
偽類球切,雙冒號(::
)用于CSS3
偽元素。
(2)::before
就是以一個子元素的存在绒障,定義在元素主體內(nèi)容之前的一個偽元素吨凑。并不存在于dom
之中,只存在在頁面之中。
注意: :before
和 :after
這兩個偽元素鸵钝,是在CSS2.1
里新出現(xiàn)的糙臼。起初,偽元素的前綴使用的是單冒號語法恩商,但隨著Web
的進化变逃,在CSS3
的規(guī)范里,偽元素的語法被修改成使用雙冒號怠堪,成為::before
揽乱、::after
。
Vue的父子組件生命周期鉤子函數(shù)執(zhí)行順序研叫?
<!-- 加載渲染過程 -->
<!-- 父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created ->
子beforeMount -> 子mounted -> 父mounted -->
<!-- 子組件更新過程 -->
<!-- 父beforeUpdate -> 子beforeUpdate -> 子updaed -> 父updated -->
<!-- 父組件跟新過程 -->
<!-- 父beforeUpdate -> 父updated -->
<!-- 銷毀過程 -->
<!-- 父beforeDestroy -> 子beforeDestroy -> 子destroyed ->父destroyed -->
復(fù)制代碼
說一下你對盒模型的理解?
CSS3中的盒模型有以下兩種:標(biāo)準(zhǔn)盒模型锤窑、IE盒模型
盒模型都是由四個部分組成的,分別是margin、border嚷炉、padding和content
標(biāo)準(zhǔn)盒模型和IE盒模型的區(qū)別在于設(shè)置width和height時, 所對應(yīng)的范圍不同
1渊啰、標(biāo)準(zhǔn)盒模型的width和height屬性的范圍只包含了content
2、IE盒模型的width和height屬性的范圍包含了border申屹、padding和content
可以通過修改元素的box-sizing屬性來改變元素的盒模型绘证;
1、box-sizing:content-box表示標(biāo)準(zhǔn)盒模型(默認(rèn)值)
2哗讥、box-sizing:border-box表示IE盒模型(怪異盒模型)
復(fù)制代碼
Vue的生命周期是什么 每個鉤子里面具體做了什么事情
Vue 實例有?個完整的?命周期嚷那,也就是從開始創(chuàng)建、初始化數(shù)據(jù)杆煞、編譯模版魏宽、掛載Dom -> 渲染、更新 -> 渲染决乎、卸載 等?系列過程队询,稱這是Vue的?命周期。
1构诚、beforeCreate(創(chuàng)建前) :數(shù)據(jù)觀測和初始化事件還未開始蚌斩,此時 data 的響應(yīng)式追蹤、event/watcher 都還沒有被設(shè)置范嘱,也就是說不能訪問到data送膳、computed、watch丑蛤、methods上的方法和數(shù)據(jù)叠聋。
2、created(創(chuàng)建后) :實例創(chuàng)建完成受裹,實例上配置的 options 包括 data晒奕、computed、watch名斟、methods 等都配置完成脑慧,但是此時渲染得節(jié)點還未掛載到 DOM,所以不能訪問到 `$el` 屬性砰盐。
3闷袒、beforeMount(掛載前) :在掛載開始之前被調(diào)用,相關(guān)的render函數(shù)首次被調(diào)用岩梳。實例已完成以下的配置:編譯模板囊骤,把data里面的數(shù)據(jù)和模板生成html。此時還沒有掛載html到頁面上冀值。
4也物、mounted(掛載后) :在el被新創(chuàng)建的 vm.$el 替換,并掛載到實例上去之后調(diào)用列疗。實例已完成以下的配置:用上面編譯好的html內(nèi)容替換el屬性指向的DOM對象滑蚯。完成模板中的html渲染到html 頁面中。此過程中進行ajax交互抵栈。
5告材、beforeUpdate(更新前) :響應(yīng)式數(shù)據(jù)更新時調(diào)用,此時雖然響應(yīng)式數(shù)據(jù)更新了古劲,但是對應(yīng)的真實 DOM 還沒有被渲染斥赋。
6、updated(更新后):在由于數(shù)據(jù)更改導(dǎo)致的虛擬DOM重新渲染和打補丁之后調(diào)用产艾。此時 DOM 已經(jīng)根據(jù)響應(yīng)式數(shù)據(jù)的變化更新了疤剑。調(diào)用時,組件 DOM已經(jīng)更新闷堡,所以可以執(zhí)行依賴于DOM的操作隘膘。然而在大多數(shù)情況下,應(yīng)該避免在此期間更改狀態(tài)缚窿,因為這可能會導(dǎo)致更新無限循環(huán)棘幸。該鉤子在服務(wù)器端渲染期間不被調(diào)用。
7倦零、beforeDestroy(銷毀前) :實例銷毀之前調(diào)用误续。這一步,實例仍然完全可用扫茅,`this` 仍能獲取到實例蹋嵌。
8、destroyed(銷毀后) :實例銷毀后調(diào)用葫隙,調(diào)用后栽烂,Vue 實例指示的所有東西都會解綁定,所有的事件監(jiān)聽器會被移除,所有的子實例也會被銷毀腺办。該鉤子在服務(wù)端渲染期間不被調(diào)用焰手。
另外還有 `keep-alive` 獨有的生命周期,分別為 `activated` 和 `deactivated` 怀喉。用 `keep-alive` 包裹的組件在切換時不會進行銷毀书妻,而是緩存到內(nèi)存中并執(zhí)行 `deactivated` 鉤子函數(shù),命中緩存渲染后會執(zhí)行 `activated` 鉤子函數(shù)躬拢。
復(fù)制代碼
Loader和Plugin 有什么區(qū)別
Loader:直譯為"加載器"躲履。Webpack將一切文件視為模塊,但是webpack原生是只能解析js文件聊闯,如果想將其他文件也打包的話工猜,就會用到loader
。 所以Loader的作用是讓webpack擁有了加載和解析非JavaScript文件的能力菱蔬。 Plugin:直譯為"插件"篷帅。Plugin可以擴展webpack的功能,讓webpack具有更多的靈活性汗销。 在 Webpack 運行的生命周期中會廣播出許多事件犹褒,Plugin 可以監(jiān)聽這些事件,在合適的時機通過 Webpack 提供的 API 改變輸出結(jié)果弛针。
iframe 有那些優(yōu)點和缺點叠骑?
iframe 元素會創(chuàng)建包含另外一個文檔的內(nèi)聯(lián)框架(即行內(nèi)框架)。
優(yōu)點:
- 用來加載速度較慢的內(nèi)容(如廣告)
- 可以使腳本可以并行下載
- 可以實現(xiàn)跨子域通信
缺點:
- iframe 會阻塞主頁面的 onload 事件
- 無法被一些搜索引擎索識別
- 會產(chǎn)生很多頁面削茁,不容易管理
解析 URL 參數(shù)為對象
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 將 ? 后面的字符串取出來
const paramsArr = paramsStr.split('&'); // 將字符串以 & 分割后存到數(shù)組中
let paramsObj = {};
// 將 params 存到對象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 處理有 value 的參數(shù)
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解碼
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判斷是否轉(zhuǎn)為數(shù)字
if (paramsObj.hasOwnProperty(key)) { // 如果對象有 key宙枷,則添加一個值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果對象沒有這個 key,創(chuàng)建 key 并設(shè)置值
paramsObj[key] = val;
}
} else { // 處理沒有 value 的參數(shù)
paramsObj[param] = true;
}
})
return paramsObj;
}
復(fù)制代碼
什么是原型什么是原型鏈?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
</body>
<script>
function Person () { } var person = new Person(); person.name = 'Kevin'; console.log(person.name) // Kevin
// prototype
function Person () { } Person.prototype.name = 'Kevin'; var person1 = new Person(); var person2 = new Person(); console.log(person1.name)// Kevin
console.log(person2.name)// Kevin
// __proto__
function Person () { } var person = new Person(); console.log(person.__proto__ === Person.prototype) // true
//constructor
function Person() { } console.log(Person === Person.prototype.constructor) // true
//綜上所述
function Person () { } var person = new Person() console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
//順便學(xué)習(xí)一下ES5得方法,可以獲得對象得原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
//實例與原型
function Person () { } Person.prototype.name = 'Kevin'; var person = new Person(); person.name = 'Daisy'; console.log(person.name) // Daisy
delete person.name; console.log(person.name) // Kevin
//原型得原型
var obj = new Object(); obj.name = 'Kevin', console.log(obj.name) //Kevin
//原型鏈
console.log(Object.prototype.__proto__ === null) //true
// null 表示"沒用對象" 即該處不應(yīng)該有值
// 補充
function Person() { } var person = new Person() console.log(person.constructor === Person) // true
//當(dāng)獲取person.constructor時茧跋,其實person中并沒有constructor屬性,當(dāng)不能讀取到constructor屬性時,會從person的原型
//也就是Person.prototype中讀取時,正好原型中有該屬性,所以
person.constructor === Person.prototype.constructor
//__proto__
//其次是__proto__慰丛,絕大部分瀏覽器都支持這個非標(biāo)準(zhǔn)的方法訪問原型,然而它并不存在于Person.prototype中瘾杭,實際上诅病,它
// 是來自與Object.prototype,與其說是一個屬性,不如說是一個getter/setter,當(dāng)使用obj.__proto__時粥烁,可以理解成返回了
// Object.getPrototypeOf(obj)
總結(jié): 1贤笆、當(dāng)一個對象查找屬性和方法時會從自身查找,如果查找不到則會通過__proto__指向被實例化的構(gòu)造函數(shù)的prototype 2、隱式原型也是一個對象,是指向我們構(gòu)造函數(shù)的原型 3讨阻、除了最頂層的Object對象沒有__proto_芥永,其他所有的對象都有__proto__,這是隱式原型 4、隱式原型__proto__的作用是讓對象通過它來一直往上查找屬性或方法钝吮,直到找到最頂層的Object的__proto__屬性埋涧,它的值是null,這個查找的過程就是原型鏈
</script>
</html>
復(fù)制代碼
NaN 是什么板辽,用 typeof 會輸出什么劲弦?
Not a Number,表示非數(shù)字,typeof NaN === 'number'
代碼輸出問題
function fun(n, o) {
console.log(o)
return {
fun: function(m){
return fun(m, n);
}
};
}
var a = fun(0); a.fun(1); a.fun(2); a.fun(3);
var b = fun(0).fun(1).fun(2).fun(3);
var c = fun(0).fun(1); c.fun(2); c.fun(3);
復(fù)制代碼
輸出結(jié)果:
undefined 0 0 0
undefined 0 1 2
undefined 0 1 1
復(fù)制代碼
這是一道關(guān)于閉包的題目,對于fun方法摊腋,調(diào)用之后返回的是一個對象。我們知道兴蒸,當(dāng)調(diào)用函數(shù)的時候傳入的實參比函數(shù)聲明時指定的形參個數(shù)要少视粮,剩下的形參都將設(shè)置為undefined值蕾殴。所以 console.log(o);
會輸出undefined钓觉。而a就是是fun(0)返回的那個對象批幌。也就是說逼裆,函數(shù)fun中參數(shù) n 的值是0,而返回的那個對象中赦政,需要一個參數(shù)n,而這個對象的作用域中沒有n绽诚,它就繼續(xù)沿著作用域向上一級的作用域中尋找n典徊,最后在函數(shù)fun中找到了n,n的值是0恩够。了解了這一點卒落,其他運算就很簡單了,以此類推蜂桶。
手寫發(fā)布訂閱
class EventListener {
listeners = {};
on(name, fn) {
(this.listeners[name] || (this.listeners[name] = [])).push(fn)
}
once(name, fn) {
let tem = (...args) => {
this.removeListener(name, fn)
fn(...args)
}
fn.fn = tem
this.on(name, tem)
}
removeListener(name, fn) {
if (this.listeners[name]) {
this.listeners[name] = this.listeners[name].filter(listener => (listener != fn && listener != fn.fn))
}
}
removeAllListeners(name) {
if (name && this.listeners[name]) delete this.listeners[name]
this.listeners = {}
}
emit(name, ...args) {
if (this.listeners[name]) {
this.listeners[name].forEach(fn => fn.call(this, ...args))
}
}
}
同步和異步的區(qū)別
- 同步指的是當(dāng)一個進程在執(zhí)行某個請求時儡毕,如果這個請求需要等待一段時間才能返回,那么這個進程會一直等待下去屎飘,直到消息返回為止再繼續(xù)向下執(zhí)行妥曲。
- 異步指的是當(dāng)一個進程在執(zhí)行某個請求時,如果這個請求需要等待一段時間才能返回钦购,這個時候進程會繼續(xù)往下執(zhí)行檐盟,不會阻塞等待消息的返回,當(dāng)消息返回時系統(tǒng)再通知進程進行處理押桃。
介紹下 promise 的特性葵萎、優(yōu)缺點,內(nèi)部是如何實現(xiàn)的唱凯,動手實現(xiàn) Promise
1)Promise基本特性
- 1羡忘、Promise有三種狀態(tài):pending(進行中)、fulfilled(已成功)磕昼、rejected(已失敗)
- 2卷雕、Promise對象接受一個回調(diào)函數(shù)作為參數(shù), 該回調(diào)函數(shù)接受兩個參數(shù),分別是成功時的回調(diào)resolve和失敗時的回調(diào)reject票从;另外resolve的參數(shù)除了正常值以外漫雕, 還可能是一個Promise對象的實例滨嘱;reject的參數(shù)通常是一個Error對象的實例。
- 3浸间、then方法返回一個新的Promise實例太雨,并接收兩個參數(shù)onResolved(fulfilled狀態(tài)的回調(diào));onRejected(rejected狀態(tài)的回調(diào)魁蒜,該參數(shù)可選)
- 4囊扳、catch方法返回一個新的Promise實例
- 5、finally方法不管Promise狀態(tài)如何都會執(zhí)行兜看,該方法的回調(diào)函數(shù)不接受任何參數(shù)
- 6锥咸、Promise.all()方法將多個多個Promise實例,包裝成一個新的Promise實例铣减,該方法接受一個由Promise對象組成的數(shù)組作為參數(shù)(Promise.all()方法的參數(shù)可以不是數(shù)組她君,但必須具有Iterator接口,且返回的每個成員都是Promise實例)葫哗,注意參數(shù)中只要有一個實例觸發(fā)catch方法,都會觸發(fā)Promise.all()方法返回的新的實例的catch方法球涛,如果參數(shù)中的某個實例本身調(diào)用了catch方法劣针,將不會觸發(fā)Promise.all()方法返回的新實例的catch方法
- 7、Promise.race()方法的參數(shù)與Promise.all方法一樣亿扁,參數(shù)中的實例只要有一個率先改變狀態(tài)就會將該實例的狀態(tài)傳給Promise.race()方法捺典,并將返回值作為Promise.race()方法產(chǎn)生的Promise實例的返回值
- 8、Promise.resolve()將現(xiàn)有對象轉(zhuǎn)為Promise對象从祝,如果該方法的參數(shù)為一個Promise對象襟己,Promise.resolve()將不做任何處理;如果參數(shù)thenable對象(即具有then方法)牍陌,Promise.resolve()將該對象轉(zhuǎn)為Promise對象并立即執(zhí)行then方法擎浴;如果參數(shù)是一個原始值,或者是一個不具有then方法的對象毒涧,則Promise.resolve方法返回一個新的Promise對象贮预,狀態(tài)為fulfilled,其參數(shù)將會作為then方法中onResolved回調(diào)函數(shù)的參數(shù)契讲,如果Promise.resolve方法不帶參數(shù)仿吞,會直接返回一個fulfilled狀態(tài)的 Promise 對象。需要注意的是捡偏,立即resolve()的 Promise 對象唤冈,是在本輪“事件循環(huán)”(event loop)的結(jié)束時執(zhí)行,而不是在下一輪“事件循環(huán)”的開始時银伟。
- 9你虹、Promise.reject()同樣返回一個新的Promise對象绘搞,狀態(tài)為rejected,無論傳入任何參數(shù)都將作為reject()的參數(shù)
2)Promise優(yōu)點
- ①統(tǒng)一異步 API
- Promise 的一個重要優(yōu)點是它將逐漸被用作瀏覽器的異步 API 售葡,統(tǒng)一現(xiàn)在各種各樣的 API 看杭,以及不兼容的模式和手法。
- ②Promise 與事件對比
- 和事件相比較挟伙, Promise 更適合處理一次性的結(jié)果楼雹。在結(jié)果計算出來之前或之后注冊回調(diào)函數(shù)都是可以的,都可以拿到正確的值尖阔。 Promise 的這個優(yōu)點很自然贮缅。但是,不能使用 Promise 處理多次觸發(fā)的事件介却。鏈?zhǔn)教幚硎?Promise 的又一優(yōu)點谴供,但是事件卻不能這樣鏈?zhǔn)教幚怼?/li>
- ③Promise 與回調(diào)對比
- 解決了回調(diào)地獄的問題,將異步操作以同步操作的流程表達出來齿坷。
- ④Promise 帶來的額外好處是包含了更好的錯誤處理方式(包含了異常處理)桂肌,并且寫起來很輕松(因為可以重用一些同步的工具,比如 Array.prototype.map() )永淌。
3)Promise缺點
- 1崎场、無法取消Promise,一旦新建它就會立即執(zhí)行遂蛀,無法中途取消谭跨。
- 2、如果不設(shè)置回調(diào)函數(shù)李滴,Promise內(nèi)部拋出的錯誤螃宙,不會反應(yīng)到外部。
- 3所坯、當(dāng)處于Pending狀態(tài)時谆扎,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
- 4包竹、Promise 真正執(zhí)行回調(diào)的時候燕酷,定義 Promise 那部分實際上已經(jīng)走完了,所以 Promise 的報錯堆棧上下文不太友好周瞎。
4)簡單代碼實現(xiàn)
最簡單的Promise實現(xiàn)有7個主要屬性, state(狀態(tài)), value(成功返回值), reason(錯誤信息), resolve方法, reject方法, then方法
class Promise{
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
}
};
try {
// 立即執(zhí)行函數(shù)
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value);
};
if (this.state === 'rejected') {
let x = onRejected(this.reason);
};
}
}
5)面試夠用版
function myPromise(constructor){ let self=this;
self.status="pending" //定義狀態(tài)改變前的初始狀態(tài)
self.value=undefined;//定義狀態(tài)為resolved的時候的狀態(tài)
self.reason=undefined;//定義狀態(tài)為rejected的時候的狀態(tài)
function resolve(value){
//兩個==="pending"苗缩,保證了了狀態(tài)的改變是不不可逆的
if(self.status==="pending"){
self.value=value;
self.status="resolved";
}
}
function reject(reason){
//兩個==="pending",保證了了狀態(tài)的改變是不不可逆的
if(self.status==="pending"){
self.reason=reason;
self.status="rejected";
}
}
//捕獲構(gòu)造異常
try{
constructor(resolve,reject);
}catch(e){
reject(e);
}
}
myPromise.prototype.then=function(onFullfilled,onRejected){
let self=this;
switch(self.status){
case "resolved": onFullfilled(self.value); break;
case "rejected": onRejected(self.reason); break;
default:
}
}
// 測試
var p=new myPromise(function(resolve,reject){resolve(1)});
p.then(function(x){console.log(x)})
//輸出1
6)大廠專供版
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
const resolvePromise = (promise, x, resolve, reject) => {
if (x === promise) {
// If promise and x refer to the same object, reject promise with a TypeError as the reason.
reject(new TypeError('循環(huán)引用'))
}
// if x is an object or function,
if (x !== null && typeof x === 'object' || typeof x === 'function') {
// If both resolvePromise and rejectPromise are called, or multiple calls to the same argument are made, the first call takes precedence, and any further calls are ignored.
let called
try { // If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
let then = x.then // Let then be x.then
// If then is a function, call it with x as this
if (typeof then === 'function') {
// If/when resolvePromise is called with a value y, run [[Resolve]](promise, y)
// If/when rejectPromise is called with a reason r, reject promise with r.
then.call(x, y => {
if (called) return
called = true
resolvePromise(promise, y, resolve, reject)
}, r => {
if (called) return
called = true
reject(r)
})
} else {
// If then is not a function, fulfill promise with x.
resolve(x)
}
} catch (e) {
if (called) return
called = true
reject(e)
}
} else {
// If x is not an object or function, fulfill promise with x
resolve(x)
}
}
function Promise(excutor) {
let that = this; // 緩存當(dāng)前promise實例例對象
that.status = PENDING; // 初始狀態(tài)
that.value = undefined; // fulfilled狀態(tài)時 返回的信息
that.reason = undefined; // rejected狀態(tài)時 拒絕的原因
that.onFulfilledCallbacks = []; // 存儲fulfilled狀態(tài)對應(yīng)的onFulfilled函數(shù)
that.onRejectedCallbacks = []; // 存儲rejected狀態(tài)對應(yīng)的onRejected函數(shù)
function resolve(value) { // value成功態(tài)時接收的終值
if(value instanceof Promise) {
return value.then(resolve, reject);
}
// 實踐中要確保 onFulfilled 和 onRejected ?方法異步執(zhí)?行行声诸,且應(yīng)該在 then ?方法被調(diào)?用的那?一輪事件循環(huán)之后的新執(zhí)?行行棧中執(zhí)?行行酱讶。
setTimeout(() => {
// 調(diào)?用resolve 回調(diào)對應(yīng)onFulfilled函數(shù)
if (that.status === PENDING) {
// 只能由pending狀態(tài) => fulfilled狀態(tài) (避免調(diào)?用多次resolve reject)
that.status = FULFILLED;
that.value = value;
that.onFulfilledCallbacks.forEach(cb => cb(that.value));
}
});
}
function reject(reason) { // reason失敗態(tài)時接收的拒因
setTimeout(() => {
// 調(diào)?用reject 回調(diào)對應(yīng)onRejected函數(shù)
if (that.status === PENDING) {
// 只能由pending狀態(tài) => rejected狀態(tài) (避免調(diào)?用多次resolve reject)
that.status = REJECTED;
that.reason = reason;
that.onRejectedCallbacks.forEach(cb => cb(that.reason));
}
});
}
// 捕獲在excutor執(zhí)?行行器器中拋出的異常
// new Promise((resolve, reject) => {
// throw new Error('error in excutor')
// })
try {
excutor(resolve, reject);
} catch (e) {
reject(e);
}
}
Promise.prototype.then = function(onFulfilled, onRejected) {
const that = this;
let newPromise;
// 處理理參數(shù)默認(rèn)值 保證參數(shù)后續(xù)能夠繼續(xù)執(zhí)?行行
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value;
onRejected = typeof onRejected === "function" ? onRejected : reason => {
throw reason;
};
if (that.status === FULFILLED) { // 成功態(tài)
return newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
try{
let x = onFulfilled(that.value);
resolvePromise(newPromise, x, resolve, reject); //新的promise resolve 上?一個onFulfilled的返回值
} catch(e) {
reject(e); // 捕獲前?面onFulfilled中拋出的異常then(onFulfilled, onRejected);
}
});
})
}
if (that.status === REJECTED) { // 失敗態(tài)
return newPromise = new Promise((resolve, reject) => {
setTimeout(() => {
try {
let x = onRejected(that.reason);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
if (that.status === PENDING) { // 等待態(tài)
// 當(dāng)異步調(diào)?用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中
return newPromise = new Promise((resolve, reject) => {
that.onFulfilledCallbacks.push((value) => {
try {
let x = onFulfilled(value);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
that.onRejectedCallbacks.push((reason) => {
try {
let x = onRejected(reason);
resolvePromise(newPromise, x, resolve, reject);
} catch(e) {
reject(e);
}
});
});
}
};
如何避免回流與重繪?
減少回流與重繪的措施:
- 操作DOM時彼乌,盡量在低層級的DOM節(jié)點進行操作
- 不要使用
table
布局泻肯, 一個小的改動可能會使整個table
進行重新布局 - 使用CSS的表達式
- 不要頻繁操作元素的樣式渊迁,對于靜態(tài)頁面,可以修改類名灶挟,而不是樣式琉朽。
- 使用absolute或者fixed,使元素脫離文檔流稚铣,這樣他們發(fā)生變化就不會影響其他元素
- 避免頻繁操作DOM箱叁,可以創(chuàng)建一個文檔片段
documentFragment
,在它上面應(yīng)用所有DOM操作惕医,最后再把它添加到文檔中 - 將元素先設(shè)置
display: none
耕漱,操作結(jié)束后再把它顯示出來。因為在display屬性為none的元素上進行的DOM操作不會引發(fā)回流和重繪抬伺。 - 將DOM的多個讀操作(或者寫操作)放在一起螟够,而不是讀寫操作穿插著寫。這得益于瀏覽器的渲染隊列機制峡钓。
瀏覽器針對頁面的回流與重繪妓笙,進行了自身的優(yōu)化——渲染隊列
瀏覽器會將所有的回流、重繪的操作放在一個隊列中能岩,當(dāng)隊列中的操作到了一定的數(shù)量或者到了一定的時間間隔给郊,瀏覽器就會對隊列進行批處理。這樣就會讓多次的回流捧灰、重繪變成一次回流重繪。
上面统锤,將多個讀操作(或者寫操作)放在一起毛俏,就會等所有的讀操作進入隊列之后執(zhí)行,這樣饲窿,原本應(yīng)該是觸發(fā)多次回流煌寇,變成了只觸發(fā)一次回流。
常見的水平垂直方式有幾種?
//利用絕對定位逾雄,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心阀溶,然后再通過 translate 來調(diào)整元素的中心點到頁面的中心。該方法需要考慮瀏覽器兼容問題鸦泳。
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
//利用絕對定位银锻,設(shè)置四個方向的值都為 0,并將 margin 設(shè)置為 auto做鹰,由于寬高固定击纬,因此對應(yīng)方向?qū)崿F(xiàn)平分,可以實現(xiàn)水平和垂直方向上的居中钾麸。該方法適用于盒子有寬高的情況:
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
//利用絕對定位更振,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心炕桨,然后再通過 margin 負(fù)值來調(diào)整元素的中心點到頁面的中心。該方法適用于盒子寬高已知的情況
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px; /* 自身 height 的一半 */
margin-left: -50px; /* 自身 width 的一半 */
}
//使用 flex 布局肯腕,通過 align-items:center 和 justify-content:center 設(shè)置容器的垂直和水平方向上為居中對齊献宫,然后它的子元素也可以實現(xiàn)垂直和水平的居中。該方法要**考慮兼容的問題**实撒,該方法在移動端用的較多:
.parent {
display: flex;
justify-content:center;
align-items:center;
}
//另外姊途,如果父元素設(shè)置了flex布局,只需要給子元素加上`margin:auto;`就可以實現(xiàn)垂直居中布局
.parent{
display:flex;
}
.child{
margin: auto;
}
復(fù)制代碼
說一下HTTP和HTTPS協(xié)議的區(qū)別?
1奈惑、HTTPS協(xié)議需要CA證書,費用較高;而HTTP協(xié)議不需要
2吭净、HTTP協(xié)議是超文本傳輸協(xié)議,信息是明文傳輸?shù)?HTTPS則是具有安全性的SSL加密傳輸協(xié)議;
3、使用不同的連接方式,端口也不同,HTTP協(xié)議端口是80,HTTPS協(xié)議端口是443;
4肴甸、HTTP協(xié)議連接很簡單,是無狀態(tài)的;HTTPS協(xié)議是具有SSL和HTTP協(xié)議構(gòu)建的可進行加密傳輸寂殉、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議,比HTTP更加安全
復(fù)制代碼
對節(jié)流與防抖的理解
- 函數(shù)防抖是指在事件被觸發(fā) n 秒后再執(zhí)行回調(diào),如果在這 n 秒內(nèi)事件又被觸發(fā)原在,則重新計時友扰。這可以使用在一些點擊請求的事件上,避免因為用戶的多次點擊向后端發(fā)送多次請求庶柿。
- 函數(shù)節(jié)流是指規(guī)定一個單位時間村怪,在這個單位時間內(nèi),只能有一次觸發(fā)事件的回調(diào)函數(shù)執(zhí)行浮庐,如果在同一個單位時間內(nèi)某事件被觸發(fā)多次甚负,只有一次能生效。節(jié)流可以使用在 scroll 函數(shù)的事件監(jiān)聽上审残,通過事件節(jié)流來降低事件調(diào)用的頻率梭域。
防抖函數(shù)的應(yīng)用場景:
- 按鈕提交場景:防?多次提交按鈕,只執(zhí)?最后提交的?次
- 服務(wù)端驗證場景:表單驗證需要服務(wù)端配合搅轿,只執(zhí)??段連續(xù)的輸?事件的最后?次病涨,還有搜索聯(lián)想詞功能類似?存環(huán)境請?lodash.debounce
節(jié)流函數(shù)的適?場景:
- 拖拽場景:固定時間內(nèi)只執(zhí)??次,防?超?頻次觸發(fā)位置變動
- 縮放場景:監(jiān)控瀏覽器resize
- 動畫場景:避免短時間內(nèi)多次觸發(fā)動畫引起性能問題
點擊刷新按鈕或者按 F5璧坟、按 Ctrl+F5 (強制刷新)既穆、地址欄回車有什么區(qū)別?
- 點擊刷新按鈕或者按 F5: 瀏覽器直接對本地的緩存文件過期雀鹃,但是會帶上If-Modifed-Since幻工,If-None-Match,這就意味著服務(wù)器會對文件檢查新鮮度褐澎,返回結(jié)果可能是 304会钝,也有可能是 200。
- 用戶按 Ctrl+F5(強制刷新): 瀏覽器不僅會對本地文件過期,而且不會帶上 If-Modifed-Since迁酸,If-None-Match先鱼,相當(dāng)于之前從來沒有請求過,返回結(jié)果是 200奸鬓。
- 地址欄回車: 瀏覽器發(fā)起請求焙畔,按照正常流程,本地檢查是否過期串远,然后服務(wù)器檢查新鮮度宏多,最后返回內(nèi)容澡罚。
代碼輸出結(jié)果
Promise.resolve(1)
.then(2)
.then(Promise.resolve(3))
.then(console.log)
復(fù)制代碼
輸出結(jié)果如下:
1
復(fù)制代碼
看到這個題目,好多的then留搔,實際上只需要記住一個原則:.then
或.catch
的參數(shù)期望是函數(shù),傳入非函數(shù)則會發(fā)生值透傳隔显。
第一個then和第二個then中傳入的都不是函數(shù),一個是數(shù)字括眠,一個是對象,因此發(fā)生了透傳掷豺,將resolve(1)
的值直接傳到最后一個then里捞烟,直接打印出1。