前端面试题总结

773 阅读15分钟

2023年3月2日,23届应届生,在郑州找前端工作已经两周了,面了5家公司了,第5家公司面试过了就直接去实习了。

2023年5月31日,实习干了两个月被踢了,答辩完之后重新找工作,过去了两周,面过了两家,再次更新题目。

简答题:

JavaScript 相关

1. JavaScript的数据类型有哪些

目前有八种数据类型,七种基本数据类型,一种引用数据类型。

七种基本数据类型:number、string、boolean、null、undefined、symbol、bigInt。

一种引用数据类型:Object。Array、Date、Math、Set、Map、RegExp、Error等类型都是基于Object派生的子类型。

2. var、let、const的区别

第一点:var 声明的变量存在变量提升

变量提升实际是指变量声明的提升var 声明会在预编译阶段提升至全局作用域或函数作用域的最顶部区域。

全局作用域
console.log(str)
var str = 'hello'

等价于:

var str
console.log(str) // => undefined
str = 'hello'
函数作用域
function fn() {
    console.log(str)
    var str = 'hello'
}
fn()

等价于:

function fn() {
    var str
    console.log(str) // => undefined
    str = 'hello'
}
fn()

第二点:var 声明的变量如果在全局作用域下,会 自动成为全局对象 window的属性

// 全局作用域下,var 声明的变量会自动成为全局对象 window的属性  
var str = 'hello'
console.log(window.str) // => hello
function fn() {
  // 函数作用域下则不会
  var str = 'hello'
}
console.log(window.str) // => undefined

第三点:在同一作用域下,var 可以重复声明

var str = 'hello'
var str = 'world' // 可以再次声明变量 var
console.log(str) // => world

理解:因为存在变量提升的嘛,欻的一下提升成了一个声明

上述代码等价于:

var str
str = 'hello'
str = 'world'
console.log(str) // => world

let、const

let、const 声明的变量不存在变量提升,在全局作用域中声明不会自动成为全局对象window的属性,在同一作用域下不可以重复声明

同时,let、const 还引入了块级作用域的概念,以及随之出现了暂时性死区问题。

暂时性死区 TDZ

function fn() {
    let str = 'hello'
    console.log(str) // => hello
}
fn()

在相应花括号形成的作用域中存在一个暂时性死区,起始于函数开头,终止于相关变量声明语句所在行。在这个范围内无法访问let、const声明的变量。

3. cookie、localStorage和sessionStorage之间的区别

参考 OBKoro1的文章:cookie、localStorage和sessionStorage 三者之间的区别以及存储、获取、删除等使用方式 - 掘金 (juejin.cn)

localStorage

localStorage的作用是可以将数据永久存储在本地,除非手动删除,否则关闭页面也存在。

localStorage按源存储的,这里的源是指同源策略的源。

本地存储只能存储字符串类型的数据,如果传入的是非字符串,会自动类型转换为字符串。

传入复杂数据类型时,自动类型转换无法满足要求,一般先使用JSON.stringify()方法将复杂数据类型转换为JSON字符串,然后存储。取出相应JSON字符串后,需要使用JSON.parse()方法进行解析。

存储数据:

localStorage.setItem(key, value)

获取数据

localStorage.getItem(key)

删除某项数据

localStorage.removeItem(key)

清除localStorage

localStorage.clear()

4. 如何判断数组和对象(数据类型判断)

参考博主的这篇文章:JS中判断数据类型的四种方法 - 掘金 (juejin.cn)

5. 什么是原型链

参考冴羽的文章:JavaScript深入之从原型到原型链

6. 什么是同步、异步

视频教程:【简单认识】什么是同步和异步,理解同步和异步,弄懂同步异步,程序开发知识,编程知识点。

同步就是后一个任务等待前一个任务执行完毕后再执行,执行顺序和任务的排列顺序一致。

举例:按照排队的顺序依次打饭,只有前面一个人打完饭之后,才轮到后面一个人。

异步是非阻塞的,异步逻辑和主逻辑相互独立,主逻辑不需要等待异步逻辑完成。而是可以立刻继续下去。

举例:有同学忘记带饭卡了,怎么办?如果一直等着饭卡拿来,那是不是很花时间,后面同学就不能打饭了!所以呢,我让出来,让后面的人先打饭,等我的饭卡到了,我再打饭。

7. 同步代码与异步代码相混合,写出字符输出顺序。

console.log('a')

setTimeout(() => {
  new Promise((resolve, reject) => {
    console.log('b')
    resolve()
  })
  .then(() => {
    console.log('c')
  })
}, 0)


new Promise((resolve, reject) => {
  console.log('d')
  resolve()
}).then(() => {
  console.log('e')
})

console.log('f')
a --> d --> f --> e --> b --> c

JavaScript任务分为同步任务和异步任务。

同步任务:if语句、while循环、for循环等。

异步任务:定时器、Ajax、DOM 事件、读写文件、数据库操作、Promise监听事件等。

对于同步任务而言,JS是从上到下执行的,执行完了上一个同步任务,才会执行下一个同步任务。但是对于异步任务来说,JS使用事件循环机制来处理。

  1. JS引擎本身是个线程,所有的同步任务在这条JS引擎线程上执行。
  2. JS引擎线程之外还有一个异步任务队列,所有异步任务放在异步任务队列里面。
  3. 只有把所有同步任务执行完之后,JS引擎线程才会读取异步任务队列,然后依次执行异步任务队列里面的异步任务。
  4. 事件触发线程持续监听异步任务队列,然后重复 3

强调

程序并不是直接将整个异步任务放到异步任务队列中的,而是将异步任务的回调函数放到异步任务队列中。异步任务队列就是个回调函数队列

关于定时器

遇到setTimeout()这个异步任务,浏览器的渲染进程会开启一个定时器线程去处理。定时器线程计时完毕后,就会通知事件触发机制定时器的回调函数推送至异步任务队列队尾。注意,定时器线程在同步任务完成后才开始计时。

关于Promise

Promise构造函数的参数是一个函数,这个函数是同步的

new Promise(
// Promise构造函数的参数是一个函数
(resolve, reject) => {}
// 使用 new 构造 Promise 实例时,同步执行该函数
)

then()方法可以传两个参数,是两个监听器:onFulfilled,onRejected,是异步任务。

 
p.then(onFulfilled[, onRejected]);

p.then(value => {
  // fulfillment
}, reason => {
  // rejection
})
 

catch() 可以传一个参数,是个监听器:onRejected,是异步任务

p.catch(onRejected);

p.catch(function(reason) {
   // 拒绝
});

8. 什么是箭头函数,以及它与普通函数的区别

箭头函数 - JavaScript | MDN (mozilla.org)

箭头函数表达式的语法比函数表达式更简洁,并且没有自己的thisargumentssupernew.target

箭头函数表达式更适用于那些本来需要匿名函数的地方,并且它不能用作构造函数,因为箭头函数没有constructor,没有构造能力,故而不能充当构造函数,无法使用new关键字生成实例。普通函数可以充当构造函数,可以用new关键字生成实例。

箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承 this

由于箭头函数没有自己的 this,通过 call()  或 apply() 方法调用一个函数时,只能传递参数,不能绑定 this, 第一个参数会被忽略,这种现象对于 bind 方法同样成立。

9. 箭头函数返回值的几种方式

明确使用return关键字返回值时,必须使用大括号。

// 返回一个基本数据类型
const fn1 = () => { return 'a' }
fn1() // => 'a' 

// 返回一个对象
const fn2 = () => { return { a: 'd' } }
fn1() // => { a: 'd' } 

明确使用return关键字返回值时,不使用大括号会报错。

const fn = () => return 'a' // => Uncaught SyntaxError: Unexpected token 'return'
const fn2 = () => return { a: 'd' }  // => Uncaught SyntaxError: Unexpected token 'return'

如果函数体只有一句执行代码,那么可以省略大括号,同时默认会被return出去,如果返回的是对象,还需要使用小括号包起来。

const fn = () => 'a' + 'b'

fn() // => 'ab'
const fn = () => ({ a: 'b' })

fn() // => { a: 'b' }

使用函数体,但是不明确使用return返回值时,默认返回undefined。

const fn = () => { 'a' + 'b' }
fn() // => undefined

10. 分别用es5、es6实现类和类的继承

父类名为 Person,拥有两个属性 name、age,一个方法 sayHello,子类名为 Studeng,拥有一个属性 grade,一个方法 sayHi,请分别用es5、es6实现相应的类以及继承。

ES6

这个当时是写出来了的,但是尴尬的是 constructor 单词拼错了,extends 忘了写s,就很nice。

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  sayHello() {}
}

class Student extends Person {
  constructor(name, age, sex) {
    super(name, age)
    this.sex = sex
  }
  sayHi() {}
}

12. 什么是 ajax

13. 什么是事件冒泡,如何阻止事件冒泡

APIs-day3-1005-事件流、事件捕获、事件冒泡以及阻止冒泡

当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡。

简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件

14. 防抖和节流

节流 throttle

节流:连续触发事件但是在n秒内只执行一次函数。

首次是否执行、结束后是否执行

关于节流的实现,有两种主流的实现方式,一种是使用时间戳,一种是设置定时器。

设置定时器
function throttle(fn, delay) {
    let timer = null
    return function () {
        if(timer) return
        timer = setTimeout(() => {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}
使用时间戳
function throttle(fn, delay) {
    // 起始时间
    let startTime = 0
    return function() {
        // 得到当前的时间
        let now = Date.now()
        // 判断如果大于等于 delay,调用函数
        if(now - startTime >= delay) {
            fn.apply(this, arguments)
            startTime = now
        }
    }
}

应用实例:小米轮播图点击效果、鼠标移动、页面尺寸缩放 resize、滚动条滚动。

假设一张轮播图完成切换需要 300 ms,不加节流效果,快速点击,则嗖嗖嗖的切换,加上节流效果,不管快速点击多少次,300ms时间内,只能切换一张图片。

防抖 debounce

防抖:触发事件后在n秒内函数只能执行一次,如果n秒内又触发了事件,则会重新计算函数执行时间。

function debounce(fn, delay) {
    let timer = null;
    return function () {
        if(timer) clearTimeout(timer)
        timer = setTimeout(()=> {
            fn.apply(this, arguments)
            timer = null
        }, delay)
    }
}

CSS 篇

1. 如何在不同分辨率下显示不同的图片

2. link和 @import 的区别

3. position的值有哪些

视频教程:渡一教育-袁进老师-定位

默认值为静态定位 static,还有相对定位 relative、绝对定位 absolute、固定定位 fixed。

基本认知:

  • 一个元素,只要 position 的取值不是static,该元素就是定位元素
  • 固定定位、绝对定位的元素会脱离文档流相对定位不会导致元素脱离文档流。

脱离文档流的元素:

  • 文档流中的元素布局时,会忽略脱离了文档流的元素。
  • 文档流中元素计算自动高度时,会忽略脱离了文档流的元素。

相对定位 relative

不会导致元素脱离文档流,只是让元素基于原来的位置进行偏移,而且偏移不会对周围元素及父元素造成影响

可以通过 left、right、top、bottom 属性设置其偏移。

绝对定位 absolute

绝对定位元素为块盒,块盒高度默认为auto,此时auto含义依旧为适应内容高度块盒宽度默认为auto,此时auto的含义从吸收剩余空间转变为了适应内容

包含块:第一个祖先定位元素的 padding-box,若祖先没有定位元素,则包含块为整个网页(初始包含块)

可以通过 left、right、top、bottom 属性设置其相对于包含块的位置

固定定位 fixed

包含块:视口

可以通过 left、right、top、bottom 属性设置其相对于包含块的位置

4. display: none;、opacity: 0;、visibility: hidden; 三者的区别

5. css盒子模型有几种? 以及盒模型设置?

推荐文章:css盒子模型有几种? 以及盒模型设置? - 掘金 (juejin.cn)

浮动元素的水平垂直居中

vue 框架

1. 对mvc、mvvm的理解

2. vue 的生命周期是什么,有哪些

生命周期是指一个组件从创建 --> 挂载 --> 状态更新 --> 销毁的整个阶段,强调的是一个时间段

生命周期函数:是由 vue 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行

lifecycle1.png

3. vue 组件通信

3.1 父组件向子组件传值

Vue2.0-22.父向子传值 - 使用自定义属性

image.png

  • props 只能是父组件向子组件进行传值,props 使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。
  • props 可以显式定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
  • props 属性名规则:若在 props 中使用驼峰形式,父组件模板中需要使用短横线的形式。
  • props传值是浅拷贝,不建议在子组件中修改props属性值

3.2 子组件向父组件传值

Vue2.0-25.子向父传值 - 自定义事件

image.png

3.3 EventBus(全局事件总线)

视频链接:Vue2.0-26.兄弟组件数据共享 - EventBus

在 vue 2.x 中,兄弟组件之间数据共享(数据通信)的方案是EventBus(全局事件总线)

实际上,任意组件之间都可以用全局事件总线通信,本质上就是全局层面的事件触发和事件响应。

image.png

EventBus的使用步骤:

  1. 创建eventBus.js模块,向外共享一个 Vue 的实例对象
  2. 数据发送方,调用bus.$emit('事件名称', 要发送的数据)方法触发一个自定义事件
  3. 数据接收方,调用bus.$on('事件名称', 事件处理程序)方法注册一个自定义事件监听器

4. vue响应式原理

5. v-model的原理

6. v-show和v-if的区别

v-if 控制的是元素或组件的创建和销毁。

v-show 会在 DOM 渲染中保留该元素,v-show 仅切换了该元素上名为 display 的 CSS属性

<div v-show="false">Hello</div>

渲染为:

<div style="display: none;">Hello</div>

回答完这个问题后,面试官进一步问了 display: none;、opacity: 0;、visibility: hidden; 三者的区别。

7. v-if和v-for的优先级

8. 如何理解v-for中的key

9. 常见的事件修饰符及其作用

10. 什么是过滤器,如何实现一个过滤器

Vue 2.x 允许自定义过滤器,用来对文本进行格式化处理

过滤器可以用在两个地方:插值表达式v-bind表达式

过滤器应该被添加在JavaScript表达式的尾部,由管道符号指示。

分类:全局过滤器、本地过滤器。当全局过滤器和局部过滤器重名时,会采用局部过滤器。

本地过滤器

可以在一个组件的filters选项中定义本地的过滤器。

注意:filters选项是带有s的

filters: {
    // 大于0并且小于10的数字添加前导0
    add: function (value) {
        return (value > 0 && value < 10 ) ? '0' + value: value
    }
}

全局过滤器

在创建 Vue 实例之前使用 Vue.filter() 全局定义过滤器。

// 大于0并且小于10的数字添加前导0
Vue.filter('add', function (value) {
    return (value > 0 && value < 10 ) ? '0' + value: value
})

new Vue({  
  // ...  
})

使用

<template>
  <div>{{ num | add }}</div>
</template>

<script>
export default {
  data() {
    return {
      num: 1
    }
  }
}
</script>

关于参数的解释

过滤器是 JavaScript 函数,因此可以接收参数。

过滤器函数接收表达式的值 (之前的操作链的结果) 作为第一个参数。在上述例子中,add 过滤器函数将会收到 num 的值作为第一个参数。

{{ num | filterA('arg1', arg2) }} 

这里,filterA 被定义为接收三个参数的过滤器函数。其中 num 的值作为第一个参数,普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。

过滤器串联

过滤器可以串联

{{ num | filterA | filterB }}

在这个例子中,filterA 被定义为接收单个参数的过滤器函数,表达式 num 的值将作为参数传入到函数中。然后继续调用同样被定义为接收单个参数的过滤器函数 filterB,将 filterA 的结果传递到 filterB 中。

11. vue router的路由模式有几种,它们的区别是什么

12. vue router的路由守卫有哪些

13. vuex 是什么,何时用,如何用

14. vuex和全局对象有什么区别

15. vue cli 或 vite 配置代理如何配置

浏览器篇

1. 什么是跨域,如何解决跨域问题

参考博主的什么是跨域,如何解决跨域问题 - 掘金 (juejin.cn)

网络

1. HTTP常见的状态码及其含义

算法题:

1. 数组去重

2. 找到数组重复次数最多项