(如下全部答案僅供參考)javascript
參考答案
php
防抖css
觸發高頻事件後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)); // 防抖
節流html
高頻事件觸發,但在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請求的參數大小是無限制的。前端
參考答案
vue
實際上HTTP 協議從未規定 GET/POST 的請求長度限制是多少。對get請求參數的限制是來源與瀏覽器或web服務器,瀏覽器或web服務器限制了url的長度。爲了明確這個概念,咱們必須再次強調下面幾點:java
補充補充一個get和post在緩存方面的區別:node
可從IIFE、AMD、CMD、CommonJS、UMD、webpack(require.ensure)、ES Module、<script type="module">
這幾個角度考慮。react
參考答案
webpack
模塊化主要是用來抽離公共代碼,隔離做用域,避免變量衝突等。
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
命令查詢node_modules目錄之中是否已經存在指定模塊
若不存在
.npm
目錄裏node_modules
目錄輸入 npm install 命令並敲下回車後,會經歷以下幾個階段(以 npm 5.5.1 爲例):
當前 npm 工程若是定義了 preinstall 鉤子此時會被執行。
首先須要作的是肯定工程中的首層依賴,也就是 dependencies 和 devDependencies 屬性中直接指定的模塊(假設此時沒有添加 npm install 參數)。
工程自己是整棵依賴樹的根節點,每一個首層依賴模塊都是根節點下面的一棵子樹,npm 會開啓多進程從每一個首層依賴模塊開始逐步尋找更深層級的節點。
獲取模塊
獲取模塊是一個遞歸的過程,分爲如下幾步:
上一步獲取到的是一棵完整的依賴樹,其中可能包含大量重複模塊。好比 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 過程完成。
參考答案
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一共能夠接收三個參數,其中第一個參數表明當前被處理的元素,而第二個參數表明該元素的索引。
parseInt(string, radix)
參考答案
Doctype聲明於文檔最前面,告訴瀏覽器以何種方式來渲染頁面,這裏有兩種模式,嚴格模式和混雜模式。
參考答案
fetch發送post請求的時候,老是發送2次,第一次狀態碼是204,第二次才成功?
緣由很簡單,由於你用fetch的post請求的時候,致使fetch 第一次發送了一個Options請求,詢問服務器是否支持修改的請求頭,若是服務器支持,則在第二次中發送真正的請求。
參考答案
http協議是超文本傳輸協議,位於tcp/ip四層模型中的應用層;經過請求/響應的方式在客戶端和服務器之間進行通訊;可是缺乏安全性,http協議信息傳輸是經過明文的方式傳輸,不作任何加密,至關於在網絡上裸奔;容易被中間人惡意篡改,這種行爲叫作中間人攻擊;
爲了安全性,雙方可使用對稱加密的方式key進行信息交流,可是這種方式對稱加密祕鑰也會被攔截,也不夠安全,進而仍是存在被中間人攻擊風險;
因而人們又想出來另一種方式,使用非對稱加密的方式;使用公鑰/私鑰加解密;通訊方A發起通訊並攜帶本身的公鑰,接收方B經過公鑰來加密對稱祕鑰;而後發送給發起方A;A經過私鑰解密;雙發接下來經過對稱祕鑰來進行加密通訊;可是這種方式仍是會存在一種安全性;中間人雖然不知道發起方A的私鑰,可是能夠作到偷天換日,將攔截髮起方的公鑰key;並將本身生成的一對公/私鑰的公鑰發送給B;接收方B並不知道公鑰已經被偷偷換過;按照以前的流程,B經過公鑰加密本身生成的對稱加密祕鑰key2;發送給A;
此次通訊再次被中間人攔截,儘管後面的通訊,二者仍是用key2通訊,可是中間人已經掌握了Key2;能夠進行輕鬆的加解密;仍是存在被中間人攻擊風險;
解決困境:權威的證書頒發機構CA來解決;
說明:各大瀏覽器和操做系統已經維護了全部的權威證書機構的名稱和公鑰。B只須要知道是哪一個權威機構發的證書,使用對應的機構公鑰,就能夠解密出證書籤名;接下來,B使用一樣的規則,生成本身的證書籤名,若是兩個簽名是一致的,說明證書是有效的;
簽名驗證成功後,B就能夠再次利用機構的公鑰,解密出A的公鑰key1;接下來的操做,就是和以前同樣的流程了;
由於證書的簽名是由服務器端網址等信息生成的,而且經過第三方機構的私鑰加密中間人沒法篡改; 因此最關鍵的問題是證書籤名的真僞;
參考答案
三次握手之因此是三次是保證client和server均讓對方知道本身的接收和發送能力沒問題而保證的最小次數。
第一次client => server 只能server判斷出client具有發送能力
第二次 server => client client就能夠判斷出server具有發送和接受能力。此時client還需讓server知道本身接收能力沒問題因而就有了第三次
第三次 client => server 雙方均保證了本身的接收和發送能力沒有問題
其中,爲了保證後續的握手是爲了應答上一個握手,每次握手都會帶一個標識 seq,後續的ACK都會對這個seq進行加一來進行確認。
參考答案
優勢:跨域完畢以後DOM操做和互相之間的JavaScript調用都是沒有問題的
缺點:1.若結果要以URL參數傳遞,這就意味着在結果數據量很大的時候須要分割傳遞,巨煩。2.還有一個是iframe自己帶來的,母頁面和iframe自己的交互自己就有安全性限制。
優勢:能夠直接返回json格式的數據,方便處理
缺點:只接受GET請求方式
優勢:能夠訪問任何url,通常用來進行點擊追蹤,作頁面分析經常使用的方法
缺點:不能訪問響應文本,只能監聽是否響應
參考答案
http傳輸的數據都是未加密的,也就是明文的,網景公司設置了SSL協議來對http協議傳輸的數據進行加密處理,簡單來講https協議是由http和ssl協議構建的可進行加密傳輸和身份認證的網絡協議,比http協議的安全性更高。 主要的區別以下:
參考答案
Bom是瀏覽器對象
location對象
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)
重繪和迴流是渲染步驟中的一小節,可是這兩個步驟對於性能影響很大。
color
就叫稱爲重繪迴流一定會發生重繪,重繪不必定會引起迴流。迴流所需的成本比重繪高的多,改變深層次的節點極可能致使父節點的一系列迴流。
因此如下幾個動做可能會致使性能問題:
不少人不知道的是,重繪和迴流其實和 Event loop 有關。
resize
或者 scroll
,有的話會去觸發事件,因此 resize
和 scroll
事件也是至少 16ms 纔會觸發一次,而且自帶節流功能。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) }
requestAnimationFrame
video
標籤,瀏覽器會自動將該節點變爲圖層。參考答案
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; } };
一、第一次和第二次都是在 react 自身生命週期內,觸發時 isBatchingUpdates 爲 true,因此並不會直接執行更新 state,而是加入了 dirtyComponents,因此打印時獲取的都是更新前的狀態 0。 二、兩次 setState 時,獲取到 this.state.val 都是 0,因此執行時都是將 0 設置成 1,在 react 內部會被合併掉,只執行一次。設置完成後 state.val 值爲 1。 三、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 和元素尺寸調節須要顯示的背景圖案。
優勢:
缺點:
link
與@import
的區別參考答案
link
是 HTML 方式, @import
是 CSS 方式link
最大限度支持並行下載,@import
過多嵌套致使串行下載,出現FOUC link
能夠經過rel="alternate stylesheet"
指定候選樣式link
支持早於@import
,可使用@import
對老瀏覽器隱藏樣式@import
必須在樣式規則以前,能夠在 css 文件中引用其餘文件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.浮動或絕對定位時會轉換爲block
8.vertical-align
屬性生效
參考答案
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; }
參考答案
GIF:
JPEG:
PNG:
參考答案
display
爲 none,那麼 position 和 float 都不起做用,這種狀況下元素不產生框參考答案
text-align: center;
便可實現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>
參考答案
七種數據類型
(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只支持事件冒泡。
Function._proto_(getPrototypeOf)是什麼?
參考答案
獲取一個對象的原型,在chrome中能夠經過__proto__的形式,或者在ES6中能夠經過Object.getPrototypeOf的形式。
那麼Function.proto是什麼麼?也就是說Function由什麼對象繼承而來,咱們來作以下判別。
Function.__proto__==Object.prototype //false Function.__proto__==Function.prototype//true
咱們發現Function的原型也是Function。
咱們用圖能夠來明確這個關係:
參考答案
(prototype)
: 一個簡單的對象,用於實現對象的 屬性繼承。能夠簡單的理解成對象的爹。在 Firefox 和 Chrome 中,每一個JavaScript
對象中都包含一個__proto__
(非標準)的屬性指向它爹(該對象的原型),可obj.__proto__
進行訪問。new
來 新建一個對象的函數。new
建立出來的對象,即是實例。 實例經過__proto__指向原型,經過constructor指向構造函數。這裏來舉個栗子,以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 中,繼承一般指的即是 原型鏈繼承,也就是經過指定原型,並能夠經過原型鏈繼承原型上的屬性或者方法。
最優化: 聖盃模式
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; } })();
class / extends
參考答案
在函數式編程中,函數是一等公民。那麼函數柯里化是怎樣的呢?
函數柯里化指的是將可以接收多個參數的函數轉化爲接收單一參數的函數,而且返回接收餘下參數且返回結果的新函數的技術。
函數柯里化的主要做用和特色就是參數複用、提早返回和延遲執行。
在一個函數中,首先填充幾個參數,而後再返回一個新的函數的技術,稱爲函數的柯里化。一般可用於在不侵入函數的前提下,爲函數 預置通用參數,供屢次重複調用。
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 函數
對於實現如下幾個函數,能夠從幾個方面思考
window
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();
Lydia
和 undefined
Lydia
和 ReferenceError
ReferenceError
和 21
undefined
和 ReferenceError
參考答案
在函數中,咱們首先使用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
也存在變量提高,可是它存在一個「暫時死區」,在變量未初始化或賦值前不容許訪問。
變量的賦值能夠分爲三個階段:
undefined
關於let
、var
和function
:
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");
orange
purple
green
TypeError
答案: 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+=1
2.使用第二次push,obj對象的push方法設置 obj[3]=2;obj.length+=1
3.使用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