IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    前端Vue必问面试题

    xiangzhihong发表于 2023-12-18 09:55:06
    love 0

    1,Vue3.0 为什么要使用 proxy

    在 Vue2 中, 0bject.defineProperty 会改变原始数据,而 Proxy 是创建对象的虚拟表示,并提供 set 、get 和 deleteProperty 等处理器,这些处理器可在访问或修改原始对象上的属性时进行拦截,有以下特点∶

    • 不需用使用 Vue.$set 或 Vue.$delete 触发响应式。
    • 全方位的数组变化检测,消除了Vue2 无效的边界情况。
    • 支持 Map,Set,WeakMap 和 WeakSet。

    Proxy 实现的响应式原理与 Vue2的实现原理相同,实现方式大同小异∶

    • get 收集依赖
    • Set、delete 等触发依赖
    • 对于集合类型,就是对集合对象的方法做一层包装:原方法执行后执行依赖相关的收集或触发逻辑。

    2,谈谈你对slot的理解,以及slot的使用场景

    在HTML中 slot 元素 ,作为 Web Components 技术套件的一部分,是Web组件内的一个占位符。该占位符可以在后期使用自己的标记语言填充:

    <template id="element-details-template">
      <slot name="element-name">Slot template</slot>
    </template>
    <element-details>
      <span slot="element-name">1</span>
    </element-details>
    <element-details>
      <span slot="element-name">2</span>
    </element-details>

    template不会展示到页面中,需要用先获取它的引用,然后才会添加到DOM中。

    customElements.define('element-details',
      class extends HTMLElement {
        constructor() {
          super();
          const template = document
            .getElementById('element-details-template')
            .content;
          const shadowRoot = this.attachShadow({mode: 'open'})
            .appendChild(template.cloneNode(true));
      }
    })

    Slot 艺名插槽,花名“占坑”,我们可以理解为solt在组件模板中占好了位置,当使用该组件标签时候,组件标签里面的内容就会自动填坑(替换组件模板中slot位置)。

    通过插槽可以让用户可以拓展组件,去更好地复用组件和对其做定制化处理。如果父组件在使用到一个复用组件的时候,获取这个组件在不同的地方有少量的更改,如果去重写组件是一件不明智的事情。通过slot插槽向组件内部指定位置传递内容,完成这个复用组件在不同场景的应用

    3,Vue渲染大量数据时怎么进行优化。

    企业级项目中渲染大量数据的情况比较常见,因此这是前端面试中一个必问的题目。对于这个题目,我们可以从以下几个方面进行考虑:

    • 在大型企业级项目中经常需要渲染大量数据,此时很容易出现卡顿的情况。比如大数据量的表格、树。
    • 处理时要根据情况做不同处理。
    • 可以采取分页的方式获取,避免渲染大量数据。
    • vue-virtual-scroller (opens new window)等虚拟滚动方案,只渲染视口范围内的数据。
    • 如果不需要更新,可以使用v-once方式只渲染一次。
    • 通过v-memo (opens new window)可以缓存结果,结合v-for使用,避免数据变化时不必要的VNode创建。
    • 可以采用懒加载方式,在用户需要的时候再加载数据,比如tree组件子树的懒加载。

    还是要看具体需求,首先从设计上避免大数据获取和渲染;实在需要这样做可以采用虚表的方式优化渲染;最后优化更新,如果不需要更新可以v-once处理,需要更新可以v-memo进一步优化大数据更新性能。其他可以采用的是交互方式优化,无线滚动、懒加载等方案。

    4,Scoped样式穿透是什么

    scoped 是 style 标签的一个属性,当在 style 标签中定义了 scoped 时,style 标签中的所有属性就只作用于当前组件的样式,实现组件样式私有化,从而也就不会造成样式全局污染。

    项目开发中,多数情况下不能避免引用第三方组件,而第三方组件的样式又不全是我们想要的,就需要在组件中局部修改第三方组件的样式,但同时又不想去除 scoped 属性和避免样式污染。此时只能通过穿透 scoped,写法如下。

    <style scoped>
     外层 > 第三方组件 {
      样式
     }
    </style>

    5,谈谈你对Vue、Angular以及React的理解

    首先,我们来看一下Vue与AngularJS的区别

    • Angular采用TypeScript开发, 而Vue可以使用javascript也可以使用TypeScript
    • AngularJS依赖对数据做脏检查,所以Watcher越多越慢;Vue.js使用基于依赖追踪的观察并且使用异步队列更新,所有的数据都是独立触发的。
    • AngularJS社区完善, Vue内置了很多默认的模版和语法,学习成本较小。

    接下来,我们看一下Vue与React的区别

    相似点:

    • Virtual DOM。其中最大的一个相似之处就是都使用了Virtual DOM,也就是能让我们通过操作数据的方式来改变真实的DOM状态。因为其实Virtual DOM的本质就是一个JS对象,它保存了对真实DOM的所有描述,是真实DOM的一个映射,所以当我们在进行频繁更新元素的时候,改变这个JS对象的开销远比直接改变真实DOM要小得多。
    • 组件化开发。它们都提倡这种组件化的开发思想,也就是建议将应用分拆成一个个功能明确的模块,再将这些模块整合在一起以满足我们的业务需求。
    • Props。Vue和React中都有props的概念,允许父组件向子组件传递数据。
    • 构建工具、Chrome插件、配套框架。还有就是它们的构建工具以及Chrome插件、配套框架都很完善。比如构建工具,React中可以使用CRA,Vue中可以使用对应的脚手架vue-cli。对于配套框架Vue中有vuex、vue-router,React中有react-router、redux。

    不同点:

    • 模版编写。最大的不同就是模版的编写,Vue鼓励你去写近似常规HTML的模板,React推荐你使用JSX去书写。
    • 状态管理与对象属性。在React中,应用的状态是比较关键的概念,也就是state对象,它允许你使用setState去更新状态。但是在Vue中,state对象并不是必须的,数据是由data属性在Vue对象中进行管理。
    • 虚拟DOM的处理方式不同。Vue中的虚拟DOM控制了颗粒度,组件层面走watcher通知,而组件内部走vdom做diff,这样,既不会有太多watcher,也不会让vdom的规模过大。而React走了类似于CPU调度的逻辑,把vdom这棵树,微观上变成了链表,然后利用浏览器的空闲时间来做diff。

    6,Vue是如何解决跨域问题的

    跨域本质是浏览器基于同源策略的一种安全手段同源策略(Sameoriginpolicy),是一种约定,它是浏览器最核心也最基本的安全功能。

    所谓同源(即指在同一个域)具有以下三个相同点

    • 协议相同(protocol)
    • 主机相同(host)
    • 端口相同(port)

    反之非同源请求,也就是协议、端口、主机其中一项不相同的时候,这时候就会产生跨域。

    Vue解决跨域的方法有很多,下面列举了三种:

    • JSONP
    • CORS
    • Proxy

    而在Vue项目中,我们主要针对CORS或Proxy这两种方案进行展开:

    CORS

    CORS (Cross-Origin Resource Sharing,跨域资源共享)是一个系统,它由一系列传输的HTTP头组成,这些HTTP头决定浏览器是否阻止前端 JavaScript 代码获取跨域请求的响应CORS 实现起来非常方便,只需要增加一些 HTTP 头,让服务器能声明允许的访问来源,只要后端实现了 CORS,就实现了跨域。

    app.use(async (ctx, next)=> {
      ctx.set('Access-Control-Allow-Origin', '*');
      ctx.set('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
      ctx.set('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
      if (ctx.method == 'OPTIONS') {
        ctx.body = 200; 
      } else {
        await next();
      }
    })

    Proxy

    代理(Proxy)也称网络代理,是一种特殊的网络服务,允许一个(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。一些网关、路由器等网络设备具备网络代理功能。一般认为代理服务有利于保障网络终端的隐私或安全,防止攻击。

    方案一

    如果是通过vue-cli脚手架工具搭建项目,我们可以通过webpack为我们起一个本地服务器作为请求的代理对象。通过该服务器转发请求至目标服务器,得到结果再转发给前端,但是最终发布上线时如果web应用和接口服务器不在一起仍会跨域。

    在vue.config.js文件,新增以下代码:

    amodule.exports = {
        devServer: {
            host: '127.0.0.1',
            port: 8084,
            open: true,// vue项目启动时自动打开浏览器
            proxy: {
                '/api': { // '/api'是代理标识,用于告诉node,url前面是/api的就是使用代理的
                    target: "http://xxx.xxx.xx.xx:8080", //目标地址,一般是指后台服务器地址
                    changeOrigin: true, //是否跨域
                    pathRewrite: { // pathRewrite 的作用是把实际Request Url中的'/api'用""代替
                        '^/api': "" 
                    }
                }
            }
        }
    }

    通过axios发送请求中,配置请求的根路径。

    axios.defaults.baseURL = '/api'

    方案二

    通过配置nginx实现代理:

    server {
        listen    80;
        # server_name www.josephxia.com;
        location / {
            root  /var/www/html;
            index  index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
        location /api {
            proxy_pass  http://127.0.0.1:3000;
            proxy_redirect   off;
            proxy_set_header  Host       $host;
            proxy_set_header  X-Real-IP     $remote_addr;
            proxy_set_header  X-Forwarded-For  $proxy_add_x_forwarded_for;
        }
    }

    7,Class 与 Style 如何实现动态绑定

    Class 可以通过对象语法和数组语法进行动态绑定,比如:

    //对象语法
    <div v-bind:class="{ active: isActive, 'text-danger': hasError }"></div>
    data: {
      isActive: true,
      hasError: false
    }
    //数组语法
    <div v-bind:class="[isActive ? activeClass : '', errorClass]"></div>
    data: {
      activeClass: 'active',
      errorClass: 'text-danger'
    }

    Style 也可以通过对象语法和数组语法进行动态绑定,比如:

    //对象语法
    <div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
    data: {
      activeColor: 'red',
      fontSize: 30
    }
    //数组语法
    <div v-bind:style="[styleColor, styleSize]"></div>
    data: {
      styleColor: {
         color: 'red'
       },
      styleSize:{
         fontSize:'23px'
      }
    }

    8,为什么要使用函数式组件,有什么优势

    函数组件的特点:

    • 函数式组件需要在声明组件是指定 functional:true。
    • 不需要实例化,所以没有this,this通过render函数的第二个参数context来代替。
    • 没有生命周期钩子函数,不能使用计算属性,watch。
    • 不能通过$emit 对外暴露事件,调用事件只能通过context.listeners.click的方式调用外部传入的事件。
    • 因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement。
    • 函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式。解析为prop,而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)。

    相比普通的类组件,函数组件有如下的一些优势:

    • 由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件;
    • 函数式组件结构比较简单,代码结构更清晰;
    Vue.component('functional',{ // 构造函数产生虚拟节点的
        functional:true, // 函数式组件 // data={attrs:{}}
        render(h){
            return h('div','test')
        }
    })
    const vm = new Vue({
        el: '#app'
    })

    9,相比Vue2.x,Vue 3有哪些性能方面的提升。

    对于这个问题,我们可以从编译阶段、源码体积和响应式系统三个方面进行回答。

     

    在编译阶段,Vue 3做了如下一些优化:

    • diff算法优化
    • 静态提升
    • 事件监听缓存
    • SSR优化

    diff算法优化

    vue3在diff算法中相比vue2增加了静态标记。关于这个静态标记,其作用是为了会发生变化的地方添加一个flag标记,下次发生变化的时候直接找该地方进行比较,性能得到了进一步的提高。

    image.png
     

    静态提升

    Vue3中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用这样就免去了重复的创建节点,大型应用会受益于这个改动,免去了重复的创建操作,优化了运行时候的内存占用。

    事件监听缓存

    默认情况下绑定事件行为会被视为动态绑定,所以每次都会去追踪它的变化。开启了缓存后,没有了静态标记,也就是说下次diff算法的时候直接使用。 

    SSR优化

    当静态内容大到一定量级时候,会用createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。

    详细内容参考:https://vue3js.cn/interview/vue3/performance.html#%E4%B8%80%E...

    10,vue-router中如何保护路由

    路由保护在应用开发过程中非常重要,几乎每个应用都要做各种路由权限管理,因此相当考察使用者基本功。首先,我们来看一些常见的路由保护实例:

    全局守卫:

    const router = createRouter({ ... })
    ​
    router.beforeEach((to, from) => {
      // ...
      // 返回 false 以取消导航
      return false
    })

    路由独享守卫:

    const routes = [
      {
        path: '/users/:id',
        component: UserDetails,
        beforeEnter: (to, from) => {
          // reject the navigation
          return false
        },
      },
    ]

    组件内的守卫:

    const UserDetails = {
      template: `...`,
      beforeRouteEnter(to, from) {
        // 在渲染该组件的对应路由被验证前调用
      },
      beforeRouteUpdate(to, from) {
        // 在当前路由改变,但是该组件被复用时调用
      },
      beforeRouteLeave(to, from) {
        // 在导航离开渲染该组件的对应路由时调用
      },
    }
    • vue-router中保护路由的方法叫做路由守卫,主要用来通过跳转或取消的方式守卫导航。
    • 路由守卫有三个级别:全局、路由独享、组件级。影响范围由大到小,例如全局的router.beforeEach(),可以注册一个全局前置守卫,每次路由导航都会经过这个守卫,因此在其内部可以加入控制逻辑决定用户是否可以导航到目标路由;在路由注册的时候可以加入单路由独享的守卫,例如beforeEnter,守卫只在进入路由时触发,因此只会影响这个路由,控制更精确;我们还可以为路由组件添加守卫配置,例如beforeRouteEnter,会在渲染该组件的对应路由被验证前调用,控制的范围更精确了。
    • 用户的任何导航行为都会走navigate方法,内部有个guards队列按顺序执行用户注册的守卫钩子函数,如果没有通过验证逻辑则会取消原有的导航。

    11,Vue-router 路由钩子在生命周期的体现

    有的时候,需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。 为此有很多种方法可以植入路由的导航过程:全局的,单个路由独享的,或者组件级的。

    1,全局路由勾子

    vue-router全局有三个路由钩子:

    • router.beforeEach 全局前置守卫 进入路由之前
    • router.beforeResolve 全局解析守卫(2.5.0+)在 beforeRouteEnter 调用之后调用
    • router.afterEach 全局后置钩子 进入路由之后

    具体来说,为了实现登录拦截,我们可以这么做:

    1. beforeEach,判断是否登录了,没登录就跳转到登录页;
    2. afterEach,跳转之后滚动条回到顶部;

    2,单个路由独享钩子

    beforeEnter 如果不想全局配置守卫的话,可以为某些路由单独配置守卫,有三个参数∶ to、from、next,代码如下。

    export default [    
        {        
            path: '/',        
            name: 'login',        
            component: login,        
            beforeEnter: (to, from, next) => {          
                console.log('即将进入登录页面')          
                next()        
            }    
        }
    ]

    3, 组件内钩子

    组件内钩子主要有三个:beforeRouteUpdate、beforeRouteEnter、beforeRouteLeave。这三个钩子都有三个参数∶to、from、next。

    • beforeRouteEnter∶ 进入组件前触发
    • beforeRouteUpdate∶ 当前地址改变并且改组件被复用时触发,举例来说,带有动态参数的路径foo/∶id,在 /foo/1 和 /foo/2 之间跳转的时候,由于会渲染同样的foa组件,这个钩子在这种情况下就会被调用
    • beforeRouteLeave∶ 离开组件被调用

    注意点,beforeRouteEnter组件内还访问不到this,因为该守卫执行前组件实例还没有被创建,需要传一个回调给 next来访问,例如:

    beforeRouteEnter(to, from, next) {      
        next(target => {        
            if (from.path == '/classProcess') {          
                target.isFromProcess = true        
            }      
        })    
    }

    以下是触发钩子函数的完整顺序:路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件。

    • beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
    • beforeEach:路由全局前置守卫,可用于登录验证、全局路由loading等。
    • beforeEnter:路由独享守卫
    • beforeRouteEnter:路由组件的组件进入路由前钩子。
    • beforeResolve:路由全局解析守卫
    • afterEach:路由全局后置钩子
    • beforeCreate:组件生命周期,不能访问tAis。
    • created;组件生命周期,可以访问tAis,不能访问dom。
    • beforeMount:组件生命周期
    • deactivated:离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
    • mounted:访问/操作dom。
    • activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
    • 执行beforeRouteEnter回调函数next。

    12,简单介绍下Vue的Tree shaking

    Tree shaking 是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫 Dead code elimination。简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码。

    在Vue2中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到。而Vue3源码引入tree shaking特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中。

    Tree shaking是基于ES6模板语法(import与exports),主要是借助ES6模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量。Tree shaking无非就是做了两件事:

    • 编译阶段利用ES6 Module判断哪些模块已经加载;
    • 判断那些模块和变量未被使用或者引用,进而删除对应代码;


沪ICP备19023445号-2号
友情链接