🦾 高级前端进阶知识
工程化,组件库,基础建设...
diff --git a/404.html b/404.html index 1892c63f..82ef0e9c 100644 --- a/404.html +++ b/404.html @@ -12,7 +12,7 @@
404
But if you don't change your direction, and if you keep looking, you may end up where you are heading.
总结:在解决问题的基础上,再进行优化。方法一使用二分复杂度为O(nlogn),使用窗口法就可以将复杂度将为O(n)。
- +这里就放一些自我介绍吧,先占个坑
https://github.com/Sunny-117/js-challenges
✨✨✨ 集锦 2022-2023 年 前端 JavaScript 手写题,编程题,Not just for interviews
文档地址:https://sunny-117.github.io/mini-anything-docs/
🚀 mini-anything 是一个集合了前端领域的各种迷你版本的库,方便前端学习者学习,探索前端源码。里面的库是原库的超级迷你版本,只包含原库的主流程,不包含边界 case,所以建议仅学习使用,请勿用于生产环境
https://github.com/Sunny-117/lodash-ts
🎉🎉🎉 lodash-ts 是一个一致性、模块化、高性能的 JavaScript 实用工具库。本仓库完成 lodash 的 typescript 版本,只实现 lodash 中漂亮的函数
https://github.com/Sunny-117/Native-project
🔧 原生 JavaScript 项目集合,github 中国区最全版
📦️ A high-quality & reliable React Hooks library.
https://github.com/Sunny-117/shooks
https://github.com/Sunny-117/BOSScript
boss 直聘投递、关闭,一条龙服务的油猴脚本,让你海投简历,只需要 2 分钟
下面列表里的问题对于参加技术面试的人来说可能有些用。
列表里的问题并不一定适用于某个特定的职位或者工作类型,也没有排序
最开始的时候这只是我自己的问题列表,但是慢慢地添加了一些我觉得可能让我对这家公司亮红牌的问题。
我也注意到被我面试的人提问我的问题太少了,感觉他们挺浪费机会的。
绝对不要想把这个列表里的每个问题都问一遍。(尊重面试官的时间,而且你可以通过查找已经发布的答案来显示 你的主动性)
请记住事情总是灵活的,组织的结构调整也会经常发生。拥有一个 bug 追踪系统并不会保证高效处理 bug。 CI/CD (持续集成系统) 也不一定保证交付时间会很短。
Find more inspiration for questions in:
https://github.com/viraptor/reverse-interview
翻译:
React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿。
为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。
所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:
核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。
父组件向子组件通信:父组件通过 props 向子组件传递需要的信息。
子组件向父组件通信:: props+回调的方式。
父组件向子组件的子组件通信,向更深层子组件通信:
即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。
DANGER
已经不维护了,仅供学习
官方网站:https://dvajs.com dva 不仅仅是一个第三方库,更是一个框架,它主要整合了 redux 的相关内容,让我们处理数据更加容易,实际上,dva 依赖了很多:react、react-router、redux、redux-saga、react-redux、connected-react-router 等。
通过dva对象.use(插件)
,来使用插件,插件本质上就是一个对象,该对象与配置对象相同,dva 会在启动时,将传递的插件对象混合到配置中。
该插件会在仓库中加入一个状态,名称为 loading,它是一个对象,其中有以下属性
如果一个组件,仅用于渲染一个 UI 界面,而没有状态(通常是一个函数组件),该组件叫做展示组件 如果一个组件,仅用于提供数据,没有任何属于自己的 UI 界面,则该组件叫做容器组件,容器组件纯粹是为了给其他组件提供数据。
react-redux 库:链接 redux 和 react
知识
- chrome 插件:redux-devtools
- 使用 npm 安装第三方库:redux-devtools-extension
希望把路由信息放进仓库统一管理的时候才需要这个库
用于将 redux 和 react-router 进行结合
本质上,router 中的某些数据可能会跟数据仓库中的数据进行联动
该组件会将下面的路由数据和仓库保持同步
该库中的内容:
这是一个函数,调用它,会返回一个用于管理仓库中路由信息的 reducer,该函数需要传递一个参数,参数是一个 history 对象。该对象,可以使用第三方库 history 得到。
该函数会返回一个 redux 中间件,用于拦截一些特殊的 action
这是一个组件,用于向上下文提供一个 history 对象和其他的路由信息(与 react-router 提供的信息一致)
之所以需要新制作一个组件,是因为该库必须保证整个过程使用的是同一个 history 对象
React 动画库:react-transition-group 文档在 npm 搜索https://reactcommunity.org/react-transition-group/
当进入时,发生:
当退出时,发生:
设置classNames属性,可以指定类样式的名称
关于首次渲染时的类样式,appear、apear-active、apear-done,它和 enter 的唯一区别在于完成时,会同时加入 apear-done 和 enter-done
还可以与 Animate.css 联用
和 CSSTransition 的区别:用于有秩序的切换内部组件
默认情况下:out-in 先退出后进入
in-out:
该库寻找 dom 元素的方式,是使用已经过时的 API:findDomNode,该方法可以找到某个组件下的 DOM 根元素,先保留,创建新的之后在删除
该组件的 children,接收多个 Transition 或 CSSTransition 组件,该组件用于根据这些子组件的 key 值,控制他们的进入和退出状态
umijs, nextjs(ssr 服务端渲染),antd,antd-pro(antd+umijs)
提供了一个命令行工具:umi,通过该命令可以对 umi 工程进行操作
umi 还可以使用对应的脚手架
umi 对路由的处理,主要通过两种方式:
umi 约定,工程中的 pages 文件夹中存放的是页面。如果工程包含 src 目录,则 src/pages 是页面文件夹。
umi 约定,页面的文件名,以及页面的文件路径,是该页面匹配的路由
umi 约定,如果页面的文件名是 index(不写 index 才能访问,写了反而不能访问了),则可以省略文件名(首页)(注意避免文件名和当前目录中的文件夹名称相同)
umi 约定,如果 src/layout 目录存在,则该目录中的 index.js 表示的是全局的通用布局,布局中的 children 则会添加具体的页面。
umi 约定,如果 pages 文件夹中包含_layout.js,则 layout.js 所在的目录以及其所有的子目录中的页面,共用该布局。
404 约定,umi 约定,pages/404.js,表示 404 页面,如果路由无匹配,则会渲染该页面。该约定在开发模式中无效,只有部署后生效。
使用$名称,会产生动态路由
umi/link
,umi/navlink
umi/router
导入模块时,@表示 src 目录
所有的页面、布局组件,都会通过属性 props,收到下面的属性
如果需要在普通组件中获取路由信息,则需要使用 withRouter 封装,可以通过umi/withRouter
导入
当使用了路由配置后,约定式路由全部失效。
两种方式书写 umi 配置:
.umirc.js
config/config.js
进行路由配置时,每个配置就是一个匹配规则,并且,每个配置是一个对象,对象中的某些属性,会直接形成 Route 组件的属性
注意:
路由配置中的信息,同样可以放到约定式路由中,方式是,为约定式路由添加第一个文档注释(注释的格式的 YAML),需要将注释放到最开始的位置
YAML 格式
官方插件集 umi-plugin-react 文档:https://umijs.org/zh/plugin/umi-plugin-react.html
dva 插件和 umi 整合后,将模型分为两种:
在src/models
目录下定义的 js 文件都会被看作是全局模型,默认情况下,模型的命名空间和文件名一致。
局部模型定义在 pages 文件夹或其子文件夹中,在哪个文件夹定义的模型,会被该文件夹中的所有页面以及子页面、以及该文件夹的祖先文件夹中的页面所共享。
局部模型的定义和全局模型的约定类似,需要创建一个 models 文件夹
解决两个问题:
底层使用了 webpack 的加载器:css-loader(内部包含了 css-module 的功能)
css 文件 -> css-module -> 对象
less 代码 -> less-loader -> css 代码 -> css-module -> 对象
代理用于解决跨域问题
配置.umirc.js
中的 proxy,配置方式和 devServer 中的 proxy 配置相同
用于解决前后端协同开发的问题
数据模拟可以让前端开发者在开发时,无视后端接口是否真正完成,因为使用的是模拟的数据
umijs 约定:
以上两种 JS 文件,均会被 umijs 读取,并作为数据模拟的配置
可以自行发挥,添加模拟数据,通常,我们会和 mockjs 配合。
书写在.umirc.js 文件中的配置
create-umi
StrictMode(React.StrictMode
),本质是一个组件,该组件不进行 UI 渲染(React.Fragment <> </>
),它的作用是,在渲染内部组件时,发现不合适的代码。
副作用:一个函数中,做了一些会影响函数外部数据的事情,例如:
相反的,如果一个函数没有副作用,则可以认为该函数是一个纯函数
在严格模式下,虽然不能监控到具体的副作用代码,但它会将不能具有副作用的函数调用两遍,以便发现问题。(这种情况,仅在开发模式下有效)
性能分析工具
分析某一次或多次提交(更新),涉及到的组件的渲染时间
火焰图:得到某一次提交,每个组件总的渲染时间以及自身的渲染时间
排序图:得到某一次提交,每个组件自身渲染时间的排序
组件图:某一个组件,在多次提交中,自身渲染花费的时间
SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
优点:
缺点:
- app.js 作为客户端与服务端的公用入口,导出 Vue 根实例,供客户端 entry 与服务端 entry 使用。客户端 entry 主要作用挂载到 DOM 上,服务端 entry 除了创建和返回实例,还需要进行路由匹配与数据预获取。
- webpack 为客服端打包一个 ClientBundle,为服务端打包一个 ServerBundle。
- 服务器接收请求时,会根据 url,加载相应组件,获取和解析异步数据,创建一个读取 Server Bundle 的 BundleRenderer,然后生成 html 发送给客户端。
- 客户端混合,客户端收到从服务端传来的 DOM 与自己的生成的 DOM 进行对比,把不相同的 DOM 激活,使其可以能够响应后续变化,这个过程称为客户端激活(也就是转换为单页应用)。为确保混合成功,客户 端与服务器端需要共享同一套数据。在服务端,可以在渲染之前获取数据,填充到 store 里,这样,在客户端挂载到 DOM 之前,可以直接从 store 里取数据。首屏的动态数据通过 window.INITIAL_STATE 发送到客户端
- VueSSR 的原理,主要就是通过 vue-server-renderer 把 Vue 的组件输出成一个完整 HTML,输出到客户端,到达客户端后重新展开为一个单页应用。
vue 对 methods 的处理比较简单,只需要遍历 methods 配置中的每个属性,将其对应的函数使用 bind 绑定当前组件实例后复制其引用到组件实例中即可
当组件实例触发生命周期函数beforeCreate
后,它会做一系列事情,其中就包括对 computed 的处理
它会遍历 computed 配置中的所有属性,为每一个属性创建一个 Watcher 对象,并传入一个函数,该函数的本质其实就是 computed 配置中的 getter,这样一来,getter 运行过程中就会收集依赖
但是和渲染函数不同,为计算属性创建的 Watcher 不会立即执行,因为要考虑到该计算属性是否会被渲染函数使用,如果没有使用,就不会得到执行。因此,在创建 Watcher 的时候,它使用了 lazy 配置,lazy 配置可以让 Watcher 不会立即执行。
收到lazy
的影响,Watcher 内部会保存两个关键属性来实现缓存,一个是value
,一个是dirty
value
属性用于保存 Watcher 运行的结果,受lazy
的影响,该值在最开始是undefined
dirty
属性用于指示当前的value
是否已经过时了,即是否为脏值,受lazy
的影响,该值在最开始是true
Watcher创建好后,vue 会使用代理模式,将计算属性挂载到组件实例中
当读取计算属性时,vue 检查其对应的 Watcher 是否是脏值,如果是,则运行函数,计算依赖,并得到对应的值,保存在 Watcher 的 value 中,然后设置 dirty 为 false,然后返回。
如果 dirty 为 false,则直接返回 watcher 的 value,即为缓存的原理 巧妙的是,在依赖收集时,被依赖的数据不仅会收集到计算属性的 Watcher,还会收集到组件的 Watcher
当计算属性的依赖变化时,会先触发计算属性的 Watcher执行,此时,它只需设置**dirty**
为 true 即可,不做任何处理。
由于依赖同时会收集到组件的 Watcher,因此组件会重新渲染,而重新渲染时又读取到了计算属性,由于计算属性目前已为 dirty,因此会重新运行 getter 进行运算
而对于计算属性的 setter,则极其简单,当设置计算属性时,直接运行 setter 即可
TODO
我:
- 在使用时,computed 当做属性使用,而 methods 则当做方法调用
- computed 可以具有 getter 和 setter,因此可以赋值,而 methods 不行
- computed 无法接收多个参数,而 methods 可以
- computed 具有缓存,而 methods 没有
面试官:回去等通知吧!
更深入的回答:↑
- 都是观察数据变化的(相同)
- 计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
- computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
- watch 支持异步,computed 不支持;
- watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
- watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
- computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)
watch 的 参数:deep:深度监听;immediate :组件加载立即触发回调函数执行
对于 Computed:
对于 Watch:
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用 watch。 总结:
运用场景:
放在 computed 里面。因为 computed 只有在它的相关依赖发生改变时才会重新求值。相比而言,方法只要发生重新渲染,methods 调用总会执行所有函数。
在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。 一般需要对 DOM 元素进行底层操作时使用,尽量只用来操作 DOM 展示,不修改内部的值。当使用自定义指令直接修改 value 值时绑定 v-model 的值也不会同步更新;如必须修改可以在自定义指令中使用 keydown 事件,在 vue 组件中使用 change 事件,回调中修改 vue 数据;
(1)自定义指令基本内容
全局定义:Vue.directive("focus",{})
局部定义:directives:{focus:
钩子函数:指令定义对象提供钩子函数
钩子函数参数
(2)使用场景
(3)使用案例 初级应用:
高级应用:
要求:有清晰思路,良好的信息流组件设计模式,可扩展性强并给出核心功能的实现方式
双向数据绑定
双向数据绑定意味着 UI 动态地绑定到模型数据,这样当 UI 改变时,模型数据就随之变化,反之亦然。
单向数据流
单向数据流意味着 model 是唯一来源。UI 触发消息的变化,将用户行为标记为 model。只有 model 具有访问更改应用程序状态的权限。其效果是数据总是朝一个方向流动,这使得理解起来更容易。
二者有什么优缺点
单向数据流是确定性的,数据流动方向可以跟踪,流动单一,追查问题的时候可以跟快捷。缺点就是写起来不太方便。要使 UI 发生变更就必须创建各种 action 来维护对应的 state 双向绑定,优点是使用方便,值和 UI 双绑定,但是由于各种数据相互依赖相互绑定,导致数据问题的源头难以被跟踪到,子组件修改父组件,兄弟组件互相修改有有违设计原则
可以介绍一下模板引擎的原理,比如实现类似 html
这种模板将其中变量替换为对应值的方式。
要求:能够从每个框架的生态系统,甚至结合之前的项目及不同的业务特点,给出框架的优劣
父子之间层级过多时,当父子组件之间层级不多的时候,父组件可以一层层的向子组件传递数据或者子组件一层层向父组件发送消息,代码上没有太难维护的地方。可是,一旦父子组件之间层级变多后,传递一个数据或者发送一个消息就变得麻烦。这块如果了解开源的 Element 组件库,就会知道其实现方式:构造一个函数自动向上/向下查询父亲节点,以[组件名, 消息名, 参数]
三元组进行消息传递,降低长链传播成本;
具体实现参考:https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js
对虚拟 dom 和 diff 算法中的一些细节理解
https://github.com/livoras/blog/issues/13
要求:写出 diff 算法的核心部分
DANGER
写作中
slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。
当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在 vm.slot 中,默认插槽为 um. slot.default,具名插槽为 vm.slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。
关于 Vue 和其他框架的不同,官方专门写了一篇文档,从性能、体积、灵活性等多个方面来进行了说明。 详细可以参阅:https://cn.vuejs.org/v2/guide/comparison.html
从 React Hook 的实现角度看,React Hook 是根据 useState 调用的顺序来确定下一次重渲染时的 state 是来源于哪个 useState,所以出现了以下限制
而 Composition API 是基于 Vue 的响应式系统实现的,与 React Hook 的相比
虽然 Compositon API 看起来比 React Hook 好用,但是其设计思想也是借鉴 React Hook 的。
定位相同:处理 UI 层的,vue 提倡渐进式处理,react 没有 vue 推崇模版写法,react 是 all in js jsx, vue 支持 jsx, react 不支持 vue 的 template react hooks, vue3 借鉴了,都有 hoos 风格的 api UI 更新策略:react 传入一个新数据,不能修改旧数据,vue 会根据两次数据渲染 dom 的 diff 更新 UI,数据变化,就会计划更新 UI,都会延迟更新 vue 文化:全部封装好;React 推崇第三方库结合 vue 有 keep-alive, react 没有,重新渲染,需要自己实现 vue css scoped;react 需要第三方:css modules/style-component
相似之处:
不同之处 :
1)数据流 Vue 默认支持数据双向绑定,而 React 一直提倡单向数据流 2)虚拟 DOM Vue2.x 开始引入"Virtual DOM",消除了和 React 在这方面的差异,但是在具体的细节还是有各自的特点。
3)组件化 React 与 Vue 最大的不同是模板的编写。
具体来讲:React 中 render 函数是支持闭包特性的,所以 import 的组件在 render 中可以直接调用。但是在 Vue 中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 一个组件完了之后,还需要在 components 中再声明下。 4)监听数据变化的实现原理不同
5)高阶组件 react 可以通过高阶组件(HOC)来扩展,而 Vue 需要通过 mixins 来扩展。 高阶组件就是高阶函数,而 React 的组件本身就是纯粹的函数,所以高阶函数对 React 来说易如反掌。相反 Vue.js 使用 HTML 模板创建视图组件,这时模板无法有效的编译,因此 Vue 不能采用 HOC 来实现。 6)构建工具 两者都有自己的构建工具:
7)跨平台
下载 node_modules 资源包的命令:npm install
启动 vue-cli 开发环境的 npm 命令:npm run dev
vue-cli 生成 生产环境部署资源 的 npm 命令:npm run build
用于查看 vue-cli 生产环境部署资源文件大小的 npm 命令:npm run build --report
简单说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经历以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成 AST 语法树(一种用 JavaScript 对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的 DOM 也不会变化。那么优化过程就是深度遍历 AST 树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的 AST 树转换为可执行的代码。
在使用 vue 的时候,我们有两种方式来创建我们的 HTML 页面,第一种情况,也是大多情况下,我们会使用模板 template 的方式,因为这更易读易懂也是官方推荐的方法;第二种情况是使用 render 函数来生成 HTML,它比 template 更接近最终结果。
complier 的主要作用是解析模板,生成渲染模板的 render, 而 render 的作用主要是为了生成 VNode
complier 主要分为 3 大块:
- parse:接受 template 原始模板,按着模板的节点和数据生成对应的 ast
- optimize:遍历 ast 的每一个节点,标记静态节点,这样就知道哪部分不会变化,于是在页面需要更新时,通过 diff 减少去对比这部分 DOM,提升性能
- generate 把前两步生成完善的 ast,组成 render 字符串,然后将 render 字符串通过 new Function 的方式转换成渲染函数
vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。
TODO