2022前端面试题汇总(持续更新中~)

目录

1. 防抖和节流

2. js闭包

vue中的data为什么是一个函数?(面试常问)

3. ES6面试题

3.1 var let const 区别

3.2 解构 

3.3 如何利用es6快速的去重?

3.4 Promise 面试题 以下代码的执行结果是?

4. Vue相关

4.1 MVC和MVVM的区别

4.2 v-model 原理

4.3  vue中的data为什么是一个函数?(面试常问)

4.4 v-if 和 v-show的区别

4.5 v-for中为什么要有key

5. 跨域的解决方法

5.1. webpack 里的proxy

5.2. jsonp (需要后端支持 )

5.3. webpack plugin (插件)

5.4. cors (后端解决)

6.git命令

7.get与post请求有什么区别

8. cookie、localStorage、sessionStorage的区别 

9. async 和 await 的区别

10. setTimeout 时间为0, 以及误差的原因

11. 求数组的最大值?

12. 求数组的最小值?

13. 数组去重 

14. 生成从0 到 指定数字的数组 

15. 数组求和

16. js的数据类型

17. js的变量提升

 18. this指向

19. map和forEach的区别

20. 箭头函数和普通函数的区别?

21. es6新增

22. 数组方法汇总

23. 项目性能优化方案


1. 防抖和节流

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

比如点击按钮,2秒后调用函数,结果在1.5秒的时候又点了,则会重新计算2秒后在调用函数。

应用场景:下拉触底加载下一页。

节流:连续发生的事件在n秒内,只执行为一次

应用场景比较多的是:搜索查询

2. js闭包

什么是闭包:闭包就是能够读取其他函数内部变量的函数

function a() {

        let a1 = 1;

        return function() {

                return a1

        }

}

闭包存在意义:

可以延长变量的生命周期4可以创建私有的环境

闭包好处:

可以读取其他函数的内部变量

将变量始终保存在内存中

可以封装对象的私有属性和方法

坏处:消耗内存、使用不当会造成内存溢出问题

vue中的data为什么是一个函数?(面试常问)

Vue 中的 data 必须是个函数,因为当 data 是函数时,组件实例化的时候这个函数将会被调用,返回一个对象,计算机会给这个对象分配一个内存地址,实例化几次就分配几个内存地址,他们的地址都不一样,所以每个组件中的数据不会相互干扰,改变其中一个组件的状态,其它组件不变。

简单来说,就是为了保证组件的独立性和可复用性,如果 data 是个函数的话,每复用一次组件就会返回新的 data,类似于给每个组件实例创建一个私有的数据空间,保护各自的数据互不影响

3. ES6面试题

3.1 var let const 区别

var: 存在变量提升;存在变量覆盖,已经被定义且赋值的变量,如果再次被赋值,则以后一次值为准;没有块级作用域;

const:定义的是常量,声明之后必须赋值;定义的值不能去修改,否则报错;有块级作用域;不存在变量提升和变量覆盖;对于数组和对象的元素修改,不算做对常量的修改,不会报错。

let: 有块级作用域;不存在变量提升和变量覆盖;let不允许在相同的作用域中重复声明,注意是相同作用域,不同作用域重复声明不会报错

3.2 解构赋值 

let a = 1; let b = 2;  如果在不声明第三个变量的前提下,使a=2, b=1?

答案:[a, b] = [b, a]

3.3 如何利用es6快速的去重?

let arr = [23, 12, 13, 33, 22, 12, 21]

let item = [...new Set(arr)]

3.4 Promise 面试题 以下代码的执行结果是?

const promise = new Promise((resolve, reject) => {

        console.log(1)

        resolve()

        console.log(2)

})

promise.then(() => {

        console.log(3)

})

console.log(4)

答案:1,2,4,3

解释:以上考察的是关于promise的原理,promise的构造函数是同步执行的,当new Promise的一瞬间,1,2 就立刻被执行,而 .then方法是异步执行的,当执行完1和2之后,会执行输出4,最后执行输出3

4. Vue相关

4.1 MVC和MVVM的区别

MVC:M(model数据)、V(view视图),C(controlle控制器)缺点是前后端无法独立开发,必须等后端接口做好了才可以往下走;前端没有自己的数据中心,太过依赖后台

MVVM:M(model数据)、V(view视图)、VM(viewModel控制数据的改变和控制视图)
html部分相当于View层,可以看到这里的View通过通过模板语法来声明式的将数据渲染进DOM元素,当ViewModel对Model进行更新时,通过数据绑定更新到View。 Vue实例中的data相当于Model层,而ViewModel层的核心是Vue中的双向数据绑定,即Model变化时VIew可以实时更新,View变化也能让Model发生变化

MVVM与MVC最大的区别就是:它实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变

4.2 v-model 原理

是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调从而达到数据和视图同步。

4.3  vue中的data为什么是一个函数?(面试常问)

实际上就是一个闭包,因为vue是单页面应用,是由很多组件构成,每一个组件中都有一个data,所以通过闭包给每一个组件创建了一个私有的作用域,这样就不会相互影响。

4.4 v-if 和 v-show的区别

v-if是通过添加和删除元素来进行显示或者隐藏

v-show是通过操作DOM修改display样式来修改元素的显示和隐藏

如果需要频繁的进行元素的显示和隐藏使用v-show性能更好

4.5 v-for中为什么要有key

key 可以提高虚拟DOM的更新效率。

在vue中,默认“就地复用”的策略,在DOM操作的时候,如果没有key 就会造成选项错乱

key 只能是字符串或者number,其他类型不可以

1. 虚拟DOM中key的作用:

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,随后Vue进行【新的虚拟DOM】与【旧的虚拟DOM】差异比较比较规则如下:

2. 比较规则:

1)旧虚拟DOM找到了与新虚拟DOM相同的key:

若虚拟DOM中内容没变,直接使用之前的真实DOM

若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM

3. 用index作为key可能会引发的问题:

1)若对数据进行:逆序添加、逆序删除等破坏顺序的操作,会产生没有必要的真实DOM更新==>界面效果没问题,但效率低

2)如果结构中还包含输入类的DOM,会产生错误的DOM更新 ==> 界面有问题

4.6 打包后 dist 目录过大,解决办法?

1. dist打包生成的文件中有 .map 文件,可以删除。在 vue.config.js文件中配置:productionSourceMap: false

2. 组价和路由使用懒加载、按需引入等

3. 对于文件和图片进行压缩。 安装压缩组件: compression-webpack-plugin

安装后进行导入配置: 

最小化代码 minisize: true

分割代码: splitChunksl

超过限定值的文件进行压缩,threshold: 文件大小(字节为单位)

4.7 watch和computed的区别

computed能完成的功能,watch都可以完成

watch能完成的小功能,computed不一定能完成。例如:watch可以进行异步操作

两个重要小原则:

1.所有被vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象

2. 所有不被vue管理的函数(定时器的回调、ajax的回调、promise的回调等),最好写成箭头函数,这样this的指向才是vm或组件实例对象

4.8 vue组件之间的数据传递

1. 父组件给子组件传递数据

通过给子组件身上绑定自定义属性,然后再子组件里使用props属性来接收即可

2. 子组件给父组件传递数据

1)第一种方式:通过父组件给子组件传递函数类型的props实现:子组件给父组件传递数据

父组件:

 

 子组件:

 

2)第二种方式:通过父组件给子组件绑定一个自定义事件实现:子组件给父组件传递数据

 父组件:

 

 子组件:

3)第三种方式:通过父组件给子组件绑定一个自定义事件实现:使用ref实现

 父组件:

 

 子组件:

 

3. 全局事件总线:可以实现任意组件间的数据传递 

main.js:将全局事件bus,挂载到Vue的原型上,这样所有的组件都可以使用

 兄弟组件A:

 兄弟组件B:

4. 消息订阅与发布

一种组件间的通信方式,适用于任意组件间通信。

使用步骤:

1)安装pubsub: npm i pubsub-js

2) 引入: import pubsub from 'pubsub-js'

3) 接收数据: A组件想要接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

mounted() {

        this.pid = punsub.subscribe('xxx', (data)=>{

                ......

        })

}

4) 提供数据: pubsub.publish('xxx', 数据)

5)最好在beforeDestory钩子中,用pubsub.unsubscribe(pid)取消订阅

5. 跨域的解决方法

跨域:只要协议、域名和端口号有一个不相同就会产生跨域问题。同源策略是一个安全策略。同源,指的是协议,域名,端口相同。浏览器处于安全方面的考虑,只允许本域名下的接口交互,不同源的客户端脚本,在没有明确授权的情况下,不能读写对方的资源。

解决办法:

5.1. webpack 里的proxy

devServer: {
    proxy: {  //配置跨域
      '/api': {
        target: 'http://121.121.67.254:8185/',  //这里后台的地址模拟的;应该填写你们真实的后台接口
        changOrigin: true,  //用于控制请求头中的post值,默认开启
        pathRewrite: {
          /* 重写路径,当我们在浏览器中看到请求的地址为:http://localhost:8080/api/core/getData/userInfo 时
            实际上访问的地址是:http://121.121.67.254:8185/core/getData/userInfo,因为重写了 /api
           */
          '^/api': '' 
        }
      },
    }
  }

5.2. jsonp (需要后端支持 )

方案1 *:通配符,全部允许,存在安全隐患(不推荐)。

一旦启用本方法,表示任何域名皆可直接跨域请求:
  1     server {
  2         ...
  3         location / {
  4             # 允许 所有头部 所有域 所有方法
  5             add_header 'Access-Control-Allow-Origin' '*';
  6             add_header 'Access-Control-Allow-Headers' '*';
  7             add_header 'Access-Control-Allow-Methods' '*';
  8             # OPTIONS 直接返回204
  9             if ($request_method = 'OPTIONS') {
 10                 return 204;
 11             }
 12         }
 13         ...
 14     }

方案2:多域名配置(推荐)

配置多个域名在map中 只有配置过的允许跨域:

  1  map $http_origin $corsHost {
  2         default 0;
  3         "~https://zzzmh.cn" https://zzzmh.cn;
  4         "~https://chrome.zzzmh.cn" https://chrome.zzzmh.cn;
  5         "~https://bz.zzzmh.cn" https://bz.zzzmh.cn;
  6     }
  7     server {
  8         ...
  9         location / {
 10             # 允许 所有头部 所有$corsHost域 所有方法
 11             add_header 'Access-Control-Allow-Origin' $corsHost;
 12             add_header 'Access-Control-Allow-Headers' '*';
 13             add_header 'Access-Control-Allow-Methods' '*';
 14             # OPTIONS 直接返回204
 15             if ($request_method = 'OPTIONS') {
 16                 return 204;
 17             }
 18         }
 19         ...
 20     }

5.3. webpack plugin (插件)

npm i -S webpack-dev-middleware  安装中间键,把前端和后端的服务绑在一起

中间件

let webpack = require('webpack')

let middle = require('webpack-dev-middleware')

let compiler = webpack(require('./webpack.config.js'))

app.use(middle(compiler))

5.4. cors (后端解决)

var allowCrossDomain = function(req,res,next) {

        // 请求源

        res.header("Access-Control-Allow-Origin", "*")

        // 请求头 token

        res.header("Access-Control-Allow-Headers", "*")

        // 请求方法 get post put del

        res.header("Access-Control-Allow-Methods", "*")

        next();

}

app.use(allowCrossDomain )

6.git命令

1. git init 初始化git仓库 (mac中Command+Shift+. 可以显示隐藏文件)

2. git status 查看文件状态

3. git add 文件列表 追踪文件

4. git commit -m 提交信息 向仓库中提交代码

5. git log 查看提交记录

6.1.分支明细

(1)主分支(master):第一次向 git 仓库中提交更新记录时自动产生的一个分支。

(2)开发分支(develop):作为开发的分支,基于 master 分支创建。

(3)功能分支(feature):作为开发具体功能的分支,基于开发分支创建

6.2.分支命令

(1)git branch 查看分支

(2)git branch 分支名称 创建分支

(3)git checkout 分支名称 切换分支

(4)git merge 来源分支 合并分支 (备注:必须在master分支上才能合并develop分支)

(5)git branch -d 分支名称 删除分支(分支被合并后才允许删除)(-D 强制删除)

6.3.暂时保存更改

(1)存储临时改动:git stash

(2)恢复改动:git stash pop

7.get与post请求有什么区别

get是从服务器上获取数据,post是向服务器传送数据。

POST比GET安全,因为数据在地址栏上不可见。

get方式提交的数据最多只能有1024字节,而post则没有此限制。

GET使用URL或Cookie传参。而POST将数据放在request BODY中。

GET与POST都有自己的语义,不能随便混用。

据研究,在网络环境好的情况下,发一次包的时间和发两次包的时间差别基,本可以无视。而在网 络环境差的情况下,两次包的TCP在验证数据包完整 性上,有非常大的优点。post 发送两次,get 只发送一次。

8. cookie、localStorage、sessionStorage的区别 

共同点: 都是保存在浏览器端、且同源的

不同点:

cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。

存储大小限制也不同:

cookie数据不能超过4K,sessionStorage和localStorage可以达到5M

sessionStorage:仅在当前浏览器窗口关闭之前有效;

localStorage:始终有效,窗口或浏览器关闭也一直保存,本地存储,因此用作持久数据;

cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭

作用域不同

sessionStorage:不在不同的浏览器窗口中共享,即使是同一个页面;

localstorage:在所有同源窗口中都是共享的;也就是说只要浏览器不关闭,数据仍然存在

cookie: 也是在所有同源窗口中都是共享的.也就是说只要浏览器不关闭,数据仍然存在

9. async 和 await 的区别

区别:

async是来定义函数的,定义异步函数,打印函数名可以得到一个promise对象,言外之意可以通过这个   函数名称.then 这个方法

await 后面跟的是任意表达式,一般使用promise的表达式

async 内部实现,又返回值 成功返回promise.resolve() ,出错返回promise.reject() 返回值用catch捕获

await 等待后面的promise对象执行完毕,拿到了promise.resolve()的值之后,执行后面的代码。await后面的表达式,能是promise.reject(),所以建议await放在try....catch语句中

优点:async和await 属于es7语法。编写方便,提高程序效率,避免了回调地狱

补充:promise和async和await的区别

promise es6语法,promise中包含catch,async需要自己定义catch

promise 提供的方法会多一些,all、race等方法,aync中是没有的。

10. setTimeout 时间为0, 以及误差的原因

setTimeout,如果

时间为0,则会立即插入队列,不是立即执行,等待前面的代码执行完毕。

11. 求数组的最大值?

function getMaxArryNum(arr) {

        return Math.max(...arr)

getMaxArryNum([1,2,3,4,5,6])

12. 求数组的最小值?

const getMinArryNum= (arr) => {

        return Math.min(...arr)

getMinArryNum([1,2,3,4,5,6]) 

13. 数组去重 

const removeEqual = (arr) => {

        const result = arr.filter((item, index, self) => {

                return self.indexof(item) === index

        })

        return result

removeEqual([1,2,3,4,5,6,1,2,3,42,1])

14. 生成从0 到 指定数字的数组 

const getArr = (startNum, endNum) => {

        let arr = []

        for(var i=startNum; i<=endNum; i++){

                arr.push(i)

        }

        return arr

getArr(0,4)

15. 数组求和

const arrSum = (arr) => {

        const temp = arr.reduce((pre, now) => {

                return pre+now

        },0)

        return temp

}

arrSum([1,2,3,4])

16. js的数据类型

js 数据类型分为基本数据类型和复杂数据类型

基本数据类型:Boolean、Number、String、Null、Undefined

复杂数据类型: Object、Array、Function、Date

17. js的变量提升

在js中,变量和函数的声明会被提升到最顶部执行

函数提升高于变量的提升

函数内部如果用var声明了相同名称的外部变量,函数将不再向上寻找

匿名函数不会提升

 18. this指向

this总是指向函数的直接调用者。

如果有new关键字,this指向new出来的对象

在事件中,this指向触发这个事件的对象

19. map和forEach的区别

forEach方法,是最基本的方法,遍历和循环。默认有3个参数:分别是遍历的每一个元素item,遍历的索引index,遍历的数组array

map方法,和foreach一致,不同的是会返回一个新的数组,所以callback需要有return返回值,如果没有会返回undefined

20. 箭头函数和普通函数的区别?

函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象

不可以当作构造函数,也就是说不可以使用new命令,否则会报错

不可以使用arguments对象,该对象在函数体内不存在,如果要用可以使用Rest参数代替

不可以使用yield命令,因此箭头函数不能用作Generator函数

21. es6新增

新增模版字符串

箭头函数

增加let、const来声明变量

for-of用来遍历数据-例如数组中的值

解构赋值

新增简单数据类型Symbol,独一无二的,不会与其他属性名冲突

将Promise对象纳入规范,提供了原生的Promise对象

22. 数组方法汇总

map 循环遍历数组、返回一个新的数组

forEach 循环遍历数组,不改变原数组

push/pop 在数组的末尾添加/删除元素  改变原数组

unshift/ shift 在数组的头部添加/删除元素,改变原数组

join  把数组转化为字符串

some  有一项返回为true,则整体为true

every  有一项返回为true,则整体为false

filter 数组过滤

slice(start, end)  数组截取,包括开头,不包括截取,返回一个新的数组

splice(start, number, value)  删除数组元素,改变原数组

indexof/lastindexof: 查找数组项,返回对应的下标

concat:数组的拼接,不影响原数组,浅拷贝

sort:数组排序 改变原数组

reverse: 数组反转,改变原数组

23. 项目性能优化方案

减少http请求

减少DNS查询

使用CDN

避免重定向

图片懒加载

路由懒加载

减少DOM元素操作

使用外部js和css

压缩js、css、字体、图片等

使用iconfont字体图标、雪碧图等

避免图片的src为空

把样式表放在link中

把js放在页面的底部

猜你喜欢

转载自blog.csdn.net/Mr_LiuP/article/details/124078819