- 近期开始找工作,主要想找以 React 技术栈为主的公司,目前为止面了 5 家,总结了一些面试题下来。
1、HTML
- 面的几家公司,都没问到这块内容,可能觉得太基础了吧
2、CSS
1. flex 的中文是什么?用于解决什么样的问题?flex 的主轴有几种方向?
- flex 是 flexible box 的缩写,意思是“弹性布局”
- 用于解决手机端自适应布局的问题
- 四个方向:
- row,column,row-reverse,column-reverse
- 可能有人会说,row 和 row-reverse 不都是横向的嘛,注意:不是的! 可以把这个方向理解为一个射线,那么从左往右和从右往左,二者其实是不同的
2. flex-grow,flex-shrink,flex-basis
- flex-grow:如果在一个宽度为 500px 的容器中,有三个宽度分别为:100px,150px,100px 的模块,那么此时空白区域宽度为 150px。此时给三个容器分别添加:
flex-grow: 1,flex-grow: 2,flex-grow: 3,那么空白区域的部分就会按这个比例分配个这三个模块,也就是拉伸。- 第一个模块分配到的宽度:(1/(1+2+3))*150=25,总宽度为:25+100=125px
- 第二个模块分配到的宽度:(2/(1+2+3))*150=50,总宽度为:50+150=200px
- 第三个模块分配到的宽度,(3/(1+2+3))*150=75,总宽度为:75+100=175px
- flex-shrink:
- 如果子容器宽度超过父容器宽度,即使是设置了 flex-grow,但是由于没有剩余空间,就分配不到剩余空间了。这时候有两个办法:换行和压缩。由于 flex 默认不换行,那么压缩的话就需要使用到 flex-shrink
- 如果在一个宽度为 500px 的父容器中,有三个宽度分别为:300px,150px,200px 的子模块,同时,他们的 flex-grow 的属性分别设置为:1、2、3,那么这三个子模块就会根据比例进行压缩。
- 计算方式:1300 + 2150 + 3*200 = 1200
- A 的压缩率 = 300*1/1200 * 150 = 37.5,A 的宽度 = 300-37.5 = 262.5
- B 的压缩率 = 150*2/1200 * 150 = 37.5,B 的宽度 = 150-37.5 = 112.5
- C 的压缩率 = 200*3/1200 * 150 = 75,C 的宽度 = 200-75 = 125
- flex-basis:
- 指定了 flex 元素在主轴方向上的初始大小
- 优先级:max-width/min-width > flex-basis > width > box
- 总结:
- 当内容区域高度不够的时候,footer 仍然需要固定在底部。这时候,我们可以给 main 使用 flex-grow: 1,使它自动填满剩余空间。
- 不希望元素被压缩时,flex-shrink 通常设置为0。
3. box-sizing 的 border-box 和 content-box 的区别
- border-box = border + padding + content
- content-box = content
- 一般使用 border-box,更好用
4. BFC 是什么
- BFC:块格式化上下文,比如
- 给一个 div 写一个 overflow: hidden,那么这个 div 里面的浮动元素就会被其包裹起来
- 行内块元素(元素的 display: inline-block)
- 绝对定位元素(元素的 position 为 absolute 或 fixed)
- html 标签中的内容
3、JS
1. localStorage、Cookie 和 SessionStorage
- localStorage:存储在本地的,不会发送到服务器,除非用户手动删除,不然会一直存在,大小一般在 5~10m 之间(随浏览器不同而不同)
- Cookie:用于存储用户信息,是服务器分发给用户的一段字符串,浏览器每次访问对应服务器,都要带上这个字段,可以理解为是门票。一般最大为 4k
- SessionStorage:与 localStorage 相似,但是不同点在于,一般在会话结束时关闭,如关闭浏览器
2. 数组的一些常用方法,reduce() 怎么用?
- array.concat()、array.filter()、array.forEach()、array.map()、array.push()、array.reduce()、array.reverse()、array.slice()、array.splice() 等
- array.reduce():核心作用就是聚合。操作一个已知数组来获取一个任意类型的值。
let array = [1,2,3,4]
let sum = (a, b) => a+b
array.reduce(sum, 0)
//10
- reduce 对数组 array 的每个成员执行 sum 函数。 sum 的参数 a 是累积变量,参数 b 是当前的数组成员。每次执行时,b 会加到 a 上,最后输出 a。
- 累积变量必须有一个初始值,上例是 reduce 函数的第二个参数 0。如果省略该参数,那么初始值默认是数组的第一个成员。
3. ES6 的 let、const、var 的区别
- var:var 声明有一个变量提升的过程,可以在声明之前使用,在函数块内定义,会变成全局变量。同时允许重复定义。
console.log(a);//正常运行,控制台输出 undefined
var a = 1;
- let:与 var 相似,但是不能再声明之前调用,不允许重复定义
console.log(a); //报错
let a = 1;
let a = 2; //报错
- const:声明一个常量,在声明时必须赋值,不能再声明之前调用,不允许重复定义
console.log(a); //报错
const a; // 报错
const a = 1;
const a = 2; //报错
4. ES6 的解构赋值
5. ES6 的 export default 和 export 的区别
- export 与 export default 均可用于导出常量、函数、文件、模块等
- 可以在其它文件或模块中通过 import+(常量 | 函数 | 文件 | 模块)名的方式,将其导入,以便能够对其进行使用
- 在一个文件或模块中,export 可以有多个,但是 export default 仅有一个
- 通过 export 方式导出,在导出和导入时要加 { },export default则不需要
6. 手写 AJAX
let request = new XMLHttpRequest()
request.open('GET','/你的文件名')
request.openreadystatechange = function(){
if(request.readyState === 4){
console.log('请求完成!')‘
if(request.response.status >= 200 && request.response.status < 300){
console.log('请求成功!')
}
}
}
request.send()
7. 说一说 this
- fn()
- this => window/global
- obj.fn()
- this => obj
- fn.call(xx)
- this => xx
- fn.apply(xx)
- this => xx
- fn.bind(xx)
- this => xx
- new Fn()
- this => 新的对象
- fn = ()=> {}
- this => 外面的 this
8. 说一说跨域
- 浏览器出于安全考虑,不允许不同域名、不同协议、不同端口的两个资源互相交互,只允许同域名、同协议、同端口的两个资源相互交互
- 为了解决跨域,有两种方法:
- CORS:在响应头上添加允许访问的网站,response.setHeader("Access-Control-Allow-Origin", "xxx.com:9999");
- JSONP:也就是 JSON With Padding,通过 script 标签去加载数据,并把数据当作 JS 代码来运行
- JSONP 优点:支持 IE
- JSONP 缺点:1. 由于是通过 script 标签加载数据,无法像 AJAX 一样获取状态码,只能判断成功还是失败;2. 只支持 get,不支持 post
9. 说一说闭包
- 闭包就是有一个函数里面还有一个函数,里面的函数可以使用外面函数内的变量,用于避免全局变量的污染
10. async 和 await 怎么用
- asyc 和 await 就是 promise 的一个语法糖
- 首先声明一个
async function(){}告诉 JS ,这是一个异步函数 - await 只能在 async 函数内部使用,await 后面的内容就是 then 的内容,如:
async function (){
await fn()
console.log('await')
}
//上面的代码也就等于
fn().then(console.log('await'))
- 为什么要使用 await 而不是直接使用 promise?
- 为了让其更像是同步函数
11. 原型链
- 对象.
__proto__=== 其构造函数.prototype - Object.protoype 是所有对象的(直接或间接)原型
- 任何函数.
__proto__=== Funtion.prototype,包括 Object / Array / Function
12. 排序算法
- 冒泡排序
const sort = (arr) => {
for(let i=0;i<arr.length-1;i++){
for(let j=0;j<arr.length-i-1;j++){
if(arr[j] > arr[j+1]){
let temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
}
return arr
}
console.log(sort([5,6,3,2,4,1])) // [1, 2, 3, 4, 5, 6]
- 快速排序
const sort = (arr) => {
if(arr.length <= 1){
return arr
}
let left = []
let right = []
let pivotIndex = Math.floor(arr.length/2)
let pivot = arr.splice(pivotIndex,1)[0]
for(let i=0;i<arr.length;i++){
if(arr[i] < pivot){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return sort(left).concat([pivot], sort(right))
}
console.log(sort([5,6,3,2,4,1])) // [1, 2, 3, 4, 5, 6]
- 计数排序
const sort = (arr) => {
let newArr = []
let hashTable = {}
let max = 0
for(let i=0;i<arr.length;i++){
if(!hashTable[arr[i]]){
hashTable[arr[i]] = 1
}else{
hashTable[arr[i]] += 1
}
if(arr[i] > max){
max = arr[i]
}
}
for(let j=0;j<=max;j++){
if(hashTable[j]){
for(let z=0;z<hashTable[j];z++){
newArr.push(j)
}
}
}
return newArr
}
console.log(sort([5,6,3,2,4,1])) // [1, 2, 3, 4, 5, 6]
13. JS 设计模式
- 模块设计模式
- 原型模式
- 观察者模式
- 发布、订阅模式
- 单例模式
14. Eventloop (事件循环)说一下(简略版本)
浏览器中的事件循环
- JS 是单线程的,分为同步和异步函数,同步函数就是按顺序从头执行到尾,异步函数则是等执行完同步了再来执行的函数,而异步函数都是存储在一个队列中。
- 任务队列又分为宏任务(macro-task)和微任务(micro-task)
- 宏任务和微任务可以理解为,去游乐场排队玩过山车,宏任务要玩,只能乖乖从最后开始排队,一个一个等待,微任务则是可以插队插到下一个
- window 提供的 setTimeout、setImmediate、setInterval 等都是宏任务
- 语法提供的 Promise.then、async/await 等都是微任务
- 首先按宏任务的队列执行第一个宏任务,然后执行该宏任务产生的微任务,若微任务在执行过程中产生了新的微任务,则继续执行微任务,微任务执行完毕后,再回到宏任务的队列中,继续下一轮循环,这个循环的过程,就是 Eventloop,事件循环。
node 中的事件循环
- node 的事件循环分为六个阶段
- timers:执行 setTimeout、setInterval 函数
- I/O callbacks
- idle, prepare
- **poll **
- check:执行 setImmediate() 函数
- close callbacks
- 而 nextTick 是一个独立于 eventLoop 的任务队列。在每一个 eventLoop 阶段完成后会去检查 nextTick 队列。
- 在 node11 之前,因为每一个 eventLoop 阶段完成后会去检查 nextTick 队列,如果里面有任务,会让这部分任务优先于微任务执行
- 在 node11 之后,process.nextTick 是微任务的一种,因此上述代码是先进入 check 阶段,执行一个 setImmediate 宏任务,然后执行其微任务队列,再执行下一个宏任务及其微任务,因此输出为
- 实例:
setImmediate(() => {
console.log('timeout1')
Promise.resolve().then(() => console.log('promise resolve'))
process.nextTick(() => console.log('next tick1'))
});
setImmediate(() => {
console.log('timeout2')
process.nextTick(() => console.log('next tick2'))
});
setImmediate(() => console.log('timeout3'));
setImmediate(() => console.log('timeout4'));
//node11 之前:timeout1 => timeout2 => timeout3 => timeout4 => next tick1 => next tick2 => promise resolve
//node11 之后:timeout1 => next tick1 => promise resolve => timeout2 => next tick2 => timeout3 => timeout4
15. new 的背后做了什么
var obj = {};
obj.__proto__ = Base.prototype;
Base.call(obj);
- 简单来说就是这三行内容
- 第一步,创建一个空对象 obj
- 第二步,把 obj 的
__proto__指向 Base 函数的原型prototype - 第三步,把 this 绑到新的这个对象 obj 上面
16. null 和 undefined 的区别
- null 相当于设置了一个对象,但是内容为空
console.log(typeof null) //object
- undefined 则是设置了一个变量,但没有设置值
console.log(typeof undefined) //undefined
4、React
1. Vue 和 React 的区别,为什么想选 React
- 个人理解:
- Vue 使用的是可变数据,而 React 则强调数据的不可变性,每次渲染都是一个新的数据
- Vue 是通过 template、script、css 来写模板,React 则使用 JSX
- Vue 提供了大量的 API 和模板语法,React 则更倾向于“自由发挥”
- Vue 的脚手架 Vue-cli 可以自行配置,create-react-app 不行
2. 函数组件和类组件更喜欢用哪个
- 更喜欢函数组件,写起来更简单更方便
3. 两种组件的生命周期
- 类组件:
- componentWillMount 在组件挂挂载前执行
- componentDidMount 在组件挂载后执行,Ajax
- componentWillUpdate 在组件更新前执行
- componentDidUpdate 在组件已经更新后执行
- componentWillUnmount 在组件消亡之前执行
- shouldComponentUpdate 手动判断是否要进行组件更新
- 函数组件:
- useEffect(fn(), []) 在第一次渲染组件时执行函数 fn
- useEffect(return fn(), []) 在组件消亡时执行函数 fn
- useEffect(fn()) 在每次组件渲染时都执行 fn
- useEffect(fn(), [n]) 当组件内的 n 更新时,执行 fn
4. 用过哪些 Hooks,useContext 怎么用的
- useState
- useEffect
- useRef
- useReducer
- useContext:全局上下文
- 使用 const Context = createContext(null) 来创建 Context
- 使用 <Context.Provider></Context.Provider> 圈定作用域
- 在 Context.Provider 中把 value 传给包裹中的组件:
<Context.Provider value={{...}}></Context.Provider> - 在作用域内使用
const {state, dispatch} = useContext(Context)来使用 Context 所传递的 value
5. React 子组件如何更改父组件的 state
- 使用 useState,父组件把 setState 方法传给子组件,子组件通过 props.setState 来调用,更改父组件中的 state
6. forwardRef 了解过吗
- 函数组件默认不能传递 ref,类组件可以使用
- 而函数组件想要用 ref 来获取 DOM 元素,就要用 forwardRef
- 用函数组件来使用 forwardRef 时,需要声明组件 = React.forwardRef,同时传递两个参数,props 和 ref,然后就可以在组件内使用 ref
- 示例:
function App() {
const buttonRef = useRef(null);
return (
<div className="App">
<Button ref={buttonRef}>按钮</Button>
</div>
);
}
const Button = React.forwardRef((props, ref) => {
return <button className="red" ref={ref} {...props} />;
});
7. React 的虚拟 DOM 是在生命周期的哪个环节创建真实 DOM 的
- 在 componentDidMount 阶段,把组件插入到真实 DOM 中的
8. React 什么时候会复用 DOM 元素,什么时候会创建新的 DOM 元素
- 前后的 key 如果相同,则会复用,key 不同则会创建新的 DOM 元素
- key 帮助 React 识别哪些元素改变了,比如被添加或删除,一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串
9. React 的 setState 的异步还是同步
- 在 React 中,如果是由 React 引发的事件处理(比如通过onClick引发的事件处理,自身生命周期内)触发时 isBatchingUpdates 为 true,所以不会直接执行更新 state,而是加入了 dirtyComponents,此时是异步的
- 除此之外的事件中,比如,绕过 React 通过 addEventListener 直接添加的事件处理函数,还有通过 setTimeout/setInterval 产生的异步调用。触发时 isBatchingUpdates 为 false,能够直接进行更新,此时是同步的
- 注意:这里所说的同步异步, 并不是真正的同步异步, 它还是同步执行的。这里的异步指的是多个 state 会合成到一起进行批量更新。
- 详细可看:
10. 受控组件和非受控组件
-
受控组件:在 React 中,每当表单的状态发生变化时,都会被写入到组件的 state 中,这种组件在 React 被称为受控组件
- 可以通过在初始 state 中动态设置 value 值
- 每当 value 的值发生变化时,调用 onChange 事件处理器。如果添加了value 而没有添加 onChange 会受到 React 警告
- 事件处理器通过合成事件对象 e 拿到改变后的状态,并更新 state。
- setState 触发视图的重新渲染,完成组件更新
-
非受控组件:
- 表现形式上,React 中没有添加 value 属性的组件元素就是非受控组件。
- 只能通过 defaltValue 来设置初始值,输入期间的变化一概不管,只看最后得到的值
5、其他
1. HTTP 缓存
- 了解 HTTP 缓存,看这篇文章:juejin.cn/post/685512…
2. 讲讲 Webpack (讲的很浅)
- 是模块打包机:它做的事情是,分析你的项目结构,找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言(Scss,TypeScript等),并将其打包为合适的格式以供浏览器使用。
3. Webpack 的 loader 和 plugin
- loader 是加载器,plugin 是插件
- loader 用于加载一个文件,
- 比如 babel-loader 是用来加载高级的 JS 将其变成低版本浏览器支持的 JS;style/css loader 是用来加载 css,将其转变为页面中的 标签,是一个文件打包成一个文件;也可以加载图片文件,对图片进行一定的优化
- 常见 loader:markdown-loader、scss-loader、less-loader、style-loader、css-loader、babel-loader、image-loader
- plugin 用于加强、扩展功能,可能是综合 n 个文件将其打包成一个文件
- 比如 HtmlWebpackPlugin,用于生成一个 HTML 文件
- MiniCssExtractPlugin 用于抽取 CSS 的代码将其转变成一个文件的
- loader 功能更加的单一,专注于转化文件这一个领域,而 plugin 功能更加丰富,不仅局限于资源的加载
4. 如何提高 Webpack 构建速度?转义出的文件过大怎么办?
-
DllPlugin 为更改不频繁的代码生成单独的编译结果,提高编译速度
-
happypack 用多线程进行打包,提高构建速度
-
CommonsChunkPlugin 提取通用模块文件
-
按需加载 import('文件路径')
5. 由于本人之前是做的 SEO,所以也被问到了 SEO 是做什么的
6. 组件化和模块化的区别(个人理解)
- 把重复的代码提取出来合并成为一个个组件,组件可以是 CSS、JS,是可以复用的,独立性强
- 把一个独立且完善的功能的模块独立、封装出来,用多个组件组合而成的一个模块
7. 浏览器渲染原理
- 浏览器渲染页面的过程
- DNS 查询
- TCP 连接
- HTTP 请求即响应
- 服务器响应
- 客户端渲染
- 浏览器对内容的渲染
- 处理 HTML 标记并构建 DOM 树。
- 处理 CSS 标记并构建 CSSOM 树。
- 将 DOM 与 CSSOM 合并成一个渲染树。
- 根据渲染树来布局,以计算每个节点的几何信息。
- 将各个节点绘制到屏幕上。
7. MVC 和 MVVM 的区别
-
MVC 是 Moder-View-Controller 的简写
- View:应于布局文件
- Model:业务逻辑和实体模型
- Controllor:对应于Activity
-
MVVM 是 Model-View-ViewModel的 简写,将“数据模型数据双向绑定”的思想作为核心
- ViewModel 是一个同步 View 和 Model 的对象
- 在 MVVM 架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,Model 和 ViewModel 之间的交互是双向的, 因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到 View 上。
- 因此开发者只需关注业务逻辑,不需要手动操作 DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。
-
mvc 和 mvvm 其实区别并不大。都是一种设计思想。主要就是 mvc 中 Controller 演变成 mvvm 中的 viewModel。mvvm 主要解决了 mvc 中大量的 DOM 操作使页面渲染性能降低,加载速度变慢,影响用户体验。和当 Model 频繁发生变化,开发者需要主动更新到 View 。