From cc32245c344c028b32e51090df2ee22e3deedd1f Mon Sep 17 00:00:00 2001 From: "pengpeng.chen" Date: Tue, 12 Mar 2024 22:56:34 +0800 Subject: [PATCH] feat: add 0312 --- docs/interview/experiences/0index.md | 4 +- .../experiences/14202308handwriting.md | 5 + .../experiences/15202309handwriting.md | 39 +- docs/interview/experiences/22leetcodeB.md | 42 +++ .../experiences/26handwritingHooks.md | 157 ++++++++ docs/interview/experiences/27handwritingTs.md | 23 +- docs/interview/experiences/34.acc20240311.md | 148 ++++++++ .../experiences/practise/202403/0310.js | 33 ++ .../experiences/practise/202403/0311.js | 31 ++ .../experiences/practise/202403/0312.js | 339 ++++++++++++++++++ .../experiences/practise/202403/0313.js | 47 +++ .../experiences/practise/202403/0314.js | 4 + docs/interview/index.md | 16 +- docs/interview/instance/redpacket.html | 151 ++++++++ docs/interview/js/17console.md | 37 ++ docs/node/index.md | 8 + src/Hooks/index.d.ts | 6 - src/Hooks/index.js | 7 - src/Hooks/index.tsx | 2 + src/Hooks/useRerender/index.d.ts | 2 - src/Hooks/useRerender/index.js | 18 - src/Hooks/useRerender/index.md | 2 +- src/Hooks/useRerender/useRerender.d.ts | 2 - src/Hooks/useRerender/useRerender.js | 9 - src/Hooks/useToggle/index.md | 4 +- src/Hooks/useWindowSize/index.d.ts | 2 - 26 files changed, 1078 insertions(+), 60 deletions(-) create mode 100644 docs/interview/experiences/34.acc20240311.md create mode 100644 docs/interview/experiences/practise/202403/0312.js create mode 100644 docs/interview/experiences/practise/202403/0313.js create mode 100644 docs/interview/experiences/practise/202403/0314.js create mode 100644 docs/interview/instance/redpacket.html delete mode 100644 src/Hooks/index.d.ts delete mode 100644 src/Hooks/index.js delete mode 100644 src/Hooks/useRerender/index.d.ts delete mode 100644 src/Hooks/useRerender/index.js delete mode 100644 src/Hooks/useRerender/useRerender.d.ts delete mode 100644 src/Hooks/useRerender/useRerender.js delete mode 100644 src/Hooks/useWindowSize/index.d.ts diff --git a/docs/interview/experiences/0index.md b/docs/interview/experiences/0index.md index 885c7a4..81869cd 100644 --- a/docs/interview/experiences/0index.md +++ b/docs/interview/experiences/0index.md @@ -20,7 +20,7 @@ nav: ### 如何防御 XSS 攻击 - 输入的内容进行过滤或者转译 -- csp,配置安全内容策略白名单,告诉浏览器哪个域名下自由受信任 +- csp,配置安全内容策略白名单,告诉浏览器**哪个域名**下自由受信任 ### 简单说明 JS 运行机制 @@ -41,7 +41,7 @@ sessionStorage 属性允许你访问一个,对应当前源的 session `Storage 对面试官:多窗口之间 sessionStorage 不可以共享状态!!!但是在某些特定场景下新开的页面会复制之前页面的 sessionStorage!!也就是在**新标签或窗口**打开一个页面时会复制**顶级浏览会话的上下文作为新会话的上下文** -### preload 跟 prefetch 区别,都应用在什么场景下 +### **preload** 跟 **prefetch** 区别,都应用在什么场景下 ```js diff --git a/docs/interview/experiences/14202308handwriting.md b/docs/interview/experiences/14202308handwriting.md index 84f48fc..4fd8f48 100644 --- a/docs/interview/experiences/14202308handwriting.md +++ b/docs/interview/experiences/14202308handwriting.md @@ -390,6 +390,11 @@ function lazyLoadImg(imgs) { const { target, intersectionRatio } = entry; if (intersectionRatio > 0) { target.src = target.dataset.src; + target.onerror = function (e) { + if (e.type === 'error') { + target.src = placeHoloder; + } + }; observer.unobserve(target); } }); diff --git a/docs/interview/experiences/15202309handwriting.md b/docs/interview/experiences/15202309handwriting.md index b92ab28..e917a0b 100644 --- a/docs/interview/experiences/15202309handwriting.md +++ b/docs/interview/experiences/15202309handwriting.md @@ -853,7 +853,7 @@ isValid('

laoyao bye bye

'); // 示例用法 const array = [1, 2, 3, 4, 5]; // 4 5 1 2 3 const k = 2; -const rotatedArray = rotateArray(inputArray, k); +const rotatedArray = rotate(array, k); console.log(rotatedArray); // 输出: [4, 5, 1, 2, 3] // first 使用额外的数组 @@ -1083,6 +1083,8 @@ function shallow(obj1, obj2) { } return true; } +// shallow({}, {}); +shallow(NaN, NaN); ``` ## 28.获取设备电池信息(**navigator.getBattery**) @@ -1255,7 +1257,7 @@ function factorial(n) { factorial(4); ``` -## 34.FileReader 使用 +## 34.FileReader 读写文件 ```js function uploadFile(file) { @@ -1347,7 +1349,7 @@ trans(123456); ## 37.两个字符串对比, 得出结论都做了什么操作, 比如插入或者删除? -> form leetcode [编辑距离](https://leetcode.cn/problems/edit-distance/description/) +> from leetcode [编辑距离](https://leetcode.cn/problems/edit-distance/description/) ```js pre = 'abcde123'; @@ -1749,6 +1751,37 @@ var compareVersion = function (v1, v2) { ## 49.【代码题】实现一个拼手气抢红包算法 +```js +/** + * @param {number } money 总共发多少红包 + * @param {number } count 红包个数 + * @param {number } minBase 红包最少发多少 + * @param {number } maxBase 红包最多不能超过平均的几倍 + */ +function redPacket({ money, count, minBase = 0.1, maxBase = 2 }) { + let remainMoney = money; + let ans = []; + for (let i = 0; i < count - 1; i++) { + let max = (remainMoney / count) * maxBase; // 最多不能超过平均的2倍 + let curMon = Math.random() * max; + curMon = curMon < minBase ? minBase : curMon; + curMon = Math.floor(curMon * 100) / 100; + remainMoney = Math.round((remainMoney - curMon) * 100) / 100; + ans.push(curMon); + } + // 最后一个剩余红包 + ans.push(remainMoney); + console.log( + ans, + ans.reduce((a, b) => Number(a) + Number(b)), + ); +} +redPacket({ + money: 20, + count: 4, +}); //  [8.67, 3.73, 1.42, 6.18] 20 +``` + ## 50. 数字转 36 进制 ```js diff --git a/docs/interview/experiences/22leetcodeB.md b/docs/interview/experiences/22leetcodeB.md index 4036e64..d715322 100644 --- a/docs/interview/experiences/22leetcodeB.md +++ b/docs/interview/experiences/22leetcodeB.md @@ -269,7 +269,49 @@ houseRobber([1, 2, 3, 1]); ## [8.零钱兑换 I/II](https://leetcode.cn/problems/coin-change/) ```js +/** +给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 +计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 +你可以认为每种硬币的数量是无限的。 +示例 1: +输入:coins = [1, 2, 5], amount = 11 +输出:3 +解释:11 = 5 + 5 + 1 + */ +var coinChange = function (coins, amount) { + let dp = new Array(amount + 1).fill(Infinity); + dp[0] = 0; + for (let coin of coins) { + for (let j = coin; j <= amount; j++) { + dp[j] = Math.min(dp[j], dp[j - coin] + 1); + } + } + return dp[amount] == Infinity ? '-1' : dp[amount]; +}; +``` +零钱兑换 II + +```js +/** +输入:amount = 5, coins = [1, 2, 5] +输出:4 +解释:有四种方式可以凑成总金额: +5=5 +5=2+2+1 +5=2+1+1+1 +5=1+1+1+1+1 + */ +var change = function (amount, coins) { + let dp = new Array(amount + 1).fill(0); + dp[0] = 1; + for (let coin of coins) { + for (let j = coin; j <= amount; j++) { + dp[j] += dp[j - coin]; + } + } + return dp[amount]; +}; ``` ## 9.[剑指 Offer 18. 删除链表的节点](https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/) diff --git a/docs/interview/experiences/26handwritingHooks.md b/docs/interview/experiences/26handwritingHooks.md index 8ceb376..0e2185f 100644 --- a/docs/interview/experiences/26handwritingHooks.md +++ b/docs/interview/experiences/26handwritingHooks.md @@ -20,6 +20,8 @@ nav: ## 1.useUpdate +有的时候我们需要组件强制更新,这个时候就可以使用这个钩子: + ```js import { useCallback, useState } from 'react'; @@ -44,3 +46,158 @@ const useLatest = (value) => { ``` ## 3.useCreation + +useCreation :是 useMemo 或 useRef 的替代品。换言之,useCreation 这个钩子增强了 useMemo 和 useRef,让这个钩子可以替换这两个钩子。(来自 ahooks-useCreation) + +- useMemo 的值不一定是最新的值,但 useCreation 可以保证拿到的值一定是最新的值 +- 对于复杂常量的创建,useRef 容易出现潜在的的性能隐患,但 useCreation 可以避免 + +```ts +import { useRef } from 'react'; +import type { DependencyList } from 'react'; + +const depsAreSame = (oldDeps: DependencyList, deps: DependencyList): boolean => { + if (oldDeps === deps) return true; + for (let i = 0; i < oldDeps.length; i++) { + // 判断两个值是否是同一个值 + if (!Object.is(oldDeps[i], deps[i])) return false; + } + return true; +}; + +const useCreation = (fn: () => any, deps: DependencyList) => { + const { current } = useRef({ + deps, + obj: undefined as undefined, + initialized: false, + }); + if (current.initialized === false || !depsAreSame(current.deps, deps)) { + current.deps = deps; + current.obj = fn(); + current.initialized = true; + } + return current.obj; +}; + +export default useCreation; +``` + +## 4.useToggle + +```js +import { useCallback, useState } from 'react'; +const useToggle = (init) => { + const [toggle, setToggle] = useState(init); + const changeToggle = useCallback(() => { + setToggle((val) => !val); + }, []); + return [toggle, changeToggle]; +}; +``` + +## 5.useRequest + +```ts +import { useState, useEffect } from 'react'; +const useRequest = (url: string, options: any) => { + const [abort, setAbort] = useState(() => {}); + const [res, setResponse] = useState(null); + const [error, setError] = useState(null); + useEffect(() => { + const fetchData = async () => { + try { + const abortController = new AbortController(); + const signal = abortController.signal; + setAbort(() => abortController.abort()); + const res = await window.fetch(url, { ...options, signal, method: 'get' }); + setResponse(res); + } catch (error) { + setError(error as any); + } + }; + fetchData(); + return () => { + if (typeof abort === 'function') { + abort(); + } + }; + }, []); + return { + res, + error, + abort, + }; +}; +export default useRequest; +``` + +## 6.useEventListener + +```ts +import { useRef, useEffect } from 'react'; + +function useEventListener(eventName: string, handler: Function, element = window): void { + const saveHandler = useRef(() => {}); + useEffect(() => { + saveHandler.current = handler; + }, [handler]); + useEffect(() => { + const isSupported = element && element.addEventListener; + if (!isSupported) return; + const eventListener = (event: Event) => saveHandler.current(event); + element.addEventListener(eventName, eventListener); + return () => { + element.removeEventListener(eventName, eventListener); + }; + }, [eventName, element]); +} +export default useEventListener; +useEventListener('click', () => {}); +``` + +## 7.useTimeout + +useTimeout:一段时间内,执行一次 + +传递参数只要函数和延迟时间即可,需要注意的是卸载的时候将定时器清除下就 OK 了 + +```ts +import { useEffect } from 'react'; +export const useTimeout = (fn: () => void, delay: number) => { + const fnRef = useLatest(fn); + useEffect(() => { + if (!delay || delay < 0) return; + const timer = setTimeout(() => { + fnRef && fnRef.current(); + }, delay); + return () => { + clearTimeout(timer); + }; + }, []); +}; +``` + +## 8.useInterval + +useInterval: 每过一段时间内一直执行 + +大体上与 useTimeout 一样,多了一个是否要首次渲染的参数 immediate + +```ts +import { useEffect } from 'react'; +export const useInterval = (fn: () => void, delay: number, immediate: boolean) => { + const fnRef = useLatest(fn); + useEffect(() => { + if (!delay || delay < 0) return; + if (immediate) { + fnRef.current(); + } + const timer = setInterval(() => { + fnRef && fnRef.current(); + }, delay); + return () => { + clearInterval(timer); + }; + }, []); +}; +``` diff --git a/docs/interview/experiences/27handwritingTs.md b/docs/interview/experiences/27handwritingTs.md index dd71edf..1293d0d 100644 --- a/docs/interview/experiences/27handwritingTs.md +++ b/docs/interview/experiences/27handwritingTs.md @@ -25,7 +25,7 @@ nav: * 11.TS手写Unique 去重 * 12.OptionalKeys提取 T 中所有可选类型的 key 组成的联合类型。 * 13.NonOptionalKeys提取 T 中非所有可选类型的 key 组成的联合类型。 - * 14. + * 14.如何限制数组索引为非负数 * 15. */ ``` @@ -191,6 +191,16 @@ type Q031 = { age: number; }; type Q041 = OptionalKeys; + +type GetFunction = { + [P in keyof T as T[P] extends Function ? P : never]: T[P]; +}; +type Q0311 = { + name?: string; + age: number; + getName: () => void; +}; +type Q0312 = GetFunction; ``` ## 13.NonOptionalKeys 提取 T 中非所有可选类型的 key 组成的联合类型。 @@ -205,3 +215,14 @@ type Q03 = { }; type Q04 = NonOptionalKeys; ``` + +## 14.如何限制数组索引为非负数 + +```ts +type NotNegative = `${N}` extends `-${number}` ? never : N; +function safeGet(arr: any[], index: NotNegative) { + console.log(arr[index]); +} +// Argument of type 'number' is not assignable to parameter of type 'never'.ts(2345) +safeGet([1, 2, 3, 4], -1); +``` diff --git a/docs/interview/experiences/34.acc20240311.md b/docs/interview/experiences/34.acc20240311.md new file mode 100644 index 0000000..1cfd7b5 --- /dev/null +++ b/docs/interview/experiences/34.acc20240311.md @@ -0,0 +1,148 @@ +--- +title: 20240311积累面试 +order: 34 +group: + order: 0 + title: interview +nav: + order: 3 + title: 'interview' + path: /interview +--- + +- 手写嵌套对象扁平化支持传递参数深度 +- 手写 promise +- 手写图片懒加载,placeholder 如何初始化 +- react 中的 fiber 架构是如何异步更新的 +- react 中的 diff 算法和策略(算法不知道) +- vue 跟 react 区别 +- 虚拟 dom 原理 +- node 如何做负载均衡? +- opacity/display/opacity 区别(忘记区别了) +- xss 和 csrf 跨站脚本攻击和跨站请求伪造 +- 前端性能优化 +- 浏览器缓存,cache-control +- js 事件循环机制 +- url 加载 +- 重绘和重排 +- 项目中有难度的说一说 + +> 自我感觉一般般,第一道题写了一半,那个深度的传参,真想不出来啊,最荒诞的时候 我首次还能写出来。面的内容够杂也够多,基本上能问的都问了个遍,面试官也是厉害,周到,全面,啥都要问问 + +> 除了 node 和 react diff 算法没答出来 其他的都是表面,需要自己多组织语言,多练习,多说 + +## 嵌套对象扁平化支持传递参数深度 + +```js +function flatten(arr, depth) { + let stack = [...arr]; + let ans = []; + let i = 0; + while (stack.length) { + let cur = stack.pop(); + if (Array.isArray(cur) && i < depth) { + i++; + stack.push(...cur); + } else { + ans.push(cur); + } + } + return ans.reverse(); +} +``` + +## 手写 promise + +```js +class Promise { + constructor() {} + then(onFulfull) {} +} +``` + +## 前端在谈性能优化 + +这个题目必考,你在项目里是如何进行性能优化的?如何给人一种不错的印象? + +### js/css + +- 将 CSS 放在文件头部,JavaScript 文件放在底部 +- 合理使用 async/defer 加载和解析脚本 +- 使用 flex 布局 减少使用老的 table 布局 +- 使用 transform 和 opacity 属性更改来实现动画 +- 减少重排重绘 +- 使用 requestAnimationFrame 优化动画,防止页面动画卡顿 +- 时间切片,时间切片是一种将长时间运行的任务分解成多个小任务的技术,从而避免阻塞主线程,提高页面的响应速度和用户体验。时间切片可以使用**requestIdleCallback** API 来实现,该 API 会在浏览器空闲时执行回调函数 +- 使用事件委托,节省内存空间 +- 并发请求 Promise.all,可以使用并发请求来提高页面的加载速度和用户体验 + +### 构建工具 + +- webpack 按需加载, +- tree shaking +- 提取第三方库,比如不怎么变的 react/react-dom +- split code + +### 网络相关 + +- 减少 HTTP 请求 +- 使用 http2 +- cdn +- gzip +- preload/prefetch +- 善用/合理利用浏览器缓存 +- 图片优化,压缩大小,webp,图片懒加载 + +### 框架层面 + +- 引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,只关注业务本身 +- 父组件更新,不波及子组件渲染,没有必要的渲染是对性能的极大浪费。合理使用 react.memo/useMemo/useCallback/shouldComponentUpdate/PureComponent +- 遍历数组的时候,记得加上唯一的标记 key + +### 针对做过的项目性能优化 + +- 组件优化 nextjs 中有个动态引入组件,对于不需要直出的组件阔以用 + +## 再谈 vue 和 react 区别 + +个人使用上: react 推崇函数式编程,主持纯函数,单向数据流,jsx 语法自由灵活 + +### 设计思想/语法/diff 算法/生态 + +### 响应式原理 + +Vue 是对数据进行劫持/代理,它对监测数据的变化更加精准,动了多少数据就触发多少更新,更新粒度很小,而 React 推崇函数式,这是没办法感知数据变化的,就是说不知道什么时候应该刷新,而且即便是手动 setState 触发更新,它也也不知道哪些组件需要刷新,而是渲染整个 DOM,说白了就是无脑刷新,这样就导致性能不好,所以后面只能不断通过其他办法来避免不必要的刷新,或者优化无脑刷新的性能 + +当然 vue 精准刷新也是需要付出代价的,需要给每个组件配置监视器,管理依赖收集和派发更新,有一些性能消耗。 + +### 编译优化 + +Vue 能够做到数据劫持,再到 Vue3 动静结合的 Diff 思想也得益于它的模板语法实现了静态编译。就是能做到预编译优化,可以静态分析,在解析模板时能根据解析到的不同的标签、文本等分别执行对应的回调函数来构造 AST,而 React 虽然 JSX 语法更加灵活,可也正是因为这样导致可以优化的地方不足,重新渲染时就是一堆递归调用 React.createElement,无法从模板层面进行静态分析,也就做不到双向绑定,即使是很厉害的 fiber,也是因为伤害已经造成,所以通过时间分片的优化来弥补伤害吧,因为已经无法在编译阶段进行优化了,这也是这个设计所带来的问题吧 + +### 从加载速度和运行时性能 + +Vue 在更新时性能优化方面需要的**心智负担**可能会少那么一点,特别是 Vue3,而 React 如果不注意,容易导致一些组件无用的 Diff + +### 总结 + +对于中小系统的开发,建议用 vue,因为不管是上手还是开发难度上都很简单,开发效率也高,而且它有更小的打包体积,毕竟在移动端网络差异大的情况下,资源体积是非常重要的。但像是一些中后台系统,或者一些大点的项目,会越做越大的,多人协作开发的,就用 React,因为它的函数式编程有更加灵活的结构和可扩展性,丰富的生态圈和工具链,解决方案多,后期也更方便迭代与维护,还适用原生 APP,所以我会偏向 React。 + +## react 中用到的 diff 算法 + +## react 中的虚拟 dom 原理 + +diff 算法的基础是**Virtual DOM**,Virtual DOM 是一棵以 JavaScript 对象作为基础的树,在 React 中通常是通过 JSX 编译而成的,每一个节点称为 VNode,用对象属性来描述节点,实际上它是一层对真实 DOM 的抽象,最终可以通过渲染操作使这棵树映射到真实环境上,简单来说 Virtual DOM 就是一个**Js 对象,用以描述整个文档**。 + +#### React 中 diff 算法的理解 + +diff 算法用来计算出 Virtual DOM 中改变的部分,然后针对该部分进行 DOM 操作,而不用重新渲染整个页面,渲染整个 DOM 结构的过程中开销是很大的,需要浏览器对 DOM 结构进行重绘与回流,而 diff 算法能够使得操作过程中只更新修改的那部分 DOM 结构而不更新整个 DOM,这样能够最小化操作 DOM 结构,能够最大程度上减少浏览器重绘与回流的规模。 + +### diff 算法 + +React 在内存中维护一颗虚拟 DOM 树,当数据发生改变时(state & props),会自动的更新虚拟 DOM,获得一个新的虚拟 DOM 树,然后通过 Diff 算法,比较新旧虚拟 DOM 树,找出最小的有变化的部分,将这个变化的部分 Patch 加入队列,最终批量的更新这些 Patch 到实际的 DOM 中 + +## react 中的 fiber 架构以及是如何达到异步可中断更新的 + +## node 如何做负载均衡? + +> 这个题我只说到了 process.fork 没有说到 cluster 点子上 diff --git a/docs/interview/experiences/practise/202403/0310.js b/docs/interview/experiences/practise/202403/0310.js index 1dfebba..f034060 100644 --- a/docs/interview/experiences/practise/202403/0310.js +++ b/docs/interview/experiences/practise/202403/0310.js @@ -10,8 +10,41 @@ * 9.10进制转换36进制 * 10.删除链表的倒数第 N 个结点 * 11.重排链表 + * 12.怎么实现图片懒加载的 */ +function longestValid(str) { + let stack = [-1]; + let max = 0; + for (let i = 0; i < str.length; i++) { + if (str[i] == '(') { + stack.push(i); + } else { + stack.pop(); + if (stack.length == 0) { + stack.push(i); + } else { + max = Math.max(max, i - stack.slice(-1)); + } + } + } + return stack.length; +} + +function loadImg(url, option) { + return new Promise((resolve, reject) => { + const img = new Image(option); + img.src = url; + img.onload = function () { + resolve(img); + }; + img.onerror = function () { + reject(e); + }; + }); +} +loadImg('', {}).then((res) => console.log()); + function flattenObj(obj, res = {}, path = '', isArray = false) { for (let [k, val] of Object.entries(obj)) { if (Array.isArray(val)) { diff --git a/docs/interview/experiences/practise/202403/0311.js b/docs/interview/experiences/practise/202403/0311.js index 054bf3d..5a47e3b 100644 --- a/docs/interview/experiences/practise/202403/0311.js +++ b/docs/interview/experiences/practise/202403/0311.js @@ -8,8 +8,39 @@ * 7.重复的子字符串 * 8.跳跃游戏 * 9.k个链表反转 + * 10.阿拉伯数字转成中文 + * 11.36进制 */ +function toNum(str) { + let base = ['零', '一', '二', '三', '四', '五', '六', '七', '八', '九']; + let Gbase = ['', '十', '百', '千']; + let ans = []; + let len = str.length; + for (let i = 0; i < len; i++) { + let num = str[i]; + if (i > 0 && str[i - 1] == '0') ans += base[0]; + ans += base[num] + Gbase[len - i - 1]; + } + if (str.length == 2 && str[0] == '') { + ans = ans.slice(1); + } + return ans; +} +toNum('1234'); // 一千二百三十四 + +function to36(str, base = 24) { + str = +str; + let radix = '0123456789abcdefghijklmnopqrstuvwxwz'; + let ans = ''; + while (str > 0) { + let flag = str % base; + ans = radix[flag] + ans; + str = Math.floor(flag / base); + } + return ans; +} + var deleteNode = function (head, val) { let dummy = { next: head, diff --git a/docs/interview/experiences/practise/202403/0312.js b/docs/interview/experiences/practise/202403/0312.js new file mode 100644 index 0000000..88e0a36 --- /dev/null +++ b/docs/interview/experiences/practise/202403/0312.js @@ -0,0 +1,339 @@ +/** + * 1.bind + * 2.分糖果 + * 3.无重复的最长子串的长度 + * 4.compose/pipe + * 5.两数之和 + * 6.拼红包手机 + * 7.async/await + * 8.并发 + * 9.拼接url参数 和解析url + * 10.render + * 11.jsonp + * 12.ajax + */ +// pipe 从左向右 +function pipe(...rest) { + if (rest.length == 0) return (arg) => arg; + if (rest.length == 1) return rest[0]; + return (...arg) => { + return rest.reduce((a, b) => b(a), arg); + }; +} +// compose 从右向左 +function compose(...rest) { + if (rest.length == 0) return (arg) => arg; + if (rest.length == 1) return rest[0]; + return rest.reduce((a, b) => { + return (...arg) => a(b(...arg)); + }); +} + +function virtualRender(node, parent) { + let mount = parent ? (el) => parent.appendChild(el) : (el) => el; + if (typeof node == 'string') { + return mount(document.createTextNode(node.type)); + } else { + const vdom = mount(document.createElement(node.type)); + // props属性 + Object.keys(node).forEach((key) => { + const val = node[key]; + vdom.setAttribute(key, val); + }); + // child + if (node.children && node.children.length) { + node.children.map((child) => { + document.appendChild(virtualRender(child, parent)); + }); + } + return vdom; + } +} +virtualRender(Vnode, document.getElementById('app')); + +class Promise { + static all(arr) { + return new Promise((resolve, reject) => { + let ans = []; + for (let [key, val] of Object.entries(arr)) { + Promise.resolve(val).then( + (value) => { + if (key == arr.length - 1) { + resolve(ans); + } + ans[key] = value; + }, + (err) => { + reject(err); + }, + ); + } + }); + } + static allSettled(arr) { + return Promise.all( + arr.map((item) => { + Promise.resolve(item).then( + (val) => ({ + value: val, + status: 'fulfilled', + }), + (err) => ({ + reason: err, + status: 'rejected', + }), + ); + }), + ); + } +} + +async function limitQuest(arr, limit, fn) { + let ans = []; + let queue = []; + for (let item of arr) { + let p1 = Promise.resolve().then(() => fn(item)); + ans.push(p1); + if (arr.length >= limit) { + let p2 = p1.then(() => queue.splice(queue.indexOf(p2), 1)); + queue.push(p2); + if (queue.length >= limit) { + await Promise.race(queue); + } + } + } + return Promise.all(ans); +} + +function asyncToGenerator(fn) { + return (...rest) => { + const genFn = fn.apply(this, rest); + return new Promise((resolve, reject) => { + return step('next'); + function step(key, ...arg) { + let res; + try { + res = genFn[key](...arg); + } catch (e) { + reject(e); + } + const { done, value } = res; + if (done) { + resolve(value); + } else { + return Promise.resolve(value).then( + (val) => { + return step('next', val); + }, + (err) => { + return step('throw', err); + }, + ); + } + } + }); + }; +} + +function flatten(arr, depth) { + let stack = [...arr]; + let i = 1; + let ans = []; + while (stack.length) { + let cur = stack.pop(); + if (Array.isArray(cur) && i < depth) { + i++; + stack.push(...cur); + } else { + ans.push(cur); + } + } + return ans.reverse(); +} +flatten([1, 2, [3, 4, [5, 6]], 5], 2); + +/** + * @param {number } money 总共发多少红包 + * @param {number } count 红包个数 + * @param {number } minBase 红包最少发多少 + * @param {number } maxBase 红包最多不能超过平均的几倍 + */ +function redPacket({ money, count, minBase = 0.1, maxBase = 2 }) { + let remainMoney = money; + let ans = []; + for (let i = 0; i < count - 1; i++) { + let max = (remainMoney / count) * maxBase; // 最多不能超过平均的2倍 + let curMon = Math.random() * max; + curMon = curMon < minBase ? minBase : curMon; + curMon = Math.floor(curMon * 100) / 100; + remainMoney = Math.round((remainMoney - curMon) * 100) / 100; + ans.push(curMon); + } + ans.push(remainMoney); + console.log( + ans, + ans.reduce((a, b) => Number(a) + Number(b)), + ); +} +redPacket({ + money: 20, + count: 4, +}); + +function print(count, money) { + let remain = money; + let ans = []; + for (let i = 0; i < count - 1; i++) { + let min = 0.1; + let max = (remain / count) * 2; + let cur = Math.floor(Math.random() * max * 100) / 100; + cur = cur > min ? min : cur; + ans.push(cur); + remain = Math.round((remain - cur) * 100) / 100; + } + ans.push(remain); +} + +// console.log(ans, 'ans', ans.reduce((a,b) => Number(a)+Number(b))) + +function arrToTree(arr) { + let ans = []; + let dfs = (arr, ans, parentId) => { + for (let item of arr) { + if (item.parentId == parentId) { + let newItem = { + ...item, + children: [], + }; + ans.push(newItem); + dfs(arr, newItem.children, item.id); + } + } + }; + dfs(arr, ans, 0); + return ans; +} +// 括号匹配 +function isvalid(str) { + str = str.split(''); + let m = new Map([ + [']', '['], + ['}', '{'], + [')', '('], + ]); + let ans = []; + for (let item of str) { + let last = ans.slice(-1); + if (last && last == m.get(item)) { + ans.pop(); + } else { + ans.push(item); + } + } + return ans.length == 0; +} +isvalid('()'); + +// [1,2,3,4,5] 8 // 返回下标 +function twoSum(arr, target) { + let m = new Map(); + let len = arr.length; + for (let i = 0; i < len; i++) { + let item = arr[i]; + if (m.has(target - item)) { + return [m.get(target - item), i]; + } else { + m.set(item, i); + } + } + return [-1, -1]; +} +twoSum([1, 2, 3, 4, 5], 8); +function random(arr) { + return arr.sort((a, b) => 0.5 - Math.random()); +} +function random(arr) { + for (let i = arr.length - 1; i >= 0; i--) { + let j = Math.floor(Math.random() * (i + 1)); + swap(arr, i, j); + } + return arr; +} + +function compose(middlewares) { + return (ctx, next) => { + return dispatch(0); + function dispatch(i) { + let fn = middlewares[i]; + if (!fn) return Promise.reject(); + if (i == middlewares.length) fn = next; + try { + return Promise.resolve(fn(ctx, () => dispatch(i + 1))); + } catch (e) { + return Promise.reject(e); + } + } + }; +} + +function mockBind(context) { + let self = this; + let arg1 = Array.prototype.slice.call(arguments, 1); + let bindFn = function () { + let arg2 = Array.prototype.slice.call(arguments); + self.apply(this instanceof self ? this : context, arg2.concat(arg1)); + }; + let BridgeFn = function () {}; + BridgeFn.prototype = this.prototype; + bindFn.prototype = new BridgeFn(); + return bindFn; +} + +function maxWidth(str) { + str = str.split(''); + let max = 0; + let arr = []; + for (let i = 0; i < str.length; i++) { + let cur = str[i]; + const index = arr.indexOf(cur); + if (index > -1) { + arr.splice(0, index + 1); + } + arr.push(cur); + max = Math.max(max, arr.length); + } + return max; +} + +Array.prototype.selectType = function (fn, context) { + let arr = this || []; + let res = {}; + for (let i = 0; i < arr.length; i++) { + let val = fn.call(this, arr[i], i, arr); + res[val] = res[val] ? [...res[val], arr[i]] : [arr[i]]; + } + return res; +}; +var test = [1, 2, 3, 4, 5]; +test.selectType((item) => { + return item > 3 ? 'bigger' : 'small'; +}); + +// 分糖果 +function dispatch(arr) { + let len = arr.length; + let candy = new Array(len).fill(1); + for (let i = 1; i < len; i++) { + // 右边比左边大 正序 + if (arr[i] > arr[i - 1]) { + candy[i] = candy[i - 1] + 1; + } + } + for (let i = len - 2; i >= 0; i--) { + // 左边比右边大 倒序 + if (arr[i] > arr[i + 1]) { + candy[i] = Math.max(candy[i], candy[i + 1] + 1); + } + } + return candy.reduce((a, b) => a + b); +} diff --git a/docs/interview/experiences/practise/202403/0313.js b/docs/interview/experiences/practise/202403/0313.js new file mode 100644 index 0000000..3d0a588 --- /dev/null +++ b/docs/interview/experiences/practise/202403/0313.js @@ -0,0 +1,47 @@ +/** + * 1.MockInterator + * 2.Promise.allSttled + * 3.生成扑克牌的所有序列 + * 4.拼手气抢红包 + */ + +class Promise { + static allSettled(arr) { + return Promise.all( + arr.map((item) => { + return Promise.resolve(item).then( + (res) => ({ + status: 'fullfilled', + value: res, + }), + (err) => ({ + status: 'rejected', + reason: err, + }), + ); + }), + ); + } +} + +class MockInterator { + constructor(obj) { + this.len = Object.keys(obj); + this.obj = obj; + this.index = 0; + } + [Symbol.iterator]() { + return this; + } + next() { + return this.index < this.len + ? { + done: false, + value: this.obj[this.index++], + } + : { + done: true, + value: undefined, + }; + } +} diff --git a/docs/interview/experiences/practise/202403/0314.js b/docs/interview/experiences/practise/202403/0314.js new file mode 100644 index 0000000..9521370 --- /dev/null +++ b/docs/interview/experiences/practise/202403/0314.js @@ -0,0 +1,4 @@ +/** + * 1.拼接url参数 和解析url + * 2. + */ diff --git a/docs/interview/index.md b/docs/interview/index.md index 99f4d2e..9da7aee 100644 --- a/docs/interview/index.md +++ b/docs/interview/index.md @@ -48,6 +48,9 @@ nav: ### JS +> 🌟🌟🌟🌟🌟 五星 必考 四星 问的几率大 三星 可能会问到 + +- 前端性能优化 🌟🌟🌟🌟🌟 - js 执行机制/事件循环 eventLoop 🌟🌟🌟🌟🌟 - 异步编程解决方案 🌟🌟🌟🌟 - 作用域和作用域链/闭包 🌟🌟🌟🌟 @@ -58,7 +61,7 @@ nav: - 箭头函数和普通函数区别 🌟🌟 - Map 和 WeakMap🌟🌟 - Set 和 WeakSet🌟🌟 -- CRP 优化关键渲染路径和重绘、重排 🌟🌟🌟 +- CRP 优化关键渲染路径和重绘、重排 🌟🌟🌟🌟 - dom 事件处理程序 🌟🌟🌟 - 事件捕获事件冒泡 🌟🌟 - 事件代理(事件委托)🌟🌟 @@ -74,16 +77,16 @@ nav: - 样式优先级 🌟🌟🌟 - position 属性 🌟🌟🌟 - 水平垂直居中 🌟🌟🌟 +- 分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景 🌟🌟🌟 ### browser -- js 执行机制 🌟🌟🌟🌟🌟 - 浏览器渲染原理 🌟🌟🌟🌟🌟 - jwt/cookie/session 🌟🌟🌟 - csrf/xss 🌟🌟🌟 - 跨域 🌟🌟🌟🌟 -- web 缓存 🌟🌟🌟🌟 -- 性能优化 🌟🌟🌟🌟 +- web 缓存 🌟🌟🌟🌟🌟 +- 性能优化 🌟🌟🌟🌟🌟 ### nodejs @@ -93,6 +96,7 @@ nav: - 垃圾回收 🌟🌟🌟 - 如何利用多核 CPU🌟🌟🌟🌟 - node 事件循环机制 🌟🌟 +- nodejs 如何做负载均衡 🌟🌟🌟🌟 ### 网络 @@ -105,6 +109,7 @@ nav: ### ts +- 泛型 🌟🌟 - 各种类型体操 🌟🌟🌟🌟🌟 - 逆变和协变 🌟🌟 - interface 和 type 区别 🌟🌟🌟🌟🌟 @@ -120,7 +125,8 @@ nav: ## 面试经验总结 - 提前准备后自我介绍,自己私下多说几遍 -- 提前准备好跳槽原因 +- 提前准备好跳槽原因 1.个人发展:想去大公司看看,了解基建/行为准则, 2.业务发展:缓慢看不到希望,组织调整频繁,下面人员变动频繁 +- 个人发展规划: - 准备问题: 技术栈/负责的业务 - 项目深挖: 提前准备好项目开发过程中遇到的难点以及如何去解决这个问题? diff --git a/docs/interview/instance/redpacket.html b/docs/interview/instance/redpacket.html new file mode 100644 index 0000000..401d23f --- /dev/null +++ b/docs/interview/instance/redpacket.html @@ -0,0 +1,151 @@ + + + + + 仿微信抢红包 + + + +

javascript实现仿微信抢红包

+
+
红包个数:
+
总 金 额:
+ +
+
+ + + + + \ No newline at end of file diff --git a/docs/interview/js/17console.md b/docs/interview/js/17console.md index a4e3f4f..82c7f39 100644 --- a/docs/interview/js/17console.md +++ b/docs/interview/js/17console.md @@ -11,6 +11,43 @@ nav: path: /interview --- +## 1.注意前面的+ + +```js +function f1() { + var sum = 0; + function f2() { + sum++; + return f2; + } + f2.valueOf = function () { + return sum; + }; + f2.toString = function () { + return sum + ''; + }; + return f2; +} +console.log(+f1()); // 0 +console.log(+f1()()); // 1 +console.log(+f1()()()); // 2 +``` + +## 函数作用域相关 + +```js +var foo = 1; +function fn() { + foo = 3; + return; + function foo() { + // todo + } +} +fn(); +console.log(foo); // ? 1 or 3? +``` + ## await/Promise 睁大眼睛看看有啥区别 diff --git a/docs/node/index.md b/docs/node/index.md index aa8731d..23808d9 100644 --- a/docs/node/index.md +++ b/docs/node/index.md @@ -53,6 +53,14 @@ nav: - Webpack 通过先将整个应用打包,再将打包后代码提供给 dev server,开发者才能开始开发 - Vite 直接将源码交给浏览器,实现 dev server 秒开,浏览器显示页面需要相关模块时,再向 dev server 发起请求,服务器简单处理后,将该模块返回给浏览器,实现真正意义的按需加载。 +#### Vite 的热更新原理是什么 + +1.Vite 本地启动时会创建一个 WebSocket 连接,同时去监听本地的文件变化 + +2.当用户修改了本地的文件时,WebSocket 的服务端会拿到变化的文件的 ID 或者其他标识,并推送给客户端 + +3.客户端获取到变化的文件信息之后,便去请求最新的文件并刷新页面 + ### webpack 中的说说 Loader 和 Plugin 的区别?编写 Loader,Plugin 的思路? ### 说说 webpack 构建流程 diff --git a/src/Hooks/index.d.ts b/src/Hooks/index.d.ts deleted file mode 100644 index 13313d7..0000000 --- a/src/Hooks/index.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { default as usePortal } from './usePortal/usePortal'; -export { default as useWindowSize } from './useWindowSize/useWindowSize'; -export { default as useRerender } from './useRerender/useRerender'; -export { default as useInterval } from './useInterval/useInterval'; -export { default as useRequest } from './useRequest/useRequest'; -export { default as useEvent } from './useEvent/useEvent'; diff --git a/src/Hooks/index.js b/src/Hooks/index.js deleted file mode 100644 index 5a1643f..0000000 --- a/src/Hooks/index.js +++ /dev/null @@ -1,7 +0,0 @@ -// Hooks 文件夹总览 -export { default as usePortal } from './usePortal/usePortal'; -export { default as useWindowSize } from './useWindowSize/useWindowSize'; -export { default as useRerender } from './useRerender/useRerender'; -export { default as useInterval } from './useInterval/useInterval'; -export { default as useRequest } from './useRequest/useRequest'; -export { default as useEvent } from './useEvent/useEvent'; diff --git a/src/Hooks/index.tsx b/src/Hooks/index.tsx index 82cd084..3739b8e 100644 --- a/src/Hooks/index.tsx +++ b/src/Hooks/index.tsx @@ -11,3 +11,5 @@ export { default as useInterval } from './useInterval/useInterval'; export { default as useRequest } from './useRequest/useRequest'; export { default as useEvent } from './useEvent/useEvent'; + +export { default as useToggle } from './useToggle/useToggle'; diff --git a/src/Hooks/useRerender/index.d.ts b/src/Hooks/useRerender/index.d.ts deleted file mode 100644 index 9089928..0000000 --- a/src/Hooks/useRerender/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const Example: () => JSX.Element; -export default Example; diff --git a/src/Hooks/useRerender/index.js b/src/Hooks/useRerender/index.js deleted file mode 100644 index fe1e5c8..0000000 --- a/src/Hooks/useRerender/index.js +++ /dev/null @@ -1,18 +0,0 @@ -import React, { useState } from 'react'; -import useRerender from './useRerender'; -const Example = () => { - const [count, setCount] = useState(0); - const reRender = useRerender(); - if (reRender) { - console.log('后续渲染'); - } - else { - console.log('首次渲染'); - } - return (React.createElement("div", null, - React.createElement("div", null, count), - React.createElement("button", { onClick: () => { - setCount(count + 1); - } }, "Add"))); -}; -export default Example; diff --git a/src/Hooks/useRerender/index.md b/src/Hooks/useRerender/index.md index 991a90c..3b14536 100644 --- a/src/Hooks/useRerender/index.md +++ b/src/Hooks/useRerender/index.md @@ -12,7 +12,7 @@ nav: ## Demo -[useRerender](http://zoeice.com/react-hook-useRef/) +是否首次渲染 [useRerender](http://zoeice.com/react-hook-useRef/) diff --git a/src/Hooks/useRerender/useRerender.d.ts b/src/Hooks/useRerender/useRerender.d.ts deleted file mode 100644 index d33a0d8..0000000 --- a/src/Hooks/useRerender/useRerender.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare function useRerender(): boolean; -export default useRerender; diff --git a/src/Hooks/useRerender/useRerender.js b/src/Hooks/useRerender/useRerender.js deleted file mode 100644 index 0950a84..0000000 --- a/src/Hooks/useRerender/useRerender.js +++ /dev/null @@ -1,9 +0,0 @@ -import { useRef, useEffect } from 'react'; -function useRerender() { - const ref = useRef(false); - useEffect(() => { - ref.current = true; - }, []); - return ref.current; -} -export default useRerender; diff --git a/src/Hooks/useToggle/index.md b/src/Hooks/useToggle/index.md index 6643c74..6dfe38e 100644 --- a/src/Hooks/useToggle/index.md +++ b/src/Hooks/useToggle/index.md @@ -1,6 +1,6 @@ --- -title: useEvent -order: 6 +title: useToggle +order: 7 group: title: Hooks order: 1, diff --git a/src/Hooks/useWindowSize/index.d.ts b/src/Hooks/useWindowSize/index.d.ts deleted file mode 100644 index ab18d7e..0000000 --- a/src/Hooks/useWindowSize/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const MyApp: () => JSX.Element; -export default MyApp;