概述
- 概念題 => 是什么 + 怎么做 + 解決了什么問題 + 優(yōu)點 + 缺點 + 怎么解決缺點
HTML
如何理解 HTML 中的語義化標簽
- 語義化標簽是一種寫 HTML 標簽的方法論
- 實現(xiàn)方法是遇到標題就用 h1 到 h6昌执,遇到段落用 p要门,遇到文檔用 article行贪,主要內(nèi)容用 main诺舔,邊欄用 aside,導航用 nav
- 它主要是明確了 HTML 的書寫規(guī)范
- 優(yōu)點在于 1. 適合搜索引擎檢索 2. 適合人類閱讀,利于團隊維護
HTML5 有哪些新標簽
文章相關:header恕酸、main、footer胯陋、nav蕊温、section袱箱、article
多媒體相關:video、audio义矛、svg发笔、canvas
Canvas 和 SVG 的區(qū)別是什么?
- Canvas 主要是用筆刷來繪制 2D 圖形的
- SVG 主要是用標簽來繪制不規(guī)則矢量圖的
- 相同點:都是主要用來畫 2D 圖形的
- 不同點
- SVG 畫的是矢量圖凉翻,Canvas 畫的是位圖
- SVG 節(jié)點多時渲染慢了讨,Canvas 性能更好一點,但寫起來更復雜
- SVG 支持分層和事件制轰,Canvas 不支持前计,但是可以用庫實現(xiàn)
CSS
BFC 是什么
BFC 是 Block Formatting Context,是塊級格式化上下文垃杖。以下可以觸發(fā) BFC
- 浮動元素(float 值不為 none)
- 絕對定位元素(position 值為 absolute 或 fixed)
- inline-block 行內(nèi)塊元素
- overflow 值不為 visible男杈、clip 的塊元素
- 彈性元素(display 值為 flex 或 inline-flex 元素的直接子元素)
BFC 可以解決 1. 清除浮動 2. 防止 margin 合并 的問題
但是它有相應的副作用,可以使用最新的 display: flow-root 來觸發(fā) BFC调俘,該屬性專門用來觸發(fā) BFC
如何實現(xiàn)垂直居中
- flex
- position + transform
CSS 選擇器優(yōu)先級如何確定
- 選擇器越具體伶棒,其優(yōu)先級越高
- 相同優(yōu)先級,出現(xiàn)在后面的彩库,覆蓋前面的
- 屬性后面加 !important 的優(yōu)先級最高肤无,但是要少用
如何清除浮動
.clearfix:after {
content: '';
display: block;
clear: both;
}
兩種盒模型區(qū)別
- content-box => width 和 height 只包含內(nèi)容的寬和高,不包括邊框和內(nèi)邊距骇钦。case:{width: 350px, border: 10px solid red;} 實際寬度為 370
- border-box => width 和 height 包含內(nèi)容宛渐、內(nèi)邊距和邊框。case:{width: 350px, border: 10px solid red;} 實際寬度為 350
JS
JS 的數(shù)據(jù)類型
基本數(shù)據(jù)類型:number/boolean/string/null/undefined/Symbol/BigInt(任意精度的整數(shù))
引用數(shù)據(jù)類型:Object
判斷數(shù)據(jù)類型
- typeof => 返回一個字符串司忱,表示操作數(shù)的類型
- typeof null === 'object'
- typeof <function> === 'function'
- instanceof => 在原型鏈中查找是否是其實例 => object instanceof constructor
- 判斷是否是數(shù)組
- arr instanceof Array
- arr.constructor === Array
- Array.isArray(arr)
- Object.prototype.toString.call(arr) === '[object Array]'
原型鏈是什么皇忿?
- case:const a = {}畴蹭,此時 a.proto == Object.prototype坦仍,即 a 的原型是 Object.prototype
- case:我們有一個數(shù)組對象,const a = []叨襟,此時 a.proto == Array.prototype繁扎,此時 a 的原型是 Array.prototype,此時 Array.prototype.proto == Object.prototype糊闽,此時:
- a 的原型是 Array.prototype
- a 的原型的原型是 Object.prototype
- 于是形成了一條原型鏈
- 可以通過 const x = Object.create(原型) 或者 const x = new 構(gòu)造函數(shù)() 的方式改變 x 的原型
- const x = Object.create(原型) => x.proto == 原型
- const x = new 構(gòu)造函數(shù)() => x.proto == 構(gòu)造函數(shù).prototype
- 原型鏈可以實現(xiàn)繼承梳玫,以上面的數(shù)組為例:a ===> Array.prototype ===> Object.prototype
- a 是 Array 的實例,a 擁有 Array.prototype 里的屬性
- Array 繼承了 Object
- a 是 Object 的間接實例右犹,a 也就擁有 Object.prototype 里的屬性
- a 即擁有了 Array.prototype 的屬性提澎,也擁有了 Object.prototype 的屬性
- 原型鏈的優(yōu)點在于:簡單優(yōu)雅
- 但是不支持私有屬性,ES6新增加的 class 可以支持私有屬性
代碼中的 this 是什么念链?
- 將所有的函數(shù)調(diào)用轉(zhuǎn)化為 call => this 就是 call 的第一個參數(shù)
- func(p1, p2) => func.call(undefined, p1, p2) => 如果 context 是 null 或 undefined盼忌,window 是默認的 context(嚴格模式下默認是 undefined)
- obj.child.method(p1, p2) => obj.child.method.call(obj.child, p1, p2)
JS 的 new 做的什么积糯?
function Person(name) {
this.name = name;
}
const ming = new Person("ming");
const ming = (function (name) {
// 1. var temp = {}; => 創(chuàng)建臨時對象
// 2. this = temp; => 指定 this = 臨時對象
this.name = name;
// 3. Person.prototype = {...Person.prototype, constructor: Person} => 執(zhí)行構(gòu)造函數(shù)
// 4. this.__proto__ == Person.prototype => 綁定原型
// return this; => 返回臨時對象
})("ming");
JS 的立即執(zhí)行函數(shù)是什么?
- 聲明一個匿名函數(shù)谦纱,然后立即執(zhí)行它看成,這種做法就是立即執(zhí)行函數(shù)
- 例如: 每一行代碼都是一個立即執(zhí)行函數(shù)
- (function() {} ())
- (function() {})()
- !function() {}()
- +function() {}()
- -function() {}()
- ~function() {}()
- 在 ES6 之前只能通過立即執(zhí)行函數(shù)來創(chuàng)建局部作用域
- 其優(yōu)點在于兼容性好
- 目前可以使用 ES6 的 block + let 代替
{ let a = '局部變量'; console.log(a); // 局部變量 } console.log(a); // Uncaught ReferenceError: a is not defined
JS 的閉包是什么?
- 閉包是 JS 的一種語法特性跨嘉,閉包 = 函數(shù) + 自由變量川慌。對于一個函數(shù)來說,變量分為:全局變量祠乃、本地變量梦重、自由變量
- case:閉包就是 count + add 組成的整體
const add2 = (function() { var count = 0; return function add() { count++; } })() // 此時 add2 就是 add add2(); // 相當于 add(); // 相當于 count++;
- 以上就是一個完整的閉包的應用
- 閉包解決了
- 避免污染全局環(huán)境 => 因為使用了局部變量
- 提供對局部變量的間接訪問 => 只能 count++,不能 count--
- 維持變量跳纳,使其不被垃圾回收
- 其優(yōu)點是:簡單好用
- 但是閉包使用不當可能造成內(nèi)存泄漏忍饰。case:
function test() { var x = {name: 'x'}; var y = {name: 'y', content: '這里很長很長,占用了很多很多字節(jié)'} return function fn() { return x; } } const myFn = test(); // myFn 就是 fn 了 const myX = myFn(); // myX 就是 x 了
- 對于正常的瀏覽器來說寺庄,y會在一段時間內(nèi)自動消失艾蓝,被垃圾回收器回收,但是舊版本的 IE 瀏覽器不會回收斗塘,這是 IE 瀏覽器的問題
JS 如何實現(xiàn)類
- 使用原型
function Dog(name) {
this.name = name;
this.legsNum = 4;
}
Dog.prototype.kind = 'dog';
Dog.prototype.run = function () {
console.log("I am running with " + this.legsNum + " legs.")
}
Dog.prototype.say = function () {
console.log("Wang Wang, I am " + this.name);
}
const dog = new Dog("ming");
dog.say();
- 使用類
class Dog {
kind = 'dog';
constructor(name) {
this.name = name;
this.legsNum = 4;
}
run() {
console.log("I am running with " + this.legsNum + " legs.")
}
say() {
console.log("Wang Wang, I am " + this.name);
}
}
const dog = new Dog('ming');
dog.say();
JS 實現(xiàn)繼承
- 使用原型鏈
// dog => Dog => Animal
function Animal(legsNum) {
this.legsNum = legsNum;
}
Animal.prototype.kind = 'animal';
Animal.prototype.run = function () {
console.log("I am running with " + this.legsNum + " legs.");
}
function Dog(name) {
this.name = name;
Animal.call(this, 4); // 繼承屬性
}
// Dog.prototype.__proto__ == Animal.prototype
const temp = function () {}
temp.prototype = Animal.prototype;
Dog.prototype = new temp();
Dog.prototype.kind = 'dog';
Dog.prototype.say = function () {
console.log("Wang Wang, I am " + this.name);
}
const dog = new Dog("ming"); // Dog 函數(shù)就是一個類
console.log(dog);
- 使用類
class Animal {
kind = 'animal';
constructor(legsNum) {
this.legsNum = legsNum;
}
run() {
console.log("I am running with " + this.legsNum + " legs.");
}
}
class Dog extends Animal {
kind = 'dog';
constructor(name) {
super(4);
this.name = name;
}
say() {
console.log("Wang Wang, I am " + this.name);
}
}
const dog = new Dog('ming');
console.log(dog);
JS 手寫節(jié)流 & 防抖
- 節(jié)流 throttle => 技能冷卻中 => 場景
- Select 去服務端動態(tài)搜索
- 按鈕用戶點擊過快赢织,發(fā)送多次請求
function throttle(time, callback) {
let flag = true;
return (...args) => {
if (flag) {
flag = false;
callback(args);
setTimeout(() => {
flag = true;
}, time);
}
}
}
const fn = throttle(2000, () => {console.log("Hello!")});
fn();
fn();
setTimeout(fn, 3000);
- 防抖 debounce => 回城被打斷 => 場景
- 滾動事件
function debounce(time, callback) {
let timer;
return (...args) => {
timer && clearTimeout(timer);
timer = setTimeout(() => {
callback(args);
}, time);
}
}
const fn = debounce(2000, () => console.log("Hello!"));
fn();
setTimeout(fn, 1000);
JS 手寫發(fā)布訂閱
const eventBus = {
bus: {},
on(eventName, callback) {
if (!this.bus[eventName]) {
this.bus[eventName] = [callback];
} else {
this.bus[eventName].push(callback);
}
},
emit(eventName, data) {
if (!this.bus[eventName]) {
throw new Error("Please check eventName " + eventName);
}
this.bus[eventName].forEach(callback => callback.call(null, data));
},
off(eventName, callback) {
if (!this.bus[eventName]) {
throw new Error("Please check eventName " + eventName);
}
const index = this.bus[eventName].indexOf(callback);
if (index < 0) {
return;
}
this.bus[eventName].splice(index, 1);
}
}
eventBus.on('click', console.log)
eventBus.on('click', console.error)
setTimeout(() => {
eventBus.emit('click', 'Hello!');
}, 3000)
JS 手寫 AJAX
const ajax = (method, url, data, success, fail) => {
const request = new XMLHttpRequest();
request.open(method, url);
request.onreadystatechange = function () {
if (request.readyState === 4) {
if (request.status >= 200 && request.status < 300 || request.status === 304) {
success(request);
} else {
fail(request);
}
}
}
if (method === "post") {
request.send(data);
} else {
request.send();
}
}
JS 手寫簡化版 Promise
class Promise2 {
#status = 'pending';
constructor(fn) {
this.queue = [];
const resolve = (data) => {
this.#status = 'fulfilled';
const f1f2 = this.queue.shift();
if (!f1f2 || !f1f2[0]) return;
const x = f1f2[0].call(undefined, data);
if (x instanceof Promise2) {
x.then(data => resolve(data), reason => reject(reason));
} else {
resolve(x);
}
}
const reject = (reason) => {
this.#status = 'rejected';
const f1f2 = this.queue.shift();
if (!f1f2 || !f1f2[1]) return;
const x = f1f2[1].call(undefined, reason);
if (x instanceof Promise2) {
x.then(data => resolve(data), reason => reject(reason));
} else {
resolve(x);
}
}
fn.call(undefined, resolve, reject);
}
then(f1, f2) {
this.queue.push([f1, f2]);
}
}
const p = new Promise2((resolve, reject) => {
setTimeout(() => {
reject('Error!');
}, 3000);
});
p.then((data) => console.log(data), error => console.error(error));
JS 手寫 Promise.all
- 要在 Promise 上寫而不是在原型上寫
- Promise.all 參數(shù)(Promise 數(shù)組)和返回值(新 Promise 對象)
- 用數(shù)組記錄結(jié)果
- 只要有一個 reject 就整體 reject
Promise.myAll = function (list) {
const results = [];
let count = 0;
return new Promise((resolve, reject) => {
list.map((promise, index) => {
promise.then((result) => {
results[index] = result;
count++;
if (count >= list.length) {
resolve(results);
}
}, reason => reject(reason));
});
});
}
JS 手寫深拷貝
JSON
const copy = JSON.parse(JSON.stringify(a));
缺點:
- 不支持 Date、正則馍盟、undefined于置、函數(shù)等數(shù)據(jù)
- 不支持引用,即環(huán)狀結(jié)構(gòu)
遞歸贞岭。要點:
- 判斷類型
- 檢查環(huán)
- 不拷貝原型上的屬性
const deepCopy = (a, cache) => {
if (!cache) {
cache = new Map();
}
// 不考慮跨 iframe
if (a instanceof Object) {
if (cache.get(a)) {
return cache.get(a);
}
let result = null;
if (a instanceof Function) {
// 有 prototype 就是普通函數(shù)
if (a.prototype) {
result = function () {
return a.apply(this, arguments);
}
} else {
result = (...args) => {
return a.call(undefined, ...args);
}
}
} else if (a instanceof Array) {
result = [];
} else if (a instanceof Date) {
result = new Date(a - 0);
} else if (a instanceof RegExp) {
result = new RegExp(a.source, a.flags);
} else {
result = {};
}
cache.set(a, result);
for (let key in a) {
if (a.hasOwnProperty(key)) {
result[key] = deepCopy(a[key], cache);
}
}
return result;
}
return a;
}
const a = {
number: 1, bool: false, str: 'hi', empty1: undefined, empty2: null,
array: [
{name: 'frank', age: 18},
{name: 'jacky', age: 19}
],
date: new Date(2000, 0, 1, 20, 30, 0),
regex: /\.(j|t)sx/i,
obj: {name: 'frank', age: 18},
f1: (a, b) => a + b,
f2: function (a, b) { return a + b }
}
a.self = a;
const b = deepCopy(a);
console.log(b.self === b); // true
b.self = 'hi'
console.log(a.self !== 'hi'); //true
JS 手寫數(shù)組去重
const unique = (nums) => {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
if (nums[i] === undefined || map.has(nums[i])) {
continue;
}
map.set(nums[i], true);
}
return [...map.keys()];
}
DOM
DOM 事件模型
- 先經(jīng)歷從上到下的捕獲階段八毯,再經(jīng)歷從下到上的冒泡階段
- addEventListener("click", fn, options, useCapture)
- options 中有一個 capture 參數(shù),true 表示捕獲階段瞄桨,false 表示冒泡階段
- useCapture true 表示捕獲階段话速,false 表示冒泡階段
- 可以使用 event.stopPropagation() 來阻止捕獲或冒泡
手寫事件委托
ul.addEventListener('click', (e) => {
if (e.target.tagName.toLowerCase() === 'li') {
// do something
}
});
- 如果點擊 li 里面的 span,就沒有辦法觸發(fā)事件
- 點擊元素之后芯侥,遞歸遍歷點擊元素的祖先元素直至遇到 li 或者 ul
const delegate = (element, eventType, selector, fn) => {
element.addEventListener(eventType, e => {
let ele = el.target;
while (!ele.matches(selector)) {
if (ele === element) {
return;
}
ele = ele.parentNode;
}
fn.call(ele, e);
});
return element;
}
delegate(ul, 'click', 'li', (e) => console.log(e));
- 事件委托優(yōu)點
- 節(jié)省監(jiān)聽器
- 實現(xiàn)動態(tài)監(jiān)聽
- 事件委托缺點 => 調(diào)試比較復雜泊交,不容易確定監(jiān)聽者
手寫可拖曳 div
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Drag</title>
<style>
.drag {
border: 1px solid red;
position: absolute;
top: 0;
left: 0;
width: 100px;
height: 100px;
}
</style>
</head>
<body>
<div class="drag"></div>
<script>
let dragging = false;
let position = null;
const dragEle = document.querySelector('.drag');
dragEle.addEventListener("mousedown", e => {
dragging = true;
position = [e.clientX, e.clientY];
});
document.addEventListener("mousemove", e => {
if (!dragging) return;
const x = e.clientX;
const y = e.clientY;
const moveX = x - position[0];
const moveY = y - position[1];
const left = parseInt(dragEle.style.left || 0);
const top = parseInt(dragEle.style.top || 0);
dragEle.style.left = left + moveX + 'px';
dragEle.style.top = top + moveY + 'px';
position = [x, y];
});
document.addEventListener('mouseup', e => {
dragging = false;
})
</script>
</body>
</html>
HTTP
HTTP status code
- 200 OK
- 201 Created
- 204 No Content
- 301 Move Permanently
- 302 Found
- 304 Not Modify
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 409 Conflict
- 410 Gone
- 414 URI Too Long
- 415 Unsupported Media Type
- 500 Internal Server Error
- 502 Bad Gateway
- 504 Gateway Timeout
GET 和 POST 的區(qū)別
- 根據(jù)技術文檔規(guī)格,GET 和 POST 最大區(qū)別就是語義柱查,一個讀一個寫
- 實踐上會有很多區(qū)別廓俭,如:
- 由于 GET 是讀,POST 是寫唉工。所以 GET 是冪等的研乒,POST 是不冪等的
- 由于 GET 是讀,POST 是寫淋硝。所以 GET 結(jié)果會被緩存雹熬,POST 結(jié)果不會被緩存
- 由于 GET 是讀错维,POST 是寫。所以 GET 打開的頁面刷新是無害的橄唬,POST 打開的頁面刷新需要確認
- 通常情況下赋焕,GET 請求參數(shù)放置在 URL 里,POST 請求參數(shù)放在 body 里
- GET 比 POST 更不安全仰楚,因為參數(shù)直接暴露在 URL 上隆判,所以不能用來傳遞敏感信息
- GET 請求參數(shù)放在 URL 里是有長度限制的(瀏覽器限制的,414 URI to long)僧界,而 POST 放在 body 里沒有長度限制(長度其實可是配置)
- GET 產(chǎn)生一個 TCP 數(shù)據(jù)包侨嘀,POST 產(chǎn)生兩個或以上 TCP 數(shù)據(jù)包
簡單請求 vs 復雜請求
- 簡單請求不會觸發(fā) CORS 預檢請求
- 以下條件是簡單請求:
- method => GET | POST
- header => 需要關注 Content-Type => text/plain | multipart/form-data | application/x-www-form-urlencoded
- 復雜請求會觸發(fā) CORS 預檢請求
- 預檢請求 => 首先使用 OPTION 方法發(fā)起一個預檢請求到服務器,已獲知服務器是否允許該實際請求
Cookie
- Cookie 是服務器發(fā)送到用戶瀏覽器并保存在本地的一小塊數(shù)據(jù)捂襟,它會在瀏覽器下次向同一服務器再發(fā)起請求時被攜帶并發(fā)送到服務器上
- 通過 Set-Cookie 設置咬腕,是一個 key=value 結(jié)構(gòu)
- Expires | Max-Age => 指明過期時間,只與客戶端有關
- HttpOnly => 保證 Cookie 不會被腳本訪問 => JS document.cookie API 無法訪問帶有 HttpOnly 屬性的 Cookie
- Domain | Path => 允許 Cookie 應該發(fā)送給哪些 URL
HTTP 緩存有哪些方案葬荷?
- HTTP 緩存分為強緩存(緩存)和弱緩存(內(nèi)容協(xié)商)
- HTTP 1.1 時代
- 強緩存(緩存)
- 在 response header 中添加 Cache-Control: max-age = 3600涨共,瀏覽器會自動緩存一個小時,如果在此時間內(nèi)宠漩,再次訪問相同的 url(path + query)举反,直接不發(fā)送這個請求
- 在 response header 中添加 Etag:ABC,代表該文件的特征值
- 弱緩存(內(nèi)容協(xié)商)
- 強緩存過期之后扒吁,該緩存是否可以繼續(xù)使用 => request header 中添加 if-None-Match: ABC => 瀏覽器向服務器發(fā)送的一個詢問
- 服務器返回相應的狀態(tài)碼:304(Not Modified火鼻,繼續(xù)使用緩存的內(nèi)容) 或 200(使用新的文件,其中 response header 中包含了 Cache-Control 和 Etag)
- 強緩存(緩存)
- HTTP 1.0 時代
- 強緩存(緩存)
- Expires => 以電腦本地時間為準
- Last-Modified => 一個文件1s內(nèi)更改多次雕崩,無法區(qū)分是否是最新的
- 弱緩存(內(nèi)容協(xié)商)
- if-Modified-Since
- 狀態(tài)碼:304 或 200
- 強緩存(緩存)
HTTP 和 HTTPS 的區(qū)別
- HTTPS = HTTP + SSL/TLS(安全層)
- HTTP 是明文傳輸?shù)目鳎话踩卜TTPS 是加密傳輸?shù)乃律浅0踩?/li>
- HTTP 使用 80 端口绑雄。HTTPS 使用 443 端口
- HTTP 較快晚树。HTTPS 較慢
- HTTP 不需要證書。HTTPS 需要證書
HTTP/1.1 和 HTTP/2 的區(qū)別有哪些?
- HTTP/2 使用二進制傳輸传泊,并且將 head 和 body 分成幀來傳輸。HTTP/1.1 是字符串傳輸
- HTTP/2 支持多路復用,一個 TCP 連接可以發(fā)送多個請求趁窃。HTTP/1.1 不支持,一個請求建立一個 TCP 連接急前。多路復用就是一個 TCP 連接從單車道變成了幾百個雙向通行的車道
- HTTP/2 可以壓縮 head醒陆。HTTP/1.1 不可以
- HTTP/2 支持服務器推送。HTTP/1.1 不支持
TCP 三次握手和四次揮手
- ACK => acknowledge => 接受
- RCVD => received => 收到
- SYN => synchronize => 同步
- seq => sequence => 順序
- EATABLISHED => established => 已建立
- 三次握手
- 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => SYN(seq = x)
- 服務器向瀏覽器發(fā)送 TCP 數(shù)據(jù) => SYN(seq = y), ACK = x + 1
- 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => ACK = y + 1
- 四次揮手
- 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => FIN(seq = x + 2), ACK = y + 1
- 服務器向瀏覽器發(fā)送 TCP 數(shù)據(jù) => ACK = x + 3
- 服務器向瀏覽器發(fā)送 TCP 數(shù)據(jù) => FIN(seq = y + 1)
- 瀏覽器向服務器發(fā)送 TCP 數(shù)據(jù) => ACK = y + 2
同源策略和跨域
- 同源指的是 protocol + host + port 相同便是同源的
- 同源策略用于控制不同源之間的交互裆针。跨源寫和跨源資源嵌入一般是允許的刨摩,但是跨源讀操作一般是不允許的寺晌。
- 只要在瀏覽器里打開頁面,默認遵守同源策略
- 保證了用戶的隱私安全和數(shù)據(jù)安全
- 很多時候前端需要訪問另一個域名的后端接口澡刹,此時瀏覽器會將響應屏蔽呻征,并報錯 CORS
- 解決跨域 => 通常需要在 response header 中添加以下即可,此時瀏覽器將不會屏蔽響應
Access-Control-Allow-Origin: <前端訪問域名> Access-Control-Allow-Method: POST, OPTIONS, GET, PUT Access-Control-Allow-header: Content-Type
- 使用 Node.js 或者 NGINX 代理 => 前端 -> NGINX/Node.js -> 另一個域名的服務端
Session罢浇、Cookie陆赋、LocalStorage、SessionStorage 的區(qū)別
- Session => 會話嚷闭,用戶信息 => 存儲在服務器的文件中攒岛,如 MySQL 或者 Redis
- Cookie => 保存了用戶憑證 => 存儲在瀏覽器文件中,在請求的時候會發(fā)送到服務端胞锰,大小 4k 左右
- LocalStorage vs SessionStorage => 存儲
- LocalStorage 如果不手動清除灾锯,會一直存在。SessionStorage 會話關閉就清除
TypeScript
TS 和 JS 的區(qū)別是什么嗅榕?有什么優(yōu)勢顺饮?
- 語法層面 => TS = JS + Type。TS 就是 JS 的超集
- 執(zhí)行環(huán)境層面 => 瀏覽器凌那、NodeJS 可以直接執(zhí)行 JS领突,但不能直接執(zhí)行 TS
- 編譯層面 => TS 有編譯階段。JS 沒有編譯階段
- TS 類型更安全案怯,IDE 可以進行提示
any君旦、unknown嘲碱、never 的區(qū)別是什么金砍?
- any 和 unknown 都是頂級類型(top type),任何類型的值都可以賦值給頂級類型變量
let foo: any = 123; // 不報錯 let bar: unknown = 123; // 不報錯
- 但是 unknown 比 any 類型檢查更嚴格麦锯,any 什么檢查都不做恕稠,unknown 要求先收窄類型
const value: unknown = "hello world"; const str: string = value; // 報錯:Type 'unknown' is not assignable to type 'string'.(2322) const str1: string = value as string; // 不報錯
- 如果改成 any,基本在哪都不會報錯扶欣。所以能用 unknown 就優(yōu)先使用 unknown鹅巍,類型更安全一點
- never 是底類型,表示不應該出現(xiàn)的類型
interface Foo { type: 'foo' } interface Bar { type: 'bar' } type All = Foo | Bar; function handleValue(val: All) { switch (val.type) { case 'foo': // 這里的 val 被收窄為 Foo break; case 'bar': // 這里的 val 被收窄為 Bar break; default: // val 在這里是 never const check: never = val; break; } }
- 在 default 里把被收窄為 never 的 val 賦值給了一個顯示聲明為 never 的變量料祠,如果一切邏輯正確骆捧,那么這里可以編譯通過
- 某一天更改了 All 的類型 =>
type All = Foo | Bar | Baz
- 此時如果沒有修改 handleValue,此時 default 會被收窄為 Baz髓绽,無法賦值給 never敛苇,此時會產(chǎn)生一個編譯錯誤
- 通過這個方法,可以確保 handleValue 總是窮盡(exhaust)了所有 All 的可能類型
type 和 interface 的區(qū)別是什么顺呕?
- 組合方式 => interface 使用 extends 來實現(xiàn)繼承枫攀。type 使用 & 來實現(xiàn)聯(lián)合類型
- 擴展方式 => interface 可以重復聲明用來擴展(merge)括饶。type 一個類型只能聲明一次
interface Foo { title: string } interface Foo { content: string } type Bar = { title: string } // Error: Duplicate identifier 'Bar' type Bar = { content: string }
- 范圍不同 => type 適用于基本類型。interface 被用于描述對象(declare the shapes of objects)
- 命名方式 => interface 會創(chuàng)建新的類型名来涨。type 只是創(chuàng)建類型別名图焰,并沒有新創(chuàng)建類型
瀏覽器
單頁面應用中實現(xiàn)前端路由有哪些方式
- hash 模式 和 history 模式
- hash 模式 =>
- 通過監(jiān)聽 URL 中 hash 部分的變化(hashchange),從而做出對應的渲染邏輯
- URL 中帶有 #
- 前端即可完成
- 請求的時候 # 后面的內(nèi)容不會包含在 HTTP 請求中蹦掐,所以改變 hash 不會重新加載頁面
- history 模式 => HTML5 history 全局對象 => go/forward/back/pushState/replaceState
- 使用 pushState 實現(xiàn)
- 需要后端配合楞泼,將所有路徑都指向首頁
- 調(diào)用
pushState()
和window.location = '#foo'
基本上一樣,都會創(chuàng)建一個新的歷史記錄
pushState(state, title, url) | window.location = '#foo' | |
---|---|---|
URL | 新的 URL 必須是同源的 | 只有在設置錨的時候才使用當前 URL |
URL | 可能會改變頁面的 URL笤闯, 輸入相同的 URL 時不會改變 URL堕阔, 但是會創(chuàng)建新的歷史記錄 |
設置相同的錨不會創(chuàng)建新的歷史記錄 |
數(shù)據(jù) | 數(shù)據(jù)可以放在 state 中 | 只能將數(shù)據(jù)寫在錨的字符串中 |
微任務和宏任務
- 瀏覽器中并不存在宏任務,宏任務(Macrotask)是 Node.js 發(fā)明的術語
- 瀏覽器中只有任務和微任務(Microtask)
- 使用 script 標簽颗味、setTimeout 可以創(chuàng)建任務
- 使用 Promise#then超陆、window.queueMicrotask 可以創(chuàng)建微任務
- 微任務會在任務間隙執(zhí)行 => 微任務只能插任務的隊
- 多個 then 里面的回調(diào)并不會一次性插入到等待隊列中,而是執(zhí)行完一個再插入下一個
- 一個 return Promise.resolve(...) 等于兩個 then
// queue => [0, 1] => 0 -> [1, 4x] => 1 -> [4x, 2] => [2, 4x(剩余一個then)] => 2 -> [4x, 3] => [3, 4x(下一次就將打印)] => 3 -> [4x, 5] => 4x -> [5] => 5 -> [6] => 6
Promise.resolve().then(() => {
console.log(0);
return Promise.resolve('4x');
}).then((res) => {
console.log(res)
});
Promise.resolve().then(() => {
console.log(1);
}).then(() => {
console.log(2);
}, () => {
console.log(2.1)
}).then(() => {
console.log(3);
}).then(() => {
console.log(5);
}).then(() => {
console.log(6);
})
Web 性能優(yōu)化
- HTTP/1.1 => 連接復用 + 并行連接
- HTTP/2 => 多路復用 => Frame + Stream => 1個 TCP 連接中可以同時進行多個請求和響應
- 緩存 + 內(nèi)容協(xié)商
- HTTP/1.1 => Cache-Control + ETag + If-None-Match + 304 | 200
- HTTP/1.0 => Expires + Last-Modified + If-Modified-Since + 304 | 200
- cookie-free => cookie 最大有4k浦马,每一個同源請求都會帶著 cookie时呀,某些文件可以啟用新的域名,從而做到 cookie-free 和 并行連接
- 使用 CDN => cookie-free + 并行連接 + 下載速度快
- 資源合并 => 典型的方案有 icon font 和 SVG symbol
- 代碼層面
- 分層 => 將一個 JS 拆分成多個 JS晶默,從而達到分層的目的
- 懶加載 | 預加載 => 多屏圖片先加載第一屏谨娜,滾動到第二屏在加載第二屏的圖片
- js 動態(tài)導入 =>
import("lodash").then(_ => _.deepClone())
工程化
babel 原理
- babel 主要是將 A 類型的文件轉(zhuǎn)化為 B 類型
- webpack 只支持 JS 文件,所以需要將其他文件類型都轉(zhuǎn)化為 JS 文件
- parse => 主要將代碼轉(zhuǎn)化為 AST
- traversal => 遍歷 AST 進行修改
- generator => 將修改后的 AST 轉(zhuǎn)化為 code
webpack 流程
webpack 常見 loader 和 plugin 有哪些磺陡?二者區(qū)別是什么趴梢?
loader
- Transpiling
- babel-loader => 將 ES2015+ 轉(zhuǎn)化為 ES5
- ts-loader => 加載 TS,并提示類型錯誤
- thread-loader => 多線程打包
- Templating
- html-loader => 將 HTML 導出為字符串
- Styling
- sass-loader => 加載并編譯 sass 文件
- less-loader => 加載并編譯 less 文件
- style-loader => 將 css 轉(zhuǎn)化為 style
- css-loader => 把 CSS 變成 JS 字符串
- Frameworks
- vue-loader => 加載并編譯 Vue 組件
plugin
- HtmlWebpackPlugin => 創(chuàng)建 HTML 文件并自動引入 JS 和 css
- CleanWebpackPlugin => 用于清理之前打包的殘余文件
- SplitChunksPlugin => 用于代碼分包
- DLLPlugin + DLLReferencePlugin => 用于避免大依賴被頻繁重新打包币他,大幅降低打包時間
- EslintWebpackPlugin => 用于檢查代碼中的錯誤
- DefinePlugin => 用于在 webpack config 中添加全局變量
- CopyWebpackPlugin => 用于拷貝靜態(tài)文件到 dist
區(qū)別
- loader 是文件加載器坞靶,主要使用在 make 階段,將 A 類型的文件轉(zhuǎn)化為 B 類型蝴悉。它能夠?qū)ξ募M行編譯彰阴、優(yōu)化、壓縮等
- plugin 是 webpack 插件拍冠,plugin 可以掛載在整個 webpack 打包過程中的鉤子中尿这,可以實現(xiàn)更多功能,如定義全局變量庆杜、加速編譯射众、Code Split
webpack 如何解決開發(fā)時的跨域問題
- 在配置中添加代理即可
devService: { proxy: { '/api': { target: 'http://host', changeOrigin: true } } }
如何實現(xiàn) tree-shaking?
- tree-shaking 就是讓沒有用到的 JS 代碼不打包,以減少包的體積
- 利用 tree-shaking 部分 JS 代碼
- 使用 ES2015 模塊語法欣福,即 export 和 import责球。
- 不要使用 CommonJS焦履,CommonJS 無法 tree-shaking => babel-loader 添加
modules: false
選項 - 引入的時候只引用需要的模塊
-
import {cloneDeep} from 'lodash-es'
=> 僅僅打包 cloneDeep -
import _ from 'lodash'
=> lodash 全部打包拓劝,無法 tree-shaking 沒有用到的模塊
-
- 不 tree-shaking JS 代碼
- 在項目的 package.json 中添加 "sideEffects" 屬性雏逾,防止某些文件被 tree-shaking
- case:
import x.js
x.js 添加了 window.x 屬性,那么 x.js 就要放到 sideEffects 中 - 所有被 import 的 CSS 都要放在 sideEffects 中
- case:
- 在項目的 package.json 中添加 "sideEffects" 屬性雏逾,防止某些文件被 tree-shaking
- 如何開啟 tree-shaking => 在 webpack config 中將 mode 設置為 production =>
mode: production
給 webpack 加了非常多的優(yōu)化
如何提高 webpack 構(gòu)建速度
- 使用 DLLPlugin 將不常變化的代碼提前打包并復用郑临,如 Vue栖博、React
- 使用 thread-loader 進行多線程打包
- 處于開發(fā)環(huán)境時,在 webpack config 中將 cache 設為 true
- 處于生產(chǎn)環(huán)境時厢洞,關閉不必要的環(huán)節(jié)仇让,如 source map
Webpack 和 Vite 的區(qū)別
開發(fā)環(huán)境區(qū)別
- Vite 自己實現(xiàn) server,不對代碼打包躺翻,充分利用瀏覽器對
<script type=module>
的支持- 假設 main.js 引入了 vue
- 該 server 會把
import {createApp} from 'vue'
改為import {createApp} from '/node_modules/.vite/vue.js'
這樣瀏覽器就知道去哪里找 vue.js 了
- webpack-dev-server 常使用 babel-loader 基于內(nèi)存打包丧叽,比 Vite 慢很多很多
- 該 server 會把 vue.js 的代碼(遞歸地)打包進 main.js
生產(chǎn)環(huán)境區(qū)別
- Vite 使用 rollup + esbuild 來打包 JS 代碼
- Webpack 使用 babel 來打包 JS 代碼,比 esbuild 慢很多很多
文件處理時機
- Vite 只會在你請求某個文件的時候處理該文件
- Webpack 會提前打包好 main.js公你,等你請求的時候直接輸出打包好的 JS 給你
目前已知 Vite 缺點
- 熱更新常常失敗
- 有些功能 rollup 不支持踊淳,需要自己寫 rollup 插件
- 不支持非現(xiàn)代瀏覽器
Webpack 怎么配置多頁應用
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js'
},
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html',
chunks: ['app']
}),
new HtmlWebpackPlugin({
filename: 'admin.html',
chunks: ['admin']
})
]
}
- 但是這樣配置后會有一個重復打包的問題:假設 app.js 和 admin.js 都引入了 vue.js,那么 vue.js 的代碼會打包進 app.js陕靠,也會打包進 admin.js
- 我們需要使用
optimization.splitChunks
將共同依賴單獨打包成 common.js迂尝,HtmlWebpackPlugin 會自動引入 common.js
swc、esbuild 是什么剪芥?
swc
- 實現(xiàn)語言 => Rust
- 功能 => 編譯 JS/TS垄开,打包 JS/TS => TS: 類型擦除
- 優(yōu)勢 => 比 babel 快很多很多(20倍以上)
- 能否集成進 webpack => 可以
- 缺點 => 1. 對 TS 代碼進行類型檢查(用 tsc 先檢查再打包)+ 2. 無法打包 CSS/SVG 等非 JS 文件
esbuild
- 實現(xiàn)語言 => GO
- 功能 => 編譯 JS/TS,打包 JS/TS => TS: 類型擦除
- 優(yōu)勢 => 比 babel 快很多很多(10 - 100倍)
- 能否集成進 webpack => 可以
- 缺點 => 1. 對 TS 代碼進行類型檢查(用 tsc 先檢查在打包)+ 2. 無法打包 CSS/SVG 等非 JS 文件
Docker
- Docker 是一種技術
- 在系統(tǒng)上安裝 Docker 軟件即可使用
- 核心概念
- 容器(Container) => 一臺虛擬的計算機税肪,擁有獨立的網(wǎng)絡溉躲、文件系統(tǒng)、進程益兄。默認和宿主機不發(fā)生任何交互
- 鏡像(Image) => 一個預先定義好的模板文件签财,Docker 引擎按照這個模板文件啟動無數(shù)個一模一樣,互不干擾的容器偏塞。默認是分層的 => 復用唱蒸、節(jié)省空間
- 命令 =>
- docker run --name -v -p -e
- docker images
- docker ps
- 解決了什么問題 =>
- 保證開發(fā)、測試灸叼、交付神汹、部署的環(huán)境完全一致
- 保證資源的隔離
- 啟動臨時的、用完即棄的環(huán)境古今,如測試
- 秒級的超大規(guī)模的部署和擴容
- 實現(xiàn)隔離技術 => namespace + Control Groups
React
React setState 異步同步
- 在 setTimeout屁魏、Promise 等原生事件 API 調(diào)用中
- setState 和 useState 是同步執(zhí)行的,立即執(zhí)行 render
- Class Component 能獲取到最新值 => this.state => 引用類型
- Function Component 不能獲取到最新值 => 只能得到之前的值 => 閉包
- 多次執(zhí)行 setState 和 useState捉腥,每一次執(zhí)行都會調(diào)用一次 render
- setState 和 useState 是同步執(zhí)行的,立即執(zhí)行 render
- 在 React 合成事件中
- setState 和 useState 是異步執(zhí)行的氓拼,不會立即執(zhí)行 render,都不能立即獲取到更新后的值
- 多次執(zhí)行 setState 和 useState,只會調(diào)用一次 render
- setState 可能會進行 state 合并
- 傳入一個對象桃漾,會進行 state 合并 => +1
- 傳入一個函數(shù)坏匪,則不會進行 state 合并 => +2
- useState 不會進行 state 合并
React Fiber
在 Fiber 出現(xiàn)之前 React 的問題
- 在 Fiber 出現(xiàn)之前對比更新虛擬 DOM 使用遞歸加循環(huán),這種對比方式有一個問題撬统,就是任務一旦開始則無法中斷
- 如果應用組件過多适滓,層級較深,那么主線程將會被一直占用
- 這會導致一些用戶操作或者動畫無法立即得到執(zhí)行恋追,頁面會產(chǎn)生卡頓
- 總結(jié) => 遞歸無法中斷凭迹,執(zhí)行任務耗時長,JavaScript 是單線程的苦囱,比較虛擬 DOM 過程中無法執(zhí)行其他任務嗅绸,導致任務延遲頁面卡頓
Fiber 架構(gòu)思路
- Fiber 架構(gòu)使用循環(huán)模擬遞歸,循環(huán)可以隨時被中斷
- Fiber 將大的任務拆分成一個個小任務
- 使用 requestIdleCallback 利用瀏覽器空余時間執(zhí)行小任務撕彤,執(zhí)行完一個任務單元后朽砰,查看是否有其他優(yōu)先級更高的任務,如果有喉刘,放棄占用線程瞧柔,先執(zhí)行優(yōu)先級更高的任務
- requestIdleCallback 方法插入一個函數(shù),這個函數(shù)將在瀏覽器空閑時期被調(diào)用
Fiber 節(jié)點
- tag
- key
- stateNode => DOM 節(jié)點
- child => 子節(jié)點
- sibling => 下一個兄弟節(jié)點
- return => 父級節(jié)點
- firstEffect
- nextEffect
- lastEffect
Fiber 執(zhí)行
- 兩棵樹 => currentFiber + workInProgress
- render 階段 => 利用 DFS 和 diff 算法構(gòu)建 workInProgress Fiber 樹睦裳,可中斷造锅,收集 Effect List 鏈表
- commit 階段 => 根據(jù) Fiber 節(jié)點的 tag 進行 DOM 操作 => 不可中斷
render 階段
- DFS 遍歷 Fiber 樹,遍歷的時候會進行 current 和 workInProgress 的 diff廉邑,決定哪些舊節(jié)點需要復用哥蔚、刪除、移動蛛蒙,哪些新節(jié)點需要創(chuàng)建
- 邊構(gòu)建糙箍、邊遍歷、邊對比牵祟、可中斷
- performUnitOfWork -> beginWork -> completeUnitOfWork -> completeWork
- performUnitOfWork => 開始函數(shù)
- beginWork => 返回 workInProgress.child 節(jié)點
- completeUnitOfWork => check sibling -> parent
- completeWork => 如果有 stateNode 則更新深夯,如果沒有 stateNode 則創(chuàng)建 => stateNode 對應 DOM 節(jié)點,有 stateNode 說明這個節(jié)點有對應的 DOM 節(jié)點
- completeWork 執(zhí)行完之后收集 effect
- completeUnitOfWork 的內(nèi)部循環(huán)會自底向上收集 effect诺苹,不斷把有 effectTag 的子節(jié)點和自身向上合并到父節(jié)點的 effectList 中咕晋,直至根節(jié)點,Effect List 就是一個鏈表
- diff
- 如果根節(jié)點類型改變了收奔,比如 div 變?yōu)榱?p掌呜,那么直接認為整顆樹都變了,不再對比子節(jié)點坪哄。此時直接刪除對應的真實 DOM 樹质蕉,創(chuàng)建新的真實 DOM 樹
- 如果根節(jié)點類型沒有改變势篡,就看看屬性變了沒有
- 屬性沒變,保留對應的真實節(jié)點
- 屬性變了模暗,就只更新該節(jié)點的屬性禁悠,不重新創(chuàng)建節(jié)點 => case: 更新 style 時,如果多個 css 屬性只有一個改變了汰蓉,那么 React 只更新改變的
- 遍歷子節(jié)點
- 子節(jié)點是單節(jié)點 => 遍歷舊的節(jié)點绷蹲,如果有可以復用的棒卷,復用顾孽,沒有則將舊節(jié)點的 effect 標記為刪除,新建子節(jié)點
- 子節(jié)點是多節(jié)點 =>
- 第一輪:
- 如果新節(jié)點遍歷完比规,則刪除所有舊節(jié)點若厚,對比結(jié)束
- 如果舊節(jié)點遍歷完,則新增所有新節(jié)點蜒什,對比結(jié)束
- 第二輪:建立 map 對比
- 有節(jié)點位置發(fā)生改變
A -> B -> C A -> C -> B // 將 B 節(jié)點 effect 標記為 Placement
- 中途出現(xiàn)增刪的節(jié)點
- 有節(jié)點位置發(fā)生改變
- 第一輪:
commit 階段
- Before Mutation 前
- flushPassiveEffects => 觸發(fā) useEffect 回調(diào)與其他同步任務测秸,由于這些任務可能觸發(fā)新的渲染,所以要一直遍歷到?jīng)]有任務
- commitRoot 是同步完成的灾常,清除 callbackNode 以允許安排新的回調(diào)
- reset workInProgress
- 獲取 Effect List
- Before Mutation
- 循環(huán)遍歷 Effect List
- 處理 DOM 節(jié)點渲染或刪除后的 autoFocus/blur 邏輯
- call getSnapshotBeforeUpdate
- Mutation => 執(zhí)行 DOM 操作
- 循環(huán)遍歷 Effect List
- reset text
- 更新 ref
- 根據(jù) flags 分別處理
- Deletion => commitDeletion
- 遞歸的刪除節(jié)點
- 清除 ref
- 調(diào)用 componentWillUnmount => ClassComponent
- 調(diào)度 destroy 函數(shù) => FunctionComponent
- Update => commitWork
- 遞歸調(diào)度 effect destroy 函數(shù) => FunctionComponent
- Placement => commitPlacement
- 獲取父級 DOM 節(jié)點
- insertOrAppendPlacementNodeIntoContainer | insertOrAppendPlacementNode
- Deletion => commitDeletion
- Layout => DOM 渲染完成霎冯,觸發(fā)生命周期鉤子和 hooks
- 循環(huán)遍歷 Effect List
- FunctionComponent => useLayout callback + 調(diào)度 useEffect destroy + callback
- ClassComponent => (componentDidMount | componentDidUpdate) + 執(zhí)行 this.setState 的 callback
- 更新 ref
- Layout 之后
- 處理 passive effect
- 檢查 root 上是否有其余的工作
- ensureRootIsScheduled => 保證任何在 root 上附加任務被 schedule
- flushSyncCallbackQueue => 執(zhí)行同步任務
React 合成事件
React 將所有的事件都綁定到了根元素上,自動實現(xiàn)了事件委托钞瀑,此時不需要使用 addEventListener 為已經(jīng)創(chuàng)建的 DOM 添加事件監(jiān)聽器
合成事件和原生事件區(qū)別
React 合成事件 | 原生事件 | |
---|---|---|
命名 | 小駝峰(onClick) | 純小寫(onclick) |
事件處理函數(shù) | 函數(shù) | 字符串 |
阻止默認行為 | event.preventDefault() | return false |
虛擬 DOM 的原理是什么沈撞?
- 虛擬 DOM 就是虛擬節(jié)點。React 使用 JS 對象來模擬 DOM 節(jié)點雕什,之后將其渲染成真實的 DOM 節(jié)點
- 使用 JSX 語法寫出來的 div 其實就是一個虛擬節(jié)點
<div className="container"> <span className="red">hi</span> </div>
- 上面的代碼其實是調(diào)用了 React.createElement 函數(shù)缠俺,之后生成了一個 JS 對象
{ tag: 'div', props: {className: 'container'}, children: [ { tag: 'span', props: { className: 'red' }, children: [ 'hi' ] } ] }
- 之后會根據(jù) JS 對象信息即虛擬節(jié)點渲染為真實節(jié)點
- 如果節(jié)點發(fā)生改變,并不會把新的虛擬節(jié)點直接重新渲染為真實節(jié)點贷岸,而是要先經(jīng)過 diff 算法得到一個 patch 再更新到真實節(jié)點上
- 虛擬 DOM 大大提升了 DOM 操作性能問題壹士。通過虛擬 DOM 和 diff 算法減少不必要的 DOM 操作,保證性能偿警。
- 之前 DOM 操作并不是很方便躏救,但是現(xiàn)在只需要 setState 即可
- 但是 React 為虛擬 DOM 創(chuàng)造了合成事件,和原生 DOM 事件不太一樣螟蒸。所有 React 事件都綁定到了根元素落剪,自動實現(xiàn)事件委托,如果混用合成事件和原生 DOM 事件尿庐,可能會出現(xiàn) bug
React DOM diff 算法
- DOM diff 就是對比兩顆虛擬 DOM 樹的算法
- 當組件變化時忠怖,會 render 出一個新的虛擬 DOM,diff 算法對比新舊虛擬 DOM 之后抄瑟,得到一個 patch凡泣,然后 React 用 patch 來更新真實 DOM
- 首先對比兩棵樹的根節(jié)點
- 如果根節(jié)點類型改變了枉疼,比如 div 變?yōu)榱?p,那么直接認為整顆樹都變了鞋拟,不再對比子節(jié)點骂维。此時直接刪除對應的真實 DOM 樹,創(chuàng)建新的真實 DOM 樹
- 如果根節(jié)點類型沒有改變贺纲,就看看屬性變了沒有
- 屬性沒變航闺,保留對應的真實節(jié)點
- 屬性變了,就只更新該節(jié)點的屬性猴誊,不重新創(chuàng)建節(jié)點 => case: 更新 style 時潦刃,如果多個 css 屬性只有一個改變了,那么 React 只更新改變的
- 然后同時遍歷兩棵樹的子節(jié)點懈叹,每個節(jié)點的對比過程如上
- case1:React 依次對比 A-A乖杠,B-B,空-C澄成,發(fā)現(xiàn) C 是新增的胧洒,最終會創(chuàng)建真實 C 節(jié)點插入頁面
<ul> <li>A</li> <li>B</li> </ul> // updated <ul> <li>A</li> <li>B</li> <li>C</li> </ul>
- case2: React 對比 B-A,刪除 B 節(jié)點新建 A 節(jié)點墨状;對比 C-B卫漫,刪除 C 節(jié)點新建 B 節(jié)點(注意:并不是邊對比邊刪除新建,而是把操作匯總到 patch 里在進行 DOM 操作肾砂,會進行標記)列赎;對比 空-C,新建 C 節(jié)點
<ul> <li>B</li> <li>C</li> </ul> // updated <ul> <li>A</li> <li>B</li> <li>C</li> </ul>
- case2 其實只需要創(chuàng)建 A 節(jié)點通今,保留 B C 節(jié)點即可粥谬,此時 React 需要你添加 key
<ul> <li key="b">B</li> <li key="c">C</li> </ul> // updated <ul> <li key="a">A</li> <li key="b">B</li> <li key="c">C</li> </ul>
- 此時 React 先對比 key,發(fā)現(xiàn) key 增加了 a辫塌,此時保留 B C漏策,新建 A 節(jié)點
Vue Dom diff
Vue 雙端交叉對比
- 頭頭對比 => 對比兩個數(shù)組的頭部,如果找到臼氨,把新節(jié)點 patch 到舊節(jié)點掺喻,頭指針后移
- 尾尾對比 => 對比兩個數(shù)組的尾部,如果找到储矩,把新節(jié)點 patch 到舊節(jié)點感耙,尾指針前移
- 舊尾新頭對比 => 交叉對比,舊尾新頭持隧,如果找到即硼,把新節(jié)點 patch 到舊節(jié)點,舊尾指針前移屡拨,新頭指針后移
- 舊頭新尾對比 => 交叉對比只酥,舊頭新尾褥实,如果找到,把新節(jié)點 patch 到舊節(jié)點裂允,新尾指針前移损离,舊頭指針后移
- 利用 Key 對比 => 用新指針對應節(jié)點的 key 去舊數(shù)組中尋找對應的節(jié)點
- 沒有對應的 key => 創(chuàng)建新節(jié)點
- 有 key 并且是相同的節(jié)點 => 把新節(jié)點 patch 到舊節(jié)點
- 有 key 但是不是相同的節(jié)點 => 創(chuàng)建新節(jié)點
React 有哪些聲明周期鉤子函數(shù)?數(shù)據(jù)請求放在哪個鉤子里绝编?
- 掛載時調(diào)用 constructor僻澎,更新時不調(diào)用
- 更新時調(diào)用 shouldComponentUpdate 和 getSnapshotBeforeUpdate,掛載時不調(diào)用
- shouldComponentUpdate 在 render 前調(diào)用十饥,getSnapshotBeforeUpdate 在 render 后調(diào)用
- 請求放置在 componentDidMount 里
React 如何實現(xiàn)組件間通信
- 父子組件通信 => props + 函數(shù)
- 爺孫組件通信 => 兩層父子通信 | Context.Provider 和 Context.Consumer
- 任意組件通信 => 狀態(tài)管理 => Redux | Mobx
如何理解 Redux
- Redux 就是一個狀態(tài)管理庫窟勃,可以實現(xiàn)任意組件之間的通信,其實就是將信息放置在頂部绷跑,如有需要其余組件去頂部獲取即可
- Redux 核心概念
- store => 信息/狀態(tài)存放的地方
- action => 可以更改 state 的唯一途徑拳恋,根據(jù) type 和 payload 進行更改 state
- dispatch => 用于派發(fā)事件
- reducer => 就是一個函數(shù)凡资,傳遞給 reducer 一個舊的 state + action砸捏,他會返回一個新的 state
- Middleware => 中間件
- Redux 常與 ReactRedux 聯(lián)合使用,ReactRedux 提供了以下 Api
- connect()(Component)
- mapStateToProps
- mapDispatchToProps
- Redux 常用中間件:
- redux-thunk => 擴展 redux 可以支持異步 action隙赁,如果 action 是一個函數(shù)垦藏,就直接調(diào)用它,否則就進入下一個中間件
- redux-promise => 如果 payload 是一個 Promise伞访,就執(zhí)行個這個 Promise掂骏,并在 then 里面去 dispatch
什么是高階組件 HOC
- 參數(shù)是組件,返回值也是組件的函數(shù)
- React.forwardRef
const FancyButton = React.forwardRef((props, ref) => { <button ref={ref} className="fancyButton"> {props.children} </button> }); // You can now get a ref directly to the DOM button const ref = React.createRef(); <FancyButton ref={ref}>Click me!</FancyButton>
- ReactRedux 的 connect
React Hooks 如何模擬組件生命周期
- 模擬 componentDidMount
- 模擬 componentDidUpdate
- 模擬 componentWillUnmount
設計模式
設計模式是人們在長久的生產(chǎn)實踐中總結(jié)出來的一套方法論厚掷,它可以提高開發(fā)效率弟灼,降低維護成本。舉例說明:
- 發(fā)布訂閱模式 => 定義了對象間的一種一對多的依賴關系冒黑,當一個對象的狀態(tài)發(fā)生改變時田绑,所有依賴于它的對象都將得到通知。case:EventBus
- 責任鏈模式 => 將請求沿著所有處理者組成的鏈發(fā)送抡爹,每個處理者都可以決定自己是否處理掩驱,或者是否繼續(xù)處理。實現(xiàn)了請求和處理者之間的解耦冬竟。case:Redux 中間件
- 模板方法 => 一個抽象類定義了執(zhí)行它的方法的模板欧穴,子類可以按需要重寫方法實現(xiàn)。case:React 的生命周期函數(shù) + Dart
其他
人崗匹配
離職原因
個人原因:
- 期待更大的平臺泵殴,更好的機會
- 目前準備成家涮帘,經(jīng)濟壓力比較大
期望薪資
- 請您方便介紹一個貴司的薪酬情況和薪酬結(jié)構(gòu)嗎?
- 希望有一個符合市場預期的漲幅
規(guī)劃
我是一個穩(wěn)定性很強的人笑诅,我覺得貴司經(jīng)過多年的發(fā)展调缨,穩(wěn)步的一個方式和我是非常匹配的映屋,所以我的規(guī)劃就是在我的本職崗位上做好,1-3年內(nèi)提高我的專業(yè)能力同蜻,并且要對于崗位進行一個深入了解棚点,通過積累3-5年的行業(yè)經(jīng)驗以及學習學習,達到一個高級或者資深的水平湾蔓,后續(xù)的發(fā)展隨著公司同步發(fā)展即可
缺點
- 性格比較急躁瘫析,對于事情會比較較真
- 后來經(jīng)過同事和領導的溝通與交流
- 之后我仔細思考了一下如何解決這個問題
- 我從兩個方面做出了努力
- 在與同事的溝通中,轉(zhuǎn)化自己的語言默责,case:你要這么做 => 你可以考慮一下這樣做
- 在與同事的溝通中贬循,說話慢慢來,從而控制我急躁的性格桃序,這個效果是非常明顯的
- 首先考慮這個事情對于整體有沒有影響杖虾,如果有,那必須去較真媒熊,如果對整體沒有影響奇适,那就不去計較
- 通過幾個月的調(diào)整,大家也能明顯感覺到我的進步芦鳍,我和同事之間的關系更加緊密了
加班看法
- 我不贊同無效的加班
- 當公司的重要項目有延期風險嚷往,那么為了保證項目如期上線是可以加班的
- 另外,我覺得更重要的是提升自己的工作效率柠衅,降低項目延期的風險
你遇到最難的 Bug 是什么皮仁?
- 我們交付了一個打開第三方頁面的功能,該功能在測試的時候沒有出現(xiàn)任何問題菲宴,但是在現(xiàn)場發(fā)現(xiàn)某些頁面空白
- 因為我們沒有相關賬號贷祈,所以只能進行遠程調(diào)試,在溝通協(xié)調(diào)了一段時間之后喝峦,我才看到具體的問題
- 空白的第三方頁面顯示 md5 is not defined势誊,之后我查看了一下 window 上是否掛載了 md5,發(fā)現(xiàn)沒有正常掛載
- 之后便懷疑 md5 這個包沒有正常的下載愈犹,之后查看了一下 network键科,發(fā)現(xiàn) md5.min.js 請求的狀態(tài)碼是 200
- 按理說我獲取到 js 文件,之后就會一行一行的執(zhí)行該文件
- 之后把 md5.min.js 文件內(nèi)容拷貝出來漩怎,粘貼在控制臺勋颖,之后查看 window 上是否掛載了 md5,發(fā)現(xiàn)也是沒有 md5 這個對象
- 但是 md5.min.js 被廣泛使用勋锤,應該不會出現(xiàn)問題饭玲,所以我將上面 md5.min.js 文件粘貼到瀏覽器控制臺中,發(fā)現(xiàn) window 上是正常掛載了 md5
- 那么我現(xiàn)在桌面應用和瀏覽器控制臺調(diào)用了相同的 js叁执,表現(xiàn)出不同的結(jié)果茄厘,說明兩者環(huán)境可能有差異
- 最終發(fā)現(xiàn)矮冬,桌面端應用在創(chuàng)建窗口是開啟了 Node 功能。之后把 Node 功能置為 false次哈,發(fā)現(xiàn)網(wǎng)頁便正常了
- 之后去查看了一下 md5.min.js 文件胎署,看看對于 md5.min.js 兩種環(huán)境差在哪里
- md5.min.js 文件最終會查看 define 和 module,之后再兩種環(huán)境中分別查看最終結(jié)果
- Node 環(huán)境 module.export = t;瀏覽器是 n.md5 = t
- 之后將該問題總結(jié)記錄在了我們公司的 wiki 上,為大家提供解決問題思路
平時是如何學習的
手段有 看書须鼎,看博客,逛論壇基本上就這些巨坊,之后通過 輸入、轉(zhuǎn)化此改、輸出來進行學習
- 輸入 => 是指閱讀別人分享的知識趾撵,比如看博客、看書
- 轉(zhuǎn)化 => 是指把知識消化之后變成自己的積累共啃,用自己的知識體系重新闡述一遍新學的概念
- 輸出 => 是指把自己的理解以博客占调、代碼的形式分享出來
自我介紹
面試官您好,我是XXX勋磕,XX年畢業(yè)妈候,本科學歷敢靡,我來應聘前端工程師崗位挂滓,目前有X年開發(fā)經(jīng)驗。我擅長的語言是 JavaScript啸胧,并且對于 Java 和 Dart 有一定的了解赶站。主要使用的技術棧是 React ,熟練掌握算法和數(shù)據(jù)結(jié)構(gòu)纺念。對于產(chǎn)品研發(fā)自動化流程有一定的實踐贝椿。我在上一份工作中從事前端開發(fā)工作,最近在公司主要負責公共組件開發(fā)項目陷谱。在工作之余我也會積極學習其他方面的知識烙博,如 Java,并且將自己的學到的知識總結(jié)烟逊,目前已經(jīng)寫了接近 200 篇的技術博客渣窜。我覺得我的個人經(jīng)歷是和貴司的崗位是非常匹配的,我也非常喜歡貴司的文化宪躯,非常期待能夠加入貴司和各位一起去做事情
回答問題
- 您問的是xxx問題嗎乔宿?
- 我覺得這個問題可以從兩個方面考慮
- 一是
- 二是
- 對了,還有一點可以補充
- 以上三點就是我對xxx問題的理解
反問問題
- 組里目前最大的挑戰(zhàn)是什么访雪?
- 咱組在整個部門或者公司的組織架構(gòu)是什么樣的详瑞?
- 如果我加入貴司掂林,在我未來的職業(yè)生涯中,有怎樣一個晉升的途徑坝橡?我更加適合哪個方式泻帮?
面試官
- 時間 => 把握面試節(jié)奏,根據(jù)需求來決定
- 分成幾個部分计寇,每個部分分別考察什么刑顺?=> 標準化(1h)
- 自我介紹 => 5min =>
- 語言基礎知識(寫代碼/系統(tǒng)設計/算法(1)) => 算法 => 暴力 + 優(yōu)化(準備3道題目)
- 框架
- 項目 => 考察崗位匹配程度 + 從一個人過去持續(xù)穩(wěn)定的行為推測他能否勝任現(xiàn)在的崗位(可能短期提升嗎?)
- 行為問題 => 10 - 15 min => 遇到最難的技術問題是什么饲常?
- 反問 => 5min => 不能問結(jié)果蹲堂,好的問題:做什么?挑戰(zhàn)是什么贝淤?發(fā)展是什么柒竞?該部門在公司的組織架構(gòu)上是什么樣的?
- 如何評價播聪? => 標準:技能匹配 + 文化匹配 + 潛力 => 評分:不合格 | 合格 | 優(yōu)秀
- 記錄優(yōu)缺點
項目中的最大的困難或挑戰(zhàn)? => 舉具體例子
回答的時候要匹配公司文化
httpClient 手寫和使用庫
亞馬遜領導力準則