前端面试题(六)-- Vue

知识点:

1、组件通信

父子组件通信

绝大部分Vue本身提供的通信方式,都是父子组件通信。

1)prop:最常见的组件通信方式之一,由父组件传递到子组件

2)event:最常见的组件通信方式之一,当子组件发生了某些事,可以通过event通知父组件

3)style和class:父组件可以向子组件传递style和class,它们会合并到子组件的根元素中

父组件
<template>
    <div>
        <HelloWorld
            style = "color:red"
            class = "hello"
            msg = "Welcome to Your Vue.js App"
        />
    </div>
</template>
<script>
    import HelloWorld from "./components/HelloWorld.vue";

    export default{
        componnets:{
            HelloWorld,
    },
};
</script>
子组件
<template>
    <div class="world" style="text-align:center">
        <h1>{
   
   {msg}}</h1>
    </div>
</template>
<script>
    export default{
        name:"HelloWorld",
        props:{
            msg:String,
        },
    };
</script>
渲染结果:
<div id="app">
    <div class="hello world" style="color:red;text-align:center">
        <h1>Welcome to Your Vue.js</h1>
    </div>
</div>

4)attribute:如果父组件传递了一些属性到子组件,但子组件并没有声明这些属性,则它们称之为attribute,这些属性会直接附着在子组件的根元素上;不包括style和class,它们会被特殊处理

父组件
<template>
    <div id="app">
    //除msg外,其余均为attribute
        <HelloWorld
            data-a="1"
            data-b="2"
            msg="Welcome to Your Vue.js App"
        />
   </div>
</template>
<script>
    import HelloWorld from "./components/HelloWorld.vue";

    export default{
        componnets:{
            HelloWorld,
        },
    };
</script>
子组件
<template>
    <div>
        <h1>{
   
   {msg}}</h1>
    </div>
</template>
<script>
    export default{
        name:"HelloWorld",
        props:{
            msg:String,
        },
        created(){
            console.log(this.$attrs);
            //得到{"data-a":"1","data-b":"2"}
        }
    }

</script>
渲染结果
<div id="app">
    <div data-a="1" data-b="2">
        <h1>Welcome to Your Vue.js App</h1>
    </div>
</div>

子组件可以通过inheritAttrs:false配置,禁止将attribute附着在子组件的根元素上,但不影响通过$attrs获取。

5)native修饰符:在注册事件时,父组件可以使用native修饰符,将事件注册到子组件的根元素上

<template>
    <div id="app">
        <HelloWorld @click.native="handleClick"/>
    </div>
</template>

<script>
    import HelloWorld from "./components/HelloWorld.vue";

    export default{
        components:{
            HelloWorld,
        },
        methods:{
            handleClick(){
                console.log(1);
            },
        },
    };
</script>
子组件
<template>
    <div>
        <h1>hello world</h1>
    </div>
</template>
渲染结果
<div id="app">
//点击该div,会输出1
    <div>
        <h1>Hello World</h1>
    </div>
/div>

6)$listeners:子组件可以通过$listeners获取父组件传递过来的所有事件处理函数

7)v-model

8)sync修饰符:和v-model的作用类似,用于双向绑定,不同点在于v-model只能针对一个数据进行双向绑定,而sync修饰符没有限制。

子组件
<template>
    <div>
        <p>
            <button @click="$emit(`update:num1`,num1-1)">-<button>
            {
   
   {num1}}
            <button @click="$emit(`update:num1`,num1+1)">+</button>
        </p>
        <p>
            <button @click="$emit(`update:num2`,num2-1)">-<button>
            {
   
   {num2}}
            <button @click="$emit(`update:num2`,num2+1)">+</button>
        </p>
    </div>
</template>
<script>
    export default{
        props:["num1","num2"],
    };
</script>
父组件
<template>
    <div>
        <Number :num1="n1" :num2="n2" @update:num1="n1 = $event"/>
    </div>
</template>
<script>
    import Number from "./components/Numbers";
    export default{
        components:{
            Numbers,
        },
        data(){
            return{n1:0,n2:3};
        },
    }
</script>

使用sync修饰符,代码更加简洁:

父组件
<template>
    <div>
        <Number :num1.sync="n1" :num2.sync="n2"/>
    </div>
</template>
<script>
    import Number from "./components/Numbers";
    export default{
        components:{
            Numbers,
        },
        data(){
            return{n1:0,n2:3};
        },
    }
</script>

9)$children 、$parent:在组件内部,可以通过$parent和$children属性,分别得到当前组件的父组件和子组件实例,注意是在mounted的时候才能挂载出来。

mounted(){
    console.log(this.$children)
}

10)$slots和$scopedSlots

11)ref:父组件可以通过ref获取到子组件的实例

跨组件通信

1)Provide和Inject:兄弟元素不可以使用,后代元素可以使用;耦合度较高。

//父组件提供'foo'
 export default {
    provide:{
        foo:'bar'
    }
}
//后代组件注入'foo'
export default {
    inject:['foo'],
    created(){
        console.log(this.foo)//=>"bar"
    }
}

2)router:如果一个组件改变了地址栏,所有监听地址栏的组件都会做出相应反应,最常见的场景就是通过点击router-link组件改变了地址,router-view组件就渲染其他内容

3)vuex:适用于大型项目的数据仓库,主要解决大中型复杂项目的数据共享问题,包括state、actions、mutations、getters和modules5个要素;主要流程:组件通过dispatch一个actions,actions是异步操作,在actions中通过commit一个mutations,mutations再通过逻辑操作改变state,从而同步到组件,更新其数据状态;而getter相当于组件的计算属性,它是对组件中获取到的数据做提前处理。

4)store模式:适用于中小型项目的数据仓库

//store.js
const store = {
    loginUser:...,
    setting:...
}
//compA
import store from './store.js'
const compA = {
    data(){
        return{
            loginUser:store.loginUser
        }
    }
}
//compB
import store from './store.js'
const compB = {
    data(){
        return{
            setting:store.setting
        }
    }
}

5)eventbus:全局事件总线,组件通知事件总线发生了某件事,事件总线通知其他监听该事件的所有组件运行某个函数

2、虚拟dom

1)什么是虚拟dom:虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构,在vue中,每个组件都有一个render函数,每个render函数都会返回一个虚拟dom树,这也就意味着每个组件都对应一棵虚拟dom树;

2)为什么需要虚拟dom:在vue中,渲染视图会调用render函数,这种渲染不仅发生在组件创建时,同时发生在视图依赖的数据更新时。如果在渲染时,直接使用真实dom,由于真实dom的创建、更新、插入等操作会带来大量的性能损损耗,从而就会极大的降低渲染效率;因此,vue在渲染时,使用虚拟dom来替代真实dom,主要为解决渲染效率的问题。

3)虚拟dom是如何转换成真实dom的?

在一个组件实例首次被渲染时,它先生成虚拟dom树,然后根据虚拟dom树创建真实dom,并把真实dom挂载到页面中合适的位置,此时,每个虚拟dom便会对应一个真实的dom。如果一个组件受响应式数据变化的影响,需要重新渲染时,它仍然会重新调用render函数,创建出一个新的虚拟dom树,用新树和旧树对比,通过对比,vue会找到最小更新量,然后更新必要的虚拟dom节点,最后,这些更新过的虚拟节点,会去修改它们对应的真实dom;这样一来,就保证了对真实dom达到最小的改动。

4)模板和虚拟dom的关系:vue框架中有一个compile模块,它主要负责将模板转换成render函数,而render函数调用后将得到虚拟dom。编译的过程分两步:1将模块字符串转换成AST(抽象语法树);2将AST转换为render函数;如果使用传统的引入方式,则编译时间发生在组件第一次加载时,这称之为运行时编译。如果实在vue-cli的默认配置下,编译发生在打包时,这称之为模板预编译;编译是一个极其耗费性能的操作,预编译可以有效的提高运行时的性能,而且,由于运行的时候已不需要编译,vue-cli在打包时会排除掉vue中的compile模块,以减少打包体积。vue最终运行的时候,最终需要的是render函数,而不是模板,因此,模板中的各种语法,在虚拟dom中都是不存在的,它们都会变成虚拟dom的配置。 

3、v-model

v-model既可以用作表单元素,又可以作用于自定义组件,无论是哪一种情况,它都是一个语法糖,最终会生成一个属性和一个事件;

当其作用于表单元素时,vue会根据作用的表单元素类型而生成合适的属性和事件。例如,作用于普通文本框的时候,它会生成value属性和input事件,而当其作用于单选框或多选框时,它会生成checked属性和change事件。v-model也可以作用于自定义组件,当其作用于自定义组件时,默认情况下,它会生成一个value属性和input事件。

<Comp v-model="data"/>
等效于
<Comp :value="data" @input="data=$event"/>

4、vue2响应式原理

响应式数据的最终目标,是当对象本身或对象属性发生变化时,将会运行一些函数,最常见的就是render函数。在具体实现上,vue用到了几个核心部件:

1)Observer

Observer要实现的目标非常简单,就是把一个普通的对象转换成响应式的对象。

为了实现这一点,Observer把对象的每个属性通过Object.defineProperty转换为带有getter和setter的属性,这样一来,当访问或设置属性时,vue就有机会做一些别的事情。

Observer是vue内部的构造器,我们可以通过Vue提供的静态方法,Vue.observable(object)间接的使用该功能。在组件声明周期中,这件事发生在beforeCreate之后,created之前。具体实现上,它会递归遍历对象的所有属性,以完成深度的属性转换。由于遍历时只能遍历到对象的当前属性,因此无法监测到将来动态增加或删除的属性,因此vue提供了$set和$delete两个实例方法,让开发者通过这两个实例方法对已有响应式对象添加或删除属性。对于数组,vue会更改它的隐式原型,之所以这样做,是因为vue需要监听那些可能改变数组内容的方法;总之,Observer的目标就是要让一个对象,它的属性的读取、赋值,内部数组的变化都要能够被vue感知到。

2)Dep

这里有两个问题没解决,就是读取属性时要做什么事,而属性变化时要做什么事,这个问题需要依靠Dep来解决。Dep的含义就是dependency,表示依赖的意思。

vue会为响应式对象中的每个属性、对象本身、数组本身创建一个Dep实例,每个Dep实例都有能力做以下两件事:

记录依赖(dep.depend()):是谁在用我;

派发更新(dep.notify()):我变了,我要通知那些用到我的人。

当读取响应式对象的某个属性时,它会进行依赖收集:有人用到我了

当改变某个属性时,它会派发更新,那些用我的人挺好了,我变了

3)Watcher

这里又出现一个问题,就是Dep如何知道是谁在用我?

要解决这个问题,需要依靠另一个东西就是Watcher。

当某个函数执行的过程中,用到了响应式数据,响应式数据是无法知道是哪个函数在用自己的;因此,vue通过一种巧妙的方法来解决这个问题;我们不要直接执行函数,而是把函数交给一个叫做watcher的东西去执行,watcher是一个对象,每个这样的函数执行时都应该创建一个watcher,通过watcher去执行;watcher会设置一个全局变量,让全局变量记录当前负责执行的watcher等于自己,然后再去执行函数,在函数的执行过程中,如果发生了依赖记录dep.depend(),那么deo就会把这个全局变量记录下来,表示,有一个watcher用到了我这个属性,当dep进行派发更新时,它会通知之前记录的所有watcher:我变了。

每一个vue组件实例,都至少对应一个watcher,该watcher中记录了该组件的render函数;

watcher首先会把render函数运行一次以收集依赖,于是那些在render中用到的响应式数据就会记录这个watcher;当数据变化时,dep就会通知该watcher,而watcher将重新运行render函数,从而让界面重新渲染同时重新记录当前的依赖。 

4)Scheduler(调度器)

现在还剩下最后一个问题,就是dep通知watcher之后,如果watcher执行重运行对应的函数,就有可能导致函数频繁运行,从而导致效率低下;试想,如果一个交给watcher的函数,它里面用到了属性a\b\c\d,那么a\b\c\d属性都会记录依赖,于是代码将触发4次更新;这样显然是不合适的,因此,watcher收到派发更新的通知,实际上不是立即执行对应的函数,而是把自己交给一个叫调度器的东西,调度器维护一个执行队列,该队列同一个watcher仅会存在一次,队列中的watcher不是立即执行,它会通过一个叫做nectTick的工具方法,把这些需要执行的watcher放入到事件循环的微队列中,nextTick的具体做法是通过Promise完成的,nextTick通过thid.$nextTick暴露给开发者,也就是说,当响应式数据变化时,render函数的执行是异步的,并且在微队列中。

5、vue内置组件Transition

Transition组件会监控slot中唯一根元素的出现和消失,并会在其出现和消失时应用过渡效果;具体的监听内容是:

1)它会对新旧两个虚拟节点进行对比,如果旧节点被销毁,则应用消失效果,如果新节点是新增的,则应用进入效果

2)如果不是上述情况,则它会对比新旧节点,观察其v-show是否变化,true->false应用消失效果,false->true应用进入效果

类名规则:

1)如果transition上没有定义name,则类名为v-xxx

2)如果transition上定义了name,则类名为${name}-xxxx

3)如果指定了类名,直接使用指定的类名

面试真题:

1、简述vue中的组件通信

1)子组件使用props来接收数据和通过$emit来触发父组件的自定义事件;

2)兄弟组件建立一个公共组件bus.js,传递方式通过事件触发bus.$emit,接收方通过在mounted时触发bus.$on;

3)使用vuex跨组件传值:状态管理工具,存储应用所有组件的状态

        state:vuex的基本数据,用来存储变量

        mutations:提交更改数据,同步更新状态

        actions:提交mutations,可异步执行

        getters:是store的计算属性

        modules:模块,每个模块里有四个属性

4)父孙传值:$attrs(向下)$listeners(向下)

5)祖先和子孙传值:Provide和Inject

6)获取父元素或子组件实例:$parent、$children

7)storage:可以利用sessionStorage、localStorage进行setItem、getItem传值

2、v-model的原理

v-model就是一个语法糖,它相当于是一个value属性和input事件的结合体。

3、vue的响应式原理

把数据里面的属性通过Object.defineProperty装换为带有getter\setter的属性

4、请阐述vue的diff算法

当组件创建和更新时,vue均会执行内部的update函数,该函数在内部调用render函数生成虚拟dom树,组件会指向新树,然后vue将新旧两树进行对比,找到差异点,最终更新到真实dom,对比差异的过程叫diff,vue在内部通过一个叫patch的函数完成该过程。

在对比时,vue采用深度优先,逐层比较的方式进行对比;在判断两个节点是否相同时,vue是通过虚拟节点的key和tag来进行判断的;

具体来说,首先对根节点进行对比,如果相同则将旧节点关联的真实dom的引用挂到新节点上,然后根据需要更新属性到真实dom,然后再对比其子节点数组;如果不相同,则按照新节点的信息递归创建所有真实dom,同事挂到对应虚拟节点上,然后移除掉旧的dom;

在对比其子节点数组时,vue对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比,这样做的目的是尽量复用真实dom,尽量少的销毁和创建真实dom。如果发现相同,则进入和根节点一样的对比流程,如果发现不同,则移动真实dom到合适的位置。

这样一直递归的遍历下去,直到整棵树完成对比。

特点:

1)比较只会在同层级进行,不会跨层级比较;2)在diff比较的过程中,循环从两边向中间收拢。

5、new Vue之后发生了什么?

1)首先做一些初始化的操作,主要是设置一些私有属性到实例中

2)运行生命周期钩子函数beforeCreate

3)进入注入流程:处理属性、computed、methods、data、provide、inject,最后使用代理模式将他们挂在到实例中

4)运行生命周期函数created

5)生成render函数:如果有配置,直接使用配置的render,如果没有,使用运行时编译器,把模板编译为render

6)运行生命周期钩子函数beforeMount

7)创建一个Watcher,传入一个函数updateComponnet,该函数会运行render,把得到的vnode再传入_update函数执行

在执行render函数的过程中,会收集所有依赖,将来依赖变化时会重新运行updateComponent函数,在执行_update函数的过程中,触发patch函数,由于目前没有旧树,因此直接为当前的虚拟dom树的每一个普通节点生成elm属性,即真实dom;如果遇到创建一个组件的vnode,则会进入组件实例化流程,该流程和创建vue实例流程基本相同,最终会把创建好的组件实例挂载vonde的componentInstance属性中,以便复用。

8)运行生命周期钩子函数mounted

6、vue中数据改变的时候发生了什么?

1)数据变化之后,所有依赖该数据的Watcher均会重新运行,这里仅考虑updateComponent函数对应的Watcher

2)wacther会被调度器放到nextTick中运行,也就是微队列中,这样是为了避免多个依赖的数据同时改变后被多次执行

3)运行生命周期钩子函数beforeUpdate

4)updateCoponent函数重新执行

在执行render函数的过程中,会去掉之前的依赖,重新收集所有依赖,将来依赖变化时会重新运行updateComponent函数;在执行_update函数的过程中,触发patch函数,新旧两棵树进行对比,普通html节点的对比会导致组件被创建、删除、移动

更新,当新组建需要创建时,进入实例化流程,当旧组件需要删除时,会调用旧组件的$destroy方法删除组件,该方法会先触发生命周期钩子函数beforeDestroy,然后递归调用子组件的$destroy方法,然后触发生命周期钩子函数destroyed,当组件属性更新时,相当于组件的updateComponent函数被重新触发执行,进入重渲染流程,和本节相同

5)运行生命周期钩子函数updated。

7、computed和methods有什么区别?

回答一:

1)在使用时,computed当做属性使用,而methods则当做方法调用

2)computed可以具有getter和setter,因此可以赋值,而methods不行

3)computed无法接收多个参数,而methods可以

4)computed具有缓存,而methods没有

回答二:

vue对methods的处理比较简单,只需要遍历methods配置中的每个属性,将其对应的函数使用bind绑定当前组件实例后复制其引用到组件实例中即可;

而vue对computed的处理会稍微复杂一些,当组件实例触发生命周期函数beforedCreate后,它会做一系列事件,其中就包括对computed的处理,它会遍历computed配置中的所有属性,为每一个属性创建一个Watcher对象,并传入一个函数,该函数的本质其实就是computed配置中的getter,这样一来,getter运行过程中就会收集依赖;但是和渲染函数不同,为计算属性创建的Watcher不会立即执行,因为要考虑到该计算属性是否会被渲染函数使用,如果没有使用,就不会得到执行。因此,在创建Watcher的时候,它使用了lazy配置,lazy配置可以让Watcher不会立即执行,受到lazy的影响,Watcher内部会保存两个关键属性来实现缓存,一个是value,一个是dirty;value属性用于保存Watcher运行的结果,受到lazy的影响,该值在最开始是undefined;

dirty属性用于指示当前的value是否已经过时了,即是否为脏值,受到lazy的影响,该值在最开始是true,Watcher创建好后,vue会使用代理模式,将计算属性挂载到组件实例中,当读取计算属性市,vue检查其对应的Watcher是否是脏值,如果是,则运行函数,计算依赖,并得到对应的值,保存在Watcher的value中,然后设置dirty为false,然后返回。

vue代理属性
Object.defineProperty(vm,"fullName",{
    get(){
        //运行watcher
    }
})

巧妙的是,在依赖收集时,被依赖的数据不仅会收集到计算属性的Watcher,还会收集到组件的Watcher,当计算属性的依赖变化时,会先触发Watcher的执行,此时,它只需设置dirty为true即可,不做任何处理。由于依赖同时会收集到组件的Watcher,因此组件会重新渲染,而重新渲染又读取到了计算属性,由于计算属性目前已为dirty,因此会重新运行getter进行运算,而对于计算属性的setter,则极其简单,当设置计算属性时,直接运行setter即可。

8、插槽

作用域插槽:如果父组件想要用子组件的信息,可以将子组件的信息通过v-bind进行绑定,在父组件中通过v-slot进行获取使用;

$slots:用于访问父组件传递的普通插槽中的vnode

$scopedSlots:用于访问父组件传递的所有用于生成vnode的函数(包括默认插槽在内)

9、vue最大特点、核心是什么?

vue最大特点我感觉就是“组件化”和“数据驱动”,组件化就是可以将页面和页面中可复用的元素都看做组件,写页面的过程,就是写组件,然后页面是由这些组件“拼接”起来的组件树;

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

10、v-if和v-show的区别?

v-if和v-show都可以显示和隐藏一个元素,但有本质区别v-if是惰性的,只是值为false就不会加载对应元素,为true才动态加载对应元素;v-show是无论true或false都会加载对应html代码,但为false时display:none,为true时display:block显示其效果,适用场景:切换频繁的使用v-show,切换不频繁的场合使用v-if.

11、vue和jquery的区别?

jquery主要是玩dom操作的“神器”,强大的选择器,封装了好多好用的dom操作方法和如何获取ajax方法,例如$.ajax()非常好用;vue主要用于数据驱动和组件化,很少操作dom,当然vue可能通过ref来选择一个dom或组件。

12、vue的动态侧边栏路由如何实现?

在做上个项目的时候,我记得后端当时给我返回了一个token值,然后我拿着token值到导航守卫里面去请求了一个动态路由表,因为后端返回的动态路由表是一个路由字符串,我这边没法解析,然后我当时递归遍历了一下,相当于把里面的字符串类型的组件地址变成了箭头函数方式引入的,因为有子路由,所以只能递归处理;其实最后就是把后端返回的路由字符串变成了一个路由对象,最后通过路由的一个addRouter方法,来添加到基础路由表里面去,这样渲染出来的路由就是一个动态的,可以被控制的。Tips:如果问到了.beforeEach里面怎么配置的,或者是导航守卫里面怎么配置、每次请求路由表递归遍历性能不好等问题:其实当时我还让那个后端提供了一个查询角色信息的接口,导航守卫里面如果有token值了,回去判断有没有角色,有角色了正常走next(),没有的话,请求角色,顺便拿着角色请求的侧边栏路由表;那么下次尽量判断,就不走请求路由表了。

13、父子组件生命周期执行顺序?

父组件beforeCreate->父created->父beforemount->子beforeCreate->子created->子beforemount->子mounted->父mounted

14、vue路由实现原理?

前端路由实现原理主要通过以下两种技术实现的:

第一种:利用H5的history API实现,主要通过history.pushState和history.replaceState来实现,不同之处在于pushState会增加一条新的历史记录,而replaceState会替换当前的历史记录。

第二种:利用url的hash实现,我们经常在url中看到#,这个#有两种情况,一个是我们所谓的锚点,路由里的#不叫锚点,我们称之为hash,我们说的就是hash,主要利用监听哈希值的变化来触发事件,hashchange事件来做页面布局更新;

总结:hash方案兼容性好,而H5的history主要针对高级浏览器。

15、项目中vue按钮权限是如何进行控制的?

当时我的项目中创建了一个directives文件夹,在这里面通过vue.directive创建了一个控制按钮是否显示的指令,其实就是登录的时候,后端会通过接口返回一个当前用户具有的所有按钮的权限标识数组,然后我这边把自定义指令中每一个按钮上传到当前按钮的权限标识,根据指令的inserted钩子函数里面来判断这个权限标识是否在后端返回的标识数组中,如果在就操作按钮正常展示,如果不在就隐藏,因为这个钩子函数默认传入了当前元素el.binding VNode这些东西,就可以直接操作。

16、vue中为什么data必须是一个函数?

因为一个组件是可以复用的,但是他们的data是私有的,如果直接返回一个对象,在组件复用时,一个组件修改内容,其他组件也会受到影响;而我们用函数的方式返回,在组件复用时,返回的就是一个新的data对象,在堆内存里面指向的就是不同的空间地址,就不会影响。

17、项目在登录的过程,存储了哪些用户信息,以及在什么位置进行了存储?

1)首先登录,登录成功后,后端会返回给我们一个token和我们其他的路由、菜单、权限数据;

2)把这些数据存储到vuex里面;

3)在axios拦截器里的config里配置token,保证需要登录验证的接口能拿到token ;

4)当在beforeEach的时候使用addroutes进行一个动态路由的加载,并且做404页面的配置;

5)菜单在进入首页的时候通过获取vuex的菜单数据,渲染菜单。

18、路由懒加载实现原理和解决了什么问题?

我认为无论是路由懒加载还是图片懒加载,就是优先展示我们能看到的区域,还有就是Vue是单页面应用,可能会有很多路由的引入,这样使用webpack打包后的文件很大,当进入首页时,加载的资源过多,页面就会出现白屏的情况,不利于用户体验。如果我们能把不同路由对应的组件分割成不同代码块,然后当路由被访问的时候才加载对应的组件,这样就更加高效了。这样会大大提高首屏显示的速度,但是可能其他页面的速度会降下来。

19、vue2和vue3的区别?

1)ES6原生更快的proxy;

2)优化diff算法:vue2:虚拟dom是进行全量的对比;vue3:新增了PatchFlag(静态标记),只对比带有PF的节点;

3)静态的提升:vue2:无论元素是否参与更新,每次都会被重新创建然后再进行渲染;vue3:对于不参与更新的元素只做静态的提升,只会被创建一次,在渲染的时候直接复用即可;

4)API:vue2的组件内部都是options api风格,也就是在data、methods、mounted等来组织代码,这样会让逻辑很分散,每次变动需要反复查找位置;vue3中使用setup,逻辑都放在里面,composition API;

5)碎片化节点的优化:vue2中,template下只允许存在一个根节点,vue3中可以有多个根节点,为我们创建一个虚拟的Fragment节点;

6)更好的ts支持

7)SSR渲染

8)生命周期的变化:vue2里面的beforeCreate和create换成了setup,初始化加载顺序:

setup->beforeCreate->created->onBeforeMount->onMounted

20、如何解决给对象添加属性,视图不更新的问题?

由于ES5的限制,Vue.js不能检测到对象属性的添加或删除,假如你给一个对象直接添加属性,你会发现属性是添加成功了,但是在这个视图上不会更新,因为Vue.js在初始化实例时将属性转为getter\setter,所以属性必须在data对象上才能让vue.js转换它,才能让它是响应的。正确写法是this.$set(this.data,"key,value"),可以发现vue.set()和this.$set()这两个api的实现原理基本一样,都是使用了set函数,set函数是从../observer/index文件中导出的。区别在于Vue.set()是将set函数绑定在vue构造函数上,this.$set()是将set函数绑定在vue原型上。

原理:每个组件实例都有相应的watcher实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而致使他关联的组件得以更新。

21、created和mounted发起请求有什么区别?

created和mounted中发起ajax请求是一样的,没有什么区别。created和mounted是在同一个tick中执行的,而ajax请求的时间一定会超过一个tick,所以即便ajax的请求耗时是0ms,那么也是在nexttick中更新数据到dom中;

区别在于created的时候,视图中的html并没有渲染出来,所以此时如果直接去操作html中的dom节点,一定找不到相关的元素,而在mounted中,由于此时已经渲染出来了,所以可以直接操作dom节点。

22、这个项目从立项到上线的整个流程

开一个需求会然后排期,排期分为写页面的时间和后端联调接口的时间段,然后制定接口参数,排期后后端去开发接口,前端开发页面,最后联调,然后上测试环境测试,测试完之后没有问题上服务器。

23、简历中重构项目包括哪些内容?

重构主要是将样式以及技术栈进行调整,以前项目里面用的技术比较混乱,维护起来不方便,然后就统一把框架换成了vue2,也进行了一些优化,例如:路由懒加载,搜索框节流,按钮防抖。

24、说一下对于$nexttick的理解

我们放在$nexttick当中的操作不会立即执行,而是等数据更新、dom更新完成之后再执行,这样我们拿到的肯定就是最新的了;vue实现响应式并不是在数据改变后就立即更新dom,而是在一次事件循环的所有数据变化后再异步执行dom更新,这就涉及了事件循环机制,这个东西我以前研究过,是js的一个底层运行原理,js是单线程的,但是也有一些耗时的任务,会影响执行效率,代码都在主线程中执行,当你遇见ajax请求,setTimeout定时器时候,会单独开启异步线程,异步线程耗时之后会推入异步队列中等候执行,然后当主线程执行完毕之后,回到异步队列中取出到主线程中执行。

25、route和router的区别

this.route:当前激活的路由信息对象,每个对象都是局部的,可以获取当前路由的path、name、parames、query等属性;

this.router:全局的router实例,通过vue根实例注入router实例,然后再注入到每个子组件,让整个应用都有路由功能。其中包含了很多属性和对象,比如history对象,任何页面也都可以调用其push()、replace()、go()等方法。

26、正向代理

正向代理是一个位于客户端和目标服务器之间的服务器,取名为代理服务器,为了从目标服务器取得内容,客户端向代理服务器发送一个请求并指定目标,然后代理服务器向目标服务器转交请求并将获得的尼日尔用返回给客户端。这种代理其实在生活中是比较常见的,比如访问外国网站技术,启用到的就是代理技术。有时候,用户想要访问某国外网站,该网站无法在国内直接访问,但是我们可以访问到一个代理服务器,这个代理服务器可以访问到国外的网站。

因此,正向代理其实是“代理服务器”代理了“客户端”,去和“目标服务器”进行交互;

好处:突破访问限制,隐藏客户端真实IP。

27、vue开发环境和线上环境如何切换?

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

28、如何实现跨域?

跨域是浏览器做出的限制,和服务器没关系;

1)使用jsonp,实现原理:主要是利用动态创建script标签请求后端接口地址,然后传递callback参数,后端接收callback,经过数据处理,返回callback函数调用的形式,callback中的参数就是json;

2)代理:跨域前端和后端都可以实现,如果只针对vue,vue本身可以通过代理的方式实现,具体就是在vue.config.js中配置proxy,里面有一个target属性指向跨域链接,修改完重启就可以了,实际上是启动了一个代理服务器,绕开同源策略,在请求的时候通过代理服务器获取到数据再转给浏览器;

3)CORS :CORS全称叫跨域资源共享,主要是后台工程师设置后端代码来实现

4)nginx反向代理配置

注:现在主流框架都是代理和CORS跨域来实现的。

29、methods、computed、watch的区别

methods中都是封装好的函数,无论是否有变化只要触发就会执行;computed是vue独有的特征计算属性,可以对data中的依赖项再重新计算,得到一个新值,应用到视图中,和methods的本质区别是computed是可缓存的,也就是说computed中的依赖项没有变化,则computed中的值就不会重新计算,而methods中的函数是没有缓存的;watch是监听data和计算属性中的新旧变化。

30、vuex如何实现数据持久化(刷新后数据还保留着)?

因为vuex中的state是存储在内存中的,一刷新就没了,例如登录状态,解决方案有:

第一种:利用h5的本地存储(localStorage、sessionStorage);

第二种:利用第三方封装好的插件,例如:vuex-persistedstate

31、移动端如何解决适配问题?

前端适配没有最好的方法,只有最合适的方法,目前前端主要做适配的方法有:百分比、em/rem、媒体查询、flex布局、vw\vh等,我在项目中用的最多的是rem、flex,有时会用到媒体查询,在做pc端响应式布局时主要是用了一个手淘的js库flexble.js,在页面变化时,检测页面宽度,除以10份,动态赋值给font-size属性;而页面布局我是通过rem来进行布局,所以就可以适配所有移动端的设备了。

32、echarts大屏适配如何实现

通过js计算监测页面的宽度,然后用css3里面的scale来进行对应比例的一个缩放来适配的

33、可视化项目-地图定点

使用过高德地图,在他那个里面申请一个key,这个key相当于一个用他们接口的标识,用的时候参照他们的官网,再从CSDN、掘金找一些对应的文章,生成地图的时候,里面会有一些覆盖物包括center这些点之类的,可以选半径。

34、自己搭建过项目吗?做过什么?

基于vue的脚手架进行搭建过,当时在里面封装了一些请求之类的文件,配置了一些公共的拦截器,过滤器等。

35、promise await理解-异同点

他们相当于是异步变成的一种方式,能够实现异步的编程,我们是把promise和await、async结合起来使用的,await后面就跟一个promise实例;我觉得async、await的好处就是相当于它代码看起来是一种同步的方式,他在里面的时候就是在下面直接可以通过,比如,上一行const result = await后面跟一个promise实例,下边就能接着用这个result了,这样的话代码更清晰,而且不用回调函数的形式,操作起来更容易理解,不容易出bug。

36、防抖节流的区别

防抖:在一定的时间内,如果频繁的触发的话,防抖会重新计时;

节流:相当于只去执行一次,其实就是用闭包的形式实现的,在里面可以把闭包清空,再重新进行一个setTimeout计时。

猜你喜欢

转载自blog.csdn.net/qinqinzqq/article/details/129097337