把一些面試題目分享一下,偏基礎底層。僅供參考,如有錯誤或者改進,可以在評論或者部落格獲取聯絡方式,謝謝指正。
HTML+CSS篇
.aaa{
font-size:20px;
background:red;
}
.bbb{
font-size:20px;
background:yellow;
float:left;
}
.ccc{
font-size:40px;
background:blue;
float:left;
}
.ddd{
font-size:20px;
background:orange;
}
<body>
<div class="aaa">
aaa
<div class="bbb">bbb</div>
<div class="ccc">ccc</div>
<div class="ddd">ddd</div>
</div>
</body>
複製程式碼
畫出HTML圖
aaa
被浮動影響所覆蓋,但是bbb
屬於aaa
的子元素,只會產生float文字圍繞的效果,所以bbb
和ccc
會向左靠,aaa
和ddd
並列的原因是因為ccc
的font-size:40px
,佔據了第二行ddd
的位置,並且有浮動屬性,不會讓後面的文字往前靠,所以才會產生這種效果。
頁面元素垂直水平居中有哪幾種方式
// 絕對定位
.box{
width: 200px;
height: 200px;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
// css3+絕對定位
.box{
position:absolute;
top:50%;
left:50%;
transform: translate(-50%,-50%);
}
// flex
.box{
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
}
// table
.box{
display:table-cell;
text-align:center;
vertical-align:middle;
}
複製程式碼
平常用到的就這些了
CSS優化提高效能的方法有哪些
這個有很多優化的地方,比如資源載入方面,渲染效能,解析及迴流方面都有所涉及, 移除空的元素選擇器
- css資源託管CDN,避免併發載入上限,依賴載入,不讓頁面載入延後資源的時候進行重繪。
- js檔案內儘量少修改首屏css樣式,引發重繪與迴流。儘量去建立圖層避免多次迴流,減少迴流的次數。
- 用translate替代top改變
- 用opacity替代visibility
- 不濫用浮動、web字型
- 不濫用無效屬性,比如使用了display:inline;就不要再設定width、height等屬性
- 值為0時不需要任何單位
- 儘量少用標籤選擇器,因為瀏覽器是從最後的選擇開始往前做匹配的
- 使用css特性樣式的時候加上瀏覽器字首
- 不要一條一條地修改 DOM 的樣式,預先定義好 class,然後修改 DOM 的 className 把 DOM 隱藏後修改,比如:先把 DOM 給 display:none (有一次 Reflow),然後你修改100次,然後再把它顯示出來
- 不要把 DOM 結點的屬性值放在一個迴圈裡當成迴圈裡的變數
- 儘量不要使用 table 佈局,可能很小的一個小改動會造成整個 table 的重新佈局
- 新建圖層實現動畫效果
- 啟用 GPU 硬體加速
max-height:0px 和 height:200px !important; 誰生效
min和max無視!important; 取前者
JS篇
怎麼去手寫實現一個new方法 以及new到底做了什麼
這個問題有一半公司都問到了,可能是考基礎和作用域鏈吧
function _new(fun) {
return function() {
let obj = {
__proto__: fun.prototype
}
fun.apply(obj, arguments)
return obj
}
}
複製程式碼
new運算子怎麼從構造器中得到一個物件
在《JavaScript設計模式和開發實踐一書》
中,我們可以這麼去理解new運算的過程
var objectFactory = function(){
var obj = new Object();//建立一個Object
var Constructor = [].shift.call(arguments);//獲取指定函式物件
obj.__proto__ = Constructor.prototype;//Object.prototype是個錯誤的指向,所以改變指向的原型
var ret = Constructor.apply(obj, arguments);//借用外部傳入的構造器給obj設定屬性
return typeof ret === 'object'? ret : obj;//相容,因為某些瀏覽器沒有暴露__proto__屬性。
}
//測試
function Person(name){
this.name = name;
}
Person.prototype.getName = function(){
return this.name;
}
var a = objectFactory(Person,'seven');
console.log(a.name);//seven
console.log(a.getName());//seven
console.log(Object.getPrototypeOf( a ) === Person.prototype);//true
複製程式碼
以下程式碼執行結果
var myName1 = function(){
this.name = 'seven';
return {
name:'juejin'
}
}
var myName2 = function(){
this.name = 'seven';
return 'juejin'
}
var myName3 = function(){
return 'juejin'
}
var obj = new myName1();
console.log(obj.name);//juejin
var str = new myName2();
console.log(str.name);//seven
var und = new myName3();
console.log(und.name);//undefined
複製程式碼
題不難,關鍵在new需要注意的一個問題:
如果構造器顯示的返回了一個object型別的物件,那麼此次運算結果會最終返回這個物件,而不是我們之前期待的this
當引用一個變數的時候,引擎做了那些事情
例如:
var obj = { name: 'seven'};
var A = function(){};
A.prototype = obj();
var B = function(){};
B.prototype = new A();
var b = new B();
console.log(b.name);//seven
複製程式碼
- 首先引擎根據LHS查詢去getter遍歷
b
的所有屬性,沒有找到 - 查詢
name
屬性的請求被委託給了物件b
的構造器的原型,b.__proto__
被指向B.prototype
- 而
B.prototype
又被指向new A()
建立出來的物件,在該物件中仍然沒有找到 - 然後再往上委託給
A.prototype
,而它被設定成obj
- 在
obj
中getter到name
屬性,返回該值 - 題外:如果最後obj也沒有找到name屬性,請求將會傳遞給obj的構造器原型:Object.prototype,而null作為物件原型鏈的終點。顯然沒有,返回undefined
怎麼手寫實現 Object.create()方法
Object.create方法克隆一個物件,把__proto__
改變指向,prototype
被new
之後才會產生__proto__
所以得出方法:
function create(target){
var F = function(){};
F.prototype = target;
return new F()
}
var obj = {name : 'seven'};
var copy = create(obj);
console.log(copy.name); //seven
複製程式碼
手動實現bind
簡化版
Function.prototype.myBind = function(context){
var _this = this;//儲存指向
return function(){
return _this.apply(context,arguments);//改變this執行物件
}
}
複製程式碼
當然,為了兩次引數結合,還需要複雜些
Function.prototype.myBind = function(){
var _this = this;
var context = [].shift.call(arguments);//獲取目標物件
var args = [].slice.call(arguments);//獲取剩餘引數
return function(){
return _this.apply(context,[].concat.call(args,[].slice.call(arguments)));
//context改變指向,而後面是為了整合兩次傳入的引數
}
}
var obj = { name: 'seven' }
var fun = function(a,b,c){
console.log(this.name)
console.log(a,b,c)
}.bind(obj,1,2)
fun(3);
//seven
//1,2,3
複製程式碼
JavaScript中的原型、原型鏈是什麼?
每一個函式都有prototype屬性,它指向一個物件,即原型物件。其他物件可以通過它實現屬性繼承,而且任何一個物件都可以成為原型。
每個物件都有__proto__
屬性,它是實現原型鏈的的關鍵,而prototype則是原型鏈的組成,任何被例項出的屬性都會通過它去呼叫父類原型的方法,父類如果沒有方法就會再去通過父類的父類的prototype
裡找,直到Object.prototype
,null
作為物件原型鏈的終點,沒有找到,返回undefined
。
function fn () {
this.name = 'seven'
}
fn.prototype.getName = function(){
console.log(this.name)
}
var fn2 = new fn();
fn2.getName();//'seven';
fn2.__proto__ === fn.prototype;// true
複製程式碼
作用域和執行上下文的區別
作用域是函式定義時確定的,函式內的變數根據變數去一層一層網上查詢。 上下文是函式呼叫時確定的,主要是this的值,簡單來說,誰呼叫它就指向誰。
為什麼typeof null 是object
因為在二進位制裡,物件的前三位000,而null的二進位制表示全0,自然前三位也是0,所以執行typeof時會返回“object”,這是ES5遺留的一個bug。
閉包是什麼,作用和缺陷
由於垃圾回收機制,所有區域性作用域的變數在退出函式後,這些區域性變數沒有標示失去引用,都將被自動回收銷燬。
閉包能夠訪問其他函式內的變數,也可以封裝變數,把一些不需要暴露在全域性的變數封裝成“私有變數”。
缺陷其實幾乎沒有,很多人以為會造成記憶體洩漏,那只是使用不當才會造成的。因為使用閉包的同時容易形成迴圈引用,如果閉包的作用域中儲存著一些DOM節點,才有可能造成記憶體洩露,由於IE的BOM和DOM中的物件是使用C++以COM物件的方式實現的,而COM物件的垃圾回收機制採用的是引用計數策略,如果兩個物件間造成了迴圈引用,那這兩個物件都無法被回收。就得手動設定為null
,然後下次垃圾收集器會刪除它們。
程式碼輸出結果
function fn1(){
for(var i=0;i<4;i++){
var tc = setTimeout(function(i){
console.log(i)
clearTimeout(tc)
},10,i)
}
}
fn1();
複製程式碼
看到這題的時候,第一時間在想clearTimeout(tc)
是什麼時候去執行的。如果沒有這句的時候。輸出肯定是0,1,2,3
,但是問題要從var tc
來看,tc
被重複賦值了,而clearTimeout
在非同步函式裡,只清除了申明的最後一次。
所以最後輸出0,1,2
function fn2(){
for(var i=0;i<4;i++){
var tc = setInterval(function(i,tc){
console.log(i)
clearTimeout(tc)
},10,i,tc)
}
}
fn2()
複製程式碼
這個跟上一題也有相似之處,不過tc被傳進去了,i=0
的時候,設定了定時器a
執行,當i=1
的時候定義了定時器b
,a
已經執行了一次,再所以會把上一次的值給清除掉,導致每次都執行了上一次的定時器才clearInterval
關閉掉,最後一次是因為i=4
的時候才能關掉i=3
的定時器,但是因為條件限制所以沒關閉掉。
所以fn2會輸出0,1,2,3(無限迴圈)......
科裡化
有如下一個函式
var fn = function(a,b,c){
return a+b+c;
}
複製程式碼
擬寫一個函式滿足curry(fn)(1)(2)(3) 返回值為6;
function curry(fn) {
if(typeof fn !== 'function') throw new Error("首個引數必須為函式");
var len = fn.length;
var arg = [].prototype.slice.call(arguments, 1);//儲存引數
return function() {
arg = arg.concat([].prototype.slice.call(arguments));//引數合併
if(arg.length < len) { //不滿足a,b,c 3個形參的長度,繼續呼叫
return arguments.callee;
}else{ //滿足條件,執行語句
return fn.apply(null,arg);
}
}
}
curry(fn)(1)(2)(3);//6
複製程式碼
簡單一點的例子 科裡化函式的特點引數複用、提前返回、延遲計算/執行。需要理解閉包的概念
var adder = function () {
var _value = 0;
return function () {
if (arguments.length === 0) { // 假如沒有引數返回,就返回值,終止迴圈呼叫
return _value
}
_value+=parseFloat([].slice.call(arguments))
return arguments.callee;// 繼續呼叫
}
};
var arr = adder();
arr(1)(2)(3)(); //6
複製程式碼
HTML5的離線儲存怎麼使用,工作原理能不能介紹下
在html屬性里加入
<html manifest = "cache.manifest">
複製程式碼
配置檔案
CACHE MANIFEST //必須寫
版本號
#v1.0.0
#需要快取的檔案
CACHE:
/reset.css
/app.js
#不需要快取的檔案
NETWORK:
/login.html
/data/*
#無法訪問頁面
FALLBACK:
/404.html
複製程式碼
基於新建的cache.manifest檔案的快取機制,通過這個檔案上的解析清單離線儲存資源。當網路在處於離線狀態時瀏覽器自動取出離線儲存的資料進行頁面解析後展示。
如何判斷一個物件是不是函式
var fn = function () {}
// typeof JQ原始碼
typeof fn === 'function' && typeof obj.nodeType !== "number";
// toString
Object.propotype.toString.call(fn) === "[object Function]"
// 通過原型
fn instanceof Function
// 通過建構函式
fn.constructor === Function
複製程式碼
列幾條JavaScript的基本規範
- 使用全等===/全不等!==來比對資料型別
- 當命名物件、函式和例項時使用駝峰命名規則
- 不要使用全域性變數申明,必須加var,避免汙染全域性名稱空間
- 當命名物件、函式和例項時使用駝峰命名規則
還一堆....
pop()-push()-shift()-unshift()功能,forEach()-map()-reduce()有什麼區別
pop(): 擷取陣列的最後一個元素,返回該值
push(): 給陣列的最後新增一個元素,返回新長度
shift(): 擷取陣列的首個元素,返回
unshift():給陣列的首個位置新增一個元素,返回新長度
reduce(): 不改變原陣列,接受一個函式作為累加器,陣列長度為0時不執行,適合計算大批資料
map(): 不影響原陣列,返回新的陣列
forEach(): 改變原陣列做處理
複製程式碼
["1","2","3"].map(parseInt) 輸出什麼;
因為map會有2個引數,第一個是item物件,第二個是索引,而parseInt誤將第二個引數認為是解析的基數,導致輸出[1,NaN,Nan]
["1","2","3"].map(parseInt)
等同於
["1","2","3"].map(function(item,index){
return parseInt(item,index)
})
複製程式碼
瀏覽器和其他考點
一個頁面,從輸入URL到頁面載入完成發生了什麼(越詳細越好)
我回答的不夠仔細,以下是針對收集的資料整合之後的
- 輸入URL,瀏覽器開啟一個執行緒來處理這個請求,對URL進行協議判斷,如果是http就按照web方式處理
- 在瀏覽器快取、系統快取、路由器快取裡查詢,有就直接返回。
- 通過DNS解析拿到真實的IP地址。
- 向真實的IP地址伺服器發起TCP連線,與瀏覽器建立tcp三次握手。
- 進行HTTP協議會話,瀏覽器傳送報頭(請求報頭)
- 伺服器處理請求將結果(資原始檔)返回至瀏覽器
- 瀏覽器下載html文件,同時設定快取;
- 瀏覽器啟用HTML Parse解析器對 HTML 檔案進行解析, 通過詞法分析的過程,將內容分析成不同的token,然後根據HTML的文件,從上到下依次nextToken進行解析
token
,並獲取下一個token
的位置,所以我們的DOM tree
是通過詞法分析token
一步一步新增的。而類似於link
,script src
引用web資源的地址由瀏覽器傳送請求css和js相關資源,將請求回來的js資源利用V8核心引擎執行js程式碼。請求回來的CSS資源則會生成相應的CSSOM
,前面的DOM tree
生成完畢後瀏覽器並不會直接渲染出來,而是會等待CSSOM(css tree)
生成後進行合併,再渲染出來。形成繪製樹Render Tree
。 - 最後計算每個結點在頁面中的位置,這一個過程稱為
layout
其過程是在一個連續的二維平面上進行的,接下來將這些結果柵格化,對映到螢幕的離散二維平面上,這一過程稱為 paint; 現代瀏覽器為提升效能,將頁面劃分多個layer
,各自進行paint
然後組合成一個頁面呈現在使用者眼前
執行緒和程式的區別
執行緒是最小的執行單元,程式是最小的資源管理單元。 一個執行緒只能屬於一個程式,而一個程式可以有多個執行緒,至少有一個執行緒
請描述你對HTTP協議的理解
HTTP是一個屬於面向應用層的超文字傳輸協議,基於TCP/IP通訊協議來傳遞資料,制定伺服器與客戶端之間資料傳輸的通訊規則。 HTTP有1.0和1.1、2.0版本,保持著無狀態、無連線的協議。 HTTP請求由三部分組成:請求行、訊息報頭、請求正文。 HTTP響應也是由三個部分組成,分別是:狀態行、訊息報頭、響應正文。 剩餘的我也不知道該說什麼了
請描述你對跨域的理解(同源限制)是什麼,解決辦法
瀏覽器的同源策略導致了跨域用於隔離潛在惡意檔案的重要安全機制,限制必須協議、域名、埠一致
- 既然前端有限制,就把請求代理轉移到後端。反向代理,需要服務端做大量修改,不建議;
- 由於瀏覽器不會阻止動態文字載入,所以JSONP方式應運而生
- CORS,雖然比代理簡單一些,但是不會自動處理響應頭(cookie等...),只需要服務端設定
Access-Control-Allow-Origin
和Access-Control-Allow-Methods
等屬性進行設定。 - iframe 巢狀通訊,通過postMessage和message事件通訊,用的較少
- WebSocket,也需要伺服器做相關設定,像Node的
socket.io
請說明sessionStorage、localStorage、cookie的區別
- local:本地儲存,視窗通用,生命週期是永久,除非使用者手動清除。
- session:會話儲存,限定本次視窗,關閉後消失。
- 本地儲存Storage的大小限制5MB,不參與通訊,遵守同源限制。
- cookie:每個domain最多隻能有20條cookie,每個cookie長度不能超過4KB。否則會被截掉。每次請求都會自動帶上。
什麼是cookie隔離
cookie在訪問對應域名下的資源時都會通過HTTP請求傳送到伺服器。但是訪問靜態資源根本不需要cookie,否則非常浪費流量,可以使用不同的domain(域名)來儲存資源。因為cookie有跨域限制不能跨域提交請求,使用其他域名的請求頭中就不會帶有cookie資料,降低請求頭的大小和請求時間,也減少了Web Server對cookie的處理分析環節,提高了webserver的http請求的解析速度,從而提高響應速度。
其他
問的最多的就是開發部署流程還有框架實現原理,筆者主Vue,建議去看vue原始碼實現,有render、update、patch、vnode等相關的解讀,vue3.0明年就要見面了。開發部署流程每個公司有其自己的規範,這裡就不提了。
一些細節的東西可能不懂或者有描述不對的地方,歡迎指正。