良好的注釋是非常重要的。請留出時間來描述組件(component)的工作方式、局限性和構建它們的方法。不要讓你的團隊其它成員 來猜測一段不通用或不明顯的代碼的目的。
提示:通過配置編輯器,可以提供快捷鍵來輸出一致認可的注釋模式。
出于性能考量,在沒有必要的情況下避免元素選擇器疊加 Class、ID 使用。
元素選擇器和 ID、Class 混合使用也違反關注分離原則。如果HTML標簽修改了,就要再去修改 CSS 代碼,不利于后期維護。
相關屬性應為一組,推薦的樣式編寫順序
由于定位(positioning)可以從正常的文檔流中移除元素,并且還能覆蓋盒模型(box model)相關的樣式,因此排在首位。盒模型決定了組件的尺寸和位置,因此排在第二位。
其他屬性只是影響組件的內部(inside)或者是不影響前兩組屬性,因此排在后面。
url() 、屬性選擇符、屬性值使用雙引號。
將媒體查詢放在盡可能相關規則的附近。不要將他們打包放在一個單一樣式文件中或者放在文檔底部。如果你把他們分開了,將來只會被大家遺忘。
與 <link> 相比,@import 要慢很多,不光增加額外的請求數,還會導致不可預料的問題。
替代辦法:
a:link -> a:visited -> a:hover -> a:active(LoVeHAte)
選擇字體
在 Web 上應用字體,是一門技術,同時也是一門藝術. 由于計算機歷史發展的原因,西文有大量優秀的字體可供選擇,可對于中文來說就是一項挑戰. 主流操作系統提供的本地中文字體極少,另一方面中文字體組成的特殊性,其體積過于龐大,無法良好地使用 webfont. 所以編寫健壯的 font-family 是一件需要深思熟慮的事情.
以下列出各種平臺下合適的中西文字體:
桌面端 Mac, Windows, Linux 上適合網頁顯示的優秀中文字體
移動端 iOS, Android 上適合網頁顯示的優秀中文字體
主流操作系統上適合網頁顯示的優秀西文字體
拋開宋/明體長時間作為系統默認字體,所產生的審美疲勞,宋/明體相比黑體是更合適作為內文字體. 大多的宋/明體針對內文設計,橫細直粗,造型方正,筆畫在小字號的情況下,不會糊在一起,給人一種素雅的感覺. 而黑體筆畫粗壯有力,引人注目,更適合作為標題使用.
但大部分人已經習慣在網頁上閱讀黑體,以及宋/明體在字重過大的情況下,顯示效果還是不太理想. 所以內文默認提供黑體,可選擇性的切換宋/明體.
Robert Bringhurst 在《The Elements of Typographic Style》談到字號大小之間的比例,形似于音樂中的音階. 作曲時以某個特定的音階為基礎,才會形成特定的風格. 字號的排版同樣如此,有規律的字號變化,才會形成特定的排版風格.
將內文以 16px 作為字號
標題 h1, h2, h3, h4, h5, h6 以 16px 作為字號基礎,按同比例的遞減
此外,Robert Bringhurst 還談到版式中的空間就像音樂中的時間(Space in typography is like time in music),言下之意,把握間距(行高)就如把握節拍. 節拍是對時間的分割,倘若搶拍便失去節奏. 文字的間距(行高)亦是對空間的分割,不一致間距(行高)比例,便會失去「垂直的旋律」.
將內文以 1.7em 作為行高
標題 h1, h2, h3, h4, h5, h6 以 1.5em 作為行高.
段落分隔對于中文排版而言也是特別重要,主要以「段首縮進」和「段落間距」兩種方式表現,它們的唯一目的就是將段落分隔.
「段首縮進」主要用于印刷書籍,節省縱向空間,保持文本連貫,但一般在網頁上的閱讀速度較快,會使文字過于密集產生壓力. 相反「段落間距」主要用于網頁,充分利用網頁無限的縱向空間,保障文本塊的整潔,同時給予長篇閱讀休息的間隙. 所以一般網頁排版,會考慮選擇「段落間距」,可以設置以下屬性實現「段落間距」.
漢字的方塊性質構成了漢字獨有的藝術美感,使其具有工整的特點,從而顯現出中文排版的重要原則:所有元素都是正方體. 但從二十世紀開始使用標點后,以及中西文混排的情況越來越多,為了保證「禁則處理」和「齊頭尾」實現,可能需要在不同條件下進行適當的斷詞處理.
「禁則」是來自日語的排版術語,主要指的就是禁止一些標點等字符出現在行首或行尾的規則,大致相當于漢語常說的「避頭尾」.
可以設置以下屬性實現「齊頭尾」,其中inter-ideographic意思是「通過調整單詞和字符之間的留白來實現兩端對齊」.
但這樣的「齊頭尾」并不是完美的,主要由于技術遺留原因,在 Windows 和 Linux 上的 webkit 瀏覽器并沒有實現 inter-ideographic 導致中西文混排的時候,容易出現過度拉伸字間距的情況。
為此有一種不優雅的解決方案,在極易出現字間距拉伸的小尺寸屏幕(手機)上使用「斷詞處理」,避免字間距拉伸,可是這樣也帶來「無視避頭尾規則」和「西文單詞斷詞」的壞毛病. 這是用一種不優雅解決另一種不優雅,按需抉擇吧.
可以設置以下屬性進行「斷詞處理」
參考答案
防抖
觸發高頻事件后n秒內函數只會執行一次,如果n秒內高頻事件再次被觸發,則重新計算時間
每次觸發事件時都取消之前的延時調用方法
function debounce(fn) {
let timeout = null; // 創建一個標記用來存放定時器的返回值
return function () {
clearTimeout(timeout); // 每當用戶輸入的時候把前一個 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又創建一個新的 setTimeout, 這樣就能保證輸入字符后的 interval 間隔內如果還有字符輸入的話,就不會執行 fn 函數
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
節流
高頻事件觸發,但在n秒內只會執行一次,所以節流會稀釋函數的執行頻率
每次觸發事件時都判斷當前是否有等待執行的延時函數
function throttle(fn) {
let canRun = true; // 通過閉包保存一個標記
return function () {
if (!canRun) return; // 在函數開頭判斷標記是否為true,不為true則return
canRun = false; // 立即設置為false
setTimeout(() => { // 將外部傳入的函數的執行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout執行完畢后再把標記設置為true(關鍵)表示可以執行下一次循環了。當定時器沒有執行的時候標記永遠是false,在開頭被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
誤區:我們經常說get請求參數的大小存在限制,而post請求的參數大小是無限制的。
參考答案
實際上HTTP 協議從未規定 GET/POST 的請求長度限制是多少。對get請求參數的限制是來源與瀏覽器或web服務器,瀏覽器或web服務器限制了url的長度。為了明確這個概念,我們必須再次強調下面幾點:
補充補充一個get和post在緩存方面的區別:
可從IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、<script type="module"> 這幾個角度考慮。
參考答案
模塊化主要是用來抽離公共代碼,隔離作用域,避免變量沖突等。
IIFE:使用自執行函數來編寫模塊化,特點:在一個單獨的函數作用域中執行代碼,避免變量沖突。
(function(){
return {
data:[]
}
})()
AMD:使用requireJS 來編寫模塊化,特點:依賴必須提前聲明好。
define('./index.js',function(code){
// code 就是index.js 返回的內容
})
CMD:使用seaJS 來編寫模塊化,特點:支持動態引入依賴文件。
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 來引入另一個 js 。
import a from 'a';
參考答案
輸入 npm install 命令并敲下回車后,會經歷如下幾個階段(以 npm 5.5.1 為例):
參考答案
ES5的繼承時通過prototype或構造函數機制來實現。ES5的繼承實質上是先創建子類的實例對象,然后再將父類的方法添加到this上(Parent.apply(this))。
ES6的繼承機制完全不同,實質上是先創建父類的實例對象this(所以必須先調用父類的super()方法),然后再用子類的構造函數修改this。
具體的:ES6通過class關鍵字定義類,里面有構造方法,類之間通過extends關鍵字實現繼承。子類必須在constructor方法中調用super方法,否則新建實例報錯。因為子類沒有自己的this對象,而是繼承了父類的this對象,然后對其進行加工。如果不調用super方法,子類得不到this對象。
ps:super關鍵字指代父類的實例,即父類的this對象。在子類構造函數中,調用super后,才可使用this關鍵字,否則報錯。
參考答案
參考答案
**因為js是單線程的,瀏覽器遇到setTimeout或者setInterval會先執行完當前的代碼塊,在此之前會把定時器推入瀏覽器的待執行事件隊列里面,等到瀏覽器執行完當前代碼之后會看一下事件隊列里面有沒有任務,有的話才執行定時器的代碼。**所以即使把定時器的時間設置為0還是會先執行當前的一些代碼。
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()
輸出結果:
test //10次
undefined
123
321
參考答案
輸出:[1, NaN, NaN]
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])這個callback一共可以接收三個參數,其中第一個參數代表當前被處理的元素,而第二個參數代表該元素的索引。
參考答案
Doctype聲明于文檔最前面,告訴瀏覽器以何種方式來渲染頁面,這里有兩種模式,嚴格模式和混雜模式。
參考答案
fetch發送post請求的時候,總是發送2次,第一次狀態碼是204,第二次才成功?
原因很簡單,因為你用fetch的post請求的時候,導致fetch 第一次發送了一個Options請求,詢問服務器是否支持修改的請求頭,如果服務器支持,則在第二次中發送真正的請求。
history對象
Navigator對象
參考答案
共同點:都是保存在瀏覽器端,并且是同源的
補充說明一下cookie的作用:
參考答案
XSS(跨站腳本攻擊)是指攻擊者在返回的HTML中嵌入javascript腳本,為了減輕這些攻擊,需要在HTTP頭部配上,set-cookie:
結果應該是這樣的:Set-Cookie=.....
參考答案
其中一個主要的區別在于瀏覽器的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以下版本的執行結果(先執行所有的宏任務,再執行微任務)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1
// 以上代碼在node11及瀏覽器的執行結果(順序執行宏任務和微任務)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1
參考答案
https協議由 http + ssl 協議構成,具體的鏈接過程可參考SSL或TLS握手的概述
中間人攻擊過程如下:
防范方法:
參考答案
(1). 減少HTTP請求數
這條策略基本上所有前端人都知道,而且也是最重要最有效的。都說要減少HTTP請求,那請求多了到底會怎么樣呢?首先,每個請求都是有成本的,既包 含時間成本也包含資源成本。一個完整的請求都需要經過DNS尋址、與服務器建立連接、發送數據、等待服務器響應、接收數據這樣一個“漫長”而復雜的過程。時間成本就是用戶需要看到或者“感受”到這個資源是必須要等待這個過程結束的,資源上由于每個請求都需要攜帶數據,因此每個請求都需要占用帶寬。
另外,由于瀏覽器進行并發請求的請求數是有上限的,因此請求數多了以后,瀏覽器需要分批進行請求,因此會增加用戶的等待時間,會給 用戶造成站點速度慢這樣一個印象,即使可能用戶能看到的第一屏的資源都已經請求完了,但是瀏覽器的進度條會一直存在。減少HTTP請求數的主要途徑包括:
(2). 從設計實現層面簡化頁面
如果你的頁面像百度首頁一樣簡單,那么接下來的規則基本上都用不著了。保持頁面簡潔、減少資源的使用時最直接的。如果不是這樣,你的頁面需要華麗的皮膚,則繼續閱讀下面的內容。
(3). 合理設置HTTP緩存
緩存的力量是強大的,恰當的緩存設置可以大大的減少HTTP請求。以有啊首頁為例,當瀏覽器沒有緩存的時候訪問一共會發出78個請求,共600多K 數據(如圖1.1),而當第二次訪問即瀏覽器已緩存之后訪問則僅有10個請求,共20多K數據(如圖1.2)。(這里需要說明的是,如果直接F5刷新頁面 的話效果是不一樣的,這種情況下請求數還是一樣,不過被緩存資源的請求服務器是304響應,只有Header沒有Body,可以節省帶寬)
怎樣才算合理設置?原則很簡單,能緩存越多越好,能緩存越久越好。例如,很少變化的圖片資源可以直接通過HTTP Header中的Expires設置一個很長的過期頭;變化不頻繁而又可能會變的資源可以使用Last-Modifed來做請求驗證。盡可能的讓資源能夠 在緩存中待得更久。
(4). 資源合并與壓縮
如果可以的話,盡可能的將外部的腳本、樣式進行合并,多個合為一個。另外,CSS、Javascript、Image都可以用相應的工具進行壓縮,壓縮后往往能省下不少空間。
(5). CSS Sprites
合并CSS圖片,減少請求數的又一個好辦法。
(6). Inline Images
使用data: URL scheme的方式將圖片嵌入到頁面或CSS中,如果不考慮資源管理上的問題的話,不失為一個好辦法。如果是嵌入頁面的話換來的是增大了頁面的體積,而且無法利用瀏覽器緩存。使用在CSS中的圖片則更為理想一些。
(7). Lazy Load Images
這條策略實際上并不一定能減少HTTP請求數,但是卻能在某些條件下或者頁面剛加載時減少HTTP請求數。對于圖片而言,在頁面剛加載的時候可以只 加載第一屏,當用戶繼續往后滾屏的時候才加載后續的圖片。這樣一來,假如用戶只對第一屏的內容感興趣時,那剩余的圖片請求就都節省了。有啊首頁曾經的做法 是在加載的時候把第一屏之后的圖片地址緩存在Textarea標簽中,待用戶往下滾屏的時候才“惰性”加載。
參考答案
重繪(Repaint)和回流(Reflow)
重繪和回流是渲染步驟中的一小節,但是這兩個步驟對于性能影響很大。
回流必定會發生重繪,重繪不一定會引發回流。回流所需的成本比重繪高的多,改變深層次的節點很可能導致父節點的一系列回流。
所以以下幾個動作可能會導致性能問題:
很多人不知道的是,重繪和回流其實和 Event loop 有關。
減少重繪和回流
參考答案
vue和react都是采用diff算法來對比新舊虛擬節點,從而更新節點。在vue的diff函數中(建議先了解一下diff算法過程)。在交叉對比中,當新節點跟舊節點頭尾交叉對比沒有結果時,會根據新節點的key去對比舊節點數組中的key,從而找到相應舊節點(這里對應的是一個key => index 的map映射)。如果沒找到就認為是一個新增節點。而如果沒有key,那么就會采用遍歷查找的方式去找到對應的舊節點。一種一個map映射,另一種是遍歷查找。相比而言。map映射的速度更快。vue部分源碼如下:
// vue項目 src/core/vdom/patch.js -488行
// 以下是為了閱讀性進行格式化后的代碼
// oldCh 是一個舊虛擬節點數組
if (isUndef(oldKeyToIdx)) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
if(isDef(newStartVnode.key)) {
// map 方式獲取
idxInOld = oldKeyToIdx[newStartVnode.key]
} else {
// 遍歷方式獲取
idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
}
創建map函數
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 是對比新舊節點是否相同的函數
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
}
}
參考答案
在React中,如果是由React引發的事件處理(比如通過onClick引發的事件處理),調用setState不會同步更新this.state,除此之外的setState調用會同步執行this.state。所謂“除此之外”,指的是繞過React通過addEventListener直接添加的事件處理函數,還有通過setTimeout/setInterval產生的異步調用。
**原因:**在React的setState函數實現中,會根據一個變量isBatchingUpdates判斷是直接更新this.state還是放到隊列中回頭再說,而isBatchingUpdates默認是false,也就表示setState會同步更新this.state,但是,有一個函數batchedUpdates,這個函數會把isBatchingUpdates修改為true,而當React在調用事件處理函數之前就會調用這個batchedUpdates,造成的后果,就是由React控制的事件處理過程setState不會同步更新this.state。
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 自身生命周期內,觸發時 isBatchingUpdates 為 true,所以并不會直接執行更新 state,而是加入了 dirtyComponents,所以打印時獲取的都是更新前的狀態 0。
2、兩次 setState 時,獲取到 this.state.val 都是 0,所以執行時都是將 0 設置成 1,在 react 內部會被合并掉,只執行一次。設置完成后 state.val 值為 1。
3、setTimeout 中的代碼,觸發時 isBatchingUpdates 為 false,所以能夠直接進行更新,所以連著輸出 2,3。
輸出: 0 0 2 3
參考答案
虛擬dom相當于在js和真實dom中間加了一個緩存,利用dom diff算法避免了沒有必要的dom操作,從而提高性能。
具體實現步驟如下:
用 JavaScript 對象結構表示 DOM 樹的結構;然后用這個樹構建一個真正的 DOM 樹,插到文檔當中
當狀態變更的時候,重新構造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹差異
把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,視圖就更新了。
參考答案
結構:display:none: 會讓元素完全從渲染樹中消失,渲染的時候不占據任何空間, 不能點擊, visibility: hidden:不會讓元素從渲染樹消失,渲染元素繼續占據空間,只是內容不可見,不能點擊 opacity: 0: 不會讓元素從渲染樹消失,渲染元素繼續占據空間,只是內容不可見,可以點擊
繼承:display: none:是非繼承屬性,子孫節點消失由于元素從渲染樹消失造成,通過修改子孫節點屬性無法顯示。visibility: hidden:是繼承屬性,子孫節點消失由于繼承了hidden,通過設置visibility: visible;可以讓子孫節點顯式。
性能:displaynone : 修改元素會造成文檔回流,讀屏器不會讀取display: none元素內容,性能消耗較大 visibility:hidden: 修改元素只會造成本元素的重繪,性能消耗較少讀屏器讀取visibility: hidden元素內容 opacity: 0 :修改元素會造成重繪,性能消耗較少
聯系:它們都能讓元素不可見
參考答案
常用的一般為三種.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 不再我們考慮之內了
.clearfix 還有另外一種寫法,
-->
.clearfix:before, .clearfix:after {
content:"";
display:table;
}
.clearfix:after{
clear:both;
overflow:hidden;
}
.clearfix{
zoom:1;
}
<!--
用display:table 是為了避免外邊距margin重疊導致的margin塌陷,
內部元素默認會成為 table-cell 單元格的形式
-->
clear:both:若是用在同一個容器內相鄰元素上,那是賊好的,有時候在容器外就有些問題了, 比如相鄰容器的包裹層元素塌陷
overflow:hidden:這種若是用在同個容器內,可以形成 BFC避免浮動造成的元素塌陷
參考答案
概念:將多個小圖片拼接到一個圖片中。通過 background-position 和元素尺寸調節需要顯示的背景圖案。
優點:
缺點:
參考答案
參考答案
block元素特點:
1.處于常規流中時,如果width沒有設置,會自動填充滿父容器 2.可以應用margin/padding 3.在沒有設置高度的情況下會擴展高度以包含常規流中的子元素 4.處于常規流中時布局時在前后元素位置之間(獨占一個水平空間) 5.忽略vertical-align
inline元素特點
1.水平方向上根據direction依次布局
2.不會在元素前后進行換行
3.受white-space控制
4.margin/padding在豎直方向上無效,水平方向上有效
5.width/height屬性對非替換行內元素無效,寬度由元素內容決定
6.非替換行內元素的行框高由line-height確定,替換行內元素的行框高由height,margin,padding,border決定 7.浮動或絕對定位時會轉換為block8.vertical-align屬性生效
參考答案
/**
* 在標準瀏覽器下使用
* 1 content內容為空格用于修復opera下文檔中出現
* contenteditable屬性時在清理浮動元素上下的空白
* 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下使用
* 通過觸發hasLayout實現包含浮動
**/
.clearfix {
*zoom: 1;
}
參考答案
GIF:
JPEG:
PNG:
參考答案
參考答案
參考答案
七種數據類型
(ES6之前)其中5種為基本類型:string,number,boolean,null,undefined,
ES6出來的Symbol也是原始數據類型 ,表示獨一無二的值
Object為引用類型(范圍挺大),也包括數組、函數,
參考答案
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
輸出結果是:
1
2
4
3
promise構造函數是同步執行的,then方法是異步執行的
Promise new的時候會立即執行里面的代碼 then是微任務 會在本次任務執行完的時候執行 setTimeout是宏任務 會在下次任務執行的時候執行
參考答案
工廠模式
簡單的工廠模式可以理解為解決多個相似的問題;
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
單例模式
只能被實例化(構造函數給實例添加屬性與方法)一次
// 單體模式
var Singleton = function(name){
this.name = name;
};
Singleton.prototype.getName = function(){
return this.name;
}
// 獲取實例對象
var getInstance = (function() {
var instance = null;
return function(name) {
if(!instance) {//相當于一個一次性閥門,只能實例化一次
instance = new Singleton(name);
}
return instance;
}
})();
// 測試單體模式的實例,所以a===b
var a = getInstance("aa");
var b = getInstance("bb");
沙箱模式
將一些函數放到自執行函數里面,但要用閉包暴露接口,用變量接收暴露的接口,再調用里面的值,否則無法使用里面的值
let sandboxModel=(function(){
function sayName(){};
function sayAge(){};
return{
sayName:sayName,
sayAge:sayAge
}
})()
發布者訂閱模式
就例如如我們關注了某一個公眾號,然后他對應的有新的消息就會給你推送,
//發布者與訂閱模式
var shoeObj = {}; // 定義發布者
shoeObj.list = []; // 緩存列表 存放訂閱者回調函數
// 增加訂閱者
shoeObj.listen = function(fn) {
shoeObj.list.push(fn); // 訂閱消息添加到緩存列表
}
// 發布消息
shoeObj.trigger = function() {
for (var i = 0, fn; fn = this.list[i++];) {
fn.apply(this, arguments);//第一個參數只是改變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);
代碼實現邏輯是用數組存貯訂閱者, 發布者回調函數里面通知的方式是遍歷訂閱者數組,并將發布者內容傳入訂閱者數組
參考答案
1.字面量
let obj={'name':'張三'}
2.Object構造函數創建
let Obj=new Object()
Obj.name='張三'
3.使用工廠模式創建對象
function createPerson(name){
var o = new Object();
o.name = name;
};
return o;
}
var person1 = createPerson('張三');
4.使用構造函數創建對象
function Person(name){
this.name = name;
}
var person1 = new Person('張三');
參考答案
HTML中與javascript交互是通過事件驅動來實現的,例如鼠標點擊事件onclick、頁面的滾動事件onscroll等等,可以向文檔或者文檔中的元素添加事件偵聽器來預訂事件。想要知道這些事件是在什么時候進行調用的,就需要了解一下“事件流”的概念。
什么是事件流:事件流描述的是從頁面中接收事件的順序,DOM2級事件流包括下面幾個階段。
addEventListener:addEventListener是DOM2 級事件新增的指定事件處理程序的操作,這個方法接收3個參數:要處理的事件名、作為事件處理程序的函數和一個布爾值。最后這個布爾值參數如果是true,表示在捕獲階段調用事件處理程序;如果是false,表示在冒泡階段調用事件處理程序。
IE只支持事件冒泡。
參考答案
獲取一個對象的原型,在chrome中可以通過__proto__的形式,或者在ES6中可以通過Object.getPrototypeOf的形式。
那么Function.proto是什么么?也就是說Function由什么對象繼承而來,我們來做如下判別。
Function.__proto__==Object.prototype //false
Function.__proto__==Function.prototype//true
我們發現Function的原型也是Function。
我們用圖可以來明確這個關系:
參考答案
這里來舉個栗子,以Object為例,我們常用的Object便是一個構造函數,因此我們可以通過它構建實例。
// 實例
const instance = new Object()
則此時, 實例為instance, 構造函數為Object,我們知道,構造函數擁有一個prototype的屬性指向原型,因此原型為:
// 原型
const prototype = Object.prototype
這里我們可以來看出三者的關系:
實例.__proto__ === 原型
原型.constructor === 構造函數
構造函數.prototype === 原型
// 這條線其實是是基于原型進行獲取的,可以理解成一條基于原型的映射線
// 例如:
// const o = new Object()
// o.constructor === Object --> true
// o.__proto__ = null;
// o.constructor === Object --> false
實例.constructor === 構造函數
參考答案
在 JS 中,繼承通常指的便是 原型鏈繼承,也就是通過指定原型,并可以通過原型鏈繼承原型上的屬性或者方法。
參考答案
在函數式編程中,函數是一等公民。那么函數柯里化是怎樣的呢?
函數柯里化指的是將能夠接收多個參數的函數轉化為接收單一參數的函數,并且返回接收余下參數且返回結果的新函數的技術。
函數柯里化的主要作用和特點就是參數復用、提前返回和延遲執行。
在一個函數中,首先填充幾個參數,然后再返回一個新的函數的技術,稱為函數的柯里化。通常可用于在不侵入函數的前提下,為函數 預置通用參數,供多次重復調用。
const add = function add(x) {
return function (y) {
return x + y
}
}
const add1 = add(1)
add1(2) === 3
add1(20) === 21
參考答案
call 和 apply 都是為了解決改變 this 的指向。作用都是相同的,只是傳參的方式不同。
除了第一個參數外,call 可以接收一個參數列表,apply 只接受一個參數數組。
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和其他兩個方法作用也是一致的,只是該方法會返回一個函數。并且我們可以通過 bind實現柯里化。
(下面是對這三個方法的擴展介紹)
如何實現一個 bind 函數
對于實現以下幾個函數,可以從幾個方面思考
Function.prototype.myBind = function (context) {
if (typeof this !== 'function') {
throw new TypeError('Error')
}
var _this = this
var args = [...arguments].slice(1)
// 返回一個函數
return function F() {
// 因為返回了一個函數,我們可以 new F(),所以需要判斷
if (this instanceof F) {
return new _this(...args, ...arguments)
}
return _this.apply(context, args.concat(...arguments))
}
}
如何實現一個call函數
Function.prototype.myCall = function (context) {
var context = context || window
// 給 context 添加一個屬性
// getValue.call(a, 'yck', '24') => a.fn = getValue
context.fn = this
// 將 context 后面的參數取出來
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
}
如何實現一個apply函數
Function.prototype.myApply = function (context) {
var context = context || window
context.fn = this
var result
// 需要判斷是否存儲第二個參數
// 如果存在,就將第二個參數展開
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
參考答案
function a() {
return () => {
return () => {
console.log(this)
}
}
}
console.log(a()()())
箭頭函數其實是沒有 this的,這個函數中的 this只取決于他外面的第一個不是箭頭函數的函數的 this。在這個例子中,因為調用 a符合前面代碼中的第一個情況,所以 this是 window。并且 this一旦綁定了上下文,就不會被任何代碼改變。
function sayHi() {
console.log(name);
console.log(age);
var name = "Lydia";
let age = 21;
}
sayHi();
參考答案
在函數中,我們首先使用var關鍵字聲明了name變量。這意味著變量在創建階段會被提升(JavaScript會在創建變量創建階段為其分配內存空間),默認值為undefined,直到我們實際執行到使用該變量的行。我們還沒有為name變量賦值,所以它仍然保持undefined的值。
使用let關鍵字(和const)聲明的變量也會存在變量提升,但與var不同,初始化沒有被提升。在我們聲明(初始化)它們之前,它們是不可訪問的。這被稱為“暫時死區”。當我們在聲明變量之前嘗試訪問變量時,JavaScript會拋出一個ReferenceError。
關于let的是否存在變量提升,我們何以用下面的例子來驗證:
let name = 'ConardLi'
{
console.log(name) // Uncaught ReferenceError: name is not defined
let name = 'code秘密花園'
}
let變量如果不存在變量提升,console.log(name)就會輸出ConardLi,結果卻拋出了ReferenceError,那么這很好的說明了,let也存在變量提升,但是它存在一個“暫時死區”,在變量未初始化或賦值前不允許訪問。
變量的賦值可以分為三個階段:
關于let、var和function:
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
依次輸出:undefined -> 10 -> 20
在立即執行函數中,var a = 20; 語句定義了一個局部變量 a,由于js的變量聲明提升機制,局部變量a的聲明會被提升至立即執行函數的函數體最上方,且由于這樣的提升并不包括賦值,因此第一條打印語句會打印undefined,最后一條語句會打印20。
由于變量聲明提升,a = 5; 這條語句執行時,局部的變量a已經聲明,因此它產生的效果是對局部的變量a賦值,此時window.a 依舊是最開始賦值的10,
class Chameleon {
static colorChange(newColor) {
this.newColor = newColor;
}
constructor({ newColor = "green" } = {}) {
this.newColor = newColor;
}
}
const freddie = new Chameleon({ newColor: "purple" });
freddie.colorChange("orange");
答案: D
colorChange方法是靜態的。靜態方法僅在創建它們的構造函數中存在,并且不能傳遞給任何子級。由于freddie是一個子級對象,函數不會傳遞,所以在freddie實例上不存在freddie方法:拋出TypeError。
var a = ?;
if(a == 1 && a == 2 && a == 3){
conso.log(1);
}
參考答案
因為==會進行隱式類型轉換 所以我們重寫toString方法就可以了
var a = {
i: 1,
toString() {
return a.i++;
}
}
if( a == 1 && a == 2 && a == 3 ) {
console.log(1);
}
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對象的push方法設置 obj[2]=1;obj.length+=12.使用第二次push,obj對象的push方法設置 obj[3]=2;obj.length+=13.使用console.log輸出的時候,因為obj具有 length 屬性和 splice 方法,故將其作為數組進行打印 4.打印時因為數組未設置下標為 0 1 處的值,故打印為empty,主動 obj[0] 獲取為 undefined
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同時引用了{n:2}對象,接著執行到a.x = a = {n:2}語句,盡管賦值是從右到左的沒錯,但是.的優先級比=要高,所以這里首先執行a.x,相當于為a(或者b)所指向的{n:1}對象新增了一個屬性x,即此時對象將變為{n:1;x:undefined}。之后按正常情況,從右到左進行賦值,此時執行a ={n:2}的時候,a的引用改變,指向了新對象{n:2},而b依然指向的是舊對象。之后執行a.x = {n:2}的時候,并不會重新解析一遍a,而是沿用最初解析a.x時候的a,也即舊對象,故此時舊對象的x的值為{n:2},舊對象為 {n:1;x:{n:2}},它被b引用著。后面輸出a.x的時候,又要解析a了,此時的a是指向新對象的a,而這個新對象是沒有x屬性的,故訪問時輸出undefined;而訪問b.x的時候,將輸出舊對象的x的值,即{n:2}。
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
在比較相等性,原始類型通過它們的值進行比較,而對象通過它們的引用進行比較。JavaScript檢查對象是否具有對內存中相同位置的引用。
我們作為參數傳遞的對象和我們用于檢查相等性的對象在內存中位于不同位置,所以它們的引用是不同的。
這就是為什么{ age: 18 } === { age: 18 }和 { age: 18 } == { age: 18 }返回 false的原因。
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
所有對象鍵(不包括Symbols)都會被存儲為字符串,即使你沒有給定字符串類型的鍵。這就是為什么obj.hasOwnProperty('1')也返回true。
上面的說法不適用于Set。在我們的Set中沒有“1”:set.has('1')返回false。它有數字類型1,set.has(1)返回true。
// 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]);
參考答案
這題考察的是對象的鍵名的轉換。
// example 1
var a={}, b='123', c=123;
a[b]='b';
// c 的鍵名會被轉換成字符串'123',這里會把 b 覆蓋掉。
a[c]='c';
// 輸出 c
console.log(a[b]);
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
// b 是 Symbol 類型,不需要轉換。
a[b]='b';
// c 是 Symbol 類型,不需要轉換。任何一個 Symbol 類型的值都是不相等的,所以不會覆蓋掉 b。
a[c]='c';
// 輸出 b
console.log(a[b]);
// example 3
var a={}, b={key:'123'}, c={key:'456'};
// b 不是字符串也不是 Symbol 類型,需要轉換成字符串。
// 對象類型會調用 toString 方法轉換成字符串 [object Object]。
a[b]='b';
// c 不是字符串也不是 Symbol 類型,需要轉換成字符串。
// 對象類型會調用 toString 方法轉換成字符串 [object Object]。這里會把 b 覆蓋掉。
a[c]='c';
// 輸出 c
console.log(a[b]);
(() => {
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塊接收參數x。當我們傳遞參數時,這與變量的x不同。這個變量x是屬于catch作用域的。
之后,我們將這個塊級作用域的變量設置為1,并設置變量y的值。現在,我們打印塊級作用域的變量x,它等于1。
在catch塊之外,x仍然是undefined,而y是2。當我們想在catch塊之外的console.log(x)時,它返回undefined,而y返回2。
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 的構建方法,沒有產生實例,此刻也沒有執行
Foo.prototype.a = function() {
console.log(3)
}
// 現在在 Foo 上掛載了原型方法 a ,方法輸出值為 3
Foo.a = function() {
console.log(4)
}
// 現在在 Foo 上掛載了直接方法 a ,輸出值為 4
Foo.a();
// 立刻執行了 Foo 上的 a 方法,也就是剛剛定義的,所以
// # 輸出 4
let obj = new Foo();
/* 這里調用了 Foo 的構建方法。Foo 的構建方法主要做了兩件事:
1. 將全局的 Foo 上的直接方法 a 替換為一個輸出 1 的方法。
2. 在新對象上掛載直接方法 a ,輸出值為 2。
*/
obj.a();
// 因為有直接方法 a ,不需要去訪問原型鏈,所以使用的是構建方法里所定義的 this.a,
// # 輸出 2
Foo.a();
// 構建方法里已經替換了全局 Foo 上的 a 方法,所以
// # 輸出 1
作者:靜觀流葉
原文地址:https://mp.weixin.qq.com/s/PTSaytcf3xgOp6C9l3Pvjw
章轉載:樂字節
簡答題
1、什么是防抖和節流?有什么區別?如何實現?
參考答案
防抖
觸發高頻事件后n秒內函數只會執行一次,如果n秒內高頻事件再次被觸發,則重新計算時間
思路:
每次觸發事件時都取消之前的延時調用方法
function debounce(fn) {
let timeout = null; // 創建一個標記用來存放定時器的返回值
return function () {
clearTimeout(timeout); // 每當用戶輸入的時候把前一個 setTimeout clear 掉
timeout = setTimeout(() => { // 然后又創建一個新的 setTimeout, 這樣就能保證輸入字符后的 interval 間隔內如果還有字符輸入的話,就不會執行 fn 函數
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log('防抖成功');
}
var inp = document.getElementById('inp');
inp.addEventListener('input', debounce(sayHi)); // 防抖
節流
高頻事件觸發,但在n秒內只會執行一次,所以節流會稀釋函數的執行頻率
思路:
每次觸發事件時都判斷當前是否有等待執行的延時函數
function throttle(fn) {
let canRun = true; // 通過閉包保存一個標記
return function () {
if (!canRun) return; // 在函數開頭判斷標記是否為true,不為true則return
canRun = false; // 立即設置為false
setTimeout(() => { // 將外部傳入的函數的執行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout執行完畢后再把標記設置為true(關鍵)表示可以執行下一次循環了。當定時器沒有執行的時候標記永遠是false,在開頭被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener('resize', throttle(sayHi));
2、 get請求傳參長度的誤區、get和post請求在緩存方面的區別
誤區:我們經常說get請求參數的大小存在限制,而post請求的參數大小是無限制的。
參考答案
實際上HTTP 協議從未規定 GET/POST 的請求長度限制是多少。對get請求參數的限制是來源與瀏覽器或web服務器,瀏覽器或web服務器限制了url的長度。為了明確這個概念,我們必須再次強調下面幾點:
HTTP 協議 未規定 GET 和POST的長度限制
GET的最大長度顯示是因為 瀏覽器和 web服務器限制了 URI的長度
不同的瀏覽器和WEB服務器,限制的最大長度不一樣
要支持IE,則最大長度為2083byte,若只支持Chrome,則最大長度 8182byte
補充補充一個get和post在緩存方面的區別:
get請求類似于查找的過程,用戶獲取數據,可以不用每次都與數據庫連接,所以可以使用緩存。
post不同,post做的一般是修改和刪除的工作,所以必須與數據庫交互,所以不能使用緩存。因此get請求適合于請求緩存。
3、模塊化發展歷程
可從IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、<script type="module"> 這幾個角度考慮。
參考答案
模塊化主要是用來抽離公共代碼,隔離作用域,避免變量沖突等。
IIFE:使用自執行函數來編寫模塊化,特點:在一個單獨的函數作用域中執行代碼,避免變量沖突。
(function(){
return {
data:[]
}
})()
AMD:使用requireJS 來編寫模塊化,特點:依賴必須提前聲明好。
define('./index.js',function(code){
// code 就是index.js 返回的內容
})
CMD:使用seaJS 來編寫模塊化,特點:支持動態引入依賴文件。
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 來引入另一個 js 。
import a from 'a';
4、npm 模塊安裝機制,為什么輸入 npm install 就可以自動安裝對應的模塊?
參考答案
1. npm 模塊安裝機制:
發出npm install命令
查詢node_modules目錄之中是否已經存在指定模塊
npm 向 registry 查詢模塊壓縮包的網址
下載壓縮包,存放在根目錄下的.npm目錄里
解壓壓縮包到當前項目的node_modules目錄
若存在,不再重新安裝
若不存在
2. npm 實現原理
輸入 npm install 命令并敲下回車后,會經歷如下幾個階段(以 npm 5.5.1 為例):
1. npm 模塊安裝機制:
發出npm install命令
查詢node_modules目錄之中是否已經存在指定模塊
npm 向 registry 查詢模塊壓縮包的網址
下載壓縮包,存放在根目錄下的.npm目錄里
解壓壓縮包到當前項目的node_modules目錄
若存在,不再重新安裝
若不存在
2. npm 實現原理
輸入 npm install 命令并敲下回車后,會經歷如下幾個階段(以 npm 5.5.1 為例):
執行工程自身 preinstall
當前 npm 工程如果定義了 preinstall 鉤子此時會被執行。
確定首層依賴模塊
首先需要做的是確定工程中的首層依賴,也就是 dependencies 和 devDependencies 屬性中直接指定的模塊(假設此時沒有添加 npm install 參數)。
工程本身是整棵依賴樹的根節點,每個首層依賴模塊都是根節點下面的一棵子樹,npm 會開啟多進程從每個首層依賴模塊開始逐步尋找更深層級的節點。
獲取模塊
獲取模塊是一個遞歸的過程,分為以下幾步:
獲取模塊信息。在下載一個模塊之前,首先要確定其版本,這是因為 package.json 中往往是 semantic version(semver,語義化版本)。此時如果版本描述文件(npm-shrinkwrap.json 或 package-lock.json)中有該模塊信息直接拿即可,如果沒有則從倉庫獲取。如 packaeg.json 中某個包的版本是 ^1.1.0,npm 就會去倉庫中獲取符合 1.x.x 形式的最新版本。
獲取模塊實體。上一步會獲取到模塊的壓縮包地址(resolved 字段),npm 會用此地址檢查本地緩存,緩存中有就直接拿,如果沒有則從倉庫下載。
查找該模塊依賴,如果有依賴則回到第1步,如果沒有則停止。
模塊扁平化(dedupe)
上一步獲取到的是一棵完整的依賴樹,其中可能包含大量重復模塊。比如 A 模塊依賴于 loadsh,B 模塊同樣依賴于 lodash。在 npm3 以前會嚴格按照依賴樹的結構進行安裝,因此會造成模塊冗余。
從 npm3 開始默認加入了一個 dedupe 的過程。它會遍歷所有節點,逐個將模塊放在根節點下面,也就是 node-modules 的第一層。當發現有重復模塊時,則將其丟棄。
這里需要對重復模塊進行一個定義,它指的是模塊名相同且 semver 兼容。每個 semver 都對應一段版本允許范圍,如果兩個模塊的版本允許范圍存在交集,那么就可以得到一個兼容版本,而不必版本號完全一致,這可以使更多冗余模塊在 dedupe 過程中被去掉。
比如 node-modules 下 foo 模塊依賴 lodash@^1.0.0,bar 模塊依賴 lodash@^1.1.0,則 ^1.1.0 為兼容版本。
而當 foo 依賴 lodash@^2.0.0,bar 依賴 lodash@^1.1.0,則依據 semver 的規則,二者不存在兼容版本。會將一個版本放在 node_modules 中,另一個仍保留在依賴樹里。
舉個例子,假設一個依賴樹原本是這樣:
node_modules -- foo ---- lodash@version1
-- bar ---- lodash@version2
假設 version1 和 version2 是兼容版本,則經過 dedupe 會成為下面的形式:
node_modules -- foo
-- bar
-- lodash(保留的版本為兼容版本)
假設 version1 和 version2 為非兼容版本,則后面的版本保留在依賴樹中:
node_modules -- foo -- lodash@version1
-- bar ---- lodash@version2
安裝模塊
這一步將會更新工程中的 node_modules,并執行模塊中的生命周期函數(按照 preinstall、install、postinstall 的順序)。
執行工程自身生命周期
當前 npm 工程如果定義了鉤子此時會被執行(按照 install、postinstall、prepublish、prepare 的順序)。
最后一步是生成或更新版本描述文件,npm install 過程完成。
5、ES5的繼承和ES6的繼承有什么區別?
參考答案
ES5的繼承時通過prototype或構造函數機制來實現。ES5的繼承實質上是先創建子類的實例對象,然后再將父類的方法添加到this上(Parent.apply(this))。
ES6的繼承機制完全不同,實質上是先創建父類的實例對象this(所以必須先調用父類的super()方法),然后再用子類的構造函數修改this。
具體的:ES6通過class關鍵字定義類,里面有構造方法,類之間通過extends關鍵字實現繼承。子類必須在constructor方法中調用super方法,否則新建實例報錯。因為子類沒有自己的this對象,而是繼承了父類的this對象,然后對其進行加工。如果不調用super方法,子類得不到this對象。
ps:super關鍵字指代父類的實例,即父類的this對象。在子類構造函數中,調用super后,才可使用this關鍵字,否則報錯。
6、setTimeout、Promise、Async/Await 的區別
參考答案
7、定時器的執行順序或機制?
參考答案
**因為js是單線程的,瀏覽器遇到setTimeout或者setInterval會先執行完當前的代碼塊,在此之前會把定時器推入瀏覽器的待執行事件隊列里面,等到瀏覽器執行完當前代碼之后會看一下事件隊列里面有沒有任務,有的話才執行定時器的代碼。**所以即使把定時器的時間設置為0還是會先執行當前的一些代碼。
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()
輸出結果:
test //10次
undefined
123
321
8、['1','2','3'].map(parseInt) 輸出什么,為什么?
參考答案
輸出:[1, NaN, NaN]
首先讓我們回顧一下,map函數的第一個參數callback:
var new_array = arr.map(function callback(currentValue[, index[, array]]) { // Return element for new_array }[, thisArg])這個callback一共可以接收三個參數,其中第一個參數代表當前被處理的元素,而第二個參數代表該元素的索引。
而parseInt則是用來解析字符串的,使字符串成為指定基數的整數。parseInt(string, radix)接收兩個參數,第一個表示被處理的值(字符串),第二個表示為解析時的基數。
了解這兩個函數后,我們可以模擬一下運行情況
parseInt('1', 0) //radix為0時,且string參數不以“0x”和“0”開頭時,按照10為基數處理。這個時候返回1
parseInt('2', 1) //基數為1(1進制)表示的數中,最大值小于2,所以無法解析,返回NaN
parseInt('3', 2) //基數為2(2進制)表示的數中,最大值小于3,所以無法解析,返回NaN
map函數返回的是一個數組,所以最后結果為[1, NaN, NaN]
9、Doctype作用? 嚴格模式與混雜模式如何區分?它們有何意義?
參考答案
Doctype聲明于文檔最前面,告訴瀏覽器以何種方式來渲染頁面,這里有兩種模式,嚴格模式和混雜模式。
嚴格模式的排版和 JS 運作模式是 以該瀏覽器支持的最高標準運行。
混雜模式,向后兼容,模擬老式瀏覽器,防止瀏覽器無法兼容頁面。
10、fetch發送2次請求的原因
參考答案
fetch發送post請求的時候,總是發送2次,第一次狀態碼是204,第二次才成功?
原因很簡單,因為你用fetch的post請求的時候,導致fetch 第一次發送了一個Options請求,詢問服務器是否支持修改的請求頭,如果服務器支持,則在第二次中發送真正的請求。
http、瀏覽器對象
1、HTTPS 握手過程中,客戶端如何驗證證書的合法性
參考答案
首先什么是HTTP協議?
http協議是超文本傳輸協議,位于tcp/ip四層模型中的應用層;通過請求/響應的方式在客戶端和服務器之間進行通信;但是缺少安全性,http協議信息傳輸是通過明文的方式傳輸,不做任何加密,相當于在網絡上裸奔;容易被中間人惡意篡改,這種行為叫做中間人攻擊;
加密通信:
為了安全性,雙方可以使用對稱加密的方式key進行信息交流,但是這種方式對稱加密秘鑰也會被攔截,也不夠安全,進而還是存在被中間人攻擊風險;于是人們又想出來另外一種方式,使用非對稱加密的方式;使用公鑰/私鑰加解密;通信方A發起通信并攜帶自己的公鑰,接收方B通過公鑰來加密對稱秘鑰;然后發送給發起方A;A通過私鑰解密;雙發接下來通過對稱秘鑰來進行加密通信;但是這種方式還是會存在一種安全性;中間人雖然不知道發起方A的私鑰,但是可以做到偷天換日,將攔截發起方的公鑰key;并將自己生成的一對公/私鑰的公鑰發送給B;接收方B并不知道公鑰已經被偷偷換過;按照之前的流程,B通過公鑰加密自己生成的對稱加密秘鑰key2;發送給A;這次通信再次被中間人攔截,盡管后面的通信,兩者還是用key2通信,但是中間人已經掌握了Key2;可以進行輕松的加解密;還是存在被中間人攻擊風險;
解決困境:權威的證書頒發機構CA來解決;
制作證書:作為服務端的A,首先把自己的公鑰key1發給證書頒發機構,向證書頒發機構進行申請證書;證書頒發機構有一套自己的公私鑰,CA通過自己的私鑰來加密key1,并且通過服務端網址等信息生成一個證書簽名,證書簽名同樣使用機構的私鑰進行加密;制作完成后,機構將證書發給A;
校驗證書真偽:當B向服務端A發起請求通信的時候,A不再直接返回自己的公鑰,而是返回一個證書;
說明:各大瀏覽器和操作系統已經維護了所有的權威證書機構的名稱和公鑰。B只需要知道是哪個權威機構發的證書,使用對應的機構公鑰,就可以解密出證書簽名;接下來,B使用同樣的規則,生成自己的證書簽名,如果兩個簽名是一致的,說明證書是有效的;簽名驗證成功后,B就可以再次利用機構的公鑰,解密出A的公鑰key1;接下來的操作,就是和之前一樣的流程了;
中間人是否會攔截發送假證書到B呢?
因為證書的簽名是由服務器端網址等信息生成的,并且通過第三方機構的私鑰加密中間人無法篡改;所以最關鍵的問題是證書簽名的真偽;
https主要的思想是在http基礎上增加了ssl安全層,即以上認證過程;
2、TCP三次握手和四次揮手
參考答案
三次握手之所以是三次是保證client和server均讓對方知道自己的接收和發送能力沒問題而保證的最小次數。
第一次client => server 只能server判斷出client具備發送能力 第二次 server => client client就可以判斷出server具備發送和接受能力。此時client還需讓server知道自己接收能力沒問題于是就有了第三次 第三次 client => server 雙方均保證了自己的接收和發送能力沒有問題
其中,為了保證后續的握手是為了應答上一個握手,每次握手都會帶一個標識 seq,后續的ACK都會對這個seq進行加一來進行確認。
3、img iframe script 來發送跨域請求有什么優缺點?
參考答案
iframe
優點:跨域完畢之后DOM操作和互相之間的JavaScript調用都是沒有問題的
缺點:1.若結果要以URL參數傳遞,這就意味著在結果數據量很大的時候需要分割傳遞,巨煩。2.還有一個是iframe本身帶來的,母頁面和iframe本身的交互本身就有安全性限制。
script
優點:可以直接返回json格式的數據,方便處理
缺點:只接受GET請求方式
圖片ping
優點:可以訪問任何url,一般用來進行點擊追蹤,做頁面分析常用的方法
缺點:不能訪問響應文本,只能監聽是否響應
4、http和https的區別?
參考答案
http傳輸的數據都是未加密的,也就是明文的,網景公司設置了SSL協議來對http協議傳輸的數據進行加密處理,簡單來說https協議是由http和ssl協議構建的可進行加密傳輸和身份認證的網絡協議,比http協議的安全性更高。主要的區別如下:
Https協議需要ca證書,費用較高。
http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協議。
使用不同的鏈接方式,端口也不同,一般而言,http協議的端口為80,https的端口為443
http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。
5、什么是Bom?有哪些常用的Bom屬性?
參考答案
Bom是瀏覽器對象
location對象
location.href-- 返回或設置當前文檔的URL
location.search -- 返回URL中的查詢字符串部分。例如 http://www.dreamdu.com/dreamd... 返回包括(?)后面的內容?id=5&name=dreamdu
location.hash -- 返回URL#后面的內容,如果沒有#,返回空 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中的協議部分。例如 http://www.dreamdu.com:8080/xhtml/ 返回(//)前面的內容http:
location.assign -- 設置當前文檔的URL
location.replace() -- 設置當前文檔的URL,并且在history對象的地址列表中移除這個URL location.replace(url);
location.reload() -- 重載當前頁面
history對象
history.go() -- 前進或后退指定的頁面數
history.go(num); history.back() -- 后退一頁
history.forward() -- 前進一頁
Navigator對象
navigator.userAgent -- 返回用戶代理頭的字符串表示(就是包括瀏覽器版本信息等的字符串)
navigator.cookieEnabled -- 返回瀏覽器是否支持(啟用)cookie
6、Cookie、sessionStorage、localStorage的區別
參考答案
共同點:都是保存在瀏覽器端,并且是同源的
Cookie:cookie數據始終在同源的http請求中攜帶(即使不需要),即cookie在瀏覽器和服務器間來回傳遞。而sessionStorage和localStorage不會自動把數據發給服務器,僅在本地保存。cookie數據還有路徑(path)的概念,可以限制cookie只屬于某個路徑下,存儲的大小很小只有4K左右。(key:可以在瀏覽器和服務器端來回傳遞,存儲容量小,只有大約4K左右)
sessionStorage:僅在當前瀏覽器窗口關閉前有效,自然也就不可能持久保持,localStorage:始終有效,窗口或瀏覽器關閉也一直保存,因此用作持久數據;cookie只在設置的cookie過期時間之前一直有效,即使窗口或瀏覽器關閉。(key:本身就是一個回話過程,關閉瀏覽器后消失,session為一個回話,當頁面不同即使是同一頁面打開兩次,也被視為同一次回話)
localStorage:localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。(key:同源窗口都會共享,并且不會失效,不管窗口或者瀏覽器關閉與否都會始終生效)
補充說明一下cookie的作用:
保存用戶登錄狀態。例如將用戶id存儲于一個cookie內,這樣當用戶下次訪問該頁面時就不需要重新登錄了,現在很多論壇和社區都提供這樣的功能。cookie還可以設置過期時間,當超過時間期限后,cookie就會自動消失。因此,系統往往可以提示用戶保持登錄狀態的時間:常見選項有一個月、三個 月、一年等。
跟蹤用戶行為。例如一個天氣預報網站,能夠根據用戶選擇的地區顯示當地的天氣情況。如果每次都需要選擇所在地是煩瑣的,當利用了 cookie后就會顯得很人性化了,系統能夠記住上一次訪問的地區,當下次再打開該頁面時,它就會自動顯示上次用戶所在地區的天氣情況。因為一切都是在后 臺完成,所以這樣的頁面就像為某個用戶所定制的一樣,使用起來非常方便
定制頁面。如果網站提供了換膚或更換布局的功能,那么可以使用cookie來記錄用戶的選項,例如:背景色、分辨率等。當用戶下次訪問時,仍然可以保存上一次訪問的界面風格。
7、Cookie如何防范XSS攻擊
參考答案
XSS(跨站腳本攻擊)是指攻擊者在返回的HTML中嵌入javascript腳本,為了減輕這些攻擊,需要在HTTP頭部配上,set-cookie:
httponly-這個屬性可以防止XSS,它會禁止javascript腳本來訪問cookie。
secure - 這個屬性告訴瀏覽器僅在請求為https的時候發送cookie。
結果應該是這樣的:Set-Cookie=.....
8、瀏覽器和 Node 事件循環的區別?
參考答案
其中一個主要的區別在于瀏覽器的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以下版本的執行結果(先執行所有的宏任務,再執行微任務)
// start
// end
// children1
// children2
// children3
// children2-1
// children3-1
// 以上代碼在node11及瀏覽器的執行結果(順序執行宏任務和微任務)
// start
// end
// children1
// children2
// children2-1
// children3
// children3-1
9、簡述HTTPS中間人攻擊
參考答案
https協議由 http + ssl 協議構成,具體的鏈接過程可參考SSL或TLS握手的概述
中間人攻擊過程如下:
服務器向客戶端發送公鑰。
攻擊者截獲公鑰,保留在自己手上。
然后攻擊者自己生成一個【偽造的】公鑰,發給客戶端。
客戶端收到偽造的公鑰后,生成加密hash值發給服務器。
攻擊者獲得加密hash值,用自己的私鑰解密獲得真秘鑰。
同時生成假的加密hash值,發給服務器。
服務器用私鑰解密獲得假秘鑰。
服務器用加秘鑰加密傳輸信息
防范方法:
服務端在發送瀏覽器的公鑰中加入CA證書,瀏覽器可以驗證CA證書的有效性
10、說幾條web前端優化策略
參考答案
(1). 減少HTTP請求數
這條策略基本上所有前端人都知道,而且也是最重要最有效的。都說要減少HTTP請求,那請求多了到底會怎么樣呢?首先,每個請求都是有成本的,既包 含時間成本也包含資源成本。一個完整的請求都需要經過DNS尋址、與服務器建立連接、發送數據、等待服務器響應、接收數據這樣一個“漫長”而復雜的過程。時間成本就是用戶需要看到或者“感受”到這個資源是必須要等待這個過程結束的,資源上由于每個請求都需要攜帶數據,因此每個請求都需要占用帶寬。
另外,由于瀏覽器進行并發請求的請求數是有上限的,因此請求數多了以后,瀏覽器需要分批進行請求,因此會增加用戶的等待時間,會給 用戶造成站點速度慢這樣一個印象,即使可能用戶能看到的第一屏的資源都已經請求完了,但是瀏覽器的進度條會一直存在。減少HTTP請求數的主要途徑包括:
(2). 從設計實現層面簡化頁面
如果你的頁面像百度首頁一樣簡單,那么接下來的規則基本上都用不著了。保持頁面簡潔、減少資源的使用時最直接的。如果不是這樣,你的頁面需要華麗的皮膚,則繼續閱讀下面的內容。
(3). 合理設置HTTP緩存
緩存的力量是強大的,恰當的緩存設置可以大大的減少HTTP請求。以有啊首頁為例,當瀏覽器沒有緩存的時候訪問一共會發出78個請求,共600多K 數據(如圖1.1),而當第二次訪問即瀏覽器已緩存之后訪問則僅有10個請求,共20多K數據(如圖1.2)。(這里需要說明的是,如果直接F5刷新頁面 的話效果是不一樣的,這種情況下請求數還是一樣,不過被緩存資源的請求服務器是304響應,只有Header沒有Body,可以節省帶寬)
怎樣才算合理設置?原則很簡單,能緩存越多越好,能緩存越久越好。例如,很少變化的圖片資源可以直接通過HTTP Header中的Expires設置一個很長的過期頭;變化不頻繁而又可能會變的資源可以使用Last-Modifed來做請求驗證。盡可能的讓資源能夠 在緩存中待得更久。
(4). 資源合并與壓縮
如果可以的話,盡可能的將外部的腳本、樣式進行合并,多個合為一個。另外,CSS、Javascript、Image都可以用相應的工具進行壓縮,壓縮后往往能省下不少空間。
(5). CSS Sprites
合并CSS圖片,減少請求數的又一個好辦法。
(6). Inline Images
使用data: URL scheme的方式將圖片嵌入到頁面或CSS中,如果不考慮資源管理上的問題的話,不失為一個好辦法。如果是嵌入頁面的話換來的是增大了頁面的體積,而且無法利用瀏覽器緩存。使用在CSS中的圖片則更為理想一些。
(7). Lazy Load Images
這條策略實際上并不一定能減少HTTP請求數,但是卻能在某些條件下或者頁面剛加載時減少HTTP請求數。對于圖片而言,在頁面剛加載的時候可以只 加載第一屏,當用戶繼續往后滾屏的時候才加載后續的圖片。這樣一來,假如用戶只對第一屏的內容感興趣時,那剩余的圖片請求就都節省了。有啊首頁曾經的做法 是在加載的時候把第一屏之后的圖片地址緩存在Textarea標簽中,待用戶往下滾屏的時候才“惰性”加載。
11、你了解的瀏覽器的重繪和回流導致的性能問題
參考答案
重繪(Repaint)和回流(Reflow)
重繪和回流是渲染步驟中的一小節,但是這兩個步驟對于性能影響很大。
重繪是當節點需要更改外觀而不會影響布局的,比如改變 color就叫稱為重繪
回流是布局或者幾何屬性需要改變就稱為回流。
回流必定會發生重繪,重繪不一定會引發回流。回流所需的成本比重繪高的多,改變深層次的節點很可能導致父節點的一系列回流。
所以以下幾個動作可能會導致性能問題:
改變 window 大小
改變字體
添加或刪除樣式
文字改變
定位或者浮動
盒模型
很多人不知道的是,重繪和回流其實和 Event loop 有關。
當 Event loop 執行完 Microtasks 后,會判斷 document 是否需要更新。因為瀏覽器是 60Hz 的刷新率,每 16ms 才會更新一次。
然后判斷是否有 resize或者 scroll,有的話會去觸發事件,所以 resize和 scroll事件也是至少 16ms 才會觸發一次,并且自帶節流功能。
判斷是否觸發了 media query
更新動畫并且發送事件
判斷是否有全屏操作事件
執行 requestAnimationFrame回調
執行 IntersectionObserver回調,該方法用于判斷元素是否可見,可以用于懶加載上,但是兼容性不好
更新界面
以上就是一幀中可能會做的事情。如果在一幀中有空閑時間,就會去執行 requestIdleCallback回調。
減少重繪和回流
使用 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,因為前者只會引起重繪,后者會引發回流(改變了布局)
把 DOM 離線后修改,比如:先把 DOM 給 display:none(有一次 Reflow),然后你修改100次,然后再把它顯示出來
不要把 DOM 結點的屬性值放在一個循環里當成循環里的變量
for(let i = 0; i < 1000; i++) {
// 獲取 offsetTop 會導致回流,因為需要去獲取正確的值
console.log(document.querySelector('.test').style.offsetTop)
}
不要使用 table 布局,可能很小的一個小改動會造成整個 table 的重新布局
動畫實現的速度的選擇,動畫速度越快,回流次數越多,也可以選擇使用 requestAnimationFrame
CSS 選擇符從右往左匹配查找,避免 DOM 深度過深
將頻繁運行的動畫變為圖層,圖層能夠阻止該節點回流影響別的元素。比如對于 video標簽,瀏覽器會自動將該節點變為圖層。
react、Vue
1、寫 React / Vue 項目時為什么要在列表組件中寫 key,其作用是什么?
參考答案
vue和react都是采用diff算法來對比新舊虛擬節點,從而更新節點。在vue的diff函數中(建議先了解一下diff算法過程)。在交叉對比中,當新節點跟舊節點頭尾交叉對比沒有結果時,會根據新節點的key去對比舊節點數組中的key,從而找到相應舊節點(這里對應的是一個key => index 的map映射)。如果沒找到就認為是一個新增節點。而如果沒有key,那么就會采用遍歷查找的方式去找到對應的舊節點。一種一個map映射,另一種是遍歷查找。相比而言。map映射的速度更快。vue部分源碼如下:
// vue項目 src/core/vdom/patch.js -488行
// 以下是為了閱讀性進行格式化后的代碼
// oldCh 是一個舊虛擬節點數組
if (isUndef(oldKeyToIdx)) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
if(isDef(newStartVnode.key)) {
// map 方式獲取
idxInOld = oldKeyToIdx[newStartVnode.key]
} else {
// 遍歷方式獲取
idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
}
創建map函數
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 是對比新舊節點是否相同的函數
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 什么時候是同步的,什么時候是異步的?
參考答案
在React中,如果是由React引發的事件處理(比如通過onClick引發的事件處理),調用setState不會同步更新this.state,除此之外的setState調用會同步執行this.state。所謂“除此之外”,指的是繞過React通過addEventListener直接添加的事件處理函數,還有通過setTimeout/setInterval產生的異步調用。
**原因:**在React的setState函數實現中,會根據一個變量isBatchingUpdates判斷是直接更新this.state還是放到隊列中回頭再說,而isBatchingUpdates默認是false,也就表示setState會同步更新this.state,但是,有一個函數batchedUpdates,這個函數會把isBatchingUpdates修改為true,而當React在調用事件處理函數之前就會調用這個batchedUpdates,造成的后果,就是由React控制的事件處理過程setState不會同步更新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 自身生命周期內,觸發時 isBatchingUpdates 為 true,所以并不會直接執行更新 state,而是加入了 dirtyComponents,所以打印時獲取的都是更新前的狀態 0。
2、兩次 setState 時,獲取到 this.state.val 都是 0,所以執行時都是將 0 設置成 1,在 react 內部會被合并掉,只執行一次。設置完成后 state.val 值為 1。
3、setTimeout 中的代碼,觸發時 isBatchingUpdates 為 false,所以能夠直接進行更新,所以連著輸出 2,3。
輸出: 0 0 2 3
4、為什么虛擬dom會提高性能?
參考答案
虛擬dom相當于在js和真實dom中間加了一個緩存,利用dom diff算法避免了沒有必要的dom操作,從而提高性能。
具體實現步驟如下:
用 JavaScript 對象結構表示 DOM 樹的結構;然后用這個樹構建一個真正的 DOM 樹,插到文檔當中
當狀態變更的時候,重新構造一棵新的對象樹。然后用新的樹和舊的樹進行比較,記錄兩棵樹差異
把2所記錄的差異應用到步驟1所構建的真正的DOM樹上,視圖就更新了。
css
1、分析比較 opacity: 0、visibility: hidden、display: none 優劣和適用場景
參考答案
結構:display:none: 會讓元素完全從渲染樹中消失,渲染的時候不占據任何空間, 不能點擊, visibility: hidden:不會讓元素從渲染樹消失,渲染元素繼續占據空間,只是內容不可見,不能點擊 opacity: 0: 不會讓元素從渲染樹消失,渲染元素繼續占據空間,只是內容不可見,可以點擊
繼承:display: none:是非繼承屬性,子孫節點消失由于元素從渲染樹消失造成,通過修改子孫節點屬性無法顯示。visibility: hidden:是繼承屬性,子孫節點消失由于繼承了hidden,通過設置visibility: visible;可以讓子孫節點顯式。
性能:displaynone : 修改元素會造成文檔回流,讀屏器不會讀取display: none元素內容,性能消耗較大 visibility:hidden: 修改元素只會造成本元素的重繪,性能消耗較少讀屏器讀取visibility: hidden元素內容 opacity: 0 :修改元素會造成重繪,性能消耗較少
聯系:它們都能讓元素不可見
2、清除浮動的方式有哪些?比較好的是哪一種?
參考答案
常用的一般為三種.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 不再我們考慮之內了
.clearfix 還有另外一種寫法,
-->
.clearfix:before, .clearfix:after {
content:"";
display:table;
}
.clearfix:after{
clear:both;
overflow:hidden;
}
.clearfix{
zoom:1;
}
<!--
用display:table 是為了避免外邊距margin重疊導致的margin塌陷,
內部元素默認會成為 table-cell 單元格的形式
-->
clear:both:若是用在同一個容器內相鄰元素上,那是賊好的,有時候在容器外就有些問題了, 比如相鄰容器的包裹層元素塌陷
overflow:hidden:這種若是用在同個容器內,可以形成 BFC避免浮動造成的元素塌陷
4、css sprite 是什么,有什么優缺點
參考答案
概念:將多個小圖片拼接到一個圖片中。通過 background-position 和元素尺寸調節需要顯示的背景圖案。
優點:
減少 HTTP 請求數,極大地提高頁面加載速度
增加圖片信息重復度,提高壓縮比,減少圖片大小
更換風格方便,只需在一張或幾張圖片上修改顏色或樣式即可實現
缺點:
圖片合并麻煩
維護麻煩,修改一個圖片可能需要重新布局整個圖片,樣式
5、link與@import的區別
參考答案
link是 HTML 方式, @import是 CSS 方式
link最大限度支持并行下載,@import過多嵌套導致串行下載,出現FOUC
link可以通過rel="alternate stylesheet"指定候選樣式
瀏覽器對link支持早于@import,可以使用@import對老瀏覽器隱藏樣式
@import必須在樣式規則之前,可以在 css 文件中引用其他文件
總體來說:link 優于@import
6、display: block;和display: inline;的區別
參考答案
block元素特點:
1.處于常規流中時,如果width沒有設置,會自動填充滿父容器 2.可以應用margin/padding 3.在沒有設置高度的情況下會擴展高度以包含常規流中的子元素 4.處于常規流中時布局時在前后元素位置之間(獨占一個水平空間) 5.忽略vertical-align
inline元素特點
1.水平方向上根據direction依次布局
2.不會在元素前后進行換行
3.受white-space控制
4.margin/padding在豎直方向上無效,水平方向上有效
5.width/height屬性對非替換行內元素無效,寬度由元素內容決定
6.非替換行內元素的行框高由line-height確定,替換行內元素的行框高由height,margin,padding,border決定 7.浮動或絕對定位時會轉換為block8.vertical-align屬性生效
7、容器包含若干浮動元素時如何清理浮動
參考答案
容器元素閉合標簽前添加額外元素并設置clear: both
父元素觸發塊級格式化上下文(見塊級可視化上下文部分)
設置容器元素偽元素進行清理推薦的清理浮動方法
/**
* 在標準瀏覽器下使用
* 1 content內容為空格用于修復opera下文檔中出現
* contenteditable屬性時在清理浮動元素上下的空白
* 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下使用
* 通過觸發hasLayout實現包含浮動
**/
.clearfix {
*zoom: 1;
}
8、PNG,GIF,JPG 的區別及如何選
參考答案
GIF:
8 位像素,256 色
無損壓縮
支持簡單動畫
支持 boolean 透明
適合簡單動畫
JPEG:
顏色限于 256
有損壓縮
可控制壓縮質量
不支持透明
適合照片
PNG:
有 PNG8 和 truecolor PNG
PNG8 類似 GIF 顏色上限為 256,文件小,支持 alpha 透明度,無動畫
適合圖標、背景、按鈕
9、display,float,position 的關系
參考答案
如果display為 none,那么 position 和 float 都不起作用,這種情況下元素不產生框
否則,如果 position 值為 absolute 或者 fixed,框就是絕對定位的,float 的計算值為 none,display 根據下面的表格進行調整。
否則,如果 float 不是 none,框是浮動的,display 根據下表進行調整
否則,如果元素是根元素,display 根據下表進行調整
其他情況下 display 的值為指定值 總結起來:絕對定位、浮動、根元素都需要調整display
10、如何水平居中一個元素
參考答案
如果需要居中的元素為常規流中 inline 元素,為父元素設置text-align: center;即可實現
如果需要居中的元素為常規流中 block 元素,1)為元素設置寬度,2)設置左右 margin 為 auto。3)IE6 下需在父元素上設置text-align: center;,再給子元素恢復需要的值
<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>
如果需要居中的元素為浮動元素,1)為元素設置寬度,2)position: relative;,3)浮動方向偏移量(left 或者 right)設置為 50%,4)浮動方向上的 margin 設置為元素寬度一半乘以-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>
如果需要居中的元素為絕對定位元素,1)為元素設置寬度,2)偏移量設置為 50%,3)偏移方向外邊距設置為元素寬度一半乘以-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>
如果需要居中的元素為絕對定位元素,1)為元素設置寬度,2)設置左右偏移量都為 0,3)設置左右外邊距都為 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有幾種數據類型,其中基本數據類型有哪些?
參考答案
七種數據類型
Boolean
Null
Undefined
Number
String
Symbol (ECMAScript 6 新定義)
Object
(ES6之前)其中5種為基本類型:string,number,boolean,null,undefined,
ES6出來的Symbol也是原始數據類型 ,表示獨一無二的值
Object為引用類型(范圍挺大),也包括數組、函數,
2、Promise 構造函數是同步執行還是異步執行,那么 then 方法呢?
參考答案
const promise = new Promise((resolve, reject) => {
console.log(1)
resolve()
console.log(2)
})
promise.then(() => {
console.log(3)
})
console.log(4)
輸出結果是:
1
2
4
3
promise構造函數是同步執行的,then方法是異步執行的
Promise new的時候會立即執行里面的代碼 then是微任務 會在本次任務執行完的時候執行 setTimeout是宏任務 會在下次任務執行的時候執行
文章轉載:樂字節
*請認真填寫需求信息,我們會在24小時內與您取得聯系。