引言
當下,正面臨著近幾年來的最嚴重的互聯(lián)網(wǎng)寒冬,聽得最多的一句話便是:相見于江湖~????s減 HC、裁員不絕于耳,大家都是人心惶惶,年前如此,年后想必肯定又是一場更為慘烈的江湖廝殺。但博主始終相信,寒冬之中,人才更是尤為珍貴。只要有過硬的操作和裝備,在逆風局下,同樣也能來一波收割翻盤。
博主也是年前經(jīng)歷了一番廝殺,最終拿到多家大廠的 offer。在閉關修煉的過程中,自己整理出了一套面試秘籍供自己反復研究,后來給了多位有需要的兄臺,均表示相當靠譜,理應在這寒冬之中回報于社會。于是決定花點精力整理成文,讓大家能比較系統(tǒng)的反復學習,快速提升自己。
面試固然有技巧,但絕不是偽造與吹流弊,通過一段短時間沉下心來閉關修煉,出山收割,步入大廠,薪資翻番,豈不爽哉???
修煉原則
想必大家很厭煩筆試和考察知識點。因為其實在平時實戰(zhàn)中,講究的是開發(fā)效率,很少會去刻意記下一些細節(jié)和深挖知識點,腦海中都是一些分散的知識點,無法系統(tǒng)性地關聯(lián)成網(wǎng),一直處于似曾相識的狀態(tài)。不知道多少人和博主一樣,至今每次寫阻止冒泡都需要谷歌一番如何拼寫。??。
以如此的狀態(tài),定然是無法在面試的戰(zhàn)場上縱橫的。其實面試就猶如考試,大家回想下高考之前所做的事,無非就是 理解 和 系統(tǒng)性關聯(lián)記憶。本秘籍的知識點較多,花點時間一個個理解并記憶后,自然也就融會貫通,無所畏懼。
由于本秘籍為了便于記憶,快速達到應試狀態(tài),類似于復習知識大綱。知識點會盡量的精簡與提煉知識脈絡,并不去展開深入細節(jié),面面俱到。有興趣或者有疑問的童鞋可以自行谷歌下對應知識點的詳細內容。??
CSS
1. 盒模型
頁面渲染時,dom 元素所采用的 布局模型??赏ㄟ^box-sizing進行設置。根據(jù)計算寬高的區(qū)域可分為:
-
content-box(W3C 標準盒模型) -
border-box(IE 盒模型) padding-box-
margin-box(瀏覽器未實現(xiàn))
2. BFC
塊級格式化上下文,是一個獨立的渲染區(qū)域,讓處于 BFC 內部的元素與外部的元素相互隔離,使內外元素的定位不會相互影響。
IE 下為 Layout,可通過 zoom:1 觸發(fā)
-
觸發(fā)條件:
- 根元素
position: absolute/fixeddisplay: inline-block / table-
float元素 -
ovevflow!==visible
-
規(guī)則:
- 屬于同一個 BFC 的兩個相鄰 Box 垂直排列
- 屬于同一個 BFC 的兩個相鄰 Box 的 margin 會發(fā)生重疊
- BFC 中子元素的 margin box 的左邊, 與包含塊 (BFC) border box 的左邊相接觸 (子元素 absolute 除外)
- BFC 的區(qū)域不會與 float 的元素區(qū)域重疊
- 計算 BFC 的高度時,浮動子元素也參與計算
- 文字層不會被浮動層覆蓋,環(huán)繞于周圍
-
應用:
- 阻止
margin重疊 - 可以包含浮動元素 —— 清除內部浮動(清除浮動的原理是兩個
div都位于同一個 BFC 區(qū)域之中) - 自適應兩欄布局
- 可以阻止元素被浮動元素覆蓋
- 阻止
3.層疊上下文
元素提升為一個比較特殊的圖層,在三維空間中 (z 軸) 高出普通元素一等。
-
觸發(fā)條件
- 根層疊上下文(
html) position- css3 屬性
flextransformopacityfilterwill-change-webkit-overflow-scrolling
- 根層疊上下文(
-
層疊等級:層疊上下文在 z 軸上的排序
- 在同一層疊上下文中,層疊等級才有意義
-
z-index的優(yōu)先級最高

4. 居中布局
-
水平居中
- 行內元素:
text-align: center - 塊級元素:
margin: 0 auto absolute + transformflex + justify-content: center
- 行內元素:
-
垂直居中
line-height: heightabsolute + transformflex + align-items: centertable
-
水平垂直居中
absolute + transformflex + justify-content + align-items
5. 選擇器優(yōu)先級
-
!important> 行內樣式 >#id>.class>tag> * > 繼承 > 默認 - 選擇器 從右往左 解析
6.去除浮動影響,防止父級高度塌陷
- 通過增加尾元素清除浮動
:after / <br> : clear: both
- 創(chuàng)建父級 BFC
- 父級設置高度
7.link 與 @import 的區(qū)別
-
link功能較多,可以定義 RSS,定義 Rel 等作用,而@import只能用于加載 css - 當解析到
link時,頁面會同步加載所引的 css,而@import所引用的 css 會等到頁面加載完才被加載 -
@import需要 IE5 以上才能使用 -
link可以使用 js 動態(tài)引入,@import不行
8. CSS 預處理器(Sass/Less/Postcss)
CSS 預處理器的原理: 是將類 CSS 語言通過 Webpack 編譯 轉成瀏覽器可讀的真正 CSS。在這層編譯之上,便可以賦予 CSS 更多更強大的功能,常用功能:
- 嵌套
- 變量
- 循環(huán)語句
- 條件語句
- 自動前綴
- 單位轉換
- mixin 復用
面試中一般不會重點考察該點,一般介紹下自己在實戰(zhàn)項目中的經(jīng)驗即可~
9.CSS 動畫
-
transition: 過渡動畫-
transition-property: 屬性 -
transition-duration: 間隔 -
transition-timing-function: 曲線 -
transition-delay: 延遲 - 常用鉤子:
transitionend
-
-
animation / keyframes-
animation-name: 動畫名稱,對應@keyframes -
animation-duration: 間隔 -
animation-timing-function: 曲線 -
animation-delay: 延遲 -
animation-iteration-count: 次數(shù)-
infinite: 循環(huán)動畫
-
-
animation-direction: 方向-
alternate: 反向播放
-
-
animation-fill-mode: 靜止模式-
forwards: 停止時,保留最后一幀 -
backwards: 停止時,回到第一幀 -
both: 同時運用forwards / backwards
-
- 常用鉤子:
animationend
-
-
動畫屬性: 盡量使用動畫屬性進行動畫,能擁有較好的性能表現(xiàn)
translatescalerotateskewopacitycolor
經(jīng)驗
通常,CSS 并不是重點的考察領域,但這其實是由于現(xiàn)在國內業(yè)界對 CSS 的專注不夠導致的,真正精通并專注于 CSS 的團隊和人才并不多。因此如果能在 CSS 領域有自己的見解和經(jīng)驗,反而會為相當?shù)募臃趾兔摲f而出。
JavaScript
1. 原型 / 構造函數(shù) / 實例
原型
(prototype): 一個簡單的對象,用于實現(xiàn)對象的 屬性繼承??梢院唵蔚睦斫獬蓪ο蟮牡T?Firefox 和 Chrome 中,每個JavaScript對象中都包含一個__proto__(非標準)的屬性指向它爹(該對象的原型),可obj.__proto__進行訪問。構造函數(shù): 可以通過
new來 新建一個對象 的函數(shù)。實例: 通過構造函數(shù)和
new創(chuàng)建出來的對象,便是實例。 實例通過__proto__指向原型,通過constructor指向構造函數(shù)。
說了一大堆,大家可能有點懵逼,這里來舉個栗子,以Object為例,我們常用的Object便是一個構造函數(shù),因此我們可以通過它構建實例。
// 實例
const instance = new Object();
則此時, 實例為instance, 構造函數(shù)為Object,我們知道,構造函數(shù)擁有一個prototype的屬性指向原型,因此原型為:
// 原型
const prototype = Object.prototype;
這里我們可以來看出三者的關系:
實例.__proto__ === 原型
原型.constructor === 構造函數(shù)
構造函數(shù).prototype === 原型
// 這條線其實是是基于原型進行獲取的,可以理解成一條基于原型的映射線
// 例如:
// const o = new Object()
// o.constructor === Object --> true
// o.__proto__ = null;
// o.constructor === Object --> false
實例.constructor === 構造函數(shù)
此處感謝 caihaihong 童鞋的指出。
放大來看,我畫了張圖供大家徹底理解:

2.原型鏈:
原型鏈是由原型對象組成,每個對象都有 __proto__ 屬性,指向了創(chuàng)建該對象的構造函數(shù)的原型,__proto__ 將對象連接起來組成了原型鏈。是一個用來實現(xiàn)繼承和共享屬性的有限的對象鏈。
屬性查找機制: 當查找對象的屬性時,如果實例對象自身不存在該屬性,則沿著原型鏈往上一級查找,找到時則輸出,不存在時,則繼續(xù)沿著原型鏈往上一級查找,直至最頂級的原型對象
Object.prototype,如還是沒找到,則輸出undefined;屬性修改機制: 只會修改實例對象本身的屬性,如果不存在,則進行添加該屬性,如果需要修改原型的屬性時,則可以用:
b.prototype.x = 2;但是這樣會造成所有繼承于該對象的實例的屬性發(fā)生改變。
3. 執(zhí)行上下文(EC)
執(zhí)行上下文可以簡單理解為一個對象:
-
它包含三個部分:
- 變量對象(VO)
- 作用域鏈(詞法作用域)
-
this指向
-
它的類型:
- 全局執(zhí)行上下文
- 函數(shù)執(zhí)行上下文
-
eval執(zhí)行上下文
-
代碼執(zhí)行過程:
- 創(chuàng)建 全局上下文 (global EC)
- 全局執(zhí)行上下文 (caller) 逐行 自上而下 執(zhí)行。遇到函數(shù)時,函數(shù)執(zhí)行上下文 (callee) 被
push到執(zhí)行棧頂層 - 函數(shù)執(zhí)行上下文被激活,成為 active EC, 開始執(zhí)行函數(shù)中的代碼,caller 被掛起
- 函數(shù)執(zhí)行完后,callee 被
pop移除出執(zhí)行棧,控制權交還全局上下文 (caller),繼續(xù)執(zhí)行
2.變量對象
變量對象,是執(zhí)行上下文中的一部分,可以抽象為一種 數(shù)據(jù)作用域,其實也可以理解為就是一個簡單的對象,它存儲著該執(zhí)行上下文中的所有 變量和函數(shù)聲明(不包含函數(shù)表達式)。
活動對象 (AO): 當變量對象所處的上下文為 active EC 時,稱為活動對象。
3. 作用域
執(zhí)行上下文中還包含作用域鏈。理解作用域之前,先介紹下作用域。作用域其實可理解為該上下文中聲明的 變量和聲明的作用范圍??煞譃?塊級作用域 和 函數(shù)作用域
特性:
- 聲明提前: 一個聲明在函數(shù)體內都是可見的, 函數(shù)優(yōu)先于變量
- 非匿名自執(zhí)行函數(shù),函數(shù)變量為 只讀 狀態(tài),無法修改
let foo = (function() {
console.log(1);
})(
(function foo() {
foo = 10; // 由于foo在函數(shù)中只為可讀,因此賦值無效
console.log(foo);
})()
);
// 結果打?。? ? foo() { foo = 10 ; console.log(foo) }
4.作用域鏈
我們知道,我們可以在執(zhí)行上下文中訪問到父級甚至全局的變量,這便是作用域鏈的功勞。作用域鏈可以理解為一組對象列表,包含 父級和自身的變量對象,因此我們便能通過作用域鏈訪問到父級里聲明的變量或者函數(shù)。
- 由兩部分組成:
-
[[scope]]屬性: 指向父級變量對象和作用域鏈,也就是包含了父級的[[scope]]和AO - AO: 自身活動對象
-
如此 [[scopr]]包含[[scope]],便自上而下形成一條 鏈式作用域。
5. 閉包
閉包屬于一種特殊的作用域,稱為 靜態(tài)作用域。它的定義可以理解為: 父函數(shù)被銷毀 的情況下,返回出的子函數(shù)的[[scope]]中仍然保留著父級的單變量對象和作用域鏈,因此可以繼續(xù)訪問到父級的變量對象,這樣的函數(shù)稱為閉包。
-
閉包會產(chǎn)生一個很經(jīng)典的問題:
- 多個子函數(shù)的
[[scope]]都是同時指向父級,是完全共享的。因此當父級的變量對象被修改時,所有子函數(shù)都受到影響。
- 多個子函數(shù)的
-
解決:
- 變量可以通過 函數(shù)參數(shù)的形式 傳入,避免使用默認的
[[scope]]向上查找 - 使用
setTimeout包裹,通過第三個參數(shù)傳入 - 使用 塊級作用域,讓變量成為自己上下文的屬性,避免共享
- 變量可以通過 函數(shù)參數(shù)的形式 傳入,避免使用默認的
6. script 引入方式:
- html 靜態(tài)
<script>引入 - js 動態(tài)插入
<script> -
<script defer>: 異步加載,元素解析完成后執(zhí)行 -
<script async>: 異步加載,但執(zhí)行時會阻塞元素渲染
7. 對象的拷貝
-
淺拷貝: 以賦值的形式拷貝引用對象,仍指向同一個地址,修改時原對象也會受到影響
Object.assign- 展開運算符(...)
-
深拷貝: 完全拷貝一個新對象,修改時原對象不再受到任何影響
-
JSON.parse(JSON.stringify(obj)): 性能最快- 具有循環(huán)引用的對象時,報錯
- 當值為函數(shù)、
undefined、或symbol時,無法拷貝
- 遞歸進行逐一賦值
-
8. new 運算符的執(zhí)行過程
- 新生成一個對象
- 鏈接到原型:
obj.__proto__ = Con.prototype - 綁定 this:
apply - 返回新對象(如果構造函數(shù)有自己 retrun 時,則返回該值)
9. instanceof 原理
能在實例的 原型對象鏈 中找到該構造函數(shù)的prototype屬性所指向的 原型對象,就返回true。即:
// __proto__: 代表原型對象鏈
instance.[__proto__...] === instance.constructor.prototype
// return true
10. 代碼的復用
當你發(fā)現(xiàn)任何代碼開始寫第二遍時,就要開始考慮如何復用。一般有以下的方式:
- 函數(shù)封裝
- 繼承
- 復制
extend - 混入
mixin - 借用
apply/call
11. 繼承
在 JS 中,繼承通常指的便是 原型鏈繼承,也就是通過指定原型,并可以通過原型鏈繼承原型上的屬性或者方法。
- 最優(yōu)化: 圣杯模式
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;
};
})();
- 使用 ES6 的語法糖
class / extends
12. 類型轉換
大家都知道 JS 中在使用運算符號或者對比符時,會自帶隱式轉換,規(guī)則如下:
- -、*、/、% :一律轉換成數(shù)值后計算
- +:
- 數(shù)字 + 字符串 = 字符串, 運算順序是從左到右
- 數(shù)字 + 對象, 優(yōu)先調用對象的
valueOf->toString - 數(shù)字 +
boolean/null-> 數(shù)字 - 數(shù)字 +
undefined->NaN
[1].toString() === '1'{}.toString() === '[object object]'-
NaN!==NaN、+undefined 為 NaN
13. 類型判斷
判斷 Target 的類型,單單用 typeof 并無法完全滿足,這其實并不是 bug,本質原因是 JS 的萬物皆對象的理論。因此要真正完美判斷時,我們需要區(qū)分對待:
- 基本類型(
null): 使用String(null) - 基本類型(
string / number / boolean / undefined) +function: 直接使用typeof即可 - 其余引用類型(
Array / Date / RegExp Error): 調用toString后根據(jù)[object XXX]進行判斷
很穩(wěn)的判斷封裝:
let class2type = {};
"Array Date RegExp Object Error"
.split(" ")
.forEach(e => (class2type["[object " + e + "]"] = e.toLowerCase()));
function type(obj) {
if (obj == null) return String(obj);
return typeof obj === "object"
? class2type[Object.prototype.toString.call(obj)] || "object"
: typeof obj;
}
14. 模塊化
模塊化開發(fā)在現(xiàn)代開發(fā)中已是必不可少的一部分,它大大提高了項目的可維護、可拓展和可協(xié)作性。通常,我們 在瀏覽器中使用 ES6 的模塊化支持,在 Node 中使用 commonjs 的模塊化支持。
-
分類:
- es6:
import / export - commonjs:
require / module.exports / exports - amd:
require / defined
- es6:
-
require與import的區(qū)別-
require支持 動態(tài)導入,import不支持,正在提案 (babel 下可支持) -
require是 同步 導入,import屬于 異步 導入 -
require是 值拷貝,導出值變化不會影響導入值;import指向 內存地址,導入值會隨導出值而變化
-
15. 防抖與節(jié)流
防抖與節(jié)流函數(shù)是一種最常用的 高頻觸發(fā)優(yōu)化方式,能對性能有較大的幫助。
- 防抖 (debounce): 將多次高頻操作優(yōu)化為只在最后一次執(zhí)行,通常使用的場景是:用戶輸入,只需再輸入完成后做一次輸入校驗即可。
function debounce(fn, wait, immediate) {
let timer = null;
return function() {
let args = arguments;
let context = this;
if (immediate && !timer) {
fn.apply(context, args);
}
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(context, args);
}, wait);
};
}
- 節(jié)流(throttle): 每隔一段時間后執(zhí)行一次,也就是降低頻率,將高頻操作優(yōu)化成低頻操作,通常使用場景: 滾動條事件 或者 resize 事件,通常每隔 100~500 ms 執(zhí)行一次即可。
function throttle(fn, wait, immediate) {
let timer = null;
let callNow = immediate;
return function() {
let context = this,
args = arguments;
if (callNow) {
fn.apply(context, args);
callNow = false;
}
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, wait);
}
};
}
16. 函數(shù)執(zhí)行改變 this
由于 JS 的設計原理: 在函數(shù)中,可以引用運行環(huán)境中的變量。因此就需要一個機制來讓我們可以在函數(shù)體內部獲取當前的運行環(huán)境,這便是this。
因此要明白 this 指向,其實就是要搞清楚 函數(shù)的運行環(huán)境,說人話就是,誰調用了函數(shù)。例如:
-
obj.fn(),便是obj調用了函數(shù),既函數(shù)中的this === obj -
fn(),這里可以看成window.fn(),因此this === window
但這種機制并不完全能滿足我們的業(yè)務需求,因此提供了三種方式可以手動修改 this 的指向:
call: fn.call(target, 1, 2)apply: fn.apply(target, [1, 2])bind: fn.bind(target)(1,2)
17. ES6/ES7
由于 Babel 的強大和普及,現(xiàn)在 ES6/ES7 基本上已經(jīng)是現(xiàn)代化開發(fā)的必備了。通過新的語法糖,能讓代碼整體更為簡潔和易讀。
-
聲明
-
let / const: 塊級作用域、不存在變量提升、暫時性死區(qū)、不允許重復聲明 -
const: 聲明常量,無法修改
-
解構賦值
class / extend: 類聲明與繼承Set / Map: 新的數(shù)據(jù)結構-
異步解決方案:
Promise的使用與實現(xiàn)-
generator:-
yield: 暫停代碼 -
next(): 繼續(xù)執(zhí)行代碼
function* helloWorld() { yield "hello"; yield "world"; return "ending"; } const generator = helloWorld(); generator.next(); // { value: 'hello', done: false } generator.next(); // { value: 'world', done: false } generator.next(); // { value: 'ending', done: true } generator.next(); // { value: undefined, done: true } -
-
await / async: 是generator的語法糖, babel 中是基于promise實現(xiàn)。async function getUserByAsync() { let user = await fetchUser(); return user; } const user = await getUserByAsync(); console.log(user);
18. AST
抽象語法樹 (Abstract Syntax Tree),是將代碼逐字母解析成 樹狀對象 的形式。這是語言之間的轉換、代碼語法檢查,代碼風格檢查,代碼格式化,代碼高亮,代碼錯誤提示,代碼自動補全等等的基礎。例如:
function square(n){
return n \* n
}
通過解析轉化成的AST如下圖:

19. babel 編譯原理
- babylon 將 ES6/ES7 代碼解析成 AST
- babel-traverse 對 AST 進行遍歷轉譯,得到新的 AST
- 新 AST 通過 babel-generator 轉換成 ES5
20. 函數(shù)柯里化
在一個函數(shù)中,首先填充幾個參數(shù),然后再返回一個新的函數(shù)的技術,稱為函數(shù)的柯里化。通??捎糜谠诓磺秩牒瘮?shù)的前提下,為函數(shù) 預置通用參數(shù),供多次重復調用。
const add = function add(x) {
return function(y) {
return x + y;
};
};
const add1 = add(1);
add1(2) === 3;
add1(20) === 21;
21. 數(shù)組(array)
map: 遍歷數(shù)組,返回回調返回值組成的新數(shù)組forEach: 無法break,可以用try/catch中throw new Error來停止filter: 過濾some: 有一項返回true,則整體為trueevery: 有一項返回false,則整體為falsejoin: 通過指定連接符生成字符串push / pop: 末尾推入和彈出,改變原數(shù)組, 返回推入/彈出項unshift / shift: 頭部推入和彈出,改變原數(shù)組,返回操作項sort(fn) / reverse: 排序與反轉,改變原數(shù)組concat: 連接數(shù)組,不影響原數(shù)組, 淺拷貝slice(start, end): 返回截斷后的新數(shù)組,不改變原數(shù)組splice(start, number, value...): 返回刪除元素組成的數(shù)組,value 為插入項,改變原數(shù)組indexOf / lastIndexOf(value, fromIndex): 查找數(shù)組項,返回對應的下標reduce / reduceRight(fn(prev, cur), defaultPrev): 兩兩執(zhí)行,prev 為上次化簡函數(shù)的return值,cur 為當前值(從第二項開始)數(shù)組亂序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function() {
return Math.random() - 0.5;
});
- 數(shù)組拆解: flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() {
this.toString()
.split(",")
.map(item => +item);
};
22. 函數(shù)式編程(Shopee 面試題)
const removeCharacter = str => str.replace(/[^\w\s]/g, " ");
const toUpper = str => str.toUpperCase();
const split = str => str.split(" ");
const filterEmpty = arr => arr.filter(str => !!str.trim().length);
const fn = compose(
removeCharacter,
toUpper,
split,
filterEmpty
);
fn("Hello, to8to World!"); // => ["HELLO","TO8TO","WORLD"]
// 請實現(xiàn)`compose`方法來達到效果
參考答案:
const compose = (...args) => {
return str => args.reduce((prev, next) => next.call(null, prev), str);
};
瀏覽器
1. 跨標簽頁通訊
不同標簽頁間的通訊,本質原理就是去運用一些可以 共享的中間介質,因此比較常用的有以下方法:
-
通過父頁面
window.open()和子頁面postMessage- 異步下,通過
window.open('about: blank')和tab.location.href = '*'
- 異步下,通過
-
設置同域下共享的
localStorage與監(jiān)聽window.onstorage- 重復寫入相同的值無法觸發(fā)
- 會受到瀏覽器隱身模式等的限制
設置共享
cookie與不斷輪詢臟檢查(setInterval)借助服務端或者中間層實現(xiàn)
2. 瀏覽器架構
- 用戶界面
- 主進程
- 內核
- 渲染引擎
- JS 引擎
- 執(zhí)行棧
- 事件觸發(fā)線程
- 消息隊列
- 微任務
- 宏任務
- 消息隊列
- 網(wǎng)絡異步線程
- 定時器線程
3. 瀏覽器下事件循環(huán)(Event Loop)
事件循環(huán)是指: 執(zhí)行一個宏任務,然后執(zhí)行清空微任務列表,循環(huán)再執(zhí)行宏任務,再清微任務列表
- 微任務
microtask(jobs):promise / ajax / Object.observe(該方法已廢棄) - 宏任務
macrotask(task):setTimout / script / IO / UI Rendering
面試題(Shopee 面試題)
題目:
實現(xiàn)一個LazyMan,可以按照以下方式調用:
LazyMan(“Hank”)輸出:
Hi! This is Hank!
?
LazyMan(“Hank”).sleep(10).eat(“dinner”)輸出
Hi! This is Hank!
//等待10秒..
Wake up after 10
Eat dinner~
?
LazyMan(“Hank”).eat(“dinner”).eat(“supper”)輸出
Hi This is Hank!
Eat dinner~
Eat supper~
?
LazyMan(“Hank”).sleepFirst(5).eat(“supper”)輸出
//等待5秒
Wake up after 5
Hi This is Hank!
Eat supper
function Lazyman ( name ) {
return new _Lazyman ( name );
}
class _Lazyman{
constructor ( name ) {
this.tasks = [];//設置任務隊列
let task = (name => () => {
console.log ( `Hi! This is ${name} !` );
this.next ();
}) ( name );
this.tasks.push ( task );
//通過settimeout的方法,將執(zhí)行函數(shù)放入下一個事件隊列中,從而達到先注冊事件,后執(zhí)行的目的
setTimeout ( () => {
this.next ();
}, 0 );
}
//尾調用函數(shù),一個任務執(zhí)行完然后再調用下一個任務
next () {
let task = this.tasks.shift ();
task && task ();
}
eat ( food ) {
let task = (food => () => {
console.log ( `Eat ${food}` );
this.next ();
}) ( food );
this.tasks.push ( task );
return this;
}
sleep ( time ) {
let task = (time => () => {
setTimeout ( () => {
console.log ( `Wake up after ${time} s!` );
this.next ();
}, time * 1000 )
}) ( time );
this.tasks.push ( task );
return this;
}
sleepFirst ( time ) {
let task = (time => () => {
setTimeout ( () => {
console.log ( `Wake up after ${time} s!` );
this.next ();
}, time * 1000 )
}) ( time );
this.tasks.unshift ( task );//sleepFirst函數(shù)需要最先執(zhí)行,所以我們需要在任務隊列前面放入,然后再執(zhí)行后面的任務
return this;
}
}
}
4. 從輸入 url 到展示的過程
- DNS 解析
- TCP 三次握手
- 發(fā)送請求,分析 url,設置請求報文(頭,主體)
- 服務器返回請求的文件 (html)
- 瀏覽器渲染
- HTML parser --> DOM Tree
- 標記化算法,進行元素狀態(tài)的標記
- dom 樹構建
- CSS parser --> Style Tree
- 解析 css 代碼,生成樣式樹
- attachment --> Render Tree
- 結合 dom 樹 與 style 樹,生成渲染樹
- layout: 布局
- GPU painting: 像素繪制頁面
- HTML parser --> DOM Tree
5. 重繪與回流
當元素的樣式發(fā)生變化時,瀏覽器需要觸發(fā)更新,重新繪制元素。這個過程中,有兩種類型的操作,即重繪與回流。
重繪(repaint): 當元素樣式的改變不影響布局時,瀏覽器將使用重繪對元素進行更新,此時由于只需要 UI 層面的重新像素繪制,因此 損耗較少
-
回流(reflow): 當元素的尺寸、結構或觸發(fā)某些屬性時,瀏覽器會重新渲染頁面,稱為回流。此時,瀏覽器需要重新經(jīng)過計算,計算后還需要重新頁面布局,因此是較重的操作。會觸發(fā)回流的操作:
- 頁面初次渲染
- 瀏覽器窗口大小改變
- 元素尺寸、位置、內容發(fā)生改變
- 元素字體大小變化
- 添加或者刪除可見的 dom 元素
- 激活 CSS 偽類(例如::hover)
- 查詢某些屬性或調用某些方法
- clientWidth、clientHeight、clientTop、clientLeft
- offsetWidth、offsetHeight、offsetTop、offsetLeft
- scrollWidth、scrollHeight、scrollTop、scrollLeft
- getComputedStyle()
- getBoundingClientRect()
- scrollTo()
回流必定觸發(fā)重繪,重繪不一定觸發(fā)回流。重繪的開銷較小,回流的代價較高。
最佳實踐:
-
css
- 避免使用
table布局 - 將動畫效果應用到
position屬性為absolute或fixed的元素上
- 避免使用
-
javascript
- 避免頻繁操作樣式,可匯總后統(tǒng)一 一次修改
- 盡量使用
class進行樣式修改 - 減少
dom的增刪次數(shù),可使用 字符串 或者documentFragment一次性插入 - 極限優(yōu)化時,修改樣式可將其
display: none后修改 - 避免多次觸發(fā)上面提到的那些會觸發(fā)回流的方法,可以的話盡量用 變量存住
6. 存儲
我們經(jīng)常需要對業(yè)務中的一些數(shù)據(jù)進行存儲,通??梢苑譃?短暫性存儲 和 持久性儲存。
短暫性的時候,我們只需要將數(shù)據(jù)存在內存中,只在運行時可用
-
持久性存儲,可以分為 瀏覽器端 與 服務器端
- 瀏覽器:
-
cookie: 通常用于存儲用戶身份,登錄狀態(tài)等- http 中自動攜帶, 體積上限為 4K, 可自行設置過期時間
-
localStorage / sessionStorage: 長久儲存/窗口關閉刪除, 體積限制為 4~5M indexDB
-
- 服務器:
- 分布式緩存 redis
- 數(shù)據(jù)庫
- 瀏覽器:
7. Web Worker
現(xiàn)代瀏覽器為JavaScript創(chuàng)造的 多線程環(huán)境??梢孕陆ú⒉糠秩蝿辗峙涞?code>worker線程并行運行,兩個線程可 獨立運行,互不干擾,可通過自帶的 消息機制 相互通信。
基本用法:
// 創(chuàng)建 worker
const worker = new Worker("work.js");
// 向主進程推送消息
worker.postMessage("Hello World");
// 監(jiān)聽主進程來的消息
worker.onmessage = function(event) {
console.log("Received message " + event.data);
};
限制:
- 同源限制
- 無法使用
document/window/alert/confirm - 無法加載本地資源
8. V8 垃圾回收機制
垃圾回收: 將內存中不再使用的數(shù)據(jù)進行清理,釋放出內存空間。V8 將內存分成 新生代空間 和 老生代空間。
-
新生代空間: 用于存活較短的對象
- 又分成兩個空間: from 空間 與 to 空間
- Scavenge GC 算法: 當 from 空間被占滿時,啟動 GC 算法
- 存活的對象從 from space 轉移到 to space
- 清空 from space
- from space 與 to space 互換
- 完成一次新生代 GC
-
老生代空間: 用于存活時間較長的對象
- 從 新生代空間 轉移到 老生代空間 的條件
- 經(jīng)歷過一次以上 Scavenge GC 的對象
- 當 to space 體積超過 25%
-
標記清除算法: 標記存活的對象,未被標記的則被釋放
- 增量標記: 小模塊標記,在代碼執(zhí)行間隙執(zhí),GC 會影響性能
- 并發(fā)標記(最新技術): 不阻塞 js 執(zhí)行
- 壓縮算法: 將內存中清除后導致的碎片化對象往內存堆的一端移動,解決 內存的碎片化
- 從 新生代空間 轉移到 老生代空間 的條件
9. 內存泄露
- 意外的全局變量: 無法被回收
- 定時器: 未被正確關閉,導致所引用的外部變量無法被釋放
- 事件監(jiān)聽: 沒有正確銷毀 (低版本瀏覽器可能出現(xiàn))
- 閉包: 會導致父級中的變量無法被釋放
- dom 引用: dom 元素被刪除時,內存中的引用未被正確清空
可用 chrome 中的 timeline 進行內存標記,可視化查看內存的變化情況,找出異常點。
服務端與網(wǎng)絡
1. http/https 協(xié)議
-
1.0 協(xié)議缺陷:
- 無法復用鏈接,完成即斷開,重新慢啟動和 TCP 3 次握手
- head of line blocking: 線頭阻塞,導致請求之間互相影響
-
1.1 改進:
- 長連接(默認 keep-alive),復用
- host 字段指定對應的虛擬站點
- 新增功能:
- 斷點續(xù)傳
- 身份認證
- 狀態(tài)管理
- cache 緩存
- Cache-Control
- Expires
- Last-Modified
- Etag
-
2.0:
- 多路復用
- 二進制分幀層: 應用層和傳輸層之間
- 首部壓縮
- 服務端推送
-
https: 較為安全的網(wǎng)絡傳輸協(xié)議
- 證書(公鑰)
- SSL 加密
- 端口 443
-
TCP:
- 三次握手
- 四次揮手
- 滑動窗口: 流量控制
- 擁塞處理
- 慢開始
- 擁塞避免
- 快速重傳
- 快速恢復
-
緩存策略: 可分為 強緩存 和 協(xié)商緩存
Cache-Control/Expires: 瀏覽器判斷緩存是否過期,未過期時,直接使用強緩存,Cache-Control 的 max-age 優(yōu)先級高于 Expires
-
當緩存已經(jīng)過期時,使用協(xié)商緩存
- 唯一標識方案: Etag(response 攜帶) & If-None-Match(request 攜帶,上一次返回的 Etag): 服務器判斷資源是否被修改,
- 最后一次修改時間: Last-Modified(response) & If-Modified-Since (request,上一次返回的 Last-Modified)
- 如果一致,則直接返回 304 通知瀏覽器使用緩存
- 如不一致,則服務端返回新的資源
-
Last-Modified 缺點:
- 周期性修改,但內容未變時,會導致緩存失效
- 最小粒度只到 s, s 以內的改動無法檢測到
Etag 的優(yōu)先級高于 Last-Modified
2. 常見狀態(tài)碼
- 1xx: 接受,繼續(xù)處理
- 200: 成功,并返回數(shù)據(jù)
- 201: 已創(chuàng)建
- 202: 已接受
- 203: 成為,但未授權
- 204: 成功,無內容
- 205: 成功,重置內容
- 206: 成功,部分內容
- 301: 永久移動,重定向
- 302: 臨時移動,可使用原有 URI
- 304: 資源未修改,可使用緩存
- 305: 需代理訪問
- 400: 請求語法錯誤
- 401: 要求身份認證
- 403: 拒絕請求
- 404: 資源不存在
- 500: 服務器錯誤
3. get / post
- get: 緩存、請求長度受限、會被歷史保存記錄
- 無副作用(不修改資源),冪等(請求次數(shù)與資源無關)的場景
- post: 安全、大數(shù)據(jù)、更多編碼類型
兩者詳細對比如下圖:

4. Websocket
Websocket 是一個 持久化的協(xié)議, 基于 http , 服務端可以 主動 push
-
兼容:
- FLASH Socket
- 長輪詢: 定時發(fā)送 ajax
- long poll: 發(fā)送 --> 有消息時再 response
new WebSocket(url)ws.onerror = fnws.onclose = fnws.onopen = fnws.onmessage = fnws.send()
5. TCP 三次握手
建立連接前,客戶端和服務端需要通過握手來確認對方:
- 客戶端發(fā)送 syn(同步序列編號) 請求,進入 syn_send 狀態(tài),等待確認
- 服務端接收并確認 syn 包后發(fā)送 syn+ack 包,進入 syn_recv 狀態(tài)
- 客戶端接收 syn+ack 包后,發(fā)送 ack 包,雙方進入 established 狀態(tài)
6. TCP 四次揮手
- 客戶端 -- FIN --> 服務端, FIN—WAIT
- 服務端 -- ACK --> 客戶端, CLOSE-WAIT
- 服務端 -- ACK,FIN --> 客戶端, LAST-ACK
- 客戶端 -- ACK --> 服務端,CLOSED
7. Node 的 Event Loop: 6 個階段
- timer 階段: 執(zhí)行到期的
setTimeout / setInterval隊列回調 - I/O 階段: 執(zhí)行上輪循環(huán)殘流的
callback - idle, prepare
- poll: 等待回調
- 執(zhí)行回調
-
- 執(zhí)行定時器
- 如有到期的
setTimeout / setInterval, 則返回 timer 階段 - 如有
setImmediate,則前往 check 階段
- check
- 執(zhí)行
setImmediate
- 執(zhí)行
- close callbacks
8. HTTP2,詳細參考
- 二進制分幀
- 頭部壓縮
- 服務端推送
- 多路復用
- 優(yōu)化手段
跨域
- JSONP: 利用
<script>標簽不受跨域限制的特點,缺點是只能支持 get 請求
function jsonp(url, jsonpCallback, success) {
const script = document.createElement("script");
script.src = url;
script.async = true;
script.type = "text/javascript";
window[jsonpCallback] = function(data) {
success && success(data);
};
document.body.appendChild(script);
}
- 設置 CORS: Access-Control-Allow-Origin:*
- postMessage
安全
- XSS 攻擊: 注入惡意代碼
- cookie 設置 httpOnly
- 轉義頁面上的輸入內容和輸出內容
- CSRF(騰訊 3.5,詳細參考): 跨站請求偽造,防護:
- get 不修改數(shù)據(jù)
- 不被第三方網(wǎng)站訪問到用戶的 cookie
- 設置白名單,不被第三方網(wǎng)站請求
- 請求校驗
框架:Vue
1. nextTick
在下次dom更新循環(huán)結束之后執(zhí)行延遲回調,可用于獲取更新后的dom狀態(tài)
新版本中默認是
mincrotasks,v-on中會使用macrotasks-
macrotasks任務的實現(xiàn):setImmediate / MessageChannel / setTimeout
2. 生命周期
-
_init_-
initLifecycle/Event,往vm上掛載各種屬性 -
callHook: beforeCreated: 實例剛創(chuàng)建 -
initInjection/initState: 初始化注入和 data 響應性 -
created: 創(chuàng)建完成,屬性已經(jīng)綁定, 但還未生成真實dom - 進行元素的掛載:
$el / vm.$mount() - 是否有
template: 解析成render function-
*.vue文件:vue-loader會將<template>編譯成render function
-
-
beforeMount: 模板編譯/掛載之前 - 執(zhí)行
render function,生成真實的dom,并替換到dom tree中 -
mounted: 組件已掛載
-
-
update:- 執(zhí)行
diff算法,比對改變是否需要觸發(fā) UI 更新 -
flushScheduleQueue-
watcher.before: 觸發(fā)beforeUpdate鉤子 -watcher.run(): 執(zhí)行watcher中的notify,通知所有依賴項更新 UI
-
- 觸發(fā)
updated鉤子: 組件已更新
- 執(zhí)行
actived / deactivated(keep-alive): 不銷毀,緩存,組件激活與失活-
destroy:-
beforeDestroy: 銷毀開始 - 銷毀自身且遞歸銷毀子組件以及事件監(jiān)聽
-
remove(): 刪除節(jié)點 -
watcher.teardown(): 清空依賴 -
vm.$off(): 解綁監(jiān)聽
-
-
destroyed: 完成后觸發(fā)鉤子
-
上面是vue的聲明周期的簡單梳理,接下來我們直接以代碼的形式來完成vue的初始化
new Vue({})
// 初始化 Vue 實例
function \_init() {
// 掛載屬性
initLifeCycle(vm)
// 初始化事件系統(tǒng),鉤子函數(shù)等
initEvent(vm)
// 編譯 slot、vnode
initRender(vm)
// 觸發(fā)鉤子
callHook(vm, 'beforeCreate')
// 添加 inject 功能
initInjection(vm)
// 完成數(shù)據(jù)響應性 props/data/watch/computed/methods
initState(vm)
// 添加 provide 功能
initProvide(vm)
// 觸發(fā)鉤子
callHook(vm, 'created')
// 掛載節(jié)點
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
// 掛載節(jié)點實現(xiàn)
function mountComponent(vm) {
// 獲取 render function
if (!this.options.render) {
// template to render
// Vue.compile = compileToFunctions
let { render } = compileToFunctions()
this.options.render = render
}
// 觸發(fā)鉤子
callHook('beforeMounte')
// 初始化觀察者
// render 渲染 vdom,
vdom = vm.render()
// update: 根據(jù) diff 出的 patchs 掛載成真實的 dom
vm.\_update(vdom)
// 觸發(fā)鉤子
callHook(vm, 'mounted')
}
// 更新節(jié)點實現(xiàn)
funtion queueWatcher(watcher) {
nextTick(flushScheduleQueue)
}
// 清空隊列
function flushScheduleQueue() {
// 遍歷隊列中所有修改
for(){
// beforeUpdate
watcher.before()
// 依賴局部更新節(jié)點
watcher.update()
callHook('updated')
}
}
// 銷毀實例實現(xiàn)
Vue.prototype.$destory = function() {
// 觸發(fā)鉤子
callHook(vm, 'beforeDestory')
// 自身及子節(jié)點
remove()
// 刪除依賴
watcher.teardown()
// 刪除監(jiān)聽
vm.$off()
// 觸發(fā)鉤子
callHook(vm, 'destoryed')
}
3. 數(shù)據(jù)響應(數(shù)據(jù)劫持)
看完生命周期后,里面的watcher等內容其實是數(shù)據(jù)響應中的一部分。數(shù)據(jù)響應的實現(xiàn)由兩部分構成: 觀察者( watcher ) 和 依賴收集器( Dep ),其核心是 defineProperty這個方法,它可以 重寫屬性的 get 與 set 方法,從而完成監(jiān)聽數(shù)據(jù)的改變。
- Observe (觀察者)觀察 props 與 state
- 遍歷 props 與 state,對每個屬性創(chuàng)建獨立的監(jiān)聽器( watcher )
- 使用
defineProperty重寫每個屬性的 get/set(defineReactive)-
get: 收集依賴-
Dep.depend()watcher.addDep()
-
-
set: 派發(fā)更新Dep.notify()watcher.update()queenWatcher()nextTickflushScheduleQueuewatcher.run()updateComponent()
-
大家可以先看下面的數(shù)據(jù)相應的代碼實現(xiàn)后,理解后就比較容易看懂上面的簡單脈絡了。
let data = { a: 1 };
// 數(shù)據(jù)響應性
observe(data);
// 初始化觀察者
new Watcher(data, "name", updateComponent);
data.a = 2;
// 簡單表示用于數(shù)據(jù)更新后的操作
function updateComponent() {
vm._update(); // patchs
}
// 監(jiān)視對象
function observe(obj) {
// 遍歷對象,使用 get/set 重新定義對象的每個屬性值
Object.keys(obj).map(key => {
defineReactive(obj, key, obj[key]);
});
}
function defineReactive(obj, k, v) {
// 遞歸子屬性
if (type(v) == "object") observe(v);
// 新建依賴收集器
let dep = new Dep();
// 定義get/set
Object.defineProperty(obj, k, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 當有獲取該屬性時,證明依賴于該對象,因此被添加進收集器中
if (Dep.target) {
dep.addSub(Dep.target);
}
return v;
},
// 重新設置值時,觸發(fā)收集器的通知機制
set: function reactiveSetter(nV) {
v = nV;
dep.nofify();
}
});
}
// 依賴收集器
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
notify() {
this.subs.map(sub => {
sub.update();
});
}
}
Dep.target = null;
// 觀察者
class Watcher {
constructor(obj, key, cb) {
Dep.target = this;
this.cb = cb;
this.obj = obj;
this.key = key;
this.value = obj[key];
Dep.target = null;
}
addDep(Dep) {
Dep.addSub(this);
}
update() {
this.value = this.obj[this.key];
this.cb(this.value);
}
before() {
callHook("beforeUpdate");
}
}
4. virtual dom 原理實現(xiàn)(騰訊 3.5 面試)
創(chuàng)建 dom 樹
-
樹的
diff,同層對比,輸出patchs(listDiff/diffChildren/diffProps)- 沒有新的節(jié)點,返回
- 新的節(jié)點
tagName與key不變, 對比props,繼續(xù)遞歸遍歷子樹- 對比屬性(對比新舊屬性列表):
- 舊屬性是否存在與新屬性列表中
- 都存在的是否有變化
- 是否出現(xiàn)舊列表中沒有的新屬性
- 對比屬性(對比新舊屬性列表):
-
tagName和key值變化了,則直接替換成新節(jié)點
-
渲染差異
- 遍歷
patchs, 把需要更改的節(jié)點取出來 - 局部更新
dom
- 遍歷
// diff 算法的實現(xiàn)
function diff(oldTree, newTree) {
// 差異收集
let pathchs = {};
dfs(oldTree, newTree, 0, pathchs);
return pathchs;
}
function dfs(oldNode, newNode, index, pathchs) {
let curPathchs = [];
if (newNode) {
// 當新舊節(jié)點的 tagName 和 key 值完全一致時
if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
// 繼續(xù)比對屬性差異
let props = diffProps(oldNode.props, newNode.props);
curPathchs.push({ type: "changeProps", props });
// 遞歸進入下一層級的比較
diffChildrens(oldNode.children, newNode.children, index, pathchs);
} else {
// 當 tagName 或者 key 修改了后,表示已經(jīng)是全新節(jié)點,無需再比
curPathchs.push({ type: "replaceNode", node: newNode });
}
}
// 構建出整顆差異樹
if (curPathchs.length) {
if (pathchs[index]) {
pathchs[index] = pathchs[index].concat(curPathchs);
} else {
pathchs[index] = curPathchs;
}
}
}
// 屬性對比實現(xiàn)
function diffProps(oldProps, newProps) {
let propsPathchs = [];
// 遍歷新舊屬性列表
// 查找刪除項
// 查找修改項
// 查找新增項
forin(olaProps, (k, v) => {
if (!newProps.hasOwnProperty(k)) {
propsPathchs.push({ type: "remove", prop: k });
} else {
if (v !== newProps[k]) {
propsPathchs.push({ type: "change", prop: k, value: newProps[k] });
}
}
});
forin(newProps, (k, v) => {
if (!oldProps.hasOwnProperty(k)) {
propsPathchs.push({ type: "add", prop: k, value: v });
}
});
return propsPathchs;
}
// 對比子級差異
function diffChildrens(oldChild, newChild, index, pathchs) {
// 標記子級的刪除/新增/移動
let { change, list } = diffList(oldChild, newChild, index, pathchs);
if (change.length) {
if (pathchs[index]) {
pathchs[index] = pathchs[index].concat(change);
} else {
pathchs[index] = change;
}
}
// 根據(jù) key 獲取原本匹配的節(jié)點,進一步遞歸從頭開始對比
oldChild.map((item, i) => {
let keyIndex = list.indexOf(item.key);
if (keyIndex) {
let node = newChild[keyIndex];
// 進一步遞歸對比
dfs(item, node, index, pathchs);
}
});
}
// 列表對比,主要也是根據(jù) key 值查找匹配項
// 對比出新舊列表的新增/刪除/移動
function diffList(oldList, newList, index, pathchs) {
let change = [];
let list = [];
const newKeys = getKey(newList);
oldList.map(v => {
if (newKeys.indexOf(v.key) > -1) {
list.push(v.key);
} else {
list.push(null);
}
});
// 標記刪除
for (let i = list.length - 1; i >= 0; i--) {
if (!list[i]) {
list.splice(i, 1);
change.push({ type: "remove", index: i });
}
}
// 標記新增和移動
newList.map((item, i) => {
const key = item.key;
const index = list.indexOf(key);
if (index === -1 || key == null) {
// 新增
change.push({ type: "add", node: item, index: i });
list.splice(i, 0, key);
} else {
// 移動
if (index !== i) {
change.push({
type: "move",
form: index,
to: i
});
move(list, index, i);
}
}
});
return { change, list };
}
5. Proxy 相比于 defineProperty 的優(yōu)勢
- 數(shù)組變化也能監(jiān)聽到
- 不需要深度遍歷監(jiān)聽
let data = { a: 1 };
let reactiveData = new Proxy(data, {
get: function(target, name) {
// ...
}
// ...
});
6. vue-router
-
modehashhistory
- 跳轉
this.$router.push()<router-link to=""></router-link>
- 占位
<router-view></router-view>
7. vuex
-
state: 狀態(tài)中心 -
mutations: 更改狀態(tài) -
actions: 異步更改狀態(tài) -
getters: 獲取狀態(tài) -
modules: 將state分成多個modules,便于管理
算法
其實算法方面在前端的實際項目中涉及得并不多,但還是需要精通一些基礎性的算法,一些公司還是會有這方面的需求和考核,建議大家還是需要稍微準備下,這屬于加分題。
1. 五大算法
- 貪心算法: 局部最優(yōu)解法
- 分治算法: 分成多個小模塊,與原問題性質相同
- 動態(tài)規(guī)劃: 每個狀態(tài)都是過去歷史的一個總結
- 回溯法: 發(fā)現(xiàn)原先選擇不優(yōu)時,退回重新選擇
- 分支限界法
2. 基礎排序算法
- 冒泡排序: 兩兩比較
function bubleSort(arr) {
var len = arr.length;
for (let outer = len; outer >= 2; outer--) {
for (let inner = 0; inner <= outer - 1; inner++) {
if (arr[inner] > arr[inner + 1]) {
[arr[inner], arr[inner + 1]] = [arr[inner + 1], arr[inner]];
}
}
}
return arr;
}
- 選擇排序: 遍歷自身以后的元素,最小的元素跟自己調換位置
function selectSort(arr) {
var len = arr.length;
for (let i = 0; i < len - 1; i++) {
for (let j = i; j < len; j++) {
if (arr[j] < arr[i]) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
}
return arr;
}
- 插入排序: 即將元素插入到已排序好的數(shù)組中
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
//外循環(huán)從 1 開始,默認 arr[0]是有序段
for (let j = i; j > 0; j--) {
//j = i,將 arr[j]依次插入有序段中
if (arr[j] < arr[j - 1]) {
[arr[j], arr[j - 1]] = [arr[j - 1], arr[j]];
} else {
break;
}
}
}
return arr;
}
3. 高級排序算法
- 快速排序
- 選擇基準值(base),原數(shù)組長度減一(基準值),使用 splice
- 循環(huán)原數(shù)組,小的放左邊(left 數(shù)組),大的放右邊(right 數(shù)組);
- concat(left, base, right)
- 遞歸繼續(xù)排序 left 與 right
function quickSort(arr) {
if (arr.length <= 1) {
return arr; //遞歸出口
}
var left = [],
right = [],
current = arr.splice(0, 1);
for (let i = 0; i < arr.length; i++) {
if (arr[i] < current) {
left.push(arr[i]); //放在左邊
} else {
right.push(arr[i]); //放在右邊
}
}
return quickSort(left).concat(current, quickSort(right));
}
希爾排序:不定步數(shù)的插入排序,插入排序
口訣: 插冒歸基穩(wěn)定,快選堆希不穩(wěn)定

穩(wěn)定性: 同大小情況下是否可能會被交換位置, 虛擬 dom 的 diff,不穩(wěn)定性會導致重新渲染;
4. 遞歸運用(斐波那契數(shù)列): 爬樓梯問題
初始在第一級,到第一級有 1 種方法(s(1) = 1),到第二級也只有一種方法(s(2) = 1), 第三級(s(3) = s(1) + s(2))
function cStairs(n) {
if (n === 1 || n === 2) {
return 1;
} else {
return cStairs(n - 1) + cStairs(n - 2);
}
}
5. 數(shù)據(jù)樹
- 二叉樹: 最多只有兩個子節(jié)點
- 完全二叉樹
- 滿二叉樹
- 深度為 h, 有 n 個節(jié)點,且滿足 n = 2^h - 1
- 二叉查找樹: 是一種特殊的二叉樹,能有效地提高查找效率
- 小值在左,大值在右
- 節(jié)點 n 的所有左子樹值小于 n,所有右子樹值大于 n

- 遍歷節(jié)點
- 前序遍歷
- 根節(jié)點
- 訪問左子節(jié)點,回到 1
- 訪問右子節(jié)點,回到 1
- 中序遍歷
- 先訪問到最左的子節(jié)點
- 訪問該節(jié)點的父節(jié)點
- 訪問該父節(jié)點的右子節(jié)點, 回到 1
- 后序遍歷
- 先訪問到最左的子節(jié)點
- 訪問相鄰的右節(jié)點
- 訪問父節(jié)點, 回到 1
- 前序遍歷
- 插入與刪除節(jié)點
6. 天平找次品
有 n 個硬幣,其中 1 個為假幣,假幣重量較輕,你有一把天平,請問,至少需要稱多少次能保證一定找到假幣?
- 三等分算法:
-
- 將硬幣分成 3 組,隨便取其中兩組天平稱量
- 平衡,假幣在未上稱的一組,取其回到 1 繼續(xù)循環(huán)
- 不平衡,假幣在天平上較輕的一組, 取其回到 1 繼續(xù)循環(huán)
-
7. 大整數(shù)相加(騰訊 3.5 面試題)
function sum(a, b) {
var res = "",
temp = 0;
const aArr = `${a}`.split("");
const bArr = `$`.split("");
const langArr = aArr.length > bArr.length ? aArr : bArr;
const shortArr = aArr.length > bArr.length ? bArr : aArr;
while (shortArr.length || temp) {
temp += ~~aArr.pop() + ~~bArr.pop();
res = (temp % 10) + res;
temp = temp > 9;
}
res = langArr.join("") + res;
return res.replace(/^0+/, "");
}
// 面試官說還有更優(yōu)的算法,我想了下應該可以對數(shù)字分段補0相加的方式。懶得寫了
前端性能優(yōu)化(重要重要重要,騰訊就問這個??!)
參考文章:
https://juejin.im/post/59672fbff265da6c3f70cd53
1. 瀏覽器渲染頁面
- CSS 為什么要放到<head>里面
- js 放到</body>前面
- js 的異步加載(async、defer)等優(yōu)化
2. 減少 HTTP 請求
- CSS/JS 合并打包
- 小圖標等用 iconfont 代替
- 使用 base64 格式的圖片
- 使用 HTTP2,減少同域 TCP 連接次數(shù)
3. 減少靜態(tài)資源體積
- 壓縮靜態(tài)資源
- 編寫高效率的 CSS,減少嵌套
- 服務端開啟
gzip壓縮
4. 使用緩存
詳細參考:緩存策略
5. 內存溢出
- Performance 面板使用
- Memory 面板使用
- 最好舉個項目中的例子說明
前端性能監(jiān)控(重要重要重要,騰訊就問這個??!)
參考文章:
https://juejin.im/post/5b7a50c0e51d4538af60d995
結語
由于精力時間及篇幅有限,這篇就先寫到這。大家慢慢來不急。。??。下篇打算準備以下內容,我也得補補課先:
- Webpack 相關
- 原理
- Loader
- Plugin
- 項目性能優(yōu)化
- 首屏渲染優(yōu)化
- 用戶體驗優(yōu)化
- webpack 性能優(yōu)化
- Hybrid 與 Webview
- webview 加載過程
- bridge 原理
- hybrid app 經(jīng)驗
- 框架: React
在面試中,很多領域并沒有真正的答案,能回答到什么樣的深度,還是得靠自己真正的去使用和研究。知識面的廣度與深度應該并行,盡量的拓張自己的領域,至少都有些基礎性的了解,在被問到的時候可以同面試官嘮嗑兩句,然后在自己喜歡的領域,又有著足夠深入的研究,讓面試官覺得你是這方面的專家。
知識大綱還在不斷的完善和修正,由于也是精力時間有限,我會慢慢補充后面列出來的部分。當然,我也是在整理中不斷的學習,也希望大家能一起參與進來,要補充或修正的地方麻煩趕緊提出。另外,剛新建了個公眾號,想作為大家交流和分享的地方,有興趣想法的童鞋聯(lián)系我哈~~??
作者:郭東東
鏈接:https://juejin.im/post/5c64d15d6fb9a049d37f9c20
來源:掘金
著作權歸作者所有。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處。