likes
comments
collection
share

[2022]最新🔥前端高频面试题总结(一)

作者站长头像
站长
· 阅读数 55

前言

秋招已经来临了,笔者作为一名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的区别:vares5的特性,变量提升至全局,会挂载到window上,造成全局污染,它是可以被重复声明的


1.2 箭头函数与普通函数的区别

  1. 箭头函数是匿名函数,而普通函数可以匿名也可以不匿名
  2. 没有自己独立的this,内部this是上层作用域的this。箭头函数的this永远不会变,callapplybind也无法改变。
  3. 没有原型prototype,__proto__,没有自己的arguments
  4. 不能作为构造函数,原因是构造函数是通过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:

    1. arr.flat 数组扁平化
    2. Array.from() 将数组、类数组转为数组
    3. Array.of 将数值转为数组
    4. find() 找到符合要求的第一个元素值
    5. findIndex() 找到符合要求的第一个元素的下标
    6. fill() 填充一个数组
    7. includes() 数组是否包含某特定值
    8. Array.isArray() 判断是否是数组
  • 新增对象API

    1. Object.assign() 将可枚举属性从一个对象复制到另一个对象,浅拷贝,redux中使用
    2. Object.freeze(obj) 冻结对象
    3. Object.keys 返回自身可枚举属性,不包括原型链上的
    4. 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

  • BigintSymbol都是通过函数来创建的,是es6引入的独有的为了解决相应问题的数据类型。

  • Bigint是为了解决大数相加问题,Symbol是为了标志某些独一无二的东西。 它们的定义都不需要new

        let 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 区别

  • applycallbind都可以为函数指定this 先看看三者的使用方法:
// apply
func.apply(thisArg, [argsArray])  
// call
fun.call(thisArg, arg1, arg2, ...)  
// bind
const newFun = fun.bind(thisArg, arg1, arg2, ...)  
newFun()
  • applycall 就是传参方式不一样,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 事件冒泡 (默认)
  • 阻止事件冒泡
    1. 给子级加 event.stopPropagation()
    2. 在事件处理函数中返回 false
    3. event.target==event.currentTarget,让触发事件的元素等于绑定事件的元素,也可以阻止事件冒泡;
  • 阻止默认事件
    1. event.preventDefault()

    2. 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中组件和组件之间的有哪些通信方式

  1. 父组件给子组件传递信息通过props,子组件像父组件报告可以使用函数
  2. 使用 React 中的两个内置hooks,useContentuseReducer
  3. 使用Redux实现通信,而且还可以跨页面级别组件通信

2. React 中为什么不能在if 循环里写hooks

  • react用链表来严格保证hooks的顺序。hook 相关的所有信息收敛在一个 hook 对象里,而 hook 对象之间以单向链表的形式相互串联。

  • 以useState为例,在它的实现过程之有mountupdate两个阶段

    • 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
评论
请登录