写在前面
针对前端环境恶劣,很多人在前端面试的时候都直接去找相关公司的面经,或者没有真正新一点各个厂里常用面试题,现在小编给大家整理好了,前端面试无非就是那些,面试题更别谈新旧,只不过很多公司常用框架来说,面试内容也各不相同,那么vue和react作为经常出现的老大哥,各位工作上的小娇妻,应该都很熟悉,基本掌握这俩个钥匙那么就算成功了一小半。
毕竟还有算法,手写题之类的出现,不知道各位遇没遇见去公司面试,进门就是七八张纸全是手写题的情况,就很离谱,但因为坑位确实是少,哪怕你知道他是坑,也得往下跳
目录合集
面试题整理
React.js 面试真题
★★★ React 事件绑定原理
/*
一、react 并没有使用原生的浏览器事件,而是在基于 Virtual DOM 的基础上实现
了合成事件。
二、react 合成事件主要分为以下三个过程:
1、事件注册
document 上注册、存储事件回调。
2、事件合成
事件触发后,会执行一下过程:
(1)进入统一的事件分发函数 dispatchEvent;
(2)找到触发事件的 ReactDOMComponent;
(3)开始事件的合成;
—— 根据当前事件类型生成指定的合成对象
—— 封装原生事件和冒泡机制
—— 查找当前元素以及他所有父级
—— 在 listenerBank 根据 key 值查找事件回调并合成到 event(合成事
件结束)
3、批处理
批量处理合成事件内的回调函数
*/
★★★ React 中的 setState 缺点是什么呢
setState 执行的时候可以简单的认为,隶属于原生 js 执行的空间,那么就是属于
同步,被 react 处理过的空间属于异步,这其实也是一种性能的优化,如果多次使用 setState
修改值,那么在异步中会先进行合并,再进行渲染,降低了操作 dom 的次数
★★★ React 组件通信如何实现
/*
react 本身:
(1)props——父组件向子组件通过 props 传参
(2)实例方法——在父组件中可以用 refs 引用子组件,之后就可以调用子
组件的实例方法了
(3)回调函数——用于子组件向父组件通信,子组件调用 props 传递过来的
方法
(4)状态提升——两个子组件可以通过父组件定义的参数进行传参
(5)Context 上下文——一般用作全局主题
(6)Render Props——渲染的细节由父组件控制
状态管理:
(1) mobx/redux/dva——通过在 view 中触发 action,改变 state,进而
改变其他组件的 view
*/
★★★ 类组件和函数组件的区别
/* (1)语法上:函数组件是一个函数,返回一个 jsx 元素,而类组件是用 es6
语法糖 class 定义,继承 component 这个类
(2)类组件中可以通过 state 进行状态管理,而在函数组件中不能使用
setState(),在 react16.8 以后,函数组件可以通过 hooks 中的 useState 来模拟类组件
中的状态管理;
(3)类组件中有一系列的生命周期钩子函数,在函数组件中也需要借助 hooks
来使用生命周期函数;
(4)类组件能够捕获**最新**的值(永远保持一致),这是因为当实例的 props
属性发生修改时,class 组件能够直接通过 this 捕获到组件最新的 props;而函数式组件是
捕获**渲染**所使用的值,已经因为 javascript**闭包**的特性,之前的 props 参数保存
在内存之中,无法从外部进行修改。
*/
★★★★ 虚拟 DOM 的优劣如何?实现原理?
/* 虚拟 dom 是用 js 模拟一颗 dom 树,放在浏览器内存中,相当于在 js 和真实 dom 中
加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。
优点:
(1)虚拟 DOM 具有批处理和高效的 Diff 算法,最终表现在 DOM 上的修改只是变更
的部分,可以保证非常高效的渲染,优化性能;
(2)虚拟 DOM 不会立马进行排版与重绘操作,对虚拟 DOM 进行频繁修改,最后一
次性比较并修改真实 DOM 中需要改的部分;
(3)虚拟 DOM 有效降低大面积真实 DOM 的重绘与排版,因为最终与真实 DOM 比
较差异,可以只渲染局部;
缺点:
(1)首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,会比 innerHTML 插入
慢;
React 组件的渲染过程:
(1)使用 JSX 编写 React 组件后所有的 JSX 代码会通过 Babel 转化为
React.createElement 执行;
(2)createElement 函数对 key 和 ref 等特殊的 props 进行处理,并获取
defaultProps 对默认 props 进行赋值,并且对传入的子节点进行处理,最终构造成一个
ReactElement 对象(所谓的虚拟 DOM)。
(3)ReactDOM.render 将生成好的虚拟 DOM 渲染到指定容器上,其中采用了批处
理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实 DOM。
虚拟 DOM 的组成——ReactElementelement 对象结构:
(1)type:元素的类型,可以是原生 html 类型(字符串),或者自定义组件(函
数或 class)
(2)key:组件的唯一标识,用于 Diff 算法,下面会详细介绍
(3)ref:用于访问原生 dom 节点
(4)props:传入组件的 props,chidren 是 props 中的一个属性,它存储了当前
组件的孩子节点,可以是数组(多个孩子节点)或对象(只有一个孩子节点)
(5)owner:当前正在构建的 Component 所属的 Component
(6)self:(非生产环境)指定当前位于哪个组件实例
(7)_source:(非生产环境)指定调试代码来自的文件(fileName)和代码行数
(lineNumber)
*/
★★★★ 新出来两个钩子函数?和砍掉的 will 系列 有啥区别?
// react16 中废弃了三个钩子
componentWillMount // 组件将要挂载的钩子
componentWillReceiveProps // 组件将要接收一个新的参数时的钩子
componentWillUpdate // 组件将要更新的钩子
// 新增了方法
getDerivedStateFromProps // 静态方法
getSnapshotBeforeUpdate
/* 在 16.8版本以后,react将 diff运算改进为 Fiber,这样的话当我们调用 setState
方法进行更新的时候,在 reconciler 层中 js 运算会按照节点为单位拆分成一个个小的工
作单元,在 render 前可能会中断或恢复,就有可能导致在 render 前这些生命周期在进行一
次更新时存在多次执行的情况,此时如果我们在里面使用 ref 操作 dom 的话,就会造成页面
频繁重绘,影响性能。
所以废弃了这几个 will 系列的勾子,增加了 getDerivedStateFromProps 这个静
态方法,这样的话我们就不能在其中使用 this.refs 以及 this 上的方法了;
getSnapshotBeforeUpdate 这个方法已经到了 commit 阶段,只会执行一次,给想读取 dom
的用户一些空间。
*/
★★★★ 当调用`setState`时,React `render` 是如何工作的?
Answer
调用 setState()
检查上下文环境生成更新时间相关参数并判定事件优先级(fiber,currenttime,
expirationtime 等…)
根据优先级相关参数判断更新模式是 sync(同步更新)或是 batched(批量处理)
加入执行更新事件的队列,生成事件队列的链表结构
根据链表顺序执行更新
setState 既是同步的,也是异步的。同步异步取决于 setState 运行时的上下文。
且 setState 只在合成事件和钩子函数中是“异步”的,在原生 DOM 事件和
setTimeout 中都是同步的
render 如何工作
React 在 props 或 state 发生改变时,会调用 React 的 render 方法,创建一颗不
同的树
React 需要基于这两颗不同的树之间的差别来判断如何有效的更新 UI
diff 算法,将两颗树完全比较更新的算法从 O(n^3^),优化成 O(n);
同层节点之间相互比较,不会跨节点比较
不同类型的节点,产生不同的树结构
设置 key 来指定节点在不同的渲染下保持稳定
★★★★ 什么是 immutable?为什么要使用它?
Answer
immutable 是一种持久化数据。一旦被创建就不会被修改。修改 immutable 对 象的时候返回新的 immutable。但是原数据不会改变。
在 Rudux 中因为深拷贝对性能的消耗太大了(用到了递归,逐层拷贝每个节 点)。 但当你使用 immutable 数据的时候:只会拷贝你改变的节点,从而达 到了节省性能。 总结:immutable 的不可变性让纯函数更强大,每次都返回 新的 immutable 的特性让程序员可以对其进行链式操作,用起来更方便。
因为在 react 中,react 的生命周期中的 setState()之后的 shouldComponentUpdate()阶段默认返回 true,所以会造成本组件和子组件的 多余的 render,重新生成 virtual dom,并进行 virtual dom diff,所以解决办法
是我们在本组件或者子组件中的 shouldComponentUpdate()函数中比较,当不 需要 render 时,不 render。
当 state 中的值是对象时,我们必须使用深拷贝和深比较!如果不进行深拷贝后再 setState,会造成 this.state 和 nextState 指向同一个引 用,所以 shouldComponentUpdate()返回值一定是 false,造成 state 值改了, 而组件未渲染(这里不管 shouldComponentUpdate 中使用的是深比较还是浅 比较)。所以必须深拷贝。
如果不在 shouldComponentUpdate 中进行深比较,会造成即使 state 中的对 象值没有改变,因为是不同的对象,而在 shouldComponentUpdate 返回 true, 造成不必要的渲染。
所以只能是深拷贝和深比较。
★★★★ React 复用组件的状态和增强功能的方法
Answer
①render props 模式
创建 Mouse 组件,在组件中提供复用的状态逻辑代码
将要复用的状态作为 props.render(state) 方法的参数,暴露到组件外部
使用 props.render() 的返回值作为要渲染的内容
// 子组件
class Mouse extends React.Component{
// mouse 本组件的数据
state={
x:0,
y:0
}
// mouse 本组件的方法
handleMouse=(e)=>{
this.setState({
x:e.clientX,
y:e.clientY
})
}
componentDidMount(){
window.addEventListener('mousemove',this.handleMouse)
};
render(){
// 在这里用 props 接收从父传过来的 render 函数,再把 state 数据作为实参传
递出去
// 其实渲染的就是从父传过来的 UI 结构,只是公用了 Mouse 组件的数据和方法
return this.props.render(this.state)
}
}
// 父组件
class App extends React.Component{
render(){
return(<div>
<h1>app 组件:{this.props.name}</h1>
{/* 在使用 mouse 组件时,给 mouse 传递一个值(父传子),
只不过这里的 props 是函数,这个函数将要用形参接受从 mouse 组件传递过来
的实参(state 数据) */}
<Mouse render={(mouse)=>{
return (<div>
<h5>我用的是 Mouse 的 state 和方法:X 坐标{mouse.x}-Y 坐标
{mouse.y}</h5>
</div>)
}} />
</div>)
}
}
②高阶组件 HOC
const EnhancedComponent = withHOC(WrappedComponent)
// 高阶组件内部创建的类组件:
class Mouse extends React.Component {
render() {
return <WrappedComponent {...this.state} />
}
}
③hooks:自定义 hook
// 自定义 hook
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
return isOnline;
}
// 使用
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
4、webpack 的构建流程是什么?从读取配置到输出文件这个过 程尽量说全
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参 数;
2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的 插件,执行对象的 run 方法开始执行编译;
3. 确定入口:根据配置中的 entry 找出所有的入口文件;
4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译, 再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过 了本步骤的处理;
5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每 个模块被翻译后的最终内容以及它们之间的依赖关系;6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块 的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表, 这步是可以修改输出内容的最后机会;
7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把 文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到 感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
5、如何利用 webpack 来优化前端性能?(提高性能和体验)
用 webpack 优化前端性能是指优化 webpack 的输出结果,让打包的最终结果在 浏览器运行快速高效。
压缩代码。删除多余的代码、注释、简化代码的写法等等方式。可以利用 webpack 的 UglifyJsPlugin 和 ParallelUglifyPlugin 来压缩 JS 文件, 利用 cssnano(css-loader?minimize)来压缩 css
利用 CDN 加速。在构建过程中,将引用的静态资源路径修改为 CDN 上 对应的路径。可以利用 webpack 对于 output 参数和各 loader 的 publicPath 参数来修改资源路径
删除死代码(Tree Shaking)。将代码中永远不会走到的片段删除掉。可以通过在启动 webpack 时追加参数--optimize-minimize 来实现
提取公共代码。
6、npm 打包时需要注意哪些?如何利用 webpack 来更好的构建?
Npm 是目前最大的 JavaScript 模块仓库,里面有来自全世界开发者上传的可复 用模块。你可能只是 JS 模块的使用者,但是有些情况你也会去选择上传自己开 发的模块。 关于 NPM 模块上传的方法可以去官网上进行学习,这里只讲解如 何利用 webpack 来构建。
NPM 模块需要注意以下问题:
1. 要支持 CommonJS 模块化规范,所以要求打包后的最后结果也遵守该规 则。
2. Npm 模块使用者的环境是不确定的,很有可能并不支持 ES6,所以打包的最后结果应该是采用 ES5 编写的。并且如果 ES5 是经过转换的,请最好连同 SourceMap 一同上传。
3. Npm 包大小应该是尽量小(有些仓库会限制包大小)
4. 发布的模块不能将依赖的模块也一同打包,应该让用户选择性的去自行安装。这样可以避免模块应用者再次打包时出现底层模块被重复打包的情况。
5. UI 组件类的模块应该将依赖的其它资源文件,例如.css 文件也需要包含在发布的模块里。
基于以上需要注意的问题,我们可以对于 webpack 配置做以下扩展和优化:
1. CommonJS 模块化规范的解决方案: 设置 output.libraryTarget='commonjs2'使输出的代码符合 CommonJS2 模块 化规范,以供给其它模块导入使用
2. 输出 ES5 代码的解决方案:使用 babel-loader 把 ES6 代码转换成 ES5 的 代码。再通过开启 devtool: 'source-map'输出 SourceMap 以发布调试。
3. Npm 包大小尽量小的解决方案:Babel 在把 ES6 代码转换成 ES5 代码 时会注入一些辅助函数,最终导致每个输出的文件中都包含这段辅助函数的代码,造成了代码的冗余。解决方法是修改.babelrc 文件,为其加入
transform-runtime 插件
4. 不能将依赖模块打包到 NPM 模块中的解决方案:使用 externals 配置项 来告诉 webpack 哪些模块不需要打包
写在最后
面试题整理了很多所以展示部分,觉得有帮助就用,尽管前端环境确实一直在淘汰人,但是我们也要报团取暖,加油,对面试题感兴趣可【点击此处】最后希望各位能找到满意的工作