記錄近期換工作時遇到的面試題和麵試題答案
css 部分
盒模型
問題:說一下 css 的盒模型
盒模型分為標準模型和怪異盒模型(IE 盒模型)
標準盒模型:盒模型的寬高只是內容(content)的寬高
怪異盒模型:盒模型的寬高是內容(content)+填充(padding)+邊框(border)的總寬高
問題:css 如何設定兩種模型
/* 標準模型 */
box-sizing:content-box;
/*IE模型*/
box-sizing:border-box;
複製程式碼
問題:有沒有遇到過邊距重疊,怎麼解決 邊距重疊問題如下圖所示
利用 BFC 去解決,下方詳細說明 BFC
參考程式碼:利用 BFC 的特性,把另一個盒子放入另一個獨立的 BFC 中,從而使得兩個盒子之間互相不受影響
<section class="top">
<h1>上</h1>
margin-bottom:30px;
</section>
<div style="overflow: hidden;">
<section class="bottom">
<h1>下</h1>
margin-top:50px;
</section>
</div>
複製程式碼
BFC
問題:說一下 BFC
-
什麼是 BFC
BFC(Block Formatting Context)格式化上下文,是 Web 頁面中盒模型佈局的 CSS 渲染模式,指一個獨立的渲染區域或者說是一個隔離的獨立容器。
-
形成 BFC 的條件
- 浮動元素,float 除 none 以外的值
- 定位元素,position(absolute,fixed)
- display 為以下其中之一的值 inline-block,table-cell,table-caption
- overflow 除了 visible 以外的值(hidden,auto,scroll)
-
BFC 的特性
- 內部的 Box 會在垂直方向上一個接一個的放置。
- 垂直方向上的距離由 margin 決定
- bfc 的區域不會與 float 的元素區域重疊。
- 計算 bfc 的高度時,浮動元素也參與計算
- bfc 就是頁面上的一個獨立容器,容器裡面的子元素不會影響外面元素。
rem 原理
rem 佈局的本質是等比縮放,一般是基於寬度,假設將螢幕寬度分為 100 份,每份寬度是 1rem,1rem 的寬度是螢幕寬度/100,,然後子元素設定 rem 單位的屬性
通過改變 html 元素的字型大小,就可以設定子元素的實際大小。
比 rem 更好的方案(缺點相容不好)
vw(1vw 是視口寬度的 1%,100vw 就是視口寬度),vh(100vh 就是視口高度)
實現三欄佈局
(兩側定寬,中間自適應)
這裡寫出五種實現方式:
- flex 方式實現
/* css */
.box {
display: flex;
justify-content: center;
height: 200px;
}
.left {
width: 200px;
background-color: red;
height: 100%;
}
.content {
background-color: yellow;
flex: 1;
}
.right {
width: 200px;
background-color: green;
}
/* html */
<div class="box">
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</div>
複製程式碼
- 浮動方式,此方式 content 必須放在最下邊
/* css */
.box {
height: 200px;
}
.left {
width: 200px;
background-color: red;
float: left;
height: 100%;
}
.content {
background-color: yellow;
height: 100%;
}
.right {
width: 200px;
background-color: green;
float: right;
height: 100%;
}
/* html */
<div class="box">
<div class="left"></div>
<div class="right"></div>
<div class="content"></div>
</div>
複製程式碼
- 絕對定位方式實現
/* css */
.box {
position: relative;
height: 200px;
}
.left {
width: 200px;
background-color: red;
left: 0;
height: 100%;
position: absolute;
}
.content {
background-color: yellow;
left: 200px;
right: 200px;
height: 100%;
position: absolute;
}
.right {
width: 200px;
background-color: green;
right: 0;
height: 100%;
position: absolute;
}
/* html */
<div class="box">
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</div>
複製程式碼
- 表格佈局實現方式
/* css */
.box {
display: table;
height: 200px;
}
.left {
width: 200px;
background-color: red;
height: 100%;
display: table-cell;
}
.content {
background-color: yellow;
height: 100%;
display: table-cell;
}
.right {
width: 200px;
background-color: green;
height: 100%;
display: table-cell;
}
/* html */
<div class="box">
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</div>
複製程式碼
- grid 網格佈局
/* css */
.box {
display: grid;
grid-template-columns: 200px auto 200px;
grid-template-rows: 200px;
}
.left {
background-color: red;
}
.content {
background-color: yellow;
}
.right {
background-color: green;
}
/* html */
<div class="box">
<div class="left"></div>
<div class="content"></div>
<div class="right"></div>
</div>
複製程式碼
使一個盒子水平垂直居中
這裡寫出五種實現方式:
- 寬度和高度已知的
/* css */
#box{
width: 400px;
height: 200px;
position: relative;
background: red;
}
#box1{
width: 200px;
height: 100px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -100px;
margin-top: -50px;
background: green;
}
/* html */
<div id="box">
<div id="box1">
</div>
</div>
複製程式碼
- 寬度和高度未知
/* css */
#box{
width: 800px;
height: 400px;
position: relative;
background: red;
}
#box1{
width: 100px;
height: 50px;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
background: green;
}
/* html */
<div id="box">
<div id="box1">
</div>
</div>
複製程式碼
- flex 佈局
/* css */
#box{
width: 400px;
height: 200px;
background: #f99;
display: flex;
justify-content: center;//實現水平居中
align-items: center;//實現垂直居中
}
#box1{
width: 200px;
height: 100px;
background: green;
}
/* html */
<div id="box">
<div id="box1">
</div>
</div>
複製程式碼
- 平移 定位+transform
/* css */
#box{
width: 400px;
height: 200px;
background: red;
position: relative;
}
#box1{
width: 200px;
height: 100px;
background: #9ff;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/* html */
<div id="box">
<div id="box1">
</div>
</div>
複製程式碼
- table-cell 佈局
/* css */
#box{
display: table-cell;
vertical-align: middle
}
#box1{
margin: 0 auto;
}
/* html */
<div id="box">
<div id="box1">
</div>
</div>
複製程式碼
js 部分
什麼是閉包,閉包的用途
能夠讀取其他函式內部變數的函式,或簡單理解為定義在一個函式內部的函式,內部函式持有外部函式內變數的引用。
閉包的用途:
- 讀取其他函式內部的變數
- 讓變數的值始終儲存在記憶體中
- JavaScript中閉包的應用都有關鍵詞return,引用 JavaScript 祕密花園中的一段話就是因為:
閉包是 JavaScript 一個非常重要的特性,這意味著當前作用域總是能夠訪問外部作用域中的變數。 因為 函式 是 JavaScript 中唯一擁有自身作用域的結構,因此閉包的建立依賴於函式。
call, apply, bind 區別? 怎麼實現 call,apply 方法
相似之處:
- 都是用來改變函式的 this 物件的指向的。
- 第一個引數都是 this 要指向的物件。
- 都可以利用後續引數傳參。 區別:
- call 接受函式傳參方式為:fn.call(this, 1, 2, 3)
- apply 接受函式傳參方式為:fn.apply(this,[1, 2, 3])
- bind 的返回值為一個新的函式,需要再次呼叫: fn.bind(this)(1, 2, 3)
手動實現 call 方法:
Function.prototype.myCall = function(context = window, ...rest) {
context.fn = this; //此處this是指呼叫myCall的function
let result = context.fn(...rest);
//將this指向銷燬
delete context.fn;
return result;
};
複製程式碼
手動實現 apply 方法:
Function.prototype.myCall = function(context = window, params = []) {
context.fn = this; //此處this是指呼叫myCall的function
let result
if (params.length) {
result = context.fn(...params)
}else {
result = context.fn()
}
//將this指向銷燬
delete context.fn;
return result;
};
複製程式碼
手動實現 bind 方法:
Function.prototype.myBind = function(oThis, ...rest) {
let _this = this;
let F = function() {}
// 根據 bind 規定,如果使用 new 運算子構造 bind 的返回函式時,第一個引數繫結的 this 失效
let resFn = function(...parmas) {
return _this.apply(this instanceof resFn ? this : oThis, [
...rest,
...parmas
]);
};
// 繼承原型
if (this.prototype) {
F.prototype = this.prototype;
resFn.prototype = new F;
}
return resFn;
};
複製程式碼
怎麼理解原型和原型鏈
每個函式都有 prototype
每一個物件都有 __proto__
例項的 __proto__
指向建構函式的 prototype
js 引擎會沿著 __proto__
-> ptototype 的順序一直往上方查詢,找到 Object.prototype 為止,Object 為原生底層物件,到這裡就停止了查詢,如果沒有找到,就會報錯或者返回 undefined
js 繼承的幾種方式
常用繼承:組合繼承,寄生組合繼承
組合繼承: 利用 call 繼承父類上的屬性,用子類的原型等於父類例項去繼承父類的方法
缺點:呼叫兩次父類,造成效能浪費
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function() {
console.log(this.name);
};
function Child(name) {
Parent.call(this, name)
}
Child.prototype = new Parent;
let c = new Child("YaoChangTuiQueDuan");
c.say()
複製程式碼
寄生組合繼承:利用 call 繼承父類上的屬性,用一個乾淨的函式的原型去等於父類原型,再用子類的原型等於乾淨函式的例項
function Parent(name) {
this.name = name;
}
Parent.prototype.say = function() {
console.log(this.name);
};
function ExtendMiddle() {}
function Child(name) {
Parent.call(this, name)
}
ExtendMiddle.prototype = Parent.prototype;
Child.prototype = new ExtendMiddle
let c = new Child("YaoChangTuiQueDuan");
c.say()
複製程式碼
eventloop
請參考文章 Eventloop 不可怕,可怕的是遇上 Promise
new 的過程中做了什麼? 手動實現一個 new
- 新生成了一個物件
- 連結到原型
- 繫結 this
- 返回新物件
function create(...rest) {
// 建立一個空的物件
let obj = new Object()
// 獲得建構函式
let Con = rest.shift()
// 連結到原型
obj.__proto__ = Con.prototype
// 繫結 this,執行建構函式
let result = Con.apply(obj, arguments)
// 確保 new 出來的是個物件
return typeof result === 'object' ? result : obj
}
複製程式碼
說一些常用的 es6
- let/const
- 模板字串
- 解構賦值
- 塊級作用域
- Promise
- Class
- 函式預設引數
- 模組化
- 箭頭函式
- Set
- Map
- Array.map
- 等等
Promise 的基本使用和原理,以及簡易版的 Promise 實現
Promise 的幾個特性:
- Promise 捕獲錯誤與 try/catch 相同
- Promise 擁有狀態變化,並且狀態變化不可逆
- Promise 屬於微任務
- Promise 中的.then 回撥是非同步的
- Promsie 中.then 每次返回的都是一個新的 Promise
- Promise 會儲存返回值
Promise 的簡單實現:
class MyPromise {
constructor(fn) {
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
this.state = "PADDING";
this.value = "";
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(value) {
if (this.state === "PADDING") {
this.state = "RESOLVED";
this.value = value;
this.resolvedCallbacks.forEach(cb => cb());
}
}
reject(value) {
if (this.state === "PADDING") {
this.state = "REJECTED";
this.value = value;
this.rejectedCallbacks.forEach(cb => cb());
}
}
then(resolve = function() {}, reject = function() {}) {
if (this.state === "PADDING") {
this.resolvedCallbacks.push(resolve);
this.rejectedCallbacks.push(reject);
}
if (this.state === "RESOLVED") {
resolve(this.value);
}
if (this.state === "REJECTED") {
reject(this.value);
}
}
}
複製程式碼
有沒有使用過 async/await 說一下和 Promise 的區別、聯絡
- 使用了Promise,並沒有和Promise衝突
- 在寫法上完全是同步,沒有任何回撥函式
- await 必須在 async 函式中使用
- 如果 await 後面的方法是一個 Promise,會返回 Promise 的處理結果,等待 Promise 處理成功後在去執行下面的程式碼,如果等待的不是 Promise 物件,則返回該值本身,然後去同步的執行下方的程式碼
- 寫在 await 後面的程式碼,相當於是執行 Promise.resolve()
- 使用 try/catch 來捕獲錯誤
如何實現在Object上使用for...of迭代器
可以在 Object 的 prototype 上掛載一個 Symbol.iterator
方法,該方法返回一個物件,物件中包含 next
方法, next
方法也會返回一個物件,物件中包含 value
和 done
。 value 表示 每次迭代完成的值,done 表示是否迭代完成,為 false 時會繼續迭代,為 true 表示迭代完成,停止迭代
Object.prototype[Symbol.iterator] = function () {
let values = Object.values(this);
let keys = Object.keys(this);
let len = 0;
return {
next() {
return {
value: {
value: values[len],
key: keys[len++]
},
done: len > values.length
}
}
}
}
複製程式碼
CommonJS 和 ES6 中的模組化的兩者區別
- 前者支援動態匯入,也就是 require(${path}/xx.js),後者目前不支援,但是已有提案
- 前者是同步匯入,因為用於服務端,檔案都在本地,同步匯入即使卡住主執行緒影響也不大。而後者是非同步匯入,因為用於瀏覽器,需要下載檔案,如果也採用同步匯入會對渲染有很大影響
- 前者在匯出時都是值拷貝,就算匯出的值變了,匯入的值也不會改變,所以如果想更新值,必須重新匯入一次。但是後者採用實時繫結的方式,匯入匯出的值都指向同一個記憶體地址,所以匯入值會跟隨匯出值變化
- 後者會編譯成 require/exports 來執行的
module.exports 和 exports 有什麼關係
為了方便,Node為每個模組提供一個 exports 變數,指向 module.exports,相當於 exports 是 module.exports 地址的引用
會產生的問題:如果將 exports 新賦值了一個物件,如: exports = {},這個時候,就會打斷與 module.exports 的聯絡,會導致匯出不成功
什麼是防抖和節流?有什麼區別?
- 控制頻繁呼叫事件的呼叫頻率
- 防抖和節流的作用都是防止函式多次呼叫。
- 區別在於,假設一個使用者一直觸發這個函式,且每次觸發函式的間隔小於wait,防抖的情況下只會呼叫一次,而節流的情況會每隔一定時間(引數wait)呼叫函式。
深拷貝用來解決什麼問題?如何實現
如何區分深拷貝與淺拷貝,簡單點來說,就是假設B複製了A,當修改A時,看B是否會發生變化,如果B也跟著變了,說明這是淺拷貝,如果B沒變,那就是深拷貝。
由於 js 中分為兩種變數型別,基本資料型別和引用資料型別,基本資料型別包括,number,string,boolean,null,undefined,symbol。
引用資料型別(Object類)有常規名值對的無序物件 {a:1}
,陣列 [1,2,3]
,以及函式等。
而這兩類資料儲存分別是這樣的:
-
基本型別--名值儲存在棧記憶體中,例如let a=1;
當你 b=a 複製時,棧記憶體會新開闢一個記憶體,例如這樣:
所以當你此時修改 a=2,對 b 並不會造成影響,因為此時的 b 已更換記憶體地址,不受 a 的影響了
-
引用資料型別--名存在棧記憶體中,值存在於堆記憶體中,但是棧記憶體會提供一個引用的地址指向堆記憶體中的值
當 b=a 進行拷貝時,其實複製的是 a 的引用地址,而並非堆裡面的值。
而當我們 a[0]=1 時進行陣列修改時,由於 a 與 b 指向的是同一個地址,所以自然 b 也受了影響,這就是所謂的淺拷貝了。
那,如果我們需要實現深拷貝的效果,就需要在堆記憶體中也開闢一個新的的記憶體空間來存放 b 的值,如圖:
深拷貝的實現方式:
-
json.parse & json.stringify
先將一個物件轉為json物件。然後再解析這個json物件,但此方法有幾個缺點:
- 如果 obj 裡面有時間物件,序列化後,時間將只是字串的形式,而不是時間物件
- 如果 obj 裡有RegExp、Error物件,則序列化的結果將只得到空物件
- 如果 obj 裡有函式,undefined,則序列化的結果會把函式或 undefined 丟失
- 如果 ob j裡有 NaN、Infinity 和 -Infinity,則序列化的結果會變成 null
- 只能序列化物件的可列舉的自有屬性,如果 obj 中的物件是有建構函式生成的,則序列化後,會丟棄物件的 constructor
- 如果物件中存在迴圈引用的情況也無法正確實現深拷貝
-
遞迴方式實現深拷貝(ps:這裡只是簡易版,完整版建議看 lodash 的 cloneDeep 方法原始碼)
原始碼地址:github.com/lodash/loda…
function deepClone(obj) {
let objClone = Array.isArray(obj) ? [] : {};
let toString = Object.prototype.toString;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
//判斷ojb子元素是否為物件,如果是,遞迴複製
if ( toString.call(obj[key]) === "[object Object]" || toString.call(obj[key]) === "[object Array]" ) {
objClone[key] = deepClone(obj[key]);
} else {
//如果不是,簡單複製
objClone[key] = obj[key];
}
}
}
return objClone;
}
複製程式碼
什麼是堆,什麼是棧
棧(stack):棧會自動分配記憶體空間,會自動釋放,存放基本型別,簡單的資料段,佔據固定大小的空間。 堆(heap):動態分配的記憶體,大小不定也不會自動釋放,存放引用型別
為什麼會有棧記憶體和堆記憶體之分?
通常與垃圾回收機制有關。為了使程式執行時佔用的記憶體最小。
當一個方法執行時,每個方法都會建立自己的記憶體棧,在這個方法內定義的變數將會逐個放入這塊棧記憶體裡,隨著方法的執行結束,這個方法的記憶體棧也將自然銷燬了。因此,所有在方法中定義的變數都是放在棧記憶體中的;
當我們在程式中建立一個物件時,這個物件將被儲存到執行時資料區中,以便反覆利用(因為物件的建立成本通常較大),這個執行時資料區就是堆記憶體。堆記憶體中的物件不會隨方法的結束而銷燬,即使方法結束後,這個物件還可能被另一個引用變數所引用(方法的引數傳遞時很常見),則這個物件依然不會被銷燬,只有當一個物件沒有任何引用變數引用它時,系統的垃圾回收機制才會在核實的時候回收它。
插入幾萬個 DOM,如何實現頁面不卡頓?
方案一:
分頁,懶載入,把資料分頁,然後每次接受一定的資料,避免一次性接收太多
複製程式碼
方案二:
setInterval,setTimeout,requestAnimationFrame 分批渲染,讓資料在不同幀內去做渲染
複製程式碼
方案三:
使用 virtual-scroll,虛擬滾動。
這種方式是指根據容器元素的高度以及列表項元素的高度來顯示長列表資料中的某一個部分,而不是去完整地渲染長列表,以提高無限滾動的效能
virtual-scroll原理:
在使用者滾動時,改變列表在可視區域的渲染部分
- 計算當前可見區域起始資料的 startIndex
- 計算當前可見區域結束資料的 endIndex
- 計算當前可見區域的資料,並渲染到頁面中
- 計算 startIndex 對應的資料在整個列表中的偏移位置 startOffset,並設定到列表上
- 計算 endIndex 對應的資料相對於可滾動區域最底部的偏移位置 endOffset,並設定到列表上
如下圖所示:
startOffset 和 endOffset 會撐開容器元素的內容高度,讓其可持續的滾動;此外,還能保持滾動條處於一個正確的位置。
你知道的優化手段有哪些?
- 減少HTTP請求:使用 iconfont 字型圖示,使用精靈圖,合併js,合併 css
- 減少DNS查詢
- 將css放在頁面最上面,將js放在頁面最下面
- 壓縮js和css:減少檔案體積,去除不必要的空白符、格式符、註釋,移除重複,無用程式碼,使用 gzip
- 使用瀏覽器快取
- 避免 css 選擇器層級巢狀太深
- 高頻觸發事件使用防抖和節流
- 使 ajax 可快取
- 使用 cdn
- 持續補充...
瀏覽器
在瀏覽器位址列鍵入URL,按下回車之後會經歷以下流程:
- 解析 url 到 dns 伺服器
- dns 伺服器返回 ip 地址到瀏覽器
- 跟隨協議將 ip 傳送到網路中
- 經過區域網到達伺服器
- 進入伺服器的 MVC 架構 Controller
- 經過邏輯處理,請求分發,呼叫 Model 層
- Model 和資料進行互動,讀取資料庫,將最終結果通過 view 層返回到網路回到瀏覽器
- 瀏覽器根據請求回來的 html 和關聯的 css, js 進行渲染
- 在渲染的過程中,瀏覽器根據 html 生成 dom 樹,根據 css 生成 css 樹
- 將 dom 樹和 css 樹進行整合,最終知道 dom 節點的樣式,在頁面上進行樣式渲染
- 瀏覽器去執行 js 指令碼
- 最終展現頁面
瀏覽器渲染的過程,大致可以分為五步:
- html程式碼轉化為dom
- css程式碼轉化為cssom
- 結合dom和cssom,生成一顆渲染樹
- 生成佈局,即將所有的渲染樹的節點進行平面合成
- 將佈局繪製在螢幕上
瀏覽器快取
當瀏覽器再次訪問一個已經訪問過的資源時,它會這樣做:
- 看看是否命中強快取,如果命中,就直接使用快取了。
- 如果沒有命中強快取,就發請求到伺服器檢查是否命中協商快取。
- 如果命中協商快取,伺服器會返回 304 告訴瀏覽器使用本地快取。
- 否則,請求網路返回最新的資源。
瀏覽器快取的位置:
- Service Worker: 它可以讓我們自由控制快取哪些檔案、如何匹配快取、如何讀取快取,並且快取是持續性的。
- Memory Cache: 記憶體快取,讀取記憶體中的資料肯定比磁碟快。但是記憶體快取雖然讀取高效,可是快取持續性很短,會隨著程式的釋放而釋放。 一旦我們關閉 Tab 頁面,記憶體中的快取也就被釋放了。
- Disk Cache: Disk Cache 也就是儲存在硬碟中的快取,讀取速度慢點,但是什麼都能儲存到磁碟中,比之 Memory Cache 勝在容量和儲存時效性上。
快取的實現: 強快取和協商快取都是根據 HTTP Header 來實現的
什麼是重繪和迴流
迴流:佈局或者幾何屬性需要改變就稱為迴流。 重繪:當節點需要更改外觀而不會影響佈局的,比如改變 color 就叫稱為重繪
區別:
迴流必將引起重繪,而重繪不一定會引起迴流。比如:只有顏色改變的時候就只會發生重繪而不會引起迴流 當頁面佈局和幾何屬性改變時就需要回流
比如:新增或者刪除可見的DOM元素,元素位置改變,元素尺寸改變——邊距、填充、邊框、寬度和高度,內容改變
vue&react
說一下 MVVM
MVVM是雙向資料繫結
- M: Model 資料層
- V: View 檢視層
- VM: ViewModel 檢視層和資料層中間的橋,檢視層和資料層通訊的橋樑
view 層通過事件去繫結 Model 層, Model 層通過資料去繫結 View 層
什麼是 Virtual DOM 為什麼使用 Virtual DOM
在之前,渲染資料時,會直接替換掉 DOM 裡的所有元素,換成新的資料,為了渲染無用 DOM 所造成的效能浪費,所以出現了 Virtual DOM, Virtual DOM 是虛擬 DOM,是用 js 物件表示的樹結構,把 DOM 中的屬性對映到 js 的物件屬性中,它的核心定義無非就幾個關鍵屬性,標籤名、資料、子節點、鍵值等。當資料改變時,重新渲染這個 js 的物件結構,找出真正需要更新的 DOM 節點,再去渲染真實 DOM。Virtual DOM 本質就是在 JS 和 DOM 之間做了一個快取
為什麼操作真實 dom 有效能問題
因為 DOM 是屬於渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當我們通過 JS 操作 DOM 的時候,其實這個操作涉及到了兩個執行緒之間的通訊,那麼勢必會帶來一些效能上的損耗。操作 DOM 次數一多,也就等同於一直在進行執行緒之間的通訊,並且操作 DOM 可能還會帶來重繪迴流的情況,所以也就導致了效能上的問題。
Vue 的響應式原理
Vue 內部使用了 Object.defineProperty()
來實現資料響應式,通過這個函式可以監聽到 set
和 get
的事件。
- 首先利用
Object.defineProperty()
給 data 中的屬性去設定set
,get
事件 - 遞迴的去把 data 中的每一個屬性註冊為被觀察者
- 解析模板時,在屬性的
get
事件中去收集觀察者依賴 - 當屬性的值發生改變時,在
set
事件中去通知每一個觀察者,做到全部更新
Vue 的模板如何被渲染成 HTML? 以及渲染過程
- vue 模板的本質是字串,利用各種正則,把模板中的屬性去變成 js 中的變數,vif,vshow,vfor 等指令變成 js 中的邏輯
- 模板最終會被轉換為 render 函式
- render 函式執行返回 vNode
- 使用 vNode 的 path 方法把 vNode 渲染成真實 DOM
Vue 的整個實現流程
- 先把模板解析成 render 函式,把模板中的屬性去變成 js 中的變數,vif,vshow,vfor 等指令變成 js 中的邏輯
- 執行 render 函式,在初次渲染執行 render 函式的過程中 繫結屬性監聽,收集依賴,最終得到 vNode,利用 vNode 的 Path 方法,把 vNode 渲染成真實的 DOM
- 在屬性更新後,重新執行 render 函式,不過這個時候就不需要繫結屬性和收集依賴了,最終生成新的 vNode
- 把新的 vNode 和 舊的 vNode 去做對比,找出真正需要更新的 DOM,渲染
什麼是 diff 演算法,或者是 diff 演算法用來做什麼
- diff 是linux中的基礎命令,可以用來做檔案,內容之間的對比
- vNode 中使用 diff 演算法是為了找出需要更新的節點,避免造成不必要的更新
Vuex是什麼
vuex 就像一個全域性的倉庫,公共的狀態或者複雜元件互動的狀態我們會抽離出來放進裡面。
vuex的核心主要包括以下幾個部分:
- state:state裡面就是存放的我們需要使用的狀態,他是單向資料流,在 vue 中不允許直接對他進行修改,而是使用 mutations 去進行修改
- mutations: mutations 就是存放如何更改狀態的一些方法
- actions: actions 是來做非同步修改的,他可以等待非同步結束後,再去使用
commit mutations
去修改狀態 - getters: 相當於是 state 的計算屬性
使用:
- 獲取狀態在元件內部 computed 中使用 this.$store.state 得到想要的狀態
- 修改的話可在元件中使用 this.$store.commit 方法去修改狀態
- 如果在一個元件中,方法,狀態使用太多。 可以使用 mapState,mapMutations 輔助函式
Vue 中元件之間的通訊
父子元件:父元件通過 Props 傳遞子元件,子元件通過 $emit 通知父元件 兄弟元件:可以使用 vuex 全域性共享狀態,或 eventBus
如何解決頁面重新整理後 Vuex 資料丟失的問題
可以通過外掛 vuex-persistedstate 來解決 外掛原理:利用 HTML5 的本地儲存 + Vuex.Store 的方法,去同步本地和 store 中的資料,做到同步更新
Vue中插槽的用法
待補充...
Vue 的生命鉤子函式,呼叫每個鉤子的時候做了什麼
- beforeCreate: 元件例項剛被建立,在此時,元件內部的屬性還不能使用
- created: 元件例項建立完成,屬性已經繫結,但 DOM 還沒有建立完成,一般用來請求介面,不可操作 DOM
- beforeMount: 模板掛載前
- mounted: 模板掛載後,在這個鉤子中,模板已經掛載完畢,可以去操作 DOM 了
- beforeUpdate:元件更新前
- update: 元件更新後
- activated:在使用 keep-alive 時才有的鉤子函式,元件被啟用時呼叫
- deactivated: 在使用 keep-alive 時才有的鉤子函式,元件被移除時呼叫
- beforeDestory: 在元件被移除前呼叫
- destoryed: 元件被移除後呼叫,一般用來清除定時器
- 如果有子元件的話,那麼呼叫順序為:父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate -> 子created -> 子beforeMount -> 子mounted -> 父mounted
前端路由的兩種實現原理
兩種路由模式分為 hash 模式 和 history 模式
hash 模式:
hash 模式背後的原理是 onhashchange 事件,可以在window物件上監聽這個事件,在 hash 變化時,瀏覽器會記錄歷史,並且觸發事件回撥,在 hash 模式中,位址列中會帶一個 #
history 模式:
前面的 hashchange,你只能改變 # 後面的 url 片段,而 history api 則給了前端完全的自由
history api可以分為兩大部分,切換和修改,參考 MDN
使用 history 需要服務端的支援,在hash模式下,前端路由修改的是#中的資訊,而瀏覽器請求時是不帶著的,所以沒有問題.但是在history下,你可以自由的修改path,當重新整理時,如果伺服器中沒有相應的響應或者資源,會刷出一個404來
什麼是ssr, ssr和之前的後臺模板有什麼區別
待補充...
Vue 中 provide 是做什麼的?
待補充...
mixins 一般用來做什麼?
待補充...
nextTick 是什麼?
待補充...
演算法
排序演算法(快排,冒泡)
待補充...
介紹下深度優先遍歷和廣度優先遍歷,如何實現?
待補充...
筆試演算法題
已知如下陣列:
var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];
編寫一個程式將陣列扁平化去併除其中重複部分資料,最終得到一個升序且不重複的陣列
方法一:
function handleArr(arr) {
let flatten = (arr) => arr.push ? arr.reduce((pre, current) => [...pre, ...flatten(current)], []) : [arr];
return [...new Set(flatten(arr))].sort((a, b) => a - b);
}
方法二:
[...new Set(arr.toString().split(",").map(Number))].sort((a,b)=> a - b)
方法三:
[...new Set(arr.flat(Infinity))].sort((a,b)=>{ return a - b})
複製程式碼
筆試執行題
let test = (function (a) {
this.a = a;
return function (b) {
console.log(this.a + b);
}
})((function(a){
return a;
})(1,2))
test(4)
複製程式碼
答案:5
解析:我們把(function (a) { this.a = a; return function (b) { console.log(this.a + b); } })()
自執行函式體叫做 z1
把 (function(a){ return a; })(1,2)
叫做 z2
把function (b) { console.log(this.a + b); }
叫做 n1
test 函式為匿名自執行函式z1
的返回值,實際上 test 函式為 n1
,函式 z1
接收 a 作為引數,這裡的 a 實際上為自執行函式 z2
返回值為 1, 那麼 n1
函式中的引數 a 就是 1,在函式 z1
中 this.a = a
這裡的 this 指向的是 window
所以在 window
上掛載了一個 a 的屬性,值為 1
在 text(4) 執行時,傳入了 b 為 4, 函式 n1
中的 this 指向的是 window
,window 中有之前掛載的屬性 a 為 1 所以函式執行結果為 5