前言
最近受疫情的影响, 许多公司都暂停了招人的计划, 但是现在疫情都控制的差不多了, 所以公司对人才的需求肯定又掀起一片浪潮, 由于本人也是对前端有着强烈的兴趣, 所以, 总结一下以往所遇见的面试题, 希望能够对大家有所帮助.
HTML/CSS
1、div垂直居中的方式
父元素相对定位, 子元素绝对定位(margin: auto)
<div class="parent">
<div class="child"></div>
</div>
-------------
.parent{
width: 500px;
height: 500px;
background-color: royalblue;
position: relative;
}
.child {
width: 200px;
height: 200px;
background-color: red;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
}
父元素相对定位, 子元素绝对定位(定位子元素上和左50%, 相对自身偏移X、Y各-50%)
<div class="parent">
<div class="child"></div>
</div>
-------------
.parent{
width: 500px;
height: 500px;
background-color: royalblue;
position: relative;
}
.child {
width: 200px;
height: 200px;
background-color: red;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
利用flex布局
<div class="parent">
<div class="child"></div>
</div>
-------------
.parent{
width: 500px;
height: 500px;
background-color: royalblue;
display: flex;
justify-content: center;
align-items: center;
}
.child {
width: 200px;
height: 200px;
background-color: red;
}
2、rem布局
rem布局就是通过js来控制屏幕的等份,把屏幕分成N等份,然后每一等份则就是1rem,font-size就是屏幕的物理像素/N
3、圣杯布局和双飞翼布局
4、flex布局
5、用CSS绘制一个三角形
.child {
width: 0;
height: 0;
border-right: 100px solid red;
border-left: 100px solid transparent;;
border-top: 100px solid red;
border-bottom: 100px solid transparent;
}
JS
1、闭包
面试官问到这个部分一般都是考察应聘者对变量作用域的,本人对闭包的理解呢就是一个有外部变量的函数就是闭包.
function init(parameter){
var a = parameter
function closure(){
console.log(a)
}
closure();
}
init(1);
优点: 1. 可以在内存中存放一个长期变量。 2. 避免全局变量污染。 3. 存在私有成员。
缺点: 1. 增加内存使用量 2. 使用不当容易造成内存泄漏
2、防抖和节流
- 防抖
防抖指的是当持续触发事件时,一定时间内没有再触发事件,事件处理函数才会执行一次,在设定时间内,有一次触发事件,就重新计算时间. 下面是封装的一个防抖函数, 可接受一个函数和响应时间
function debounce(fn, time){
var timeout = null
return function () {
clearTimeout(timeout)
timeout = setTimeout(() => {
fn()
}, time)
}
}
- 节流
节流指的持续触发事件时,保证在一定时间内只调用一次事件处理函数,相当于定时器. 下面是封装的一个节流函数, 可接受一个函数和响应时间
function throttle(fn, time){
var execute = true
return function () {
if(!execute) return
execute = false
setTimeout(() => {
fn()
execute = true
}, time)
}
}
3、URL从开始到展示
4、深拷贝
- JSON.parse(JSON.stringify())
let obj = {
a: 1,
b: { c: 2 }
}
let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2)
- 递归方法
var sourceObj = {
a: 1,
b: {
c: {
d: 2
},
e: [1,2,3]
}
}
function deepClone(targetObj, sourceObj){
var objNames = Object.getOwnPropertyNames(sourceObj) // 获取源对象所有属性
for(var i=0; i < objNames.length; i++){
var objValue = Object.getOwnPropertyDescriptor(sourceObj, objNames[i]) // 获取源对象下的所有值
if(typeof objValue.value === 'object' && objValue.value !== null) { // 如果不做此判断已经完成浅拷贝
var newObj = Array.isArray(objValue.value) ? [] : {};
deepClone(newObj, objValue.value); // 将新对象newObj作为引用对象带入递归函数中
Object.defineProperty(targetObj, objNames[i], {
enumerable: objValue.enumerable,
writable: objValue.writable,
configurable: objValue.configurable,
value: newObj
});
continue;
}
Object.defineProperty(targetObj, objNames[i], objValue); // 将源对象下的值赋给目标对象
}
return targetObj
}
var targetObj = deepClone({}, sourceObj);
sourceObj.b.c.d = 10
console.log(targetObj.b.c.d) // 2
- 第三方的JS库, 如lodash
lodash的属性cloneDeep也可以完成深拷贝
5、浅拷贝
- Object.assign()
var sourceObj = {
a: 1,
b: {
c: {
d: 2
},
e: [1,2,3]
}
}
var targetObj = Object.assign({}, sourceObj);
targetObj.b.c.d = 3;
console.log(targetObj.b.c.d); // 3
注: 若对象只有一层, 则此方法是深拷贝
6、跨域
由于浏览器的同源策略导致了跨域, 那同源策略是什么呢, 同源策略是一种约定, 是浏览为了防止受到XSS、CSFR等攻击, 如果两个通讯地址的协议、域名、端口号相同, 那么这两个地址通讯将被浏览器视为不安全的.那如何解决跨域呢
- JSONP
利用script标签不受同源策略限制的特性, 缺点只限get请求
- iframe + document.domain
页面通过js强制设置document.domain为基础主域,就实现了同域。
- postMessage跨域
postMessage事件可以解决页面和其打开的新窗口的数据传递、多窗口之间消息传递等问题,可解决不同域的通信
- 服务器代理
常见服务器代理, nginx
7、call, apply, bind
这三个都是改变函数的执行时的this指向, call()方法可以接收一个参数列表, apply()方法可以接收一个数组,bind()方法是将this绑定在这个函数上,但是不会立即执行
let a = {
b: 1
}
function init(item) {
console.log(item)
console.log(this.b)
}
init.call(a, 'string')
init.apply(a, ['arr'])
区别: 1、接收参数不同, call是接收参数列表, apply是接收一个数组 2、call、apply与bind, 前两个是立即执行, 而bind是只是改变函数的this指向
8、存储方式
- cookie
一般是由服务器生成, 可以设置过期时间, 可携带到请求中, 储存大小为4k
- localStorage
储存于浏览器中, 若非手动删除, 则一直存在, 储存大小为5M
- sessionStorage
储存在浏览器中, 关闭页面则清除, 储存大小为5M
- indexDB
可一直存在, 储存大小不限, 还可以indexDB做页面缓存, 后期会专门出一遍关于页面缓存的文章
9、promise与setTimeout
setTimeout(function() {
console.log(1)
}, 0);
new Promise(function executor(resolve) {
console.log(2);
for( var i=0 ; i<10000 ; i++ ) {
i == 9999 && resolve();
}
console.log(3);
}).then(function() {
console.log(4);
});
console.log(5);
// 结果 2 3 5 4 1
这是在笔试经常会遇到的题, 首先你要知道的是事件的执行顺序, 同步大于异步, 而在这道题中, promise > .then > setTimeout. 根据这个我们来分析一下这道题.
- 整个函数的执行是同步的, 那么就是从上执行到下的, 遇到setTimeout, 将setTimeout放入异步队列中, 等待执行, 然后遇到promise(promise本身是同步的, 但是它的then和catch方法是异步的), 所以首先会在控制台输出2 .
- 函数内部都是从上到下执行的, 但是then方法是异步的, 所以暂时又把then()放入异步队列中, 继续向下执行, 然后输出3.
- 这个时候函数的执行已经完成, 但是外部的同步继续向下执行, 然后输出5,
- 这个时候同步已经执行完成, 执行异步函数, 由于then>setTimeout, 所以, 先执行then(), 输出4,
- 最后执行setTimeout, 输出1
有关promise方面的知识,后期会专门出一篇有关于promise的文章
VUE
1、vue常用的一些指令
v-text、v-html、v-if、v-else、v-else-if、v-for、v-show、v-on、v-bind、v-model等具体用法可参考VUE官网
2、v-model的原理
v-model其实就是vue一语法糖,主要使用于表单元素的双向绑定, 在子组件中可自动获取value和change函数, 类似antd中的form.
3、怎么理解MVVM
MVVM是Model–View–ViewModel的简称, Model代表数据模块, 主要是处理数据和业务逻辑, View代表视图, 主要是视图的展示, ViewModel相当于连接Model与View的桥梁, 在MVVM的框架下,View和Model是没有关联的, 主要是ViewModel将两者相结合, 所以ViewModel是双向的,Model的变化会影响View的展示, 而View的的数据变化也会同步在Model中,如下图所示.
MVVM的好处就是View与Model都是相对独立存在的, 开发和设计人员可分开做属于自己的模块. 可以将视图逻辑绑定在ViewModel, 多次渲染. 在ViewModel可绑定多个不同的View, 当View发生变化时, Model可不变, Model变化时, View也可以不变.
4、vue有哪些生命周期
- 各个生命周期
beforeCreate: 组件实例创建之前, data与props还不能使用
creted: 组件实例完全被创建, Dom还未创建, 但data和props生效
beforeMount: Dom被挂载之前, render被调用
mounted: 实例初始化完成, Dom完全被挂载
beforeUpdata: 组件更新之前, data更新, 但是并没有同步页面
updataed: 组件更新完成, 同步页面
beforeDestroy: 销毁实例之前, 此时实例仍然完全可用
destroyed: 销毁实例之后, 实例完全被销毁
- 生命周期示意图
5、谈谈Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式的管理方式储存应用程序的各种状态.主要有以下几个模块
- State:单一状态树, 里面包含了所有的状态的初始状态
- Getter:就像计算属性一样,getter的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
- Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
- Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
- Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
- Vuex示意图
REACT
1、props与state的区别
props是不可改变的,在每个组件都可以使用,通过redux传递或者父子组件传递,state是组件内的状态,在组件创建完毕后,可以通过this.setState改变其状态。
2、this.setState同步和异步的特性
this.setState在React中是个异步的API, 主要是用来改变State的状态, 因为这个API是异步的, 所以说在函数中, 如果调用了这个API, 不会立马得到改变后的值.
constructor(props){
super(props)
this.state = {
value: 0
}
this.getValue = this.getValue.bind(this)
}
getValue(){
const { value } = this.state
console.log(value) // 0
this.setState({ value: value + 1 })
console.log(value) // 0
}
那如果在函数中多次调用会怎么样呢, 结果是一样的, 还是没办法立即获取到state的值, 即使多次调用this.setState, 也只是执行一次, 而且执行是最后一次.
getValue(){
const { value } = this.state
console.log(value) // 0
this.setState({ value: value + 1 })
this.setState({ value: value + 2 })
this.setState({ value: value + 3 })
console.log(value) // 0
}
那this.setState改变state的值后,能不能直接读取更新后的state值呢, 答案是肯定的, 因为this.setState可以接收回调函数, 所以我们可以在this.setState中传回调函数.
getValue(){
const { value } = this.state
console.log(value) // 0
this.setState((prevState) => ({ value: prevState.value + 2 }), () => {
console.log(this.state.value) // 2
})
console.log(value) // 0
}
3、react的生命周期
实在是有点多, 而且更新16版本以后生命周期有变化, 所以这个地方分享给大家一篇比较详细的文章吧. 参考文档
4、redux的工作原理
Redux是react的状态管理库, 这玩意说起来确实有点多, 我给大家分享一篇吧, 这对redux有比较详细的解释. 参考文档
5、redux与mobx的区别
Redux和Mobx都可以做为React的状态管理库, 那两者有什么区别的, 优缺点又如何呢
- Redux遵循函数型编程思想, Mobx遵循面向对象编程思想.
- Redux是单一状态管理库, Mobx是多个独立的状态管理库.
- Redux需要手动设置监听状态的变化, Mobx可自动监听.
- Redux改变状态需在原来状态的基础上返回新的状态, Mobx可直接更改状态.
- Redux对typescript支持困难, Mobx完美支持typescript
6、react的通信方式
通信方式无非就是父子通信, 兄弟通信, 可通过props/context/redux/mobx等实现通信, 因为这个属于比较基础一点的东西, 而且比较简单,不仔细说了. 参考文档
7、谈谈Hooks
Hooks是react推出来的新特性.
- 优点:
- 面向函数型编程
- 不用面对生命周期
- 不用在为This而烦恼.
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
8、函数型组件和类组件有什么区别
- 函数型组件面对Function编程, 类组件面对Class编程.
- 函数型组件没有维护本地状态的变量(但是Hooks有), 类组件有维护本地状态的变量(state).
- 函数型组件没有生命周期(Hooks有, useEffect), 类组件有生命周期,可在不同周期做不同的操作
- 函数型组件注重UI的渲染, 类组件注重逻辑与显示.
- 函数型组件不需要声明类和绑定this, 只接收一个props, 类组件需要声明类和绑定this的作用域.
9、hooks怎么优化
Hooks的优化可以分为三类, useMemo、useCallback、memo
- useMemo
优化变量, 当依赖变量没有发生变化时, 不会触发更新
- 当没有使用useMemo时
- 父组件
export default () => {
const [value, setValue] = useState(0)
const [testValue, setTestValue] = useState(1)
let obj = {
value,
testValue
}
const change = () => {
setTestValue((prev) => {
let count = prev + 1
console.log(count, '这是testValue')
return count
})
}
return (
<div>
<Hooks parameter={obj} /> // 传递变量时没有使用useMemo
<Button onClick={change} >改变testValue</Button>
</div>
);
}
- 子组件
export default function Hooks(props) {
const { parameter } = props
console.log(parameter)
return (
<div></div>
)
}
可以看出每次点击时, 都会响应式的更新子组件的数据.
- 使用useMemo时
export default () => {
const [value, setValue] = useState(0)
const [testValue, setTestValue] = useState(1)
let obj = {
value,
testValue
}
const change = () => {
setTestValue((prev) => {
let count = prev + 1
console.log(count, '这是testValue')
return count
})
}
return (
<div>
<Hooks parameter={useMemo(() => (obj), [value])} />
<Button onClick={change} >改变testValue</Button>
</div>
);
}
- 子组件
export default function Hooks(props) {
const { parameter } = props
console.log(parameter)
return (
<div></div>
)
}
2. useCallback使用value作为依赖变量, 每次点击更新testValue时, 并不会更新子组件中的值, 此时需依赖value的变化才会更新传入的parameter
3. memo优化函数, 只初始化一次函数, 依靠变量的状态改变决定是否重新渲染, 与useMemo原理相同
优化组件, 与类组件的PureComponent相似, 但memo只适用于函数型组件, 如子组件的变量并没有更新, 那将不会重新渲染子组件. 例子如上诉使用useMemo时的父组件, 但子组件需要做出更新
- 父组件
export default () => {
const [value, setValue] = useState(0)
const [testValue, setTestValue] = useState(1)
let obj = {
value,
testValue
}
const change = () => {
setTestValue((prev) => {
let count = prev + 1
console.log(count, '这是testValue')
return count
})
}
return (
<div>
<Hooks parameter={useMemo(() => (obj), [value])} />
<Button onClick={change} >改变testValue</Button>
</div>
);
}
- 子组件
function Hooks(props) {
const { parameter } = props
console.log(parameter)
return (
<div></div>
)
}
export default memo(Hooks) // 使用memo包裹起来
结果, 与使用useMemo时的父组件打印出的结果对比, 子组件只打印一次, 所以, 子组件的变量并没有更新时, 不会再次渲染.
10、虚拟DOM与真实DOM
虚拟dom是为了用来提升性能的,它最大优势是diff算法,首先,一个普通的对象,包含tag,props,children,把一段html 代码转化成一个对象,就是虚拟dom,虚拟dom提升性能的点就是在于dom发生变化的时候,可以通过diff算法对比JavaScript 原生对象,然后只针对变化的dom进行操作,而不是更新整个视图 子节点做比对.
key的产生:如果子节点只是变换了位置,那么diff算法就会替换掉所有的子节点,重新将子节点的虚拟dom转换成真实的dom,十分消耗性能,所以对子节点增加key,通过key的对比,来判断子节点是否移动了,具体过程是,首先对新的节点进行重排,先进行相同节点的diff,最后把子节点按照新的节点重新排序。
基础算法
1、冒泡排序
原理:从第一个元素开始找,与每相邻元素做比较, 若前者比后者大, 则交换位置, 每遍历一次可找到这次遍历的最大值.
function bubbleSort(arr){
var len = arr.length
for (var i = 0; i < len - 1; i++){
for (var j = 0; j < len - i - 1 ; j++){
if (arr[j] > arr[j + 1]){
var temp = arr[j]
arr[j] = arr[j+1]
arr[j+1] = temp
}
}
}
return arr;
}
console.log(bubbleSort([1,45,64,52,32,2,6,78])) // [1, 2, 6, 32, 45, 52, 64, 78]
2、快速排序
原理: 选择一个数作为基准,可选择第一个元素,也可以选择中间元素,将比这个数大的放在右边, 比这个数小的放在左边, 再对左右两边重复比较, 直到不能只剩一个元素.
function quickSort(arr) {
if (arr.length < 2) return arr // 只剩1个元素,不能分割
let left = [], right=[], mid=arr.splice(Math.floor(arr.length/2),1)
for(var i=0;i<arr.length;i++){
if(arr[i]<mid){
left.push(arr[i])
}else{
right.push(arr[i])
}
}
return quickSort(left).concat(mid, quickSort(right)) // 采用递归继续调用
}
console.log(quickSort([1,45,32,64,52,78,2,32,6,6,78])) // [1, 2, 6, 6, 32, 32, 45, 52, 64, 78, 78]
提示: 排序算法还包括有:希尔排序, 插入排序, 归并排序等,但面试中一般对以上两种考察的多.
3、将对象转成一个树型结构
这是我之前遇到的一个面试题, 题意是,将对象转成树型结构(pid为父级id, 为0表示顶级), 形式如{id:1,pid:0,children:[...]}
let arr = [
{id: 1, pid: 0},
{id: 2, pid: 1},
{id: 3, pid: 2},
{id: 4, pid: 4},
{id: 5, pid: 1},
{id: 6, pid: 3},
{id: 7, pid: 3},
{id: 8, pid: 0}
]
function tree(data){
// 定义一个临时对象
let temp = {};
let newData = []
data.forEach(item => {
// 将数组转化为对象
temp[item.id] = item;
});
data.forEach(item => {
let parent = temp[item.pid];
if(parent) {
(parent.children || (parent.children = [])).push(item);
} else {
newData.push(item);
}
});
return newData
}
console.log(tree(arr))
4、数组去重
数组去重的方法有很多啊, 同样, 封装一个函数分享给大家, 这个可以普通的数组去重, 但是如果数组内有引用类型, 想针对引用类型去重, 则需要指定key去重
function uniqueArr(arr = [], key){
if(key) {
// 如果数组为引用类型数组, 可以指定key去重
let hash = {}
return arr.reduce((prev, next) => {
!hash[next[key]] && (hash[next[key]] = true) && prev.push(next)
return prev
}, [])
}else {
return [...new Set(arr)]
}
}
let arr = [
{ name: 'huxiaolei', age: '24' },
{ name: 'huxiaolei', age: '25' },
{ name: 'huxiaolei', age: '26' }
]
console.log(uniqueArr(arr, 'huxiaolei')) // 如果数组为引用类型数组, 可以指定key去重
// [{name:'huxiaolei',age:'24'}]
console.log(uniqueArr([1,45,32,64,52,78,2,32,6,6,78])) // 普通数组去重
// [1, 45, 32, 64, 52, 78, 2, 6]
5、递归
递归的原理就是在本函数中调用本函数, 但是需要结束条件, 这个部分本人在上述的深拷贝、快速排序中都有实现, 所以就不多叙述了
最后
本人是第一次写文章, 若文中有任何不足, 欢迎指正. 同时也祝愿各位正在面试的同学早日找到工作, 疫情很快就会过去, 也祝愿我们的祖国越来越繁荣昌盛