[2022]最新🔥前端高频面试题总结(一)
前言
秋招已经来临了,笔者作为一名23届毕业的大三学生,在这周内已经面试完4-5家公司了。所以趁着周末,总结一下这周面试碰到值得记录的题目。作为分享,也作为自我的总结。
一、js 系列
1. 说一说es6的新特性
可以先列举出来自己知道的,不用一一展开说,面试官一般会在你说的里面挑出几个让你具体说说。
- let const
- 块级作用域
- 箭头函数
- class
- 模板字符串
- 展开运算符
- 结构赋值
- 新增的数组API和对象API
- promise
- proxy
- Symbol、bigInt
- Set、Map/WeapMap
这里分享几个最常被问到的:
1.1 let const的区别
-
const
是常量,没有变量的提升,声明的变量只能在其块级作用域内使用。对于普遍类型声明之后值不可改变,复杂类型声明后值可以改变,但是类型不可改变,如obj.a
可以改变 -
let
没有变量提升,在块级作用域内使用,不可重复声明,声明后值可以改变 -
另外可以再说说与
var
的区别:var
是es5
的特性,变量提升至全局,会挂载到window
上,造成全局污染,它是可以被重复声明的
1.2 箭头函数与普通函数的区别
- 箭头函数是匿名函数,而普通函数可以匿名也可以不匿名
- 没有自己独立的
this
,内部this是上层作用域的this。箭头函数的this永远不会变,call
、apply
、bind
也无法改变。 - 没有原型prototype,
__proto__
,没有自己的arguments - 不能作为构造函数,原因是构造函数是通过
new
来实例化对象的,而new
的过程是这样的:- 在内存中创建一个新的空对象;
- 让this指向这个新的对象;
- 执行构造函数里面的代码,给这个新对象添加属性和方法;
- 返回这个新对象(所以构造函数里面不需要return)
function mynew() {
// 1. 新建一个实例对象
let obj = {};
// 2. 获得构造函数
let con = [].shift.call(arguments);
// 3. 链接原型
obj.__proto__ = con.prototype;
// 4. 绑定this,执行构造函数
let res = con.apply(obj,arguments)
// 5. 返回新对象
return typeof res === 'object'?res:obj;
}
了解了new
一个实例对象的过程后,再结合箭头函数与普通函数区别的2、3点,就能够解释清楚为什么箭头函数不能作为构造函数啦~
1.3 新增数组API和对象API
-
新增数组API:
- arr.flat 数组扁平化
- Array.from() 将数组、类数组转为数组
- Array.of 将数值转为数组
- find() 找到符合要求的第一个元素值
- findIndex() 找到符合要求的第一个元素的下标
- fill() 填充一个数组
- includes() 数组是否包含某特定值
- Array.isArray() 判断是否是数组
-
新增对象API
- Object.assign() 将可枚举属性从一个对象复制到另一个对象,浅拷贝,redux中使用
- Object.freeze(obj) 冻结对象
- Object.keys 返回自身可枚举属性,不包括原型链上的
- Object.entries() 返回对象可枚举键值对
1.4 Map/WeapMap的区别
Map
可以接受任何类型作为key,weapmap
只接受对象作为key。map 通过两个数组分别存放键和值,容易导致内存泄漏。- map可以实现
LRU cache
,weakMap可以用于深拷贝。 - weapmap 对键是弱引用,有垃圾回收机制,不可枚举。
详细可以查看MDN:WeapMap
2. 说说js实现继承的几种方式
如下两个构造函数,一个是‘动物’对象构造函数,另一个是‘猫’对象构造函数。我们需要让‘猫’继承‘动物’
function Animal(){
this.species = "动物";
}
function Cat(name,color){
this.name = name;
this.color = color;
}
- 2.1 构造函数绑定 使用call或apply方法,将父对象的构造函数绑定在子对象上。即只需要在子对象构造函数中加上一句:
Animal.apply(this, arguments);
- 2.2 继承父类的实例对象
让"猫"的prototype对象,指向一个Animal的实例,那么所有"猫"的实例,就能继承Animal了。
我们让‘猫’的prototype对象指向‘动物’的实例对象后,
Cat.prototype.constructor
会错误的指向Animal
;所以这里需要手动的修正。
Cat.prototype = new Animal();
Cat.prototype.constructor = Cat;
- 2.3 直接继承父类prototype
第三种方法是对第二种方法的改进。我们也可以让Cat()跳过 Animal(),直接继承Animal.prototype。
Cat.prototype = Animal.prototype;
Cat.prototype.constructor = Cat;
- 2.4 组合继承方式 前面一二种方式的结合
function Animal(){
this.species = "动物";
}
Animal.prototype.say = function () {
alert(this.species);
};
function Cat(name,color){
this.name = name;
this.color = color;
Animal.apply(this, arguments);
}
Cat.prototype = new Animal();
- 2.5 寄生组合继承方式
function Animal(){
this.species = "动物";
}
Animal.prototype.say = function () {
alert(this.species);
};
function Cat(name,color){
this.name = name;
this.color = color;
Animal.apply(this, arguments);
}
Cat.prototype =Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
3. 说说 防抖、节流的区别
- 3.1 防抖
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。如可以对获取
input
输入内容的函数上使用防抖功能。这里可以结合自己做的demo
或者自己在使用在项目上的实例去说,效果会更好。
function debounce(func,time){
let timer
return function(){
clearTimeout(timer)
let args = arguments
timer = setTimeout(() => {
func.apply(this,args)
},time)
}
}
- 3.2 节流
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。如可以在对监听
scroll
的函数上使用节流。
function throttle(fun,time){
let t1 = 0; // 初始时间
return function(){
let t2 = new Date(); // 当前时间
if(t2-t1>time){
fun.apply(this,arguments)
t1=t2
}
}
}
4. 说说js的数据类型
- 基本数据类型
number
,string
,null
,undefined
,boolean
;- 复杂数据类型
Object
(Array
,function
,Date
...)- ES6新增数据类型
Symbol
,bigInt
你以为说完这些就完了?面试官一般会继续问出下面的内容:
4.1 基本数据类型和复杂数据类型区别在哪里?
基本数据类型是存在栈内存中;复杂数据类型是存在堆内存中,而栈内会保存一个地址引用,指向存放在堆内存中的地址,因此通过
==
赋值的引用数据类型是指向同一块堆内存空间的。
4.2 判断类型的方式有哪些?
typeof
适合对基本类型的判断
- 特殊的:typeof null = Object 。因为在js早期,使用32位系统,低三位来存储变量的类型,000表示为对象,null表示为全0,所以将它错误的判断为 object
- type NaN = number
Object.prototype.toString.call()
一般用于复杂数据类型的判断 Object.prototype.toString可以返回当前调用者的对象类型,所以一般用于复杂数据类型的判断。instanceof
判断复杂数据类型 通过判断左侧对象的原型链(__proto__
)上是否能找到右侧对象的prototype
,来确定 instanceof 返回值。
- 特殊的:Array instanceOf Array = false
- Object instanceOf Object = true
instanceof
具体实现如下:
function instaceofFc(l,r) {
let lpro = l.__proto__;
const rpro = r.prototype;
while (true) {
if(lpro===null){
return false;
}
if(lpro ===rpro){
return true;
}
lpro= lpro.__proto__;
}
}
4.3 说说BigInt和Symbol
-
Bigint
和Symbol
都是通过函数来创建的,是es6引入的独有的为了解决相应问题的数据类型。 -
Bigint
是为了解决大数相加问题,Symbol
是为了标志某些独一无二的东西。 它们的定义都不需要newlet b = Bigint('11'); let s = Symbol('foo');
-
Symbol
常用于定义对象的key
,当多人开发时可以使用Symbol作为key定义对象的属性,这样可以一定程度上避免属性名冲突的问题。 -
以
Symbol
作为key
的属性,无法通过Object.keys()
和Object.getOwnPropertyNames()
得到,需要通过Symbol,Object.getOwnPropertySymbols()
得到。
5. 精确丢失问题
-
5.1 为什么0.1+0.2!=0.3 js是弱类型语言,在计算时会将浮点数转化为二进制数进行计算,因为js中不能无限的存储小数点后的数,最多52位,所以计算出来的值有误差,最后再将结果二进制转换为浮点数时结果就会有误差。
-
5.2 如果需要判断0.1+0.2是否等于0.3要怎么做?
- 将小数转换为整数再进行相加判断。
- 用
0.3-(0.1+0.2)
判断结果是否小于number的最大安全数
6. apply call bind 区别
apply
、call
、bind
都可以为函数指定this
先看看三者的使用方法:
// apply
func.apply(thisArg, [argsArray])
// call
fun.call(thisArg, arg1, arg2, ...)
// bind
const newFun = fun.bind(thisArg, arg1, arg2, ...)
newFun()
-
apply
和call
就是传参方式不一样,apply
参数以一个数组的形式传入。但是两个都是会在调用的时候同时执行调用的函数。bind
则会返回一个绑定了this的函数。接下来看看怎么实现的吧~
-
实现
apply
:
const myApply = function(context,args) {
// 不传this默认指向window
context = context || window;
args = args ? args : [];
// 给context新增一个独一无二的属性以免覆盖原有属性
const key = Symbol();
// this 指向调用它的对象
context[key]=this;
// 通过隐式绑定的方式调用函数
const result = context[key](...args);
// 删除添加的属性
delete context[key];
return result
}
- 实现
call
(与apply
相比获取参数的方式不一致而已):
const myCall = function(context,...args) {
context = context || window;
args = args ? args : [];
const key = Symbol();
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
}
- 实现
bind
(这里借用apply
方法):
const myBind = function(context,...args){
const self = this;
args = args?args:[];
return function(...newArgs) {
return self.apply(context,[...args,...newArgs])
}
}
7. ajax fetch axios的区别
都用于发送网络请求
-
Ajax
是一个技术统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新。xmlHttpRequest
是ajax的一种实现方式。存在回调地狱问题。 -
Fetch
是es6提出的,是一个 API,它是真实存在的,它是基于 promise 的,采用.then的链式调用方式处理结果,解决了回调地狱问题。 -
Axios
是一个基于promise
封装的网络请求库,它是基于XHR
进行二次封装。是xhr的一个子集。
实现一个AJAX
请求:
function myAjax (options) {
// 1. 创建异步对象
const xhr = new XMLHttpRequest();
const url = options.url;
const type = (options.type || 'GET').toUpperCase();
const sendData = options.data;
// 2. 设置 请求行 open(请求方式,请求url):
xhr.open(type,url,true);
// 3. 设置请求体 send()
if(type === 'GET') {
xhr.send();
} else {
xhr.send(sendData);
}
// 4. 让异步对象接收服务器的响应数据
xhr.onreadystatechange = function() {
if(xhr.readyState === 4) {
if(xhr.status>=200 && xhr.status<300) {
options.success && options.success(xhr.responseText,xhr.responseXML);
} else {
options.error && options.error(xml.status)
}
}
}
}
8. JS事件机制
- JS的事件机制有:事件冒泡(微软)、事件捕获(网景) 捕获过程 -> 目标阶段 -> 冒泡阶段
addEventListenter
第三个参数: true 事件捕获 false 事件冒泡 (默认)- 阻止事件冒泡
- 给子级加
event.stopPropagation()
- 在事件处理函数中返回
false
event.target==event.currentTarget
,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;
- 给子级加
- 阻止默认事件
-
event.preventDefault()
-
return false
-
二、css系列
1. 说说 position的属性以及分别相对什么定位
-
relative: 元素的定位永远是相对于元素自身位置的,和其他元素没关系,也不会影响其他元素。不脱离文档流。
-
fixed: 元素的定位是相对于
window
(或者 iframe)边界的,和其他元素没有关系。但是它具有破坏性,会导致其他元素位置的变化。脱离文档流定位。 -
absolute: 它会找到一个离它最近的设置了
position:relative/absolute/fixed
的元素定位,如果没找到,就以浏览器边界定位。脱离文档流定位。 -
sticky 在
position:relative
与position:fixed
定位之间切换。元素定位表现为在跨越特定阈值前为相对定位,之后为固定定位。 -
inherit 继承父元素的定位值进行定位。
2. 实现 三栏布局 说出多种方法
5.1 flex实现
外层盒子设置dispaly:flex;
左右两个盒子设置width:100px;
中间盒子设置flex:1;
5.2 grid实现
外层盒子设置grid-template-columns: 100px 1fr 100px;
5.3 浮动 + calc实现
左侧盒子设置float:left;
右侧盒子设置float:right;
中间盒子设置float:left; width:calc(100% - 100px);
5.3 浮动 + margin实现
左侧盒子设置float:left;
右侧盒子设置float:right;
中间盒子设置margin:0 100px
三、React 系列
1. React中组件和组件之间的有哪些通信方式
- 父组件给子组件传递信息通过
props
,子组件像父组件报告可以使用函数- 使用 React 中的两个内置hooks,
useContent
和useReducer
- 使用
Redux
实现通信,而且还可以跨页面级别组件通信
2. React 中为什么不能在if 循环里写hooks
-
react用链表来严格保证hooks的顺序。hook 相关的所有信息收敛在一个 hook 对象里,而 hook 对象之间以单向链表的形式相互串联。
-
以useState为例,在它的实现过程之有
mount
和update
两个阶段mountState
(首次渲染)阶段会按useState声明的顺序构建出一个链表并渲染;updateState
阶段会按顺序去遍历之前构建好的链表,取出对应的数据信息进行渲染。hooks 的渲染是通过“依次遍历”来定位每个 hooks 内容的。如果前后两次读到的链表在顺序上出现差异,那么渲染的结果自然是不可控的。
-
必须按照顺序调用从根本上来说是因为 useState 这个钩子在设计层面并没有“状态命名”这个动作,也就是说你每生成一个新的状态,React 并不知道这个状态名字叫啥,所以需要通过顺序来索引到对应的状态值
3. React 组件map中key的作用是什么?
- key有利于提高程序运行和渲染效率
- react的虚拟dom使用diff算法比对更改前后发生的最小差异,再对真实dom进行更改 我们用key的真实目的是为了标识在前后两次渲染中元素的对应关系,防止发生不必要的更新操作。
- 那为什么不能用index代替key呢? 如果我们用index来标识key,数组在执行插入、排序等操作之后,原先的index并不再对应到原先的值,那么这个key就失去了本身的意义
4. setState 是同步还是异步的?
setState
有时是同步的有时是异步的。为了避免不必要的重新渲染、提升性能,react 提出分批更新的机制,因此出现了异步的情况。- 目前,在事件处理函数内部的
setState
是异步的。这些setState
会等到浏览器事件结束的时候(所有在组件的事件处理函数内调用的setState()
完成之后),再统一的进行更新。 - 那如何让setState 同步更新呢?
使用
componentDidUpdate
或者setState
的回调函数(setState(updater, callback)
),这两种方式都可以保证在应用更新后触发。
最后
以上是我对上一周面试出现高频题目的总结和分享,后面将会对本篇文章持续更新~ 希望我和正在看文章的你都能拿到心仪的offer~ 另外我遇到的其他面试问题也在仓库中记录:gitee.com/fish-ball-f…
转载自:https://juejin.cn/post/7133913334763651103