记录面试难点

4 阅读17分钟

一、性能优化

1. 虚拟列表  
  
    比如:table的数据,如果不分页的情况,要展示XXX条数据,可以用虚拟列表解决  
    场景有:options数据特别多、table数据特别多...  
      
    通常的面试题:后端一次性给你10W条数据如何优化?虚拟列表 & 长列表  
      
    总结:不过有多少条数据,在页面上永远渲染的dom个数是有限的,比如10W条数据渲染永远都渲染10个dom  
2. 关于项目打包的优化【webpack构建项目的打包优化】【基于vite构建项目的打包优化】  

注意:

懒加载:是指盒子存在,只是图片路径没赋值  
          
        <img src='' data-src='1.jpg'> ==> 等到这个盒子需要加载了,把data-src1.jpg赋值给src  
面试中被问到:你在项目中做了哪些性能优化:  
  
1. 可以把部分只是渲染到数据做冻结(不需要修改和删除此数据的)  
    vue2中使用:Object.freeze  
    Vue3中使用:markRaw()  
      
2. 后端给的数据比较多,减少dom渲染可以用虚拟列表  
    比如options数据特别多、table数据特别多,或者BI项目,反正就是需要展示很多数据,我们需要减少dom  
      
3. 剩下的小细节就不说了 , 比如减少重绘,回流、做图标优化...  
  
4. 最主要的就是项目打包的优化,比如项目打包的分包处理,在vite中无需配置,在webpack中需要配置路由懒加载的name,还有比如拆包的处理(某一个主体js文件体积多大,可能是多个路由都打包在一个js中了),还有打包完成的主体js文件内容过于庞大,可以gzip(需要后端去配置,前端就需要打包就行了)  

二、兼容处理

2.1 全屏设置兼容

//目前是否是放大  
var isFull=!!(document.webkitIsFullScreen || document.mozFullScreen ||   
        document.msFullscreenElement || document.fullscreenElement);  
  
if(  isFull  ){//缩小     
  
      if (document.exitFullscreen) {  
            document.exitFullscreen();  
        } else if (document.msExitFullscreen) {  
            document.msExitFullscreen();  
        } else if (document.mozCancelFullScreen) {  
            document.mozCancelFullScreen();  
        } else if (document.webkitExitFullscreen) {  
            document.webkitExitFullscreen();  
        }  
  
}else{//放大  
  
      if (element.requestFullscreen) {  
            element.requestFullscreen();  
        } else if (element.msRequestFullscreen) {  
            element.msRequestFullscreen();  
        } else if (element.mozRequestFullScreen) {  
            element.mozRequestFullScreen();  
        } else if (element.webkitRequestFullscreen) {  
            element.webkitRequestFullscreen();  
        }  
  
}  

三、权限

1.1 左侧菜单权限

1. 用户登录,需要请求 【个人信息】 接口  (接口会返回:角色权限编码)  
2. 接着 【获取路由】(需要把 角色权限编码 传递给后端)  
3. 【获取路由】接口会返回 : 对应登录的账号的权限路由树  

1.2 右侧某按钮权限

1. 用户登录,需要请求 【个人信息】 接口  (接口会返回:权限信息)  
2. 这时候会把后端返回过来的 用户的 “权限信息” 存储起来  
3. 新建自定义指令 v-auth='某某权限字符'  
4. 自定义指令大体的判断:  
        1. 指令有没有传值  
        2. 在用户的权限信息中, 有没有指令 传递过来的参数  
        3. 如果有return , 如果没有删除该节点  

1.3 某某用户如果没有此权限不可以进入某某路由  [ 在软件级别的系统中,无需判断 ]

1. 在导航守卫【前置性导航守卫】进行判断   
2. 如果是超级管理员直接通行  
3. 如果不是超级管理,需要拿到用户的所有路由信息(扁平化:map对象) 和 当前的路由判断,存不存在,如果不存在那么就提示(展示错误页面)  

1.4 权限管理 [ 删除 、 新增 ]

前沿:其实权限是由 角色 来控制的  
1. 给某某用户 “新增” 一个权限   
    直白点:给某某用户,新增一个 角色  
2. 角色新增流程  
    1. 点击新增按钮  
    2. 获取菜单权限树  
    3. 选择对应的权限,其实就是选择了某权限的id  
    4. 当点击【确认新增】,需要给后端传递 沟通的权限id。  
    5. 后端会把多个权限id,组合成一个 角色, 一个角色对应一个ID( 这个id就是多个权限生成)  
1. 给某某用户 “删除” 一个权限  
    直白点:给某某用户,去掉一个 角色  
      
    ***要注意一点:如果某某用户,在使用本角色,那么直接删除(返回:30006 [数据存在引用资源] )  
        直白点:用户没有用某权限才可以删除,如果被引用会返回:30006  

四、微信登录

***在开发平台中,是有基础url的,就是我们项目的网址

2.1 点击 微信登录 ==> 二维码

1. 渲染进程:点击按钮 ==> 通信主进程  
2. 主进程:创建新窗口 ==> 打开由 后端 给 前端 的 url 从而展示二维码 ==> 二维码已经渲染到页面上了   

2.2 点击 微信登录 ==> 二维码 ==> 如何知道 用户 扫码了 , 并且 成功成功

1. 主进程 监听 URL改变  
2. 当URL改变的时候 ==》 去发送一个请求 ==》传递的参数:{  
        url:网站的网址  
        headers:{  
            WechatOauthType:'login'  
        }  
}  
3. 请求发送成功后 会返回 微信授权code4. 主进程 通信 渲染进程 同时把code码(微信授权code码)传递给渲染进程  
5. 渲染进程拿到主进程给的code码(微信授权code码),再次发送请求 [获取AccessToken],同时把code码(微信授权code码)传递给后端  
      
    ***后端返回:如果此微信用户 绑定过 会直接返回 token , 如果没有绑定 会返回其他信息(该微信未绑定账号)  

***微信登录之前,需要绑定  【在我们这个系统中是这样的:后台管理系统】

2.3 绑定用户

1. 渲染进程:点击按钮 ==> 通信主进程 (需要渲染进程给主进程传递token)  
2. 主进程:创建新窗口 ==> 打开由 后端 给 前端 的 url 从而展示二维码 ==> 二维码已经渲染到页面上了   
3. 主进程 监听 URL改变  
4. 当URL改变的时候 ==》 去发送一个请求 ==》 传递的参数:{  
        url:网站的网址  
        headers:{  
            WechatOauthType:'bind',  
            Authorization:'token'  
        }  
}  
5. 请求发送成功后 会返回 微信授权code6. 主进程 通信 渲染进程 同时把code码(微信授权code码)传递给渲染进程  
7. 渲染进程拿到主进程给的code码(微信授权code码),再次发送请求 [检查绑定状态],同时把code码(微信授权code码)传递给后端  
      
    ***后端返回:如果此微信用户 绑定过 会返回 【已绑定】, 如果没有绑定 会返回【绑定成功】  

五、阿里云oss

1.1 下载oss

参考网址:www.npmjs.com/package/ali…

下载安装:npm install ali-oss --save  

1.2 配置oss文件

//引入  
import OSS from 'ali-oss'  
  
//实例化 OSS : 创建OSS对象  
export const clint = new OSS({  
  region: 'oss-cn-beijing',  //区域  
  bucket: 'xiaoluxian-1302830050', //bucket名称  
  accessKeyId: 'LTAI5t9m6NZe2GiCsFBiF2nT', //访问键ID  
  accessKeySecret: 'EPnMPkHg41O7Tk3wZh212wLmQ6r7jA', //访问密钥  
  endpoint: 'oss-cn-beijing.aliyuncs.com',//地域节点 ===> 外网访问  
  secure:true, //如果是http默认就是false,如果是https就要写成true  
});  

1.3 阿里云oss上传文件的方法

client.multipartUpload(上传文件的名称 | 目录 + 文件名称 , 上传的文件 )  
  
or  
  
new OSS.multipartUpload(上传文件的名称 | 目录 + 文件名称 , 上传的文件 )  

1.4 oss上传代码

const tool = {};  
  
import { nanoid } from 'nanoid'  
import { client } from '@config/alioss'  
  
//阿里云oss  ==> 断点续传  
tool.oss = {  
    //执行上传文件操作的方法  
    upload( file ,  loading = true ){  
        const uuid = nanoid();  
        //截取文件后缀名  
        const index = file.name.lastIndexOf('.');  
        const suffix = file.name.substring( index + 1 );  
        const currentDate = tool.dateFormat( new Date() , 'yyyy-MM-dd');  
        let fileName = 'xiaoluxian-crm/' + currentDate +'/' + uuid + '.' +suffix;  
        //const _loading = loading ? ElLoading.service({ lock: true }) : {close:noop};  
        //执行上传操作  
        return client.multipartUpload(fileName,file,{  
            progress:function( percentage , checkpoint ){  
                // console.log( percentage , checkpoint )  
            },  
            //checkpoint:'',  
        }).then(data=>{  
            console.log( data );  
        }).finally(()=>{  
            //_loading.close();  
        })  
    }  
}  
  
//时间戳转换为标准时间  
tool.dateFormat = ( date:String , fmt='yyyy-MM-dd hh:mm:ss'  )=>{  
  
    date = new Date( date );  
  
    var o = {  
        'M+': date.getMonth() + 1, // 月份  
        'd+': date.getDate(), // 日  
        'h+': date.getHours(), // 小时  
        'm+': date.getMinutes(), // 分  
        's+': date.getSeconds(), // 秒  
        'q+': Math.floor((date.getMonth() + 3) / 3), // 季度  
        'S': date.getMilliseconds() // 毫秒      
    }  
  
    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))  
        for (var k in o) {  
        if (new RegExp('(' + k + ')').test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))  
    }  
      
    return fmt;  
}      
  
export default tool;  

六、electron下载文件

简单来说:导出就是一个请求,传递对应的参数就可以了,下载就是一个任务列表,在任务列表中点击下载按钮,对应调用fs创建文件调用管道写入内容。

流程为:

1. 页面(渲染进程)点击下载按钮,给主进程传递参数  
2. 由主进程创建新的窗口  
3. 新窗口打开一个.vue文件,这个文件其实是一个路由的component  
4. 打开页面后,点击任务列表中的某一个下载按钮  
5. 调用https.get方法 并且 主进程执行dialog ,当dialog中用户选择了存储的地址后,通过fs.createWriteStream创建生成文件,由https.get请求的res.pipe(file) 【通过管道往文件中写入内容】  

七、ws通信

前言:接收消息(下载了某一个东西,通知)。

1. 生命周期执行WebSocket操作  
     a>创建WebSocket对象      new WebSocket  
     b>连接成功后的回调函数      onopen  
     c>从服务器接收信息时的回调函数 onmessage  
             ***如果有消息,onmessage中的函数参数可以接收到  
            把数据渲染到页面上  
              
2. 点击联系人【树】==》获取某一个用户的id  
      a>发起请求,产生会话==》谁发消息,谁接收  
        b>请求聊天记录接口==》获取聊天记录内容,展示页面  
3. 点击发送消息  
      a>向服务端发送的消息 ==》WebSocket对象.send(内容:按照后端给的数据结构来传递)  
      b>把自己输入的内容展示到页面  
      

http 和 tcp区别

1. Socket建立连接,服务器可以向客户端主动发送消息 ( http做不到,http的服务端是被动的,需要客户端主动发,服务端才可以返回数据  
2. 如果请求内容特别多的情况下(http频繁去发送请求会导致性能下降,Socket因为一直通信,所以可以解决这个问题)  

WebSocket使用场景:

1. 如果不确定用户什么时候会操作,同时操作完成,客户端要有响应的行为。  
      
    扫码支付(短轮训、WebSocket)  
    聊天(WebSocket)  
    即时消息[站内信、通知...](WebSocket)  
      
2. 如果要做纯聊天,没人直接用WebSocket,一般会用(环信、腾讯IM)  

八、electron项目打包上线

1. electron-vite 什么都不写的情况下,打包40MB+  
2. 打包多端:linux、macos、windows  
3. 打包命令:npm run build:win  
4. electron不用做优化 ==》因为electron内部已经做了  
5. electron不涉及到上架问题  
appId: com.electron.app          //包名  
productName: 小鹿线CRM客户管理系统  //软件的名称  

windows配置:  256*256 图标

win:  
  executableName: electron-app     //可执行文件  
nsis:  
  artifactName: ${name}-${version}-setup.${ext}  //安装包的名称和后缀  
  shortcutName: ${productName}    //快捷方式的名称  
  uninstallDisplayName: ${productName}  //卸载程序的名称  
  createDesktopShortcut: always         //是否创建快捷方式  
    
  oneClick:false                                                //一键安装  
  allowToChangeInstallationDirectory    //是否可以选择安装目录  

mac配置

图标:icon.icons    512 * 512 图标  
如果是vue2、vue3项目无所谓,因为取决于构建工具:webpack、vite  
  
webpack ==》  
    1. 代理(解决跨域问题,在开发阶段生效,生产阶段不生效) ==> 环境变量解决  
    2. 路由懒加载 import(/* webpackChunkName: 'ImportFuncDemo' */ '../components/Foo') ==> 分包  
    3. 拆包  
    4. 压缩打包(某一个js文件贼大:zip/gz)  
      
vite  
    1. 代理(解决跨域问题,在开发阶段生效,生产阶段不生效) ==> 环境变量解决  
    2. 自动分包( 不需要配置 )  
    3. 拆包(也会自动,可能自己有个性化的内容)  
    4. 压缩打包(某一个js文件贼大:zip/gz)  
***注意坑:node版本太高打包可能有问题,建议node版本:16.19.0  
面试回答:  
    1. node版本不要太高,我设置的是16.9.0  
    2. windows跟根据公司的要求来配置,比如是否可以选择安装目录,是否创建快捷方式,设置卸载的名称...  
    3. windows配置的图标是:build/icon.ico 、mac os 配置的图标是build/icon.icons  
    4. 包名无所谓/名称自己起  
    5. 设置好 环境变量 进行打包即可 : npm run build:win. 、 npm run build:mac  
    6. 可能会配置更新 ???  
打包所遇到的问题:  
  
    mac os 有问题 : Error: Cannot find module 'builder-util-runtime'  
      
    解决:  
        1.package.json中dependencies中的  
      
         "@electron-toolkit/preload": "^1.0.3",  
     "@electron-toolkit/utils": "^1.0.2",  
     "electron-log": "^4.4.8",  
     "electron-updater": "^5.3.0",  
     "electron-win-state": "^1.1.22",  
       
           放入devDependencies中  
     
   2. 重新安装依赖  

九、electron判断多端

import isElectron from 'is-electron';  
   
const isInElectron = isElectron();  
export const isOsx = isInElectron && window._platform && window._platform === 'darwin';  
   
export const isWin = isInElectron && window._platform && window._platform === 'win32';  
   
export const isLinux = isInElectron && window._platform && window._platform === 'linux';  

九.1 软件更新问题

electron 中 有自动更新软件的api方法他叫:autoUpdater,在我们打包项目上线的时候  
  
会生成latest-mac文件,我们需要给后端,后端上线以后,会给一个地址,这个地址包含了软件的安装包也包含了软件的更新latest-mac文件,如果有需要更新的(其实根据再次打包的package.json中的版本号和线上更新文件的版本号是否一致,如果不一致证明需要更新了),更新操作通过autoUpdater的事件来完成。  

十、 目前项目使用的vue技术

2.1 全局混入:字典

2.2 自定义指令:判断按钮级权限控制

2.3 store + 持久化存储:刷新页面数据还在

十一、vue2和3通用的api

11.1 混入

面试题1:混入有什么缺点?  
    不好维护(因为数据来源不好找)  
面试题2:混入是什么?什么情况下去用?  
    混入就是在任何组件中混入数据或者方法( 混入就是全局 )  
    场景:字典  
      
混入分为2种:  
    1. 全局 ( main.js )  
    2. 混入到组件中( 某某.vue )  

11.2 插槽

匿名:  
  
父组件:  
<Table>  
        <span>这是插入的东西222222</span>  
</Table>  
      
      
子组件:  
<template>        
  
    <div>第一行</div>  
    <div>  
        第二行  
        <slot></slot>  
    </div>  
    <div>  
        第三行  
        <slot></slot>  
    </div>  
  
</template>  
具名:  
  
父组件:  
        <Table>  
        <template v-slot:er>  
            <span>这是插入的东西222222</span>  
        </template>      
  
        <template #san>  
            <span>这是插入的东西333333</span>  
        </template>   
    </Table>  
      
      
子组件:  
<template>        
  
    <div>第一行</div>  
    <div>  
        第二行  
        <slot name="er"></slot>  
    </div>  
    <div>  
        第三行  
        <slot name="san"></slot>  
    </div>  
  
</template>  
作用域:就是传值  
  
父组件:  
  
<Table>  
       <template v-slot:er="scope">  
            <span>这是插入的东西222222 {{  scope.row  }} {{  scope.names  }} </span>  
       </template>      
  
        <template #san="scope">  
            <span>这是插入的东西333333  {{  scope.row  }} {{  scope.names  }}  </span>  
        </template>   
</Table>  
  
子组件:  
   
 <template>        
    <div>第一行</div>  
    <div>  
        第二行  
        <slot name="er" :row="headerTitle" :names="name"></slot>  
    </div>  
    <div>  
        第三行  
        <slot name="san" :row="headerTitle" :names="name"></slot>  
    </div>  
</template>  
  
<script setup lang="ts">  
import { ref } from 'vue'  
let headerTitle = ref(100);  
let name = ref('张三');  
</script>  

11.3 APP.use具体做了什么

app.use( 参数1 , ...options 多个参数 )  
  
1. 首先判断插件 参数1 是否是函数  
  
2. 判断Vue是否已注册过这个插件,如果注册过就跳出方法   
  
3. 如果没有注册就进行注册 ,vue直接调用install方法,在install方法中进行注册 并且传递 vue 这个函数   

11.4 自定义指令

语法:app.directive(  指令名称 ,  function  );  
场景:后台管理系统 ,按钮级别的控制,就需要自定义指令  

11.5 nextTick

是什么:整体返回一个异步行为==》当dom更新完毕触发  
  
场景:比如,发起请求拿到后端数据,把数据渲染到页面上,当渲染完毕 nextTick(()=>{ 设置滚动条位置 })  

11.6 组件传值

Vue2:
父子:  
  
    父:<abc :msg='msg'/>  
    子:props  
  
子父:自定义事件  
      
    this.$emit()  
      
兄弟:bus  
Vue3
父子:  
  
    父:<abc :msg='msg'/>  
    子:definedProps  
  
子父:自定义事件  
      
    this.$emit()      
      
兄弟:mitt()  

Vue3:v-model传值

父组件:  
    <abc v-model:msg='msg'/>  
子组件:  
    $emit('update:msg','123');  
      
//原来父组件的msg 修改成了 123  

11.7 组件直接修改值

Vue2
父 组件 直接使用[修改]子组件的值: ref  
子 组件 直接使用[修改]父组件的值: $parent  
Vue3没有$parent

11.8 vue2.6以后版本和vue3的依赖注入

是什么:跨域组件  
问题:不好维护(数据来源不明确)  

11.9 keep-alive

是什么:缓存组件,会多2个生命周期  
场景:后台管理系统,有一个打包路由页面的标签,当打开A标签(A页面),在打包B标签(B页面),之前A页面填写了内容,然后切换到B标签页,那么A标签的内容就不存在(那么就白写了)  
  
***提升用户体验、提升性能  

11.10 computed

有缓存,有2种写法,  
  
不可以修改  
    computed:{  
        changeStr(){}  
    }  
    this.changeStr = 123;  
  
可以修改  
    computed:{  
        changeStr:{  
            get(){  
                return this.str  
      },  
      set( value ){  
                this.str = value;  
      }  
        }  
    }  
      
    this.changeStr = 123;  

11.11 watch

监听某一个数据变化  
监听路由的变化  
  
初始化监听  
深度监听(以下俩种情况需要深度监听)  
  
  data : {  
    arr:[1,2,3]  
  }  
  
  
  data:{  
    obj:{  
      a:1  
    }  
  }  
  

11.12 双向绑定原理

vue是由v-model完成双向绑定,那么在完成双向绑定之前就要有单项绑定过程,这个单项绑定就被叫做初始化编译解析过程,也就是把data中定义的数据,展示到页面上。其实在定义data中属性的时候,就已经通过new Proxy或者Object.defineProperty已经做好了劫持(给没一个属性添加了getset),v-model的行为就是视图修改数据驱动data数据修改,那么已经做好了监听,当input发生改变时在源码中其实是加了input事件的,当value发生改变时,在添加的订阅者中进行更新,从而视图和数据的值就都发生了改变。  

11.13 diff算法

虚拟dom的对比==》数据的对比  
  
算法对比条件( 平级 、有层级 )  
  
1. 平级关系  
      
    如果新和旧的节点都更换了,那么就不对比了  
      
    有旧dom => 转换成虚拟dom [其实拿key做对比]  
    有虚拟dom了 => 旧虚拟dom的节点名称和key 与 新虚拟dom的节点名称和key进行对比  
      
    旧前=》新前  
    旧后=》新后  
    旧前=》新后  
    旧后=》新前  
    查找  
      
    如果不满足以上5个条件,就反复不断执行,到最后剩下的,要不然新建要不然删除  
      
2. 有层级  
      
    1. 顶级去做对比  , 对比条件和平级关系一样  
    2. 平级对比      

11.14 虚拟dom

把dom弄成数据的结构  

11.15 store

1. 持久化存储  
        store + localStorage插件  
          
    ***面试题:a组件使用了vuex中的某一个数据,然后修改了数据,修改完数据刷新页面又回到之前的数据了为什么?  
    解决:store + localStorage插件  
  
2. vuex的mutaitons和actions区别  
  
    mutaitons同步  
    actions异步  
  
3. vuex和pinia区别  
      
    pinia小  
    pinia没有mutations和modules  
    pinia可以直接修改state数据  
      
        *** vuex也可以,只不过需要this.$store.state.xx , 不能使用辅助函数的形式修改mapState  

11.16 路由

1. 路由导航守卫  
  
    三大类型:全局、路由独享、组件内  
      
    场景:  
      beforeEach(前置导航守卫):开启加载条、判断用户是否登录、判断有没有权限、document.title设置...  
        afterEach(全局后置):关闭加载条  
  
2. 路由传参  
      
    query==》注意应用类型转换格式 ( JSON.xxx )  
      
3. 动态路由  
      
    根据返回的不同数据,展示不同的路由 ==》 :id  
  
4. 添加路由 : add.addRoute   
  
    router.addRoute('名称',{  
         path: "/login",   
     meta:{  
        title:'登录',  
     },  
     component: ()=>import('@views/login/Login.vue')  
    })  
  
5. router和route  
     router是整个路由的对象,包含路由所有内容和route  
     route是router中的属性,包含当前路由内容  
       
6. Vue3的路由router4.x版本 :   <router-link tag='li'> 取消了tag  

11.17 ref

Vue2:<div ref='aaa'>  ==> this.$refs.aaa  
Vue3:<div ref='bbb'>  ==> let bbb = ref();   bbb.value  

十二、Vue3面试题

12.1 Vue3项目采用什么写法?

纯setup写法  

12.2 在setup写法中无法使用this,你怎么办?

import { getCurrentInstance} from 'vue'  
const { proxy } = getCurrentInstance();  

12.3 Vue2和Vue3有什么区别?

1. 双向绑定原理不同  
  
    Vue2:defineProperty  
              
            不在Object.defineProperty中添加的属性,是监听不到的(如果对象创建了,后添加的也监听不到),这个问题衍生出来的问题是(数据修改了,视图不更新),在vue2中使用$set来解决这个问题. this.$set(对象,key,值)  
              
              
    Vue3:new Proxy  
      
            即使给对象后添加的属性,也可以被监听到  
              
2. Vue3删除了一些Vue2的api:$set、$parent...  
  
3. 纯setup写法,没有this,要获取this,就要config上下文  

12.4 异步组件

异步组件写法:对于加载没有用,但是打包后会单独分包  
  
import { defineAsyncComponent } from 'vue'  
const AsyncComponent = defineAsyncComponent(() =>  
  import('./components/MyComponent.vue')  
)  
  
如果要用异步组件,一定要给条件( 什么时候加载这个组件 )  
  
也可以配合Suspense内置组件一起使用,说明:知道此异步组件加载的状态  
  
课程:https://www.xuexiluxian.cn/course/detail/e88ee53679324a2292dbba23051b81db,搜索:异步组件  

十三、vue项目打包出现的问题

一、面试题:出现空白页面怎么解决?  
  
    配置路径相对于本目录: publicPath:'./'  
    配置环境变量:axios代理请求的问题,开发环境不生效  
二、面试题:做过单元测试  
      
    我们公司有专门测试,不需要我做!  
      
    链接:https://www.bilibili.com/video/BV1Ua411o7KT?p=5&vd_source=428ad5db675cd7c14aa84c9336139535  

十四、ES6

14.1 Promise和async/await区别

  
1. 关于严格控制代码执行顺序  
  
    async/await :  返回虽然是异步(请求的数据)但是也可以控制代码的执行顺序  
    Pormise不可以控制以上的行为  
      
2. await可以直接解析promise  

14.2 使用过promise的哪些方法?如果同时发送多个请求怎么办?

.all  
  
场景:选中删除( 后端给的接口,只能传递一个删除的id )  xxx/delete/id

14.3 es6用过哪些东西?

promsie、解构、letconst、箭头函数、字符串扩展方法、对象扩展方法、new Map、?.  

14.4 new Map和普通对象区别

1. key的类型  
  
    new Map的key可以是任何类型,普通对象的key永远是字符串  
      
2. new Map对于判断操作性更强 ( has ) ,对于数据存储对象形式,更加优雅。  
  
*** 很多算法,都会用map  

14.5 箭头函数和普通函数区别

1. this指向  
  
    箭头函数this不可以修改,定义时候就决定了  
    普通函数this可以修改,随着环境也会更改      
  
2. 箭头不能new,不能当作构造函数  
3. 箭头没有arguments  
...  
  
  
面试题:如果拷贝了呢?和箭头函数没关系,this永远指向定义时的对象,拷贝的值和this指向没关系,因为对象多了1

十五、TS和Js有什么区别

15.1 关于面向对象

TS语法是面向对象的语法  
  
ES6的class是一种模拟形式的面向对象,但是在ES2022添加了私有化  

十六、JavaScript

16.1 event loop(事件循环机制)

js的执行机制:同步任务和异步任务  
  
js的执行顺序为:同步任务==》异步任务(当前的微任务==》下一个宏任务==》)  

16.2 防抖、节流、手写Promise源码

  

16.3 判断某某变量是不是数组类型

Array.isArray  
Object.prototype.toString.call(变量) === '[object Array]'  

16.4 new 操作具体做了什么?

1. 创建了一个空的对象  
2. 将空对象的原型,指向于构造函数的原型  
3. 将空对象作为构造函数的上下文(改变this指向)  
4. 对构造函数有返回值的处理判断  
function Fun( age,name ){  
    this.age = age;  
    this.name = name;  
}  
function create( fn , ...args ){  
    //1. 创建了一个空的对象  
    var obj = {}; //var obj = Object.create({})  
    //2. 将空对象的原型,指向于构造函数的原型  
    Object.setPrototypeOf(obj,fn.prototype);  
    //3. 将空对象作为构造函数的上下文(改变this指向)  
    var result = fn.apply(obj,args);  
    //4. 对构造函数有返回值的处理判断  
    return result instanceof Object ? result : obj;  
}  
console.log( create(Fun,18,'张三')   )  

16.5 闭包

1. 闭包是什么  
    闭包是一个函数加上到创建函数的作用域的连接,闭包“关闭”了函数的自由变量。  
2. 闭包可以解决什么问题【闭包的优点】  
    2.1 内部函数可以访问到外部函数的局部变量  
    2.2 闭包可以解决的问题  
            var lis = document.getElementsByTagName('li');  
      for(var i=0;i<lis.length;i++){  
        (function(i){  
          lis[i].onclick = function(){  
            alert(i);  
          }  
        })(i)  
      }  
3. 闭包的缺点  
    3.1 变量会驻留在内存中,造成内存损耗问题。  
                解决:把闭包的函数设置为null  
  3.2 内存泄漏【ie】 ==> 可说可不说,如果说一定要提到ie  

16.6 原型链

1. 原型可以解决什么问题  
    对象共享属性和共享方法  
2. 谁有原型  
函数拥有:prototype  
对象拥有:__proto__  
3. 对象查找属性或者方法的顺序  
    先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找  
4. 原型链  
    4.1 是什么?:就是把原型串联起来  
    4.2 原型链的最顶端是null  

16.7 JS继承

方式一:ES6
class Parent{  
    constructor(){  
        this.age = 18;  
    }  
}  
  
class Child extends Parent{  
    constructor(){  
        super();  
        this.name = '张三';  
    }  
}  
let o1 = new Child();  
console.log( o1,o1.name,o1.age );  
方式二:原型链继承
function Parent(){  
    this.age = 20;  
}  
function Child(){  
    this.name = '张三'  
}  
Child.prototype = new Parent();  
let o2 = new Child();  
console.log( o2,o2.name,o2.age );  
方式三:借用构造函数继承
function Parent(){  
    this.age = 22;  
}  
function Child(){  
    this.name = '张三'  
    Parent.call(this);  
}  
let o3 = new Child();  
console.log( o3,o3.name,o3.age );  
方式四:组合式继承
function Parent(){  
    this.age = 100;  
}  
function Child(){  
    Parent.call(this);  
    this.name = '张三'  
}  
Child.prototype = new Parent();  
let o4 = new Child();  
console.log( o4,o4.name,o4.age );  

16.8 说一下call、apply、bind区别

共同点:功能一致
可以改变this指向  
  
语法: 函数.call()、函数.apply()、函数.bind()  
区别:
1. call、apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行。  
2. 参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写。  
场景:
1. 用apply的情况  
var arr1 = [1,2,4,5,7,3,321];  
console.log( Math.max.apply(null,arr1) )  
  
2. 用bind的情况  
var btn = document.getElementById('btn');  
var h1s = document.getElementById('h1s');  
btn.onclick = function(){  
    console.log( this.id );  
}.bind(h1s)  

16.9 深拷贝和浅拷贝

共同点:复制  
  
1. 浅拷贝:只复制引用,而未复制真正的值。  
var arr1 = ['a','b','c','d'];  
var arr2 = arr1;  
  
var obj1 = {a:1,b:2}  
var obj2 = Object.assign(obj1);  
  
2. 深拷贝:是复制真正的值 (不同引用)  
var obj3 = {  
    a:1,  
    b:2  
}  
var obj4 = JSON.parse(JSON.stringify( obj3 ));  
  
//递归的形式  
function copyObj( obj ){  
    if(  Array.isArray(obj)  ){  
        var newObj = [];  
    }else{  
        var newObj = {};  
    }  
    for( var key in obj ){  
        if( typeof obj[key] == 'object' ){  
            newObj[key] = copyObj(obj[key]);  
        }else{  
            newObj[key] = obj[key];  
        }  
    }  
    return newObj;  
}  
console.log(  copyObj(obj5)  );  

补充

vue优点

1. 因为vue是单页面应用,所以不涉及到跳转页面,如果要按照传统方式a标签的形式,就会多长时间等待页面加载出来。  
  
2. vue中有v-model,这个模式比传统的dom操作要方面的多。  

vue缺点

因为vue是单页面应用,也就是说打包后整个项目只有一个html,所以对于SEO方面就比较弱。所以vue适合后台管理系统 或 不做SEO的项目  
  
SEO条件:其中做seo必须的条件是 ==》 多页面