31、http 的理解 ?
HTTP 協議是超文本傳輸協議,是客戶端瀏覽器或其他程序“請求”與 Web 服務器響應之間的應用層通信協議。HTTPS主要是由HTTP+SSL構建的可進行加密傳輸、身份認證的一種安全通信通道。
32、http 和 https 的區別 ?
-
1、https協議需要到ca申請證書,一般免費證書較少,因而需要一定費用。
-
2、http是超文本傳輸協議,信息是明文傳輸,https則是具有安全性的ssl加密傳輸協議。
-
3、http和https使用的是完全不同的連接方式,用的端口也不一樣,前者是80,后者是443。
-
4、http的連接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比http協議安全。
33、git 的常用指令有哪些 ?
git?branch 分支查看git?branch branch_1 增加分支git?checkout branch 分支切換git?merge branch_1 合并分支(合并前要切換當前分支至master)git?branch?-d?branch_1 刪除分支git?remote 查看當前倉庫管理的遠程倉庫信息git?remote show origin 查看指定的遠程倉庫的詳細信息git?push?--set-upstream?origin branch_1 第一次將本地分支推到遠程倉庫git?push <遠程主機名> <本地分支名>:<遠程分支名> 將本地分支推到遠程分支git?pull <遠程主機名> <遠程分支>:<本地分支> 將遠程分支拉到本地分支git?branch?-d?branch_0 刪除本地合并后分支git?brench?-D?branch_0 刪除本地未合并分支it push origin?--delete?branch_0 刪除遠程分支git?restore [filename] 進行清除工作區的改變git?tag 查看標簽git?tag v1.0.0 打標簽git?push origin v1.0.0 將tag同步到遠程服務器
34、平時是使用 git 指令還是圖形化工具 ?
repository:git庫相關操作,基本意思就是字面意思。
-
1)資源管理器中瀏覽該Git庫工作空間文件,省去查找路徑不斷點擊鼠標的操作。
-
2)啟動Git bash工具(命令行工具)。
-
3)查看當前分支文件狀態,不包括未提交的信息。
-
4)查看某個分支的文件(彈出框中可選擇需要查看的版本、分支或標簽),跟上一條差不多,用的比較少,可能是沒有這方面的額需求。
-
5)可視化當前分支歷史、可視化所有分支歷史:彈出分支操作歷史,也就是gitk工具,放到gitk工具中介紹。
-
edit:用于操作commit時操作信息輸入,只能操作文字輸入部分,你沒有看錯。常用的快捷鍵大家都知道,何必要單獨做成基本沒啥用的。本來以為對變更的文件進行批量操作、本來以為可以對未版本跟蹤的文件批量刪除、本來、、、,都說了是本來。
-
Branch:新建分支(需要選擇其實版本,可以根據版本號、其他分支或標簽來選擇)、檢出分支(覺得切換分支更合適)、重命名分支、刪除分支、當前分支Reset操作(會丟棄所有未提交的變更,包括工作區和索引區,當然了,有彈出框提示危險操作)。
35、Promsie.all() 使用過嗎, 它是怎么使用的 ?
? promise.all()用于一個異步操作需要在幾個異步操作完成后再進行時使用。promise.all()接受一個promise對象組成的數組參數,返回promise對象。當數組中所有promise都完成了,就執行當前promise對象的then方法,如果數組中有一個promise執行失敗了,就執行當前promise對象的catch方法。
36、什么是三次握手和四次揮手 ?
? 三次握手是網絡客戶端跟網絡服務器之間建立連接,并進行通信的過程。相當于客戶端和服務器之間你來我往的3個步驟。
-
第一次握手是建立連接,客戶端發送連接請求報文,并傳送規定的數據包;
-
第二次握手是服務器端表示接收到連接請求報文,并回傳規定的數據包;
-
第三次握手是客戶端接收到服務器回傳的數據包后,給服務器端再次發送數據包。這樣就完成了客戶端跟服務器的連接和數據傳送。
? 四次揮手表示當前這次連接請求已經結束,要斷開這次連接。
-
第一次揮手是客戶端對服務器發起斷開請求,
-
第二次揮手是服務器表示收到這次斷開請求,
-
第三次揮手是服務器表示已經斷開連接
-
第四次揮手是客戶端斷開連接。
37、for in 和 for of 循環的區別 ?
??`for in`?用于遍歷對象的鍵(`key`),`for in`會遍歷所有自身的和原型鏈上的可枚舉屬性。如果是數組,for?in會將數組的索引(index)當做對象的key來遍歷,其他的object也是一樣的。
??`for of`是`es6`引入的語法,用于遍歷 所有迭代器iterator,其中包括`HTMLCollection`,`NodeList`,`Array`,`Map`,`Set`,`String`,`TypedArray`,`arguments`等對象的值(`item`)。
38、async/await 怎么拋出錯誤異常 ?
如果可能出錯的代碼比較少的時候可以使用try/catch結構來了處理,如果可能出錯的代碼比較多的時候,可以利用async函數返回一個promise對象的原理來處理,給async修飾的函數調用后返回的promise對象,調用catch方法來處理異常。
39、 函數式編程和命令式編程的區別 ?
-
命令式編程(過程式編程) :
? 專注于”如何去做”,這樣不管”做什么”,都會按照你的命令去做。解決某一問題的具體算法實現。
-
函數式編程:把運算過程盡量寫成一系列嵌套的函數調用。
? ? 函數式編程強調沒有”副作用”,意味著函數要保持獨立,所有功能就是返回一個新的值,沒有其他行為,尤其是不得修改外部變量的值。所謂”副作用”,指的是函數內部與外部交互(最典型的情況,就是修改全局變量的值),產生運算以外的其他結果。
40、http 常見的響應狀態碼 ?
??100——客戶必須繼續發出請求101——客戶要求服務器根據請求轉換HTTP協議版本200——交易成功201——提示知道新文件的URL202——接受和處理、但處理未完成203——返回信息不確定或不完整204——請求收到,但返回信息為空205——服務器完成了請求,用戶代理必須復位當前已經瀏覽過的文件206——服務器已經完成了部分用戶的GET請求300——請求的資源可在多處得到301——刪除請求數據302——在其他地址發現了請求數據303——建議客戶訪問其他URL或訪問方式304——客戶端已經執行了GET,但文件未變化305——請求的資源必須從服務器指定的地址得到306——前一版本HTTP中使用的代碼,現行版本中不再使用307——申明請求的資源臨時性刪除400——錯誤請求,如語法錯誤401——請求授權失敗402——保留有效ChargeTo頭響應403——請求不允許404——沒有發現文件、查詢或URl405——用戶在Request-Line字段定義的方法不允許406——根據用戶發送的Accept拖,請求資源不可訪問407——類似401,用戶必須首先在代理服務器上得到授權408——客戶端沒有在用戶指定的餓時間內完成請求409——對當前資源狀態,請求不能完成410——服務器上不再有此資源且無進一步的參考地址411——服務器拒絕用戶定義的Content-Length屬性請求412——一個或多個請求頭字段在當前請求中錯誤413——請求的資源大于服務器允許的大小414——請求的資源URL長于服務器允許的長度415——請求資源不支持請求項目格式416——請求中包含Range請求頭字段,在當前請求資源范圍內沒有range指示值,請求也不包含If-Range請求頭字段417——服務器不滿足請求Expect頭字段指定的期望值,如果是代理服務器,可能是下一級服務器不能滿足請求500——服務器產生內部錯誤501——服務器不支持請求的函數502——服務器暫時不可用,有時是為了防止發生系統過載503——服務器過載或暫停維修504——關口過載,服務器使用另一個關口或服務來響應用戶,等待時間設定值較長505——服務器不支持或拒絕支請求頭中指定的HTTP版本
41、 什么是事件流以及事件流的傳播機制 ?
? 事件觸發后,從開始找目標元素,然后執行目標元素的事件,再到離開目標元素的整個過程稱之為事件流。
? W3C標準瀏覽器事件流的傳播分為3個階段:捕獲階段、目標階段、冒泡階段
-
捕獲階段指找目標元素的過程,這個找的過程,是從最大的document對象到html,再到body,。。。直到目標元素。
-
找到目標元素后,調用執行他綁定事件時對應的處理函數,這個過程被稱之為目標階段。
-
當目標元素的事件執行結束后,再從目標元素,到他的父元素。。。body、html再到document的過程,是冒泡階段。
42、模塊化語法 ? commonJS AMD CMD ES6 Module
-
commonJS是nodejs自帶的一種模塊化語法,將一個文件看做是一個模塊,可以將文件中導出的時候,被另一個文件導入使用。導出使用:
module.exports
導出。導入使用:require
函數導入。 -
AMD是社區開發的模塊化語法,需要依賴
require.js
實現,分為定義模塊,導出數據和導入模塊,使用數據。AMD語法的導入是依賴前置的,也就是說,需要用到的文件需要在第一次打開頁面全部加載完成,造成的后果就是首屏加載很慢,后續操作會很流暢。 -
CMD是玉伯開發的模塊化語法,需要依賴
sea.js
實現,也分為模塊定義導出,和模塊導入使用數據。CMD語法可以依賴前置,也可以按需導入,緩解了AMD語法的依賴前置。 -
ES6的模塊化語法,類似于commonJS的語法,分為數據導出和數據導入,導入導出更加靈活。
43、 什么是懶加載和預加載 ?
-
懶加載:懶加載也叫延遲加載,延遲加載網絡資源或符合某些條件時才加載資源。常見的就是圖片延時加載。懶加載的意義:懶加載的主要目的是作為服務器前端的優化,減少請求數或延遲請求數。懶惰實現方式:
-
1.第一種是純粹的延遲加載,使用setTimeOut或setInterval進行加載延遲.
-
2.第二種是條件加載,符合某些條件,或觸發了某些事件才開始異步下載。
-
3.第三種是可視區加載,即僅加載用戶可以看到的區域,這個主要由監控滾動條來實現,一般會在距用戶看到某圖片前一定距離便開始加載,這樣能保證用戶拉下時正好能看到圖片。
-
-
預加載:提前加載圖片,當用戶需要查看時可直接從本地緩存中渲染。
? 兩者的行為是相反的,一個是提前加載,一個是遲緩甚至不加載。懶加載對服務器前端有一定的緩解壓力作用,預加載則會增加服務器前端壓力。預加載應用如廣告彈窗等。
44、token 一般存放在哪里 ? 為什么不存放在 cookie 內 ?
token一般放在本地存儲中。token的存在本身只關心請求的安全性,而不關心token本身的安全,因為token是服務器端生成的,可以理解為一種加密技術。但如果存在cookie內的話,瀏覽器的請求默認會自動在請求頭中攜帶cookie,所以容易受到csrf攻擊。
45、 less 和 sass 的區別 ?
-
編譯環境不一樣,sass是服務器端處理的,可以用Ruby、node-sass來編譯;less需要引入less.js來處理輸出,也可以使用工具在服務器端處理成css,也有在線編譯的。
-
變量定義符不一樣,less用的是@,而sass用$。
-
sass支持分支語句,less不支持
44、瀏覽器的同源策略機制 ?
同源策略,又稱SOP,全稱Same Origin Policy,是瀏覽器最基本的安全功能。站在瀏覽器的較短看網頁,如果網絡上的接口可以不受限制、無需授權隨意被人調用,那將是一個非常嚴重的混亂場景。瀏覽器為了安全有序,內部實現了同源策略。同源策略,指的是瀏覽器限制當前網頁只能訪問同源的接口資源。所謂同源,指當前頁面和請求的接口,兩方必須是同協議、且同域名、且同端口。只要有一個不相同,則會受到瀏覽器約束,不允許請求。但當一個項目變的很大的時候,將所有內容放在一個網站或一個服務器中會讓網站變的臃腫且性能低下,所以,在一些場景中,我們需要跨過同源策略,請求到不同源的接口資源,這種場景叫跨域。跨域大致有3種方案:
-
jsonp
這種方式是利用瀏覽器不限制某些標簽發送跨域請求,例如link、img、iframe、script。通常請求請求回來的資源要在js中進行處理,所以jsonp跨域是利用script標簽進行發送,且這種請求方式只能是get請求。
-
cors
這種方式是讓接口資源方面進行授權,授權允許訪問。在接口資源處添加響應頭即可通過瀏覽器的同源策略,響應頭具體的鍵值對如下:
{Access-Control-Allow-Origin:?'*'}
-
proxy
這種方式屬于找外援的一種方式,瀏覽器只能限制當前正在打開的web頁面發送請求,但無法限制服務器端請求接口資源。所以我們可以將請求發送到自己服務器,然后自己服務器去請求目標接口資源,最后自己服務器將接口資源返回給當前頁面,類似于找外援代替自己請求目標接口資源。
這種方式通常要對服務器進行代理配置,需要對apache服務器、nginx服務器、nodejs服務器進行配置。
45、 瀏覽器的緩存有哪些 ? 什么時候使用強制緩存 ? 什么時候使用協商緩存 ?
當我們訪問同一個頁面時,請求資源、數據都是需要一定的耗時,如果可以將一些資源緩存下來,那么從第二次訪問開始,就可以減少加載時間,提高用戶體驗,也能減輕服務器的壓力。瀏覽器緩存分為強緩存和協商緩存,當存在緩存時,客戶端第一次向服務器請求數據時,客戶端會緩存到內存或者硬盤當中,當第二次獲取相同的資源,強緩存和協商緩存的應對方式有所不同。強緩存:當客戶端第二次向服務器請求相同的資源時,不會向服務器發送請求,而是直接從內存/硬盤中間讀取。緩存由服務器的響應頭里 cache-control 和 expires 兩個字段決定協商緩存:當客戶端第二次向服務器請求相同的資源時,先向服務器發送請求"詢問"該請求的文件緩存在ben'd與服務器相比是否更改,如果更改,則更新文件,如果沒有就從內存/硬盤中讀取。協商緩存由 last-modified 和 etag兩個字段決定
46、 數組方法 forEach 和 map 的區別 ?
forEach和map都是循環遍歷數組中的每一項。forEach() 和 map() 里面每一次執行匿名函數都支持3個參數:數組中的當前項item,當前項的索引index,原始數組input。匿名函數中的this都是指Window。只能遍歷數組。他們的區別是:forEach沒有返回值,但map中要有返回值,返回處理后的所有新元素組成的數組。
47、 什么是函數作用域 ? 什么是作用域鏈 ?
作用域就是在代碼執行過程中,形成一個獨立的空間,讓空間內的變量不會泄露在空間外,也讓獨立空間內的變量函數在獨立空間內運行,而不會影響到外部的環境。作用域分為全局作用域和局部作用域,也就是本來有一個巨大的空間,空間內定義的函數內部,就形成了一個獨立的小空間,全局作用域是最大的作用域。但是當獨立空間內的數據不能滿足需求時,是可以從外部獲取數據的,也就是說這樣的獨立空間之間是可以有層級關系的,外部的空間不可以從內部的空間獲取數據,但內部的空間可以。當子級空間在父級空間中獲取數據的時,父級空間沒有的話,父級空間也會到他的父級空間中查找數據,這樣形成的鏈式結構叫作用域鏈。當將一個變量當做值使用時,會先在當前作用域中查找這個變量的定義和數據,如果沒有定義的話,就會去父級作用域中查找,如果父級作用域中有的話就使用這個值,如果父級作用域中也沒有的話,就通過父級作用域查找他的父級作用域,直到找到最大的作用域-全局,如果全局也沒有就報錯。當將一個變量當做數據容器存儲,也就是給變量賦值的時候,也要先在自己作用域中查找變量的定義,如果沒有就在上一級作用域中查找,直到全局,如果全局作用域中也沒有這個變量的定義,就在全局定義這個變量并賦值。
48、 ES6 中 Set 和 Map 的原理 ?
Set 是無重復值的有序列表。根據 `Object.is()`方法來判斷其中的值不相等,以保證無重復。Set 會自動移除重復的值,因此你可以使用它來過濾數組中的重復值并返回結果。Set并不是數組的子類型,所以你無法隨機訪問其中的值。但你可以使用`has()` 方法來判斷某個值是否存在于 Set 中,或通過 `size` 屬性來查看其中有多少個值。Set 類型還擁有`forEach()`方法,用于處理每個值Map 是有序的鍵值對,其中的鍵允許是任何類型。與 Set 相似,通過調用 `Object.is()`方法來判斷重復的鍵,這意味著能將數值 5 與字符串 "5" 作為兩個相對獨立的鍵。使用`set()` 方法能將任何類型的值關聯到某個鍵上,并且該值此后能用 `get()` 方法提取出來。Map 也擁有一個 `size` 屬性與一個 `forEach()` 方法,讓項目訪問更容易。
49、 0.1 + 0.2 為什么不等于 0.3, 在項目中遇到要怎么處理 ?
計算機內部存儲數據使用2進制存儲,兩個數字進行的數學運算,首先是將這兩個數字以2進制形式,存儲在計算機內部,然后在計算機內部使用兩個2進制數字進行計算,最后將計算結果的2進制數字轉為10進制展示出來。由于10進制的小數在轉2進制的時候,規則是小數部分乘以2,判斷是否得到一個整數,如果得到整數,轉換完成;如果沒有得到整數,則繼續乘以2判斷。所以,0.1和0.2在轉換2進制的時候,其實是一個無限死循環,也就是一直乘以2沒有得到整數的時候,但計算機內部對于無限死循環的數據,會根據一個標準保留52位。也就是說,計算機內部在存儲0.1和0.2的時候,本來就不精準,兩個不精準的小數在計算后,距離精準的結果是有一定誤差的。項目中碰到這種情況,有3種處理方法:
-
將小數乘以10的倍數,轉為整數,然后計算,計算完成后,再縮小10的倍數,例如:
var result = ((0.1 * 10) + (0.2 * 10)) / 10
// result === 0.3
-
使用數字的toFixed方法,強制保留小數點后多少位,例
var result = (0.1 + 0.2).toFixed(2)
// result === 0.30
-
自定義數字運算方法,當需要進行數學運算的時候,不直接進行,調用自定義的方法進行,例:(加法封裝)
function add(...args){
var num = args.find(item => {
if(item != 0 && !item){
throw new Error("數學運算要使用數字")
}
})
var arr = args.map(item => {
var index = (item+'').indexOf('.')
if(index >= 0){
return (item+'').split('.')[1].length
}
})
arr = arr.filter(item => item)
if(arr.length){
var max = Math.max(...arr)
var data = args.map(item => item * Math.pow(10, max))
var data.reduce((a, b) => a + b) / Math.pow(10, max)
}else{
var data = args
return data.reduce((a, b) => a + b)
}
}
// 調用使用:
var num1 = add(0.1, 0.2)
console.log(num1); // 0.3
var num2 = add(1, 2)
console.log(num2); // 3
var num3 = add(1, 2.1)
console.log(num3); // 3.1
50、 什么是模塊化思想 ?
就是JS中將不同功能的代碼封裝在不同的文件中, 互相引用時不會發生命名沖突的一種思想, 大多數情況下, 一個文件就是一個模塊模塊化的實現,有多種方案:
-
CommonJS:
CommonJS
是nodejs
中使用的模塊化規范在?nodejs
?應用中每個文件就是一個模塊,擁有自己的作用域,文件中的變量、函數都是私有的,與其他文件相隔離。模塊導出:module.exports=數據
,模塊導入:require('模塊文件路徑')
-
ES6的模塊化:
模塊功能主要由兩個命令構成:
export
和import
。export
命令用于規定模塊的對外接口,import
命令用于輸入其他模塊提供的功能。一個模塊就是一個獨立的文件。該文件內部的所有變量,外部無法獲取。如果你希望外部能夠讀取模塊內部的某個變量,就必須使用
export
關鍵字輸出該變量。下面是一個 JS 文件,里面使用export
命令輸出變量。 -
AMD (Asynchronous Module Definition):
特點: 提倡依賴前置,在定義模塊的時候就要聲明其依賴的模塊:導入模塊
require([module],callback);
定義模塊:define('模塊名稱', 函數)
。 -
CMD (Common Module Definition):
CMD規范是國內SeaJS的推廣過程中產生的。提倡就近依賴(按需加載),在用到某個模塊的時候再去require。定義模塊:
define(function (require, exports, module) {})
,使用模塊:seajs.use()
51、 說說怎么用js 寫無縫輪播圖
將所有需要輪播的內容動態復制一份,放在原本的容器中,加定時器讓整個容器中的內容滾動輪播,當內容輪播到left值為-原本的內容寬度時,快速將內容切換到left值為0的狀態。
52、 JS 如何實現多線程 ?
我們都知道JS是一種單線程語言,即使是一些異步的事件也是在JS的主線程上運行的(具體是怎么運行的,可以看我另一篇博客JS代碼運行機制)。像setTimeout、ajax的異步請求,或者是dom元素的一些事件,都是在JS主線程執行的,這些操作并沒有在瀏覽器中開辟新的線程去執行,而是當這些異步操作被操作時或者是被觸發時才進入事件隊列,然后在JS主線程中開始運行。首先說一下瀏覽器的線程,瀏覽器中主要的線程包括,UI渲染線程,JS主線程,GUI事件觸發線程,http請求線程。JS作為腳本語言,它的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很復雜的同步問題。(這里這些問題我們不做研究)但是單線程的語言,有一個很致命的確定。如果說一個腳本語言在執行時,其中某一塊的功能在執行時耗費了大量的時間,那么就會造成阻塞。這樣的項目,用戶體驗是非常差的,所以這種現象在項目的開發過程中是不允許存在的。其實JS為我們提供了一個Worker的類,它的作用就是為了解決這種阻塞的現象。當我們使用這個類的時候,它就會向瀏覽器申請一個新的線程。這個線程就用來單獨執行一個js文件。
? var worker?=?new Worker(js文件路徑);
那么這個語句就會申請一個線程用來執行這個js文件。這樣也就實現了js的多線程。
53、 閉包的使用場景 ?
一個函數被當作值返回時,也就相當于返回了一個通道,這個通道可以訪問這個函數詞法作用域中的變量,即函數所需要的數據結構保存了下來,數據結構中的值在外層函數執行時創建,外層函數執行完畢時理因銷毀,但由于內部函數作為值返回出去,這些值得以保存下來。而且無法直接訪問,必須通過返回的函數。這也就是私有性。本來執行過程和詞法作用域是封閉的,這種返回的函數就好比是一個蟲洞,開了掛。閉包的形成很簡單,在執行過程完畢后,返回函數,或者將函數得以保留下來,即形成閉包。
-
防抖:
function debounce(fn, interval) {
let timer = null; // 定時器
return function() {
// 清除上一次的定時器
clearTimeout(timer);
// 拿到當前的函數作用域
let _this = this;
// 拿到當前函數的參數數組
let args = Array.prototype.slice.call(arguments, 0);
// 開啟倒計時定時器
timer = setTimeout(function() {
// 通過apply傳遞當前函數this,以及參數
fn.apply(_this, args);
// 默認300ms執行
}, interval || 300)
}
}
-
節流:
function throttle(fn, interval) {
let timer = null; // 定時器
let firstTime = true; // 判斷是否是第一次執行
// 利用閉包
return function() {
// 拿到函數的參數數組
let args = Array.prototype.slice.call(arguments, 0);
// 拿到當前的函數作用域
let _this = this;
// 如果是第一次執行的話,需要立即執行該函數
if(firstTime) {
// 通過apply,綁定當前函數的作用域以及傳遞參數
fn.apply(_this, args);
// 修改標識為null,釋放內存
firstTime = null;
}
// 如果當前有正在等待執行的函數則直接返回
if(timer) return;
// 開啟一個倒計時定時器
timer = setTimeout(function() {
// 通過apply,綁定當前函數的作用域以及傳遞參數
fn.apply(_this, args);
// 清除之前的定時器
timer = null;
// 默認300ms執行一次
}, interval || 300)
}
}
-
迭代器:
var arr =['aa','bb','cc'];
function incre(arr){
var i=0;
return function(){
//這個函數每次被執行都返回數組arr中 i下標對應的元素
return arr[i++] || '數組值已經遍歷完';
}
}
var next = incre(arr);
console.log(next());//aa
console.log(next());//bb
console.log(next());//cc
console.log(next());//數組值已經遍歷完
-
緩存:
var fn=(function(){
var cache={};//緩存對象
var calc=function(arr){//計算函數
var sum=0;
//求和
for(var i=0;i<arr.length;i++){
sum+=arr[i];
}
return sum;
}
return function(){
var args = Array.prototype.slice.call(arguments,0);//arguments轉換成數組
var key=args.join(",");//將args用逗號連接成字符串
var result , tSum = cache[key];
if(tSum){//如果緩存有
console.log('從緩存中取:',cache)//打印方便查看
result = tSum;
}else{
//重新計算,并存入緩存同時賦值給result
result = cache[key]=calc(args);
console.log('存入緩存:',cache)//打印方便查看
}
return result;
}
})();
fn(1,2,3,4,5);
fn(1,2,3,4,5);
fn(1,2,3,4,5,6);
fn(1,2,3,4,5,8);
fn(1,2,3,4,5,6);
-
getter和setter:
function fn(){
var name='hello'
setName=function(n){
name = n;
}
getName=function(){
return name;
}
//將setName,getName作為對象的屬性返回
return {
setName:setName,
getName:getName
}
}
var fn1 = fn();//返回對象,屬性setName和getName是兩個函數
console.log(fn1.getName());//getter
fn1.setName('world');//setter修改閉包里面的name
console.log(fn1.getName());//getter
-
柯里化:
function curryingCheck(reg) {
return function(txt) {
return reg.test(txt)
}
}
var hasNumber = curryingCheck(/\d+/g)
var hasLetter = curryingCheck(/[a-z]+/g)
hasNumber('test1') // true
hasNumber('testtest') // false
hasLetter('21212') // false
-
循環中綁定事件或執行異步代碼
var p1 = "ss";
var p2 = "jj";
function testSetTime(para1,para2){
return (function(){
console.log(para1 + "-" + para2);
})
}
var test = testSetTime(p1, p2);
setTimeout(test, 1000);
setTimeout(function(){
console.log(p1 + "-" + p2)
},1000)
-
單例模式:
var Singleton = (function () {
var instance;
function createInstance() {
return new Object("I am the instance");
}
return {
getInstance: function () {
if (!instance) {
instance = createInstance();
}
return instance;
}
};
})();
54、 常見的兼容問題有哪些 ?
-
獲取標簽節點:
document.getElementsByClassName('類名')在低版本
ie
中不兼容。解決方法是使用其他方式獲取:
document.getElementById('id名')
document.getElementsByTagName('標簽名')
document.getElementsByName('name屬性值')
document.querySelector('css選擇器')
document.querySelectorAll('css選擇器')
獲取卷去的高度
// 當有文檔聲明的時候
document.documentElement.scrollTop
document.documentElement.srollLeft
// 沒有文檔聲明的時候
document.body.scrollTop
document.body.scrollLeft
?解決辦法使用兼容寫法:
???????
// 獲取
var t = document.documentElement.scrollTop || document.body.scrollTop
var l = document.documentElement.srollLeft || document.body.scrollLeft
// 設置
document.documentElement.scrollTop = document.body.scrollTop = 數值
document.documentElement.srollLeft = document.body.scrollLeft = 數值
-
獲取樣式
// W3C標準瀏覽器
window.getComputedStyle(元素)
// 低版本IE中
元素.currentStyle
-
使用函數封裝的方式兼容:
function getStyle(ele,attr){
if(window.getComputedStyle){
return getComputedStyle(ele)[attr]
}else{
return ele.currentStyle[attr]
}
}
-
事件偵聽器
// W3C瀏覽器
ele.addEventListener(事件類型,函數)
// 低版本Ie
ele.attachEvent('on事件類型',函數)
-
使用函數封裝的方式解決:
function bindEvent(ele,type,handler){
if(ele.addEventListener){
ele.addEventListener(type,handler)
}else if(ele.attachEvent){
ele.attachEvent('on'+type,handler)
}else{
ele['on'+type] = handler
}
}
-
事件解綁
// W3C瀏覽器
ele.removeEventListener(事件類型,函數)
// 低版本Ie
ele.detachEvent('on事件類型',函數)
-
使用函數封裝的方式解決:
function unBind(ele,type,handler){
if(ele.removeEventListener){
ele.removeEventListener(type,handler)
}else if(ele.detachEvent){
ele.detachEvent('on'+type,handler)
}else{
ele['on'+type] = null
}
}
-
事件對象的獲取
// W3C瀏覽器
元素.on事件類型 = function(e){}
元素.addEventListener(事件類型,fn)
function fn(e){
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event }
元素.addEventListener(事件類型,fn)
function fn(){
window.event
}
-
使用短路運算符解決:
元素.on事件類型 = function(e){
var e = e || window.event
}
元素.addEventListener(事件類型,fn)
function fn(e){
var e = e || window.event
}
-
阻止默認行為
// W3C瀏覽器
元素.on事件類型 = function(e){
e.preventDefault()
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event.returnValue = false }
-
通過封裝函數解決
元素.on事件類型 = function(e){
var e = e || window.event
e.preventDefault?e.preventDefault():e.returnValue=false
}
-
阻止事件冒泡
// W3C瀏覽器
元素.on事件類型 = function(e){
e.stopPropagation()
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event.cancelBubble = true }
-
通過函數封裝解決:
元素.on事件類型 = function(e){
var e = e || window.event
e.stopPropagation?e.stopPropagation():e.cancelBubble=true
}
-
獲取精準的目標元素
// W3C瀏覽器
元素.on事件類型 = function(e){
e.target
}
// 在低版本IE中
元素.on事件類型 = function(){ window.event.srcElement }
-
通過短路運算符解決:
元素.on事件類型 = function(e){
var e = e || window.event
var target = e.target || e.srcElement;
}
-
獲取鍵盤碼
// W3C瀏覽器
元素.on事件類型 = function(e){
e.keyCode
}
// 在低版本火狐中
元素.on事件類型 = function(e){
e.which
}
-
通過短路運算符解決:
元素.on事件類型 = function(e){
var e = e || window.event
var keycode = e.keyCode || e.which;
}
55、 在 JS 中如何阻止事件冒泡 ?
使用事件對象阻止事件冒泡,以前的w3c瀏覽器中,使用事件對象的方法阻止:
? 事件對象.stopPropagation()
在ie低版本瀏覽器中,使用事件對象的屬性阻止:
? 事件對象.cancelBubble?=?true
現在的w3c瀏覽器也支持ie低版本瀏覽器中的寫法,所以以前在阻止事件冒泡的時候,需要考慮兼容寫法,現在就不需要了,直接用ie低版本瀏覽器中的寫法即可。
56、兩個數組 var A = [1, 5, 6]; var B = [2, 6, 7],實現一個方法,找出僅存在于A 或者 僅 存在于B中的所有數字。
???????
function getDiff(arr, brr){
// 僅存在于arr中的內容
var onlyArr = arr.filter(item => !brr.some(v => item === v))
// 僅存在于brr中的內容
var onlyBrr = brr.filter(v => !arr.some(item => v === item))
// 需要哪個就返回哪個,或者一起返回
return {
"僅存在于arr中的內容": onlyArr,
"僅存在于brr中的內容": onlyBrr
}
}
57、 你了解構造函數嗎 ? class 是什么 ? 兩者有什么區別 ?
在es5中構造函數其實就是在定義一個類,可以實例化對象,es6中class其實是構造函數的語法糖。但還是有點區別的:
-
在class內部和class的方法內部,默認使用嚴格模式
-
class類不存在預解析,也就是不能先調用class生成實例,再定義class類,但是構造函數可以。
-
class中定義的方法默認不能被枚舉,也就是不能被遍歷。
-
class必須使用new執行,但是構造函數沒有new也可以執行。
-
class中的所有方法都沒有原型,也就不能被new
-
class中繼承可以繼承靜態方法,但是構造函數的繼承不能。
58、是否存在a的值(a==0 && a==1)為true 的情況 ?
???????
var value = -1
Object.defineProperty(window,'a',{
get(){
return value+=1;
}
})
if(a===0&&a===1){ // true
console.log('success')
}
59、for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, 1000); } 要求:輸出0,1,2,3,4
首先這個面試題考察的是對于js中異步代碼以及作用域的理解:js中常見的異步代碼包括定時器和ajax。js執行代碼的流程是碰到同步代碼就執行,碰到異步就交給瀏覽器的webAPI處理,當webAPI中的異步該執行時,webAPI會將需要執行的回調函數放在任務隊列中,等候執行,所以,js中所有的異步代碼總會在所有同步代碼執行結束后,再執行任務隊列中的代碼。在這個問題中,循環是同步代碼,定時器是異步代碼,所以整個循環都執行結束以后才會執行定時器代碼。for循環中使用var定義的變量是全局變量,定時器回調函數中輸出變量的時候,根據作用域規則,先在當前作用域中變量i的定義表達式,如果沒有找到,就去上一級作用域中找,此時,在局部作用域中沒有找到,去上級作用域中,也就是全局找到了,全局中的i,因為循環已經執行結束了,所以i的值是5。最終,會輸出5個5。其次考察的是對于類似問題的解決方式,間接性判斷你是否有過類似情況的開發:這個問題的解決思路就是讓回調函數中輸出i的時候,不要去全局中找i,因為全局的i在循環執行結束后已經變成5了,根據這個思路,有2種解決辦法:
-
在異步代碼外面嵌套一層函數作用域
for(var i = 0;i < 5; i++){
(function(i) {
setTimeout(function() {
console.log(i)
}, 1000)
})(i)
}
原理是自調用函數會產生作用域,循環5次就會產生5個作用域,每個作用域代碼在執行的時候都有形參i傳遞。所以每個作用域中的i都是不同的,分別是:0 1 2 3 4。當作用域中的異步代碼執行的時候,自己作用域中沒有i變量的定義,然后上級作用域就是自調用函數的作用域,找到了單獨的i。最終可以輸出:0 1 2 3 4
-
將循環代碼中的var換成es6的let
for(let i = 0;i < 5; i++){
setTimeout(function() {
console.log(i)
}, 1000)
}
es6的let自帶塊級作用域,原理跟第一種解決思路是一樣的,轉成es5后,代碼是一樣的。
60、實現一個 add 方法 使計算結果能夠滿足如下預期:?- add(1)(2)(3)() = 6 - add(1,2,3)(4)() = 10
??????????????
function add (...args) {
if(args.length === 3){
return -(args[0] * args[1] * 2 + args[2] * 2)
}else{
return -args[args.length-1]
}
}
function currying (fn) {
let args = []
return function _c (...newArgs) {
if (newArgs.length) {
args = [
...args,
...newArgs
]
return _c
} else {
return fn.apply(this, args)
}
}
}
let addCurry = currying(add)
var a = addCurry(1)(2)(3)()
console.log(-a); // 10
var b = addCurry(1,2,3)(4)()
console.log(6 - b); // 10