layout: "../layouts/BlogPost.astro" title: "Frontend interview questions" slug: frontend-interview description: "" added: "" top: true order: 5
更全面的准备可以参考:
由于 http2 支持并发处理,如果后端接口设计基于这个假设,可能不会提供批量获取数据的接口,需要前端通过 id 来逐个获取。但当同时发送上千个请求时,浏览器会变的明显卡顿,虽然这样发送可以更快的获取数据,但会带来不好的用户体验,所以一个解决方案是给并发添加最大数量限制。如果是 http1.1,浏览器会有默认的并发限制,并不需要我们处理这个问题,比如 Chrome 中并发数量是 6 个,所以这个问题的成立是建立在 http2 的基础上。
const concurrencyRequest = (urls, maxNum) => {
return new Promise((resolve) => {
if (urls.length === 0) {
resolve([]);
return;
}
const results = [];
let index = 0; // 下一个请求的下标
let count = 0; // 当前请求完成的数量
async function request() {
if (index === urls.length) return;
const i = index; // 保存序号,使得 result 和 url 对应
const url = urls[index];
index++;
try {
const resp = await fetch(url);
results[i] = resp;
} catch (err) {
results[i] = err;
} finally {
count++;
// 判断是否所有的请求都已完成
if (count === urls.length) {
resolve(results);
}
request();
}
}
const times = Math.min(maxNum, urls.length);
for (let i = 0; i < times; i++) {
request();
}
})
}
// test
const urls = [];
for (let i = 1; i <= 20; i++) {
urls.push(`https://jsonplaceholder.typicode.com/todos/${i}`);
}
concurrencyRequest(urls, 3).then(res => {
console.log(res);
})
Promise.all
and Promise.resolve
by yourself.
``js
Promise._all = function (promises) {
return new Promise((resolve, reject) => {
let counter = 0;
const result = [];
for (let i = 0; i < promises.length; i++) {
// Use
Promise.resolve(promises[i])instead of
promises[i].then(),
// because
promises[i]could be a non-promise so it won’t have
.then()` method
Promise.resolve(promises[i]).then(res => { result[i] = res;
counter += 1;
if (counter === promises.length) {
resolve(result);
}
}, err => { reject(err);
});
}
});
};Promise._resolve = function (value) { if (value instanceof Promise) { return value; } else { return new Promise((resolve, reject) => { resolve(value); }); } };
3. Convert a list of objects into a tree.
```js
let list = [
{ id: 1, name: 'node1', pid: 0 },
{ id: 2, name: 'node2', pid: 1 },
{ id: 3, name: 'node3', pid: 1 },
{ id: 4, name: 'node4', pid: 3 },
{ id: 5, name: 'node5', pid: 4 },
{ id: 6, name: 'node6', pid: 0 },
]
function listToTree(list) {
const map = {}
const roots = []
list.forEach(item => {
map[item.id] = { ...item, children: [] }
})
list.forEach(item => {
if (item.pid === 0) {
roots.push(map[item.id])
} else {
if (map[item.pid]) {
map[item.pid].children.push(map[item.id])
}
}
})
return roots
}
Implement debounce
and throttle
.
```js
function debounce(fn, time) {
let timer = null
return (...args) => { if (timer) { clearTimeout(timer) timer = null } timer = setTimeout(() => { fn(...args) }, time) } }
function throttle(fn, delay) { let currentTime = Date.now()
return (...args) => { if (Date.now() - currentTime > delay) { fn(...args) currentTime = Date.now() } } }
5. Implement calling click event listener only once without using `{once: true}`.
```js
function clickOnce(el, cb) {
const cb2 = () => {
cb();
el.removeEventListener('click', cb2, false);
}
el.addEventListener('click', cb2, false);
}
clickOnce($0, () => console.log('click'));
bind
function by yourself.Function.prototype.myBind = function(context, ...args1) {
const fn = this;
return function(...args2) {
return fn.apply(context, [...args1, ...args2]);
};
};
Implement the deep clone method.
function deepClone(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
let copy = Array.isArray(obj) ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = typeof obj[key] === 'object' ? deepClone(obj[key]) : obj[key];
}
}
return copy;
}
Use setTimeout
to invoke a function multiple times in the fixed interval.
```js
function repeat(func, times, ms, immediate) {
let count = 0;
return function inner(...args) { if (count === 0 && immediate) { func(...args); count++; } if (count >= times) { return; } setTimeout(() => { func(...args); count++; inner(...args); }, ms); } }
// test const repeatFunc = repeat(console.log, 4, 3000, true); repeatFunc("hello");
9. Implement the render function to convert the virtual dom JSON to real DOM.
```js
function render(vnode) {
const { tag, props, children } = vnode;
const el = document.createElement(tag);
if (props) {
for (const key in props) {
const value = props[key];
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), value);
} else {
el.setAttribute(key, value);
}
}
}
if (children) {
if (typeof children === "string") {
el.textContent = children;
} else {
children.forEach((item) => {
el.appendChild(render(item));
});
}
}
return el;
}
/**
* Parse a string of HTML or XML to a DOM tree.
* @param {string} [html] String containing HTML or XML to be parsed.
* @returns {DocumentFragment} Object containing Node structure of HTML/XML parsed.
*/
const parse = (html) => {
const element = document.createElement('template');
if (html !== undefined) {
element.innerHTML = html;
}
return element.content;
};
parse('Some <em>awesome</em> markup <img src="explosion.gif">');
// returns DocumentFragment(4) [ #text, em, #text, img ]
Implement the functionality of lodash.get
.
```js
// _.get(object, path, [defaultValue])
function get(obj, path, defaultValue = undefined) {
const keys = Array.isArray(path) ? path : path.split('.');
let result = obj;
for (const key of keys) { if (result == null || typeof result !== 'object') { return defaultValue; } result = result[key]; }
return result === undefined ? defaultValue : result; }
// test const obj = { a: { b: { c: 42 } } }; console.log(get(obj, 'a.b.c')); // 42 console.log(get(obj, ['a', 'b', 'c'])); // 42 console.log(get(obj, 'a.b.d', 'default')); // 'default' console.log(get(obj, 'x.y.z', 'not found')); // 'not found'
11. How to add two big integers in js?
```js
function add(A, B) {
const AL = A.length
const BL = B.length
const ML = Math.max(AL, BL)
let carry = 0, sum = ''
for (let i = 1; i <= ML; i++) {
let a = +A.charAt(AL - i)
let b = +B.charAt(BL - i)
let t = carry + a + b
carry = Math.floor(t / 10)
t %= 10
sum = (i === ML && carry)
? (carry * 10 + t) + sum
: t + sum
}
return sum
}
app.use
is used to register middleware functions, and app.compose
is meant to run them in sequence.const app = { middlewares: [] };
app.use = (fn) => {
app.middlewares.push(fn);
};
app.compose = function() {
// Your code goes here
}
app.use(next => {
console.log(1);
next();
console.log(2);
});
app.use(next => {
console.log(3);
next();
console.log(4);
});
app.use(next => {
console.log(5);
next();
console.log(6);
});
app.compose(); // Logs: 1, 3, 5, 6, 4, 2
const compose = (middlewares) => {
return () => {
const dispatch = (i) => {
const fn = middlewares[i];
if (!fn) return;
fn(() => dispatch(i + 1));
};
dispatch(0);
};
};
app.compose = compose(app.middlewares);
renderToString
does.// written by Dan Abramov
// {
// $$typeof: Symbol("react.element"),
// type: "div",
// props: { children: "Hello" },
// }
async function renderJSXToHTML(jsx) {
if (typeof jsx === "string" || typeof jsx === "number") {
// This is a string. Escape it and put it into HTML directly.
return escapeHtml(jsx);
} else if (jsx == null || typeof jsx === "boolean") {
// This is an empty node. Don't emit anything in HTML for it.
return "";
} else if (Array.isArray(jsx)) {
const childHtmls = await Promise.all(
jsx.map((child) => renderJSXToHTML(child))
);
return childHtmls.join("");
} else if (typeof jsx === "object") {
// Check if this object is a React JSX element.
if (jsx.$$typeof === Symbol.for("react.element")) {
if (typeof jsx.type === "string") {
let html = "<" + jsx.type;
for (const propName in jsx.props) {
if (jsx.props.hasOwnProperty(propName) && propName !== "children") {
html += " ";
html += propName;
html += "=";
html += escapeHtml(jsx.props[propName]);
}
}
html += ">";
html += await renderJSXToHTML(jsx.props.children);
html += "</" + jsx.type + ">";
return html;
} else if (typeof jsx.type === "function") {
// Call the component with its props, and turn its returned JSX into HTML.
const Component = jsx.type;
const props = jsx.props;
const returnedJsx = await Component(props);
return renderJSXToHTML(returnedJsx);
}
}
};
}
let activeEffect = null;
function reactive(target) {
const depsMap = new Map();
const handler = {
get(target, key, receiver) {
if (!depsMap.has(key)) {
depsMap.set(key, new Set());
}
const dep = depsMap.get(key);
track(dep);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
if (depsMap.has(key)) {
const dep = depsMap.get(key);
trigger(dep);
}
return result;
}
};
return new Proxy(target, handler);
}
function track(dep) {
if (activeEffect) {
dep.add(activeEffect);
}
}
function trigger(dep) {
dep.forEach(effect => effect());
}
// `JSON.stringify` throws if one attempts to encode an object with circular references.
function hasCircularReference(obj) {
try {
JSON.stringify(obj);
return false;
} catch (e) {
return true;
}
}
// use `WeakSet`
// 1. don’t need to worry about cleaning up the references manually.
// 2. O(1) time complexity.
// 3. specifically designed to store objects.
function hasCircularReference(obj) {
const seenObjects = new WeakSet();
function detect(obj) {
if (obj && typeof obj === 'object') {
if (seenObjects.has(obj)) {
return true;
}
seenObjects.add(obj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (detect(obj[key])) {
return true;
}
}
}
}
return false;
}
return detect(obj);
}
sseStreamIterator
, which can be used in for await (const event of sseStreamIterator(apiUrl, requestBody))
.// https://gist.github.com/simonw/209b46563b520d1681a128c11dd117bc
async function* sseStreamIterator(apiUrl, requestBody) {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const reader = response.body.getReader();
// `TextDecoder` is needed to convert the binary data (Uint8Array) into string.
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
// `stream: true` keeps that partial character in an internal buffer, not incorrectly decoded.
buffer += decoder.decode(value, { stream: true });
const events = buffer.split(/\r\n\r\n/);
// the last element which might be an incomplete event is removed
// Partial data is stored in the buffer and completed with data from the next chunk.
buffer = events.pop() || '';
for (const event of events) {
// could include multiple fields per event like id:, event:, in addition to the data: field.
const lines = event.split(/\r\n/);
const parsedEvent = {};
for (const line of lines) {
if (line.startsWith('data: ')) {
const dataContent = line.slice(6);
try {
parsedEvent.data = JSON.parse(dataContent);
} catch (error) {
parsedEvent.data = null;
parsedEvent.data_raw = dataContent;
}
} else if (line.includes(': ')) {
const [key, value] = line.split(': ', 2);
parsedEvent[key] = value;
}
}
if (Object.keys(parsedEvent).length > 0) {
yield parsedEvent;
}
}
}
}