HTML/CSS
1. 盒模型
- 標(biāo)準(zhǔn)盒模型
- width 和 height 是內(nèi)容區(qū)域即 content 的 width 和 height。
- 盒子總寬度= width + margin(左右) + padding(左右) + border(左右)
- IE 盒模型或怪異盒模型
- width 和 height 除了 content 區(qū)域外,還包含 padding 和 border
- 盒子寬度 = width + margin(左右)
- 通過(guò)
box-sizing切換border-box或content-box
2. 隱藏一個(gè)元素的方式
- display: none
- visibility: hidden
- opacity: 0
- 高度為 0
- 定位
position: absolute; left: 100%;| top: -100% - text-indent 設(shè)置一個(gè)足夠大的負(fù)值
3. display: none 和 visibility: hidden 的區(qū)別
- display: none 不占位
- visibility: hidden 占位 (內(nèi)部盒子也不會(huì)顯示)
4. BFC 模式
5. flex 1 代表什么
6. align-self 屬性
align-self 屬性允許單個(gè)項(xiàng)目有與其他項(xiàng)目不一樣的對(duì)齊方式,可覆蓋 align-items 屬性。默認(rèn)值為 auto,表示繼承父元素的 align-items 屬性,如果沒(méi)有父元素,則等同于 stretch。
JavaScript
1. 數(shù)據(jù)類(lèi)型
Number String Boolean Undefined Null Symbol Object BigInt
2. var let const 區(qū)別
- var 變量聲明提升
- let const 區(qū)別
- let 變量
- const 常量
- let 可以直接修改值(或者引用)
- const 只可以改變屬性值
- let const 暫時(shí)性死區(qū)
3. 箭頭函數(shù)
4. 原型、原型鏈
5. 作用域、作用域鏈
6. 為什么 0.1+0.2 不等于 0.3
01. + 0.2 = 0.3 // false
7. 小數(shù)相加、整數(shù)相加
8. 實(shí)現(xiàn) new 操作符
function Person(name, age) {
this.name = name
this.age = age
}
方式一:
function _new(func, ...args) {
if (typeof func !== 'function') throw new Error('func is not a function')
const context = Object.create(func.prototype)
const rest = func.apply(context, args)
return typeof rest === 'object' && rest !== null ? rest : context
}
const p = _new(Person, 'mike', 12)
方式二:借助 Reflect.construct
Reflect.construct 相當(dāng)于 new target()
const p = Reflect.construct(Person, ['jack', 11])
9. 實(shí)現(xiàn) instanceof
function instanceofX(obj, constructor) {
const proto = Object.getPrototypeOf(obj)
if (proto === null) return false
if (proto === constructor.prototype) return true
return instanceofX(proto , constructor)
}
10. splice 的使用及返回值
const arr = [1, 2, 3, 4, 5]
- 刪除:返回所刪除的元素組成的數(shù)組
arr.splice(1, 2) // 返回 [2, 3]
- 增加:
arr.splice(1, 0, 9, 9) // 返回空數(shù)組 []
- 替換:返回所有替換后的元素組成的數(shù)組
arr.splice(1, 2, 7, 8) // [2, 3]
11. 類(lèi)型轉(zhuǎn)換
123 instanceof Number // false
new Number(123) instanceof Number // true
Number(123) instanceof Number // false
Number === 123
// Number 就是強(qiáng)轉(zhuǎn)換
12. arguments 參數(shù)
function sidEffecting(ary) {
ary[0] = ary[2]
}
function bar(a, b, c) {
c = 10
sidEffecting(arguments)
return a + b + c
}
bar(1, 1, 1) // 21
說(shuō)明:考察 arguments 參數(shù)。arguments 內(nèi)部屬性及其函數(shù)形參創(chuàng)建 getter 和 setter 方法,因此改變形參的值會(huì)影響到函數(shù) arguments 的值,反過(guò)來(lái)也一樣。
13. 深拷貝(只考慮數(shù)組和對(duì)象)
- 方式一
function deepClone(value) {
if (Array.isArray(value)) {
return value.map(deepClone)
} else if (value && typeof value === 'object' && value !== null) {
const res = {}
for (const key in value) {
if (value.hasOwnProperty(key)) {
res[key] = deepClone(value[key])
}
}
return res
}
return value
}
方式二: 借助reduce
function deepClone(value) {
if (Array.isArray(value)) {
return value.map(deepClone)
} else if (typeof value === 'object' && value !== null) {
return Object.keys(value).reduce((pre, key) => {
return {
...pre,
[key]: deepClone(value[key]),
}
}, {})
}
return value
}
以上兩種方式的弊端是只能克隆屬性為對(duì)象自身的、可枚舉、且不為Symbol類(lèi)型的屬性。
方式三: JSON.stringify()
JSON.parse(JSON.stringify(value))
缺點(diǎn):
- 會(huì)忽略值為undefined、函數(shù)類(lèi)型的屬性
- 忽略值為Symbol類(lèi)型的屬性
- 日期對(duì)象的值會(huì)通過(guò)toJSON()轉(zhuǎn)成字符串
- 存在循環(huán)引用會(huì)報(bào)錯(cuò)
比如:
const obj = {
a: [1, 2, 3],
b: { c: 1 }
}
obj.a.push(obj.b)
obj.b.j = obj.a
// JSON.stringify(obj)
- 實(shí)現(xiàn)值為undefined,key為symbol類(lèi)型的情況
function clone(obj) {
if (typeof obj === 'object' && obj !== null) {
let map = new WeakMap()
function deep(data) {
const result = {}
// const keys = [...Object.getOwnPropertyNames(data), ...Object.getOwnPropertySymbols(data)]
// Or
const keys = Reflect.ownKeys(data)
if (!keys.length) return result
const exist = map.get(data)
if (exist) return exist
map.set(data, result)
keys.forEach(key => {
let item = data[key]
if (typeof item === 'object' && item) {
result[key] = deep(item)
} else {
result[key] = item
}
})
return result
}
return deep(obj)
}
return obj
}
14. 克隆數(shù)組
1. 一維數(shù)組
- 擴(kuò)展運(yùn)算 (Shallow copy)
;[...arr]
- for 循環(huán) (Shallow copy)
const newArr = []
for (let i = 0; i < arr.length; i++) {
newArr[i] = arr[i]
}
- while 循環(huán) (Shallow copy)
let i = -1
const newArr = []
while (++i < arr.length) {
newArr[i] = arr[i]
}
- Array.map (Shallow copy)
const newArr = arr.map(item => item)
- Array.filter (Shallow copy)
const newArr = arr.filter(Boolean)
不足之處就是:數(shù)組中有選項(xiàng)是 0 ,就會(huì)丟失0,最終結(jié)果就是錯(cuò)的。因?yàn)?code>Boolean(0) --> false
- Array.reduce (Shallow copy)
arr.reduce((newArr, item) => newArr.concat(item), [])
- Array.slice (Shallow copy)
const newArr = arr.slice()
- Array.concat (Shallow copy)
const newArr = arr.concat()
// Or
const newArr = arr.concat([])
- Array.from (Shallow copy)
const newArr = Array.from(arr)
- JSON.stringify & JSON.parse (Deep copy)
const newArr = JSON.parse(JSON.stringify(arr))
2. 多維數(shù)組
- 借助 Array.from()
const deepCloneArr = value =>
Array.isArray(value) ? Array.from(value, deepCloneArr) : value
- 借助 Array.prototype.map()
const deepCloneArr = value =>
Array.isArray(value) ? value.map(deepCloneArr) : value
15. 平鋪數(shù)組
ES6 方法
參數(shù)默認(rèn)是 1,多維用 Infinity 參數(shù)
Array.prototype.flat()
js 實(shí)現(xiàn)
- 二維數(shù)組
const arr = [1, 2, [3, 4], [5, 6]]
const result = [].concat(...arr)
- 多維數(shù)組
function spread(arr) {
const result = [].concat(...arr)
return result.some(item => Array.isArray(item)) ? spread(result) : result
}
- 可以定義平鋪的層級(jí)數(shù)
function spread(arr, count = 1) {
const result = [].concat(...arr)
if (1 < count) {
return result.some(item => Array.isArray(item))
? spread(result, --count)
: result
}
return result
}
16. 實(shí)現(xiàn) Promise.all
Promise.allX = function (promises) {
return new Promise((resolve, reject) => {
promises = [...promises]
const result = []
const length = promises.length
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(res => {
result[index] = res
if (--length === 0) resolve(result)
})
.catch(err => {
reject(err)
})
})
})
}
17. 實(shí)現(xiàn) Promise.allSettled
Promise.allSettled 接收一組 Promise 實(shí)例作為參數(shù),返回新的 Promise 實(shí)例,只有等到所有 Promise 實(shí)例的狀態(tài)都改變之后才 resolve。
所以 Promise.allSettled 的狀態(tài)總是 fulfilled。其返回值為每個(gè) Promise 實(shí)例的 status 和 value/reason。
Promise.allSettled = function (promises) {
return new Promise(resolve => {
promises = [...promises]
let time = 0
const length = promises.length
const result = []
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
result[index] = {
status: 'fulfilled',
value,
}
if (++time === length) resolve(result)
})
.catch(reason => {
result[index] = {
status: 'rejected',
reason,
}
if (++time === length) resolve(result)
})
})
})
}
18. 實(shí)現(xiàn) Promise.any
接收一組 Promise 實(shí)例作為參數(shù),返回 Promise 實(shí)例,其中只要有一個(gè) Promise 的狀態(tài)變成 fulfilled,則返回的 Promise 的狀態(tài)就為 fulfilled,只有所有的 Promise 都為 rejected狀態(tài)則為 rejected
Promise.any = function (promises) {
return new Promise((resolve, reject) => {
promises = [...promises]
let time = 0
const length = promises.length
const result = []
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(res => {
resolve(res)
})
.catch(err => {
result[index] = err
if(++time === length) reject(result)
})
})
})
}
實(shí)現(xiàn)Promise.race()
function race(promises) {
return new Promise((resolve, reject) => {
promises = [...promises]
promises.forEach(p => {
Promise.resolve(p).then(
res => {
resolve(res)
},
err => {
reject(err)
}
)
})
})
}
19. 防抖、節(jié)流實(shí)現(xiàn)方式
20. 多種方式實(shí)現(xiàn) call/apply/bind
21. js 借助 async/await 實(shí)現(xiàn)休眠效果
function sleep(interval) {
return new Promise(resolve => {
setTimeout(resolve, interval)
})
}
// 用法
async function one2FiveInAsync() {
for (let i = 1; i <= 5; i++) {
console.log(i)
await sleep(1000)
}
}
one2FiveInAsync()
22. 實(shí)現(xiàn)多次重復(fù)嘗試,比如多次請(qǐng)求一個(gè)接口,最多請(qǐng)求 3 次
const COUNT = 3
async function request(url) {
for (let i = 0; i < COUNT; ++i) {
try {
await fetch(url)
break // 跳出循環(huán)
} catch {}
}
}
23. 實(shí)現(xiàn) pipe
pipe 執(zhí)行順序從左向右
- 單個(gè)參數(shù)
const pipe = (...fns) => val => fns.reduce((pre, cur) => cur(pre), val)
- 多個(gè)參數(shù)
const pipes = (...fns) =>
fns.reduce((pre, cur) => (...args) => cur(pre(...args)))
24. 實(shí)現(xiàn) compose
執(zhí)行順序從右向左 第一個(gè)執(zhí)行函數(shù)可以接收多個(gè)參數(shù),后面的執(zhí)行函數(shù)參數(shù)都是單個(gè)的。所有函數(shù)的執(zhí)行都是同步的。
- 單個(gè)參數(shù)
const compose = (...fns) => val => fns.reduceRight((pre, cur) => cur(pre), val)
- 多個(gè)參數(shù)
const composes = (...fns) =>
fns.reduceRight((pre, cur) => (...args) => cur(pre(...args)))
// 或
const composesX = (...fns) =>
fns.reduce((pre, cur) => (...args) => pre(cur(...args)))
實(shí)現(xiàn)無(wú)限柯里化
// sum(1)(2)(3)(4)(5)(6)...()
function sum(a) {
return function (b) {
if (b != null) {
return sum(a + b)
}
return a
}
}
25. 實(shí)現(xiàn) trim()
function trim(str) {
return str.replace(/^\s+|\s+$/g, '')
// 或
return str.replace(/^\s*/, '').replace(/\s*$/, '')
}
26. requestAnimationFrame
27. 本地存儲(chǔ)
- localStorage
- 持久型
- 存儲(chǔ)容量大 大約 5M
- sessionStorage 會(huì)話(huà)
- 會(huì)話(huà)型
- 存儲(chǔ)容量大
- cookie
- 同域下往返于客戶(hù)端和服務(wù)端
- 存儲(chǔ)容量小 大概 4k
28. 交換兩個(gè)值為 number 類(lèi)型的變量
let a = 3
let b = 5
- 解構(gòu)
;[a, b] = [b, a]
- 加減法
a = a + b
b = a - b
a = a - b
- 交換變量
let c = a
a = b
b = c
- 對(duì)象
a = { a, b }
b = a.a
a = a.b
- 托夢(mèng)做出來(lái)的吧?
a = [b, (b = a)][0]
- 位運(yùn)算
a = a ^ b
b = b ^ a
a = a ^ b
29. isNaN 和 Number.isNaN 的區(qū)別?
- isNaN 在調(diào)用時(shí)會(huì)將傳入的參數(shù)轉(zhuǎn)換為數(shù)字類(lèi)型,所以非數(shù)字傳入也有可能返回 true
- Number.isNaN 首先會(huì)判斷傳入的參數(shù)是否為數(shù)字,如果為非數(shù)字,直接返回 false
30. js 垃圾回收機(jī)制
31. 實(shí)現(xiàn)圖片懶加載
<div class="img-area">
<img class="my-pic" alt="loading" data-src="./img/img1.png" />
</div>
//圖片什么時(shí)候出現(xiàn)在可視區(qū)域內(nèi)
function isInSight(el) {
const rect = el.getBoundingClientRect()
//這里我們只考慮向下滾動(dòng)
const clientHeight = window.innerHeight
// 這里加50為了讓其提前加載
return rect.top <= clientHeight + 50
}
//data-src的屬性值代替src屬性值
function loadImg(el) {
if (!el.src) {
const source = el.dataset.src
el.src = source
}
}
let index = 0
function checkImgs() {
const imgs = document.querySelectorAll('.my-pic')
for (let i = index; i < imgs.length; i++) {
if (isInSight(imgs[i])) {
loadImg(imgs[i])
index = i
}
}
}
Element.getBoundingClientRect
Tips:Element.getBoundingClientRect 方法返回一個(gè) rect 對(duì)象,提供當(dāng)前元素節(jié)點(diǎn)的大小、位置等信息,基本上就是 CSS 盒狀模型的所有信息,如下:
- x:元素左上角相對(duì)于視口的橫坐標(biāo)
- y:元素左上角相對(duì)于視口的縱坐標(biāo)
- height:元素高度
- width:元素寬度
- left:元素左上角相對(duì)于視口的橫坐標(biāo),與 x 屬性相等
- right:元素右邊界相對(duì)于視口的橫坐標(biāo)(等于 x + width)
- top:元素頂部相對(duì)于視口的縱坐標(biāo),與 y 屬性相等
- bottom:元素底部相對(duì)于視口的縱坐標(biāo)(等于 y + height)
32. 實(shí)現(xiàn) thunk
將數(shù)組(array)拆分成多個(gè) size 長(zhǎng)度的區(qū)塊,并將這些區(qū)塊組成一個(gè)新數(shù)組。 如果 array 無(wú)法被分割成全部等長(zhǎng)的區(qū)塊,那么最后剩余的元素將組成一個(gè)區(qū)塊。
例如:
chunk(['a', 'b', 'c', 'd'], 2)
// => [['a', 'b'], ['c', 'd']]
chunk(['a', 'b', 'c', 'd'], 3)
// => [['a', 'b', 'c'], ['d']]
方法1
function chunk(array, size = 1) {
const length = array == null ? 0 : array.length
let start = 0
if (!length || size < 1) return []
const result = []
while (start < length) {
result.push(array.slice(start, (start += size)))
}
return result
}
方法2
function chunk(arr, limit) {
if (!arr.length || limit < 1) return []
return arr.reduce(
(pre, cur) => {
const temp = pre[pre.length - 1]
if (temp.length < limit) {
temp.push(cur)
} else {
pre.push([cur])
}
return pre
},
[[]]
)
}
33. 實(shí)現(xiàn)render函數(shù) 將VNode轉(zhuǎn)成真是VNode,并插入文檔
VNode
const VNode = {
tagName: 'ul',
props: {
class: 'list'
},
children: [
{
tagName: 'li',
props: {
class: 'item'
},
children: ['item1']
},
{
tagName: 'li',
props: {
style: 'color: red'
},
children: ['item2']
},
{
tagName: 'li',
children: ['item3'],
props: {
style: 'cursor: pointer'
},
on: {
click: () => {
console.log('click item3')
}
}
}
]
}
實(shí)現(xiàn)如下:
<div id="app"></div>
// 元素節(jié)點(diǎn)
function createEle(tag, props = {}, on = {}) {
const ele = document.createElement(tag)
const keys = Object.keys(props)
const events = Object.keys(on)
keys.length && keys.forEach(key => {
ele.setAttribute(key, props[key])
})
events.length && events.forEach(event => {
ele.addEventListener(event, on[event])
})
return ele
}
// 文本節(jié)點(diǎn)
function createTextNode(text) {
return document.createTextNode(text)
}
function render(VNode) {
const { tagName, props, children, on } = VNode
const ele = createEle(tagName, props, on)
if (children.length) {
const fragment = document.createDocumentFragment()
children.forEach(child => {
if (typeof child === 'object' && child !== null) {
fragment.appendChild(render(child))
} else {
fragment.appendChild(createTextNode(child))
}
})
ele.appendChild(fragment)
}
return ele
}
document.querySelector('#app').appendChild(render(VNode))
34. (鏈?zhǔn)秸{(diào)用)要求設(shè)計(jì) LazyMan 類(lèi),實(shí)現(xiàn)以下功能
new LazyMan('Tony')
// Hi I am Tony
new LazyMan('Tony').sleep(10).eat('lunch')
// Hi I am Tony
// 等待了10秒...
// I am eating lunch
new LazyMan('Tony').eat('lunch').sleep(10).eat('dinner')
// Hi I am Tony
// I am eating lunch
// 等待了10秒...
// I am eating diner
new LazyMan('Tony').eat('lunch').eat('dinner').sleepFirst(5).sleep(10).eat('junk food')
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了10秒...
// I am eating junk food
35. 實(shí)現(xiàn) Object.is()
比較兩個(gè)值是否相等。
在這里NaN等于NaN,+0不等于-0。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
實(shí)現(xiàn)思路
如果兩個(gè)值相等,就要進(jìn)一步排除0的情況;如果不相等就需要排除NaN的情況。
Object.defineProperty(Object, 'is', {
value(x, y) {
if (x === y) {
// 針對(duì)+0 不等于 -0的情況
return x !== 0 || 1 / x === 1 / y
}
// 針對(duì)NaN的情況
return x !== x && y !== y
},
configurable: true,
enumerable: false,
writable: true
})
36. a 在什么情況下會(huì)打印 1?
const a = {
i: 1,
toString() {
return a.i++
}
}
// 進(jìn)行隱式轉(zhuǎn)換 Object == Primitive,需要Object轉(zhuǎn)為Primitive(具體通過(guò)valueOf和toString方法)
if (a == 1 && a == 2 && a == 3) {
console.log(1)
}
const a = [1, 2, 3]
// 移除第一個(gè)元素,返回值為移除后的元素
a.toString = a.shift
// 對(duì)象和數(shù)字比較,會(huì)調(diào)用對(duì)象的toString方法
if (a == 1 && a == 2 && a == 3) {
console.log(1)
}
Vue
1. vue
1. vue響應(yīng)式和依賴(lài)收集
2. nextTick 原理
let timerFunc
const noop = () => {}
// 有、且是原生Promise
if (typeof Promise !== 'undefined' && isNative(Promise)) { // Promise
const p = Promise.resolve()
timerFunc = () => {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if (!isIE && typeof MutationObserver !== 'undefined' && (
isNative(MutationObserver) ||
// PhantomJS and iOS 7.x
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () => {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) {
timerFunc = () => {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () => {
setTimeout(flushCallbacks, 0)
}
}
// 傳入回調(diào)和返回Promise實(shí)例只能二選一
export function nextTick (cb?: Function, ctx?: Object) {
let _resolve
callbacks.push(() => {
// 如果傳入了回調(diào),則執(zhí)行回調(diào)。反之執(zhí)行Promise的resolve方法
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')
}
} else if (_resolve) {
_resolve(ctx)
}
})
if (!pending) {
pending = true
timerFunc() // 該函數(shù)內(nèi)部的flushCallbacks是異步的,所以不會(huì)阻塞下面的流程
}
// $flow-disable-line
// 如果沒(méi)有傳入回調(diào),并且支持Promise的話(huà),返回一個(gè)Promise的實(shí)例
if (!cb && typeof Promise !== 'undefined') {
return new Promise(resolve => {
_resolve = resolve
})
}
}
3. keep-alive 原理
4. key 的作用
- key的作用主要是為了更高效的更新虛擬DOM。
- vue在patch過(guò)程中判斷兩個(gè)節(jié)點(diǎn)是否是相同節(jié)點(diǎn)的一個(gè)必要條件就是key。在渲染一組列表時(shí),key往往是唯一標(biāo)識(shí),所以如果不定義key的話(huà),vue只能認(rèn)為比較的兩個(gè)節(jié)點(diǎn)是同一個(gè),哪怕它們實(shí)際上不是,這導(dǎo)致了頻繁更新元素,使得整個(gè)patch過(guò)程比較低效,影響性能。
- 實(shí)際使用中在渲染一組列表時(shí)key必須設(shè)置,而且必須是唯一標(biāo)識(shí),應(yīng)該避免使用數(shù)組索引作為key,這可能導(dǎo)致一些隱蔽的bug;
- vue中在使用相同標(biāo)簽元素過(guò)渡切換時(shí),也會(huì)使用key屬性,其目的也是為了讓vue可以區(qū)分它們,否則vue只會(huì)替換其內(nèi)部屬性而不會(huì)觸發(fā)過(guò)渡效果。
從源碼中可以知道,vue判斷兩個(gè)節(jié)點(diǎn)是否相同時(shí)主要判斷兩者的key和元素類(lèi)型等,因此如果不設(shè)置key,它的值就是undefined,則可能永遠(yuǎn)認(rèn)為這是兩個(gè)相同節(jié)點(diǎn),只能去做更新操作,這造成了大量的dom更新操作,明顯是不可取的。
5. VNode有哪些好處?
- 優(yōu)化性能 。DOM 操作是比較耗時(shí)的,對(duì)于大量、頻繁的 DOM 操作,如果先在 JavaScript 中模擬進(jìn)行,然后再通過(guò)計(jì)算比對(duì),找到真正需要更新的節(jié)點(diǎn),這樣就有可能減少不必要的 DOM 操作,從而提升渲染性能。
- 跨平臺(tái) 。由于虛擬 DOM 以 JavaScript 對(duì)象為基礎(chǔ),所以可根據(jù)不同的運(yùn)行環(huán)境進(jìn)行代碼轉(zhuǎn)換(比如瀏覽器、服務(wù)端、原生應(yīng)用等),這使得它具有了跨平臺(tái)的能力。
一定比真實(shí)DOM的性能高嗎?
并不是所有的 DOM 操作都能通過(guò)虛擬 DOM 提升性能,比如單次刪除某個(gè)節(jié)點(diǎn),直接操作 DOM 肯定比虛擬 DOM 計(jì)算比對(duì)之后再刪除要快??傮w而言, 虛擬 DOM 提升了 DOM 操作的性能下限,降低了 DOM 操作的性能上限。 所以會(huì)看到一些對(duì)渲染性能要求比較高的場(chǎng)景,比如在線(xiàn)文檔、表格編輯,還是會(huì)使用原生 DOM 操作。
6. 組件通信
- 父?jìng)髯?props
- 子傳父
on
- 事件總線(xiàn) eventBus
- 跨組件之間 provide/inject 常見(jiàn)于組件庫(kù)的封裝,實(shí)際項(xiàng)目很少會(huì)用到
-
children/ref獲取子組件實(shí)例
-
listeners
- vuex
7. 生命周期
8. diff 算法
9. vue 中優(yōu)化策略
- 路由懶加載
- keep-alive
- 頻繁切換顯隱式的使用v-show
- 靜態(tài)列表通過(guò)Object.freeze()不開(kāi)啟響應(yīng)式
- 無(wú)狀態(tài)組件使用函數(shù)式組件
- 長(zhǎng)列表使用虛擬列表
- 事件代理
- 圖片懶加載
- 三方組件按需引入
- SSR
2. vuex
1. actions 和 mutations 區(qū)別
- 必須通過(guò)提交 mutation 的方式修改 state
- mutation 中只可以做同步操作
- action 中可同步可異步
2. vuex 原理
3. vuex 如何實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)式
new Vue({
data: state
})
4. mapState 實(shí)現(xiàn)方式
3. vue-router
1. 導(dǎo)航守衛(wèi)
-
全局守衛(wèi)
- beforeEach 全局前置守衛(wèi)
- beforeResolve 全局解析守衛(wèi)
- afterEach 全局后置
-
路由獨(dú)享守衛(wèi)
- beforeEnter
-
組件守衛(wèi)
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
2. 模式
- hash
- history
3. 原理
4. vue3
1、vue2 和 vue3 響應(yīng)式原理對(duì)比,及具體實(shí)現(xiàn)思路
vue2響應(yīng)式的缺陷
- 初始化時(shí)需要遍歷對(duì)象所有key,如果對(duì)象層級(jí)較深,性能不好
- 不能監(jiān)聽(tīng)數(shù)組的變化,需要重寫(xiě)數(shù)組的變異方法
- 通知更新過(guò)程需要維護(hù)大量dep實(shí)例和watcher實(shí)例,額外占用內(nèi)存較多
- 動(dòng)態(tài)新增、刪除對(duì)象屬性無(wú)法攔截,只能用特定set/delete api代替
- 不支持新的Map、Set等數(shù)據(jù)結(jié)構(gòu)
vue3使用Proxy
- 可以同時(shí)支持object和array
- 動(dòng)態(tài)屬性增、刪都可以攔截,新增數(shù)據(jù)結(jié)構(gòu)均支持
- 對(duì)象嵌套屬性運(yùn)行時(shí)遞歸,也就是說(shuō)只有用到時(shí)才代理,也不需要維護(hù)特別多的依賴(lài)關(guān)系,極大的降低性能
缺點(diǎn)就是兼容性問(wèn)題,因?yàn)閜roxy是js引擎提供的API,所以無(wú)法完全polyfill。
2、vue3 做了哪些優(yōu)化
- proxy
- 靜態(tài)節(jié)點(diǎn)標(biāo)記
- VNode 重構(gòu)
3、composition API 有哪些特性
setup()
React
1. React
- React 如何區(qū)分 class 和 functional?
- 為什么要寫(xiě) super(props)?
- 為什么在編寫(xiě)組件時(shí)沒(méi)有用到 React 還要引入 React?
- JSX 原理
- setState() 是同步還是異步的?
- 高階組件 HOC
- 解決了什么問(wèn)題
- render props
- HOOKS
- useEffect 如何實(shí)現(xiàn) class 組件的 componentDidMounted 和 componentDidUnMounted
- useCallback() 和 useMemo() 的區(qū)別
2. Redux
- 實(shí)現(xiàn)簡(jiǎn)易版 redux
function createStore(reducer) {
let state = reducer(undefined, {})
let callback = []
return {
getState() {
return state
},
dispatch(action) {
state = reducer(state, action)
callback.forEach(fn => fn())
},
subscribe(handler) {
callback.push(handler)
},
unsubscribe(l) {
callback = callback.filter(fn => fn !== l)
}
}
}
- 實(shí)現(xiàn) bindActionCreators
/**
* @param {Object | Function} actionCreators (Function: action creator,Object:value為action creator的對(duì)象)
* @param {Function} dispatch 由store提供的dispatch函數(shù)
* @returns {Object | Function}
*/
const bindActionCreators = (actionCreators, dispatch) => {
if (typeof actionCreators === 'function') {
return (...args) => {
dispatch(actionHandle(...args))
}
}
return Object.keys(actionCreators).reduce((key, creators) => {
creators[key] = (...args) => dispatch(creators[key](...args))
return handle
}, {})
}
- 實(shí)現(xiàn) combineReducers
const combineReducers = reducers => {
return (state = {}, action) => {
return Object.keys(reducers).reduce((nextState, key) => {
nextState[key] = reducers[key](state[key], action)
return nextState
}, {})
}
}
- redux 中間件實(shí)現(xiàn)原理
中間件本質(zhì)上就是一個(gè)函數(shù)。 因?yàn)?reducer 為純函數(shù),且派發(fā)出 dispatch 之后,redux 內(nèi)部會(huì)立即調(diào)用 reducer,所以只能在發(fā)出 action 和執(zhí)行 reducer 之間做操作。
// 保存store原本的dispatch方法
const dispatch = store.dispatch
// 重寫(xiě)store.dispatch
store.dispatch = action => {
setTimeout(() => {
console.log('異步執(zhí)行完成,派發(fā)action')
dispatch(action)
}, 2000)
}
store.dispatch({ type: 'ADD' })
Node
1. Node
- http
- fs
- path.join() 和 path.resolve()區(qū)別?
- 啟動(dòng)一個(gè) http 服務(wù)
2. Express
3. Koa2
瀏覽器
1. 輸入 URL 到頁(yè)面展示做了什么
用戶(hù)輸入關(guān)鍵詞,地址欄判斷是搜索內(nèi)容還是 url 地址。
如果是搜索內(nèi)容,會(huì)使用瀏覽器默認(rèn)搜索引擎加上搜索內(nèi)容合成 url;
如果是域名會(huì)加上協(xié)議(如 https)合成完整的 url。然后按下回車(chē)。瀏覽器進(jìn)程通過(guò) IPC(進(jìn)程間通信)把 url 傳給網(wǎng)絡(luò)進(jìn)程(網(wǎng)絡(luò)進(jìn)程接收到 url 才發(fā)起真正的網(wǎng)絡(luò)請(qǐng)求)。
網(wǎng)絡(luò)進(jìn)程接收到 url 后,先查找有沒(méi)有緩存。
有緩存并且緩存還在生存期內(nèi),直接返回緩存的資源。
緩存過(guò)期或沒(méi)有緩存。(進(jìn)入真正的網(wǎng)絡(luò)請(qǐng)求)。如果緩存過(guò)期了,瀏覽器則會(huì)繼續(xù)發(fā)起網(wǎng)絡(luò)請(qǐng)求,并且在 HTTP 請(qǐng)求頭中帶上If-None-Match: "XXX"
首先獲取域名的 IP,系統(tǒng)會(huì)首先自動(dòng)從 hosts 文件(DNS 緩存)中尋找域名對(duì)應(yīng)的 IP 地址,一旦找到,和服務(wù)器建立 TCP 連接;如果沒(méi)有找到,則系統(tǒng)會(huì)將網(wǎng)址提交 DNS 域名解析服務(wù)器進(jìn)行 IP 地址的解析。
利用 IP 地址和服務(wù)器建立 TCP 連接(3 次握手)。
建立連接后,瀏覽器網(wǎng)絡(luò)進(jìn)程構(gòu)建數(shù)據(jù)包(包含請(qǐng)求行,請(qǐng)求頭,請(qǐng)求正文,并把該域名相關(guān) Cookie 等數(shù)據(jù)附加到請(qǐng)求頭),然后向服務(wù)器發(fā)送請(qǐng)求消息。
服務(wù)器接收到消息后根據(jù)請(qǐng)求信息構(gòu)建響應(yīng)數(shù)據(jù)(包括響應(yīng)行,響應(yīng)頭,響應(yīng)正文),然后發(fā)送回網(wǎng)絡(luò)進(jìn)程。
網(wǎng)絡(luò)進(jìn)程接收到響應(yīng)數(shù)據(jù)后進(jìn)行解析。
如果發(fā)現(xiàn)響應(yīng)行的返回的狀態(tài)碼為 301,302,說(shuō)明服務(wù)器需要瀏覽器重定向到其他 URL,這時(shí)網(wǎng)絡(luò)進(jìn)程會(huì)找響應(yīng)頭中的 Location 字段的 URL,重新發(fā)起 HTTP 或者 HTTPS 請(qǐng)求。
如果返回的狀態(tài)碼為 200,說(shuō)明服務(wù)器返回了數(shù)據(jù)。
如果狀態(tài)碼是 304,則說(shuō)明緩存內(nèi)容在服務(wù)端沒(méi)有更新,服務(wù)器不會(huì)再返回內(nèi)容,讓瀏覽器從緩存中讀取。
網(wǎng)絡(luò)進(jìn)程將響應(yīng)頭和響應(yīng)行傳給瀏覽器進(jìn)程,瀏覽器進(jìn)程根據(jù)響應(yīng)頭中的
Content-Type告訴瀏覽器服務(wù)器返回的數(shù)據(jù)類(lèi)型。如果返回的類(lèi)型是 text/html,則告訴瀏覽器返回的是 HTML,如果是 application/octet-stream 則為下載類(lèi)型,那么請(qǐng)求會(huì)交給瀏覽器的下載管理器,同時(shí) URL 的導(dǎo)航到此結(jié)束,如果是 HTML,那么瀏覽器會(huì)繼續(xù)進(jìn)行導(dǎo)航流程。(導(dǎo)航:用戶(hù)發(fā)出 URL 請(qǐng)求到頁(yè)面開(kāi)始解析的這個(gè)過(guò)程,就叫做導(dǎo)航)瀏覽器進(jìn)程接收到網(wǎng)絡(luò)進(jìn)程的響應(yīng)頭數(shù)據(jù)后,向渲染進(jìn)程發(fā)出“提交導(dǎo)航”(CommitNavigation)的消息(帶著響應(yīng)頭等消息)。
渲染進(jìn)程接收到提交導(dǎo)航的消息后,準(zhǔn)備渲染進(jìn)程。創(chuàng)建新的渲染進(jìn)程還是復(fù)用,規(guī)則如下:
如果是新打開(kāi)的頁(yè)面,會(huì)單獨(dú)使用一個(gè)渲染進(jìn)程;
如果是A頁(yè)面打開(kāi)“同一站點(diǎn)”的B頁(yè)面,則會(huì)復(fù)用A頁(yè)面的渲染進(jìn)程;
如果不是“同一站點(diǎn)”,則單獨(dú)創(chuàng)建新的渲染進(jìn)程;渲染進(jìn)程準(zhǔn)備好后便和網(wǎng)絡(luò)進(jìn)程建立數(shù)據(jù)傳輸?shù)摹肮艿馈薄?/p>
等文檔數(shù)據(jù)傳輸完成后,渲染進(jìn)程會(huì)返回“確認(rèn)提交”的消息給瀏覽器進(jìn)程。意思是告訴瀏覽器進(jìn)程我已經(jīng)準(zhǔn)備好接收網(wǎng)絡(luò)進(jìn)程的數(shù)據(jù)和解析頁(yè)面數(shù)據(jù)了。
-
瀏覽器進(jìn)程接收到“確認(rèn)提交”消息后移除舊的文檔,然后更新瀏覽器界面,包括 web 頁(yè)面(空白頁(yè))、導(dǎo)航按鈕、URL 地址欄、網(wǎng)絡(luò)安全狀態(tài)。瀏覽器的加載按鈕還是加載中狀態(tài)。至此,導(dǎo)航流程就完成了。接下來(lái)就是解析渲染階段了。
- 構(gòu)建 DOM
- 輸入 html --> 解析器 ---> DOM Tree
- 樣式計(jì)算
- css 來(lái)源:行內(nèi)、style、外部(link 引入)
- css ---> styleSheets
- css 轉(zhuǎn)成 styleSheets
- 屬性標(biāo)準(zhǔn)化
- 計(jì)算 DOM 樹(shù)中每個(gè)節(jié)點(diǎn)的具體樣式 (繼承、層疊)
- 布局 layout DOM 樹(shù)&styleSheets ---> 布局樹(shù)
- 創(chuàng)建布局樹(shù)(只包含可見(jiàn)元素)
- 布局計(jì)算
- 分層 特定節(jié)點(diǎn)生成專(zhuān)用圖層,并生成圖層樹(shù)
圖層生成的條件:
- 擁有層疊上下文屬性的元素
- 需要剪裁的元素
在主線(xiàn)程上生成圖層的繪制列表,將繪制列表提交給合成線(xiàn)程
-
柵格化
- 主線(xiàn)程將繪制列表提交給合成線(xiàn)程,合成線(xiàn)程將圖層分塊,生成圖塊(圖塊是柵格化的最小單位)
- 光柵化線(xiàn)程池和GPU生成位圖
- 圖塊被光柵化完成后,合成線(xiàn)程就會(huì)生成一個(gè)繪制圖塊的命令——“DrawQuad”,然后將該命令提交給瀏覽器進(jìn)程。
-
合成
- 合成線(xiàn)程提交命令 ---> 瀏覽器進(jìn)程 ---> viz 組件接收命令 ---> 合成圖片 ---> 顯示
- 合成的圖層會(huì)被提交給瀏覽器進(jìn)程,瀏覽器進(jìn)程里會(huì)執(zhí)行顯示合成(Display Compositor),也就是將所有的圖層合成為可以顯示的頁(yè)面圖片。 最終顯示器顯示的就是瀏覽器進(jìn)程中合成的頁(yè)面圖片。
渲染進(jìn)程開(kāi)始頁(yè)面解析和加載子資源(邊下載邊解析),一旦資源加載、渲染完畢,渲染進(jìn)程會(huì)發(fā)送一個(gè)消息給瀏覽器進(jìn)程,瀏覽器接收到這個(gè)消息后會(huì)停止標(biāo)簽圖標(biāo)的加載動(dòng)畫(huà)。
- 構(gòu)建 DOM
至此,一個(gè)完整的頁(yè)面形成了。
2. 重繪和重排
為什么 DOM 操作耗費(fèi)性能和時(shí)間?
- 渲染引擎和 js 引擎切換,(俗稱(chēng)上下文切換)耗費(fèi)性能。
- 重繪/重排(元素及樣式變化引起的再次渲染),而且重排耗時(shí)明顯高于重繪
- 重排一定引起重繪,而重繪不一定引起重排
引起重繪的操作:
- 修改元素樣式,比如顏色、bgc、border-color
引起重排的操作:
- 內(nèi)容改變
- 增、刪 DOM
- DOM 幾何屬性變化,如:寬高、位置、邊框、padding、margin
- DOM 樹(shù)結(jié)構(gòu)發(fā)生改變
- 瀏覽器窗口尺寸改變
- 頁(yè)面一開(kāi)始渲染(不可避免的)
- 獲取某些布局屬性時(shí)。當(dāng)獲取一些屬性值時(shí),瀏覽器為了保證獲取到正確的值也會(huì)引起重排。如:
- offsetTop,offsetLeft,offsetHeight,offsetWidth
- scrollTop,scrollWidth,scrollLeft,scrollHeight
- clientWidth,clientHeight,clientLeft,clientTop
- getComputedStyle()
- getBoundingClientRect()
- 更多參考這里
如何減少重排和重繪?
- 批量操作 DOM
- 在循環(huán)外操作元素
- 拼接字符串 --> innerHTML
- DocumentFragment 文檔片段
- 緩存元素集合
- 復(fù)雜動(dòng)畫(huà),使用絕對(duì)定位讓其脫離文檔流
- CSS3 硬件加速(GPU 加速)
- transform
- opacity
- filters
- will-change
硬件加速
- 使用硬件加速可以讓 transform、opacity、filters 不會(huì)引起重繪和回流,但是對(duì)于 bgc 這些屬性還是會(huì)引起重繪
- 硬件加速不可濫用,因?yàn)闀?huì)占用內(nèi)存較大,會(huì)影響性能
CSS 加載阻塞情況
- CSS 加載不會(huì)阻塞 DOM 樹(shù)生成
- CSS 加載會(huì)阻塞 DOM 樹(shù)的渲染。主要是瀏覽器出于性能考慮,避免渲染完成后又有樣式變動(dòng),造成回流和重繪
- CSS 加載會(huì)阻塞后面 js 的執(zhí)行
縮短白屏?xí)r間,盡可能加快 CSS 加載速度
- 使用 CDN
- 壓縮 CSS
- 合理使用緩存
- 減少 HTTP 請(qǐng)求數(shù)(合并 CSS)
原理:
- HTML 解析和 CSS 解析是并行的過(guò)程,所以 CSS 加載不會(huì)阻塞 DOM 樹(shù)生成
- render Tree 的形成依賴(lài)于 DOM Tree 和 CSSOM Tree,所以必須等到 CSS 加載完成才渲染
- 因?yàn)?js 可能會(huì)操作 DOM 節(jié)點(diǎn)和 CSS 樣式,因此樣式表會(huì)在加載完畢后執(zhí)行后面的 js
跨域
原因:
瀏覽器同源策略的限制。
解決方案:
- jsonp
- cors
- Iframe
- postMessage
- node middleware
- nginx
- webSocket
網(wǎng)絡(luò)
應(yīng)用層
1. HTTP
1.1 狀態(tài)碼
- 1xx 收到請(qǐng)求,需要請(qǐng)求繼續(xù)執(zhí)行操作
- 2xx 成功
- 200 ok
- 204 沒(méi)有資源可返回
- 206 返回部分資源,(請(qǐng)求范圍資源)比如:音視頻文件
- 3xx 重定向、瀏覽器需要執(zhí)行某些特殊處理以完成正常請(qǐng)求
- 301 永久重定向 表示舊地址資源被永久地移除了(這個(gè)資源不可訪問(wèn)了),搜索引擎在抓取新內(nèi)容的同時(shí)也將舊的網(wǎng)址交換為重定向之后的網(wǎng)址
- 302 臨時(shí)重定向 表示舊地址 A 的資源還在(仍然可以訪問(wèn)),這個(gè)重定向只是臨時(shí)地從舊地址 A 跳轉(zhuǎn)到地址 B,搜索引擎會(huì)抓取新的內(nèi)容而保存舊的網(wǎng)址
- 304 協(xié)商緩存
- 4xx 客戶(hù)度錯(cuò)誤
- 400 請(qǐng)求報(bào)文中存在語(yǔ)法錯(cuò)誤
- 401 表示發(fā)送的請(qǐng)求需要有通過(guò) HTTP 認(rèn)證的認(rèn)證信息
- 403 Forbidden(禁止) 請(qǐng)求被服務(wù)器拒絕了
- 404 Not Found
- 405 Method Not Allowed(不允許使用的方法)
- 406 請(qǐng)求的 content-Type 和相應(yīng)的 content-type 不一致。說(shuō)白了就是后臺(tái)返回的資源前臺(tái)無(wú)法解析
- 416 所請(qǐng)求的范圍無(wú)法滿(mǎn)足(讀取文件時(shí)設(shè)置的 Range 有誤造成的)
- 5xx 服務(wù)端錯(cuò)誤
- 500 表示服務(wù)器在執(zhí)行請(qǐng)求時(shí)發(fā)生了錯(cuò)誤
- 503 表示服務(wù)器暫時(shí)處于超負(fù)載或正在進(jìn)行停機(jī)維護(hù)狀態(tài),現(xiàn)在無(wú)法處理請(qǐng)求
1.2 request
- 請(qǐng)求行 method、URI、HTTP 版本
- 請(qǐng)求頭
- 請(qǐng)求體
1.3 response
- 狀態(tài)行 狀態(tài)碼、原因短語(yǔ)、服務(wù)器HTTP版本
- 響應(yīng)頭
- 響應(yīng)體
1.4 http 頭
connection: keep-alive 長(zhǎng)連接
google chrome 默認(rèn)同時(shí)最多可以建立6個(gè)TCP連接,TCP上http傳輸是串行的。(HTTP1.1)
HTTP2 只建立一個(gè)TCP,HTTP并行,理論上沒(méi)有數(shù)量限制。
http 緩存
http緩存分為強(qiáng)緩存和協(xié)商緩存。詳細(xì)內(nèi)容查看 HTTP 緩存
2. FTP
文件傳輸協(xié)議
3. DNS
域名解析系統(tǒng)。域名和 IP 的映射,域名查 IP,IP 反查域名。
2. 傳輸層
1. TCP
面向連接的、可靠的、字節(jié)流服務(wù)。
- 面向連接 ---> TCP 連接
- 可靠的
- 具有重發(fā)機(jī)制,不會(huì)丟包
- TCP 三次握手
- 數(shù)據(jù)包上標(biāo)記序號(hào),到達(dá)接收方可以重組
- 字節(jié)流服務(wù) --> 大數(shù)據(jù)包切割成報(bào)文段的小數(shù)據(jù)包
缺陷:
傳輸速度慢,不如 UDP
2. UDP
- 無(wú)連接的
- 易丟包
- 無(wú)序無(wú)法重組
- 傳輸速度快
- 場(chǎng)景:直播、視頻會(huì)議等
3. 網(wǎng)絡(luò)層
IP 協(xié)議 負(fù)責(zé)傳輸
4. 鏈路層
操作系統(tǒng)、顯卡等物理器件
HTTPS
HTTP 缺點(diǎn):
1. 明文傳輸,不加密,內(nèi)容容易被竊聽(tīng)
解決:
- 內(nèi)容(報(bào)文)加密,仍不可靠,容易被篡改
- SSL 通信加密 建立安全的通信線(xiàn)路,http 在該線(xiàn)路上傳輸
2. 無(wú)法驗(yàn)證身份,容易被偽裝(通信雙方無(wú)法確認(rèn)對(duì)方身份)
解決:
SSL/TLS 證書(shū)認(rèn)證
3. 無(wú)法保證內(nèi)容的完整性,容易被篡改(明文傳輸,無(wú)法驗(yàn)證身份,導(dǎo)致內(nèi)容容易被篡改)
解決:
SSL/TLS
HTTPS
HTTP + 通信加密 + 身份認(rèn)證 + 內(nèi)容完整性保護(hù) = HTTPS
SSL/TLS 不僅提供了通信加密,還提供了證書(shū)認(rèn)證,及內(nèi)容完整性保護(hù)。
缺點(diǎn):
使用 SSL/TLS 處理速度變慢
- SSL/TLS 建立連接耗費(fèi)時(shí)間
- 通信慢: SSL/TLS 通信部分消耗網(wǎng)絡(luò)資源
- 通信雙方進(jìn)行加解密處理,消耗大量的 CPU 和內(nèi)存等資源
HTTP1.1
HTTP/1.1 為網(wǎng)絡(luò)效率做了大量的優(yōu)化,最核心的有如下三種方式:
- 增加了持久連接
- 瀏覽器為每個(gè)域名最多同時(shí)維護(hù) 6 個(gè) TCP 持久連接
- 使用 CDN 的實(shí)現(xiàn)域名分片機(jī)制
HTTP/1.1 的主要問(wèn)題
對(duì)帶寬的利用率不理想
原因:
- TCP 的慢啟動(dòng)
- 同時(shí)開(kāi)啟了多條 TCP 連接,那么這些連接會(huì)競(jìng)爭(zhēng)固定的帶寬
- HTTP/1.1 隊(duì)頭阻塞的問(wèn)題(一個(gè)TCP同時(shí)可以進(jìn)行一個(gè)HTTP傳輸)
慢啟動(dòng)和 TCP 連接之間相互競(jìng)爭(zhēng)帶寬是由于 TCP 本身的機(jī)制導(dǎo)致的,而隊(duì)頭阻塞是由于 HTTP/1.1 的機(jī)制導(dǎo)致的。
HTTP2 特性
- 多路復(fù)用機(jī)制:
- 一個(gè)域名建立一個(gè) TCP 連接
- 一個(gè)TCP可以同時(shí)進(jìn)行多個(gè)HTTP的請(qǐng)求和響應(yīng)
- 可以設(shè)置請(qǐng)求的優(yōu)先級(jí):在發(fā)送請(qǐng)求時(shí),可以標(biāo)上該請(qǐng)求的優(yōu)先級(jí)
- 服務(wù)器推送:提前將靜態(tài)資源推送到客戶(hù)端
- 頭部壓縮
算法
1. 排序
1.1 冒泡排序
思路:
雙層遍歷,第二層遍歷,當(dāng)前值和下一個(gè)值比較,根據(jù)大小交換位置 修改的是原數(shù)組
時(shí)間復(fù)雜度 O(n^2) 空間復(fù)雜度 O(1)
function sort(array) {
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array.length - i; j++) {
const temp = array[j + 1]
if (array[j] > array[j + 1]) {
array[j + 1] = array[j]
array[j] = temp
}
}
}
return array
}
1.2 快速排序
思路:
選取數(shù)組第一項(xiàng)作為基準(zhǔn)值,從數(shù)組第二項(xiàng)開(kāi)始比較,小的放在左邊數(shù)組,大的放在右邊數(shù)組。
最終結(jié)束時(shí)機(jī)是當(dāng)數(shù)組長(zhǎng)度為 1 時(shí)。最終就是 arr.length 個(gè)長(zhǎng)度為 1 的數(shù)組的合并。
返回的是新數(shù)組。
時(shí)間復(fù)雜度: O(nlogn)
function quickSort(arr) {
if (arr.length <= 1) return arr
const base = arr[0]
const left = []
const right = []
for (let i = 1; i < arr.length; i++) {
const temp = arr[i]
if (base >= temp) {
left.push(temp)
} else {
right.push(temp)
}
}
return [...quickSort(left), base, ...quickSort(right)]
}
2. 反轉(zhuǎn)數(shù)組
直接修改原數(shù)組
1. 遍歷
function reverse(arr) {
const mid = arr.length / 2
for (let i = 0; i < mid; i++) {
;[arr[arr.length - 1 - i], arr[i]] = [arr[i], arr[arr.length - 1 - i]]
}
return arr
}
2. 借助 reduce
function reverseReduce(arr) {
return arr.reduce((pre, cur) => [cur, ...pre], [])
}
3. 借助 reduceRight
function reverseReduceRight(arr) {
return arr.reduceRight((pre, cur) => [...pre, cur], [])
}
3. 二分查找
前提條件:一定是有序數(shù)組。
查找有序數(shù)組中的某一項(xiàng),以下實(shí)現(xiàn)方式假設(shè)數(shù)組是遞增的。
- 非遞歸方式
function binarySearch(arr, target) {
let start = 0
let end = arr.length - 1
while (start <= end) {
let mid = Math.floor((start + end) / 2)
const midVal = arr[mid]
if (target === midVal) return midVal
if (target > midVal) {
start = mid + 1
} else {
end = mid - 1
}
}
return false
}
}
- 遞歸方式
function binary(arr, target, start = 0, end = arr.length - 1) {
if (start > end) return false
const mid = Math.floor((start + end) / 2)
const val = arr[mid]
if (val === target) return mid
if (val > target) return binary(arr, target, start, mid - 1)
return binary(arr, target, mid + 1, end)
}
4. 斐波那契數(shù)列
寫(xiě)一個(gè)函數(shù),輸入 n ,求斐波那契(Fibonacci)數(shù)列的第 n 項(xiàng)。斐波那契數(shù)列的定義如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契數(shù)列由 0 和 1 開(kāi)始,之后的斐波那契數(shù)就是由之前的兩數(shù)相加而得出。
答案需要取模 1e9+7(1000000007),如計(jì)算初始結(jié)果為:1000000008,請(qǐng)返回 1。
思路:
斐波那契數(shù)是由前兩數(shù)相加而得,需對(duì)初始值 0 和 1 做單獨(dú)判斷。
之后只需做一次 for 循環(huán)遍歷,需要注意得是,為避免時(shí)間復(fù)雜度的消耗,需要緩存前兩數(shù)的結(jié)果,最后按題要求,所得的數(shù)需要經(jīng)過(guò)取模運(yùn)算。
var fib = function (n) {
if (n <= 1) return n
// n-1
let prev1 = 1
// n-2
let prev2 = 0
let sum = 0
for (let i = 2; i <= n; i++) {
sum = (prev1 + prev2) % 1000000007
prev2 = prev1
prev1 = sum
}
return sum
}
5. 青蛙跳臺(tái)階
一只青蛙一次可以跳上 1 級(jí)臺(tái)階,也可以跳上 2 級(jí)臺(tái)階。求該青蛙跳上一個(gè) n 級(jí)的臺(tái)階總共有多少種跳法。
答案需要取模 1e9+7(1000000007),如計(jì)算初始結(jié)果為:1000000008,請(qǐng)返回 1。
示例 1:
輸入:n = 2
輸出:2
示例 2:
輸入:n = 7
輸出:21
示例 3:
輸入:n = 0
輸出:1
思路:
和斐波那契一樣的思路,區(qū)別是斐波那契的 0 項(xiàng)為 0,而爬樓梯的 0 項(xiàng)是 1
空間復(fù)雜度: O(1) 時(shí)間復(fù)雜度: O(n)
function numWays(n) {
if (n <= 1) return 1
let a = 1
let b = 2
let sum
for (let i = 3; i <= n; i++) {
sum = (a + b) % 1000000007
a = b
b = sum
}
return b
}
遞歸實(shí)現(xiàn)方式
function bar(n) {
if (n <= 1) return 1
if (n < 3) return n
return f(n - 1) + f(n - 2)
}
6. 有序數(shù)組合并 排序
假設(shè)是升序的
function concatSort(a, b) {
let i = 0
let j = 0
const arr = []
while (i < a.length && j < b.length) {
if (a[i] > b[j]) {
arr.push(b[j++])
} else {
arr.push(a[i++])
}
}
while (i < a.length) {
arr.push(a[i++])
}
while (j < b.length) {
arr.push(b[j++])
}
return arr
}
7. 刪除數(shù)組中的重復(fù)項(xiàng)
給定一個(gè)排序數(shù)組,需要在 原地 刪除重復(fù)出現(xiàn)的元素,使得每個(gè)元素只出現(xiàn)一次,返回移除后數(shù)組的新長(zhǎng)度。
不要使用額外的數(shù)組空間,你必須在 原地 修改輸入數(shù)組 并在使用 O(1) 額外空間的條件下完成。
方法一:雙指針?lè)ǎ熘羔槪?/h3>
思路:
數(shù)組完成排序后,我們可以放置兩個(gè)指針 i 和 j,其中 i 是慢指針,而 j 是快指針。慢指針初始值為 0,快指針初始值為 1.
如果 arr[i] === arr[j],跳過(guò),快指針加 1。反之,慢指針加 1,快指針的值賦值給慢指針。
function removeDuplicates(arr) {
let i = 0
for (let j = 1; j < arr.length; j++) {
if (arr[i] !== arr[j]) {
arr[++i] = arr[j]
}
}
return i + 1
}
方法二: 計(jì)數(shù)排序思想
思路:
因?yàn)轭}目是要求返回去重后數(shù)組的長(zhǎng)度,并不關(guān)心去重后的數(shù)組,所以找出重復(fù)出現(xiàn)的個(gè)數(shù)即可。
由于數(shù)組是有序排列的,定義一個(gè)變量 count = 0; 從位置 1 開(kāi)始遍歷,判斷當(dāng)前元素是否和上一個(gè)元素相等,如果相等 count+1,反之跳過(guò)。
最后 count 就是所有重復(fù)元素出現(xiàn)的個(gè)數(shù)。那么不重復(fù)元素組成的數(shù)組就是 數(shù)組長(zhǎng)度 - count
function removeItem(arr) {
let count = 0
for (let i = 1; i < arr.length; i++) {
if (arr[i] === arr[i - 1]) ++count
}
return arr.length - count
}
8. 組中的第 K 個(gè)最大元素
function findKthLargest(nums, k) {
// 快速排序
function sort(nums) {
if (nums.length <= 1) return nums
let base = nums[0]
let left = []
let right = []
for (let i = 1; i < nums.length; i++) {
if (nums[i] > base) {
right.push(nums[i])
} else {
left.push(nums[i])
}
}
return [...sort(left), base, ...sort(right)]
}
nums = sort(nums)
const len = nums.length
return nums[len - k]
}
9. 兩數(shù)之和
找出數(shù)組中 兩個(gè)元素加起來(lái)等于 target 的元素的索引
1. 雙層循環(huán)
適用情況:對(duì)數(shù)組的排序無(wú)要求,最后返回元素的索引
function twoSum(arr, target) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] + arr[j] === target) {
return [i, j]
}
}
}
return []
}
2. 雙指針對(duì)撞
適用情況: 數(shù)組必須是有序的。
時(shí)間復(fù)雜度 O(n)
空間復(fù)雜度 O(1)
假設(shè)有序遞增數(shù)組
function search(arr, target) {
let left = 0
let right = arr.length - 1
while (left < right) {
const i = arr[left]
const j = arr[right]
if (i + j === target) return [i, j]
if (i + j > target) {
right--
} else {
left++
}
}
return []
}
10. 二維數(shù)組的查找
在一個(gè) n * m 的二維數(shù)組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請(qǐng)完成一個(gè)函數(shù),輸入這樣的一個(gè)二維數(shù)組和一個(gè)整數(shù),判斷數(shù)組中是否含有該整數(shù)。
示例:
現(xiàn)有矩陣 matrix 如下:
;[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
給定 target = 5,返回 true。
給定 target = 20,返回 false。
時(shí)間復(fù)雜度 O(n + m)
空間復(fù)雜度 O(1)
function findNumberIn2DArray(matrix, target) {
if (matrix.length < 1 || matrix[0].length < 1) return false
let row = 0
let col = matrix[0].length - 1
while (row < matrix.length && col >= 0) {
const current = matrix[row][col]
if (current === target) return true
if (current > target) {
col--
} else {
row++
}
}
return false
}
11. 第一個(gè)只出現(xiàn)一次的字符
在字符串 s 中找出第一個(gè)只出現(xiàn)一次的字符。如果沒(méi)有,返回一個(gè)單空格。 s 只包含小寫(xiě)字母。
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
1. Set + 正則
function firstUniqChar(s) {
for (let char of new Set(s)) {
// 正則匹配變量
if (s.match(new RegExp(char, 'g')).length === 1) {
return char
}
}
return ' '
}
2. Map 的 keys 可以保證順序
function search(s) {
const map = s.split('').reduce((pre, cur) => {
const temp = pre.get(cur)
pre.set(cur, temp + 1 || 1)
return pre
}, new Map())
for (let key of map.keys()) {
if (map.get(key) === 1) return key
}
return ' '
}
12. 打印出從 1 到最大的 n 位數(shù)
輸入數(shù)字 n,按順序打印出從 1 到最大的 n 位十進(jìn)制數(shù)。比如輸入 3,則打印出 1、2、3 一直到最大的 3 位數(shù) 999。
示例 1:
輸入: n = 1
輸出: [1,2,3,4,5,6,7,8,9]
function printNumbers(n) {
const length = Math.pow(10, n) - 1
return Array.from({ length }, (_, index) => index + 1)
}
13. 數(shù)組中重復(fù)的數(shù)字
找出數(shù)組中重復(fù)的數(shù)字。
在一個(gè)長(zhǎng)度為 n 的數(shù)組 nums 里的所有數(shù)字都在 0 ~ n-1 的范圍內(nèi)。數(shù)組中某些數(shù)字是重復(fù)的,但不知道有幾個(gè)數(shù)字重復(fù)了,也不知道每個(gè)數(shù)字重復(fù)了幾次。請(qǐng)找出數(shù)組中任意一個(gè)重復(fù)的數(shù)字。
示例 1:
輸入:
[2, 3, 1, 0, 2, 5, 3]
輸出:2 或 3
function findRepeatNumber(arr) {
const set = new Set()
for (let item of arr) {
if (set.has(item)) return item
set.add(item)
}
}
14. 查找出數(shù)組中出現(xiàn)次數(shù)最多的元素
const arr = [1, 2, 3, 2, 2, 1, 3, 3, 3, 3, 4, 4332, 21, 12, 2, 2, 2, 2, 2]
function maxTime(arr) {
if (!arr.length) return
if (arr.length === 1) return arr[0]
let maxEle = arr[0]
let maxCount = 0
arr.reduce((pre, cur) => {
pre = {
...pre,
[cur]: pre[cur] + 1 || 1,
}
if (pre[cur] > maxCount) {
maxCount = pre[cur]
maxEle = cur
}
return pre
}, {})
return maxEle
}
maxTime(arr) // 2
15. 輸出以奇數(shù)結(jié)尾的字符串拼接
題:[任意字符][一位數(shù)字]_,拼接的字符串,輸出以奇數(shù)結(jié)尾的字符串拼接。
輸入:str1_key2_val3_d4_e5
輸出:str1val3e5
let str = 'str1_key2_val3_d4_e5'
- charAt()
let arr = str.split('_')
const result = arr.reduce((pre, cur) => {
if (cur.charAt(cur.length - 1) % 2 !== 0) {
return pre.concat(cur)
} else {
return pre
}
}, '')
- slice()
const result = str.split('_').reduce((pre, cur) => {
if (cur.slice(-1) % 2) {
return (pre += cur)
}
return pre
}, '')
16. 二維數(shù)組的排列組合
設(shè)計(jì)模式
1. 單例模式
class Person {
static getInstance() {
if (!Person.instance) {
console.log('只創(chuàng)建一次')
Person.instance = new Person()
}
return Person.instance
}
constructor(name, age) {}
// 原型方法
getSkill() {
console.log('吃飯')
}
// 靜態(tài)方法
static myWork() {
console.log('睡覺(jué)')
}
}
// 單例模式。實(shí)例只創(chuàng)建一次
const p1 = Person.getInstance()
const p2 = Person.getInstance()
2. 發(fā)布訂閱模式
class EventEmitter {
constructor() {
this.events = {}
}
emit(eventName, ...args) {
const events = this.events[eventName]
if (events) {
events.forEach(event => {
event.apply(null, args)
})
}
}
on(eventName, fn) {
if (this.events[eventName]) {
this.events[eventName].push(fn)
} else {
this.events[eventName] = [fn]
}
}
}
3. 觀察者模式
性能優(yōu)化
1. webpack 構(gòu)建優(yōu)化
首先造成 webpack 構(gòu)建速度慢的因素就是重復(fù)的編譯、代碼壓縮
方案
- 緩存
- 大部分 loader 提供了緩存選項(xiàng)
- 或者使用
cache-loader;需要定義在所有 loader 的最前面
- js 壓縮
- 開(kāi)啟緩存
- 開(kāi)啟 parallel(并行編譯)
-
happypack多核編譯 (多線(xiàn)程)-
MiniCssExtractPlugin無(wú)法與happypack共存 -
MiniCssExtractPlugin必須置于cache-loader執(zhí)行之后,否則無(wú)法生效
-
- 通過(guò)
DllPlugin抽離靜態(tài)依賴(lài)包,避免反復(fù)編譯,比如 lodash 等,或者通過(guò)externalsCDN 引入。 Tree shaking-
Scope hoisting構(gòu)建后的代碼會(huì)存在大量閉包,造成體積增大,運(yùn)行代碼時(shí)創(chuàng)建的函數(shù)作用域變多,內(nèi)存開(kāi)銷(xiāo)變大。Scope hoisting 將所有模塊的代碼按照引用順序放在一個(gè)函數(shù)作用域里,然后適當(dāng)?shù)闹孛恍┳兞恳苑乐棺兞棵麤_突
提升構(gòu)建體驗(yàn)
-
progress-bar-webpack-plugin構(gòu)建進(jìn)度提示 -
webpack-build-notifier構(gòu)建完成后提示,還有提示音 -
webpack-dashboard構(gòu)建界面
2. 資源優(yōu)化
圖片
- 減小 favicon.ico 的體積 設(shè)置強(qiáng)緩存,過(guò)期時(shí)間設(shè)置幾個(gè)月
- 壓縮圖片
- webpack
- 在線(xiàn)壓縮工具
- 雪碧圖
- 橫向排列會(huì)更小
- 禁止在 HTML 中縮放圖片
- PNG logo
- WebP 格式 注意降級(jí)處理
- 多后綴方式兼容
- 指定 Accept 頭支持 WebP 格式
- 小圖標(biāo) base64 直接嵌入 HTML 文檔
- 懶加載
- 圖片漸進(jìn)顯示
CSS
- css 放在頂部
- 使用 link 而不是@import 加載樣式
- css 通過(guò)文件的方式引入,不要寫(xiě)在 html 文檔,以減小文檔的大小,此外 css 文件可以被緩存
- 壓縮 css
- 硬件加速
- transform: translateZ(0); // 僅開(kāi)啟硬件加速
- transform: translate3d(0, 0, 0); // 3d 變換開(kāi)啟硬件加速
- perspective: 1000
注意:使用 3D 硬件加速提升動(dòng)畫(huà)性能時(shí),最好給元素增加一個(gè) z-index 屬性,人為干擾復(fù)合層的排序,可以有效減少 chrome 創(chuàng)建不必要的復(fù)合層,提升渲染性能,移動(dòng)端優(yōu)化效果尤為明顯。
硬件加速最好只用在 animation 或者 transform 上。不要濫用硬件加速,因?yàn)檫@樣會(huì)增加性能的消耗(內(nèi)存的使用),如果濫用反而會(huì)使動(dòng)畫(huà)變得更加卡,就得不償失了。
JavaScript
- 腳本放在底部
- js 和 css 通過(guò)文件的方式引入,不要寫(xiě)在 html 中,原因:
- 減小了 HTML 文檔的大小
- js 和 css 會(huì)被緩存
- 壓縮 js
- 刪除重復(fù)的腳本
- 減少 DOM 的訪問(wèn)
- 緩存頻繁訪問(wèn)的 DOM
- 在 DOM 樹(shù)外更新節(jié)點(diǎn),然后添加到 DOM 樹(shù),比如 DocumentFragment
- 動(dòng)畫(huà)能用 CSS 實(shí)現(xiàn)的不用 js 實(shí)現(xiàn)
- 防抖/節(jié)流
- 減少重繪重排
3. cookie 優(yōu)化
- 消除不必要的 cookie
- 盡可能減小 cookie 的大小
- 注意設(shè)置 cookie 到合適的域名級(jí)別,則其它子域名不會(huì)被影響
- 正確設(shè)置 Expires 日期
- 靜態(tài)資源請(qǐng)求沒(méi)有 cookie,比如將靜態(tài)資源放在全新的域下
4. HTTP 優(yōu)化
- 最小化請(qǐng)求數(shù)
- 預(yù)加載資源
- 緩存策略
5. performance API 的使用
window.performance
const timingInfo = window.performance.timing
// TCP連接耗時(shí)
timingInfo.connectEnd - timingInfo.connectStart
// DNS查詢(xún)耗時(shí)
timingInfo.domainLookupEnd - timingInfo.domainLookupStart
// 獲得首字節(jié)耗費(fèi)時(shí)間,也叫TTFB
timingInfo.responseStart - timingInfo.navigationStart
// domReady時(shí)間(與前面提到的DomContentLoad事件對(duì)應(yīng))
timingInfo.domContentLoadedEventStart - timingInfo.navigationStart
// DOM資源下載
timingInfo.responseEnd - timingInfo.responseStart
web 安全
XSS(跨站腳本攻擊)
跨站腳本攻擊(XSS):為什么 Cookie 中有 HttpOnly 屬性?
CSRF(跨站偽造攻擊)
錯(cuò)誤監(jiān)控及上報(bào)
一、類(lèi)型及解決方式
1. 運(yùn)行時(shí)錯(cuò)誤
- try...catch
- window.onerror
- window.addEventListener('error')
2. 資源加載錯(cuò)誤(圖片)
- img.onerror
3. script Error(跨域代碼)
原因:
跨域訪問(wèn)的 js 內(nèi)部報(bào)錯(cuò),瀏覽器處于安全考慮,不會(huì)報(bào)告具體的錯(cuò)誤堆棧和行號(hào),只拋出 script error 錯(cuò)誤
解決:
- script 添加 crossorigin
- 服務(wù)端設(shè)置 Access-Control-allow-origin 為 * 或 訪問(wèn)域
二、錯(cuò)誤上報(bào)
- ajax
- image 的 src((new Image()).src = '錯(cuò)誤上報(bào)的請(qǐng)求地址') 使用圖片發(fā)送 get 請(qǐng)求,上報(bào)信息,由于瀏覽器對(duì)圖片有緩存,同樣的請(qǐng)求,圖片只會(huì)發(fā)送一次,避免重復(fù)上報(bào)
Webpack
loader 和 plugin 區(qū)別及原理
Git
git pull 和 git fetch 的區(qū)別?
- fetch:相當(dāng)于是從遠(yuǎn)程獲取最新版本到本地,不會(huì)自動(dòng) merge
- git pull:相當(dāng)于是從遠(yuǎn)程獲取最新版本并 merge 到本地
2. git rebase
場(chǎng)景題
1. 音樂(lè)播放器
2. 虛擬列表
3. 數(shù)據(jù)預(yù)加載
4. 幾十萬(wàn)條數(shù)據(jù)渲染
<ul id="list"></ul>
// 總數(shù)據(jù)
const total = 100000
// 每幀渲染的數(shù)量
const once = 20
// 總共渲染的次數(shù)
const count = Math.ceil(total / once)
// 父容器
const ul = document.querySelector('#list')
let loopTime = 1
function render() {
const fragment = document.createDocumentFragment()
for (let i = 0; i < once; i++) {
const li = document.createElement('li')
li.innerText = Math.random() * total
fragment.appendChild(li)
}
ul.appendChild(fragment)
loop()
}
function loop() {
if (loopTime++ < count) {
window.requestAnimationFrame(render)
}
}
loop()
未完待續(xù)...