2020 初级前端面试题(React )

1,597 阅读14分钟
  • 近期开始找工作,主要想找以 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: 1flex-grow: 2flex-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 被称为受控组件

    1. 可以通过在初始 state 中动态设置 value 值
    2. 每当 value 的值发生变化时,调用 onChange 事件处理器。如果添加了value 而没有添加 onChange 会受到 React 警告 
    3. 事件处理器通过合成事件对象 e 拿到改变后的状态,并更新 state。 
    4. setState 触发视图的重新渲染,完成组件更新
  • 非受控组件:

    • 表现形式上,React 中没有添加 value 属性的组件元素就是非受控组件。
    • 只能通过 defaltValue 来设置初始值,输入期间的变化一概不管,只看最后得到的值

5、其他

1. HTTP 缓存

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. 浏览器渲染原理

  • 浏览器渲染页面的过程
    1. DNS 查询
    2. TCP 连接
    3. HTTP 请求即响应
    4. 服务器响应
    5. 客户端渲染
  • 浏览器对内容的渲染
    1. 处理 HTML 标记并构建 DOM 树。
    2. 处理 CSS 标记并构建 CSSOM 树。
    3. 将 DOM 与 CSSOM 合并成一个渲染树。
    4. 根据渲染树来布局,以计算每个节点的几何信息。
    5. 将各个节点绘制到屏幕上。

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 。

6、开放题

1. 自学前端过程中遇到了哪些问题

2. 通过哪些途径来自学的

3. 对前端工程化的理解

4. 最近有在看什么新的技术呢,通过哪些方式去了解

5. 对自己的职业规划

6. 是怎么写博客的

7. 对加班怎么看

8. 觉得自己的优势在哪里