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使用事件循环机制来处理。
- JS引擎本身是个线程,所有的同步任务在这条JS引擎线程上执行。
- JS引擎线程之外还有一个异步任务队列,所有异步任务放在异步任务队列里面。
- 只有把所有同步任务执行完之后,JS引擎线程才会读取异步任务队列,然后依次执行异步任务队列里面的异步任务。
- 事件触发线程会持续监听异步任务队列,然后重复 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)
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this
,arguments
,super
或new.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 框架提供的内置函数,会伴随着组件的生命周期,自动按次序执行。
3. vue 组件通信
3.1 父组件向子组件传值
- props 只能是父组件向子组件进行传值,props 使得父子组件之间形成了一个单向下行绑定。子组件的数据会随着父组件不断更新。
- props 可以显式定义一个或一个以上的数据,对于接收的数据,可以是各种数据类型,同样也可以传递一个函数。
- props 属性名规则:若在 props 中使用驼峰形式,父组件模板中需要使用短横线的形式。
- props传值是浅拷贝,不建议在子组件中修改props属性值
3.2 子组件向父组件传值
3.3 EventBus(全局事件总线)
视频链接:Vue2.0-26.兄弟组件数据共享 - EventBus
在 vue 2.x 中,兄弟组件之间数据共享(数据通信)的方案是EventBus(全局事件总线)。
实际上,任意组件之间都可以用全局事件总线通信,本质上就是全局层面的事件触发和事件响应。
EventBus的使用步骤:
- 创建eventBus.js模块,向外共享一个 Vue 的实例对象
- 在数据发送方,调用bus.$emit('事件名称', 要发送的数据)方法触发一个自定义事件
- 在数据接收方,调用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)