常見的水平垂直方式有幾種?
//利用絕對定位,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心,然后再通過 translate 來調整元素的中心點到頁面的中心。該方法需要考慮瀏覽器兼容問題。
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
}
//利用絕對定位,設置四個方向的值都為 0,并將 margin 設置為 auto,由于寬高固定,因此對應方向實現平分,可以實現水平和垂直方向上的居中。該方法適用于盒子有寬高的情況:
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
//利用絕對定位,先將元素的左上角通過 top:50%和 left:50%定位到頁面的中心,然后再通過 margin 負值來調整元素的中心點到頁面的中心。該方法適用于盒子寬高已知的情況
.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 設置容器的垂直和水平方向上為居中對齊,然后它的子元素也可以實現垂直和水平的居中。該方法要**考慮兼容的問題**,該方法在移動端用的較多:
.parent {
display: flex;
justify-content:center;
align-items:center;
}
//另外,如果父元素設置了flex布局,只需要給子元素加上`margin:auto;`就可以實現垂直居中布局
.parent{
display:flex;
}
.child{
margin: auto;
}
----問題知識點分割線----
代碼輸出結果
const promise = new Promise((resolve, reject) => {
console.log(1);
setTimeout(() => {
console.log("timerStart");
resolve("success");
console.log("timerEnd");
}, 0);
console.log(2);
});
promise.then((res) => {
console.log(res);
});
console.log(4);
輸出結果如下:
1
2
4
timerStart
timerEnd
success
代碼執行過程如下:
- 首先遇到Promise構造函數,會先執行里面的內容,打印
1
; - 遇到定時器
steTimeout
,它是一個宏任務,放入宏任務隊列; - 繼續向下執行,打印出2;
- 由于
Promise
的狀態此時還是pending
,所以promise.then
先不執行; - 繼續執行下面的同步任務,打印出4;
- 此時微任務隊列沒有任務,繼續執行下一輪宏任務,執行
steTimeout
; - 首先執行
timerStart
,然后遇到了resolve
,將promise
的狀態改為resolved
且保存結果并將之前的promise.then
推入微任務隊列,再執行timerEnd
; - 執行完這個宏任務,就去執行微任務
promise.then
,打印出resolve
的結果。
----問題知識點分割線----
函數柯里化
柯里化(currying) 指的是將一個多參數的函數拆分成一系列函數,每個拆分后的函數都只接受一個參數。
對于已經柯里化后的函數來說,當接收的參數數量與原函數的形參數量相同時,執行原函數; 當接收的參數數量小于原函數的形參數量時,返回一個函數用于接收剩余的參數,直至接收的參數數量與形參數量一致,執行原函數。
----問題知識點分割線----
對瀏覽器的緩存機制的理解
瀏覽器緩存的全過程:
瀏覽器第一次加載資源,服務器返回 200,瀏覽器從服務器下載資源文件,并緩存資源文件與 response header,以供下次加載時對比使用;
下一次加載資源時,由于強制緩存優先級較高,先比較當前時間與上一次返回 200 時的時間差,如果沒有超過 cache-control 設置的 max-age,則沒有過期,并命中強緩存,直接從本地讀取資源。如果瀏覽器不支持HTTP1.1,則使用 expires 頭判斷是否過期;
如果資源已過期,則表明強制緩存沒有被命中,則開始協商緩存,向服務器發送帶有 If-None-Match 和 If-Modified-Since 的請求;
服務器收到請求后,優先根據 Etag 的值判斷被請求的文件有沒有做修改,Etag 值一致則沒有修改,命中協商緩存,返回 304;如果不一致則有改動,直接返回新的資源文件帶上新的 Etag 值并返回 200;
-
如果服務器收到的請求沒有 Etag 值,則將 If-Modified-Since 和被請求文件的最后修改時間做比對,一致則命中協商緩存,返回 304;不一致則返回新的 last-modified 和文件并返回 200;
很多網站的資源后面都加了版本號,這樣做的目的是:每次升級了 JS 或 CSS 文件后,為了防止瀏覽器進行緩存,強制改變版本號,客戶端瀏覽器就會重新下載新的 JS 或 CSS 文件 ,以保證用戶能夠及時獲得網站的最新更新。
----問題知識點分割線----
如何避免ajax數據請求重新獲取
一般而言,ajax請求的數據都放在redux中存取。
----問題知識點分割線----
Promise.resolve
Promise.resolve = function(value) {
// 1.如果 value 參數是一個 Promise 對象,則原封不動返回該對象
if(value instanceof Promise) return value;
// 2.如果 value 參數是一個具有 then 方法的對象,則將這個對象轉為 Promise 對象,并立即執行它的then方法
if(typeof value === "object" && 'then' in value) {
return new Promise((resolve, reject) => {
value.then(resolve, reject);
});
}
// 3.否則返回一個新的 Promise 對象,狀態為 fulfilled
return new Promise(resolve => resolve(value));
}
----問題知識點分割線----
數組去重
使用 indexOf/includes 實現
function unique(arr) {
var res = [];
for(var i = 0; i < arr.length; i++) {
if(res.indexOf(arr[i]) === -1) res.push(arr[i]);
// if(!res.includes(arr[i])) res.push(arr[i]);
}
return res;
}
使用 filter(forEach) + indexOf/includes 實現
// filter
function unique(arr) {
var res = arr.filter((value, index) => {
// 只存第一個出現的元素
return arr.indexOf(value) === index;
});
return res;
}
// forEach
function unique(arr) {
var res = [];
arr.forEach((value) => {
if(!res.includes(value)) res.push(value);
});
return res;
}
非 API 版本(原生)實現
function unique(arr) {
var res = [];
for(var i = 0; i < arr.length; i++) {
var flag = false;
for(var j = 0; j < res.length; j++) {
if(arr[i] === res[j]) {
flag = true;
break;
}
}
if(flag === false) res.push(arr[i]);
}
return res;
}
ES6 使用 Set + 擴展運算符(...)/Array.from() 實現
function unique(arr) {
// return [...new Set(arr)];
return Array.from(new Set(arr));
}
----問題知識點分割線----
documentFragment 是什么?用它跟直接操作 DOM 的區別是什么?
MDN中對documentFragment
的解釋:
DocumentFragment,文檔片段接口,一個沒有父對象的最小文檔對象。它被作為一個輕量版的 Document使用,就像標準的document一樣,存儲由節點(nodes)組成的文檔結構。與document相比,最大的區別是DocumentFragment不是真實 DOM 樹的一部分,它的變化不會觸發 DOM 樹的重新渲染,且不會導致性能等問題。
當我們把一個 DocumentFragment 節點插入文檔樹時,插入的不是 DocumentFragment 自身,而是它的所有子孫節點。在頻繁的DOM操作時,我們就可以將DOM元素插入DocumentFragment,之后一次性的將所有的子孫節點插入文檔中。和直接操作DOM相比,將DocumentFragment 節點插入DOM樹時,不會觸發頁面的重繪,這樣就大大提高了頁面的性能。
----問題知識點分割線----
寫版本號排序的方法
題目描述:有一組版本號如下['0.1.1', '2.3.3', '0.302.1', '4.2', '4.3.5', '4.3.4.5']。現在需要對其進行排序,排序的結果為 ['4.3.5','4.3.4.5','2.3.3','0.302.1','0.1.1']
實現代碼如下:
arr.sort((a, b) => {
let i = 0;
const arr1 = a.split(".");
const arr2 = b.split(".");
while (true) {
const s1 = arr1[i];
const s2 = arr2[i];
i++;
if (s1 === undefined || s2 === undefined) {
return arr2.length - arr1.length;
}
if (s1 === s2) continue;
return s2 - s1;
}
});
console.log(arr);
----問題知識點分割線----
談一談隊頭阻塞問題
什么是隊頭阻塞?
對于每一個HTTP請求而言,這些任務是會被放入一個任務隊列中串行執行的,一旦隊首任務請求太慢時,就會阻塞后面的請求處理,這就是HTTP隊頭阻塞
問題。
有什么解決辦法嗎??
并發連接
我們知道對于一個域名而言,是允許分配多個長連接的,那么可以理解成增加了任務隊列,也就是說不會導致一個任務阻塞了該任務隊列的其他任務,在
RFC規范
中規定客戶端最多并發2個連接,不過實際情況就是要比這個還要多,舉個例子,Chrome中是6個。
域名分片
- 顧名思義,我們可以在一個域名下分出多個二級域名出來,而它們最終指向的還是同一個服務器,這樣子的話就可以并發處理的任務隊列更多,也更好的解決了隊頭阻塞的問題。
- 舉個例子,比如
TianTian.com
,可以分出很多二級域名,比如Day1.TianTian.com
,Day2.TianTian.com
,Day3.TianTian.com
,這樣子就可以有效解決隊頭阻塞問題。
----問題知識點分割線----
對原型、原型鏈的理解
在JavaScript中是使用構造函數來新建一個對象的,每一個構造函數的內部都有一個 prototype 屬性,它的屬性值是一個對象,這個對象包含了可以由該構造函數的所有實例共享的屬性和方法。當使用構造函數新建一個對象后,在這個對象的內部將包含一個指針,這個指針指向構造函數的 prototype 屬性對應的值,在 ES5 中這個指針被稱為對象的原型。一般來說不應該能夠獲取到這個值的,但是現在瀏覽器中都實現了 proto 屬性來訪問這個屬性,但是最好不要使用這個屬性,因為它不是規范中規定的。ES5 中新增了一個 Object.getPrototypeOf() 方法,可以通過這個方法來獲取對象的原型。
當訪問一個對象的屬性時,如果這個對象內部不存在這個屬性,那么它就會去它的原型對象里找這個屬性,這個原型對象又會有自己的原型,于是就這樣一直找下去,也就是原型鏈的概念。原型鏈的盡頭一般來說都是 Object.prototype 所以這就是新建的對象為什么能夠使用 toString() 等方法的原因。
特點: JavaScript 對象是通過引用來傳遞的,創建的每個新對象實體中并沒有一份屬于自己的原型副本。當修改原型時,與之相關的對象也會繼承這一改變。
----問題知識點分割線----
函數柯里化
什么叫函數柯里化?其實就是將使用多個參數的函數轉換成一系列使用一個參數的函數的技術。還不懂?來舉個例子。
function add(a, b, c) {
return a + b + c
}
add(1, 2, 3)
let addCurry = curry(add)
addCurry(1)(2)(3)
現在就是要實現 curry 這個函數,使函數從一次調用傳入多個參數變成多次調用每次傳一個參數。
function curry(fn) {
let judge = (...args) => {
if (args.length == fn.length) return fn(...args)
return (...arg) => judge(...args, ...arg)
}
return judge
}
----問題知識點分割線----
如何判斷數組類型
Array.isArray
----問題知識點分割線----
數組能夠調用的函數有那些?
- push
- pop
- splice
- slice
- shift
- unshift
- sort
- find
- findIndex
- map/filter/reduce 等函數式編程方法
- 還有一些原型鏈上的方法:toString/valudOf
----問題知識點分割線----
單行、多行文本溢出隱藏
- 單行文本溢出
overflow: hidden; // 溢出隱藏
text-overflow: ellipsis; // 溢出用省略號顯示
white-space: nowrap; // 規定段落中的文本不進行換行
- 多行文本溢出
overflow: hidden; // 溢出隱藏
text-overflow: ellipsis; // 溢出用省略號顯示
display:-webkit-box; // 作為彈性伸縮盒子模型顯示。
-webkit-box-orient:vertical; // 設置伸縮盒子的子元素排列方式:從上到下垂直排列
-webkit-line-clamp:3; // 顯示的行數
注意:由于上面的三個屬性都是 CSS3 的屬性,沒有瀏覽器可以兼容,所以要在前面加一個-webkit-
來兼容一部分瀏覽器。
----問題知識點分割線----
介紹一下HTTPS和HTTP區別
HTTPS 要比 HTTPS 多了 secure 安全性這個概念,實際上, HTTPS 并不是一個新的應用層協議,它其實就是 HTTP + TLS/SSL 協議組合而成,而安全性的保證正是 SSL/TLS 所做的工作。
SSL
安全套接層(Secure Sockets Layer)
TLS
(傳輸層安全,Transport Layer Security)
現在主流的版本是 TLS/1.2, 之前的 TLS1.0、TLS1.1 都被認為是不安全的,在不久的將來會被完全淘汰。
HTTPS 就是身披了一層 SSL 的 HTTP 。
[圖片上傳失敗...(image-c15684-1662526631598)]
那么區別有哪些呢??
- HTTP 是明文傳輸協議,HTTPS 協議是由 SSL+HTTP 協議構建的可進行加密傳輸、身份認證的網絡協議,比 HTTP 協議安全。
- HTTPS比HTTP更加安全,對搜索引擎更友好,利于SEO,谷歌、百度優先索引HTTPS網頁。
- HTTPS標準端口443,HTTP標準端口80。
- HTTPS需要用到SSL證書,而HTTP不用。
我覺得記住以下兩點HTTPS主要作用就行??
- 對數據進行加密,并建立一個信息安全通道,來保證傳輸過程中的數據安全;
- 對網站服務器進行真實身份認證。
HTTPS的缺點
- 證書費用以及更新維護。
- HTTPS 降低一定用戶訪問速度(實際上優化好就不是缺點了)。
- HTTPS 消耗 CPU 資源,需要增加大量機器。
----問題知識點分割線----
請實現 DOM2JSON 一個函數,可以把一個 DOM 節點輸出 JSON 的格式
題目描述:
<div>
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
把上訴dom結構轉成下面的JSON格式
{
tag: 'DIV',
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
實現代碼如下:
function dom2Json(domtree) {
let obj = {};
obj.name = domtree.tagName;
obj.children = [];
domtree.childNodes.forEach((child) => obj.children.push(dom2Json(child)));
return obj;
}
擴展思考:如果給定的不是一個 Dom 樹結構 而是一段 html 字符串 該如何解析?
那么這個問題就類似 Vue 的模板編譯原理 我們可以利用正則 匹配 html 字符串 遇到開始標簽 結束標簽和文本 解析完畢之后生成對應的 ast 并建立相應的父子關聯 不斷的 advance 截取剩余的字符串 直到 html 全部解析完畢
----問題知識點分割線----
對執行上下文的理解
1. 執行上下文類型
(1)全局執行上下文
任何不在函數內部的都是全局執行上下文,它首先會創建一個全局的window對象,并且設置this的值等于這個全局對象,一個程序中只有一個全局執行上下文。
(2)函數執行上下文
當一個函數被調用時,就會為該函數創建一個新的執行上下文,函數的上下文可以有任意多個。
(3)eval
函數執行上下文
執行在eval函數中的代碼會有屬于他自己的執行上下文,不過eval函數不常使用,不做介紹。
2. 執行上下文棧
- JavaScript引擎使用執行上下文棧來管理執行上下文
- 當JavaScript執行代碼時,首先遇到全局代碼,會創建一個全局執行上下文并且壓入執行棧中,每當遇到一個函數調用,就會為該函數創建一個新的執行上下文并壓入棧頂,引擎會執行位于執行上下文棧頂的函數,當函數執行完成之后,執行上下文從棧中彈出,繼續執行下一個上下文。當所有的代碼都執行完畢之后,從棧中彈出全局執行上下文。
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
//執行順序
//先執行second(),在執行first()
3. 創建執行上下文
創建執行上下文有兩個階段:創建階段和執行階段
1)創建階段
(1)this綁定
- 在全局執行上下文中,this指向全局對象(window對象)
- 在函數執行上下文中,this指向取決于函數如何調用。如果它被一個引用對象調用,那么 this 會被設置成那個對象,否則 this 的值被設置為全局對象或者 undefined
(2)創建詞法環境組件
- 詞法環境是一種有標識符——變量映射的數據結構,標識符是指變量/函數名,變量是對實際對象或原始數據的引用。
- 詞法環境的內部有兩個組件:加粗樣式:環境記錄器:用來儲存變量個函數聲明的實際位置外部環境的引用:可以訪問父級作用域
(3)創建變量環境組件
- 變量環境也是一個詞法環境,其環境記錄器持有變量聲明語句在執行上下文中創建的綁定關系。
2)執行階段 此階段會完成對變量的分配,最后執行完代碼。
簡單來說執行上下文就是指:
在執行一點JS代碼之前,需要先解析代碼。解析的時候會先創建一個全局執行上下文環境,先把代碼中即將執行的變量、函數聲明都拿出來,變量先賦值為undefined,函數先聲明好可使用。這一步執行完了,才開始正式的執行程序。
在一個函數執行之前,也會創建一個函數執行上下文環境,跟全局執行上下文類似,不過函數執行上下文會多出this、arguments和函數的參數。
- 全局上下文:變量定義,函數聲明
- 函數上下文:變量定義,函數聲明,
this
,arguments
----問題知識點分割線----
對Promise的理解
Promise是異步編程的一種解決方案,它是一個對象,可以獲取異步操作的消息,他的出現大大改善了異步編程的困境,避免了地獄回調,它比傳統的解決方案回調函數和事件更合理和更強大。
所謂Promise,簡單說就是一個容器,里面保存著某個未來才會結束的事件(通常是一個異步操作)的結果。從語法上說,Promise 是一個對象,從它可以獲取異步操作的消息。Promise 提供統一的 API,各種異步操作都可以用同樣的方法進行處理。
(1)Promise的實例有三個狀態:
- Pending(進行中)
- Resolved(已完成)
- Rejected(已拒絕)
當把一件事情交給promise時,它的狀態就是Pending,任務完成了狀態就變成了Resolved、沒有完成失敗了就變成了Rejected。
(2)Promise的實例有兩個過程:
- pending -> fulfilled : Resolved(已完成)
- pending -> rejected:Rejected(已拒絕)
注意:一旦從進行狀態變成為其他狀態就永遠不能更改狀態了。
Promise的特點:
- 對象的狀態不受外界影響。promise對象代表一個異步操作,有三種狀態,
pending
(進行中)、fulfilled
(已成功)、rejected
(已失敗)。只有異步操作的結果,可以決定當前是哪一種狀態,任何其他操作都無法改變這個狀態,這也是promise這個名字的由來——“承諾”; - 一旦狀態改變就不會再變,任何時候都可以得到這個結果。promise對象的狀態改變,只有兩種可能:從
pending
變為fulfilled
,從pending
變為rejected
。這時就稱為resolved
(已定型)。如果改變已經發生了,你再對promise對象添加回調函數,也會立即得到這個結果。這與事件(event)完全不同,事件的特點是:如果你錯過了它,再去監聽是得不到結果的。
Promise的缺點:
- 無法取消Promise,一旦新建它就會立即執行,無法中途取消。
- 如果不設置回調函數,Promise內部拋出的錯誤,不會反應到外部。
- 當處于pending狀態時,無法得知目前進展到哪一個階段(剛剛開始還是即將完成)。
總結: Promise 對象是異步編程的一種解決方案,最早由社區提出。Promise 是一個構造函數,接收一個函數作為參數,返回一個 Promise 實例。一個 Promise 實例有三種狀態,分別是pending、resolved 和 rejected,分別代表了進行中、已成功和已失敗。實例的狀態只能由 pending 轉變 resolved 或者rejected 狀態,并且狀態一經改變,就凝固了,無法再被改變了。
狀態的改變是通過 resolve() 和 reject() 函數來實現的,可以在異步操作結束后調用這兩個函數改變 Promise 實例的狀態,它的原型上定義了一個 then 方法,使用這個 then 方法可以為兩個狀態的改變注冊回調函數。這個回調函數屬于微任務,會在本輪事件循環的末尾執行。
注意: 在構造 Promise
的時候,構造函數內部的代碼是立即執行的