bytedance¶
- Great Frontend – Bytedance Guide
- Great Frontend – JavaScript Playbook
- BigFrontend – Company
- Frontend Interview Handbook – Bytedance/TikTok Questions
- BigFrontend – Company 1
- Great Frontend – TikTok Guide
- Great Frontend – User Interface
Debounce¶
每次触发事件时,重置定时器,推迟函数执行,直到事件停止触发超过 wait 时间。
export default function debounce(func, wait = 0) {
let timeoutID = null;
return function (...args) {
// Keep a reference to `this` so that
// func.apply() can access it.
const context = this;
clearTimeout(timeoutID);
timeoutID = setTimeout(function () {
timeoutID = null; // Not strictly necessary but good to do this.
func.apply(context, args);
}, wait);
};
}
Throttle¶
在 wait 时间内,函数最多执行一次;如果事件持续触发,函数会在每个 wait 时间窗口内执行一次(通常是窗口开始时)。
/**
* @callback func
* @param {number} wait
* @return {Function}
*/
export default function throttle(func, wait = 0) {
let shouldThrottle = false;
return function (...args) {
if (shouldThrottle) {
return;
}
shouldThrottle = true;
setTimeout(function () {
shouldThrottle = false;
}, wait);
func.apply(this, args);
};
}
map¶
/**
* @template T, U
* @param { (value: T, index: number, array: Array<T>) => U } callbackFn
* @param {any} [thisArg]
* @return {Array<U>}
*/
Array.prototype.myMap = function (callbackFn, thisArg) {
const len = this.length;
const array = new Array(len);
for (let k = 0; k < len; k++) {
// Ignore index if value is not defined for index (e.g. in sparse arrays).
if (Object.hasOwn(this, k)) {
array[k] = callbackFn.call(thisArg, this[k], k, this);
}
}
return array;
};
filter¶
/**
* @template T
* @param { (value: T, index: number, array: Array<T>) => boolean } callbackFn
* @param {any} [thisArg]
* @return {Array<T>}
*/
Array.prototype.myFilter = function (callbackFn, thisArg) {
const len = this.length;
const results = [];
for (let k = 0; k < len; k++) {
const kValue = this[k];
if (
// Ignore index if value is not defined for index (e.g. in sparse arrays).
Object.hasOwn(this, k) &&
callbackFn.call(thisArg, kValue, k, this)
) {
results.push(kValue);
}
}
return results;
};
Reduce¶
/**
* @template T, U
* @param {(previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U} callbackFn
* @param {U} [initialValue]
* @return {Array<U>}
*/
Array.prototype.myReduce = function (callbackFn, initialValue) {
const noInitialValue = initialValue === undefined;
const len = this.length;
if (noInitialValue && len === 0) {
throw new TypeError("Reduce of empty array with no initial value");
}
let acc = noInitialValue ? this[0] : initialValue;
let startingIndex = noInitialValue ? 1 : 0;
for (let k = startingIndex; k < len; k++) {
if (Object.hasOwn(this, k)) {
acc = callbackFn(acc, this[k], k, this);
}
}
return acc;
};
deep clone¶
/**
* @template T
* @param {T} value
* @return {T}
*/
export default function deepClone(value) {
if (typeof value !== "object" || value === null) {
return value;
}
if (Array.isArray(value)) {
return value.map((item) => deepClone(item));
}
return Object.fromEntries(
Object.entries(value).map(([key, value]) => [key, deepClone(value)])
);
}
bind¶
/**
* @param {any} thisArg
* @param {...*} argArray
* @return {Function}
*/
Function.prototype.myBind = function (thisArg, ...argArray) {
const originalMethod = this;
return function (...args) {
return originalMethod.apply(thisArg, [...argArray, ...args]);
};
};
Flatten¶
type ArrayValue = any | Array<ArrayValue>;
export default function flatten(value: Array<ArrayValue>): Array<any> {
const res = [];
const copy = value.slice();
while (copy.length) {
const item = copy.shift();
if (Array.isArray(item)) {
copy.unshift(...item);
} else {
res.push(item);
}
}
return res;
}
Promise.all¶
/**
* @param {Array} iterable
* @return {Promise<Array>}
*/
export default function promiseAll(iterable) {
return new Promise((resolve, reject) => {
const results = new Array(iterable.length);
let unresolved = iterable.length;
if (unresolved === 0) {
resolve(results);
return;
}
iterable.forEach(async (item, index) => {
try {
const value = await item;
results[index] = value;
unresolved -= 1;
if (unresolved === 0) {
resolve(results);
}
} catch (err) {
reject(err);
}
});
});
}
Promise.race¶
/**
* @param {Array} iterable
* @return {Promise}
*/
export default function promiseRace(iterable) {
return new Promise((resolve, reject) => {
if (iterable.length === 0) {
return;
}
iterable.forEach(async (item) => {
try {
const result = await item;
resolve(result);
} catch (err) {
reject(err);
}
});
});
}
UseRef¶
React 会将 ref 对象的 current 属性设置为该 DOM 节点
UseEffect vs UseLayoutEffect¶
- useEffect 是 async 的, 不会 delay browser 的 paint(performance 的角度, 所以用 UseEffect 比较多)
- UseLayoutEffect 是 sync 的, 会 delay paint(before directly modify dom == paint?), 等执行完才会 paint, 所以有延迟
Next.js 水合问题¶
浏览器 API 相关的¶
- API 仅在 client 端存在
不确定的值¶
- Date 或者 random
响应式布局相关的¶
- window.innerWidth
custom hook 例子分析¶
- 将可复用的逻辑抽象成一个 Hook
- 返回状态相关的逻辑, 不渲染 ui
React Fiber¶
- 并发模式
低优先级任务正在进行:假设 React 正在进行一个耗时的渲染工作,比如根据网络请求返回的大量数据渲染一个长列表。这个任务的优先级很低。React 正在不紧不慢地处理一个个 Fiber 工作单元。
用户点击事件发生:用户突然点击了一个按钮,触发了一个setState。
React调度高优先级更新:React识别到这次setState是由用户交互(如onClick)触发的,于是它将这个更新标记为高优先级。
中断与切换:React的调度器(Scheduler)会立刻暂停正在进行的低优先级渲染任务(长列表的渲染被“冻结”了)。
执行高优先级任务:然后,React立即开始为这次高优先级的点击事件执行**Reconciliation(比较虚拟DOM)和Commit(渲染到真实DOM)**的过程。因为这个更新通常很小(比如只是切换一个按钮的状态),所以这个过程飞快地完成了。
用户感知:用户几乎在点击的瞬间就看到了UI的变化,感觉应用非常“跟手”。
恢复低优先级任务:在高优先级任务完成后,React会回到之前被暂停的地方,继续它未完成的低优先级渲染工作。
```Fiber将大的更新任务拆解成无数个小块。它在每完成一小块工作后,都会检查有没有更紧急的任务(比如用户的点击)。
如果用户有操作,React会立即暂停当前的渲染工作,去响应用户的操作,让UI立刻给出反馈。
然后再在浏览器空闲时,回来继续之前没完成的渲染任务。
在用户看来,应用始终是流畅的、可交互的。即使后台在进行复杂的渲染,也不会影响他们当前的操作。这就是更好的用户体验和更高的感知性能。
## Nextjs
- page, layout, loading, error, not-found, route, template, middleware, default
## Suspense
## performance
- React:用 `React.lazy` + `Suspense` 实现组件懒加载和代码切分。
- Next.js:页面自动切分,组件用 `next/dynamic` 实现懒加载。
##
```