2021-10月终极版-前端面试题

978 阅读35分钟

自我介绍环节

  你好,我叫xxx,一共有5年的工作经历,其中做过2年测试,3年前端,
  在前端前工作中,主要是用vue uniapp框架来做一些PC和app端项目功能模块的开发,
  开发完成后,进行打包测试和上线bug跟进修复的一些工作。
  
  下面介绍一下最近做过的一个项目,
  这个项目叫在线教育sass系统:分为PC端后台管理系统 和前台的app端,
  我负责的是app端开发
  采用的是 uniapp+uview组件库搭建的项目,项目中有 首页 分类,课程 和 我的页面
  我在这里主要负责课程模块的组件封装,课程正常购买支付,课程正常播放浏览,
  以及登录这些模块的开发,课程信息和用户信息采用vuex存储。
  
  第二个项目叫茶七网,是一个电商平台的app系统,框架采用的是uniapp,主要负责的功能有首页的商品展示,
  购物车模块完整的支付流程,
  和调用sdk短信验证码登录一体化这些功能模块,商品数据数据也是采用是vuex。
  
  第三个项目叫后台管理系统,是公司内部使用的员工一个管理平台,
  采用的事vue+elementui来做的,主要负责的功能有,员工模块的增删改查,
  权限管理,权限分配,和数据分配可视化,采用echarts做的。
  
  第四个项目叫:政府类的垃圾分类项目,采用Mui+HTML5plus政府智慧垃圾分类项目
  有回收站,垃圾站,垃圾分类,语音识别
  
  整个app采用的框架是mui,调取设备使用html5plus,

3、具体实现哪些功能? 
下面我说一下我负责的功能模块有三个,分别是 语音查找,垃圾分类,和文字搜索。 
先说一下语音查找,使用的是讯飞语音识别,触摸语音识别按钮调用讯飞语音识别功能,
在规定10秒时间内通过语音识别成功后调用后端接口跳转到新的匹配结果页面,
可以看到查找的垃圾是干垃圾还是湿垃圾,如果语音识别失败,会有相应的toast弹窗提示用户。

垃圾分类:采用了轮播组件和区域滚组件,进行了页面渲染垃圾分类数据,在数据渲染完成后,

文字搜索功能,可以输入对应的垃圾名称,点击搜索后进行相应的查找,
的信息做了本地数据持久化存放在localstore中,
用户下次可以通过点击手搜索历史快速查找,还有一个是热门搜索的部分
,也可以直接点击热门的数据进行快速搜索

4、遇到哪些问题?以及处理方式? 
在做垃圾分类数据渲染完成后,发现轮播功能无效
,官方文档没有对应的解决方案,通过快速查找相关技术论坛得到解决方案。 
打了一个不是正式的apk包,在手机运行的时候,
发现语音功能不能正确搜索到对应的垃圾分类,
但是本地运行的时候又是可以的。通过调试发现语音识别完成后都有一个句号,
通过正则或用语音官当文档提供的api方法把多余的标点符号攫取掉就可以正常使用了

5、上线流程? 打包上线是先在mainifest配置完相关配置,
然后再运行中选择制作自定义调试基座,填写相关配置信息,
比如android的apk要填写证书相关信息,
然后打一个自定义调试基座的apk包安装到手机进行测试,
测试通过再打正式包,发布到手机应用商店上。


  1.垃圾分类语音识别接入,plus.speech.startRecognize2.搜索详情查找以及历史记录持久化存储,localStorage3.安卓和IOS状态栏修正,plus.navigator.setStatusBarBackground4.路径传值问题修正,数据类型判断切换;
  5.基座调试,打包上架商店,安卓apk+iosipa。
  
  第四个项目叫:游侠客是一个旅游社交平台,有PC和app一起的配套项目,我主要负责的是pc端的开发,
  采用 vue+elementui框架来做的。
  app端是另一小组同学负责,我负责开发的模块有各个旅游的导航栏模块,数据和图片功能的处理,比如游客攻略,
  周边游,国内游,以及各种旅游模块。
  以上就是我的一些基本的介绍,关于技术这一块待会可以具体聊一些,您看下关于项目有什么想要问我的吗?
  

CSS部分

JS部分

兼容和性能优化

二 项目相关:

2. 项目遇到过最大的问题是什么?
项目中遇到过很多问题,最大的问题就是:
在第一个项目sass系统一期,准备发布外网的时候,老大突然说要安排做seo,
SEO有几个特点:
第一个就是: 多页面,就是打包成多个html文件,
第二个是:可以动态设置meta标签上的title,description,keyworks这些关键信息
第三个是:接口数据存在dom中,爬虫就可以爬到,
分别是:
采用预渲染,webpack的一个插件prerender-spa-plugin 或者 服务端渲染 Nuxt.js
这两个方式的区别就是:预渲染 做不到整个项目的页面都做seo,因为存在一个问题就是:解决不了动态路由,比如详情页,每一个商品详情跳转的时候都是一个新的页面,路由地址不能动态更新,还有不能动态设置title description 和 ,keyworks关键字,可以用vue-meta-info解决动态title,但是刷新页面的时候title会闪烁。当时根据要求,项目只需要做其中2个模块的seo,就是课程首页和学习页面,所以采用了预渲染的模式解决的,就是把需要做成seo的页面路由地址,配置到插件的routes数组里,重新打包测试通过之后就可以上线了。

再多说一下:服务端渲染可以完美解决预渲染的问题,可以整个页面都做seo,但是服务端渲染压力都在后端上,项目大和网络不好的时候访问速度都会很慢,因为他们的请求是这样的,首先 node服务发送请求,去请求后端java接口,接口返回数据给node之后,node再将数据再给到客户端,接口数据有了才打开页面。
  了解过一些Nuxt :
     路由跳转:nuxt.lint, 传参也是跟vue一样,可以通过query或params传,
     新建页面之后,自动生成路由,子路由,不需要像vue一样手动配置
  
这就是我项目遇到过最大的问题。
2.1 seo后面做的,为什么一开始没有做seo?
   项目开始阶段,甲方公司没有要求,快要上线的时候才临时提出的这么一个要求。
3.项目是怎么上线的?
就拿这个sass教育系统来说吧,项目开发完成后先打包自测,测试通过后,去申请对应的ios和Android平台的证书,然后打正式包,
发布到手机应用商店上。我操作的主要是android平台,因为ios需要企业证书一般由项目组组长来完成
3.1 项目打包后上线有没有遇到过什么问题?
在打包后自测会出现空白页,有几个原因
    1.1 打包路径问题,因为打包后publishPath的路径如果没有设置url指向dist文件夹,默认情况下读取的就是电脑的用户的根目录,所以找不到项目代码,就出现空白页。
   解决这个问题方式就是在vue的config文件中设置publishPath的值写成'./' 就可以,让他指定当前的dist根目录。
   1.2 网页内容不显示,是路由问题
    解决:前端自测路由模式要改成hash,再次打包测试,测试通过后,再改城history模式,需要后端配合做一下重定向配置就可以了。
   1.3 项目采用代理请求数据处理跨域,但是打包后跨域不生效,可以看到axios的url写的是什么,请求url就是什么。
    解决:
    需要新建两个文件来区分是.env.development还是.env.production环境,
    两个文件里面都声明一个变量问ENV,值为development,或 production
    第二变量就是设置VUE_APP_BASE_API的url来指向开发环境还是生产环境。
    设置完成后,在axios里面进行二次封装判断url的值是生成环境还是开发环境。
    vue会自动通过process.env.变量是等于development还是production,去自动切换环境。
4.后台管理系统是用vue做的吗? 为什么用vue来开发项目,不用react?
是的,但是得看具体的情况,
第一个 取决于公司技术团队人员是否熟练,
第二个 具体看项目是不是要做seo,后台管理系统一般都是公司内部使用,不需要seo也不需要考虑是单页面还是多页面。
第三个 就是老大到底会不会,因为老大要负责整个团队,不可能说团队成员去问一个问题,不会就不好了。也没有特别的标准,就是完全看团队和继续老大来选择开发技术方案了。
5. 后台管理系统,权限管理和权限分配是怎么做的?
  首先登录的时候会根据用户分配相应的角色,相应的角色去绑定对应的权限。
  用户登录的时候,会默认获取到角色列表第一个角色。是存储在vuex中的
  请求角色列表,给用户添加上对应的角色,然后根据角色可以去对权限做增删改一些操作。
  一般添加权限都是要根据后端返回特定的字段来判断当前用户是否有权限去给对应的角色操作权限。
6. pc端支付是怎么做的?
    支付只做了阿里的支付宝支付,采用条件编译的H5支付,确认订单之后,无论是成功还是失败,都要将选中的商品清空。
7. uniapp 条件编译是怎么判断是ios还是android?
    uniapp条件编译只能判断 是H5还是weixin 还是QQ,还是app-plus ,支付里面走的支付宝并且是H5支付
    如果要设备,需要单独用js来判断,有个方法可以获取设备 uni.getSystemInfoSync()
    
    getClientHeight() {
        let res = uni.getSystemInfoSync()
        let platform = res.platform
        if (platform == 'android') {
            return res.statusBarHeight - 4
        } else if (platform == 'ios') {
            return 44 + res.statusBarHeight
        } else {
          return uni.upx2px(250)
        }
    },
9. 前端安全问题?
    做项目涉及到最大的安全问题有哪些?
    视频防盗链,就是后端返回的一个url只能在特定的系统播放,如果复制url去其他地方是无法获取到视频的,都是后端做好的一个防盗功能,前端只是把他返回的url,写在对应的src属性上。
    接口安全:防止别人爬虫盗用接口。
    在封装接口的时候,项目需要判断请求来源的 ,采用aes的十六位十六进制数来加密,安全性比较好一点 ,前端就是做加密,项目中就是根据后端要求将加密的source参数添加在请求投header中,后端会自动解密 判断,如果请求没有携带source,就是非法的,后端会进一步处理。
    
    验证码登录的时候,输入手机号跳转到验证码输入页面,在发送请求跳转的时候就需要将手机号加密起来,应用的是封装请求的时候就已经将header方式加密过了。
    用户名密码登录也是要加密的方法传参。
    登录成功后用户信息存在vuex中,有一个modules的login.js中
    
    在跳转到输入验证码的页面,调用解密方法,将手机号解密后,配上正则表达式,只显示前3位和后4位,中间用*表示,
   
      比如 平台的所有视频,播放之前判断是否买了这门课,通过权限接口查询是否有权限,传递的参数是课程id
      如果直接下载视频,可以下载吗?
    

image.png

    xss攻击 用户输入的文本框,需要替换某些特殊字符( <> ... )
    SQL注入 用户输入的文本框中不可以有特殊符号( 引号、空格 )
    
10. 在这么多项目中,如何判断用户是否已经登录了,怎么判断登录是过期了?
    封装接口中会根据后端返回的cood码来判断用户是否已经登录成功,
    tooken过期也是根据后端返回的cood状态码来判断的,如果已经过期就清除tooke
    uni.removeStorageSync('token')
    
    uni中存储是:getStorageSync(key,value) setStorageSync(key,value)
    获取本地存储数据可以在 onshow()生命周期
    
    把tooken存到哪里了?
    手机验证码登录成功后token以加密的方式存在本地setStorageSync(key,value)
    
    修改个人信息之前有个防重复提交token接口。
    
    当用户退出了登录,但是本地存储的token没有过期,此时再次进入到页面不做登录,可以直接访问内容吗?不行,需要做进入到页面之后发送一次获取用户信息才可以。
    
    localstory和cookie有什么区别?       
11. 问一个场景
A页面-->B页面,B-->A 这时候会执行哪些生命周期?
    视图和数据没有同步更新有遇到过吗?
12. 登录这一块你们用到扫码登录对接和扫码的对接,整个申请过程你有参与吗?
13. 怎么判断用户到底是扫码了还是没扫码?
14. 怎么判断用户到底是登录还是没登录?
15. 怎么判断某个订单是支付成功还是失败?
        支付:支付宝支付-->ios 和 android
16. 逻辑题:一个字符串 ‘aabbbb’ 判断字符串是否是回文,如果是返回true,否则false。
17. 用什么方法可以判断哪个字符在整个字符出现的次数最多?哪个是第二大 哪个是第三大?
18. 两个很大的数相加,怎么做?
19. 你封装过哪些组件?怎么封装的?
        项目中,
       1. 头部会封装成一个单独的vue文件,放在common里,只是背景色不一样,采用uview组件库的 u-navbar
       2. 首页里:课程板块,免费课程,的展示,推荐好课布局都是一样, 
       3. tabbar 都会封装起来,课程详情的目录跟播放课程展示的目录一样,也要封装
        怎么封装的?
        就说tabbar封装吧,
        因为uview提供的一些很好的api,就自己在common上封装了一个tabbar,覆盖原来的tabbar,
        解决:需要做用户在没有登录的时候,进入页面就提示用户登录信息
           记得在onShow生命周期中隐藏原生的tabbar,底部原理跳原理还是原来的跳转uni.switchTab方法
           onShow() {
		// 隐藏原生的tabbar
		uni.hideTabBar();
	},
            
            1. 在pages目录新建4个页面
            2. 找pages.json的tabBar上list上配上页面路径,icon路径和text文件名,同时根据设计稿在这里面设置间距
            3. 在app.vue 每个生命周期中,都要加上这句话 uni.hideTabBar()
            4. 在pages中的每个页面也要加上
             onShow() {
		// 隐藏原生的tabbar
		uni.hideTabBar();
	},
       
       
20 请求是怎么封装的?
由一个api文件夹有个index.js
1. 封装公共部分内容,baseurl methods headers dataType data
2. request 请求传一个options对象,
    拼接请求url 判断data参数是否存在,判断数据类型
    uni.showLoading({title: '加载中'})
    return 一个 new Promise,在里面根据后端返回的cood码判断用户登录成功 失败,以及tooken过期等操作,同时每一个操作都要关掉loading状态
    接口请求的时候后端要求对请求做加密,在封装请求的时候就把加密的参数封装进去,参数是携带在header中,当时的参数叫做sources,让后端之后请求来源的。
    
21 loading提示是怎么做的?
在封装请求的时候会将 请求数据返回之情把uni.showLoading 放进去,请求成功之后将loading关闭,在采用uni.hideLoading();

三 vue相关问题

一 说一下 vue的生命周期
1. 主要是从组件的创建到销毁的过程,系统4个阶段分别是:
   创建 beforeCreate, created
   挂载 beforeMount, mounted
   更新 beforeUpdate, updated
   销毁 beforeDestroy,destroyed
   
   beforeCreate:初始化实例,进行数据观测,可以加loading事件
   
        created:完成数据初始化 data\methods\watch,里面的方法和数据可以访问,
                 可以进行异步请求和结束loading事件
                 DOM没有挂载,如果需要修改就放在Nexttick回调函数里
                 
    beforeMount:完成DOM阶段初始化,但并没有挂载到el选项上
    
        mounted:完成DOM挂载渲染,可以在这里操作DOM,双向数据绑定
                 发送请求,请求数据(created也可以发送请求,请求数据,
                 但是没有dom,所以一般不会再create发送请求)
                 
   beforeUpdate:可以访问dom,删除一些数据监听器,更新数据必须是被渲染在模板上的(el template render之一),此时view还没有更新
                 若在beforeUpdate中再次修改数据,不会再触发更新方法
                 
         update:数据更新后,完成view的重新渲染,
                 不再在这里继续修改数据,否则会在此触发更新方法(beforeUpdate/update)
                 
   beforeDestroy: 实例销毁前,删除一些提示操作
   
         destroy: 销毁后,实例被删除,监听 组件 子组件 不能操作任何东西

  1.1 为什么要在created中获取数据,而不是在mounted中去获取?
    因为前端都是 先是获取到数据再到页面渲染,而不是数据页面又请求数据,
    如果在这里获取一次数据会造成重绘回流,性能不太好。在mounted主要是用来获取dom的,
    比如点击了某个按钮跳转到某个导航栏目,就先获取到偏移量数据再去跳转。
    
2. 一旦进入组件或进入页面 会执行 前4个生命周期
    beforeCreate, created
    beforeMount, mounted
    
3. 如果使用了keep-alive 缓存组件,会多出两个生命周期
    activated  deactivated
    
4. 如果使用keep-alive 第一次进入组件会执行5个生命周期
    beforeCreate, created
    beforeMount, mounted
    activated
    
5. 如果使用keep-alive 第二次或第N次进入组件,每次都会执行一个生命周期,
     activated
 
6. 生命周期原理:mvvm
一(补充)vue3 和 vue 2 生命周期的区别
vue3使用生命周期需要单独从vue中引用,并且只能在setup函数内调用
Vue3 的生命周期比较多。
setup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
onBeforeMount() : 组件挂载到节点上之前执行的函数。
onMounted() : 组件挂载完成后执行的函数。
onBeforeUpdate(): 组件更新之前执行的函数。
onUpdated(): 组件更新完成之后执行的函数。
onBeforeUnmount(): 组件卸载之前执行的函数。
onUnmounted(): 组件卸载完成后执行的函数。
onActivated(): 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行。
onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数(以后用到再讲,不好展
现)。


Vue2--------------vue3
beforeCreate  -> setup()
created       -> setup()
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
activated     -> onActivated
deactivated   -> onDeactivated
errorCaptured -> onErrorCaptured
一 补充 uniapp 生命周期:常用的
    1. 应用生命周期:就是启动应用的时候会触发的生命周期主要有4个
        onLunch:应用初始化的时候触发,只触发一次
        onShow:从后台进入前台,触发多次
        onHide:前台进入后台,多次触发
        onError: app报错的时候会触发
        
    2. 页面生命周期:常用的有
        1. onLoad:监听页面加载,他的参数是上一个页面传递的数据,一般是在这里发请求uni.request()
        2. onShow:监听页面显式,每次出现在屏幕上都触发,触发多次
        3. onReady:监听页面初次渲染完成,就是所有的页面加载 包括请求完成,可以动态获取页面的高度
        4. onUnload: 一旦页面已卸载(或浏览器窗口已关闭),就会发生 *onunload* 事件
        5. onPullDownRefresh:用于下拉刷新
        6. onReachBottom: 用于上拉加载下一页数据
        
    3. 组件生命周期:跟vue是一样的
   
   第一次进入app触发5个生命周期:
      1. 应用生命周期 :onLunch onShow
      2. 页面生命周期: onLoad onShow onReady
   
   第二次触发:隐藏后台运行
      1. 应用生命周期 :onHide 
      2. 页面生命周期: onHide 
   
   第二次触发:从新打开app
      1. 应用生命周期 :onShow 
      2. 页面生命周期: onShow 

一补充 说一下vue双向数据绑定的原理

双向绑定,首先就是 数据更新能驱动视图更新,视图更新同时数据也更新。

答:核心主要利用ES5中的Object.defineProperty实现的,然后利用里面的getset来实现双向数据绑定的,
具体的就拿input输入框来举例吧:
首先要生明一个变量 let text
获取到inputdom节点:通过js的原生方法,document.queryselect(‘input’)

第二步就是采用input原有的方法oninput来创建一个函数,监听input输入框值的变化,函数体里面主要是获取到视图修改的值,赋值给修改Object.defineProperty要修改的属性名

Object.defineProperty(obj,'msg',{get(){},set(value){})
参数一:是一个对象,
参数二:是一个修改或新增对象的属性名
参数三:是一个对象,里面放getset函数来对 对象的数据进行读写操作来达到数据双向绑定

vue2.0 :缺点

因为es5的object.defineProperty无法监听对象属性的删除和添加
不能监听数组的变化,除了push/pop/shift/unshift/splice/spObject.definert/reverse,其他都不行
Object.defineProperty只能遍历对象属性直接修改(需要深拷贝进行修改)

vue3.0 双向绑定原理:

  Vue 3.0与Vue 2.0的区别仅是数据劫持的方式由Object.defineProperty更改为Proxy代理,其他代码不变。
  
  proxy优点:
  >1、直接监听对象而非属性
  >2、直接监听数组的变化
  >3、拦截的方式有很多种(有13种,set,get,has) 
  >4、Proxy返回一个新对象,可以操作新对象达到目的
  **1get(target, propKey, receiver):**

拦截对象属性的读取,比如`proxy.foo`和`proxy['foo']`。

**2set(target, propKey, value, receiver):**\
拦截对象属性的设置,比如`proxy.foo = v`或`proxy['foo'] = v`,返回一个布尔值。

**3has(target, propKey):**

拦截`propKey in proxy`的操作,返回一个布尔值。

**4deleteProperty(target, propKey):**

拦截`delete proxy[propKey]`的操作,返回一个布尔值。

**5ownKeys(target):**

拦截`Object.getOwnPropertyNames(proxy)`、`Object.getOwnPropertySymbols(proxy)`、`Object.keys(proxy)`、`for...in`循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而`Object.keys()`的返回结果仅包括目标对象自身的可遍历属性。

**6getOwnPropertyDescriptor(target, propKey):**

拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。

**7defineProperty(target, propKey, propDesc):**

拦截`Object.defineProperty(proxy, propKey, propDesc)`、`Object.defineProperties(proxy, propDescs)`,返回一个布尔值。

**8preventExtensions(target):**

拦截`Object.preventExtensions(proxy)`,返回一个布尔值。

**9getPrototypeOf(target):**

拦截`Object.getPrototypeOf(proxy)`,返回一个对象。

**10isExtensible(target):**

拦截`Object.isExtensible(proxy)`,返回一个布尔值。

**11setPrototypeOf(target, proto):**

拦截`Object.setPrototypeOf(proxy, proto)`,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。

**12apply(target, object, args):**

拦截`Proxy` 实例作为函数调用的操作,比如`proxy(...args)`、`proxy.call(object, ...args)`、`proxy.apply(...)`。

**13construct(target, args):**

拦截 `Proxy` 实例作为构造函数调用的操作,比如`new proxy(...args)`。
 

image.png

image.png

二 用过keep-alive吗?原理是什么?
用过,keep-alive 在view-route 中调用,是用来缓存组件的,避免组件内的数据重复渲染,提高性能,
项目中:订单页 和详情页 频繁切换,又不做数据修改,减少请求发送,就可以用keep-alive将页面组件缓存起来,

原理:
第一步:获取keep-alive包裹着的第一个子组件对象及其组件名;

第二步:根据设定的黑白名单include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例(VNode),否则执行第三步;

第三步:根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该key在this.keys中的位置(更新key的位置是实现LRU置换策略的关键),否则执行第四步;

第四步:在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max设置值,超过则根据LRU置换策略删除最近最久未使用的实例(即是下标为0的那个key);

第五步:最后并且很重要,将该组件实例的keepAlive属性值设置为trueLRU 缓存淘汰算法

LRULeast recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

<keep-alive :include="whiteList" :exclude="blackList" :max="amount">
     <component :is="currentComponent"></component>
</keep-alive>

**include**定义缓存白名单,keep-alive会缓存命中的组件;
**exclude**定义缓存黑名单,被命中的组件将不会被缓存;
**max**定义缓存组件上限,超出上限使用LRU的策略置换缓存数据。

image.png

路由中使用:

image.png

image.png

二(补充) 如果遇到二级路由访问页面,如何处理?
    比如:有这样的下拉选项:新闻      社会  房产
                             体育
                             财经
                             
         新闻下有(体育,财经),新闻整个下拉都用了keep-alive包裹,
         点击顺序是这样的:新闻   ---》体育 --》社会--->新闻,此时希望状态保存在体育上,此时是保存不了的,需要怎么办?
         
        解决:首先在社会这里使用组件内的路由钩子函数将新闻体育的路径保存下来
                beforeRouteLeave((to, from, next) => {
                let url = this.$route.path // 新闻体育的路径
                })
            再使用keep-alive生命周期,activated,将路由跳转过去
            activated(){
                    this.$router.push(url)
            }
三 组件之间是怎么通信的?

VUE2 父传子

 第一种:父传子:主要通过props来实现的
 方式一:自定义的组件
 父传子场景:一 :https://juejin.cn/post/6999929891516858398/
 具体实现:(注意:props数据 除了数组和对象可以修改里面的成员数据,其他类型不能修改的,比如字符串)
 
 自定义的组件 可以在父组件通过 v-module 来传参,子组件接收参数采用props,
 里面的value 就是父组件传过来的参数。
 通常value这个参数名称辨识度不高,会在props上加一个modle对象,
 参数一为prop,值就是用来设置参数名称的,
 参数二:event,设置 事件名称,最后再props对象上,将modle中prop的值 替换掉默认的value,
 这样就知道参数是什么,可读性更强。如果组件要传递事件,同样可以使用event这个名称来替换默认的input事件名称
 
 一个组件上只能使用一次v-module :如果有多个数据需要实现类似 v-modul的效果怎么办?
 可以使用 .sync 修饰符 例如 <text-document v-bind.sync="doc"></text-document>,
 这样会把 `doc` 对象中的每一个 property (如 `title`) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 `v-on` 监听器。
 
 
方式二:传统传参方式
具体实现:父组件通过import引入子组件,并注册,在子组件标签上 通过v-bind 绑定要传递的属性,子组件通过props接
收,接收有两种形式一是通过数组形式[‘要接收的属性’ ],二是通过对象形式{  }来接收,对象形式可以设置
要传递的数据类型和默认值,而数组只是简单的接收
 
父传子场景:二 
父组件传递 :search-text="searchText"
<search-result v-if="isResultShow" :search-text="searchText" />
子组件 接收:
  props: {
   searchText: {
      type: String,
      required: true
    }
},

方式三:边界情况的处理,采用依赖注入,也就是所,所有子组件都要使用父组件中的某个参数,
可以使用依赖注入的方式 来传递参数。https://juejin.cn/post/7000045773614219278/
 

 
父组件传参: 
  // 父组件给所有子组件传参:传递一个 文章id
  provide: function () {
    return {
      articleId: this.articleId
    }
  },
  
  子组件接收:
   // 接收父组件通过依赖注入传过来的 文章id参数
  inject: {
    articleId: {
      type: [Number, String, Object],
      default: null
    }
  },

provide 和 inject 并不是响应式的,单项数据流 ,改成响应式有两种方式 1.把要传递的参数,换成对象, 2 函数返回响应式 数据 image.png

VUE3 父传子

父组件:给子组件传参跟 vue2一样 子组件:接受参数用:defineProps

1. 父

    <template>
            <div>
                    <List :msg='msg'></List>
            </div>
    </template>

    <script setup>
    import List from '../components/List.vue'
    let msg = ref('这是父传过去的数据');
    </script>

2. 子

    <template>
            <div> 
                    这是子组件 ==> {{ msg }}
            </div>
    </template>

    <script setup>
    defineProps({
            msg:{
                    type:String,
                    default:'1111'
            }
    })
    </script>

VUE2 子传父 主要通过$emit来实现

具体实现:子组件通过绑定自定义事件触发函数,在其中设置this.$emit(‘要派发的自定义事件’,要传递的值),
\$emit中有两个参数一是要派发的自定义事件,第二个参数是要传递的值
然后父组件中,在这个子组件上v-on 简写@派发的自定义事件,绑定事件触发的methods中的方法接受的默认值,就是传递过来的参数

子传父场景:
场景一:
子组件传递 :$emit('search',text)
    有这种写法,直接绑定在click事件上 @click="$emit('search',text)"
父组件接收: @search="onSearch"
   <search-suggess v-else-if="searchText" :search-text="searchText" @search="onSearch" />

场景二:
删除所有历史记录,不能在当前组件删除,因为数据是通过prop接收父组件传递过来的,通过子传父来删除
子组件传递:
    <span @click="$emit('clear-history-text',[])">全部删除</span>

父组件接收:并清空历史记录数据==>@clear-history-text="SearchHistoryText = []"
  <search-history v-else :search-history="SearchHistoryText" @clear-history-text="SearchHistoryText = []" @search="onSearch" />



场景三 
子组件传递:  

image.png

父组件接收: 接收的时候直接是一个$even事件参数,然后以对象的形式获取需要的参数即可

image.png

VUE3 子传父 子组件通过 defineEmits 给父组件传值

子:
        <template>
                <div> 
                        这是子组件 ==> {{ num }}
                        <button @click='changeNum'>按钮</button>
                </div>
        </template>

        <script setup lang='ts'>
        let num = ref(200);

        const emit = defineEmits<{
          (e: 'fn', id: number): void
        }>()

        const changeNum = ()=>{
                emit('fn',num)
        }	
        </script>

父:
        <template>
                <div>
                        <List @fn='changeHome'></List>
                </div>
        </template>

        <script setup>
        import List from '../components/List.vue'
        const changeHome = (n)=>{
                console.log( n.value );
        }
        </script>

VUE2 兄弟之间传值

兄弟之间传值有用bus共文件方式:
具体实现:创建一个公共bus文件,
import Vue from 'vue';
export default new Vue

即当作两个组件的桥梁,在两个兄弟组件中分别引
入刚才创建的bus,在组件A中通过bus.$emit(’自定义事件名’,要发送的值)发送数据,
在组件B中通过bus.$on(‘自定义事件名‘,function(v) { //v即为要接收的值 })接收数据

VUE3 兄弟之间传值

1》下载安装

npm install mitt -S

2》plugins/Bus.js

        import mitt from 'mitt';
        const emitter = mitt()
        export default emitter;

3》A组件

        emitter.emit('fn',str);

4》B组件

        emitter.on('fn',e=>{
                s.value = e.value;
        })
四 (面试补充):父子组件优先级
1.父组件先初始化 beforecreate created  beforemount 
2.父组件渲染完成 render 
3.子组件开始初始化 beforecreate created  beforemount 
4.子组件渲染完成 render 
5.子组件先挂载完毕mounted
6.父组件挂载完毕mounted

以上这个过程是:
父组件先初始化-> 父组件渲染完成 -> 子组件开始初始化->子组件渲染完成->子组件先挂载完毕->父组件挂载完毕

这个过程会产生一个问题:子组件先挂载完毕 怎么知道父组件执行完成?
解决:
    1.在main.js挂载$bus到原型 vue.prototype.$bus = new Vue()
    2.在child组件监听
        mounted:{
        this.$bus.$on('parent',function(){
            console.log('父组件执行完成')
        })
        }
    3.在parent组件向外提交事件
        mounted:{
        this.$bus.$emit('parent')
        }
    
五 说一下vue路由钩子函数/路由导航守卫:
一共有3种,全局守卫  路由独享  组件内

第一种:全局守卫  
beforeEach((to, from, next) => {}) 
beforeResolve  全局解析守卫 在导航被确认之前,**同时在所有组件内守卫和异步路由组件被解析之后**,
               解析守卫就被调用。
router.afterEach((to, from) => {}) 全局后置钩子 这些钩子不会接受 `next` 函数也不会改变导航本身:

第二种:路由独享  
beforeEnter((to, from, next) => {})

第三种:组件内,我没有用过,因为他在项目中不好维护,比如在某个组件内写一个导航守卫,在后期项目维护不好找,所以我在项目中把所有用到的导航守卫都写在rooter的index.js文件中来统一管理,这里每一个路由的配置或者全局的都在这个文件中好维护,在项目中用到的导航守卫都是路由独享或者全局的。
我用到的主要场景就是,比如说一个用户要查询一个订单,首先要通过token来判断用户是否是登录状态,如果是登录的,就正常next()跳转到对应的查询订单页面,如果没有登录,就在判断里面拦截跳转到登录页面,采用router.push('/login'),登录完成之后才能进入查询订单页。这就是我在项目中用的导航守卫的其中一个场景,还有其他很多页面都需要验证登录来的,就在对应的路由页面加上你导航守卫就可以。



beforeRouteEnter((to, from, next) => {})
beforeRouteUpdate((to, from, next) => {})
beforeRouteLeave((to, from, next) => {})


全局守卫使用场景:进入地址管理 或 购物车页面 如果用户没有登录进入不了 ,
在进入之前判断拦截 需要先登录 https://juejin.cn/post/6998072290533441572

// 路由导航守卫 ==>全局守卫 
router.beforeEach((to, from, next) => {
  let nextRoute = ["Cartlist", "MyPath", "List"] // 被限制的 路由地址 【购物车 地址管理 分类】
  console.log(nextRoute.indexOf(to.name));
  let loginData = JSON.parse(localStorage.getItem('userData'))
  if (nextRoute.indexOf(to.name) >= 0) {
    // 判断是否登录
    if (!loginData) {
      router.push('/login')
    }
  }
  next()
})


每个守卫方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。

next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),
那么 URL 地址会重置到 from 路由对应的地址。

next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,
然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: truename: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。

next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,
则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
六 路由钩子函数和生命周期的先后顺序:

image.png

七 子路由(动态路由)
子路由,比如点击搜索框,跳转到一个单独的搜索页面,在路由表中通过children来配一下子path name
和component就可以了,都是为了更方便去管理路由,不需要单独写一个一级路由,一般项目中都会使用。

动态路由,项目中商品页有很多商品,点击每个商品会跳转到商品详情页,每个跳转都需要配置路由,
那就可以采用动态路由传参和监听路由变化的方式去跳转到对应页面,不然一个去写路由也不好维护,
相当于modules,可以一层一层嵌套方便数据维护管理。
八 router 路由跳转方式有哪些 怎么传参?
一  router-link 
    1. 不带参数
    <router-link :to="{name:'home'}">
    <router-link :to="{path:'/home'}"> //name,path都行, 建议用name

    2.带参数 有2种 相当于动态路由     路由组件传参https://router.vuejs.org/zh/guide/essentials/passing-props.html

     2.1 params传参 比如从文章列表页跳转到文章详情页,要带上文章id,
     需要在router上配置一个 path: '/article/:articleId',
     然后再router-link 写上 to 等于一个对象,的方式跳转 里面第一个参数就是配置跳转的路由的路由名称name,
     第二个参数params对象是路由的动态参数,带上文章id
    <router-link :to="{name:'home', params: {id:1}}">

    另外说明:
    params传参数 (类似post)
    路由配置 path: "/home/:id" 或者 path: "/home:id" 
    不配置path ,第一次可请求,刷新页面id会消失
    配置path,刷新页面id会保留

    html 取参  $route.params.id
    script 取参  this.$route.params.id


    2.2 query方式传参
    <router-link :to="{name:'home', query: {id:1}}">
    说明:
    query传参数 (类似get,url后面会显示参数)
    路由可不配置

    html 取参  $route.query.id
    script 取参  this.$route.query.id

二:vue2:this.$router.push()  
    vue3  useRouter ==> this.$router 
          useRoute  ==> this.$route
    
    router和route的区别
    router为VueRouter实例,想要导航到不同URL,则使用router.push方法
    route为当前router跳转对象里面可以获取name、path、query、params等
    

    1.  不带参数
    this.$router.push('/home')
    this.$router.push({name:'home'})
    this.$router.push({path:'/home'})

    2. query传参 
    this.$router.push({name:'home',query: {id:'1'}})
    this.$router.push({path:'/home',query: {id:'1'}})

    // html 取参  $route.query.id
    // script 取参  this.$route.query.id

    3. params传参 只能用 name

    this.$router.push({name:'home',params: {id:'1'}})  // 只能用 name

    // 路由配置 path: "/home/:id" 或者 path: "/home:id" ,
    // 不配置path ,第一次可请求,刷新页面id会消失
    // 配置path,刷新页面id会保留

    // html 取参  $route.params.id
    // script 取参  this.$route.params.id

    4. query和params区别
    query类似 get, 跳转之后页面 url后面会拼接参数,类似?id=1, 非重要性的可以这样传, 密码之类还是用params刷新页面id还在

    params类似 post, 跳转之后页面 url后面不会拼接参数 , 但是刷新页面id 会消失

 
注意:这里开启了 props: true 

1. 
{
  // 文章详情页路由
  path: '/article/:articleId',
  name: 'article',
  component: () => import('@/views/article'),
  props: true    // 将路由动态参数映射到组件的 props 中,更推荐这种做法
}

2. 然后在组件上, 配置带参数的路由跳转方式
:to="{name:'article',params:{articleId:article.art_id}}" //  components/article-item/index.vuethis.$router.replace() (用法同上,push)

四  this.$router.go(n)  向前或者向后跳转n个页面,n可为正整数或负整数

ps : 区别

this.$router.push
跳转到指定url路径,并向history栈中添加一个记录,点击后退会返回到上一个页面

this.$router.replace
跳转到指定url路径,但是history栈中不会有记录,点击返回会跳转到上上个页面 (就是直接替换了当前页面)

this.$router.go(n)
向前或者向后跳转n个页面,n可为正整数或负整数
九 前端路由模式有哪些? 区别
新答案:

路由模式有俩种:historyhash
区别:
   
    1. 跳转请求
        history : http://localhost:8080/id   ===>发送请求
        hash 	  : 不会发送请求
    2. 打包后前端自测要使用hash,如果使用history会出现空白页,需要后端配置代理。
   

 
旧答案: 
第一种 hash模式

原理:  在 url 中的 # 之后对应的是 hash 值, 其原理是通过hashChange() 事件监听hash值的变化, 
根据路由表对应的hash值来判断加载对应的路由加载对应的组件
优点:
(1) 只需要前端配置路由表, 不需要后端的参与
(2) 兼容性好, 浏览器都能支持
(3) hash值改变不会向后端发送请求, 完全属于前端路由
**缺点:**
(1) hash值前面需要加#, 不符合url规范,也不美观
window.onhashchange = function (event) {
  console.log(event.oldURL, event.newURL);
  let hash = location.hash.slice(1);
  document.body.style.color = hash;
};
上面的代码可以通过改变hash来改变页面字体颜色,虽然没什么用,但是一定程度上说明了原理。
 
第二种:history 
  原理: history API 是 H5 提供的新特性,允许开发者直接更改前端路由,
   即`更新浏览器 URL 地址`而`不重新发起请求`(将url替换并且不刷新页面)

优点:
(1) 符合url地址规范, 不需要#, 使用起来比较美观
 缺点:
(1) 在用户手动输入地址或刷新页面时会发起url请求, 后端需要配置index.html页面用户匹配不到静态资源的情况, 
否则会出现404错误
(2) 兼容性比较差, 是利用了 HTML5 History对象中新增的 pushState() 和 replaceState() 方法,需要特定浏览器的支持.

只能改变#后面的url片段**,history api可以分为两大部分:切换和修改

(1)切换历史状态

  包括`back、forward`、`go`三个方法,对应浏览器的前进,后退,跳转操作
    history.go(2);//前进两次
    history.back(); //后退
    hsitory.forward(); //前进
(2)修改历史状态

  包括了`pushState、replaceState`两个方法,这两个方法接收三个参数:stateObj,title,url
十: SPA单页面应用 和传统页面跳转有什么区别?
新答案: 多页面seo比单页面seo要好,爬虫收录多
        传统的页面内容过于庞大
        单页面应用因为不涉及打开一个新网页,所以用户体验可能好一些
        后台管理系统是不需要收录的,所以单页面和多页面都可以。
        
SEO新答案:
1. 网站一定要多页面
2. <meta keywords>  <meta description> <title> 
3. 图片、音频、视频、的标签属性特别关键
4. 网站不能出现死链接
十一 axios 十进行二次封装的吗?是怎么封装的?
import axios from "axios"
import store from '@/store'
const request = axios.create({
  baseURL: "http://toutiao-app.itheima.net/", // 基础路径
})


//2  添加请求拦截器的理解:在项目中对后端http请求和响应自动拦截处理,减少请求和响应的代码量,
提升开发效  率同时也方便项目后期维护

例如 在请求拦截器中处理了 所有授权的接口,发送请求的时候都要带上 token的情况
request.interceptors.request.use(function (config) {
  const {
    user
  } = store.state
  // 在发送请求之前做些什么
  if (user && user.token) {
    config.headers.Authorization = `Bearer ${user.token}`
  }
  return config;
}, function (error) {
  // 对请求错误做些什么
  return Promise.reject(error);
});

// 响应拦截器
// request.interceptors.request.use(function (res) {
  const code = res.data.code || 200
})

 export default request
 

image.png

image.png

image.png

十二(面试补充)知道options请求方式吗?什么情况下会出现这种请求

OPTIONS请求方法的主要用途有两个:

1、获取服务器支持的HTTP请求方法;也是黑客经常使用的方法。

2、用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,
需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。

总之,OPTIONS请求相当于一个检测目标是否安全的操作,类似于心跳机制。
所以我们在后台拦截器里面应该把这个请求过滤掉。
public void doFilter(ServletRequest servletRequest, 
                      ServletResponse servletResponse, 
                      FilterChain filterChain) 
throws IOException, ServletException {

        if("OPTIONS".equals(httpRequest.getMethod())){
           filterChain.doFilter(httpRequest, httpResponse);
           return;
       }

}

十三 说一下防抖节流

项目中:用户名密码登录使用了,uview提供好的。
防抖:(只执行最后一次)用户触发事件过于频繁,只要最后一次事件的操作,比如:实时搜索,拖拽
    
   防抖代码:
    	<input type="text">
            
	let inp = document.querySelector('input')
	inp.oninput = debounce(function () {
		console.log(this.value);
	}, 500)

	function debounce(fn, delay) {
		let t = null;
		return function () {
			if (t !== null) {
				clearTimeout(t)
			}
			t = setTimeout(() => {
				fn.call(this);
			}, delay);
		}
	}



节流:(控制执行次数) 短时间内大量执行事件,就只执行一次(比如页面滚动)

window.onscroll = throttle(function () {
        console.log("hello");
}, 500)

function throttle(fn, delay) {
    let flag = true;
    return function () {
        if (flag) {
            setTimeout(() => {
                fn.call(this)
                flag = true
            }, delay);
        }
        flag = false
    }
}




防抖:在搜索框 会监听搜索内容的变化,有变化就发送请求,这样比较耗费资源,
使用第三方工具包 lodash 的 debounce 方法,在设定的防抖时间内输入内容,不需要发送请求,直到停下的时候,
超过设定的时间到了才发送请求,没停下之前,一直输入是不会发送请求的
应用场景:实时搜索,拖拽
完整的写法要写上handler 才能配置 immediate: true 和 deep:true

// debounce 函数
// 参数1:函数
// 参数2:防抖时间
// 返回值:防抖之后的函数,和参数1功能是一样的

 toutiao-m: views search search-suggess.vue
    // 子组件监听父组件搜索框内容的变化
    // lodash 支持按需加载,有利于打包结果优化  1、安装 lodash
    import { debounce } from "lodash"
  watch: {
    searchText: {
      // handler(value) { 
      //   console.log(value)
      //   this.loadSearchSuggess(value) // 调用搜索请求
      // },
      handler: debounce(function (value) {  // debounce 是防抖优化的功能 1、安装 lodash
        // console.log(value)
        this.loadSearchSuggess(value)
      }, 200),
      immediate: true // 该回调将会在侦听开始之后被立即调用
    }
  },

十四 说一下你对vuex 和 pinia的理解:

vuex是一个状态管理工具,主要解决大中型复杂项目的数据共享问题,主要包括
state 存放公共数据, 调用 state : this.$store.state.changeValue
mutations, 存放同步方法,更新state中的数据,
actions, 存放异步方法,状态提交,因为vuex是单向数据流,不能直接修改state的数据,否则报错,如果用v-module来修改,也会报错,就可以确定是单向数据流了,通过commit对mutations进行提交,访问组件通过 this.$store.dispach('方法名',参数)
getters,获取state中的数据, 调用 getters: this.$store.getters.getlists
modules 5个要素

 
数据刷新页面消失,需要用localstorage对数据进行持久化
或者 vuex-persistedstate || pinia-plugin-persist安装插件 完成后需要在 new Vuex.store中添加配置 plugins:[]
persistedstate 本质也是用了localstorage


vue3 
	Vuex和pinia的区别

		参考网址: https://github.com/vuejs/rfcs/pull/271 

		1. pinia没有mutations,只有:state、getters、actions
		2. pinia分模块不需要modules(之前vuex分模块需要modules)
		3. pinia体积更小(性能更好)
		4. pinia可以直接修改state数据

	14.2 pinia使用

		官方网址:https://pinia.vuejs.org/

		具体使用:https://xuexiluxian.cn/blog/detail/242b0ed71feb412991f04d448fc86636

	14.3 pinia持久化存储 插件 :pinia-plugin-persist

		参考链接:https://xuexiluxian.cn/blog/detail/acebacd99612447e8c80dcf6354240f6
   

image.png

使用vue-cookie插件来做存储

image.png

modules:{} 使用场景:当state中的数据 特别多不方便管理的时候 使用modules,分别有一套 state getters mutations actions数据,并再store的index.js引入这个js文件,并挂载到modules上
moudles上还有一个命名空间:namespace

十五 说一下mutions 和 action 区别:

1 .mutions  可以直接改变state中的状态,action是通过commit提交mutions,在mutions中改变 state状态
2. mutions 必须是同步函数,会有一个问题(假如修改mutions里有个方法修改 state数据,这个方法加了一个定时器延迟1秒,
    页面渲染的数据和实际的值就不相同,因为是同步的,就有这样一个问题)

   action 可以包含任何异步操作:这样可以解决mutions里的方法加了定时器后:视图和数据不同步的问题,更加容易调试
   

十六 methods ,computed(原理) 和 watch 有什么区别,使用场景有哪些?

computed :主要用于简单运算
    本质是一个惰性更新的观察者,可以使用在有多个值可能修改的情况,比如,购物车上商品总计,
    需要用到单价和数量的一个变化,如果总价需要多次调用,那computed只执行一次,
    剩下的次数都是走缓存的,前提保障单价和数量的数据没有被修改的,这样就可以提高一些性能。
 
watch:
    是用来监听数据和路由的变化,watch的特点是,如果监听数据,没有改变的情况下他是无法监听到的,
    可以通过使用handle里面配置immediate,就可以立刻监听到数据的变化,同时watch也可以配置深度监听,
    还有涉及到页面路由的切换,例如项目中点击某个菜单,可能涉及到路由切换并且通过路径传值,就可以使用watch来监听获取到路径的值,来发送数据
    这些都是计算属性无法做到的
    
methods:
    没有缓存,这样就可以看出computed性能要比watch好。

从vue的源码里可看出到他们的一个优先级:props ===>  methods ===> data ===> computed ===>watch

computed 原理说一下:
    内部实现了一个惰性的 watcher,也就是 computed watcher,computed watcher 不会立刻求值,同时持有一个 dep 实例。
    其内部通过 this.dirty 属性标记计算属性是否需要重新求值。
    当 computed 的依赖状态发生改变时,就会通知这个惰性的 watcher,
    computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
    有的话,会重新计算,然后对比新旧值,如果变化了,会重新渲染。 
    没有的话,仅仅把 this.dirty = true。

   
   watch 配置   [ handler , deep 是否深度 ,immeditate 是否立即执行]
   使用场景:监听搜索记录保存到本地存储  或 监听组件搜索框内容的变化
    watch: {
    SearchHistoryText(value) {
      console.log(123);
      setItem('TOUTIAO_SEARCH_HISTORY', value)
     }
    }

toutiao-m: views search search-suggess.vue
// 子组件监听父组件搜索框内容的变化
// lodash 支持按需加载,有利于打包结果优化  1、安装 lodash
import { debounce } from "lodash"
watch: {
searchText: {
  // handler(value) {
  //   console.log(value)
  //   this.loadSearchSuggess(value) // 调用搜索请求
  // },
  handler: debounce(function (value) {  // debounce 是防抖优化的功能 1、安装 lodash
    // console.log(value)
    this.loadSearchSuggess(value)
  }, 200),
  immediate: true // 该回调将会在侦听开始之后被立即调用
}
},


注意:不应该使用箭头函数来定义 watcher 函数,理由是箭头函数绑定了父级作用域的上下文,
所以 `this` 将不会按照期望指向 Vue 实例,`this.methodsName` 将是 undefined。

十七 说一下 v-if 和 v-show 作用区别 和 使用场景是什么

 1.作用区别:
v-if 在切换过程中 是创建和销毁元素,并且是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始创建元素。
 
v-show 就简单得多, 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 display 属性进行切换,显式display:block 和  隐藏:display:none
不支持 template元素 和v-else

所以,v-if 适用于不需要频繁切换条件的场景;
v-show 则适用于需要非常频繁切换条件的场景。

2. 使用场景:
v-if: 首页的tab导航栏,每个导航栏对应的页面都会加载很多内容,根据v-if是创建和销毁的特点,减少开销,没必要默认加载那么多内容
v-show: 比如购物车里,点击加入购物车 有一个弹窗选择商品规格内容,或 分享,蒙层 之类的 弹窗,会频繁切换可以使用v-show

十八 v-if 和 v-for 优先级

VUE2
当它们处于同一节点,v-for的优先级比v-if更高,如果目的是有条件的跳过循环执行,可以将v-if置于外层节点上
VUE3
v-if  > v-for

十九 v-for 中的 为什么要设置唯一的 key,原理 如果是相同的key怎么办?

其实在vue中 有一个默认就地复用策略,在dom操作的时候,方便 Vue内部机制精准找到列表数据,
来进行新旧状态值对比,这样列表的顺序就不会发生错乱,提高渲染性能 。
比如在一组商品列表中选中了某个商品,位置是第二个,这时候向数组新添加商品进去,会发现原来选中的商品已经不是处于被选中状态。

原理:
    因为vue在patch过程中,就是diff算法里面通过key可以判断两个虚拟节点vNode是否相同,
    如果相同就可以复用老节点,如果没有key 就导致更新列表的时候出错。

补充 虚拟dom

    虚拟dom是一个js对象
    在vue中是由render函数生成的,如果组件修改了数据,render函数就会生成一个新的虚拟dom,
    在js中如果用原生方法来操作dom,浏览器会从dom树开始 从头到尾执行一边
    根据diff算法对新旧虚拟dom节点进行比对,dom没有更新就会复用,如果有更新就把需要更新的一次性追加到真实的dom树上。
    批量操作中性能体现更明显。同时还可以跨平台。

image.png

二十 Vue 组件中 data 为什么必须是函数

首先组件是可以共享的,为了不和其他组件共用一个对象。
object是引用类型,每个对象的data是指向同一个地址,一个值变,这个属性就发生变化,导致其他对象的该属性也发生变化。
js中有关作用域,只有函数,data是一个函数的话,每个对象都是独立的,而对象的函数也是独立的,函数中的数据也是独立的,所以data必须是一个函数。

二十一 说一下vue过滤器做什么的(vue1.x和vue2.x这块的区别)

vue过滤器主要用于对渲染出来的数据进行格式化处理
例如:项目中需要将日期时间处理成相对时间,就是 xx年前 或多少天前 

全局过滤器:
Vue.filter(‘过滤器名’,function(参数1,参数2,…) {
  return 要返回的数据格式
})

局部过滤器:在组件内部添加filters属性来定义过滤器

fitlers:{
   过滤器名(参数1,参数2,,…参数n) {
       //………..
      return 要返回的数据格式
    }
}

二十三 跨域 域名端口协议

jsonp跨域 cors跨域

   首先跨域的产出是违反了同源策略:同域名,同端口,同协议

   跨域方式有几种,
   第一种是jsonp
   1.创建一个script标签,声明一个回调函数,
   2.创建一个script标签把那个跨域的api数据接口地址赋值给script的src 还要在这个地址中向服务器传递该函数名
   3. 只支持get请求。
   
   第二种:cors 跨域资源共享,目前是支持所有主流浏览器 IE9+
   1. 支持所有类型的HTTP请求,
   2. 分为简单请求 和非简单请求
   3. 简单请求就是同时符合两种要求的:
       3.1 请求方法为:- HEAD - GET - POST
       3.2 HTTP的头信息不超出以下几种字段
       - Accept 
       - Accept-Language 
       - Content-Language 
       - Last-Event-ID 
       - Content-Type:
           只限于三个值`application/x-www-form-urlencoded``multipart/form-data``text/plain`
    4. 简单请求:基本流程,具体来说,就是在headers头信息之中,增加一个`Origin`字段,
        用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。 
        服务器根据这个值,决定是否同意这次请求,服务器返回的响应,会包含其中一个头信息字段
        Access-Control-Allow-Origin** 它的值要么是请求时`Origin`字段的值,要么是一个`*`,表示接受任意域名的请求。
    
   5.非简单请求:
       那种对服务器有特殊要求的请求,比如请求方法是`PUT``DELETE`,
       或者`Content-Type`字段的类型是`application/json`,
       会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求,
       浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,
       以及可以使用哪些HTTP动词和头信息字段。
       只有得到肯定答复,浏览器才会发出正式的`XMLHttpRequest`请求,否则就报错。
       "预检"请求用的请求方法是`OPTIONS`,表示这个请求是用来询问的。头信息里面, 
       关键字段是`Origin`,表示请求来自哪个源。
   
       与JSONP的比较 CORSJSONP的使用目的相同,但是比JSONP更强大。 
       JSONP只支持`GET`请求,CORS支持所有类型的HTTP请求。
       JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
  
   
开发环境对vue,vue本身可以通过代理的方式可以实现,具体实现:
在config中的index.js中配置proxy来实现:target changeOrigin pathRewrite

跨域如何携带cookie?

image.png image.png

生产环境解决跨域问题:

image.png

image.png image.png

image.png

image.png

image.png

二十四 vue如何动态添加属性,实现数据响应?

答:vue主要通过用this.$set(对象,‘属性‘,值)实现动态添加属性,
以实现数据的响应注意是添加,我记忆中如果是修改引用类型属性的值,是可以自动渲染的.

二十五 webpack 和 vite 工作原理

 1.所有的文件从入口文件出发,根据入口文件的依赖关系,找到所有的模块,
 2. 调用各种loader进行处理,常处理的文件类型有 img,js json css 字体,或其他的
 3.如果处理不了的就调用babel进行语法兼容转换,babel-loader core(IE浏览器无法兼容[es6]) preset(预设置), es6转es5,像里面的promise,Generator map,这些都转换成es5语法
 4. 在这个过程中还有一些像html模板,压缩或其他的一些东西,功能性的,可能需要调用plugin插件,htmlwebpackplugin
 5. 根据出口的路径编译打包多个chunk,如果把css 或者 js这些模块都摘出去了,需要单个打包,就打包了多个chunk,最后统一生成一个列表输出到bundle中,这就是打包的工作原理。
 6. 工程化,就是一种思想,让项目按照标准化,模块化,自动化方式进行编码,这就是工程化,现在用的最好的就是美团,vue+anguage+西瓜ui+electronic
 

webpack插件:待补充功能点:插件有哪些,loader有哪些?babel有哪些?

image.png

以前的答案:不好答
答:我们公司用的vue官方的脚手架(vue-cli),vue-cli版本有3.02.9.x版本
webpack是一个前端模块化打包构建工具,vue脚手架本身就用的webpack来构建的,w
ebpack本身需要的入口文件通过entry来指定,出口通过output来指定,默认只支持js文件,
其他文件类型需要通过对应的loader来转换,例如:less需要less,less-loader,sass需要sass-loader,
css需要style-loader,css-loader来实现。当然本身还有一些内置的插件来对文件进行压缩合并等操作
postcss-loader:做浏览器兼容, 内部依赖autoprefixer插件

vite:
比webpack启动速度快,因为在启动的时候不需要进行打包,也就是不需要分析模块的依赖 编译。
修改了对应的文件就直接进行对应的更新,webpack 会对所有的依赖模块都重新编译一次。
同时也没有像webpack这样 需要了解庞大的 plugin 和 loader 生态。

二十六 说一下vue封装组件中的slot作用

1.封装了倒计时组件
2.为什么要封装,ui组件不是都有吗?
    因为我们都逻辑中,要进行其他计算,算出现在的时间和订单生成的时间,剩余多少毫秒,ui库的组件不满足我的需求。
项目中,有些组件布局可能大多一致,但是有些细微变化 可以用slot插槽,
比如手机端的头部导航来 通常都是左中右布局。左边一个返回箭头 中间 input搜索框 右边一个图标
具名插槽:在template中 通过名称来使用
作用域插槽:比如父组件想调用子组件data中的数据
子组件:发送数据给父组件 <slot :user='user'></slot>
父组件:通过template接收数据
        <template v-slot:default={user}> 
            {{user.age}}
        </template>

分为具名的slot和匿名的slot 使用的时候 在template中 直接用 或 通过名称来使用

二十八 说一下你对单向数据流和双向数据流的理解

答:**单向数据流**主要是vue 组件间传递数据是单向的,即数据总是由父组件传递给子组件,子组件在其内部维护自己的数据,但它无权修改父组件传递给它的数据,当开发者尝试这样做的时候,vue 将会报错。这样做是为了组件间更好的维护。

在开发中可能有多个子组件依赖于父组件的某个数据,假如子组件可以修改父组件数据的话,一个子组件变化会引发所有依赖这个数据的子组件发生变化,所以 vue 不推荐子组件修改父组件的数据

双向数据流待补充:

二十九 说一下vue自定义指令如何实现的和适用场景?

全局自定义指令:Vue.directive(‘指令名’,{ inserted(el) {  }  })

image.png 局部自定义指令:directives:{  }

image.png

~~ 废弃场景待补充:在用户登录的时候 头像图片无法获取,可以使用自定义指令,添加默认的头像~~

三十 说一下vue最大特点是什么或者说vue核心是什么

答:vue最大特点我感觉就是“组件化“和”数据驱动“

   组件化就是可以将页面和页面中可复用的元素都看做成组件,写页面的过程,就是写组件,然后页面是由这些组件“拼接“起来的组件树

   数据驱动就是让我们只关注数据层,只要数据变化,页面(即视图层)会自动更新,至于如何操作dom,完全交由vue去完成,咱们只关注数据,数据变了,页面自动同步变化了,很方便

三十一 说一下vue常用基本指令有哪些

v-if v-show v-for v-bind v-on v-module

三十二 说一下vue开发环境和线上环境如何切换

主要通过检测process.env.NODE_ENV===”production”和process.env.NODE_ENV===”development”环境,
来设置线上和线下环境地址,从而实现线上和线下环境地址的切换

三十三 nextTick()有用过吗?原理是什么?

用过,在项目中使用better-scroll插件的时候,当dom加载完毕 再执行滑动操作,其实就是用来知道什么时候DOM更新完成的,

原理:
 Vue异步执行DOM更新,只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,只会被推入到队列中一次。

image.png

image.png

兼容环节问题

算法:

一数组排序:

image.png

二、数组去重:

第一种:利用ES6的set来实现 例如:[...**new** Set(arr)]
第二种:借用临时对象的方式

image.png

查找多维数组的最大值:

function fnArr(arr){
        var newArr = [];
        arr.forEach((item,index)=>{
                newArr.push( Math.max(...item)  )
        })
        return newArr;
}
console.log(fnArr([
        [4,5,1,3],
        [13,27,18,26],
        [32,35,37,39],
        [1000,1001,857,1]
]));

面试结束环节:

面试官:(假装问)你还有什么想问的吗?

我:当知道自己面试即使不好的时候 ,还是要假装迎合随便问几个问题:
1. 想了解一下公司部门有多少位同事?
2. 目前在做什么类型的项目,如果进入公司是做二次开发项目还是新的项目?用的什么技术栈选型?
   等他说完 就假装说:如果入职可以提前准备 不耽误工作进度
3.公司的发展情况

你能为公司带来什么?

1.首先前端可以把页面布局的更合理,页面效果做的更好,兼容多款浏览器,适配各种型号的手机,以及多媒体设备,在数据交互方面处理的更好,在这个过程中就知道,处理的数据多了之后,与后台交互的时候,出了问题,可以快速定位错误,到底是前端还是后端的问题,对于项目的优化,css js一系列的优化,还有一些常见的接口安全攻防等,就是我能为公司带来的。

你希望公司能带来什么?

1.在公司不断的工作当中,接触到很多技术的东西,可以把技术进一步提高一些
2. 我相信通过自己的努力,如果公司有合理的晋升制度,我愿意跟着公司一起去成长打拼共同成长。
这就是公司能给我带来什么。